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}