Skip to main content

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}