1pub mod element;
15pub mod id;
16
17pub use element::Component;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ElementSource {
22 BuiltIn,
23 Plugin,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum ElementShape {
29 Component,
31 InlineMarker,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ElementAuthority {
38 SharedOperatorAuthoritative,
40 GranularTrackedWork,
42 DerivedProjection,
44 Archive,
46 Signals,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum ElementWritePolicy {
53 MergeOnly,
55 GranularOnly,
57 ProjectionOnly,
59 ArchiveOnly,
61 SignalReconcile,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum ElementSchedulingRole {
68 None,
69 ActiveQueue,
70 RunnableWorkSource,
71 ParkedWorkSource,
72 ReviewGate,
73 CompletionArchive,
74 Status,
75 Signals,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum ElementRealtimeModel {
81 None,
83 Exchange,
85 TrackedItems,
87 Queue,
89 Status,
91 Archive,
93 Signals,
95 Boundary,
97 Unknown,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum ElementCompositionRole {
104 LocalOnly,
106 Producer,
108 Consumer,
110 Observer,
112 ArchiveTarget,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct ElementDescriptor {
120 pub name: &'static str,
121 pub aliases: &'static [&'static str],
122 pub source: ElementSource,
123 pub shape: ElementShape,
124 pub authority: ElementAuthority,
125 pub write_policy: ElementWritePolicy,
126 pub scheduling_role: ElementSchedulingRole,
127 pub realtime_model: ElementRealtimeModel,
128 pub composition_role: ElementCompositionRole,
129 pub realtime: bool,
132}
133
134impl ElementDescriptor {
135 pub fn matches_name(self, name: &str) -> bool {
136 self.name == name || self.aliases.contains(&name)
137 }
138
139 pub fn canonical_name(self, name: &str) -> Option<&'static str> {
140 self.matches_name(name).then_some(self.name)
141 }
142
143 pub fn as_registration(self) -> ElementRegistration {
144 ElementRegistration {
145 name: self.name.to_string(),
146 aliases: self
147 .aliases
148 .iter()
149 .map(|alias| (*alias).to_string())
150 .collect(),
151 source: self.source,
152 shape: self.shape,
153 authority: self.authority,
154 write_policy: self.write_policy,
155 scheduling_role: self.scheduling_role,
156 realtime_model: self.realtime_model,
157 composition_role: self.composition_role,
158 realtime: self.realtime,
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct ElementRegistration {
166 pub name: String,
167 pub aliases: Vec<String>,
168 pub source: ElementSource,
169 pub shape: ElementShape,
170 pub authority: ElementAuthority,
171 pub write_policy: ElementWritePolicy,
172 pub scheduling_role: ElementSchedulingRole,
173 pub realtime_model: ElementRealtimeModel,
174 pub composition_role: ElementCompositionRole,
175 pub realtime: bool,
176}
177
178impl ElementRegistration {
179 pub fn matches_name(&self, name: &str) -> bool {
180 self.name == name || self.aliases.iter().any(|alias| alias == name)
181 }
182}
183
184pub trait ElementPlugin {
187 fn element_descriptors(&self) -> Vec<ElementRegistration>;
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 const BACKLOG: ElementDescriptor = ElementDescriptor {
195 name: "backlog",
196 aliases: &["pending"],
197 source: ElementSource::BuiltIn,
198 shape: ElementShape::Component,
199 authority: ElementAuthority::GranularTrackedWork,
200 write_policy: ElementWritePolicy::GranularOnly,
201 scheduling_role: ElementSchedulingRole::RunnableWorkSource,
202 realtime_model: ElementRealtimeModel::TrackedItems,
203 composition_role: ElementCompositionRole::Producer,
204 realtime: true,
205 };
206
207 #[test]
208 fn descriptor_matches_aliases_and_canonicalizes() {
209 assert!(BACKLOG.matches_name("backlog"));
210 assert!(BACKLOG.matches_name("pending"));
211 assert_eq!(BACKLOG.canonical_name("pending"), Some("backlog"));
212 assert_eq!(BACKLOG.canonical_name("queue"), None);
213 }
214
215 #[test]
216 fn built_in_descriptor_converts_to_plugin_registration_shape() {
217 let registration = BACKLOG.as_registration();
218 assert_eq!(registration.name, "backlog");
219 assert_eq!(registration.aliases, vec!["pending"]);
220 assert!(registration.matches_name("pending"));
221 }
222
223 #[test]
224 fn manifest_stays_pure() {
225 let manifest = include_str!("../Cargo.toml");
226 for forbidden in [
227 "agent-doc-orchestration",
228 "interprocess",
229 "notify",
230 "rusqlite",
231 "tmux-router",
232 ] {
233 assert!(
234 !manifest.contains(forbidden),
235 "agent-doc-element must stay pure; found forbidden dependency {forbidden}"
236 );
237 }
238 }
239}