Skip to main content

frame_core/
fragment.rs

1//! Component fragment declarations and the authoritative fragment snapshot
2//! vocabulary (F-5a R1).
3//!
4//! Shared types live here, beside the action vocabulary, so the server-side
5//! fragment authority and every assembly consumer read ONE vocabulary owned
6//! by frame-core. Registration is the only door for the fragment ID SET —
7//! validated atomically per component, visible in the authoritative snapshot
8//! exactly while that component's incarnation is Running (F-5a R2's ingress
9//! pin). Fragment CONTENT is live: a Running component swaps one fragment's
10//! whole content through the registry's lifecycle-owned
11//! [`update_fragment_content`], fenced by the same incarnation.
12//!
13//! Kind and content carry NO rendering semantics: the authority stores and
14//! fences bytes, never interprets them (the fragments design's honesty pin —
15//! a `frame:fragments@v1` payload rides these bytes unchanged).
16//!
17//! [`update_fragment_content`]: crate::registry::ComponentRegistry::update_fragment_content
18
19use serde::{Deserialize, Serialize};
20
21use crate::component::ComponentId;
22
23/// Component-local fragment identity.
24///
25/// Uniqueness is scoped by [`FragmentKey`]; two components may intentionally
26/// declare the same local id without collision. Registration refuses an
27/// empty id.
28#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
29pub struct FragmentId(String);
30
31impl FragmentId {
32    /// Wraps a raw id without validating; registration validates every id it
33    /// admits, so an unregistered invalid id can never enter the authority.
34    #[must_use]
35    pub fn new(value: impl Into<String>) -> Self {
36        Self(value.into())
37    }
38
39    /// Returns the id text.
40    #[must_use]
41    pub fn as_str(&self) -> &str {
42        &self.0
43    }
44
45    /// Returns the canonical UTF-8 bytes used for deterministic ordering.
46    #[must_use]
47    pub fn as_bytes(&self) -> &[u8] {
48        self.0.as_bytes()
49    }
50}
51
52/// Opaque fragment kind token.
53///
54/// Registration refuses an empty token; beyond nonemptiness the authority
55/// never interprets it — kind vocabulary belongs to wire contracts layered
56/// above (F-5a amendment A2), never to this crate.
57#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
58pub struct FragmentKind(String);
59
60impl FragmentKind {
61    /// Wraps a raw kind token without validating; registration refuses an
62    /// empty token.
63    #[must_use]
64    pub fn new(value: impl Into<String>) -> Self {
65        Self(value.into())
66    }
67
68    /// Returns the kind token text.
69    #[must_use]
70    pub fn as_str(&self) -> &str {
71        &self.0
72    }
73}
74
75/// Signed integer assembly-ordering key (F-5a R4's primary sort key).
76#[derive(
77    Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize, Default,
78)]
79pub struct FragmentOrder(i64);
80
81impl FragmentOrder {
82    /// Wraps an ordering key.
83    #[must_use]
84    pub const fn new(value: i64) -> Self {
85        Self(value)
86    }
87
88    /// Returns the ordering key value.
89    #[must_use]
90    pub const fn value(self) -> i64 {
91        self.0
92    }
93}
94
95/// One declared component fragment.
96///
97/// Declared inside [`ComponentMeta::fragments`] and validated at
98/// registration: nonempty id and kind, no duplicate local ids, and content
99/// no larger than the registry's caller-supplied `max_fragment_bytes` — no
100/// hidden default; an unconfigured limit refuses fragment-carrying
101/// registration typed. Refusal is atomic: no declaration from that
102/// component enters the authority (F-5a R1).
103///
104/// `content` is the INITIAL content for each entry into Running; empty is
105/// valid. The declared fragment ID SET is static per incarnation; content is
106/// live through the registry's update operation.
107///
108/// [`ComponentMeta::fragments`]: crate::component::ComponentMeta::fragments
109#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
110pub struct FragmentDeclaration {
111    /// Component-local identity.
112    pub id: FragmentId,
113    /// Validated, nonempty opaque kind token.
114    pub kind: FragmentKind,
115    /// Opaque initial content bytes (empty is valid).
116    pub content: Vec<u8>,
117    /// Assembly ordering key.
118    pub order: FragmentOrder,
119}
120
121/// Globally unique fragment identity: component identity plus local id.
122#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
123pub struct FragmentKey {
124    /// The declaring component.
125    pub component_id: ComponentId,
126    /// The component-local fragment id.
127    pub fragment_id: FragmentId,
128}
129
130impl FragmentKey {
131    /// Canonical ordering bytes: the 32 fixed-width component identity bytes,
132    /// then the fragment id's UTF-8 bytes. The fixed component width makes
133    /// the concatenation injective (the [`crate::action::ActionKey`]
134    /// precedent, F-6a R2).
135    #[must_use]
136    pub fn canonical_bytes(&self) -> Vec<u8> {
137        let mut bytes = Vec::with_capacity(32 + self.fragment_id.as_bytes().len());
138        bytes.extend_from_slice(self.component_id.as_bytes());
139        bytes.extend_from_slice(self.fragment_id.as_bytes());
140        bytes
141    }
142}
143
144impl Ord for FragmentKey {
145    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
146        self.component_id
147            .as_bytes()
148            .cmp(other.component_id.as_bytes())
149            .then_with(|| {
150                self.fragment_id
151                    .as_bytes()
152                    .cmp(other.fragment_id.as_bytes())
153            })
154    }
155}
156
157impl PartialOrd for FragmentKey {
158    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
159        Some(self.cmp(other))
160    }
161}
162
163/// Opaque monotonic token for one component entry into Running (F-5a R2).
164///
165/// Minted by the registry each time a transition into Running commits — the
166/// SAME registry-owned ordinal the action surface reads (one lifecycle-owned
167/// number; `ActionIncarnation` is its action-surface view). Every fragment
168/// install, withdrawal, and content update is conditioned on the matching
169/// incarnation: stale work from incarnation N after remove + re-register
170/// cannot mutate incarnation N+1 (F-2a's held-vs-current rule).
171///
172/// The ordinal is also the wire generation source (F-5a amendment A3):
173/// strictly higher on each new incarnation of the publishing component and
174/// ≥ 1 for every Running incarnation, so the assembler's generation-bump
175/// recovery works unchanged — deterministically, with no clock anywhere.
176#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
177pub struct ComponentIncarnation(u64);
178
179impl ComponentIncarnation {
180    /// Wraps a raw incarnation ordinal (registry-minted; test fixtures only).
181    #[must_use]
182    pub const fn new(value: u64) -> Self {
183        Self(value)
184    }
185
186    /// Returns the ordinal — the A3 wire-generation source (≥ 1 while
187    /// Running, strictly higher per re-entry).
188    #[must_use]
189    pub const fn ordinal(self) -> u64 {
190        self.0
191    }
192}
193
194/// One row of the registry's authoritative fragment snapshot.
195///
196/// Rows exist only for Running component incarnations and carry the fragment
197/// identity, its declared kind and ordering key, the CURRENT content bytes,
198/// and the incarnation that owns them — everything assembly needs, never
199/// host-facade authority.
200#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
201pub struct FragmentRow {
202    /// Globally unique fragment identity.
203    pub key: FragmentKey,
204    /// Declared opaque kind token.
205    pub kind: FragmentKind,
206    /// Declared assembly ordering key.
207    pub order: FragmentOrder,
208    /// Current whole content bytes (initial declaration or the latest
209    /// committed update — never a partial write, by the swap's atomicity).
210    pub content: Vec<u8>,
211    /// The Running incarnation these bytes belong to.
212    pub incarnation: ComponentIncarnation,
213}