Skip to main content

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}