From 8e358a25dea498a7a4ff9e0b985ca5e50c96e6bc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 08:24:03 +0000 Subject: [PATCH 1/2] test(jina): pin pack_edge and edge_plasticity to the canonical CE64 contract (red) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh --- src/hpc/jina/causal.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/hpc/jina/causal.rs b/src/hpc/jina/causal.rs index 3109952e..2a437174 100644 --- a/src/hpc/jina/causal.rs +++ b/src/hpc/jina/causal.rs @@ -185,6 +185,38 @@ mod tests { assert!((exp - 0.82).abs() < 0.02); } + // The canonical CausalEdge64 contract (lance-graph `causal_edge::layout`): + // 40..42 Pearl, 43..45 orientation, 46..49 signed inference mantissa, + // 50..52 plasticity, 53..58 W, 59..63 epistemic state. + const ORIENT: (u32, u64) = (43, 0x7); + const INFER: (u32, u64) = (46, 0xF); + const PLAST: (u32, u64) = (50, 0x7); + const W: (u32, u64) = (53, 0x3F); + const EPI: (u32, u64) = (59, 0x1F); + + fn field(edge: u64, (shift, mask): (u32, u64)) -> u64 { + (edge >> shift) & mask + } + + #[test] + fn pack_edge_writes_the_canonical_contract() { + for temporal in [0u16, 42, 1234, 3072, 4095] { + let edge = pack_edge(42, 187, 91, 0.75, 0.60, 0b111, temporal); + assert_eq!(field(edge, ORIENT), 0, "orientation is Core, temporal {temporal}"); + assert_eq!(field(edge, INFER), 0, "an observation carries the neutral mantissa, temporal {temporal}"); + assert_eq!(field(edge, PLAST), 0b100, "plasticity sits at 50..52, temporal {temporal}"); + assert_eq!(field(edge, W), 0, "temporal must not leak into W, temporal {temporal}"); + assert_eq!(field(edge, EPI), 0, "temporal must not leak into the epistemic state, temporal {temporal}"); + } + } + + #[test] + fn edge_plasticity_reads_bits_50_to_52() { + assert_eq!(edge_plasticity(0b101u64 << 50), 0b101); + // Bit 49 is the mantissa's top bit, not plasticity. + assert_eq!(edge_plasticity(1u64 << 49), 0); + } + #[test] fn test_pearl_mask_distance() { use super::super::codec::{Base17Token, JinaPalette, BASE_DIM}; From 9ea9ac52b26c32d71c911c082353a75dcf7eee21 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 08:26:02 +0000 Subject: [PATCH 2/2] jina: pack_edge writes the canonical CausalEdge64 contract; position moves to the row pack_edge kept its v1 upper bits: orientation 4 at 43 (a Haspelmath function, not Core), mantissa +1 at 46, plasticity at 49 and a 12-bit temporal position at 52, which spilled into W (53..58) and the epistemic state (59..63; reserved codes from position 3072 on). Same names and signatures, canonical bodies: orientation Core, neutral mantissa (an observation is not an inference), plasticity at 50..52, W and epistemic state 0. edge_plasticity reads 50..52. The temporal argument is accepted and not stored; edge_temporal is deprecated and returns 0. GPT-2 and OpenChat AttentionEdge gain a position field. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh --- src/hpc/gpt2/inference.rs | 4 +++ src/hpc/jina/causal.rs | 52 +++++++++++++++++++---------------- src/hpc/jina/runtime.rs | 7 +++-- src/hpc/openchat/inference.rs | 4 +++ 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/hpc/gpt2/inference.rs b/src/hpc/gpt2/inference.rs index 1cf06a7f..1b44329e 100644 --- a/src/hpc/gpt2/inference.rs +++ b/src/hpc/gpt2/inference.rs @@ -35,6 +35,9 @@ pub struct AttentionEdge { pub head: u8, /// The packed CausalEdge64 (subject=query token, predicate=head, object=key token). pub edge: u64, + /// Sequence position when the edge was emitted. The canonical CausalEdge64 + /// word has no temporal field, so the position lives here. + pub position: u16, } /// GPT-2 inference engine. @@ -290,6 +293,7 @@ impl Gpt2Engine { layer: layer_idx as u8, head: head as u8, edge, + position: self.seq_len as u16, }); } } diff --git a/src/hpc/jina/causal.rs b/src/hpc/jina/causal.rs index 2a437174..f039a8ce 100644 --- a/src/hpc/jina/causal.rs +++ b/src/hpc/jina/causal.rs @@ -1,19 +1,28 @@ //! CausalEdge64 integration for Jina token triples. //! //! Every SPO triple from token palette indices packs into one u64 -//! with NARS truth, Pearl hierarchy, plasticity, and temporal index. +//! with NARS truth, Pearl hierarchy and plasticity, in the canonical +//! CausalEdge64 layout (lance-graph `causal_edge::layout`). A temporal +//! position has no field in that word; callers carry it in their row. use super::codec::JinaPalette; /// Pack an SPO triple + metadata into a CausalEdge64 (one u64). /// -/// Bit layout: +/// Bit layout (canonical, lance-graph `causal_edge::layout`): /// ```text -/// [S:8][P:8][O:8][freq:8][conf:8][pearl:3][dir:3][inf:3][plast:3][temporal:12] +/// 0..23 S/P/O palette codes | 24..31 freq | 32..39 conf | 40..42 Pearl +/// 43..45 orientation (0 = Core) | 46..49 signed inference mantissa (0 = neutral) +/// 50..52 plasticity | 53..58 W (0 = no corpus anchor) | 59..63 epistemic state /// ``` +/// +/// `temporal` is accepted for source compatibility and not stored: the +/// canonical word has no temporal field (bits 52..63 belong to plasticity, +/// W and the epistemic state). Keep the position in the row that holds the +/// edge, as `AttentionEdge::position` does. #[inline(always)] pub fn pack_edge( - s_palette: u8, p_palette: u8, o_palette: u8, frequency: f32, confidence: f32, pearl_mask: u8, temporal: u16, + s_palette: u8, p_palette: u8, o_palette: u8, frequency: f32, confidence: f32, pearl_mask: u8, _temporal: u16, ) -> u64 { let f_u8 = (frequency.clamp(0.0, 1.0) * 255.0) as u8; let c_u8 = (confidence.clamp(0.0, 1.0) * 255.0) as u8; @@ -24,10 +33,9 @@ pub fn pack_edge( | ((f_u8 as u64) << 24) | ((c_u8 as u64) << 32) | (((pearl_mask & 0x7) as u64) << 40) - | (0b100u64 << 43) // default direction: S→O - | (0b001u64 << 46) // default inference: observation - | (0b100u64 << 49) // default plasticity: hot - | (((temporal & 0xFFF) as u64) << 52) + // 43..45 orientation: Core (0). 46..49 mantissa: neutral (0), an + // observation is not an inference. 53..63 W and epistemic state: 0. + | (0b100u64 << 50) // plasticity: hot } /// Unpack S palette index from a CausalEdge64. @@ -66,16 +74,19 @@ pub fn edge_pearl(edge: u64) -> u8 { ((edge >> 40) & 0x7) as u8 } -/// Unpack temporal index (12 bits: 0-4095). +/// The canonical CausalEdge64 word has no temporal field, so this always +/// returns 0. Read the position from the row that holds the edge +/// (`AttentionEdge::position`). +#[deprecated(note = "CausalEdge64 has no temporal field; read AttentionEdge::position")] #[inline(always)] -pub fn edge_temporal(edge: u64) -> u16 { - ((edge >> 52) & 0xFFF) as u16 +pub fn edge_temporal(_edge: u64) -> u16 { + 0 } -/// Unpack plasticity (3 bits). +/// Unpack plasticity (3 bits, 50..52). #[inline(always)] pub fn edge_plasticity(edge: u64) -> u8 { - ((edge >> 49) & 0x7) as u8 + ((edge >> 50) & 0x7) as u8 } /// Pearl-masked distance between two edges using palette distance table. @@ -145,19 +156,14 @@ mod tests { assert!((edge_freq(edge) - 0.75).abs() < 0.01); assert!((edge_conf(edge) - 0.60).abs() < 0.01); assert_eq!(edge_pearl(edge), 0b111); - assert_eq!(edge_temporal(edge), 1234); + assert_eq!(edge_plasticity(edge), 0b100); } #[test] - fn test_temporal_sort() { - let e1 = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 100); - let e2 = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 200); - let e3 = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 50); - let mut edges = vec![e2, e3, e1]; - edges.sort(); // native u64 sort - assert_eq!(edge_temporal(edges[0]), 50); - assert_eq!(edge_temporal(edges[1]), 100); - assert_eq!(edge_temporal(edges[2]), 200); + fn temporal_does_not_change_the_word() { + let a = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 50); + let b = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 4095); + assert_eq!(a, b, "the canonical word has no temporal field"); } #[test] diff --git a/src/hpc/jina/runtime.rs b/src/hpc/jina/runtime.rs index aababaa5..128f6d21 100644 --- a/src/hpc/jina/runtime.rs +++ b/src/hpc/jina/runtime.rs @@ -195,7 +195,10 @@ impl ModelRuntime { (leaf, HhtlLevel::Leaf) } - /// Pack two tokens + a predicate into a CausalEdge64. + /// Pack two tokens + a predicate into a CausalEdge64 (canonical layout). + /// + /// `temporal` is not stored: the word has no temporal field. Keep the + /// position in the row that holds the edge. #[inline] pub fn pack_spo_edge( &self, subject_token: usize, predicate_token: usize, object_token: usize, frequency: f32, confidence: f32, @@ -377,7 +380,7 @@ mod tests { fn test_pack_spo_edge() { let rt = &*GPT2; let edge = rt.pack_spo_edge(100, 200, 300, 0.8, 0.6, 42); - assert_eq!(causal::edge_temporal(edge), 42); + assert_eq!(causal::edge_plasticity(edge), 0b100); assert!((causal::edge_freq(edge) - 0.8).abs() < 0.01); } diff --git a/src/hpc/openchat/inference.rs b/src/hpc/openchat/inference.rs index b91a09fb..ed78c0a1 100644 --- a/src/hpc/openchat/inference.rs +++ b/src/hpc/openchat/inference.rs @@ -25,6 +25,9 @@ pub struct AttentionEdge { pub layer: u8, pub head: u8, pub edge: u64, + /// Sequence position when the edge was emitted. The canonical CausalEdge64 + /// word has no temporal field, so the position lives here. + pub position: u16, } /// KV cache for one layer (GQA: only 8 KV heads cached, not 32). @@ -236,6 +239,7 @@ impl OpenChatEngine { layer: layer_idx as u8, head: qh as u8, edge, + position: self.seq_len as u16, }); } }