1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::{ErrorCategory, ResourceKey, Revision, ScopeId};
/// Host-observed resource outcome carried by a canonical status input.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HostResourceOutcome {
/// The host has not reported a resource outcome.
Unknown,
/// The resource is live according to the host.
Open,
/// The resource failed outside graph propagation.
Failed(String),
/// The resource is closed according to the host.
Closed,
/// The host cannot apply the requested transition.
Unsupported(String),
}
impl HostResourceOutcome {
/// Returns the model category for host-reported resource status.
pub const fn category(&self) -> ErrorCategory {
ErrorCategory::HostResourceStatus
}
}
/// Canonical host report for a resource command observed outside the graph.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HostResourceStatus<S = HostResourceOutcome> {
/// Stable graph-visible resource identity.
pub resource_key: ResourceKey,
/// Scope associated with the command being reported.
pub scope: ScopeId,
/// Graph revision of the resource command the host is reporting for.
pub command_revision: Revision,
/// Monotonic host observation revision.
pub status_revision: Revision,
/// Application-defined status payload.
pub status: S,
}
impl<S> HostResourceStatus<S> {
/// Creates a host resource status input.
pub fn new(
resource_key: ResourceKey,
scope: ScopeId,
command_revision: Revision,
status_revision: Revision,
status: S,
) -> Self {
Self {
resource_key,
scope,
command_revision,
status_revision,
status,
}
}
/// Returns the model category for host-reported resource status.
pub const fn category(&self) -> ErrorCategory {
ErrorCategory::HostResourceStatus
}
/// Maps the status payload while preserving structural identity.
pub fn map_status<T>(self, map: impl FnOnce(S) -> T) -> HostResourceStatus<T> {
HostResourceStatus {
resource_key: self.resource_key,
scope: self.scope,
command_revision: self.command_revision,
status_revision: self.status_revision,
status: map(self.status),
}
}
}
/// Classification for host status delivery relative to graph command state.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum HostStatusClass {
/// Status matches the current resource/scope/revision.
Current,
/// Status duplicates an already accepted status revision.
Duplicate,
/// Status targets an old command revision.
Stale,
/// Status targets a command revision newer than the graph has observed.
Future,
/// Status targets a resource or scope that is no longer current.
Late,
}
/// Graph command state needed to classify a host resource status.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct HostResourceCommandState {
/// Scope associated with the latest command for this resource.
pub scope: ScopeId,
/// Latest command revision known for this resource.
pub command_revision: Revision,
/// Whether the resource is still live in graph state.
pub resource_is_live: bool,
/// Whether the status scope currently owns the live resource.
pub scope_owns_resource: bool,
}
/// Classifies a host resource status against graph command state.
pub fn classify_host_resource_status(
status: &HostResourceStatus,
state: Option<HostResourceCommandState>,
duplicate: bool,
) -> HostStatusClass {
let Some(state) = state else {
return HostStatusClass::Late;
};
if !state.resource_is_live {
if matches!(status.status, HostResourceOutcome::Closed) && state.scope == status.scope {
return classify_revision(status.command_revision, state.command_revision, duplicate);
}
return HostStatusClass::Late;
}
if !state.scope_owns_resource {
return HostStatusClass::Late;
}
classify_revision(status.command_revision, state.command_revision, duplicate)
}
fn classify_revision(
status_revision: Revision,
command_revision: Revision,
duplicate: bool,
) -> HostStatusClass {
if status_revision < command_revision {
HostStatusClass::Stale
} else if status_revision > command_revision {
HostStatusClass::Future
} else if duplicate {
HostStatusClass::Duplicate
} else {
HostStatusClass::Current
}
}