frame_core/registry/query.rs
1//! Read-only authority queries over the registry's records.
2//!
3//! Split from `registry.rs` (the 2026-07-23 module-size rider): the
4//! lifecycle operations stay in the parent module; the snapshot and status
5//! query surface lives here. Pure moves — behavior is byte-identical.
6
7use crate::action::{ActionIncarnation, ActionKey, ActionRow};
8use crate::component::ComponentId;
9use crate::error::RegistryError;
10use crate::event::LifecycleState;
11use crate::status::ComponentStatus;
12
13use super::ComponentRegistry;
14use super::support::{snapshot, state};
15
16impl ComponentRegistry {
17 /// Returns the authoritative all-components action snapshot: one row per
18 /// declared action of every component currently in `Running`, sorted by
19 /// [`ActionKey`] canonical byte order (F-6a R1/R3's authority query).
20 ///
21 /// Per-component atomicity holds by construction: a component's complete
22 /// batch rides its registration record, `Running`-ness is read once per
23 /// record, and the incarnation ordinal is the one minted by that entry
24 /// into `Running` — a snapshot therefore contains ALL of a component's
25 /// actions with one incarnation, or none of them.
26 ///
27 /// # Errors
28 ///
29 /// Returns a synchronization failure if registry state was poisoned.
30 pub fn action_snapshot(&self) -> Result<Vec<ActionRow>, RegistryError> {
31 let records = self
32 .records
33 .lock()
34 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
35 let mut rows = Vec::new();
36 for record in records.values() {
37 if state(record)? != LifecycleState::Running {
38 continue;
39 }
40 let incarnation = ActionIncarnation::new(
41 record
42 .incarnation
43 .load(std::sync::atomic::Ordering::Acquire),
44 );
45 for declaration in &record.meta.actions {
46 rows.push(ActionRow {
47 key: ActionKey {
48 component_id: record.meta.id,
49 action_id: declaration.id.clone(),
50 },
51 component_name: record.meta.name.clone(),
52 description: declaration.description.clone(),
53 request_schema: declaration.request_schema.clone(),
54 response_schema: declaration.response_schema.clone(),
55 requirements: declaration.requirements.clone(),
56 incarnation,
57 });
58 }
59 }
60 rows.sort_by(|left, right| left.key.cmp(&right.key));
61 Ok(rows)
62 }
63
64 /// Returns the current status snapshot, or `None` when unregistered.
65 ///
66 /// # Errors
67 ///
68 /// Returns a synchronization failure if registry state was poisoned.
69 pub fn status(&self, id: ComponentId) -> Result<Option<ComponentStatus>, RegistryError> {
70 self.optional_record(id)?
71 .map(|record| snapshot(id, &record))
72 .transpose()
73 }
74
75 /// Returns the number of registrations, including Failed components.
76 ///
77 /// # Errors
78 ///
79 /// Returns a synchronization failure if registry state was poisoned.
80 pub fn len(&self) -> Result<usize, RegistryError> {
81 self.records
82 .lock()
83 .map(|records| records.len())
84 .map_err(|_| RegistryError::SynchronizationPoisoned)
85 }
86
87 /// Returns whether the registry has no component definitions.
88 ///
89 /// # Errors
90 ///
91 /// Returns a synchronization failure if registry state was poisoned.
92 pub fn is_empty(&self) -> Result<bool, RegistryError> {
93 self.len().map(|length| length == 0)
94 }
95}