qualia_core_db/foundation/frame_layout.rs
1//! FrameLayout — the single canonical registry for the ~6 computational-support
2//! bytes of the 48-byte NQuin Frame.
3//!
4//! A Frame is `6 × u64 = 48 bytes`: roughly **42 bytes of semantics** (the
5//! subject / property-path / object / context hashes and packed literals) plus
6//! **~6 bytes of computational support** (opcode, flags, datatype tags, a clock,
7//! ECC parity) — which is what makes a Frame both *data* and *executable code* in
8//! one cell. Every modality reads and writes those computational bytes through
9//! here, so the primitive stays universal and no two conventions silently collide
10//! (enforced by the tests below).
11//!
12//! ## `predicate` — opcode | property-path | defeater (co-resident; MUST NOT overlap)
13//! `[0..7]` deontic/epistemic opcode · `[8..62]` property-path hash · `[63]` defeater.
14//! "Proposition-mode" modalities (LTL/CTL/modal/abductive/causal/…) use the WHOLE
15//! predicate as a plain `q_hash` (no opcode) — a documented mode, not a collision.
16//!
17//! ## `object` — MSB pointer flag | inline datatype tag | value (co-resident)
18//! `[63]` set ⇒ the value is a lexicon/embedded pointer. When `[63]` is clear,
19//! `[60..62]` is an inline datatype tag and `[0..59]` is the value. Canonical tags
20//! (from `resolver`): INTEGER 001 · DECIMAL 010 · BOOLEAN 011 · BLOB 100 ·
21//! **FLOAT 101** (new — raw f32 bits; resolves the prior clash where f32 squatted
22//! on the INTEGER tag).
23//!
24//! ## Tag policy — closed datatypes inline, open identifier *kinds* in the graph
25//! The `object` inline-tag region is reserved STRICTLY for the small, CLOSED set of
26//! structural **datatypes** the GPU/SIMD hot path must read in-register (the tags
27//! above: INTEGER/DECIMAL/BOOLEAN/BLOB/FLOAT + the embedded-triple / pointer flags).
28//! Open, extensible **identifier *kinds* / namespaces** (Webizen, did:q42, DID,
29//! content-hash, instrument-language, cluster-node, …) are NOT inline tags — they are
30//! scoped by **modal predicates** in the graph (`<X> <has-modality-kind> <…>`),
31//! resolved at the logic layer via `indexing::QuinIndex` point lookups, with the
32//! **lexicon as the collision backstop** (the u64 is a *handle* to a full value, so a
33//! handle collision is detectable + resolvable, never silent corruption). Rationale:
34//! datatypes are a fixed vocabulary that belongs in the bit-budget; identifier kinds
35//! are an open fabric that belongs in the relations (identifiers-not-identity). This
36//! is why a dictionary identity is 60-bit (top nibble = the datatype overlay) while
37//! identifier-kind breadth lives in predicates, not in more inline tag bits.
38//!
39//! ## `metadata` — a ROLE-KEYED OVERLAY field (read this carefully)
40//! Unlike `predicate`/`object`, the 32 high bits of `metadata` are NOT a flat set
41//! of independently-addressable sub-fields. They are a union of overlays, each
42//! valid only for a particular quin *role*, several of which share bit positions.
43//! Two overlays sharing bits is safe **iff** their roles are mutually exclusive
44//! (a quin is never both at once). The ONE invariant that always holds — and is
45//! enforced below — is that the **low-32 payload is disjoint from every high
46//! overlay**, so a degree/expiry/timestamp can never silently corrupt a type/flag.
47//!
48//! * `[0..31]` low payload (any quin): exactly ONE of expiry / f32 truth-degree /
49//! 32-bit timestamp / packed (alpha,mu,sigma) — selected by role.
50//! * `[32..59]` tensor-bake `t` clock — ONLY on Tensor10D ground-truth nodes
51//! (`tensor::bake_pipeline`).
52//! * `[50..59]` per-modality flag bits — ONLY on that modality's quins (each flag
53//! is a single distinct bit; see the pairwise-distinct test).
54//! * `[56..59]` ODRL sensitivity tier — ONLY on access-controlled quins.
55//! * `[60..63]` quin-type nibble — general typing; `[61..62]` of it doubles as the
56//! permissive-routing lane (`cbor_compiler`, `daemon_swarm`). Typed vs routed are
57//! treated as exclusive roles. quin_type was deliberately NOT relocated lower:
58//! every lower slot lands inside the `[32..59]` bake clock, which would be a worse
59//! (cross-role) collision than the documented `[61..62]` overlap.
60
61use crate::NQuin;
62
63// ════════════════════════════════════════════════════════════════════════════════
64// predicate field
65// ════════════════════════════════════════════════════════════════════════════════
66pub const OPCODE_MASK: u64 = 0xFF;
67pub const DEFEATER_BIT: u64 = 1u64 << 63;
68pub const PATH_MASK: u64 = 0x7FFF_FFFF_FFFF_FF00;
69
70#[inline]
71pub const fn opcode(predicate: u64) -> u8 {
72 (predicate & OPCODE_MASK) as u8
73}
74#[inline]
75pub const fn path_bits(predicate: u64) -> u64 {
76 predicate & PATH_MASK
77}
78#[inline]
79pub const fn is_defeater(predicate: u64) -> bool {
80 predicate & DEFEATER_BIT != 0
81}
82/// Canonical norm-predicate packing — MUST equal `deontic::compile_norm_quin`.
83#[inline]
84pub const fn pack_predicate(opcode: u8, path_hash: u64, defeater: bool) -> u64 {
85 let d = if defeater { DEFEATER_BIT } else { 0 };
86 d | ((path_hash << 8) & PATH_MASK) | (opcode as u64)
87}
88
89// ════════════════════════════════════════════════════════════════════════════════
90// object field — inline literal datatype tags (canonical in `resolver`)
91// ════════════════════════════════════════════════════════════════════════════════
92pub use crate::resolver::{
93 INLINE_TAG_BOOLEAN, INLINE_TAG_DECIMAL, INLINE_TAG_FLOAT, INLINE_TAG_INTEGER, INLINE_TAG_MASK,
94 INLINE_VALUE_MASK, MSB_FLAG,
95};
96/// Blob/byte-offset pointer tag (canonical in `dicom`).
97pub const INLINE_TAG_BLOB: u64 = 0b100u64 << 60;
98
99/// The 3-bit inline datatype tag of an object value (only meaningful when MSB clear).
100#[inline]
101pub const fn object_tag(object: u64) -> u64 {
102 object & INLINE_TAG_MASK
103}
104/// Pack an f32 into an object field with the canonical FLOAT tag.
105#[inline]
106pub fn pack_float_object(f: f32) -> u64 {
107 INLINE_TAG_FLOAT | (f.to_bits() as u64 & INLINE_VALUE_MASK)
108}
109/// Recover an f32 from a FLOAT-tagged object field.
110#[inline]
111pub fn unpack_float_object(object: u64) -> f32 {
112 f32::from_bits((object & INLINE_VALUE_MASK) as u32)
113}
114
115// ════════════════════════════════════════════════════════════════════════════════
116// metadata field
117// ════════════════════════════════════════════════════════════════════════════════
118/// Low-32 payload mask: expiry | f32 truth-degree | 32-bit timestamp (typed).
119pub const LOW32_MASK: u64 = 0xFFFF_FFFF;
120
121#[inline]
122pub const fn expiry(metadata: u64) -> u32 {
123 (metadata & LOW32_MASK) as u32
124}
125/// CANONICAL truth/belief/confidence/fuzzy degree (IEEE-754 f32 in the low 32 bits).
126#[inline]
127pub fn truth_degree(metadata: u64) -> f32 {
128 f32::from_bits((metadata & LOW32_MASK) as u32)
129}
130#[inline]
131pub fn with_truth_degree(d: f32) -> u64 {
132 let d = if d.is_finite() { d } else { 0.0 };
133 d.to_bits() as u64
134}
135#[inline]
136pub const fn timestamp(metadata: u64) -> u64 {
137 metadata
138}
139
140// ── high-overlay metadata fields (role-keyed; see module header) ──
141/// quin-type nibble `[60..63]` (general typing). Its low two bits `[61..62]` are
142/// reused as the permissive-routing lane on routed quins — typed vs routed are
143/// exclusive roles. NOT relocated lower: every lower slot collides with the
144/// `[32..59]` tensor-bake clock (a worse, cross-role collision).
145pub const QUIN_TYPE_SHIFT: u32 = 60;
146pub const QUIN_TYPE_MASK: u64 = 0xFu64 << QUIN_TYPE_SHIFT;
147/// ODRL sensitivity tier `[56..59]` (only on access-controlled quins).
148pub const SENSITIVITY_SHIFT: u32 = 56;
149pub const SENSITIVITY_MASK: u64 = 0xFu64 << SENSITIVITY_SHIFT;
150/// Permissive-routing lane `[61..62]` (only on routed quins; shares bits with the
151/// quin-type nibble by role-exclusivity).
152pub const ROUTING_LANE_SHIFT: u32 = 61;
153pub const ROUTING_LANE_MASK: u64 = 0b11u64 << ROUTING_LANE_SHIFT;
154/// Tensor-bake `t` clock (only on Tensor10D ground-truth nodes). Matches
155/// `bake_pipeline`'s `(metadata >> 32) & 0x1FFF_FFFF`, i.e. bits `[32..60]` — it
156/// even grazes quin_type's bit 60, an existing tensor-only quirk. Documentary
157/// only; never asserted disjoint (its role excludes the others).
158pub const BAKE_CLOCK_MASK: u64 = 0x1FFF_FFFFu64 << 32;
159
160#[inline]
161pub const fn quin_type(metadata: u64) -> u8 {
162 ((metadata >> QUIN_TYPE_SHIFT) & 0xF) as u8
163}
164#[inline]
165pub const fn with_quin_type(metadata: u64, ty: u8) -> u64 {
166 (metadata & !QUIN_TYPE_MASK) | (((ty as u64) & 0xF) << QUIN_TYPE_SHIFT)
167}
168
169// ── per-modality flag bits [50..59] (each set only on its OWN modality's quins) ──
170// These are mutually exclusive by quin type (a dialectical-synthesis quin is never
171// also an argumentation node), so they may share bit positions with the ODRL
172// sensitivity tier [56..59] WITHOUT a real same-quin collision. They are pairwise
173// distinct (enforced) and disjoint from routing/quin_type (enforced).
174pub const STABILIZATION_BIT: u64 = 1u64 << 50;
175pub const FEEDBACK_BIT: u64 = 1u64 << 51;
176pub const CONTROL_BIT: u64 = 1u64 << 52;
177pub const DEFENSE_BIT: u64 = 1u64 << 53;
178pub const ATTACK_BIT: u64 = 1u64 << 54;
179pub const ARGUMENT_BIT: u64 = 1u64 << 55;
180pub const COUNTERFACTUAL_BIT: u64 = 1u64 << 56;
181pub const DO_INTERVENTION_BIT: u64 = 1u64 << 57;
182pub const SYNTHESIZED_BIT: u64 = 1u64 << 58;
183pub const CONSUMED_BIT: u64 = 1u64 << 59;
184
185// ════════════════════════════════════════════════════════════════════════════════
186// parity (ECC over the four semantic fields)
187// ════════════════════════════════════════════════════════════════════════════════
188#[inline]
189pub const fn parity(subject: u64, predicate: u64, object: u64, context: u64) -> u64 {
190 subject ^ predicate ^ object ^ context
191}
192#[inline]
193pub fn sealed(mut q: NQuin) -> NQuin {
194 q.parity = parity(q.subject, q.predicate, q.object, q.context);
195 q
196}
197#[inline]
198pub fn parity_valid(q: &NQuin) -> bool {
199 q.parity == parity(q.subject, q.predicate, q.object, q.context)
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn predicate_regions_do_not_collide() {
208 assert_eq!(OPCODE_MASK & PATH_MASK, 0);
209 assert_eq!(OPCODE_MASK & DEFEATER_BIT, 0);
210 assert_eq!(PATH_MASK & DEFEATER_BIT, 0);
211 assert_eq!(OPCODE_MASK | PATH_MASK | DEFEATER_BIT, u64::MAX);
212 }
213
214 #[test]
215 fn object_datatype_tags_are_distinct() {
216 let tags = [
217 INLINE_TAG_INTEGER,
218 INLINE_TAG_DECIMAL,
219 INLINE_TAG_BOOLEAN,
220 INLINE_TAG_BLOB,
221 INLINE_TAG_FLOAT,
222 ];
223 for (i, &a) in tags.iter().enumerate() {
224 assert_eq!(
225 a & !INLINE_TAG_MASK,
226 0,
227 "tag {i} outside the [60..62] tag region"
228 );
229 for &b in &tags[i + 1..] {
230 assert_ne!(a, b, "two object datatype tags collide");
231 }
232 }
233 // value region is disjoint from the tag + MSB regions.
234 assert_eq!(INLINE_VALUE_MASK & INLINE_TAG_MASK, 0);
235 assert_eq!(INLINE_VALUE_MASK & MSB_FLAG, 0);
236 }
237
238 #[test]
239 fn low32_payload_is_disjoint_from_every_high_overlay() {
240 // The ONE always-true metadata invariant (see module header): the low-32
241 // payload (degree/expiry/timestamp/packed-moments) never overlaps any high
242 // overlay, so it can't silently corrupt a type / flag / sensitivity / clock.
243 let high = QUIN_TYPE_MASK | ROUTING_LANE_MASK | SENSITIVITY_MASK | BAKE_CLOCK_MASK;
244 assert_eq!(
245 high & LOW32_MASK,
246 0,
247 "a high overlay reaches into the low-32 payload"
248 );
249 // Routing is a sub-lane of the quin-type nibble (documented role-exclusive overlap).
250 assert_eq!(
251 ROUTING_LANE_MASK & QUIN_TYPE_MASK,
252 ROUTING_LANE_MASK,
253 "routing lane must sit inside the quin-type nibble [60..63]"
254 );
255 }
256
257 #[test]
258 fn modality_flag_bits_are_pairwise_distinct() {
259 let flags = [
260 COUNTERFACTUAL_BIT,
261 DO_INTERVENTION_BIT,
262 SYNTHESIZED_BIT,
263 CONSUMED_BIT,
264 STABILIZATION_BIT,
265 FEEDBACK_BIT,
266 CONTROL_BIT,
267 DEFENSE_BIT,
268 ATTACK_BIT,
269 ARGUMENT_BIT,
270 ];
271 for (i, &a) in flags.iter().enumerate() {
272 assert_eq!(a.count_ones(), 1, "flag {i} is not a single bit");
273 for &b in &flags[i + 1..] {
274 assert_ne!(a, b, "two modality flag bits collide");
275 }
276 }
277 // Flags are disjoint from the general fields that co-exist on any quin and
278 // from the typed payload. (They MAY share bits with the ODRL sensitivity
279 // tier [56..59] — that is a quin-type-exclusive overlap, not a same-quin
280 // collision, and is documented in the module header.)
281 let all_flags = flags.iter().fold(0u64, |acc, &f| acc | f);
282 assert_eq!(all_flags & ROUTING_LANE_MASK, 0, "a flag overlaps routing");
283 assert_eq!(all_flags & QUIN_TYPE_MASK, 0, "a flag overlaps quin_type");
284 assert_eq!(
285 all_flags & LOW32_MASK,
286 0,
287 "a flag overlaps the low-32 payload"
288 );
289 }
290
291 #[test]
292 fn predicate_and_degree_round_trip() {
293 let path = crate::q_hash("q42:disclose");
294 let p = pack_predicate(0x12, path, true);
295 assert_eq!(opcode(p), 0x12);
296 assert!(is_defeater(p));
297 for d in [0.0f32, 0.25, 0.8, 1.0] {
298 assert!((truth_degree(with_truth_degree(d)) - d).abs() < 1e-6);
299 }
300 assert!((unpack_float_object(pack_float_object(3.5)) - 3.5).abs() < 1e-6);
301 assert_eq!(object_tag(pack_float_object(3.5)), INLINE_TAG_FLOAT);
302 // quin_type round-trips and leaves the low-32 payload untouched (the
303 // always-true invariant). It deliberately overlays routing [61..62] — that
304 // overlap is role-exclusive, not a bug, so we only check payload safety.
305 let m = with_quin_type(12345, 0b1010);
306 assert_eq!(quin_type(m), 0b1010);
307 assert_eq!(
308 m & LOW32_MASK,
309 12345,
310 "quin_type must not disturb the low-32 payload"
311 );
312 }
313
314 #[test]
315 fn matches_deontic_packing() {
316 use crate::modalities::logic::deontic::{compile_norm_quin, OP_FORBID, OP_PERMIT};
317 let path = crate::q_hash("q42:x");
318 let norm = compile_norm_quin(1, OP_FORBID, path, 2, 3, 0, false);
319 assert_eq!(norm.predicate, pack_predicate(OP_FORBID, path, false));
320 let defeater = compile_norm_quin(1, OP_PERMIT, path, 2, 3, 0, true);
321 assert!(is_defeater(defeater.predicate));
322 }
323
324 #[test]
325 fn parity_ignores_computational_metadata() {
326 let q = sealed(NQuin {
327 subject: 7,
328 predicate: 8,
329 object: 9,
330 context: 10,
331 metadata: 0,
332 parity: 0,
333 });
334 assert!(parity_valid(&q));
335 let mut q2 = q;
336 q2.metadata = QUIN_TYPE_MASK | ROUTING_LANE_MASK | 999;
337 assert!(
338 parity_valid(&q2),
339 "parity must ignore the computational-support field"
340 );
341 }
342}