frame-core 0.3.0

Component model, lifecycle, process isolation — hosts components as supervised BEAM process trees
Documentation
//! Read-only authority queries over the registry's records.
//!
//! Split from `registry.rs` (the 2026-07-23 module-size rider): the
//! lifecycle operations stay in the parent module; the snapshot and status
//! query surface lives here. Pure moves — behavior is byte-identical.

use crate::action::{ActionIncarnation, ActionKey, ActionRow};
use crate::component::ComponentId;
use crate::error::RegistryError;
use crate::event::LifecycleState;
use crate::status::ComponentStatus;

use super::ComponentRegistry;
use super::support::{snapshot, state};

impl ComponentRegistry {
    /// Returns the authoritative all-components action snapshot: one row per
    /// declared action of every component currently in `Running`, sorted by
    /// [`ActionKey`] canonical byte order (F-6a R1/R3's authority query).
    ///
    /// Per-component atomicity holds by construction: a component's complete
    /// batch rides its registration record, `Running`-ness is read once per
    /// record, and the incarnation ordinal is the one minted by that entry
    /// into `Running` — a snapshot therefore contains ALL of a component's
    /// actions with one incarnation, or none of them.
    ///
    /// # Errors
    ///
    /// Returns a synchronization failure if registry state was poisoned.
    pub fn action_snapshot(&self) -> Result<Vec<ActionRow>, RegistryError> {
        let records = self
            .records
            .lock()
            .map_err(|_| RegistryError::SynchronizationPoisoned)?;
        let mut rows = Vec::new();
        for record in records.values() {
            if state(record)? != LifecycleState::Running {
                continue;
            }
            let incarnation = ActionIncarnation::new(
                record
                    .incarnation
                    .load(std::sync::atomic::Ordering::Acquire),
            );
            for declaration in &record.meta.actions {
                rows.push(ActionRow {
                    key: ActionKey {
                        component_id: record.meta.id,
                        action_id: declaration.id.clone(),
                    },
                    component_name: record.meta.name.clone(),
                    description: declaration.description.clone(),
                    request_schema: declaration.request_schema.clone(),
                    response_schema: declaration.response_schema.clone(),
                    requirements: declaration.requirements.clone(),
                    incarnation,
                });
            }
        }
        rows.sort_by(|left, right| left.key.cmp(&right.key));
        Ok(rows)
    }

    /// Returns the current status snapshot, or `None` when unregistered.
    ///
    /// # Errors
    ///
    /// Returns a synchronization failure if registry state was poisoned.
    pub fn status(&self, id: ComponentId) -> Result<Option<ComponentStatus>, RegistryError> {
        self.optional_record(id)?
            .map(|record| snapshot(id, &record))
            .transpose()
    }

    /// Returns the number of registrations, including Failed components.
    ///
    /// # Errors
    ///
    /// Returns a synchronization failure if registry state was poisoned.
    pub fn len(&self) -> Result<usize, RegistryError> {
        self.records
            .lock()
            .map(|records| records.len())
            .map_err(|_| RegistryError::SynchronizationPoisoned)
    }

    /// Returns whether the registry has no component definitions.
    ///
    /// # Errors
    ///
    /// Returns a synchronization failure if registry state was poisoned.
    pub fn is_empty(&self) -> Result<bool, RegistryError> {
        self.len().map(|length| length == 0)
    }
}