frame-core 0.3.0

Component model, lifecycle, process isolation — hosts components as supervised BEAM process trees
Documentation
//! Component fragment declarations and the authoritative fragment snapshot
//! vocabulary (F-5a R1).
//!
//! Shared types live here, beside the action vocabulary, so the server-side
//! fragment authority and every assembly consumer read ONE vocabulary owned
//! by frame-core. Registration is the only door for the fragment ID SET —
//! validated atomically per component, visible in the authoritative snapshot
//! exactly while that component's incarnation is Running (F-5a R2's ingress
//! pin). Fragment CONTENT is live: a Running component swaps one fragment's
//! whole content through the registry's lifecycle-owned
//! [`update_fragment_content`], fenced by the same incarnation.
//!
//! Kind and content carry NO rendering semantics: the authority stores and
//! fences bytes, never interprets them (the fragments design's honesty pin —
//! a `frame:fragments@v1` payload rides these bytes unchanged).
//!
//! [`update_fragment_content`]: crate::registry::ComponentRegistry::update_fragment_content

use serde::{Deserialize, Serialize};

use crate::component::ComponentId;

/// Component-local fragment identity.
///
/// Uniqueness is scoped by [`FragmentKey`]; two components may intentionally
/// declare the same local id without collision. Registration refuses an
/// empty id.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct FragmentId(String);

impl FragmentId {
    /// Wraps a raw id without validating; registration validates every id it
    /// admits, so an unregistered invalid id can never enter the authority.
    #[must_use]
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    /// Returns the id text.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Returns the canonical UTF-8 bytes used for deterministic ordering.
    #[must_use]
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }
}

/// Opaque fragment kind token.
///
/// Registration refuses an empty token; beyond nonemptiness the authority
/// never interprets it — kind vocabulary belongs to wire contracts layered
/// above (F-5a amendment A2), never to this crate.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct FragmentKind(String);

impl FragmentKind {
    /// Wraps a raw kind token without validating; registration refuses an
    /// empty token.
    #[must_use]
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    /// Returns the kind token text.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Signed integer assembly-ordering key (F-5a R4's primary sort key).
#[derive(
    Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize, Default,
)]
pub struct FragmentOrder(i64);

impl FragmentOrder {
    /// Wraps an ordering key.
    #[must_use]
    pub const fn new(value: i64) -> Self {
        Self(value)
    }

    /// Returns the ordering key value.
    #[must_use]
    pub const fn value(self) -> i64 {
        self.0
    }
}

/// One declared component fragment.
///
/// Declared inside [`ComponentMeta::fragments`] and validated at
/// registration: nonempty id and kind, no duplicate local ids, and content
/// no larger than the registry's caller-supplied `max_fragment_bytes` — no
/// hidden default; an unconfigured limit refuses fragment-carrying
/// registration typed. Refusal is atomic: no declaration from that
/// component enters the authority (F-5a R1).
///
/// `content` is the INITIAL content for each entry into Running; empty is
/// valid. The declared fragment ID SET is static per incarnation; content is
/// live through the registry's update operation.
///
/// [`ComponentMeta::fragments`]: crate::component::ComponentMeta::fragments
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FragmentDeclaration {
    /// Component-local identity.
    pub id: FragmentId,
    /// Validated, nonempty opaque kind token.
    pub kind: FragmentKind,
    /// Opaque initial content bytes (empty is valid).
    pub content: Vec<u8>,
    /// Assembly ordering key.
    pub order: FragmentOrder,
}

/// Globally unique fragment identity: component identity plus local id.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct FragmentKey {
    /// The declaring component.
    pub component_id: ComponentId,
    /// The component-local fragment id.
    pub fragment_id: FragmentId,
}

impl FragmentKey {
    /// Canonical ordering bytes: the 32 fixed-width component identity bytes,
    /// then the fragment id's UTF-8 bytes. The fixed component width makes
    /// the concatenation injective (the [`crate::action::ActionKey`]
    /// precedent, F-6a R2).
    #[must_use]
    pub fn canonical_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(32 + self.fragment_id.as_bytes().len());
        bytes.extend_from_slice(self.component_id.as_bytes());
        bytes.extend_from_slice(self.fragment_id.as_bytes());
        bytes
    }
}

impl Ord for FragmentKey {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.component_id
            .as_bytes()
            .cmp(other.component_id.as_bytes())
            .then_with(|| {
                self.fragment_id
                    .as_bytes()
                    .cmp(other.fragment_id.as_bytes())
            })
    }
}

impl PartialOrd for FragmentKey {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

/// Opaque monotonic token for one component entry into Running (F-5a R2).
///
/// Minted by the registry each time a transition into Running commits — the
/// SAME registry-owned ordinal the action surface reads (one lifecycle-owned
/// number; `ActionIncarnation` is its action-surface view). Every fragment
/// install, withdrawal, and content update is conditioned on the matching
/// incarnation: stale work from incarnation N after remove + re-register
/// cannot mutate incarnation N+1 (F-2a's held-vs-current rule).
///
/// The ordinal is also the wire generation source (F-5a amendment A3):
/// strictly higher on each new incarnation of the publishing component and
/// ≥ 1 for every Running incarnation, so the assembler's generation-bump
/// recovery works unchanged — deterministically, with no clock anywhere.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct ComponentIncarnation(u64);

impl ComponentIncarnation {
    /// Wraps a raw incarnation ordinal (registry-minted; test fixtures only).
    #[must_use]
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    /// Returns the ordinal — the A3 wire-generation source (≥ 1 while
    /// Running, strictly higher per re-entry).
    #[must_use]
    pub const fn ordinal(self) -> u64 {
        self.0
    }
}

/// One row of the registry's authoritative fragment snapshot.
///
/// Rows exist only for Running component incarnations and carry the fragment
/// identity, its declared kind and ordering key, the CURRENT content bytes,
/// and the incarnation that owns them — everything assembly needs, never
/// host-facade authority.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FragmentRow {
    /// Globally unique fragment identity.
    pub key: FragmentKey,
    /// Declared opaque kind token.
    pub kind: FragmentKind,
    /// Declared assembly ordering key.
    pub order: FragmentOrder,
    /// Current whole content bytes (initial declaration or the latest
    /// committed update — never a partial write, by the swap's atomicity).
    pub content: Vec<u8>,
    /// The Running incarnation these bytes belong to.
    pub incarnation: ComponentIncarnation,
}