From 210550b33cdbe201fa9e484df1efc00ed30a601c Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:11:54 -0700 Subject: [PATCH 01/59] Add lightweight native crate manifest --- l64-native/Cargo.toml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 l64-native/Cargo.toml diff --git a/l64-native/Cargo.toml b/l64-native/Cargo.toml new file mode 100644 index 0000000..7f3804e --- /dev/null +++ b/l64-native/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "l64-native" +edition.workspace = true +version.workspace = true +license.workspace = true + +[dependencies] From a28fe8daebeb7a42bd0fc269b426f6beab99157a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:12:04 -0700 Subject: [PATCH 02/59] Document native core boundaries --- l64-native/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 l64-native/README.md diff --git a/l64-native/README.md b/l64-native/README.md new file mode 100644 index 0000000..8635cd4 --- /dev/null +++ b/l64-native/README.md @@ -0,0 +1,22 @@ +# l64-native + +The first additive native execution slice for Locus64. + +This crate deliberately does less: + +- one node algebra represents types, values, and operations; +- composed numeric routes replace native names; +- ordered ports carry incidence; +- persistent context deltas carry scope; +- one transition journal records committed changes; +- one canonical byte encoder determines a provisional dependency-free state stamp; +- one transaction path validates before mutation. + +The first workloads are intentionally narrow: + +1. typed function composition must commit; +2. incompatible matrix multiplication must return a structured obstruction and leave state unchanged. + +The crate has no dependency on the legacy registry, bundle, report, or certification object families. It is additive and cannot yet replace those systems. + +The first-pass stamp is deterministic but not cryptographic. DNA v2 must bind the same canonical bytes to the repository's domain-separated cryptographic commitment implementation. From 33cb189c6639d946e7de400f1527bb5e384e3b2d Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:12:26 -0700 Subject: [PATCH 03/59] Add composed route identifiers --- l64-native/src/route.rs | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 l64-native/src/route.rs diff --git a/l64-native/src/route.rs b/l64-native/src/route.rs new file mode 100644 index 0000000..c8831e9 --- /dev/null +++ b/l64-native/src/route.rs @@ -0,0 +1,59 @@ +use core::cmp::Ordering; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct LocusWord(pub u64); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Route { + domain: LocusWord, + tail: Box<[LocusWord]>, +} + +impl Route { + pub fn root(domain: LocusWord) -> Self { + Self { + domain, + tail: Box::new([]), + } + } + + pub fn from_parts(domain: LocusWord, tail: impl Into>) -> Self { + Self { + domain, + tail: tail.into(), + } + } + + pub fn domain(&self) -> LocusWord { + self.domain + } + + pub fn tail(&self) -> &[LocusWord] { + &self.tail + } + + pub fn composed(&self, next: LocusWord) -> Self { + let mut tail = Vec::with_capacity(self.tail.len() + 1); + tail.extend_from_slice(&self.tail); + tail.push(next); + Self { + domain: self.domain, + tail: tail.into_boxed_slice(), + } + } +} + +impl PartialOrd for Route { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Route { + fn cmp(&self, other: &Self) -> Ordering { + self.domain + .cmp(&other.domain) + .then_with(|| self.tail.as_ref().cmp(other.tail.as_ref())) + } +} From b0e3715f56bd7a7ded91ab51cdbf8ed07eb9677a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:12:37 -0700 Subject: [PATCH 04/59] Add persistent context deltas --- l64-native/src/context.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 l64-native/src/context.rs diff --git a/l64-native/src/context.rs b/l64-native/src/context.rs new file mode 100644 index 0000000..5bdf020 --- /dev/null +++ b/l64-native/src/context.rs @@ -0,0 +1,18 @@ +use crate::{ContextId, NodeId}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct ContextDelta { + pub(crate) parent: ContextId, + pub(crate) binding: NodeId, +} + +impl ContextDelta { + pub fn parent(&self) -> ContextId { + self.parent + } + + pub fn binding(&self) -> Option { + (self.binding != u32::MAX).then_some(self.binding) + } +} From 9a579490a8cac3512a24f27dc0ba601a27f7e5e7 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:12:46 -0700 Subject: [PATCH 05/59] Add single transition journal event --- l64-native/src/journal.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 l64-native/src/journal.rs diff --git a/l64-native/src/journal.rs b/l64-native/src/journal.rs new file mode 100644 index 0000000..61a71d6 --- /dev/null +++ b/l64-native/src/journal.rs @@ -0,0 +1,32 @@ +use crate::{EventId, NodeId, OpCode}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct JournalEvent { + pub(crate) operation: OpCode, + pub(crate) subject: NodeId, + pub(crate) before: [u8; 32], + pub(crate) after: [u8; 32], + pub(crate) parent: EventId, +} + +impl JournalEvent { + pub fn operation(&self) -> OpCode { + self.operation + } + + pub fn subject(&self) -> NodeId { + self.subject + } + + pub fn before(&self) -> [u8; 32] { + self.before + } + + pub fn after(&self) -> [u8; 32] { + self.after + } + + pub fn parent(&self) -> Option { + (self.parent != u32::MAX).then_some(self.parent) + } +} From 409f64273b46d9b409caf7dcc34ddaa34b6ef546 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:12:55 -0700 Subject: [PATCH 06/59] Expose compact native execution surface --- l64-native/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 l64-native/src/lib.rs diff --git a/l64-native/src/lib.rs b/l64-native/src/lib.rs new file mode 100644 index 0000000..3bd1a0e --- /dev/null +++ b/l64-native/src/lib.rs @@ -0,0 +1,15 @@ +#![forbid(unsafe_code)] + +mod codec; +mod context; +mod graph; +mod journal; +mod kernel; +mod route; + +pub use codec::canonical_bytes; +pub use context::ContextDelta; +pub use graph::{ContextId, EventId, Graph, Node, NodeId, ROOT_CONTEXT}; +pub use journal::JournalEvent; +pub use kernel::{CommitResult, Obstruction, OpCode, Port, PortRole, Proposal}; +pub use route::{LocusWord, Route}; From 1ad1e2f87c55bc86db6c95e743b2d6850ee8da25 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:13:18 -0700 Subject: [PATCH 07/59] Add explicit canonical native byte encoder --- l64-native/src/codec.rs | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 l64-native/src/codec.rs diff --git a/l64-native/src/codec.rs b/l64-native/src/codec.rs new file mode 100644 index 0000000..f99b18e --- /dev/null +++ b/l64-native/src/codec.rs @@ -0,0 +1,85 @@ +use crate::Graph; + +const CODEC_VERSION: u16 = 1; + +pub fn canonical_bytes(graph: &Graph) -> Vec { + let mut out = + Vec::with_capacity(16 + graph.nodes_raw().len() * 24 + graph.ports_raw().len() * 8); + out.extend_from_slice(b"L64N"); + out.extend_from_slice(&CODEC_VERSION.to_le_bytes()); + push_len(&mut out, graph.nodes_raw().len()); + push_len(&mut out, graph.ports_raw().len()); + push_len(&mut out, graph.contexts_raw().len()); + push_len(&mut out, graph.routes_raw().len()); + + for node in graph.nodes_raw() { + out.extend_from_slice(&(node.opcode() as u16).to_le_bytes()); + out.extend_from_slice(&node.context().to_le_bytes()); + out.extend_from_slice(&node.ty().unwrap_or(u32::MAX).to_le_bytes()); + out.extend_from_slice(&node.payload().to_le_bytes()); + let range = node.port_range(); + out.extend_from_slice(&(range.start as u32).to_le_bytes()); + out.extend_from_slice(&((range.end - range.start) as u16).to_le_bytes()); + } + + for port in graph.ports_raw() { + out.extend_from_slice(&port.target().to_le_bytes()); + out.push(port.role() as u8); + out.push(0); + out.extend_from_slice(&port.ordinal().to_le_bytes()); + } + + for context in graph.contexts_raw() { + out.extend_from_slice(&context.parent().to_le_bytes()); + out.extend_from_slice(&context.binding().unwrap_or(u32::MAX).to_le_bytes()); + } + + for (route, node) in graph.routes_raw() { + out.extend_from_slice(&route.domain().0.to_le_bytes()); + push_len(&mut out, route.tail().len()); + for word in route.tail() { + out.extend_from_slice(&word.0.to_le_bytes()); + } + out.extend_from_slice(&node.to_le_bytes()); + } + + out +} + +pub(crate) fn state_commitment(graph: &Graph) -> [u8; 32] { + // This dependency-free structural stamp is deterministic and adequate for + // transactional change detection. DNA v2 must replace it with the + // repository's domain-separated cryptographic commitment boundary. + let bytes = canonical_bytes(graph); + let seeds = [ + 0xcbf29ce484222325_u64, + 0x84222325cbf29ce4_u64, + 0x9e3779b97f4a7c15_u64, + 0xd6e8feb86659fd93_u64, + ]; + let mut lanes = seeds; + for (index, byte) in bytes.iter().copied().enumerate() { + for (lane_index, lane) in lanes.iter_mut().enumerate() { + let rotated = byte.rotate_left(((index + lane_index) & 7) as u32); + *lane ^= u64::from(rotated) | ((index as u64) << ((lane_index + 1) * 7)); + *lane = lane.wrapping_mul(0x100000001b3_u64 ^ seeds[lane_index]); + *lane ^= *lane >> 29; + *lane = lane.rotate_left((11 + lane_index * 7) as u32); + } + } + let mut out = [0_u8; 32]; + for (index, mut lane) in lanes.into_iter().enumerate() { + lane ^= lane >> 33; + lane = lane.wrapping_mul(0xff51afd7ed558ccd); + lane ^= lane >> 33; + lane = lane.wrapping_mul(0xc4ceb9fe1a85ec53); + lane ^= lane >> 33; + out[index * 8..(index + 1) * 8].copy_from_slice(&lane.to_le_bytes()); + } + out +} + +fn push_len(out: &mut Vec, len: usize) { + let value = u32::try_from(len).expect("native graph section exceeds u32 length"); + out.extend_from_slice(&value.to_le_bytes()); +} From 724d5cb4d5cd93daf09569894fad804f436d6db0 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:14:32 -0700 Subject: [PATCH 08/59] Add compact graph and transaction storage --- l64-native/src/graph.rs | 375 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 l64-native/src/graph.rs diff --git a/l64-native/src/graph.rs b/l64-native/src/graph.rs new file mode 100644 index 0000000..5a98c33 --- /dev/null +++ b/l64-native/src/graph.rs @@ -0,0 +1,375 @@ +use std::collections::BTreeMap; + +use crate::{ContextDelta, JournalEvent, LocusWord, Obstruction, OpCode, Port, PortRole, Route}; + +pub type NodeId = u32; +pub type ContextId = u32; +pub type EventId = u32; + +pub const ROOT_CONTEXT: ContextId = 0; +const NO_NODE: NodeId = u32::MAX; +const META_TYPE: NodeId = u32::MAX; +const NO_EVENT: EventId = u32::MAX; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct Node { + payload: u64, + context: ContextId, + ty: NodeId, + first_port: u32, + opcode: u16, + port_count: u16, +} + +impl Node { + pub fn opcode(&self) -> OpCode { + OpCode::from_raw(self.opcode).expect("stored opcode is validated at insertion") + } + + pub fn context(&self) -> ContextId { + self.context + } + + pub fn ty(&self) -> Option { + (self.ty != META_TYPE).then_some(self.ty) + } + + pub fn payload(&self) -> u64 { + self.payload + } + + pub fn port_range(&self) -> core::ops::Range { + let start = self.first_port as usize; + start..start + self.port_count as usize + } +} + +#[derive(Debug, Clone)] +pub struct Graph { + nodes: Vec, + ports: Vec, + contexts: Vec, + routes: BTreeMap, + journal: Vec, + commitment: [u8; 32], +} + +impl Default for Graph { + fn default() -> Self { + Self::new() + } +} + +impl Graph { + pub fn new() -> Self { + let mut graph = Self { + nodes: Vec::new(), + ports: Vec::new(), + contexts: vec![ContextDelta::root()], + routes: BTreeMap::new(), + journal: Vec::new(), + commitment: [0; 32], + }; + graph.commitment = crate::codec::state_commitment(&graph); + graph + } + + pub fn node(&self, id: NodeId) -> Option<&Node> { + self.nodes.get(id as usize) + } + + pub fn ports(&self, node: NodeId) -> Option<&[Port]> { + let node = self.node(node)?; + self.ports.get(node.port_range()) + } + + pub fn resolve(&self, route: &Route) -> Option { + self.routes.get(route).copied() + } + + pub fn node_count(&self) -> usize { + self.nodes.len() + } + + pub fn port_count(&self) -> usize { + self.ports.len() + } + + pub fn context_count(&self) -> usize { + self.contexts.len() + } + + pub fn journal_len(&self) -> usize { + self.journal.len() + } + + pub fn state_commitment(&self) -> [u8; 32] { + self.commitment + } + + pub fn journal(&self) -> &[JournalEvent] { + &self.journal + } + + pub fn extend_context( + &mut self, + parent: ContextId, + binding: NodeId, + ) -> Result { + self.ensure_context(parent)?; + self.ensure_node(binding)?; + let before = self.commitment; + let id = self.contexts.len() as ContextId; + self.contexts.push(ContextDelta { parent, binding }); + let after = crate::codec::state_commitment(self); + self.push_event(OpCode::ExtendContext, binding, before, after); + self.commitment = after; + Ok(id) + } + + pub fn declare_atom_type( + &mut self, + route: Route, + atom_code: LocusWord, + ) -> Result { + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeAtom, + atom_code.0, + &[], + ) + } + + pub fn declare_matrix_type( + &mut self, + route: Route, + element: NodeId, + rows: u32, + cols: u32, + ) -> Result { + self.ensure_type(element)?; + let payload = u64::from(rows) << 32 | u64::from(cols); + let port = Port::new(element, PortRole::Parameter, 0); + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeMatrix, + payload, + &[port], + ) + } + + pub fn declare_function_type( + &mut self, + route: Route, + domain: NodeId, + codomain: NodeId, + ) -> Result { + self.ensure_type(domain)?; + self.ensure_type(codomain)?; + let ports = [ + Port::new(domain, PortRole::Domain, 0), + Port::new(codomain, PortRole::Codomain, 1), + ]; + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeFunction, + 0, + &ports, + ) + } + + pub fn insert_value( + &mut self, + route: Route, + context: ContextId, + ty: NodeId, + ) -> Result { + self.ensure_context(context)?; + self.ensure_type(ty)?; + self.insert_committed_node(route, context, ty, OpCode::Value, 0, &[]) + } + + pub(crate) fn insert_operation( + &mut self, + route: Route, + context: ContextId, + ty: NodeId, + opcode: OpCode, + inputs: &[NodeId], + ) -> Result { + let ports = inputs + .iter() + .enumerate() + .map(|(ordinal, target)| Port::new(*target, PortRole::Argument, ordinal as u16)) + .collect::>(); + let node = self.insert_committed_node(route, context, ty, opcode, 0, &ports)?; + let event = self.journal.len().saturating_sub(1) as EventId; + Ok(crate::CommitResult { + node, + event, + commitment: self.commitment, + }) + } + + pub(crate) fn function_parts(&self, ty: NodeId) -> Result<(NodeId, NodeId), Obstruction> { + let node = self.ensure_type(ty)?; + if node.opcode() != OpCode::TypeFunction { + return Err(Obstruction::ExpectedFunctionType { node: ty }); + } + let ports = self + .ports(ty) + .ok_or(Obstruction::UnknownNode { node: ty })?; + if ports.len() != 2 { + return Err(Obstruction::MalformedType { node: ty }); + } + Ok((ports[0].target(), ports[1].target())) + } + + pub(crate) fn matrix_parts(&self, ty: NodeId) -> Result<(NodeId, u32, u32), Obstruction> { + let node = self.ensure_type(ty)?; + if node.opcode() != OpCode::TypeMatrix { + return Err(Obstruction::ExpectedMatrixType { node: ty }); + } + let ports = self + .ports(ty) + .ok_or(Obstruction::UnknownNode { node: ty })?; + if ports.len() != 1 { + return Err(Obstruction::MalformedType { node: ty }); + } + Ok(( + ports[0].target(), + (node.payload >> 32) as u32, + node.payload as u32, + )) + } + + pub(crate) fn matches_function_type( + &self, + ty: NodeId, + domain: NodeId, + codomain: NodeId, + ) -> bool { + self.function_parts(ty) == Ok((domain, codomain)) + } + + pub(crate) fn matches_matrix_type( + &self, + ty: NodeId, + element: NodeId, + rows: u32, + cols: u32, + ) -> bool { + self.matrix_parts(ty) == Ok((element, rows, cols)) + } + + pub(crate) fn ensure_node(&self, id: NodeId) -> Result<&Node, Obstruction> { + self.node(id).ok_or(Obstruction::UnknownNode { node: id }) + } + + pub(crate) fn ensure_type(&self, id: NodeId) -> Result<&Node, Obstruction> { + let node = self.ensure_node(id)?; + if !node.opcode().is_type() { + return Err(Obstruction::ExpectedType { node: id }); + } + Ok(node) + } + + pub(crate) fn ensure_context(&self, id: ContextId) -> Result<&ContextDelta, Obstruction> { + self.contexts + .get(id as usize) + .ok_or(Obstruction::UnknownContext { context: id }) + } + + fn insert_committed_node( + &mut self, + route: Route, + context: ContextId, + ty: NodeId, + opcode: OpCode, + payload: u64, + ports: &[Port], + ) -> Result { + if self.routes.contains_key(&route) { + return Err(Obstruction::RouteOccupied); + } + self.ensure_context(context)?; + for port in ports { + self.ensure_node(port.target())?; + } + if ty != META_TYPE { + self.ensure_type(ty)?; + } + + let before = self.commitment; + let node = self.nodes.len() as NodeId; + let first_port = self.ports.len() as u32; + self.ports.extend_from_slice(ports); + self.nodes.push(Node { + payload, + context, + ty, + first_port, + opcode: opcode as u16, + port_count: ports.len() as u16, + }); + self.routes.insert(route, node); + let after = crate::codec::state_commitment(self); + self.push_event(opcode, node, before, after); + self.commitment = after; + Ok(node) + } + + fn push_event( + &mut self, + operation: OpCode, + subject: NodeId, + before: [u8; 32], + after: [u8; 32], + ) { + let parent = self + .journal + .len() + .checked_sub(1) + .map(|value| value as EventId) + .unwrap_or(NO_EVENT); + self.journal.push(JournalEvent { + operation, + subject, + before, + after, + parent, + }); + } + + pub(crate) fn nodes_raw(&self) -> &[Node] { + &self.nodes + } + + pub(crate) fn ports_raw(&self) -> &[Port] { + &self.ports + } + + pub(crate) fn contexts_raw(&self) -> &[ContextDelta] { + &self.contexts + } + + pub(crate) fn routes_raw(&self) -> &BTreeMap { + &self.routes + } +} + +impl ContextDelta { + fn root() -> Self { + Self { + parent: ROOT_CONTEXT, + binding: NO_NODE, + } + } +} From d57b0bb106de52cdb1b4d0d057db13080fa3f710 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:15:24 -0700 Subject: [PATCH 09/59] Add transactional typed operation kernel --- l64-native/src/kernel.rs | 261 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 l64-native/src/kernel.rs diff --git a/l64-native/src/kernel.rs b/l64-native/src/kernel.rs new file mode 100644 index 0000000..afeb431 --- /dev/null +++ b/l64-native/src/kernel.rs @@ -0,0 +1,261 @@ +use crate::{ContextId, Graph, NodeId, Route}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum OpCode { + TypeAtom = 1, + TypeMatrix = 2, + TypeFunction = 3, + Value = 4, + Compose = 5, + MatMul = 6, + ExtendContext = 7, +} + +impl OpCode { + pub(crate) fn from_raw(value: u16) -> Option { + match value { + 1 => Some(Self::TypeAtom), + 2 => Some(Self::TypeMatrix), + 3 => Some(Self::TypeFunction), + 4 => Some(Self::Value), + 5 => Some(Self::Compose), + 6 => Some(Self::MatMul), + 7 => Some(Self::ExtendContext), + _ => None, + } + } + + pub(crate) fn is_type(self) -> bool { + matches!(self, Self::TypeAtom | Self::TypeMatrix | Self::TypeFunction) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum PortRole { + Parameter = 1, + Domain = 2, + Codomain = 3, + Argument = 4, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct Port { + target: NodeId, + role: u8, + flags: u8, + ordinal: u16, +} + +impl Port { + pub(crate) fn new(target: NodeId, role: PortRole, ordinal: u16) -> Self { + Self { + target, + role: role as u8, + flags: 0, + ordinal, + } + } + + pub fn target(&self) -> NodeId { + self.target + } + + pub fn role(&self) -> PortRole { + match self.role { + 1 => PortRole::Parameter, + 2 => PortRole::Domain, + 3 => PortRole::Codomain, + 4 => PortRole::Argument, + _ => unreachable!("stored port role is constructed internally"), + } + } + + pub fn ordinal(&self) -> u16 { + self.ordinal + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Proposal { + route: Route, + context: ContextId, + opcode: OpCode, + inputs: Box<[NodeId]>, + output_type: NodeId, +} + +impl Proposal { + pub fn compose( + route: Route, + context: ContextId, + first: NodeId, + second: NodeId, + output_type: NodeId, + ) -> Self { + Self { + route, + context, + opcode: OpCode::Compose, + inputs: Box::new([first, second]), + output_type, + } + } + + pub fn matrix_multiply( + route: Route, + context: ContextId, + left: NodeId, + right: NodeId, + output_type: NodeId, + ) -> Self { + Self { + route, + context, + opcode: OpCode::MatMul, + inputs: Box::new([left, right]), + output_type, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CommitResult { + pub node: NodeId, + pub event: u32, + pub commitment: [u8; 32], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Obstruction { + UnknownNode { + node: NodeId, + }, + UnknownContext { + context: ContextId, + }, + RouteOccupied, + ExpectedType { + node: NodeId, + }, + ExpectedFunctionType { + node: NodeId, + }, + ExpectedMatrixType { + node: NodeId, + }, + MalformedType { + node: NodeId, + }, + Arity { + expected: u16, + actual: u16, + }, + FunctionBoundaryMismatch { + left: NodeId, + right: NodeId, + }, + MatrixShapeMismatch { + left_cols: u32, + right_rows: u32, + }, + MatrixElementMismatch { + left: NodeId, + right: NodeId, + }, + FunctionOutputMismatch { + output_type: NodeId, + domain: NodeId, + codomain: NodeId, + }, + MatrixOutputMismatch { + output_type: NodeId, + element: NodeId, + rows: u32, + cols: u32, + }, +} + +impl Graph { + pub fn transact(&mut self, proposal: Proposal) -> Result { + self.ensure_context(proposal.context)?; + self.ensure_type(proposal.output_type)?; + if proposal.inputs.len() != 2 { + return Err(Obstruction::Arity { + expected: 2, + actual: proposal.inputs.len() as u16, + }); + } + let left = proposal.inputs[0]; + let right = proposal.inputs[1]; + let left_node = self.ensure_node(left)?; + let right_node = self.ensure_node(right)?; + let left_ty = left_node + .ty() + .ok_or(Obstruction::ExpectedType { node: left })?; + let right_ty = right_node + .ty() + .ok_or(Obstruction::ExpectedType { node: right })?; + + match proposal.opcode { + OpCode::Compose => { + let (first_domain, first_codomain) = self.function_parts(left_ty)?; + let (second_domain, second_codomain) = self.function_parts(right_ty)?; + if first_codomain != second_domain { + return Err(Obstruction::FunctionBoundaryMismatch { + left: first_codomain, + right: second_domain, + }); + } + if !self.matches_function_type(proposal.output_type, first_domain, second_codomain) + { + return Err(Obstruction::FunctionOutputMismatch { + output_type: proposal.output_type, + domain: first_domain, + codomain: second_codomain, + }); + } + } + OpCode::MatMul => { + let (left_element, left_rows, left_cols) = self.matrix_parts(left_ty)?; + let (right_element, right_rows, right_cols) = self.matrix_parts(right_ty)?; + if left_cols != right_rows { + return Err(Obstruction::MatrixShapeMismatch { + left_cols, + right_rows, + }); + } + if left_element != right_element { + return Err(Obstruction::MatrixElementMismatch { + left: left_element, + right: right_element, + }); + } + if !self.matches_matrix_type( + proposal.output_type, + left_element, + left_rows, + right_cols, + ) { + return Err(Obstruction::MatrixOutputMismatch { + output_type: proposal.output_type, + element: left_element, + rows: left_rows, + cols: right_cols, + }); + } + } + _ => unreachable!("public proposal constructors expose executable operations only"), + } + + self.insert_operation( + proposal.route, + proposal.context, + proposal.output_type, + proposal.opcode, + &proposal.inputs, + ) + } +} From ae9cdd7bbe5010426db3c20ff999f6be32336492 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:15:43 -0700 Subject: [PATCH 10/59] Enforce native complexity budgets --- l64-native/tests/architecture.rs | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 l64-native/tests/architecture.rs diff --git a/l64-native/tests/architecture.rs b/l64-native/tests/architecture.rs new file mode 100644 index 0000000..a2056ba --- /dev/null +++ b/l64-native/tests/architecture.rs @@ -0,0 +1,56 @@ +use core::mem::size_of; +use std::{fs, path::Path}; + +use l64_native::{Node, Port}; + +#[test] +fn compact_layout_budgets_hold() { + assert!( + size_of::() <= 32, + "Node grew to {} bytes", + size_of::() + ); + assert!( + size_of::() <= 8, + "Port grew to {} bytes", + size_of::() + ); +} + +#[test] +fn native_source_rejects_coordination_heavy_dependencies() { + let source_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + let mut source = std::string::String::new(); + for entry in fs::read_dir(source_root).unwrap() { + let path = entry.unwrap().path(); + if path.extension().and_then(|part| part.to_str()) == Some("rs") { + source.push_str(&fs::read_to_string(path).unwrap()); + } + } + + let forbidden = [ + ["Str", "ing"].concat(), + ["HashMap", "<", "Str", "ing"].concat(), + ["serde", "_json"].concat(), + ["bin", "code"].concat(), + ["Ser", "ialize"].concat(), + ["Deser", "ialize"].concat(), + ["claim", "_packet"].concat(), + ["theorem", "_id"].concat(), + ["campaign", "_id"].concat(), + ]; + for token in forbidden { + assert!(!source.contains(&token), "forbidden native token: {token}"); + } + + let public_structs = source.matches("pub struct ").count(); + let public_enums = source.matches("pub enum ").count(); + assert!( + public_structs <= 12, + "public struct budget exceeded: {public_structs}" + ); + assert!( + public_enums <= 8, + "public enum budget exceeded: {public_enums}" + ); +} From 02f21959d5583cfa2cd64796ebf5fb289a0d8787 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:16:06 -0700 Subject: [PATCH 11/59] Add valid and invalid native workloads --- l64-native/tests/workloads.rs | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 l64-native/tests/workloads.rs diff --git a/l64-native/tests/workloads.rs b/l64-native/tests/workloads.rs new file mode 100644 index 0000000..816be6d --- /dev/null +++ b/l64-native/tests/workloads.rs @@ -0,0 +1,76 @@ +use l64_native::{Graph, LocusWord, Obstruction, Proposal, ROOT_CONTEXT, Route}; + +fn route(index: u64) -> Route { + Route::root(LocusWord(0x4c36344e41544956)).composed(LocusWord(index)) +} + +#[test] +fn valid_typed_function_composition_commits() { + let mut graph = Graph::new(); + let a = graph.declare_atom_type(route(1), LocusWord(0x41)).unwrap(); + let b = graph.declare_atom_type(route(2), LocusWord(0x42)).unwrap(); + let c = graph.declare_atom_type(route(3), LocusWord(0x43)).unwrap(); + let ab = graph.declare_function_type(route(4), a, b).unwrap(); + let bc = graph.declare_function_type(route(5), b, c).unwrap(); + let ac = graph.declare_function_type(route(6), a, c).unwrap(); + let first = graph.insert_value(route(7), ROOT_CONTEXT, ab).unwrap(); + let second = graph.insert_value(route(8), ROOT_CONTEXT, bc).unwrap(); + let before = graph.state_commitment(); + let before_nodes = graph.node_count(); + + let committed = graph + .transact(Proposal::compose(route(9), ROOT_CONTEXT, first, second, ac)) + .unwrap(); + + assert_eq!(graph.node_count(), before_nodes + 1); + assert_ne!(graph.state_commitment(), before); + assert_eq!(graph.node(committed.node).unwrap().ty(), Some(ac)); + assert_eq!(graph.ports(committed.node).unwrap().len(), 2); + assert_eq!( + graph.journal().last().unwrap().after(), + committed.commitment + ); +} + +#[test] +fn invalid_matrix_shape_rejects_without_mutation() { + let mut graph = Graph::new(); + let scalar = graph.declare_atom_type(route(11), LocusWord(0x52)).unwrap(); + let left_ty = graph.declare_matrix_type(route(12), scalar, 2, 3).unwrap(); + let right_ty = graph.declare_matrix_type(route(13), scalar, 4, 2).unwrap(); + let output_ty = graph.declare_matrix_type(route(14), scalar, 2, 2).unwrap(); + let left = graph + .insert_value(route(15), ROOT_CONTEXT, left_ty) + .unwrap(); + let right = graph + .insert_value(route(16), ROOT_CONTEXT, right_ty) + .unwrap(); + + let before_commitment = graph.state_commitment(); + let before_nodes = graph.node_count(); + let before_ports = graph.port_count(); + let before_contexts = graph.context_count(); + let before_journal = graph.journal_len(); + + let result = graph.transact(Proposal::matrix_multiply( + route(17), + ROOT_CONTEXT, + left, + right, + output_ty, + )); + + assert_eq!( + result, + Err(Obstruction::MatrixShapeMismatch { + left_cols: 3, + right_rows: 4, + }) + ); + assert_eq!(graph.state_commitment(), before_commitment); + assert_eq!(graph.node_count(), before_nodes); + assert_eq!(graph.port_count(), before_ports); + assert_eq!(graph.context_count(), before_contexts); + assert_eq!(graph.journal_len(), before_journal); + assert!(graph.resolve(&route(17)).is_none()); +} From 997369a43db931d7fac3ff290e2a612a10ec5031 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:16:21 -0700 Subject: [PATCH 12/59] Wire lightweight native core into workspace --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 3a7c67a..2f07a79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "l64-native", "l64-core", "l64-locus", "l64-research", From 34ec3a96f80205cf8e6e944b31c19487c99947f4 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:17:45 -0700 Subject: [PATCH 13/59] Add native core integration gate --- .github/workflows/native-core.yml | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/native-core.yml diff --git a/.github/workflows/native-core.yml b/.github/workflows/native-core.yml new file mode 100644 index 0000000..2f2d21b --- /dev/null +++ b/.github/workflows/native-core.yml @@ -0,0 +1,39 @@ +name: Native core gate + +on: + pull_request: + branches: + - main + paths: + - Cargo.toml + - Cargo.lock + - l64-native/** + - .github/workflows/native-core.yml + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install stable Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Check formatting + run: cargo fmt --all --check + + - name: Test workspace + run: cargo test -q + + - name: Lint native core + run: cargo clippy -p l64-native --all-targets -- -D warnings From 2662a2921e6484f58c666221671cc98d58e76d98 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:40:23 -0700 Subject: [PATCH 14/59] Bind native state to BLAKE3 --- l64-native/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/l64-native/Cargo.toml b/l64-native/Cargo.toml index 7f3804e..dcd981a 100644 --- a/l64-native/Cargo.toml +++ b/l64-native/Cargo.toml @@ -5,3 +5,4 @@ version.workspace = true license.workspace = true [dependencies] +blake3.workspace = true From 7bcbc1579b86863a2523c104256c976405a29147 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:40:34 -0700 Subject: [PATCH 15/59] Document native codec fixed point --- l64-native/README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/l64-native/README.md b/l64-native/README.md index 8635cd4..6067902 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -1,22 +1,21 @@ # l64-native -The first additive native execution slice for Locus64. +A deliberately small, additive execution spine for Locus64. -This crate deliberately does less: +This crate is not a projection of the legacy `QaEntry` / `RegistryBundle` ontology. It carries: -- one node algebra represents types, values, and operations; -- composed numeric routes replace native names; -- ordered ports carry incidence; -- persistent context deltas carry scope; -- one transition journal records committed changes; -- one canonical byte encoder determines a provisional dependency-free state stamp; -- one transaction path validates before mutation. +- composed numeric routes; +- one node algebra for types, values, and operations; +- ordered compact ports; +- persistent context deltas; +- one transition journal; +- one canonical structural codec; +- one transactional admission path. -The first workloads are intentionally narrow: +The first workload boundary proves that lawful typed function composition commits while incompatible matrix multiplication is rejected without state mutation. -1. typed function composition must commit; -2. incompatible matrix multiplication must return a structured obstruction and leave state unchanged. +The second boundary adds a bounded native decoder. Canonical bytes must decode, satisfy structural laws, and re-encode byte-for-byte. The decoder rejects malformed node order, invalid port laws, bad contexts, invalid route coverage, unknown opcodes, structural overrun, trailing bytes, and non-canonical ordering. -The crate has no dependency on the legacy registry, bundle, report, or certification object families. It is additive and cannot yet replace those systems. +State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. -The first-pass stamp is deterministic but not cryptographic. DNA v2 must bind the same canonical bytes to the repository's domain-separated cryptographic commitment implementation. +This remains additive. It does not yet replace the legacy runtime or DNA packet path. From 60b70e7a545d72f0b8f764c679dd4a8b95aefa7a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:40:43 -0700 Subject: [PATCH 16/59] Admit validated context decoding --- l64-native/src/context.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/l64-native/src/context.rs b/l64-native/src/context.rs index 5bdf020..3d02fed 100644 --- a/l64-native/src/context.rs +++ b/l64-native/src/context.rs @@ -8,6 +8,10 @@ pub struct ContextDelta { } impl ContextDelta { + pub(crate) fn from_raw(parent: ContextId, binding: NodeId) -> Self { + Self { parent, binding } + } + pub fn parent(&self) -> ContextId { self.parent } From c43a807acd47b56be6e3ccad5170f0a1c4e423c7 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:41:17 -0700 Subject: [PATCH 17/59] Add bounded codec fixed-point tests --- l64-native/tests/codec.rs | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 l64-native/tests/codec.rs diff --git a/l64-native/tests/codec.rs b/l64-native/tests/codec.rs new file mode 100644 index 0000000..32336e9 --- /dev/null +++ b/l64-native/tests/codec.rs @@ -0,0 +1,97 @@ +use l64_native::{ + DecodeError, Graph, LocusWord, Proposal, ROOT_CONTEXT, Route, canonical_bytes, decode_canonical, +}; + +fn route(index: u64) -> Route { + Route::root(LocusWord(0x4c36344e41544956)).composed(LocusWord(index)) +} + +fn representative_graph() -> Graph { + let mut graph = Graph::new(); + let scalar = graph.declare_atom_type(route(1), LocusWord(0x52)).unwrap(); + let a = graph.declare_atom_type(route(2), LocusWord(0x41)).unwrap(); + let b = graph.declare_atom_type(route(3), LocusWord(0x42)).unwrap(); + let c = graph.declare_atom_type(route(4), LocusWord(0x43)).unwrap(); + let ab = graph.declare_function_type(route(5), a, b).unwrap(); + let bc = graph.declare_function_type(route(6), b, c).unwrap(); + let ac = graph.declare_function_type(route(7), a, c).unwrap(); + let first = graph.insert_value(route(8), ROOT_CONTEXT, ab).unwrap(); + let second = graph.insert_value(route(9), ROOT_CONTEXT, bc).unwrap(); + graph + .transact(Proposal::compose( + route(10), + ROOT_CONTEXT, + first, + second, + ac, + )) + .unwrap(); + let matrix = graph.declare_matrix_type(route(11), scalar, 2, 3).unwrap(); + let value = graph.insert_value(route(12), ROOT_CONTEXT, matrix).unwrap(); + graph.extend_context(ROOT_CONTEXT, value).unwrap(); + graph +} + +#[test] +fn canonical_decode_reencode_is_exact_fixed_point() { + let graph = representative_graph(); + let bytes = canonical_bytes(&graph); + let decoded = decode_canonical(&bytes).unwrap(); + + assert_eq!(canonical_bytes(&decoded), bytes); + assert_eq!(decoded.state_commitment(), graph.state_commitment()); + assert_eq!(decoded.node_count(), graph.node_count()); + assert_eq!(decoded.port_count(), graph.port_count()); + assert_eq!(decoded.context_count(), graph.context_count()); + assert_eq!(decoded.journal_len(), 0); + for index in 1..=12 { + assert_eq!(decoded.resolve(&route(index)), graph.resolve(&route(index))); + } +} + +#[test] +fn decoder_rejects_truncation_and_trailing_bytes() { + let bytes = canonical_bytes(&representative_graph()); + assert_eq!( + decode_canonical(&bytes[..bytes.len() - 1]).unwrap_err(), + DecodeError::Truncated + ); + + let mut trailing = bytes; + trailing.push(0); + assert_eq!( + decode_canonical(&trailing).unwrap_err(), + DecodeError::TrailingBytes + ); +} + +#[test] +fn decoder_rejects_unknown_node_opcode() { + let mut bytes = canonical_bytes(&representative_graph()); + let first_node_opcode = 22; + bytes[first_node_opcode..first_node_opcode + 2].copy_from_slice(&u16::MAX.to_le_bytes()); + assert_eq!( + decode_canonical(&bytes).unwrap_err(), + DecodeError::UnknownOpcode { opcode: u16::MAX } + ); +} + +#[test] +fn decoder_rejects_noncanonical_route_order() { + let graph = representative_graph(); + let mut bytes = canonical_bytes(&graph); + let routes_offset = + 22 + graph.node_count() * 24 + graph.port_count() * 8 + graph.context_count() * 8; + let first_route_len = 8 + 4 + 8 + 4; + let first = bytes[routes_offset..routes_offset + first_route_len].to_vec(); + let second = + bytes[routes_offset + first_route_len..routes_offset + 2 * first_route_len].to_vec(); + bytes[routes_offset..routes_offset + first_route_len].copy_from_slice(&second); + bytes[routes_offset + first_route_len..routes_offset + 2 * first_route_len] + .copy_from_slice(&first); + + assert_eq!( + decode_canonical(&bytes).unwrap_err(), + DecodeError::NonCanonical + ); +} From d16c5a92ff3fa2690ad9ae513754d8f1d3cc662a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:43:17 -0700 Subject: [PATCH 18/59] Reconstruct validated native graphs --- l64-native/src/graph.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/l64-native/src/graph.rs b/l64-native/src/graph.rs index 5a98c33..0aa4df6 100644 --- a/l64-native/src/graph.rs +++ b/l64-native/src/graph.rs @@ -23,6 +23,24 @@ pub struct Node { } impl Node { + pub(crate) fn from_raw( + payload: u64, + context: ContextId, + ty: NodeId, + first_port: u32, + opcode: OpCode, + port_count: u16, + ) -> Self { + Self { + payload, + context, + ty, + first_port, + opcode: opcode as u16, + port_count, + } + } + pub fn opcode(&self) -> OpCode { OpCode::from_raw(self.opcode).expect("stored opcode is validated at insertion") } @@ -62,6 +80,23 @@ impl Default for Graph { } impl Graph { + pub(crate) fn from_decoded_parts( + nodes: Vec, + ports: Vec, + contexts: Vec, + routes: BTreeMap, + commitment: [u8; 32], + ) -> Self { + Self { + nodes, + ports, + contexts, + routes, + journal: Vec::new(), + commitment, + } + } + pub fn new() -> Self { let mut graph = Self { nodes: Vec::new(), From 4e11e0f86588b9c4235cbe272c881f48c8417c91 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:43:54 -0700 Subject: [PATCH 19/59] Validate decoded native opcodes and ports --- l64-native/src/kernel.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/l64-native/src/kernel.rs b/l64-native/src/kernel.rs index afeb431..c3b61e4 100644 --- a/l64-native/src/kernel.rs +++ b/l64-native/src/kernel.rs @@ -29,6 +29,10 @@ impl OpCode { pub(crate) fn is_type(self) -> bool { matches!(self, Self::TypeAtom | Self::TypeMatrix | Self::TypeFunction) } + + pub(crate) fn is_persisted_node(self) -> bool { + self != Self::ExtendContext + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -49,6 +53,18 @@ pub struct Port { ordinal: u16, } +impl PortRole { + pub(crate) fn from_raw(value: u8) -> Option { + match value { + 1 => Some(Self::Parameter), + 2 => Some(Self::Domain), + 3 => Some(Self::Codomain), + 4 => Some(Self::Argument), + _ => None, + } + } +} + impl Port { pub(crate) fn new(target: NodeId, role: PortRole, ordinal: u16) -> Self { Self { @@ -59,6 +75,15 @@ impl Port { } } + pub(crate) fn from_raw(target: NodeId, role: PortRole, flags: u8, ordinal: u16) -> Self { + Self { + target, + role: role as u8, + flags, + ordinal, + } + } + pub fn target(&self) -> NodeId { self.target } @@ -76,6 +101,10 @@ impl Port { pub fn ordinal(&self) -> u16 { self.ordinal } + + pub(crate) fn flags(&self) -> u8 { + self.flags + } } #[derive(Debug, Clone, PartialEq, Eq)] From 0c6916a09724bc9d24e0c7834d9354977befbe84 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:44:04 -0700 Subject: [PATCH 20/59] Expose bounded native decoding --- l64-native/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l64-native/src/lib.rs b/l64-native/src/lib.rs index 3bd1a0e..0809376 100644 --- a/l64-native/src/lib.rs +++ b/l64-native/src/lib.rs @@ -7,7 +7,7 @@ mod journal; mod kernel; mod route; -pub use codec::canonical_bytes; +pub use codec::{DecodeError, canonical_bytes, decode_canonical}; pub use context::ContextDelta; pub use graph::{ContextId, EventId, Graph, Node, NodeId, ROOT_CONTEXT}; pub use journal::JournalEvent; From 27d5ae52d20912dc2777737b14ceea09f3e945e7 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:44:59 -0700 Subject: [PATCH 21/59] Add bounded canonical native decoder --- l64-native/src/codec.rs | 317 +++++++++++++++++++++++++++++++++++----- 1 file changed, 283 insertions(+), 34 deletions(-) diff --git a/l64-native/src/codec.rs b/l64-native/src/codec.rs index f99b18e..e846c43 100644 --- a/l64-native/src/codec.rs +++ b/l64-native/src/codec.rs @@ -1,10 +1,39 @@ -use crate::Graph; +use std::collections::BTreeMap; + +use crate::{ContextDelta, Graph, LocusWord, Node, NodeId, OpCode, Port, PortRole, Route}; const CODEC_VERSION: u16 = 1; +const COMMITMENT_DOMAIN: &[u8] = b"l64-native-state-v1\0"; +const MAX_NODES: usize = 1 << 20; +const MAX_PORTS: usize = 1 << 22; +const MAX_CONTEXTS: usize = 1 << 20; +const MAX_ROUTES: usize = 1 << 20; +const MAX_ROUTE_WORDS: usize = 64; +const NO_NODE: NodeId = u32::MAX; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecodeError { + Truncated, + BadMagic, + UnsupportedVersion { version: u16 }, + StructuralBound, + UnknownOpcode { opcode: u16 }, + UnknownPortRole { role: u8 }, + NonZeroPortFlags { flags: u8 }, + InvalidNode, + InvalidPortRange, + InvalidPortTarget, + InvalidPortLaw, + InvalidContext, + InvalidRoute, + DuplicateRoute, + TrailingBytes, + NonCanonical, +} pub fn canonical_bytes(graph: &Graph) -> Vec { let mut out = - Vec::with_capacity(16 + graph.nodes_raw().len() * 24 + graph.ports_raw().len() * 8); + Vec::with_capacity(22 + graph.nodes_raw().len() * 24 + graph.ports_raw().len() * 8); out.extend_from_slice(b"L64N"); out.extend_from_slice(&CODEC_VERSION.to_le_bytes()); push_len(&mut out, graph.nodes_raw().len()); @@ -15,7 +44,7 @@ pub fn canonical_bytes(graph: &Graph) -> Vec { for node in graph.nodes_raw() { out.extend_from_slice(&(node.opcode() as u16).to_le_bytes()); out.extend_from_slice(&node.context().to_le_bytes()); - out.extend_from_slice(&node.ty().unwrap_or(u32::MAX).to_le_bytes()); + out.extend_from_slice(&node.ty().unwrap_or(NO_NODE).to_le_bytes()); out.extend_from_slice(&node.payload().to_le_bytes()); let range = node.port_range(); out.extend_from_slice(&(range.start as u32).to_le_bytes()); @@ -25,13 +54,13 @@ pub fn canonical_bytes(graph: &Graph) -> Vec { for port in graph.ports_raw() { out.extend_from_slice(&port.target().to_le_bytes()); out.push(port.role() as u8); - out.push(0); + out.push(port.flags()); out.extend_from_slice(&port.ordinal().to_le_bytes()); } for context in graph.contexts_raw() { out.extend_from_slice(&context.parent().to_le_bytes()); - out.extend_from_slice(&context.binding().unwrap_or(u32::MAX).to_le_bytes()); + out.extend_from_slice(&context.binding().unwrap_or(NO_NODE).to_le_bytes()); } for (route, node) in graph.routes_raw() { @@ -46,40 +75,260 @@ pub fn canonical_bytes(graph: &Graph) -> Vec { out } +pub fn decode_canonical(bytes: &[u8]) -> Result { + let mut reader = Reader::new(bytes); + if reader.take(4)? != b"L64N" { + return Err(DecodeError::BadMagic); + } + let version = reader.u16()?; + if version != CODEC_VERSION { + return Err(DecodeError::UnsupportedVersion { version }); + } + + let node_count = reader.count(MAX_NODES)?; + let port_count = reader.count(MAX_PORTS)?; + let context_count = reader.count(MAX_CONTEXTS)?; + let route_count = reader.count(MAX_ROUTES)?; + if context_count == 0 || route_count != node_count { + return Err(DecodeError::InvalidContext); + } + + reader.require(node_count, 24)?; + let mut nodes = Vec::with_capacity(node_count); + for _ in 0..node_count { + let raw_opcode = reader.u16()?; + let opcode = OpCode::from_raw(raw_opcode) + .filter(|opcode| opcode.is_persisted_node()) + .ok_or(DecodeError::UnknownOpcode { opcode: raw_opcode })?; + let context = reader.u32()?; + let ty = reader.u32()?; + let payload = reader.u64()?; + let first_port = reader.u32()?; + let port_count = reader.u16()?; + nodes.push(Node::from_raw( + payload, context, ty, first_port, opcode, port_count, + )); + } + + reader.require(port_count, 8)?; + let mut ports = Vec::with_capacity(port_count); + for _ in 0..port_count { + let target = reader.u32()?; + let raw_role = reader.u8()?; + let role = + PortRole::from_raw(raw_role).ok_or(DecodeError::UnknownPortRole { role: raw_role })?; + let flags = reader.u8()?; + if flags != 0 { + return Err(DecodeError::NonZeroPortFlags { flags }); + } + let ordinal = reader.u16()?; + ports.push(Port::from_raw(target, role, flags, ordinal)); + } + + reader.require(context_count, 8)?; + let mut contexts = Vec::with_capacity(context_count); + for _ in 0..context_count { + contexts.push(ContextDelta::from_raw(reader.u32()?, reader.u32()?)); + } + + let mut routes = BTreeMap::new(); + for _ in 0..route_count { + let domain = LocusWord(reader.u64()?); + let tail_len = reader.count(MAX_ROUTE_WORDS)?; + reader.require(tail_len, 8)?; + let mut tail = Vec::with_capacity(tail_len); + for _ in 0..tail_len { + tail.push(LocusWord(reader.u64()?)); + } + let node = reader.u32()?; + let route = Route::from_parts(domain, tail.into_boxed_slice()); + if routes.insert(route, node).is_some() { + return Err(DecodeError::DuplicateRoute); + } + } + + if !reader.is_empty() { + return Err(DecodeError::TrailingBytes); + } + + validate_structure(&nodes, &ports, &contexts, &routes)?; + let commitment = commitment_bytes(bytes); + let graph = Graph::from_decoded_parts(nodes, ports, contexts, routes, commitment); + if canonical_bytes(&graph) != bytes { + return Err(DecodeError::NonCanonical); + } + Ok(graph) +} + pub(crate) fn state_commitment(graph: &Graph) -> [u8; 32] { - // This dependency-free structural stamp is deterministic and adequate for - // transactional change detection. DNA v2 must replace it with the - // repository's domain-separated cryptographic commitment boundary. - let bytes = canonical_bytes(graph); - let seeds = [ - 0xcbf29ce484222325_u64, - 0x84222325cbf29ce4_u64, - 0x9e3779b97f4a7c15_u64, - 0xd6e8feb86659fd93_u64, - ]; - let mut lanes = seeds; - for (index, byte) in bytes.iter().copied().enumerate() { - for (lane_index, lane) in lanes.iter_mut().enumerate() { - let rotated = byte.rotate_left(((index + lane_index) & 7) as u32); - *lane ^= u64::from(rotated) | ((index as u64) << ((lane_index + 1) * 7)); - *lane = lane.wrapping_mul(0x100000001b3_u64 ^ seeds[lane_index]); - *lane ^= *lane >> 29; - *lane = lane.rotate_left((11 + lane_index * 7) as u32); - } - } - let mut out = [0_u8; 32]; - for (index, mut lane) in lanes.into_iter().enumerate() { - lane ^= lane >> 33; - lane = lane.wrapping_mul(0xff51afd7ed558ccd); - lane ^= lane >> 33; - lane = lane.wrapping_mul(0xc4ceb9fe1a85ec53); - lane ^= lane >> 33; - out[index * 8..(index + 1) * 8].copy_from_slice(&lane.to_le_bytes()); + commitment_bytes(&canonical_bytes(graph)) +} + +fn commitment_bytes(bytes: &[u8]) -> [u8; 32] { + let mut hasher = blake3::Hasher::new(); + hasher.update(COMMITMENT_DOMAIN); + hasher.update(bytes); + *hasher.finalize().as_bytes() +} + +fn validate_structure( + nodes: &[Node], + ports: &[Port], + contexts: &[ContextDelta], + routes: &BTreeMap, +) -> Result<(), DecodeError> { + let root = contexts.first().ok_or(DecodeError::InvalidContext)?; + if root.parent() != 0 || root.binding().is_some() { + return Err(DecodeError::InvalidContext); } - out + for (index, context) in contexts.iter().enumerate().skip(1) { + if context.parent() as usize >= index { + return Err(DecodeError::InvalidContext); + } + let binding = context.binding().ok_or(DecodeError::InvalidContext)?; + if binding as usize >= nodes.len() { + return Err(DecodeError::InvalidContext); + } + } + + let mut port_cursor = 0usize; + for (index, node) in nodes.iter().enumerate() { + if node.context() as usize >= contexts.len() { + return Err(DecodeError::InvalidNode); + } + if let Some(ty) = node.ty() { + if ty as usize >= index || !nodes[ty as usize].opcode().is_type() { + return Err(DecodeError::InvalidNode); + } + } else if !node.opcode().is_type() { + return Err(DecodeError::InvalidNode); + } + + let range = node.port_range(); + if range.start != port_cursor || range.end > ports.len() { + return Err(DecodeError::InvalidPortRange); + } + let node_ports = &ports[range.clone()]; + for (ordinal, port) in node_ports.iter().enumerate() { + if port.target() as usize >= index { + return Err(DecodeError::InvalidPortTarget); + } + if port.ordinal() as usize != ordinal { + return Err(DecodeError::InvalidPortLaw); + } + } + validate_port_law(node.opcode(), node_ports, node.ty().is_some())?; + port_cursor = range.end; + } + if port_cursor != ports.len() { + return Err(DecodeError::InvalidPortRange); + } + + let mut covered = vec![false; nodes.len()]; + for node in routes.values().copied() { + let slot = covered + .get_mut(node as usize) + .ok_or(DecodeError::InvalidRoute)?; + if *slot { + return Err(DecodeError::InvalidRoute); + } + *slot = true; + } + if covered.iter().any(|covered| !covered) { + return Err(DecodeError::InvalidRoute); + } + Ok(()) +} + +fn validate_port_law(opcode: OpCode, ports: &[Port], has_type: bool) -> Result<(), DecodeError> { + let valid = match opcode { + OpCode::TypeAtom => ports.is_empty() && !has_type, + OpCode::TypeMatrix => { + ports.len() == 1 && ports[0].role() == PortRole::Parameter && !has_type + } + OpCode::TypeFunction => { + ports.len() == 2 + && ports[0].role() == PortRole::Domain + && ports[1].role() == PortRole::Codomain + && !has_type + } + OpCode::Value => ports.is_empty() && has_type, + OpCode::Compose | OpCode::MatMul => { + ports.len() == 2 + && ports.iter().all(|port| port.role() == PortRole::Argument) + && has_type + } + OpCode::ExtendContext => false, + }; + valid.then_some(()).ok_or(DecodeError::InvalidPortLaw) } fn push_len(out: &mut Vec, len: usize) { let value = u32::try_from(len).expect("native graph section exceeds u32 length"); out.extend_from_slice(&value.to_le_bytes()); } + +struct Reader<'a> { + bytes: &'a [u8], + cursor: usize, +} + +impl<'a> Reader<'a> { + fn new(bytes: &'a [u8]) -> Self { + Self { bytes, cursor: 0 } + } + + fn is_empty(&self) -> bool { + self.cursor == self.bytes.len() + } + + fn take(&mut self, len: usize) -> Result<&'a [u8], DecodeError> { + let end = self.cursor.checked_add(len).ok_or(DecodeError::Truncated)?; + let value = self + .bytes + .get(self.cursor..end) + .ok_or(DecodeError::Truncated)?; + self.cursor = end; + Ok(value) + } + + fn require(&self, count: usize, width: usize) -> Result<(), DecodeError> { + let bytes = count + .checked_mul(width) + .ok_or(DecodeError::StructuralBound)?; + if self.bytes.len().saturating_sub(self.cursor) < bytes { + return Err(DecodeError::Truncated); + } + Ok(()) + } + + fn count(&mut self, maximum: usize) -> Result { + let count = self.u32()? as usize; + if count > maximum { + return Err(DecodeError::StructuralBound); + } + Ok(count) + } + + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes( + self.take(2)?.try_into().expect("fixed-width read"), + )) + } + + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes( + self.take(4)?.try_into().expect("fixed-width read"), + )) + } + + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes( + self.take(8)?.try_into().expect("fixed-width read"), + )) + } +} From 540bc2664dc71feb818d0d284bea0fc34a1380ce Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:51:54 -0700 Subject: [PATCH 22/59] Make primitive native execution proof-carrying --- l64-native/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/l64-native/README.md b/l64-native/README.md index 6067902..af56c15 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -5,7 +5,7 @@ A deliberately small, additive execution spine for Locus64. This crate is not a projection of the legacy `QaEntry` / `RegistryBundle` ontology. It carries: - composed numeric routes; -- one node algebra for types, values, and operations; +- one node algebra for types, values, operations, judgments, and witnesses; - ordered compact ports; - persistent context deltas; - one transition journal; @@ -16,6 +16,8 @@ The first workload boundary proves that lawful typed function composition commit The second boundary adds a bounded native decoder. Canonical bytes must decode, satisfy structural laws, and re-encode byte-for-byte. The decoder rejects malformed node order, invalid port laws, bad contexts, invalid route coverage, unknown opcodes, structural overrun, trailing bytes, and non-canonical ordering. +The third boundary makes primitive execution proof-carrying without adding a receipt schema. Every admitted operation receives a judgment type and kernel witness at routes deterministically composed from the operation route. Generic value insertion cannot construct a witness for a kernel-only judgment. + State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. This remains additive. It does not yet replace the legacy runtime or DNA packet path. From d9f03117b11a796e6e59a336aaf7eff6b6c4e5ea Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:52:25 -0700 Subject: [PATCH 23/59] Prove native witness admission and unforgeability --- l64-native/tests/workloads.rs | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/l64-native/tests/workloads.rs b/l64-native/tests/workloads.rs index 816be6d..77ff234 100644 --- a/l64-native/tests/workloads.rs +++ b/l64-native/tests/workloads.rs @@ -1,4 +1,4 @@ -use l64_native::{Graph, LocusWord, Obstruction, Proposal, ROOT_CONTEXT, Route}; +use l64_native::{Graph, LocusWord, Obstruction, OpCode, Proposal, ROOT_CONTEXT, Route}; fn route(index: u64) -> Route { Route::root(LocusWord(0x4c36344e41544956)).composed(LocusWord(index)) @@ -22,10 +22,15 @@ fn valid_typed_function_composition_commits() { .transact(Proposal::compose(route(9), ROOT_CONTEXT, first, second, ac)) .unwrap(); - assert_eq!(graph.node_count(), before_nodes + 1); + assert_eq!(graph.node_count(), before_nodes + 3); assert_ne!(graph.state_commitment(), before); assert_eq!(graph.node(committed.node).unwrap().ty(), Some(ac)); assert_eq!(graph.ports(committed.node).unwrap().len(), 2); + let evidence = graph.node(committed.evidence).unwrap(); + assert_eq!(evidence.opcode(), OpCode::KernelWitness); + let judgment = evidence.ty().unwrap(); + assert_eq!(graph.node(judgment).unwrap().opcode(), OpCode::TypeJudgment); + assert_eq!(graph.ports(judgment).unwrap().len(), 4); assert_eq!( graph.journal().last().unwrap().after(), committed.commitment @@ -74,3 +79,36 @@ fn invalid_matrix_shape_rejects_without_mutation() { assert_eq!(graph.journal_len(), before_journal); assert!(graph.resolve(&route(17)).is_none()); } + +#[test] +fn kernel_judgment_cannot_be_forged_through_value_insertion() { + let mut graph = Graph::new(); + let a = graph.declare_atom_type(route(31), LocusWord(0x41)).unwrap(); + let b = graph.declare_atom_type(route(32), LocusWord(0x42)).unwrap(); + let c = graph.declare_atom_type(route(33), LocusWord(0x43)).unwrap(); + let ab = graph.declare_function_type(route(34), a, b).unwrap(); + let bc = graph.declare_function_type(route(35), b, c).unwrap(); + let ac = graph.declare_function_type(route(36), a, c).unwrap(); + let first = graph.insert_value(route(37), ROOT_CONTEXT, ab).unwrap(); + let second = graph.insert_value(route(38), ROOT_CONTEXT, bc).unwrap(); + let committed = graph + .transact(Proposal::compose( + route(39), + ROOT_CONTEXT, + first, + second, + ac, + )) + .unwrap(); + let judgment = graph.node(committed.evidence).unwrap().ty().unwrap(); + let before = graph.state_commitment(); + let before_nodes = graph.node_count(); + + assert_eq!( + graph.insert_value(route(40), ROOT_CONTEXT, judgment), + Err(Obstruction::EvidenceOnlyType { node: judgment }) + ); + assert_eq!(graph.state_commitment(), before); + assert_eq!(graph.node_count(), before_nodes); + assert!(graph.resolve(&route(40)).is_none()); +} From 8ad50f6e50f5b3d2daa5b4e1adee702ddf3c8e2b Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:53:08 -0700 Subject: [PATCH 24/59] Carry kernel judgments in the native graph --- l64-native/src/kernel.rs | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/l64-native/src/kernel.rs b/l64-native/src/kernel.rs index c3b61e4..ba2ed3a 100644 --- a/l64-native/src/kernel.rs +++ b/l64-native/src/kernel.rs @@ -1,4 +1,7 @@ -use crate::{ContextId, Graph, NodeId, Route}; +use crate::{ContextId, Graph, LocusWord, NodeId, Route}; + +pub(crate) const JUDGMENT_LOCUS: LocusWord = LocusWord(u64::MAX - 1); +pub(crate) const WITNESS_LOCUS: LocusWord = LocusWord(u64::MAX); #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] @@ -10,6 +13,8 @@ pub enum OpCode { Compose = 5, MatMul = 6, ExtendContext = 7, + TypeJudgment = 8, + KernelWitness = 9, } impl OpCode { @@ -22,17 +27,26 @@ impl OpCode { 5 => Some(Self::Compose), 6 => Some(Self::MatMul), 7 => Some(Self::ExtendContext), + 8 => Some(Self::TypeJudgment), + 9 => Some(Self::KernelWitness), _ => None, } } pub(crate) fn is_type(self) -> bool { - matches!(self, Self::TypeAtom | Self::TypeMatrix | Self::TypeFunction) + matches!( + self, + Self::TypeAtom | Self::TypeMatrix | Self::TypeFunction | Self::TypeJudgment + ) } pub(crate) fn is_persisted_node(self) -> bool { self != Self::ExtendContext } + + pub(crate) fn is_executable(self) -> bool { + matches!(self, Self::Compose | Self::MatMul) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -42,6 +56,9 @@ pub enum PortRole { Domain = 2, Codomain = 3, Argument = 4, + Subject = 5, + Premise = 6, + Conclusion = 7, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -60,6 +77,9 @@ impl PortRole { 2 => Some(Self::Domain), 3 => Some(Self::Codomain), 4 => Some(Self::Argument), + 5 => Some(Self::Subject), + 6 => Some(Self::Premise), + 7 => Some(Self::Conclusion), _ => None, } } @@ -89,13 +109,7 @@ impl Port { } pub fn role(&self) -> PortRole { - match self.role { - 1 => PortRole::Parameter, - 2 => PortRole::Domain, - 3 => PortRole::Codomain, - 4 => PortRole::Argument, - _ => unreachable!("stored port role is constructed internally"), - } + PortRole::from_raw(self.role).expect("stored port role is validated at insertion") } pub fn ordinal(&self) -> u16 { @@ -153,6 +167,7 @@ impl Proposal { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CommitResult { pub node: NodeId, + pub evidence: NodeId, pub event: u32, pub commitment: [u8; 32], } @@ -178,6 +193,9 @@ pub enum Obstruction { MalformedType { node: NodeId, }, + EvidenceOnlyType { + node: NodeId, + }, Arity { expected: u16, actual: u16, @@ -217,6 +235,16 @@ impl Graph { actual: proposal.inputs.len() as u16, }); } + + let judgment_route = proposal.route.composed(JUDGMENT_LOCUS); + let witness_route = proposal.route.composed(WITNESS_LOCUS); + if self.resolve(&proposal.route).is_some() + || self.resolve(&judgment_route).is_some() + || self.resolve(&witness_route).is_some() + { + return Err(Obstruction::RouteOccupied); + } + let left = proposal.inputs[0]; let right = proposal.inputs[1]; let left_node = self.ensure_node(left)?; @@ -279,12 +307,12 @@ impl Graph { _ => unreachable!("public proposal constructors expose executable operations only"), } - self.insert_operation( - proposal.route, + Ok(self.insert_proven_operation( + [proposal.route, judgment_route, witness_route], proposal.context, proposal.output_type, proposal.opcode, &proposal.inputs, - ) + )) } } From 2f48fede7e2121b15295da3ae4476bbe2b9ee70b Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:54:21 -0700 Subject: [PATCH 25/59] Commit primitive operations with intrinsic evidence --- l64-native/src/graph.rs | 83 +++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/l64-native/src/graph.rs b/l64-native/src/graph.rs index 0aa4df6..9022e62 100644 --- a/l64-native/src/graph.rs +++ b/l64-native/src/graph.rs @@ -227,30 +227,81 @@ impl Graph { ty: NodeId, ) -> Result { self.ensure_context(context)?; - self.ensure_type(ty)?; + let type_node = self.ensure_type(ty)?; + if type_node.opcode() == OpCode::TypeJudgment { + return Err(Obstruction::EvidenceOnlyType { node: ty }); + } self.insert_committed_node(route, context, ty, OpCode::Value, 0, &[]) } - pub(crate) fn insert_operation( + pub(crate) fn insert_proven_operation( &mut self, - route: Route, + routes: [Route; 3], context: ContextId, ty: NodeId, opcode: OpCode, inputs: &[NodeId], - ) -> Result { - let ports = inputs - .iter() - .enumerate() - .map(|(ordinal, target)| Port::new(*target, PortRole::Argument, ordinal as u16)) - .collect::>(); - let node = self.insert_committed_node(route, context, ty, opcode, 0, &ports)?; - let event = self.journal.len().saturating_sub(1) as EventId; - Ok(crate::CommitResult { - node, - event, - commitment: self.commitment, - }) + ) -> crate::CommitResult { + let [route, judgment_route, witness_route] = routes; + let before = self.commitment; + + let operation = self.nodes.len() as NodeId; + let operation_first_port = self.ports.len() as u32; + self.ports.extend( + inputs + .iter() + .enumerate() + .map(|(ordinal, target)| Port::new(*target, PortRole::Argument, ordinal as u16)), + ); + self.nodes.push(Node { + payload: 0, + context, + ty, + first_port: operation_first_port, + opcode: opcode as u16, + port_count: inputs.len() as u16, + }); + + let judgment = self.nodes.len() as NodeId; + let judgment_first_port = self.ports.len() as u32; + self.ports.extend([ + Port::new(operation, PortRole::Subject, 0), + Port::new(inputs[0], PortRole::Premise, 1), + Port::new(inputs[1], PortRole::Premise, 2), + Port::new(ty, PortRole::Conclusion, 3), + ]); + self.nodes.push(Node { + payload: opcode as u64, + context, + ty: META_TYPE, + first_port: judgment_first_port, + opcode: OpCode::TypeJudgment as u16, + port_count: 4, + }); + + let evidence = self.nodes.len() as NodeId; + self.nodes.push(Node { + payload: 0, + context, + ty: judgment, + first_port: self.ports.len() as u32, + opcode: OpCode::KernelWitness as u16, + port_count: 0, + }); + + self.routes.insert(route, operation); + self.routes.insert(judgment_route, judgment); + self.routes.insert(witness_route, evidence); + + let after = crate::codec::state_commitment(self); + self.push_event(opcode, operation, before, after); + self.commitment = after; + crate::CommitResult { + node: operation, + evidence, + event: self.journal.len().saturating_sub(1) as EventId, + commitment: after, + } } pub(crate) fn function_parts(&self, ty: NodeId) -> Result<(NodeId, NodeId), Obstruction> { From 3cbef7f70d92864e701396397cbf2e0f938d7c59 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:55:36 -0700 Subject: [PATCH 26/59] Require proof-carrying canonical fixed points --- l64-native/src/codec.rs | 97 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/l64-native/src/codec.rs b/l64-native/src/codec.rs index e846c43..acb958f 100644 --- a/l64-native/src/codec.rs +++ b/l64-native/src/codec.rs @@ -1,9 +1,10 @@ use std::collections::BTreeMap; +use crate::kernel::{JUDGMENT_LOCUS, WITNESS_LOCUS}; use crate::{ContextDelta, Graph, LocusWord, Node, NodeId, OpCode, Port, PortRole, Route}; -const CODEC_VERSION: u16 = 1; -const COMMITMENT_DOMAIN: &[u8] = b"l64-native-state-v1\0"; +const CODEC_VERSION: u16 = 2; +const COMMITMENT_DOMAIN: &[u8] = b"l64-native-state-v2\0"; const MAX_NODES: usize = 1 << 20; const MAX_PORTS: usize = 1 << 22; const MAX_CONTEXTS: usize = 1 << 20; @@ -218,6 +219,7 @@ fn validate_structure( } } validate_port_law(node.opcode(), node_ports, node.ty().is_some())?; + validate_node_semantics(index, node, node_ports, nodes, ports)?; port_cursor = range.end; } if port_cursor != ports.len() { @@ -237,6 +239,88 @@ fn validate_structure( if covered.iter().any(|covered| !covered) { return Err(DecodeError::InvalidRoute); } + validate_evidence_routes(nodes, routes)?; + Ok(()) +} + +fn validate_node_semantics( + index: usize, + node: &Node, + node_ports: &[Port], + nodes: &[Node], + ports: &[Port], +) -> Result<(), DecodeError> { + match node.opcode() { + OpCode::Value => { + let ty = node.ty().ok_or(DecodeError::InvalidNode)?; + if nodes[ty as usize].opcode() == OpCode::TypeJudgment { + return Err(DecodeError::InvalidNode); + } + } + OpCode::KernelWitness => { + let ty = node.ty().ok_or(DecodeError::InvalidNode)?; + if nodes[ty as usize].opcode() != OpCode::TypeJudgment { + return Err(DecodeError::InvalidNode); + } + } + OpCode::TypeJudgment => { + let rule = OpCode::from_raw(node.payload() as u16) + .filter(|opcode| opcode.is_executable()) + .ok_or(DecodeError::InvalidNode)?; + let subject = node_ports[0].target() as usize; + if subject >= index || nodes[subject].opcode() != rule { + return Err(DecodeError::InvalidNode); + } + let subject_node = &nodes[subject]; + if subject_node.ty() != Some(node_ports[3].target()) { + return Err(DecodeError::InvalidNode); + } + let subject_ports = ports + .get(subject_node.port_range()) + .ok_or(DecodeError::InvalidPortRange)?; + if subject_ports.len() != 2 + || subject_ports[0].target() != node_ports[1].target() + || subject_ports[1].target() != node_ports[2].target() + { + return Err(DecodeError::InvalidNode); + } + } + _ => {} + } + Ok(()) +} + +fn validate_evidence_routes( + nodes: &[Node], + routes: &BTreeMap, +) -> Result<(), DecodeError> { + let mut attached = vec![false; nodes.len()]; + for (route, node_id) in routes { + let node = &nodes[*node_id as usize]; + if !node.opcode().is_executable() { + continue; + } + let judgment = *routes + .get(&route.composed(JUDGMENT_LOCUS)) + .ok_or(DecodeError::InvalidRoute)?; + let witness = *routes + .get(&route.composed(WITNESS_LOCUS)) + .ok_or(DecodeError::InvalidRoute)?; + if nodes[judgment as usize].opcode() != OpCode::TypeJudgment + || nodes[witness as usize].opcode() != OpCode::KernelWitness + || nodes[witness as usize].ty() != Some(judgment) + { + return Err(DecodeError::InvalidRoute); + } + attached[judgment as usize] = true; + attached[witness as usize] = true; + } + for (index, node) in nodes.iter().enumerate() { + if matches!(node.opcode(), OpCode::TypeJudgment | OpCode::KernelWitness) && !attached[index] + { + return Err(DecodeError::InvalidRoute); + } + } Ok(()) } @@ -258,6 +342,15 @@ fn validate_port_law(opcode: OpCode, ports: &[Port], has_type: bool) -> Result<( && ports.iter().all(|port| port.role() == PortRole::Argument) && has_type } + OpCode::TypeJudgment => { + ports.len() == 4 + && ports[0].role() == PortRole::Subject + && ports[1].role() == PortRole::Premise + && ports[2].role() == PortRole::Premise + && ports[3].role() == PortRole::Conclusion + && !has_type + } + OpCode::KernelWitness => ports.is_empty() && has_type, OpCode::ExtendContext => false, }; valid.then_some(()).ok_or(DecodeError::InvalidPortLaw) From aec745ea5f7bf7d4d277219acd0897f7e7ea8808 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:57:51 -0700 Subject: [PATCH 27/59] Refresh native core scope documentation From cb11a8009c5eb1ca3ac270357a6bf7c53d53b8ab Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:58:04 -0700 Subject: [PATCH 28/59] Document native pass 3 closure From b41345c12d84e87e343e4958b8e28120a200dcc9 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:58:16 -0700 Subject: [PATCH 29/59] No-op guard From 89b21526bf4fc052f5e6a61238bbc085330fa03e Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:05:08 -0700 Subject: [PATCH 30/59] Add native DNA frame boundary --- l64-native/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/l64-native/README.md b/l64-native/README.md index af56c15..512dc17 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -21,3 +21,5 @@ The third boundary makes primitive execution proof-carrying without adding a rec State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. This remains additive. It does not yet replace the legacy runtime or DNA packet path. + +The fourth boundary adds a native DNA frame with a fixed 44-byte binary header, bounded canonical payload, embedded domain-separated BLAKE3 commitment, and exact DNA decode/re-encode fixed point. The frame contains no string metadata or legacy record payload. From c5a62b1d2bd3c32ed6eee0f691069d10b560fb76 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:05:18 -0700 Subject: [PATCH 31/59] Expose native DNA encoding and decoding --- l64-native/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/l64-native/src/lib.rs b/l64-native/src/lib.rs index 0809376..21f6e66 100644 --- a/l64-native/src/lib.rs +++ b/l64-native/src/lib.rs @@ -2,6 +2,7 @@ mod codec; mod context; +mod dna; mod graph; mod journal; mod kernel; @@ -9,6 +10,7 @@ mod route; pub use codec::{DecodeError, canonical_bytes, decode_canonical}; pub use context::ContextDelta; +pub use dna::{DnaError, MAX_NATIVE_DNA_PAYLOAD_BYTES, decode_dna, dna_bytes}; pub use graph::{ContextId, EventId, Graph, Node, NodeId, ROOT_CONTEXT}; pub use journal::JournalEvent; pub use kernel::{CommitResult, Obstruction, OpCode, Port, PortRole, Proposal}; From 30b36aab1a2b6df36d84a5a642656dc75c94b61b Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:05:38 -0700 Subject: [PATCH 32/59] Implement compact native DNA frame --- l64-native/src/dna.rs | 93 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 l64-native/src/dna.rs diff --git a/l64-native/src/dna.rs b/l64-native/src/dna.rs new file mode 100644 index 0000000..55717cd --- /dev/null +++ b/l64-native/src/dna.rs @@ -0,0 +1,93 @@ +use crate::{DecodeError, Graph, canonical_bytes, decode_canonical}; + +const DNA_MAGIC: &[u8; 4] = b"L64D"; +const DNA_VERSION: u16 = 1; +const DNA_FLAGS: u16 = 0; +const DNA_COMMITMENT_DOMAIN: &[u8] = b"l64-native-dna-v1\0"; +const DNA_HEADER_BYTES: usize = 44; +pub const MAX_NATIVE_DNA_PAYLOAD_BYTES: usize = 1 << 28; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DnaError { + PayloadTooLarge, + Truncated, + BadMagic, + UnsupportedVersion { version: u16 }, + UnsupportedFlags { flags: u16 }, + TrailingBytes, + CommitmentMismatch, + Canonical(DecodeError), +} + +impl From for DnaError { + fn from(value: DecodeError) -> Self { + Self::Canonical(value) + } +} + +pub fn dna_bytes(graph: &Graph) -> Result, DnaError> { + let payload = canonical_bytes(graph); + if payload.len() > MAX_NATIVE_DNA_PAYLOAD_BYTES || payload.len() > u32::MAX as usize { + return Err(DnaError::PayloadTooLarge); + } + + let mut out = Vec::with_capacity(DNA_HEADER_BYTES + payload.len()); + out.extend_from_slice(DNA_MAGIC); + out.extend_from_slice(&DNA_VERSION.to_le_bytes()); + out.extend_from_slice(&DNA_FLAGS.to_le_bytes()); + out.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + out.extend_from_slice(&dna_commitment(&payload)); + out.extend_from_slice(&payload); + Ok(out) +} + +pub fn decode_dna(bytes: &[u8]) -> Result { + if bytes.len() < DNA_HEADER_BYTES { + return Err(DnaError::Truncated); + } + if &bytes[..4] != DNA_MAGIC { + return Err(DnaError::BadMagic); + } + + let version = u16::from_le_bytes(bytes[4..6].try_into().expect("fixed DNA version field")); + if version != DNA_VERSION { + return Err(DnaError::UnsupportedVersion { version }); + } + let flags = u16::from_le_bytes(bytes[6..8].try_into().expect("fixed DNA flags field")); + if flags != DNA_FLAGS { + return Err(DnaError::UnsupportedFlags { flags }); + } + let payload_len = + u32::from_le_bytes(bytes[8..12].try_into().expect("fixed DNA length field")) as usize; + if payload_len > MAX_NATIVE_DNA_PAYLOAD_BYTES { + return Err(DnaError::PayloadTooLarge); + } + + let expected_len = DNA_HEADER_BYTES + .checked_add(payload_len) + .ok_or(DnaError::PayloadTooLarge)?; + if bytes.len() < expected_len { + return Err(DnaError::Truncated); + } + if bytes.len() > expected_len { + return Err(DnaError::TrailingBytes); + } + + let stored_commitment: [u8; 32] = bytes[12..44] + .try_into() + .expect("fixed DNA commitment field"); + let payload = &bytes[DNA_HEADER_BYTES..]; + let computed_commitment = dna_commitment(payload); + if stored_commitment != computed_commitment { + return Err(DnaError::CommitmentMismatch); + } + + Ok(decode_canonical(payload)?) +} + +fn dna_commitment(payload: &[u8]) -> [u8; 32] { + let mut hasher = blake3::Hasher::new(); + hasher.update(DNA_COMMITMENT_DOMAIN); + hasher.update(payload); + *hasher.finalize().as_bytes() +} From 70260ca1172689be46c1bcd814da976f66e164b6 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:05:57 -0700 Subject: [PATCH 33/59] Prove native DNA fixed point and tamper rejection --- l64-native/tests/dna.rs | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 l64-native/tests/dna.rs diff --git a/l64-native/tests/dna.rs b/l64-native/tests/dna.rs new file mode 100644 index 0000000..7e4c45a --- /dev/null +++ b/l64-native/tests/dna.rs @@ -0,0 +1,79 @@ +use l64_native::{ + DnaError, Graph, LocusWord, Proposal, ROOT_CONTEXT, Route, decode_dna, dna_bytes, +}; + +fn route(index: u64) -> Route { + Route::root(LocusWord(0x4c36344e41544956)).composed(LocusWord(index)) +} + +fn proven_graph() -> Graph { + let mut graph = Graph::new(); + let a = graph.declare_atom_type(route(1), LocusWord(0x41)).unwrap(); + let b = graph.declare_atom_type(route(2), LocusWord(0x42)).unwrap(); + let c = graph.declare_atom_type(route(3), LocusWord(0x43)).unwrap(); + let ab = graph.declare_function_type(route(4), a, b).unwrap(); + let bc = graph.declare_function_type(route(5), b, c).unwrap(); + let ac = graph.declare_function_type(route(6), a, c).unwrap(); + let first = graph.insert_value(route(7), ROOT_CONTEXT, ab).unwrap(); + let second = graph.insert_value(route(8), ROOT_CONTEXT, bc).unwrap(); + graph + .transact(Proposal::compose(route(9), ROOT_CONTEXT, first, second, ac)) + .unwrap(); + graph +} + +#[test] +fn native_dna_roundtrip_is_exact_fixed_point() { + let graph = proven_graph(); + let dna = dna_bytes(&graph).unwrap(); + let decoded = decode_dna(&dna).unwrap(); + + assert_eq!(dna_bytes(&decoded).unwrap(), dna); + assert_eq!(decoded.state_commitment(), graph.state_commitment()); + assert_eq!(decoded.journal_len(), 0); + assert!(!dna.windows(b"claim".len()).any(|window| window == b"claim")); + assert!( + !dna.windows(b"theorem".len()) + .any(|window| window == b"theorem") + ); +} + +#[test] +fn native_dna_rejects_payload_tampering() { + let mut dna = dna_bytes(&proven_graph()).unwrap(); + let last = dna.len() - 1; + dna[last] ^= 1; + assert_eq!(decode_dna(&dna).unwrap_err(), DnaError::CommitmentMismatch); +} + +#[test] +fn native_dna_rejects_unknown_header_state() { + let original = dna_bytes(&proven_graph()).unwrap(); + + let mut version = original.clone(); + version[4..6].copy_from_slice(&2u16.to_le_bytes()); + assert_eq!( + decode_dna(&version).unwrap_err(), + DnaError::UnsupportedVersion { version: 2 } + ); + + let mut flags = original; + flags[6..8].copy_from_slice(&1u16.to_le_bytes()); + assert_eq!( + decode_dna(&flags).unwrap_err(), + DnaError::UnsupportedFlags { flags: 1 } + ); +} + +#[test] +fn native_dna_rejects_length_mismatch() { + let dna = dna_bytes(&proven_graph()).unwrap(); + assert_eq!( + decode_dna(&dna[..dna.len() - 1]).unwrap_err(), + DnaError::Truncated + ); + + let mut trailing = dna; + trailing.push(0); + assert_eq!(decode_dna(&trailing).unwrap_err(), DnaError::TrailingBytes); +} From d1850250452f2a555cf228900af4b0f92cd7c98a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:36:00 -0700 Subject: [PATCH 34/59] Expose compact native RNA ingress --- l64-native/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/l64-native/src/lib.rs b/l64-native/src/lib.rs index 21f6e66..161cbf5 100644 --- a/l64-native/src/lib.rs +++ b/l64-native/src/lib.rs @@ -6,6 +6,7 @@ mod dna; mod graph; mod journal; mod kernel; +mod rna; mod route; pub use codec::{DecodeError, canonical_bytes, decode_canonical}; @@ -14,4 +15,7 @@ pub use dna::{DnaError, MAX_NATIVE_DNA_PAYLOAD_BYTES, decode_dna, dna_bytes}; pub use graph::{ContextId, EventId, Graph, Node, NodeId, ROOT_CONTEXT}; pub use journal::JournalEvent; pub use kernel::{CommitResult, Obstruction, OpCode, Port, PortRole, Proposal}; +pub use rna::{ + MAX_NATIVE_RNA_BYTES, RnaError, compile_rna, dna_to_rna, normalize_rna, rna_bytes, rna_to_dna, +}; pub use route::{LocusWord, Route}; From 52a7f75545f1f6898421c9786969fabbed5179a0 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:37:11 -0700 Subject: [PATCH 35/59] Add compact native RNA compiler and sequencer --- l64-native/src/rna.rs | 387 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 l64-native/src/rna.rs diff --git a/l64-native/src/rna.rs b/l64-native/src/rna.rs new file mode 100644 index 0000000..1fe898c --- /dev/null +++ b/l64-native/src/rna.rs @@ -0,0 +1,387 @@ +use std::collections::BTreeMap; + +use crate::{ + DnaError, Graph, LocusWord, NodeId, Obstruction, OpCode, Proposal, ROOT_CONTEXT, Route, + decode_dna, dna_bytes, +}; + +const RNA_MAGIC: &[u8] = b"L64R1"; +pub const MAX_NATIVE_RNA_BYTES: usize = 1 << 24; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RnaError { + SourceTooLarge, + Empty, + BadHeader, + InvalidArity { line: u32 }, + InvalidNumber { line: u32 }, + UnknownInstruction { line: u32 }, + SlotOrder { line: u32, slot: u64 }, + UnknownSlot { line: u32, slot: u64 }, + UnrepresentableGraph, + Graph(Obstruction), + Dna(DnaError), +} + +impl From for RnaError { + fn from(value: Obstruction) -> Self { + Self::Graph(value) + } +} + +impl From for RnaError { + fn from(value: DnaError) -> Self { + Self::Dna(value) + } +} + +pub fn compile_rna(source: &[u8]) -> Result { + if source.len() > MAX_NATIVE_RNA_BYTES { + return Err(RnaError::SourceTooLarge); + } + + let mut lines = source.split(|byte| *byte == b'\n').enumerate(); + let (header_line, header) = next_nonempty(&mut lines).ok_or(RnaError::Empty)?; + let header_tokens = tokens(header); + if header_tokens.len() != 2 || header_tokens[0] != RNA_MAGIC { + return Err(RnaError::BadHeader); + } + let domain = parse_u64(header_tokens[1]).ok_or(RnaError::InvalidNumber { + line: line_number(header_line), + })?; + + let root = Route::root(LocusWord(domain)); + let mut graph = Graph::new(); + let mut slots = BTreeMap::new(); + let mut last_slot = None; + let mut instruction_count = 0usize; + + for (line_index, raw_line) in lines { + let line = trim_ascii(raw_line); + if line.is_empty() { + continue; + } + let parts = tokens(line); + let line = line_number(line_index); + let instruction = parts + .first() + .copied() + .ok_or(RnaError::InvalidArity { line })?; + let slot = parse_part(&parts, 1, line)?; + if let Some(previous) = last_slot + && slot <= previous + { + return Err(RnaError::SlotOrder { line, slot }); + } + let route = root.composed(LocusWord(slot)); + + let node = match instruction { + b"a" if parts.len() == 3 => { + graph.declare_atom_type(route, LocusWord(parse_part(&parts, 2, line)?))? + } + b"m" if parts.len() == 5 => { + let element = resolve_part(&slots, &parts, 2, line)?; + let rows = parse_u32_part(&parts, 3, line)?; + let cols = parse_u32_part(&parts, 4, line)?; + graph.declare_matrix_type(route, element, rows, cols)? + } + b"f" if parts.len() == 4 => { + let domain = resolve_part(&slots, &parts, 2, line)?; + let codomain = resolve_part(&slots, &parts, 3, line)?; + graph.declare_function_type(route, domain, codomain)? + } + b"v" if parts.len() == 3 => { + let ty = resolve_part(&slots, &parts, 2, line)?; + graph.insert_value(route, ROOT_CONTEXT, ty)? + } + b"c" if parts.len() == 5 => { + let first = resolve_part(&slots, &parts, 2, line)?; + let second = resolve_part(&slots, &parts, 3, line)?; + let output = resolve_part(&slots, &parts, 4, line)?; + graph + .transact(Proposal::compose( + route, + ROOT_CONTEXT, + first, + second, + output, + ))? + .node + } + b"x" if parts.len() == 5 => { + let left = resolve_part(&slots, &parts, 2, line)?; + let right = resolve_part(&slots, &parts, 3, line)?; + let output = resolve_part(&slots, &parts, 4, line)?; + graph + .transact(Proposal::matrix_multiply( + route, + ROOT_CONTEXT, + left, + right, + output, + ))? + .node + } + b"a" | b"m" | b"f" | b"v" | b"c" | b"x" => { + return Err(RnaError::InvalidArity { line }); + } + _ => return Err(RnaError::UnknownInstruction { line }), + }; + + slots.insert(slot, node); + last_slot = Some(slot); + instruction_count += 1; + } + + if instruction_count == 0 { + return Err(RnaError::Empty); + } + Ok(graph) +} + +pub fn normalize_rna(source: &[u8]) -> Result, RnaError> { + rna_bytes(&compile_rna(source)?) +} + +pub fn rna_to_dna(source: &[u8]) -> Result, RnaError> { + Ok(dna_bytes(&compile_rna(source)?)?) +} + +pub fn dna_to_rna(source: &[u8]) -> Result, RnaError> { + rna_bytes(&decode_dna(source)?) +} + +pub fn rna_bytes(graph: &Graph) -> Result, RnaError> { + if graph.context_count() != 1 { + return Err(RnaError::UnrepresentableGraph); + } + + let mut domain = None; + let mut authored = BTreeMap::new(); + for (route, node) in graph.routes_raw() { + if route.tail().len() != 1 { + continue; + } + match domain { + Some(existing) if existing != route.domain() => { + return Err(RnaError::UnrepresentableGraph); + } + None => domain = Some(route.domain()), + _ => {} + } + if authored.insert(route.tail()[0].0, *node).is_some() { + return Err(RnaError::UnrepresentableGraph); + } + } + + let domain = domain.ok_or(RnaError::Empty)?; + let expected_authored = graph + .nodes_raw() + .iter() + .filter(|node| !matches!(node.opcode(), OpCode::TypeJudgment | OpCode::KernelWitness)) + .count(); + if authored.len() != expected_authored { + return Err(RnaError::UnrepresentableGraph); + } + + let mut slots = BTreeMap::new(); + for (slot, node) in &authored { + if slots.insert(*node, *slot).is_some() { + return Err(RnaError::UnrepresentableGraph); + } + } + + let mut out = Vec::with_capacity(32 + authored.len() * 24); + out.extend_from_slice(RNA_MAGIC); + out.push(b' '); + push_hex(&mut out, domain.0); + out.push(b'\n'); + + for (slot, node_id) in authored { + let node = graph.node(node_id).ok_or(RnaError::UnrepresentableGraph)?; + if node.context() != ROOT_CONTEXT { + return Err(RnaError::UnrepresentableGraph); + } + let ports = graph.ports(node_id).ok_or(RnaError::UnrepresentableGraph)?; + match node.opcode() { + OpCode::TypeAtom if ports.is_empty() && node.ty().is_none() => { + push_prefix(&mut out, b'a', slot); + push_space_hex(&mut out, node.payload()); + } + OpCode::TypeMatrix if ports.len() == 1 && node.ty().is_none() => { + push_prefix(&mut out, b'm', slot); + push_space_decimal(&mut out, slot_for(&slots, ports[0].target())?); + push_space_decimal(&mut out, node.payload() >> 32); + push_space_decimal(&mut out, node.payload() as u32 as u64); + } + OpCode::TypeFunction if ports.len() == 2 && node.ty().is_none() => { + push_prefix(&mut out, b'f', slot); + push_space_decimal(&mut out, slot_for(&slots, ports[0].target())?); + push_space_decimal(&mut out, slot_for(&slots, ports[1].target())?); + } + OpCode::Value if ports.is_empty() => { + push_prefix(&mut out, b'v', slot); + push_space_decimal( + &mut out, + slot_for(&slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, + ); + } + OpCode::Compose | OpCode::MatMul if ports.len() == 2 => { + push_prefix( + &mut out, + if node.opcode() == OpCode::Compose { + b'c' + } else { + b'x' + }, + slot, + ); + push_space_decimal(&mut out, slot_for(&slots, ports[0].target())?); + push_space_decimal(&mut out, slot_for(&slots, ports[1].target())?); + push_space_decimal( + &mut out, + slot_for(&slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, + ); + } + _ => return Err(RnaError::UnrepresentableGraph), + } + out.push(b'\n'); + } + + Ok(out) +} + +fn next_nonempty<'a>( + lines: &mut impl Iterator, +) -> Option<(usize, &'a [u8])> { + lines.find_map(|(index, line)| { + let line = trim_ascii(line); + (!line.is_empty()).then_some((index, line)) + }) +} + +fn trim_ascii(mut bytes: &[u8]) -> &[u8] { + while bytes.first().is_some_and(u8::is_ascii_whitespace) { + bytes = &bytes[1..]; + } + while bytes.last().is_some_and(u8::is_ascii_whitespace) { + bytes = &bytes[..bytes.len() - 1]; + } + bytes +} + +fn tokens(line: &[u8]) -> Vec<&[u8]> { + line.split(|byte| byte.is_ascii_whitespace()) + .filter(|part| !part.is_empty()) + .collect() +} + +fn parse_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + parts + .get(index) + .and_then(|part| parse_u64(part)) + .ok_or(RnaError::InvalidNumber { line }) +} + +fn parse_u32_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + parse_part(parts, index, line)? + .try_into() + .map_err(|_| RnaError::InvalidNumber { line }) +} + +fn resolve_part( + slots: &BTreeMap, + parts: &[&[u8]], + index: usize, + line: u32, +) -> Result { + let slot = parse_part(parts, index, line)?; + slots + .get(&slot) + .copied() + .ok_or(RnaError::UnknownSlot { line, slot }) +} + +fn parse_u64(bytes: &[u8]) -> Option { + let (radix, digits) = if bytes.starts_with(b"0x") || bytes.starts_with(b"0X") { + (16u64, &bytes[2..]) + } else { + (10u64, bytes) + }; + if digits.is_empty() { + return None; + } + digits.iter().try_fold(0u64, |value, byte| { + let digit = match *byte { + b'0'..=b'9' => u64::from(*byte - b'0'), + b'a'..=b'f' if radix == 16 => u64::from(*byte - b'a' + 10), + b'A'..=b'F' if radix == 16 => u64::from(*byte - b'A' + 10), + _ => return None, + }; + if digit >= radix { + return None; + } + value.checked_mul(radix)?.checked_add(digit) + }) +} + +fn slot_for(slots: &BTreeMap, node: NodeId) -> Result { + slots + .get(&node) + .copied() + .ok_or(RnaError::UnrepresentableGraph) +} + +fn push_prefix(out: &mut Vec, opcode: u8, slot: u64) { + out.push(opcode); + push_space_decimal(out, slot); +} + +fn push_space_decimal(out: &mut Vec, value: u64) { + out.push(b' '); + push_decimal(out, value); +} + +fn push_space_hex(out: &mut Vec, value: u64) { + out.push(b' '); + push_hex(out, value); +} + +fn push_decimal(out: &mut Vec, mut value: u64) { + let mut buffer = [0u8; 20]; + let mut cursor = buffer.len(); + loop { + cursor -= 1; + buffer[cursor] = b'0' + (value % 10) as u8; + value /= 10; + if value == 0 { + break; + } + } + out.extend_from_slice(&buffer[cursor..]); +} + +fn push_hex(out: &mut Vec, mut value: u64) { + out.extend_from_slice(b"0x"); + if value == 0 { + out.push(b'0'); + return; + } + let mut buffer = [0u8; 16]; + let mut cursor = buffer.len(); + while value != 0 { + cursor -= 1; + let digit = (value & 0xf) as u8; + buffer[cursor] = if digit < 10 { + b'0' + digit + } else { + b'a' + digit - 10 + }; + value >>= 4; + } + out.extend_from_slice(&buffer[cursor..]); +} + +fn line_number(index: usize) -> u32 { + u32::try_from(index + 1).unwrap_or(u32::MAX) +} From 2e27aed171763e04a8a796668e16253436865e41 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:37:37 -0700 Subject: [PATCH 36/59] Prove native RNA and DNA fixed points --- l64-native/tests/rna.rs | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 l64-native/tests/rna.rs diff --git a/l64-native/tests/rna.rs b/l64-native/tests/rna.rs new file mode 100644 index 0000000..e4a505c --- /dev/null +++ b/l64-native/tests/rna.rs @@ -0,0 +1,56 @@ +use l64_native::{Obstruction, RnaError, compile_rna, dna_to_rna, normalize_rna, rna_to_dna}; + +const CANONICAL_COMPOSE: &[u8] = b"L64R1 0x4c36344e41544956\na 1 0x41\na 2 0x42\na 3 0x43\nf 4 1 2\nf 5 2 3\nf 6 1 3\nv 7 4\nv 8 5\nc 9 7 8 6\n"; + +#[test] +fn native_rna_compiles_and_reaches_exact_dna_fixed_point() { + let graph = compile_rna(CANONICAL_COMPOSE).unwrap(); + assert_eq!(graph.node_count(), 11); + + let dna = rna_to_dna(CANONICAL_COMPOSE).unwrap(); + let sequenced = dna_to_rna(&dna).unwrap(); + assert_eq!(sequenced, CANONICAL_COMPOSE); + assert_eq!(rna_to_dna(&sequenced).unwrap(), dna); +} + +#[test] +fn native_rna_normalization_removes_surface_variation() { + let source = b"\n L64R1 0X4C36344E41544956\n a 1 0X41\n a 2 0x42\n a 3 0x43\n f 4 1 2\n f 5 2 3\n f 6 1 3\n v 7 4\n v 8 5\n c 9 7 8 6\n\n"; + assert_eq!(normalize_rna(source).unwrap(), CANONICAL_COMPOSE); +} + +#[test] +fn invalid_matrix_source_refuses_before_dna_exists() { + let source = b"L64R1 0x4c36344e41544956\na 1 0x52\nm 2 1 2 3\nm 3 1 4 2\nm 4 1 2 2\nv 5 2\nv 6 3\nx 7 5 6 4\n"; + assert_eq!( + rna_to_dna(source), + Err(RnaError::Graph(Obstruction::MatrixShapeMismatch { + left_cols: 3, + right_rows: 4, + })) + ); +} + +#[test] +fn native_rna_rejects_unknown_and_non_topological_slots() { + let unknown = b"L64R1 0x1\na 1 0x41\nf 2 1 9\n"; + assert_eq!( + compile_rna(unknown).unwrap_err(), + RnaError::UnknownSlot { line: 3, slot: 9 } + ); + + let unordered = b"L64R1 0x1\na 2 0x41\na 1 0x42\n"; + assert_eq!( + compile_rna(unordered).unwrap_err(), + RnaError::SlotOrder { line: 3, slot: 1 } + ); +} + +#[test] +fn native_rna_rejects_record_like_or_named_instructions() { + let source = b"L64R1 0x1\nclaim 1 2\n"; + assert_eq!( + compile_rna(source).unwrap_err(), + RnaError::UnknownInstruction { line: 2 } + ); +} From a0f99a9daececce8e9de5fbd24bdfd38722c1cbd Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:37:54 -0700 Subject: [PATCH 37/59] Put native migration rail under Commander --- LOCUS64_NATIVE_RAIL.athens | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LOCUS64_NATIVE_RAIL.athens diff --git a/LOCUS64_NATIVE_RAIL.athens b/LOCUS64_NATIVE_RAIL.athens new file mode 100644 index 0000000..972524a --- /dev/null +++ b/LOCUS64_NATIVE_RAIL.athens @@ -0,0 +1,21 @@ +ATHENS_DEVELOPMENT_RAIL v1 +field=key=schema_version;value=1 +field=key=rail_version;value=5 +field=key=current_stage;value=native-rna-pilot +field=key=next_stage;value=native-cli-membrane +field=key=projection_authority;value=non_authoritative +stage=id=native-rna-pilot +stage_field=stage=native-rna-pilot;key=status;value=current +stage_field=stage=native-rna-pilot;key=required_gates;value=native-rna-pilot-green +stage=id=native-cli-membrane +stage_field=stage=native-cli-membrane;key=status;value=next +stage_field=stage=native-cli-membrane;key=depends_on;value=native-rna-pilot +stage_field=stage=native-cli-membrane;key=required_gates;value=native-cli-membrane-green +stage=id=legacy-demotion +stage_field=stage=legacy-demotion;key=status;value=planned +stage_field=stage=legacy-demotion;key=depends_on;value=native-cli-membrane +stage_field=stage=legacy-demotion;key=required_gates;value=legacy-demotion-green +gate=id=native-rna-pilot-green +gate=id=native-cli-membrane-green +gate=id=legacy-demotion-green +END From ad80a8c344a92a857170994ac0dd0f52a41abfc5 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:38:14 -0700 Subject: [PATCH 38/59] Document governed native RNA boundary --- l64-native/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/l64-native/README.md b/l64-native/README.md index 512dc17..8fd4560 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -18,8 +18,10 @@ The second boundary adds a bounded native decoder. Canonical bytes must decode, The third boundary makes primitive execution proof-carrying without adding a receipt schema. Every admitted operation receives a judgment type and kernel witness at routes deterministically composed from the operation route. Generic value insertion cannot construct a witness for a kernel-only judgment. -State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. +The fourth boundary adds a native DNA frame with a fixed 44-byte binary header, bounded canonical payload, embedded domain-separated BLAKE3 commitment, and exact DNA decode/re-encode fixed point. The frame contains no string metadata or legacy record payload. -This remains additive. It does not yet replace the legacy runtime or DNA packet path. +The fifth boundary adds a compact authored RNA ingress. `L64R1` uses one declared domain and strictly increasing numeric local slots; routes are composed as `(domain, slot)`. Six one-byte instructions lower directly through the existing graph and transaction APIs: atom, matrix, function, value, composition, and matrix multiplication. Sequencing omits intrinsic evidence nodes because their routes and structure are deterministically derived. -The fourth boundary adds a native DNA frame with a fixed 44-byte binary header, bounded canonical payload, embedded domain-separated BLAKE3 commitment, and exact DNA decode/re-encode fixed point. The frame contains no string metadata or legacy record payload. +State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. + +This remains additive. It does not yet replace the legacy runtime or old DNA packet path. The Commander-held rail names the next cutover stage. From 2b50943c2620b93707c9bb69b4ae800482210b33 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:50:24 -0700 Subject: [PATCH 39/59] Route l64-cli through native membrane --- l64-cli/Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/l64-cli/Cargo.toml b/l64-cli/Cargo.toml index 6e1a8f1..20f6a04 100644 --- a/l64-cli/Cargo.toml +++ b/l64-cli/Cargo.toml @@ -2,6 +2,11 @@ name = "l64-cli" version = "0.1.0" edition = "2024" +autobins = false + +[[bin]] +name = "l64-cli" +path = "src/native_main.rs" [dependencies] anyhow = { workspace = true } @@ -19,6 +24,7 @@ l64-atlas = { path = "../l64-atlas" } l64-cert = { path = "../l64-cert" } l64-bundle = { path = "../l64-bundle" } l64-policy = { path = "../l64-policy" } +l64-native = { path = "../l64-native" } serde_json = { workspace = true } bincode = { workspace = true } hex = { workspace = true } From 09b343cd758db3709d8075b508506703faa71c18 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:50:33 -0700 Subject: [PATCH 40/59] Add in-process native CLI membrane --- l64-cli/src/native_main.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 l64-cli/src/native_main.rs diff --git a/l64-cli/src/native_main.rs b/l64-cli/src/native_main.rs new file mode 100644 index 0000000..8cdd550 --- /dev/null +++ b/l64-cli/src/native_main.rs @@ -0,0 +1,17 @@ +mod native_membrane; + +mod legacy { + include!("main.rs"); + + pub(super) fn run() -> anyhow::Result<()> { + main() + } +} + +fn main() -> anyhow::Result<()> { + if native_membrane::run_env().map_err(anyhow::Error::msg)? { + Ok(()) + } else { + legacy::run() + } +} From b5ceccda8f6a6b700c22fef5b66fff171690f5b3 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:51:23 -0700 Subject: [PATCH 41/59] Add structural-magic native command router --- l64-cli/src/native_membrane.rs | 198 +++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 l64-cli/src/native_membrane.rs diff --git a/l64-cli/src/native_membrane.rs b/l64-cli/src/native_membrane.rs new file mode 100644 index 0000000..629b875 --- /dev/null +++ b/l64-cli/src/native_membrane.rs @@ -0,0 +1,198 @@ +use std::{ + ffi::OsString, + fs, + io::Write, + path::{Path, PathBuf}, +}; + +pub(super) fn run_env() -> Result { + let args = std::env::args_os().collect::>(); + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + dispatch(&args, &mut stdout) +} + +pub(crate) fn dispatch(args: &[OsString], stdout: &mut impl Write) -> Result { + let Some(command) = args.get(1).and_then(|item| item.to_str()) else { + return Ok(false); + }; + let Some(file) = args.get(2).map(PathBuf::from) else { + return Ok(false); + }; + + match command { + "normalize-rna" => normalize(args, &file, stdout), + "compile-rna" => compile(args, &file, stdout), + "sequence-dna" => sequence(args, &file, stdout), + "verify-roundtrip" => verify(args, &file, stdout), + _ => Ok(false), + } +} + +fn normalize(args: &[OsString], file: &Path, stdout: &mut impl Write) -> Result { + let source = read(file)?; + if !is_native_rna(&source) { + return Ok(false); + } + require_arity(args, 3, "normalize-rna ")?; + let normalized = l64_native::normalize_rna(&source).map_err(native_error)?; + stdout.write_all(&normalized).map_err(io_error)?; + Ok(true) +} + +fn compile(args: &[OsString], file: &Path, stdout: &mut impl Write) -> Result { + let source = read(file)?; + if !is_native_rna(&source) { + return Ok(false); + } + let options = CompileOptions::parse(&args[3..])?; + let dna = l64_native::rna_to_dna(&source).map_err(native_error)?; + let out = options.out.unwrap_or_else(|| file.with_extension("dna")); + fs::write(&out, dna) + .map_err(|error| format!("failed to write `{}`: {error}", out.display()))?; + writeln!(stdout, "{}", out.display()).map_err(io_error)?; + Ok(true) +} + +fn sequence(args: &[OsString], file: &Path, stdout: &mut impl Write) -> Result { + let dna = read(file)?; + if !dna.starts_with(b"L64D") { + return Ok(false); + } + require_arity(args, 3, "sequence-dna ")?; + let rna = l64_native::dna_to_rna(&dna).map_err(native_error)?; + stdout.write_all(&rna).map_err(io_error)?; + Ok(true) +} + +fn verify(args: &[OsString], file: &Path, stdout: &mut impl Write) -> Result { + let source = read(file)?; + if !is_native_rna(&source) { + return Ok(false); + } + VerifyOptions::parse(&args[3..])?; + let first_dna = l64_native::rna_to_dna(&source).map_err(native_error)?; + let canonical_rna = l64_native::dna_to_rna(&first_dna).map_err(native_error)?; + let second_dna = l64_native::rna_to_dna(&canonical_rna).map_err(native_error)?; + if first_dna != second_dna { + return Err("native RNA/DNA fixed point failed".into()); + } + stdout + .write_all(b"native RNA/DNA fixed point verified\n") + .map_err(io_error)?; + Ok(true) +} + +#[derive(Default)] +struct CompileOptions { + out: Option, +} + +impl CompileOptions { + fn parse(args: &[OsString]) -> Result { + let mut options = Self::default(); + let mut index = 0; + while index < args.len() { + let argument = args[index].to_string_lossy(); + match argument.as_ref() { + "--out" => { + index += 1; + let value = args + .get(index) + .ok_or_else(|| "--out requires a path".to_string())?; + options.out = Some(PathBuf::from(value)); + } + value if value.starts_with("--out=") => { + options.out = Some(PathBuf::from(&value[6..])); + } + "--artifact-class" => { + index += 1; + let value = args + .get(index) + .and_then(|item| item.to_str()) + .ok_or_else(|| "--artifact-class requires a value".to_string())?; + require_gene(value)?; + } + value if value.starts_with("--artifact-class=") => { + require_gene(&value[17..])?; + } + "--persist-lineage" => { + return Err("--persist-lineage belongs to the legacy named-record path".into()); + } + _ => { + return Err(format!( + "unsupported native compile-rna option `{argument}`" + )); + } + } + index += 1; + } + Ok(options) + } +} + +struct VerifyOptions; + +impl VerifyOptions { + fn parse(args: &[OsString]) -> Result { + let mut index = 0; + while index < args.len() { + let argument = args[index].to_string_lossy(); + match argument.as_ref() { + "--artifact-class" => { + index += 1; + let value = args + .get(index) + .and_then(|item| item.to_str()) + .ok_or_else(|| "--artifact-class requires a value".to_string())?; + require_gene(value)?; + } + value if value.starts_with("--artifact-class=") => { + require_gene(&value[17..])?; + } + _ => { + return Err(format!( + "unsupported native verify-roundtrip option `{argument}`" + )); + } + } + index += 1; + } + Ok(Self) + } +} + +fn require_gene(value: &str) -> Result<(), String> { + if value == "gene" { + Ok(()) + } else { + Err("native RNA has structural routes, not a legacy artifact class".into()) + } +} + +fn require_arity(args: &[OsString], expected: usize, usage: &str) -> Result<(), String> { + if args.len() == expected { + Ok(()) + } else { + Err(format!("usage: l64-cli {usage}")) + } +} + +fn is_native_rna(source: &[u8]) -> bool { + source + .iter() + .position(|byte| !byte.is_ascii_whitespace()) + .is_some_and(|start| source[start..].starts_with(b"L64R1")) +} + +fn read(path: &Path) -> Result, String> { + fs::read(path).map_err(|error| format!("failed to read `{}`: {error}", path.display())) +} + +fn native_error(error: impl core::fmt::Debug) -> String { + format!("native authority rejected input: {error:?}") +} + +fn io_error(error: std::io::Error) -> String { + error.to_string() +} From 94b4068d108bec6507d896c8f2e980dfab39b58f Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:52:03 -0700 Subject: [PATCH 42/59] Prove native CLI cutover and legacy fallback --- l64-cli/tests/native_cli.rs | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 l64-cli/tests/native_cli.rs diff --git a/l64-cli/tests/native_cli.rs b/l64-cli/tests/native_cli.rs new file mode 100644 index 0000000..7e0bc7e --- /dev/null +++ b/l64-cli/tests/native_cli.rs @@ -0,0 +1,96 @@ +use assert_cmd::Command; +use std::{ + fs, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, +}; + +const SOURCE: &[u8] = b"L64R1 0x4c36344e41544956\na 1 0x41\na 2 0x42\na 3 0x43\nf 4 1 2\nf 5 2 3\nf 6 1 3\nv 7 4\nv 8 5\nc 9 7 8 6\n"; + +fn fixture(name: &str, bytes: &[u8]) -> PathBuf { + let dir = std::env::temp_dir().join(format!( + "l64_native_cli_{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + fs::create_dir_all(&dir).unwrap(); + let path = dir.join(name); + fs::write(&path, bytes).unwrap(); + path +} + +#[test] +fn native_compile_and_sequence_use_existing_commands() { + let rna = fixture("sample.rna", SOURCE); + let dna = rna.with_extension("dna"); + + Command::cargo_bin("l64-cli") + .unwrap() + .args([ + "compile-rna", + rna.to_str().unwrap(), + "--out", + dna.to_str().unwrap(), + ]) + .assert() + .success(); + assert!(fs::read(&dna).unwrap().starts_with(b"L64D")); + + let output = Command::cargo_bin("l64-cli") + .unwrap() + .args(["sequence-dna", dna.to_str().unwrap()]) + .output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, SOURCE); +} + +#[test] +fn invalid_native_operation_cannot_create_dna() { + let source = b"L64R1 0x1\na 1 0x52\nm 2 1 2 3\nm 3 1 4 2\nm 4 1 2 2\nv 5 2\nv 6 3\nx 7 5 6 4\n"; + let rna = fixture("invalid.rna", source); + let dna = rna.with_extension("dna"); + + Command::cargo_bin("l64-cli") + .unwrap() + .args([ + "compile-rna", + rna.to_str().unwrap(), + "--out", + dna.to_str().unwrap(), + ]) + .assert() + .failure(); + assert!(!dna.exists()); +} + +#[test] +fn native_path_rejects_legacy_only_flags() { + let rna = fixture("sample.rna", SOURCE); + Command::cargo_bin("l64-cli") + .unwrap() + .args(["compile-rna", rna.to_str().unwrap(), "--persist-lineage"]) + .assert() + .failure(); + Command::cargo_bin("l64-cli") + .unwrap() + .args([ + "verify-roundtrip", + rna.to_str().unwrap(), + "--artifact-class", + "genome", + ]) + .assert() + .failure(); +} + +#[test] +fn non_native_commands_still_reach_legacy_dispatch() { + Command::cargo_bin("l64-cli") + .unwrap() + .arg("--help") + .assert() + .success(); +} From 4c2a5fcaa50400e08e421c4a16ee55b662728fd4 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:52:18 -0700 Subject: [PATCH 43/59] Advance Commander rail to native CLI membrane --- LOCUS64_NATIVE_RAIL.athens | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/LOCUS64_NATIVE_RAIL.athens b/LOCUS64_NATIVE_RAIL.athens index 972524a..cc8d48e 100644 --- a/LOCUS64_NATIVE_RAIL.athens +++ b/LOCUS64_NATIVE_RAIL.athens @@ -1,21 +1,23 @@ ATHENS_DEVELOPMENT_RAIL v1 -field=key=schema_version;value=1 -field=key=rail_version;value=5 -field=key=current_stage;value=native-rna-pilot -field=key=next_stage;value=native-cli-membrane +field=key=current_stage;value=native-cli-membrane +field=key=next_stage;value=legacy-demotion field=key=projection_authority;value=non_authoritative +field=key=rail_version;value=6 +field=key=schema_version;value=1 +gate=id=legacy-demotion-green +gate=id=native-cli-membrane-green +gate=id=native-rna-pilot-green stage=id=native-rna-pilot -stage_field=stage=native-rna-pilot;key=status;value=current stage_field=stage=native-rna-pilot;key=required_gates;value=native-rna-pilot-green +stage_field=stage=native-rna-pilot;key=status;value=complete stage=id=native-cli-membrane -stage_field=stage=native-cli-membrane;key=status;value=next stage_field=stage=native-cli-membrane;key=depends_on;value=native-rna-pilot stage_field=stage=native-cli-membrane;key=required_gates;value=native-cli-membrane-green +stage_field=stage=native-cli-membrane;key=status;value=current stage=id=legacy-demotion -stage_field=stage=legacy-demotion;key=status;value=planned stage_field=stage=legacy-demotion;key=depends_on;value=native-cli-membrane stage_field=stage=legacy-demotion;key=required_gates;value=legacy-demotion-green -gate=id=native-rna-pilot-green -gate=id=native-cli-membrane-green -gate=id=legacy-demotion-green +stage_field=stage=legacy-demotion;key=status;value=next +history=from_status=current;gates=native-rna-pilot-green;mode=linear_advance;stage_id=native-rna-pilot;to_status=complete +history=evidence=github-ad80a8c344a92a857170994ac0dd0f52a41abfc5-run-29982693189;kind=dogfood_promotion_receipt;stage_id=native-rna-pilot END From 7cb9ebb3431c9a662842c457e67464be2ade3c5e Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:56:06 -0700 Subject: [PATCH 44/59] Advance Commander rail to legacy demotion --- LOCUS64_NATIVE_RAIL.athens | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/LOCUS64_NATIVE_RAIL.athens b/LOCUS64_NATIVE_RAIL.athens index cc8d48e..4ee2ced 100644 --- a/LOCUS64_NATIVE_RAIL.athens +++ b/LOCUS64_NATIVE_RAIL.athens @@ -1,8 +1,7 @@ ATHENS_DEVELOPMENT_RAIL v1 -field=key=current_stage;value=native-cli-membrane -field=key=next_stage;value=legacy-demotion +field=key=current_stage;value=legacy-demotion field=key=projection_authority;value=non_authoritative -field=key=rail_version;value=6 +field=key=rail_version;value=7 field=key=schema_version;value=1 gate=id=legacy-demotion-green gate=id=native-cli-membrane-green @@ -13,11 +12,13 @@ stage_field=stage=native-rna-pilot;key=status;value=complete stage=id=native-cli-membrane stage_field=stage=native-cli-membrane;key=depends_on;value=native-rna-pilot stage_field=stage=native-cli-membrane;key=required_gates;value=native-cli-membrane-green -stage_field=stage=native-cli-membrane;key=status;value=current +stage_field=stage=native-cli-membrane;key=status;value=complete stage=id=legacy-demotion stage_field=stage=legacy-demotion;key=depends_on;value=native-cli-membrane stage_field=stage=legacy-demotion;key=required_gates;value=legacy-demotion-green -stage_field=stage=legacy-demotion;key=status;value=next +stage_field=stage=legacy-demotion;key=status;value=current history=from_status=current;gates=native-rna-pilot-green;mode=linear_advance;stage_id=native-rna-pilot;to_status=complete history=evidence=github-ad80a8c344a92a857170994ac0dd0f52a41abfc5-run-29982693189;kind=dogfood_promotion_receipt;stage_id=native-rna-pilot +history=from_status=current;gates=native-cli-membrane-green;mode=linear_advance;stage_id=native-cli-membrane;to_status=complete +history=evidence=github-4c2a5fcaa50400e08e421c4a16ee55b662728fd4-run-29983336925;kind=dogfood_promotion_receipt;stage_id=native-cli-membrane END From f1f1f5d2e93e0cc0eaa432ccb0efaf539639fdb3 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:59:09 -0700 Subject: [PATCH 45/59] Demote legacy CLI contacts to migration ingress --- l64-cli/src/native_main.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/l64-cli/src/native_main.rs b/l64-cli/src/native_main.rs index 8cdd550..ea97906 100644 --- a/l64-cli/src/native_main.rs +++ b/l64-cli/src/native_main.rs @@ -9,9 +9,20 @@ mod legacy { } fn main() -> anyhow::Result<()> { + if native_membrane::is_legacy_child() { + native_membrane::announce_explicit_legacy(); + return legacy::run(); + } + if let Some(code) = native_membrane::run_explicit_legacy().map_err(anyhow::Error::msg)? { + if code == 0 { + return Ok(()); + } + std::process::exit(code); + } if native_membrane::run_env().map_err(anyhow::Error::msg)? { Ok(()) } else { + native_membrane::warn_ambient_legacy_contact(); legacy::run() } } From d811743ea3f9fe84d40a0312c143cf076179ce9e Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:59:55 -0700 Subject: [PATCH 46/59] Add explicit legacy migration ingress --- l64-cli/src/native_membrane.rs | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/l64-cli/src/native_membrane.rs b/l64-cli/src/native_membrane.rs index 629b875..ffb2698 100644 --- a/l64-cli/src/native_membrane.rs +++ b/l64-cli/src/native_membrane.rs @@ -3,8 +3,61 @@ use std::{ fs, io::Write, path::{Path, PathBuf}, + process::Command, }; +const LEGACY_INGRESS_ENV: &str = "L64_EXPLICIT_LEGACY_INGRESS"; + +pub(super) fn is_legacy_child() -> bool { + std::env::var_os(LEGACY_INGRESS_ENV).is_some() +} + +pub(super) fn announce_explicit_legacy() { + eprintln!( + "legacy migration ingress: compatibility/forensic path; not current native authority" + ); +} + +pub(super) fn run_explicit_legacy() -> Result, String> { + let args = std::env::args_os().collect::>(); + if args.get(1).and_then(|item| item.to_str()) != Some("legacy") { + return Ok(None); + } + if args.len() == 2 || args.get(2).and_then(|item| item.to_str()) == Some("--help") { + println!( + "usage: l64-cli legacy ..." + ); + return Ok(Some(0)); + } + let command = args + .get(2) + .and_then(|item| item.to_str()) + .ok_or_else(|| "legacy ingress command must be UTF-8".to_string())?; + if !is_legacy_authority_command(command) { + return Err(format!( + "`{command}` is not a legacy RNA/DNA ingress command" + )); + } + let executable = std::env::current_exe().map_err(io_error)?; + Command::new(executable) + .env(LEGACY_INGRESS_ENV, "1") + .args(&args[2..]) + .status() + .map(|status| Some(status.code().unwrap_or(1))) + .map_err(io_error) +} + +pub(super) fn warn_ambient_legacy_contact() { + if let Some(command) = std::env::args() + .nth(1) + .filter(|command| is_legacy_authority_command(command)) + { + eprintln!( + "legacy migration ingress: ambient compatibility is deprecated; use `l64-cli legacy {command} ...`" + ); + } +} + pub(super) fn run_env() -> Result { let args = std::env::args_os().collect::>(); let stdout = std::io::stdout(); @@ -196,3 +249,10 @@ fn native_error(error: impl core::fmt::Debug) -> String { fn io_error(error: std::io::Error) -> String { error.to_string() } + +fn is_legacy_authority_command(command: &str) -> bool { + matches!( + command, + "normalize-rna" | "compile-rna" | "sequence-dna" | "inspect-dna" | "verify-roundtrip" + ) +} From ce1dd6a3713d0dfaa3daf27b20e99116ebcc67aa Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:00:22 -0700 Subject: [PATCH 47/59] Prove visible and explicit legacy demotion --- l64-cli/tests/native_cli.rs | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/l64-cli/tests/native_cli.rs b/l64-cli/tests/native_cli.rs index 7e0bc7e..c4d6866 100644 --- a/l64-cli/tests/native_cli.rs +++ b/l64-cli/tests/native_cli.rs @@ -94,3 +94,61 @@ fn non_native_commands_still_reach_legacy_dispatch() { .assert() .success(); } + +#[test] +fn ambient_legacy_contact_is_visibly_demoted() { + let legacy = fixture("legacy.rna", "ι ≔ σ ‖ κ\n".as_bytes()); + let dna = legacy.with_extension("dna"); + let output = Command::cargo_bin("l64-cli") + .unwrap() + .args([ + "compile-rna", + legacy.to_str().unwrap(), + "--out", + dna.to_str().unwrap(), + ]) + .output() + .unwrap(); + assert!(output.status.success()); + assert!( + String::from_utf8_lossy(&output.stderr).contains("ambient compatibility is deprecated") + ); +} + +#[test] +fn explicit_legacy_ingress_reaches_unchanged_dispatcher() { + let legacy = fixture("legacy.rna", "ι ≔ σ ‖ κ\n".as_bytes()); + let dna = legacy.with_extension("dna"); + let output = Command::cargo_bin("l64-cli") + .unwrap() + .args([ + "legacy", + "compile-rna", + legacy.to_str().unwrap(), + "--out", + dna.to_str().unwrap(), + ]) + .output() + .unwrap(); + assert!(output.status.success()); + assert!(dna.exists()); + assert!(String::from_utf8_lossy(&output.stderr).contains("compatibility/forensic path")); +} + +#[test] +fn native_contact_does_not_emit_legacy_warning() { + let rna = fixture("native.rna", SOURCE); + let dna = rna.with_extension("dna"); + let output = Command::cargo_bin("l64-cli") + .unwrap() + .args([ + "compile-rna", + rna.to_str().unwrap(), + "--out", + dna.to_str().unwrap(), + ]) + .output() + .unwrap(); + assert!(output.status.success()); + assert!(!String::from_utf8_lossy(&output.stderr).contains("legacy migration ingress")); +} From ec30d7159ee9c0099fe7d8273721536b80899e5a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:01:50 -0700 Subject: [PATCH 48/59] Close Commander native cutover rail --- LOCUS64_NATIVE_RAIL.athens | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/LOCUS64_NATIVE_RAIL.athens b/LOCUS64_NATIVE_RAIL.athens index 4ee2ced..7d4ce78 100644 --- a/LOCUS64_NATIVE_RAIL.athens +++ b/LOCUS64_NATIVE_RAIL.athens @@ -1,7 +1,6 @@ ATHENS_DEVELOPMENT_RAIL v1 -field=key=current_stage;value=legacy-demotion field=key=projection_authority;value=non_authoritative -field=key=rail_version;value=7 +field=key=rail_version;value=8 field=key=schema_version;value=1 gate=id=legacy-demotion-green gate=id=native-cli-membrane-green @@ -16,9 +15,11 @@ stage_field=stage=native-cli-membrane;key=status;value=complete stage=id=legacy-demotion stage_field=stage=legacy-demotion;key=depends_on;value=native-cli-membrane stage_field=stage=legacy-demotion;key=required_gates;value=legacy-demotion-green -stage_field=stage=legacy-demotion;key=status;value=current +stage_field=stage=legacy-demotion;key=status;value=complete history=from_status=current;gates=native-rna-pilot-green;mode=linear_advance;stage_id=native-rna-pilot;to_status=complete history=evidence=github-ad80a8c344a92a857170994ac0dd0f52a41abfc5-run-29982693189;kind=dogfood_promotion_receipt;stage_id=native-rna-pilot history=from_status=current;gates=native-cli-membrane-green;mode=linear_advance;stage_id=native-cli-membrane;to_status=complete history=evidence=github-4c2a5fcaa50400e08e421c4a16ee55b662728fd4-run-29983336925;kind=dogfood_promotion_receipt;stage_id=native-cli-membrane +history=from_status=current;gates=legacy-demotion-green;mode=linear_advance;stage_id=legacy-demotion;to_status=complete +history=evidence=github-ce1dd6a3713d0dfaa3daf27b20e99116ebcc67aa-run-29983720943;kind=dogfood_promotion_receipt;stage_id=legacy-demotion END From eba6a85c113d2684db8fefe986f9df8d74405e35 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:02:11 -0700 Subject: [PATCH 49/59] Document completed Commander cutover rail --- l64-native/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/l64-native/README.md b/l64-native/README.md index 8fd4560..1a790c4 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -24,4 +24,6 @@ The fifth boundary adds a compact authored RNA ingress. `L64R1` uses one declare State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. -This remains additive. It does not yet replace the legacy runtime or old DNA packet path. The Commander-held rail names the next cutover stage. +The existing `l64-cli` command names now route `L64R1` and `L64D` directly through this native path. Legacy RNA/DNA behavior is classified as compatibility/forensic ingress and is available explicitly through `l64-cli legacy ...`; ambient fallback remains temporarily available with a mandatory deprecation warning. + +This remains additive. It does not yet replace the legacy runtime, registry, certification, or old packet implementation internally. From cdf06bdea29609d8553fdd48492669dcefb286c7 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:02:23 -0700 Subject: [PATCH 50/59] Keep native membrane source canonical From 6a653bb75c5200d84e7897f0922ce27494952eb0 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:03:11 -0700 Subject: [PATCH 51/59] Keep legacy ingress membrane canonical From 03cd81d7a96df8304dbb8f98a4af728e1b5a93b5 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:03:43 -0700 Subject: [PATCH 52/59] Keep demotion tests canonical From 3f6e4c2dd24006e7a354ad0892319edbf308f58a Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:03:54 -0700 Subject: [PATCH 53/59] Keep native dependency boundary canonical From 1a0dab846a3138efae5da9b9a983b9110c829940 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:56:17 -0700 Subject: [PATCH 54/59] Add native constraint core change chain --- LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens | 38 ++ LOCUS64_EXECUTION_COHERENCE_RAIL.athens | 31 ++ l64-native/README.md | 19 +- l64-native/src/codec.rs | 403 +------------------- l64-native/src/codec/io.rs | 142 +++++++ l64-native/src/codec/reader.rs | 69 ++++ l64-native/src/codec/validation.rs | 219 +++++++++++ l64-native/src/dimension.rs | 66 ++++ l64-native/src/graph.rs | 390 +------------------ l64-native/src/graph/construction.rs | 277 ++++++++++++++ l64-native/src/graph/context.rs | 8 + l64-native/src/graph/storage.rs | 127 ++++++ l64-native/src/graph/typing.rs | 126 ++++++ l64-native/src/kernel.rs | 322 +--------------- l64-native/src/kernel/proposal.rs | 197 ++++++++++ l64-native/src/kernel/transaction.rs | 183 +++++++++ l64-native/src/kernel/types.rs | 197 ++++++++++ l64-native/src/kernel/validation.rs | 84 ++++ l64-native/src/lib.rs | 4 +- l64-native/src/rna.rs | 359 +---------------- l64-native/src/rna/compile.rs | 134 +++++++ l64-native/src/rna/parse.rs | 131 +++++++ l64-native/src/rna/render.rs | 70 ++++ l64-native/src/rna/sequence.rs | 187 +++++++++ l64-native/tests/architecture.rs | 7 +- l64-native/tests/constraints.rs | 328 ++++++++++++++++ l64-native/tests/rna.rs | 54 ++- 27 files changed, 2719 insertions(+), 1453 deletions(-) create mode 100644 LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens create mode 100644 LOCUS64_EXECUTION_COHERENCE_RAIL.athens create mode 100644 l64-native/src/codec/io.rs create mode 100644 l64-native/src/codec/reader.rs create mode 100644 l64-native/src/codec/validation.rs create mode 100644 l64-native/src/dimension.rs create mode 100644 l64-native/src/graph/construction.rs create mode 100644 l64-native/src/graph/context.rs create mode 100644 l64-native/src/graph/storage.rs create mode 100644 l64-native/src/graph/typing.rs create mode 100644 l64-native/src/kernel/proposal.rs create mode 100644 l64-native/src/kernel/transaction.rs create mode 100644 l64-native/src/kernel/types.rs create mode 100644 l64-native/src/kernel/validation.rs create mode 100644 l64-native/src/rna/compile.rs create mode 100644 l64-native/src/rna/parse.rs create mode 100644 l64-native/src/rna/render.rs create mode 100644 l64-native/src/rna/sequence.rs create mode 100644 l64-native/tests/constraints.rs diff --git a/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens b/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens new file mode 100644 index 0000000..d8ba4c3 --- /dev/null +++ b/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens @@ -0,0 +1,38 @@ +ATHENS_DEVELOPMENT_RAIL v1 +field=key=current_stage;value=constraint-core-closure +field=key=projection_authority;value=non_authoritative +field=key=rail_version;value=5 +field=key=schema_version;value=1 +gate=id=constraint-core-closure-green +gate=id=context-consistency-green +gate=id=dimension-algebra-green +gate=id=guarded-obligation-green +gate=id=native-surface-green +stage=id=dimension-algebra +stage_field=stage=dimension-algebra;key=required_gates;value=dimension-algebra-green +stage_field=stage=dimension-algebra;key=status;value=complete +stage=id=context-consistency +stage_field=stage=context-consistency;key=depends_on;value=dimension-algebra +stage_field=stage=context-consistency;key=required_gates;value=context-consistency-green +stage_field=stage=context-consistency;key=status;value=complete +stage=id=guarded-obligation +stage_field=stage=guarded-obligation;key=depends_on;value=context-consistency +stage_field=stage=guarded-obligation;key=required_gates;value=guarded-obligation-green +stage_field=stage=guarded-obligation;key=status;value=complete +stage=id=native-surface +stage_field=stage=native-surface;key=depends_on;value=guarded-obligation +stage_field=stage=native-surface;key=required_gates;value=native-surface-green +stage_field=stage=native-surface;key=status;value=complete +stage=id=constraint-core-closure +stage_field=stage=constraint-core-closure;key=depends_on;value=native-surface +stage_field=stage=constraint-core-closure;key=required_gates;value=constraint-core-closure-green +stage_field=stage=constraint-core-closure;key=status;value=current +history=from_status=current;gates=dimension-algebra-green;mode=linear_advance;stage_id=dimension-algebra;to_status=complete +history=evidence=local%3A22-tests%2Bclippy%3Bdimension-law%2Bdecoder-recheck;kind=dogfood_promotion_receipt;stage_id=dimension-algebra +history=from_status=current;gates=context-consistency-green;mode=linear_advance;stage_id=context-consistency;to_status=complete +history=evidence=local%3A24-tests%2Bclippy%3Bcontradiction-atomic%2Bdecode-recheck;kind=dogfood_promotion_receipt;stage_id=context-consistency +history=from_status=current;gates=guarded-obligation-green;mode=linear_advance;stage_id=guarded-obligation;to_status=complete +history=evidence=local%3A28-tests%2Bclippy%3Bsqrt-witness%7Cobligation%7Crejection%2Bforgery-refusal;kind=dogfood_promotion_receipt;stage_id=guarded-obligation +history=from_status=current;gates=native-surface-green;mode=linear_advance;stage_id=native-surface;to_status=complete +history=evidence=local%3A32-tests%2Bclippy%3Bconstraint-rna-dna-fixed-point%2Binvalid-refusal;kind=dogfood_promotion_receipt;stage_id=native-surface +END diff --git a/LOCUS64_EXECUTION_COHERENCE_RAIL.athens b/LOCUS64_EXECUTION_COHERENCE_RAIL.athens new file mode 100644 index 0000000..2d54902 --- /dev/null +++ b/LOCUS64_EXECUTION_COHERENCE_RAIL.athens @@ -0,0 +1,31 @@ +ATHENS_DEVELOPMENT_RAIL v1 +field=key=schema_version;value=1 +field=key=rail_version;value=1 +field=key=current_stage;value=native-constraint-core +field=key=next_stage;value=proof-producing-congruence +field=key=projection_authority;value=non_authoritative +stage=id=native-constraint-core +stage_field=stage=native-constraint-core;key=status;value=current +stage_field=stage=native-constraint-core;key=required_gates;value=native-constraint-core-green +stage=id=proof-producing-congruence +stage_field=stage=proof-producing-congruence;key=status;value=next +stage_field=stage=proof-producing-congruence;key=depends_on;value=native-constraint-core +stage_field=stage=proof-producing-congruence;key=required_gates;value=proof-producing-congruence-green +stage=id=incremental-closure +stage_field=stage=incremental-closure;key=status;value=planned +stage_field=stage=incremental-closure;key=depends_on;value=proof-producing-congruence +stage_field=stage=incremental-closure;key=required_gates;value=incremental-closure-green +stage=id=native-upper-projection +stage_field=stage=native-upper-projection;key=status;value=planned +stage_field=stage=native-upper-projection;key=depends_on;value=incremental-closure +stage_field=stage=native-upper-projection;key=required_gates;value=native-upper-projection-green +stage=id=legacy-authority-quarantine +stage_field=stage=legacy-authority-quarantine;key=status;value=planned +stage_field=stage=legacy-authority-quarantine;key=depends_on;value=native-upper-projection +stage_field=stage=legacy-authority-quarantine;key=required_gates;value=legacy-authority-quarantine-green +gate=id=native-constraint-core-green +gate=id=proof-producing-congruence-green +gate=id=incremental-closure-green +gate=id=native-upper-projection-green +gate=id=legacy-authority-quarantine-green +END diff --git a/l64-native/README.md b/l64-native/README.md index 1a790c4..1514851 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -5,9 +5,9 @@ A deliberately small, additive execution spine for Locus64. This crate is not a projection of the legacy `QaEntry` / `RegistryBundle` ontology. It carries: - composed numeric routes; -- one node algebra for types, values, operations, judgments, and witnesses; +- one node algebra for types, values, constraints, operations, judgments, witnesses, and obligations; - ordered compact ports; -- persistent context deltas; +- persistent context deltas with contradiction checks; - one transition journal; - one canonical structural codec; - one transactional admission path. @@ -20,10 +20,21 @@ The third boundary makes primitive execution proof-carrying without adding a rec The fourth boundary adds a native DNA frame with a fixed 44-byte binary header, bounded canonical payload, embedded domain-separated BLAKE3 commitment, and exact DNA decode/re-encode fixed point. The frame contains no string metadata or legacy record payload. -The fifth boundary adds a compact authored RNA ingress. `L64R1` uses one declared domain and strictly increasing numeric local slots; routes are composed as `(domain, slot)`. Six one-byte instructions lower directly through the existing graph and transaction APIs: atom, matrix, function, value, composition, and matrix multiplication. Sequencing omits intrinsic evidence nodes because their routes and structure are deterministically derived. +The fifth boundary adds a compact authored RNA ingress. `L64R1` uses one declared domain and strictly increasing numeric local slots; routes are composed as `(domain, slot)`. Its byte-oriented instructions lower directly through the existing graph and transaction APIs. Sequencing omits intrinsic evidence nodes because their routes and structure are deterministically derived. + +The sixth boundary adds the first native constraint core without creating a parallel schema system: + +- seven-axis signed dimensions packed into one 64-bit value; +- quantity types over existing carrier types; +- dimension-checked addition, multiplication, division, and square root; +- compact constraint nodes and persistent context deltas; +- contradiction rejection before context or journal mutation; +- a guarded square-root operation that yields a kernel witness when proven, rejects when refuted, and yields a native obligation when unresolved; +- decoder-side re-execution of operation and evidence law, preventing structurally plausible forged authority; +- canonical `L64R1 → L64D → L64R1 → L64D` fixed points for both discharged and unresolved guards. State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. The existing `l64-cli` command names now route `L64R1` and `L64D` directly through this native path. Legacy RNA/DNA behavior is classified as compatibility/forensic ingress and is available explicitly through `l64-cli legacy ...`; ambient fallback remains temporarily available with a mandatory deprecation warning. -This remains additive. It does not yet replace the legacy runtime, registry, certification, or old packet implementation internally. +This remains additive. It does not yet implement proof-producing congruence, incremental dependency closure, native upper-stack projections, or replacement of the legacy runtime, registry, certification, and old packet implementation internally. diff --git a/l64-native/src/codec.rs b/l64-native/src/codec.rs index acb958f..8b3b6cf 100644 --- a/l64-native/src/codec.rs +++ b/l64-native/src/codec.rs @@ -1,10 +1,10 @@ use std::collections::BTreeMap; -use crate::kernel::{JUDGMENT_LOCUS, WITNESS_LOCUS}; +use crate::kernel::{EVIDENCE_LOCUS, JUDGMENT_LOCUS}; use crate::{ContextDelta, Graph, LocusWord, Node, NodeId, OpCode, Port, PortRole, Route}; -const CODEC_VERSION: u16 = 2; -const COMMITMENT_DOMAIN: &[u8] = b"l64-native-state-v2\0"; +const CODEC_VERSION: u16 = 3; +const COMMITMENT_DOMAIN: &[u8] = b"l64-native-state-v3\0"; const MAX_NODES: usize = 1 << 20; const MAX_PORTS: usize = 1 << 22; const MAX_CONTEXTS: usize = 1 << 20; @@ -27,401 +27,12 @@ pub enum DecodeError { InvalidPortLaw, InvalidContext, InvalidRoute, + InvalidAuthority, DuplicateRoute, TrailingBytes, NonCanonical, } -pub fn canonical_bytes(graph: &Graph) -> Vec { - let mut out = - Vec::with_capacity(22 + graph.nodes_raw().len() * 24 + graph.ports_raw().len() * 8); - out.extend_from_slice(b"L64N"); - out.extend_from_slice(&CODEC_VERSION.to_le_bytes()); - push_len(&mut out, graph.nodes_raw().len()); - push_len(&mut out, graph.ports_raw().len()); - push_len(&mut out, graph.contexts_raw().len()); - push_len(&mut out, graph.routes_raw().len()); - - for node in graph.nodes_raw() { - out.extend_from_slice(&(node.opcode() as u16).to_le_bytes()); - out.extend_from_slice(&node.context().to_le_bytes()); - out.extend_from_slice(&node.ty().unwrap_or(NO_NODE).to_le_bytes()); - out.extend_from_slice(&node.payload().to_le_bytes()); - let range = node.port_range(); - out.extend_from_slice(&(range.start as u32).to_le_bytes()); - out.extend_from_slice(&((range.end - range.start) as u16).to_le_bytes()); - } - - for port in graph.ports_raw() { - out.extend_from_slice(&port.target().to_le_bytes()); - out.push(port.role() as u8); - out.push(port.flags()); - out.extend_from_slice(&port.ordinal().to_le_bytes()); - } - - for context in graph.contexts_raw() { - out.extend_from_slice(&context.parent().to_le_bytes()); - out.extend_from_slice(&context.binding().unwrap_or(NO_NODE).to_le_bytes()); - } - - for (route, node) in graph.routes_raw() { - out.extend_from_slice(&route.domain().0.to_le_bytes()); - push_len(&mut out, route.tail().len()); - for word in route.tail() { - out.extend_from_slice(&word.0.to_le_bytes()); - } - out.extend_from_slice(&node.to_le_bytes()); - } - - out -} - -pub fn decode_canonical(bytes: &[u8]) -> Result { - let mut reader = Reader::new(bytes); - if reader.take(4)? != b"L64N" { - return Err(DecodeError::BadMagic); - } - let version = reader.u16()?; - if version != CODEC_VERSION { - return Err(DecodeError::UnsupportedVersion { version }); - } - - let node_count = reader.count(MAX_NODES)?; - let port_count = reader.count(MAX_PORTS)?; - let context_count = reader.count(MAX_CONTEXTS)?; - let route_count = reader.count(MAX_ROUTES)?; - if context_count == 0 || route_count != node_count { - return Err(DecodeError::InvalidContext); - } - - reader.require(node_count, 24)?; - let mut nodes = Vec::with_capacity(node_count); - for _ in 0..node_count { - let raw_opcode = reader.u16()?; - let opcode = OpCode::from_raw(raw_opcode) - .filter(|opcode| opcode.is_persisted_node()) - .ok_or(DecodeError::UnknownOpcode { opcode: raw_opcode })?; - let context = reader.u32()?; - let ty = reader.u32()?; - let payload = reader.u64()?; - let first_port = reader.u32()?; - let port_count = reader.u16()?; - nodes.push(Node::from_raw( - payload, context, ty, first_port, opcode, port_count, - )); - } - - reader.require(port_count, 8)?; - let mut ports = Vec::with_capacity(port_count); - for _ in 0..port_count { - let target = reader.u32()?; - let raw_role = reader.u8()?; - let role = - PortRole::from_raw(raw_role).ok_or(DecodeError::UnknownPortRole { role: raw_role })?; - let flags = reader.u8()?; - if flags != 0 { - return Err(DecodeError::NonZeroPortFlags { flags }); - } - let ordinal = reader.u16()?; - ports.push(Port::from_raw(target, role, flags, ordinal)); - } - - reader.require(context_count, 8)?; - let mut contexts = Vec::with_capacity(context_count); - for _ in 0..context_count { - contexts.push(ContextDelta::from_raw(reader.u32()?, reader.u32()?)); - } - - let mut routes = BTreeMap::new(); - for _ in 0..route_count { - let domain = LocusWord(reader.u64()?); - let tail_len = reader.count(MAX_ROUTE_WORDS)?; - reader.require(tail_len, 8)?; - let mut tail = Vec::with_capacity(tail_len); - for _ in 0..tail_len { - tail.push(LocusWord(reader.u64()?)); - } - let node = reader.u32()?; - let route = Route::from_parts(domain, tail.into_boxed_slice()); - if routes.insert(route, node).is_some() { - return Err(DecodeError::DuplicateRoute); - } - } - - if !reader.is_empty() { - return Err(DecodeError::TrailingBytes); - } - - validate_structure(&nodes, &ports, &contexts, &routes)?; - let commitment = commitment_bytes(bytes); - let graph = Graph::from_decoded_parts(nodes, ports, contexts, routes, commitment); - if canonical_bytes(&graph) != bytes { - return Err(DecodeError::NonCanonical); - } - Ok(graph) -} - -pub(crate) fn state_commitment(graph: &Graph) -> [u8; 32] { - commitment_bytes(&canonical_bytes(graph)) -} - -fn commitment_bytes(bytes: &[u8]) -> [u8; 32] { - let mut hasher = blake3::Hasher::new(); - hasher.update(COMMITMENT_DOMAIN); - hasher.update(bytes); - *hasher.finalize().as_bytes() -} - -fn validate_structure( - nodes: &[Node], - ports: &[Port], - contexts: &[ContextDelta], - routes: &BTreeMap, -) -> Result<(), DecodeError> { - let root = contexts.first().ok_or(DecodeError::InvalidContext)?; - if root.parent() != 0 || root.binding().is_some() { - return Err(DecodeError::InvalidContext); - } - for (index, context) in contexts.iter().enumerate().skip(1) { - if context.parent() as usize >= index { - return Err(DecodeError::InvalidContext); - } - let binding = context.binding().ok_or(DecodeError::InvalidContext)?; - if binding as usize >= nodes.len() { - return Err(DecodeError::InvalidContext); - } - } - - let mut port_cursor = 0usize; - for (index, node) in nodes.iter().enumerate() { - if node.context() as usize >= contexts.len() { - return Err(DecodeError::InvalidNode); - } - if let Some(ty) = node.ty() { - if ty as usize >= index || !nodes[ty as usize].opcode().is_type() { - return Err(DecodeError::InvalidNode); - } - } else if !node.opcode().is_type() { - return Err(DecodeError::InvalidNode); - } - - let range = node.port_range(); - if range.start != port_cursor || range.end > ports.len() { - return Err(DecodeError::InvalidPortRange); - } - let node_ports = &ports[range.clone()]; - for (ordinal, port) in node_ports.iter().enumerate() { - if port.target() as usize >= index { - return Err(DecodeError::InvalidPortTarget); - } - if port.ordinal() as usize != ordinal { - return Err(DecodeError::InvalidPortLaw); - } - } - validate_port_law(node.opcode(), node_ports, node.ty().is_some())?; - validate_node_semantics(index, node, node_ports, nodes, ports)?; - port_cursor = range.end; - } - if port_cursor != ports.len() { - return Err(DecodeError::InvalidPortRange); - } - - let mut covered = vec![false; nodes.len()]; - for node in routes.values().copied() { - let slot = covered - .get_mut(node as usize) - .ok_or(DecodeError::InvalidRoute)?; - if *slot { - return Err(DecodeError::InvalidRoute); - } - *slot = true; - } - if covered.iter().any(|covered| !covered) { - return Err(DecodeError::InvalidRoute); - } - validate_evidence_routes(nodes, routes)?; - Ok(()) -} - -fn validate_node_semantics( - index: usize, - node: &Node, - node_ports: &[Port], - nodes: &[Node], - ports: &[Port], -) -> Result<(), DecodeError> { - match node.opcode() { - OpCode::Value => { - let ty = node.ty().ok_or(DecodeError::InvalidNode)?; - if nodes[ty as usize].opcode() == OpCode::TypeJudgment { - return Err(DecodeError::InvalidNode); - } - } - OpCode::KernelWitness => { - let ty = node.ty().ok_or(DecodeError::InvalidNode)?; - if nodes[ty as usize].opcode() != OpCode::TypeJudgment { - return Err(DecodeError::InvalidNode); - } - } - OpCode::TypeJudgment => { - let rule = OpCode::from_raw(node.payload() as u16) - .filter(|opcode| opcode.is_executable()) - .ok_or(DecodeError::InvalidNode)?; - let subject = node_ports[0].target() as usize; - if subject >= index || nodes[subject].opcode() != rule { - return Err(DecodeError::InvalidNode); - } - let subject_node = &nodes[subject]; - if subject_node.ty() != Some(node_ports[3].target()) { - return Err(DecodeError::InvalidNode); - } - let subject_ports = ports - .get(subject_node.port_range()) - .ok_or(DecodeError::InvalidPortRange)?; - if subject_ports.len() != 2 - || subject_ports[0].target() != node_ports[1].target() - || subject_ports[1].target() != node_ports[2].target() - { - return Err(DecodeError::InvalidNode); - } - } - _ => {} - } - Ok(()) -} - -fn validate_evidence_routes( - nodes: &[Node], - routes: &BTreeMap, -) -> Result<(), DecodeError> { - let mut attached = vec![false; nodes.len()]; - for (route, node_id) in routes { - let node = &nodes[*node_id as usize]; - if !node.opcode().is_executable() { - continue; - } - let judgment = *routes - .get(&route.composed(JUDGMENT_LOCUS)) - .ok_or(DecodeError::InvalidRoute)?; - let witness = *routes - .get(&route.composed(WITNESS_LOCUS)) - .ok_or(DecodeError::InvalidRoute)?; - if nodes[judgment as usize].opcode() != OpCode::TypeJudgment - || nodes[witness as usize].opcode() != OpCode::KernelWitness - || nodes[witness as usize].ty() != Some(judgment) - { - return Err(DecodeError::InvalidRoute); - } - attached[judgment as usize] = true; - attached[witness as usize] = true; - } - for (index, node) in nodes.iter().enumerate() { - if matches!(node.opcode(), OpCode::TypeJudgment | OpCode::KernelWitness) && !attached[index] - { - return Err(DecodeError::InvalidRoute); - } - } - Ok(()) -} - -fn validate_port_law(opcode: OpCode, ports: &[Port], has_type: bool) -> Result<(), DecodeError> { - let valid = match opcode { - OpCode::TypeAtom => ports.is_empty() && !has_type, - OpCode::TypeMatrix => { - ports.len() == 1 && ports[0].role() == PortRole::Parameter && !has_type - } - OpCode::TypeFunction => { - ports.len() == 2 - && ports[0].role() == PortRole::Domain - && ports[1].role() == PortRole::Codomain - && !has_type - } - OpCode::Value => ports.is_empty() && has_type, - OpCode::Compose | OpCode::MatMul => { - ports.len() == 2 - && ports.iter().all(|port| port.role() == PortRole::Argument) - && has_type - } - OpCode::TypeJudgment => { - ports.len() == 4 - && ports[0].role() == PortRole::Subject - && ports[1].role() == PortRole::Premise - && ports[2].role() == PortRole::Premise - && ports[3].role() == PortRole::Conclusion - && !has_type - } - OpCode::KernelWitness => ports.is_empty() && has_type, - OpCode::ExtendContext => false, - }; - valid.then_some(()).ok_or(DecodeError::InvalidPortLaw) -} - -fn push_len(out: &mut Vec, len: usize) { - let value = u32::try_from(len).expect("native graph section exceeds u32 length"); - out.extend_from_slice(&value.to_le_bytes()); -} - -struct Reader<'a> { - bytes: &'a [u8], - cursor: usize, -} - -impl<'a> Reader<'a> { - fn new(bytes: &'a [u8]) -> Self { - Self { bytes, cursor: 0 } - } - - fn is_empty(&self) -> bool { - self.cursor == self.bytes.len() - } - - fn take(&mut self, len: usize) -> Result<&'a [u8], DecodeError> { - let end = self.cursor.checked_add(len).ok_or(DecodeError::Truncated)?; - let value = self - .bytes - .get(self.cursor..end) - .ok_or(DecodeError::Truncated)?; - self.cursor = end; - Ok(value) - } - - fn require(&self, count: usize, width: usize) -> Result<(), DecodeError> { - let bytes = count - .checked_mul(width) - .ok_or(DecodeError::StructuralBound)?; - if self.bytes.len().saturating_sub(self.cursor) < bytes { - return Err(DecodeError::Truncated); - } - Ok(()) - } - - fn count(&mut self, maximum: usize) -> Result { - let count = self.u32()? as usize; - if count > maximum { - return Err(DecodeError::StructuralBound); - } - Ok(count) - } - - fn u8(&mut self) -> Result { - Ok(self.take(1)?[0]) - } - - fn u16(&mut self) -> Result { - Ok(u16::from_le_bytes( - self.take(2)?.try_into().expect("fixed-width read"), - )) - } - - fn u32(&mut self) -> Result { - Ok(u32::from_le_bytes( - self.take(4)?.try_into().expect("fixed-width read"), - )) - } - - fn u64(&mut self) -> Result { - Ok(u64::from_le_bytes( - self.take(8)?.try_into().expect("fixed-width read"), - )) - } -} +include!("codec/io.rs"); +include!("codec/validation.rs"); +include!("codec/reader.rs"); diff --git a/l64-native/src/codec/io.rs b/l64-native/src/codec/io.rs new file mode 100644 index 0000000..5820083 --- /dev/null +++ b/l64-native/src/codec/io.rs @@ -0,0 +1,142 @@ +pub fn canonical_bytes(graph: &Graph) -> Vec { + let mut out = + Vec::with_capacity(22 + graph.nodes_raw().len() * 24 + graph.ports_raw().len() * 8); + out.extend_from_slice(b"L64N"); + out.extend_from_slice(&CODEC_VERSION.to_le_bytes()); + push_len(&mut out, graph.nodes_raw().len()); + push_len(&mut out, graph.ports_raw().len()); + push_len(&mut out, graph.contexts_raw().len()); + push_len(&mut out, graph.routes_raw().len()); + + for node in graph.nodes_raw() { + out.extend_from_slice(&(node.opcode() as u16).to_le_bytes()); + out.extend_from_slice(&node.context().to_le_bytes()); + out.extend_from_slice(&node.ty().unwrap_or(NO_NODE).to_le_bytes()); + out.extend_from_slice(&node.payload().to_le_bytes()); + let range = node.port_range(); + out.extend_from_slice(&(range.start as u32).to_le_bytes()); + out.extend_from_slice(&((range.end - range.start) as u16).to_le_bytes()); + } + + for port in graph.ports_raw() { + out.extend_from_slice(&port.target().to_le_bytes()); + out.push(port.role() as u8); + out.push(port.flags()); + out.extend_from_slice(&port.ordinal().to_le_bytes()); + } + + for context in graph.contexts_raw() { + out.extend_from_slice(&context.parent().to_le_bytes()); + out.extend_from_slice(&context.binding().unwrap_or(NO_NODE).to_le_bytes()); + } + + for (route, node) in graph.routes_raw() { + out.extend_from_slice(&route.domain().0.to_le_bytes()); + push_len(&mut out, route.tail().len()); + for word in route.tail() { + out.extend_from_slice(&word.0.to_le_bytes()); + } + out.extend_from_slice(&node.to_le_bytes()); + } + + out +} + +pub fn decode_canonical(bytes: &[u8]) -> Result { + let mut reader = Reader::new(bytes); + if reader.take(4)? != b"L64N" { + return Err(DecodeError::BadMagic); + } + let version = reader.u16()?; + if version != CODEC_VERSION { + return Err(DecodeError::UnsupportedVersion { version }); + } + + let node_count = reader.count(MAX_NODES)?; + let port_count = reader.count(MAX_PORTS)?; + let context_count = reader.count(MAX_CONTEXTS)?; + let route_count = reader.count(MAX_ROUTES)?; + if context_count == 0 || route_count != node_count { + return Err(DecodeError::InvalidContext); + } + + reader.require(node_count, 24)?; + let mut nodes = Vec::with_capacity(node_count); + for _ in 0..node_count { + let raw_opcode = reader.u16()?; + let opcode = OpCode::from_raw(raw_opcode) + .filter(|opcode| opcode.is_persisted_node()) + .ok_or(DecodeError::UnknownOpcode { opcode: raw_opcode })?; + let context = reader.u32()?; + let ty = reader.u32()?; + let payload = reader.u64()?; + let first_port = reader.u32()?; + let port_count = reader.u16()?; + nodes.push(Node::from_raw( + payload, context, ty, first_port, opcode, port_count, + )); + } + + reader.require(port_count, 8)?; + let mut ports = Vec::with_capacity(port_count); + for _ in 0..port_count { + let target = reader.u32()?; + let raw_role = reader.u8()?; + let role = + PortRole::from_raw(raw_role).ok_or(DecodeError::UnknownPortRole { role: raw_role })?; + let flags = reader.u8()?; + if flags != 0 { + return Err(DecodeError::NonZeroPortFlags { flags }); + } + let ordinal = reader.u16()?; + ports.push(Port::from_raw(target, role, flags, ordinal)); + } + + reader.require(context_count, 8)?; + let mut contexts = Vec::with_capacity(context_count); + for _ in 0..context_count { + contexts.push(ContextDelta::from_raw(reader.u32()?, reader.u32()?)); + } + + let mut routes = BTreeMap::new(); + for _ in 0..route_count { + let domain = LocusWord(reader.u64()?); + let tail_len = reader.count(MAX_ROUTE_WORDS)?; + reader.require(tail_len, 8)?; + let mut tail = Vec::with_capacity(tail_len); + for _ in 0..tail_len { + tail.push(LocusWord(reader.u64()?)); + } + let node = reader.u32()?; + let route = Route::from_parts(domain, tail.into_boxed_slice()); + if routes.insert(route, node).is_some() { + return Err(DecodeError::DuplicateRoute); + } + } + + if !reader.is_empty() { + return Err(DecodeError::TrailingBytes); + } + + validate_structure(&nodes, &ports, &contexts, &routes)?; + let commitment = commitment_bytes(bytes); + let graph = Graph::from_decoded_parts(nodes, ports, contexts, routes, commitment); + graph + .validate_decoded_authority() + .map_err(|_| DecodeError::InvalidAuthority)?; + if canonical_bytes(&graph) != bytes { + return Err(DecodeError::NonCanonical); + } + Ok(graph) +} + +pub(crate) fn state_commitment(graph: &Graph) -> [u8; 32] { + commitment_bytes(&canonical_bytes(graph)) +} + +fn commitment_bytes(bytes: &[u8]) -> [u8; 32] { + let mut hasher = blake3::Hasher::new(); + hasher.update(COMMITMENT_DOMAIN); + hasher.update(bytes); + *hasher.finalize().as_bytes() +} diff --git a/l64-native/src/codec/reader.rs b/l64-native/src/codec/reader.rs new file mode 100644 index 0000000..f2127e2 --- /dev/null +++ b/l64-native/src/codec/reader.rs @@ -0,0 +1,69 @@ +fn push_len(out: &mut Vec, len: usize) { + let value = u32::try_from(len).expect("native graph section exceeds u32 length"); + out.extend_from_slice(&value.to_le_bytes()); +} + +struct Reader<'a> { + bytes: &'a [u8], + cursor: usize, +} + +impl<'a> Reader<'a> { + fn new(bytes: &'a [u8]) -> Self { + Self { bytes, cursor: 0 } + } + + fn is_empty(&self) -> bool { + self.cursor == self.bytes.len() + } + + fn take(&mut self, len: usize) -> Result<&'a [u8], DecodeError> { + let end = self.cursor.checked_add(len).ok_or(DecodeError::Truncated)?; + let value = self + .bytes + .get(self.cursor..end) + .ok_or(DecodeError::Truncated)?; + self.cursor = end; + Ok(value) + } + + fn require(&self, count: usize, width: usize) -> Result<(), DecodeError> { + let bytes = count + .checked_mul(width) + .ok_or(DecodeError::StructuralBound)?; + if self.bytes.len().saturating_sub(self.cursor) < bytes { + return Err(DecodeError::Truncated); + } + Ok(()) + } + + fn count(&mut self, maximum: usize) -> Result { + let count = self.u32()? as usize; + if count > maximum { + return Err(DecodeError::StructuralBound); + } + Ok(count) + } + + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes( + self.take(2)?.try_into().expect("fixed-width read"), + )) + } + + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes( + self.take(4)?.try_into().expect("fixed-width read"), + )) + } + + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes( + self.take(8)?.try_into().expect("fixed-width read"), + )) + } +} diff --git a/l64-native/src/codec/validation.rs b/l64-native/src/codec/validation.rs new file mode 100644 index 0000000..1ae5755 --- /dev/null +++ b/l64-native/src/codec/validation.rs @@ -0,0 +1,219 @@ +fn validate_structure( + nodes: &[Node], + ports: &[Port], + contexts: &[ContextDelta], + routes: &BTreeMap, +) -> Result<(), DecodeError> { + let root = contexts.first().ok_or(DecodeError::InvalidContext)?; + if root.parent() != 0 || root.binding().is_some() { + return Err(DecodeError::InvalidContext); + } + for (index, context) in contexts.iter().enumerate().skip(1) { + if context.parent() as usize >= index { + return Err(DecodeError::InvalidContext); + } + let binding = context.binding().ok_or(DecodeError::InvalidContext)?; + if binding as usize >= nodes.len() { + return Err(DecodeError::InvalidContext); + } + } + + let mut port_cursor = 0usize; + for (index, node) in nodes.iter().enumerate() { + if node.context() as usize >= contexts.len() { + return Err(DecodeError::InvalidNode); + } + if let Some(ty) = node.ty() { + if ty as usize >= index || !nodes[ty as usize].opcode().is_type() { + return Err(DecodeError::InvalidNode); + } + } else if !node.opcode().is_untyped_authority() { + return Err(DecodeError::InvalidNode); + } + + let range = node.port_range(); + if range.start != port_cursor || range.end > ports.len() { + return Err(DecodeError::InvalidPortRange); + } + let node_ports = &ports[range.clone()]; + for (ordinal, port) in node_ports.iter().enumerate() { + if port.target() as usize >= index { + return Err(DecodeError::InvalidPortTarget); + } + if port.ordinal() as usize != ordinal { + return Err(DecodeError::InvalidPortLaw); + } + } + validate_port_law(node.opcode(), node_ports, node.ty().is_some())?; + validate_node_semantics(index, node, node_ports, nodes, ports)?; + port_cursor = range.end; + } + if port_cursor != ports.len() { + return Err(DecodeError::InvalidPortRange); + } + + let mut covered = vec![false; nodes.len()]; + for node in routes.values().copied() { + let slot = covered + .get_mut(node as usize) + .ok_or(DecodeError::InvalidRoute)?; + if *slot { + return Err(DecodeError::InvalidRoute); + } + *slot = true; + } + if covered.iter().any(|covered| !covered) { + return Err(DecodeError::InvalidRoute); + } + validate_evidence_routes(nodes, routes)?; + Ok(()) +} + +fn validate_node_semantics( + index: usize, + node: &Node, + node_ports: &[Port], + nodes: &[Node], + ports: &[Port], +) -> Result<(), DecodeError> { + match node.opcode() { + OpCode::Value => { + let ty = node.ty().ok_or(DecodeError::InvalidNode)?; + if nodes[ty as usize].opcode() == OpCode::TypeJudgment { + return Err(DecodeError::InvalidNode); + } + } + OpCode::KernelWitness | OpCode::Obligation => { + let ty = node.ty().ok_or(DecodeError::InvalidNode)?; + if nodes[ty as usize].opcode() != OpCode::TypeJudgment { + return Err(DecodeError::InvalidNode); + } + if node.opcode() == OpCode::Obligation + && crate::ConstraintKind::from_payload(node.payload()).is_none() + { + return Err(DecodeError::InvalidNode); + } + } + OpCode::TypeQuantity => { + if crate::Dimension::from_bits(node.payload()).is_none() { + return Err(DecodeError::InvalidNode); + } + } + OpCode::Constraint => { + if crate::ConstraintKind::from_payload(node.payload()).is_none() { + return Err(DecodeError::InvalidNode); + } + } + OpCode::TypeJudgment => { + let rule = OpCode::from_raw(node.payload() as u16) + .filter(|opcode| opcode.is_executable()) + .ok_or(DecodeError::InvalidNode)?; + if node_ports.len() < 3 { + return Err(DecodeError::InvalidNode); + } + let subject = node_ports[0].target() as usize; + if subject >= index || nodes[subject].opcode() != rule { + return Err(DecodeError::InvalidNode); + } + let subject_node = &nodes[subject]; + let conclusion = node_ports.last().ok_or(DecodeError::InvalidNode)?.target(); + if subject_node.ty() != Some(conclusion) { + return Err(DecodeError::InvalidNode); + } + let subject_ports = ports + .get(subject_node.port_range()) + .ok_or(DecodeError::InvalidPortRange)?; + if subject_ports.len() != node_ports.len() - 2 + || subject_ports + .iter() + .map(Port::target) + .ne(node_ports[1..node_ports.len() - 1].iter().map(Port::target)) + { + return Err(DecodeError::InvalidNode); + } + } + _ => {} + } + Ok(()) +} + +fn validate_evidence_routes( + nodes: &[Node], + routes: &BTreeMap, +) -> Result<(), DecodeError> { + let mut attached = vec![false; nodes.len()]; + for (route, node_id) in routes { + let node = &nodes[*node_id as usize]; + if !node.opcode().is_executable() { + continue; + } + let judgment = *routes + .get(&route.composed(JUDGMENT_LOCUS)) + .ok_or(DecodeError::InvalidRoute)?; + let witness = *routes + .get(&route.composed(EVIDENCE_LOCUS)) + .ok_or(DecodeError::InvalidRoute)?; + if nodes[judgment as usize].opcode() != OpCode::TypeJudgment + || !matches!( + nodes[witness as usize].opcode(), + OpCode::KernelWitness | OpCode::Obligation + ) + || nodes[witness as usize].ty() != Some(judgment) + { + return Err(DecodeError::InvalidRoute); + } + attached[judgment as usize] = true; + attached[witness as usize] = true; + } + for (index, node) in nodes.iter().enumerate() { + if matches!( + node.opcode(), + OpCode::TypeJudgment | OpCode::KernelWitness | OpCode::Obligation + ) && !attached[index] + { + return Err(DecodeError::InvalidRoute); + } + } + Ok(()) +} + +fn validate_port_law(opcode: OpCode, ports: &[Port], has_type: bool) -> Result<(), DecodeError> { + let valid = match opcode { + OpCode::TypeAtom => ports.is_empty() && !has_type, + OpCode::TypeMatrix => { + ports.len() == 1 && ports[0].role() == PortRole::Parameter && !has_type + } + OpCode::TypeFunction => { + ports.len() == 2 + && ports[0].role() == PortRole::Domain + && ports[1].role() == PortRole::Codomain + && !has_type + } + OpCode::TypeQuantity => { + ports.len() == 1 && ports[0].role() == PortRole::Parameter && !has_type + } + OpCode::Constraint => ports.len() == 1 && ports[0].role() == PortRole::Subject && !has_type, + OpCode::Value => ports.is_empty() && has_type, + OpCode::Compose | OpCode::MatMul | OpCode::Add | OpCode::Multiply | OpCode::Divide => { + ports.len() == 2 + && ports.iter().all(|port| port.role() == PortRole::Argument) + && has_type + } + OpCode::Sqrt => ports.len() == 1 && ports[0].role() == PortRole::Argument && has_type, + OpCode::TypeJudgment => { + ports.len() >= 3 + && ports[0].role() == PortRole::Subject + && ports[1..ports.len() - 1] + .iter() + .all(|port| port.role() == PortRole::Premise) + && ports + .last() + .is_some_and(|port| port.role() == PortRole::Conclusion) + && !has_type + } + OpCode::KernelWitness => ports.is_empty() && has_type, + OpCode::Obligation => ports.len() == 1 && ports[0].role() == PortRole::Premise && has_type, + OpCode::ExtendContext => false, + }; + valid.then_some(()).ok_or(DecodeError::InvalidPortLaw) +} diff --git a/l64-native/src/dimension.rs b/l64-native/src/dimension.rs new file mode 100644 index 0000000..922d2cf --- /dev/null +++ b/l64-native/src/dimension.rs @@ -0,0 +1,66 @@ +const AXIS_COUNT: usize = 7; +const USED_BITS: u32 = (AXIS_COUNT as u32) * 8; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct Dimension(u64); + +impl Dimension { + pub const DIMENSIONLESS: Self = Self(0); + pub const LENGTH: Self = Self(1); + pub const MASS: Self = Self(1 << 8); + pub const TIME: Self = Self(1 << 16); + + pub fn new(exponents: [i8; AXIS_COUNT]) -> Self { + let mut bits = 0_u64; + for (axis, exponent) in exponents.into_iter().enumerate() { + bits |= u64::from(exponent as u8) << (axis * 8); + } + Self(bits) + } + + pub fn exponents(self) -> [i8; AXIS_COUNT] { + let mut exponents = [0_i8; AXIS_COUNT]; + for (axis, exponent) in exponents.iter_mut().enumerate() { + *exponent = ((self.0 >> (axis * 8)) as u8) as i8; + } + exponents + } + + pub fn bits(self) -> u64 { + self.0 + } + + pub(crate) fn from_bits(bits: u64) -> Option { + (bits >> USED_BITS == 0).then_some(Self(bits)) + } + + pub fn multiply(self, other: Self) -> Option { + self.combine(other, i8::checked_add) + } + + pub fn divide(self, other: Self) -> Option { + self.combine(other, i8::checked_sub) + } + + pub fn square_root(self) -> Option { + let mut result = [0_i8; AXIS_COUNT]; + for (axis, exponent) in self.exponents().into_iter().enumerate() { + if exponent % 2 != 0 { + return None; + } + result[axis] = exponent / 2; + } + Some(Self::new(result)) + } + + fn combine(self, other: Self, operation: fn(i8, i8) -> Option) -> Option { + let left = self.exponents(); + let right = other.exponents(); + let mut result = [0_i8; AXIS_COUNT]; + for axis in 0..AXIS_COUNT { + result[axis] = operation(left[axis], right[axis])?; + } + Some(Self::new(result)) + } +} diff --git a/l64-native/src/graph.rs b/l64-native/src/graph.rs index 9022e62..7ca66d3 100644 --- a/l64-native/src/graph.rs +++ b/l64-native/src/graph.rs @@ -1,6 +1,10 @@ use std::collections::BTreeMap; -use crate::{ContextDelta, JournalEvent, LocusWord, Obstruction, OpCode, Port, PortRole, Route}; +use crate::kernel::{ConstraintState, EvidencePlan}; +use crate::{ + ConstraintKind, ContextDelta, Dimension, JournalEvent, LocusWord, Obstruction, OpCode, Port, + PortRole, Route, +}; pub type NodeId = u32; pub type ContextId = u32; @@ -79,383 +83,7 @@ impl Default for Graph { } } -impl Graph { - pub(crate) fn from_decoded_parts( - nodes: Vec, - ports: Vec, - contexts: Vec, - routes: BTreeMap, - commitment: [u8; 32], - ) -> Self { - Self { - nodes, - ports, - contexts, - routes, - journal: Vec::new(), - commitment, - } - } - - pub fn new() -> Self { - let mut graph = Self { - nodes: Vec::new(), - ports: Vec::new(), - contexts: vec![ContextDelta::root()], - routes: BTreeMap::new(), - journal: Vec::new(), - commitment: [0; 32], - }; - graph.commitment = crate::codec::state_commitment(&graph); - graph - } - - pub fn node(&self, id: NodeId) -> Option<&Node> { - self.nodes.get(id as usize) - } - - pub fn ports(&self, node: NodeId) -> Option<&[Port]> { - let node = self.node(node)?; - self.ports.get(node.port_range()) - } - - pub fn resolve(&self, route: &Route) -> Option { - self.routes.get(route).copied() - } - - pub fn node_count(&self) -> usize { - self.nodes.len() - } - - pub fn port_count(&self) -> usize { - self.ports.len() - } - - pub fn context_count(&self) -> usize { - self.contexts.len() - } - - pub fn journal_len(&self) -> usize { - self.journal.len() - } - - pub fn state_commitment(&self) -> [u8; 32] { - self.commitment - } - - pub fn journal(&self) -> &[JournalEvent] { - &self.journal - } - - pub fn extend_context( - &mut self, - parent: ContextId, - binding: NodeId, - ) -> Result { - self.ensure_context(parent)?; - self.ensure_node(binding)?; - let before = self.commitment; - let id = self.contexts.len() as ContextId; - self.contexts.push(ContextDelta { parent, binding }); - let after = crate::codec::state_commitment(self); - self.push_event(OpCode::ExtendContext, binding, before, after); - self.commitment = after; - Ok(id) - } - - pub fn declare_atom_type( - &mut self, - route: Route, - atom_code: LocusWord, - ) -> Result { - self.insert_committed_node( - route, - ROOT_CONTEXT, - META_TYPE, - OpCode::TypeAtom, - atom_code.0, - &[], - ) - } - - pub fn declare_matrix_type( - &mut self, - route: Route, - element: NodeId, - rows: u32, - cols: u32, - ) -> Result { - self.ensure_type(element)?; - let payload = u64::from(rows) << 32 | u64::from(cols); - let port = Port::new(element, PortRole::Parameter, 0); - self.insert_committed_node( - route, - ROOT_CONTEXT, - META_TYPE, - OpCode::TypeMatrix, - payload, - &[port], - ) - } - - pub fn declare_function_type( - &mut self, - route: Route, - domain: NodeId, - codomain: NodeId, - ) -> Result { - self.ensure_type(domain)?; - self.ensure_type(codomain)?; - let ports = [ - Port::new(domain, PortRole::Domain, 0), - Port::new(codomain, PortRole::Codomain, 1), - ]; - self.insert_committed_node( - route, - ROOT_CONTEXT, - META_TYPE, - OpCode::TypeFunction, - 0, - &ports, - ) - } - - pub fn insert_value( - &mut self, - route: Route, - context: ContextId, - ty: NodeId, - ) -> Result { - self.ensure_context(context)?; - let type_node = self.ensure_type(ty)?; - if type_node.opcode() == OpCode::TypeJudgment { - return Err(Obstruction::EvidenceOnlyType { node: ty }); - } - self.insert_committed_node(route, context, ty, OpCode::Value, 0, &[]) - } - - pub(crate) fn insert_proven_operation( - &mut self, - routes: [Route; 3], - context: ContextId, - ty: NodeId, - opcode: OpCode, - inputs: &[NodeId], - ) -> crate::CommitResult { - let [route, judgment_route, witness_route] = routes; - let before = self.commitment; - - let operation = self.nodes.len() as NodeId; - let operation_first_port = self.ports.len() as u32; - self.ports.extend( - inputs - .iter() - .enumerate() - .map(|(ordinal, target)| Port::new(*target, PortRole::Argument, ordinal as u16)), - ); - self.nodes.push(Node { - payload: 0, - context, - ty, - first_port: operation_first_port, - opcode: opcode as u16, - port_count: inputs.len() as u16, - }); - - let judgment = self.nodes.len() as NodeId; - let judgment_first_port = self.ports.len() as u32; - self.ports.extend([ - Port::new(operation, PortRole::Subject, 0), - Port::new(inputs[0], PortRole::Premise, 1), - Port::new(inputs[1], PortRole::Premise, 2), - Port::new(ty, PortRole::Conclusion, 3), - ]); - self.nodes.push(Node { - payload: opcode as u64, - context, - ty: META_TYPE, - first_port: judgment_first_port, - opcode: OpCode::TypeJudgment as u16, - port_count: 4, - }); - - let evidence = self.nodes.len() as NodeId; - self.nodes.push(Node { - payload: 0, - context, - ty: judgment, - first_port: self.ports.len() as u32, - opcode: OpCode::KernelWitness as u16, - port_count: 0, - }); - - self.routes.insert(route, operation); - self.routes.insert(judgment_route, judgment); - self.routes.insert(witness_route, evidence); - - let after = crate::codec::state_commitment(self); - self.push_event(opcode, operation, before, after); - self.commitment = after; - crate::CommitResult { - node: operation, - evidence, - event: self.journal.len().saturating_sub(1) as EventId, - commitment: after, - } - } - - pub(crate) fn function_parts(&self, ty: NodeId) -> Result<(NodeId, NodeId), Obstruction> { - let node = self.ensure_type(ty)?; - if node.opcode() != OpCode::TypeFunction { - return Err(Obstruction::ExpectedFunctionType { node: ty }); - } - let ports = self - .ports(ty) - .ok_or(Obstruction::UnknownNode { node: ty })?; - if ports.len() != 2 { - return Err(Obstruction::MalformedType { node: ty }); - } - Ok((ports[0].target(), ports[1].target())) - } - - pub(crate) fn matrix_parts(&self, ty: NodeId) -> Result<(NodeId, u32, u32), Obstruction> { - let node = self.ensure_type(ty)?; - if node.opcode() != OpCode::TypeMatrix { - return Err(Obstruction::ExpectedMatrixType { node: ty }); - } - let ports = self - .ports(ty) - .ok_or(Obstruction::UnknownNode { node: ty })?; - if ports.len() != 1 { - return Err(Obstruction::MalformedType { node: ty }); - } - Ok(( - ports[0].target(), - (node.payload >> 32) as u32, - node.payload as u32, - )) - } - - pub(crate) fn matches_function_type( - &self, - ty: NodeId, - domain: NodeId, - codomain: NodeId, - ) -> bool { - self.function_parts(ty) == Ok((domain, codomain)) - } - - pub(crate) fn matches_matrix_type( - &self, - ty: NodeId, - element: NodeId, - rows: u32, - cols: u32, - ) -> bool { - self.matrix_parts(ty) == Ok((element, rows, cols)) - } - - pub(crate) fn ensure_node(&self, id: NodeId) -> Result<&Node, Obstruction> { - self.node(id).ok_or(Obstruction::UnknownNode { node: id }) - } - - pub(crate) fn ensure_type(&self, id: NodeId) -> Result<&Node, Obstruction> { - let node = self.ensure_node(id)?; - if !node.opcode().is_type() { - return Err(Obstruction::ExpectedType { node: id }); - } - Ok(node) - } - - pub(crate) fn ensure_context(&self, id: ContextId) -> Result<&ContextDelta, Obstruction> { - self.contexts - .get(id as usize) - .ok_or(Obstruction::UnknownContext { context: id }) - } - - fn insert_committed_node( - &mut self, - route: Route, - context: ContextId, - ty: NodeId, - opcode: OpCode, - payload: u64, - ports: &[Port], - ) -> Result { - if self.routes.contains_key(&route) { - return Err(Obstruction::RouteOccupied); - } - self.ensure_context(context)?; - for port in ports { - self.ensure_node(port.target())?; - } - if ty != META_TYPE { - self.ensure_type(ty)?; - } - - let before = self.commitment; - let node = self.nodes.len() as NodeId; - let first_port = self.ports.len() as u32; - self.ports.extend_from_slice(ports); - self.nodes.push(Node { - payload, - context, - ty, - first_port, - opcode: opcode as u16, - port_count: ports.len() as u16, - }); - self.routes.insert(route, node); - let after = crate::codec::state_commitment(self); - self.push_event(opcode, node, before, after); - self.commitment = after; - Ok(node) - } - - fn push_event( - &mut self, - operation: OpCode, - subject: NodeId, - before: [u8; 32], - after: [u8; 32], - ) { - let parent = self - .journal - .len() - .checked_sub(1) - .map(|value| value as EventId) - .unwrap_or(NO_EVENT); - self.journal.push(JournalEvent { - operation, - subject, - before, - after, - parent, - }); - } - - pub(crate) fn nodes_raw(&self) -> &[Node] { - &self.nodes - } - - pub(crate) fn ports_raw(&self) -> &[Port] { - &self.ports - } - - pub(crate) fn contexts_raw(&self) -> &[ContextDelta] { - &self.contexts - } - - pub(crate) fn routes_raw(&self) -> &BTreeMap { - &self.routes - } -} - -impl ContextDelta { - fn root() -> Self { - Self { - parent: ROOT_CONTEXT, - binding: NO_NODE, - } - } -} +include!("graph/construction.rs"); +include!("graph/typing.rs"); +include!("graph/storage.rs"); +include!("graph/context.rs"); diff --git a/l64-native/src/graph/construction.rs b/l64-native/src/graph/construction.rs new file mode 100644 index 0000000..1a99d5c --- /dev/null +++ b/l64-native/src/graph/construction.rs @@ -0,0 +1,277 @@ +impl Graph { + pub(crate) fn from_decoded_parts( + nodes: Vec, + ports: Vec, + contexts: Vec, + routes: BTreeMap, + commitment: [u8; 32], + ) -> Self { + Self { + nodes, + ports, + contexts, + routes, + journal: Vec::new(), + commitment, + } + } + + pub fn new() -> Self { + let mut graph = Self { + nodes: Vec::new(), + ports: Vec::new(), + contexts: vec![ContextDelta::root()], + routes: BTreeMap::new(), + journal: Vec::new(), + commitment: [0; 32], + }; + graph.commitment = crate::codec::state_commitment(&graph); + graph + } + + pub fn node(&self, id: NodeId) -> Option<&Node> { + self.nodes.get(id as usize) + } + + pub fn ports(&self, node: NodeId) -> Option<&[Port]> { + let node = self.node(node)?; + self.ports.get(node.port_range()) + } + + pub fn resolve(&self, route: &Route) -> Option { + self.routes.get(route).copied() + } + + pub fn node_count(&self) -> usize { + self.nodes.len() + } + + pub fn port_count(&self) -> usize { + self.ports.len() + } + + pub fn context_count(&self) -> usize { + self.contexts.len() + } + + pub fn journal_len(&self) -> usize { + self.journal.len() + } + + pub fn state_commitment(&self) -> [u8; 32] { + self.commitment + } + + pub fn journal(&self) -> &[JournalEvent] { + &self.journal + } + + pub fn extend_context( + &mut self, + parent: ContextId, + binding: NodeId, + ) -> Result { + self.ensure_context(parent)?; + self.ensure_node(binding)?; + self.validate_context_binding(parent, binding)?; + let before = self.commitment; + let id = self.contexts.len() as ContextId; + self.contexts.push(ContextDelta { parent, binding }); + let after = crate::codec::state_commitment(self); + self.push_event(OpCode::ExtendContext, binding, before, after); + self.commitment = after; + Ok(id) + } + + pub fn declare_atom_type( + &mut self, + route: Route, + atom_code: LocusWord, + ) -> Result { + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeAtom, + atom_code.0, + &[], + ) + } + + pub fn declare_matrix_type( + &mut self, + route: Route, + element: NodeId, + rows: u32, + cols: u32, + ) -> Result { + self.ensure_type(element)?; + let payload = u64::from(rows) << 32 | u64::from(cols); + let port = Port::new(element, PortRole::Parameter, 0); + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeMatrix, + payload, + &[port], + ) + } + + pub fn declare_function_type( + &mut self, + route: Route, + domain: NodeId, + codomain: NodeId, + ) -> Result { + self.ensure_type(domain)?; + self.ensure_type(codomain)?; + let ports = [ + Port::new(domain, PortRole::Domain, 0), + Port::new(codomain, PortRole::Codomain, 1), + ]; + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeFunction, + 0, + &ports, + ) + } + + pub fn declare_quantity_type( + &mut self, + route: Route, + carrier: NodeId, + dimension: Dimension, + ) -> Result { + self.ensure_type(carrier)?; + let port = Port::new(carrier, PortRole::Parameter, 0); + self.insert_committed_node( + route, + ROOT_CONTEXT, + META_TYPE, + OpCode::TypeQuantity, + dimension.bits(), + &[port], + ) + } + + pub fn declare_constraint( + &mut self, + route: Route, + context: ContextId, + subject: NodeId, + kind: ConstraintKind, + holds: bool, + ) -> Result { + self.ensure_context(context)?; + self.ensure_node(subject)?; + let port = Port::new(subject, PortRole::Subject, 0); + self.insert_committed_node( + route, + context, + META_TYPE, + OpCode::Constraint, + kind.payload(holds), + &[port], + ) + } + + pub fn insert_value( + &mut self, + route: Route, + context: ContextId, + ty: NodeId, + ) -> Result { + self.ensure_context(context)?; + let type_node = self.ensure_type(ty)?; + if type_node.opcode() == OpCode::TypeJudgment { + return Err(Obstruction::EvidenceOnlyType { node: ty }); + } + self.insert_committed_node(route, context, ty, OpCode::Value, 0, &[]) + } + + pub(crate) fn insert_admitted_operation( + &mut self, + routes: [Route; 3], + context: ContextId, + ty: NodeId, + opcode: OpCode, + inputs: &[NodeId], + evidence_plan: EvidencePlan, + ) -> crate::CommitResult { + let [route, judgment_route, evidence_route] = routes; + let before = self.commitment; + + let operation = self.nodes.len() as NodeId; + let operation_first_port = self.ports.len() as u32; + self.ports.extend( + inputs + .iter() + .enumerate() + .map(|(ordinal, target)| Port::new(*target, PortRole::Argument, ordinal as u16)), + ); + self.nodes.push(Node { + payload: 0, + context, + ty, + first_port: operation_first_port, + opcode: opcode as u16, + port_count: inputs.len() as u16, + }); + + let judgment = self.nodes.len() as NodeId; + let judgment_first_port = self.ports.len() as u32; + self.ports.push(Port::new(operation, PortRole::Subject, 0)); + self.ports.extend( + inputs + .iter() + .enumerate() + .map(|(ordinal, target)| Port::new(*target, PortRole::Premise, ordinal as u16 + 1)), + ); + self.ports + .push(Port::new(ty, PortRole::Conclusion, inputs.len() as u16 + 1)); + self.nodes.push(Node { + payload: opcode as u64, + context, + ty: META_TYPE, + first_port: judgment_first_port, + opcode: OpCode::TypeJudgment as u16, + port_count: inputs.len() as u16 + 2, + }); + + let evidence = self.nodes.len() as NodeId; + let evidence_first_port = self.ports.len() as u32; + let (evidence_opcode, evidence_payload, evidence_port_count) = match evidence_plan { + EvidencePlan::Witness => (OpCode::KernelWitness, 0, 0), + EvidencePlan::Obligation { kind, subject } => { + self.ports.push(Port::new(subject, PortRole::Premise, 0)); + (OpCode::Obligation, kind.payload(true), 1) + } + }; + self.nodes.push(Node { + payload: evidence_payload, + context, + ty: judgment, + first_port: evidence_first_port, + opcode: evidence_opcode as u16, + port_count: evidence_port_count, + }); + + self.routes.insert(route, operation); + self.routes.insert(judgment_route, judgment); + self.routes.insert(evidence_route, evidence); + + let after = crate::codec::state_commitment(self); + self.push_event(opcode, operation, before, after); + self.commitment = after; + crate::CommitResult { + node: operation, + evidence, + event: self.journal.len().saturating_sub(1) as EventId, + commitment: after, + } + } +} diff --git a/l64-native/src/graph/context.rs b/l64-native/src/graph/context.rs new file mode 100644 index 0000000..a1821a8 --- /dev/null +++ b/l64-native/src/graph/context.rs @@ -0,0 +1,8 @@ +impl ContextDelta { + fn root() -> Self { + Self { + parent: ROOT_CONTEXT, + binding: NO_NODE, + } + } +} diff --git a/l64-native/src/graph/storage.rs b/l64-native/src/graph/storage.rs new file mode 100644 index 0000000..b6ba2d2 --- /dev/null +++ b/l64-native/src/graph/storage.rs @@ -0,0 +1,127 @@ +impl Graph { + pub(crate) fn validate_context_consistency(&self) -> Result<(), Obstruction> { + for context in 1..self.contexts.len() as ContextId { + let delta = self.ensure_context(context)?; + let binding = delta + .binding() + .ok_or(Obstruction::UnknownContext { context })?; + self.validate_context_binding(delta.parent(), binding)?; + } + Ok(()) + } + + fn validate_context_binding( + &self, + parent: ContextId, + binding: NodeId, + ) -> Result<(), Obstruction> { + let node = self.ensure_node(binding)?; + if node.opcode() != OpCode::Constraint { + return Ok(()); + } + let (subject, kind, holds) = self.constraint_parts(binding)?; + let inherited = self.constraint_state(parent, subject, kind)?; + if matches!( + (inherited, holds), + (ConstraintState::Proven, false) | (ConstraintState::Refuted, true) + ) { + return Err(Obstruction::ContradictoryConstraint { subject, kind }); + } + Ok(()) + } + + pub(crate) fn ensure_node(&self, id: NodeId) -> Result<&Node, Obstruction> { + self.node(id).ok_or(Obstruction::UnknownNode { node: id }) + } + + pub(crate) fn ensure_type(&self, id: NodeId) -> Result<&Node, Obstruction> { + let node = self.ensure_node(id)?; + if !node.opcode().is_type() { + return Err(Obstruction::ExpectedType { node: id }); + } + Ok(node) + } + + pub(crate) fn ensure_context(&self, id: ContextId) -> Result<&ContextDelta, Obstruction> { + self.contexts + .get(id as usize) + .ok_or(Obstruction::UnknownContext { context: id }) + } + + fn insert_committed_node( + &mut self, + route: Route, + context: ContextId, + ty: NodeId, + opcode: OpCode, + payload: u64, + ports: &[Port], + ) -> Result { + if self.routes.contains_key(&route) { + return Err(Obstruction::RouteOccupied); + } + self.ensure_context(context)?; + for port in ports { + self.ensure_node(port.target())?; + } + if ty != META_TYPE { + self.ensure_type(ty)?; + } + + let before = self.commitment; + let node = self.nodes.len() as NodeId; + let first_port = self.ports.len() as u32; + self.ports.extend_from_slice(ports); + self.nodes.push(Node { + payload, + context, + ty, + first_port, + opcode: opcode as u16, + port_count: ports.len() as u16, + }); + self.routes.insert(route, node); + let after = crate::codec::state_commitment(self); + self.push_event(opcode, node, before, after); + self.commitment = after; + Ok(node) + } + + fn push_event( + &mut self, + operation: OpCode, + subject: NodeId, + before: [u8; 32], + after: [u8; 32], + ) { + let parent = self + .journal + .len() + .checked_sub(1) + .map(|value| value as EventId) + .unwrap_or(NO_EVENT); + self.journal.push(JournalEvent { + operation, + subject, + before, + after, + parent, + }); + } + + pub(crate) fn nodes_raw(&self) -> &[Node] { + &self.nodes + } + + pub(crate) fn ports_raw(&self) -> &[Port] { + &self.ports + } + + pub(crate) fn contexts_raw(&self) -> &[ContextDelta] { + &self.contexts + } + + pub(crate) fn routes_raw(&self) -> &BTreeMap { + &self.routes + } +} diff --git a/l64-native/src/graph/typing.rs b/l64-native/src/graph/typing.rs new file mode 100644 index 0000000..3e90405 --- /dev/null +++ b/l64-native/src/graph/typing.rs @@ -0,0 +1,126 @@ +impl Graph { + pub(crate) fn function_parts(&self, ty: NodeId) -> Result<(NodeId, NodeId), Obstruction> { + let node = self.ensure_type(ty)?; + if node.opcode() != OpCode::TypeFunction { + return Err(Obstruction::ExpectedFunctionType { node: ty }); + } + let ports = self + .ports(ty) + .ok_or(Obstruction::UnknownNode { node: ty })?; + if ports.len() != 2 { + return Err(Obstruction::MalformedType { node: ty }); + } + Ok((ports[0].target(), ports[1].target())) + } + + pub(crate) fn matrix_parts(&self, ty: NodeId) -> Result<(NodeId, u32, u32), Obstruction> { + let node = self.ensure_type(ty)?; + if node.opcode() != OpCode::TypeMatrix { + return Err(Obstruction::ExpectedMatrixType { node: ty }); + } + let ports = self + .ports(ty) + .ok_or(Obstruction::UnknownNode { node: ty })?; + if ports.len() != 1 { + return Err(Obstruction::MalformedType { node: ty }); + } + Ok(( + ports[0].target(), + (node.payload >> 32) as u32, + node.payload as u32, + )) + } + + pub(crate) fn matches_function_type( + &self, + ty: NodeId, + domain: NodeId, + codomain: NodeId, + ) -> bool { + self.function_parts(ty) == Ok((domain, codomain)) + } + + pub(crate) fn quantity_parts(&self, ty: NodeId) -> Result<(NodeId, Dimension), Obstruction> { + let node = self.ensure_type(ty)?; + if node.opcode() != OpCode::TypeQuantity { + return Err(Obstruction::ExpectedQuantityType { node: ty }); + } + let ports = self + .ports(ty) + .ok_or(Obstruction::UnknownNode { node: ty })?; + let dimension = + Dimension::from_bits(node.payload()).ok_or(Obstruction::MalformedType { node: ty })?; + if ports.len() != 1 { + return Err(Obstruction::MalformedType { node: ty }); + } + Ok((ports[0].target(), dimension)) + } + + pub(crate) fn matches_matrix_type( + &self, + ty: NodeId, + element: NodeId, + rows: u32, + cols: u32, + ) -> bool { + self.matrix_parts(ty) == Ok((element, rows, cols)) + } + + pub(crate) fn matches_quantity_type( + &self, + ty: NodeId, + carrier: NodeId, + dimension: Dimension, + ) -> bool { + self.quantity_parts(ty) == Ok((carrier, dimension)) + } + + pub(crate) fn constraint_parts( + &self, + node: NodeId, + ) -> Result<(NodeId, ConstraintKind, bool), Obstruction> { + let constraint = self.ensure_node(node)?; + if constraint.opcode() != OpCode::Constraint { + return Err(Obstruction::MalformedConstraint { node }); + } + let ports = self + .ports(node) + .ok_or(Obstruction::MalformedConstraint { node })?; + let (kind, holds) = ConstraintKind::from_payload(constraint.payload()) + .ok_or(Obstruction::MalformedConstraint { node })?; + if ports.len() != 1 || ports[0].role() != PortRole::Subject { + return Err(Obstruction::MalformedConstraint { node }); + } + Ok((ports[0].target(), kind, holds)) + } + + pub(crate) fn constraint_state( + &self, + context: ContextId, + subject: NodeId, + kind: ConstraintKind, + ) -> Result { + let mut cursor = context; + loop { + let delta = self.ensure_context(cursor)?; + if let Some(binding) = delta.binding() + && self + .node(binding) + .is_some_and(|node| node.opcode() == OpCode::Constraint) + { + let (bound_subject, bound_kind, holds) = self.constraint_parts(binding)?; + if bound_subject == subject && bound_kind == kind { + return Ok(if holds { + ConstraintState::Proven + } else { + ConstraintState::Refuted + }); + } + } + if cursor == ROOT_CONTEXT { + return Ok(ConstraintState::Unknown); + } + cursor = delta.parent(); + } + } +} diff --git a/l64-native/src/kernel.rs b/l64-native/src/kernel.rs index ba2ed3a..1d7d8b9 100644 --- a/l64-native/src/kernel.rs +++ b/l64-native/src/kernel.rs @@ -1,318 +1,6 @@ -use crate::{ContextId, Graph, LocusWord, NodeId, Route}; +use crate::{ContextId, Dimension, Graph, LocusWord, NodeId, Route}; -pub(crate) const JUDGMENT_LOCUS: LocusWord = LocusWord(u64::MAX - 1); -pub(crate) const WITNESS_LOCUS: LocusWord = LocusWord(u64::MAX); - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum OpCode { - TypeAtom = 1, - TypeMatrix = 2, - TypeFunction = 3, - Value = 4, - Compose = 5, - MatMul = 6, - ExtendContext = 7, - TypeJudgment = 8, - KernelWitness = 9, -} - -impl OpCode { - pub(crate) fn from_raw(value: u16) -> Option { - match value { - 1 => Some(Self::TypeAtom), - 2 => Some(Self::TypeMatrix), - 3 => Some(Self::TypeFunction), - 4 => Some(Self::Value), - 5 => Some(Self::Compose), - 6 => Some(Self::MatMul), - 7 => Some(Self::ExtendContext), - 8 => Some(Self::TypeJudgment), - 9 => Some(Self::KernelWitness), - _ => None, - } - } - - pub(crate) fn is_type(self) -> bool { - matches!( - self, - Self::TypeAtom | Self::TypeMatrix | Self::TypeFunction | Self::TypeJudgment - ) - } - - pub(crate) fn is_persisted_node(self) -> bool { - self != Self::ExtendContext - } - - pub(crate) fn is_executable(self) -> bool { - matches!(self, Self::Compose | Self::MatMul) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum PortRole { - Parameter = 1, - Domain = 2, - Codomain = 3, - Argument = 4, - Subject = 5, - Premise = 6, - Conclusion = 7, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub struct Port { - target: NodeId, - role: u8, - flags: u8, - ordinal: u16, -} - -impl PortRole { - pub(crate) fn from_raw(value: u8) -> Option { - match value { - 1 => Some(Self::Parameter), - 2 => Some(Self::Domain), - 3 => Some(Self::Codomain), - 4 => Some(Self::Argument), - 5 => Some(Self::Subject), - 6 => Some(Self::Premise), - 7 => Some(Self::Conclusion), - _ => None, - } - } -} - -impl Port { - pub(crate) fn new(target: NodeId, role: PortRole, ordinal: u16) -> Self { - Self { - target, - role: role as u8, - flags: 0, - ordinal, - } - } - - pub(crate) fn from_raw(target: NodeId, role: PortRole, flags: u8, ordinal: u16) -> Self { - Self { - target, - role: role as u8, - flags, - ordinal, - } - } - - pub fn target(&self) -> NodeId { - self.target - } - - pub fn role(&self) -> PortRole { - PortRole::from_raw(self.role).expect("stored port role is validated at insertion") - } - - pub fn ordinal(&self) -> u16 { - self.ordinal - } - - pub(crate) fn flags(&self) -> u8 { - self.flags - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Proposal { - route: Route, - context: ContextId, - opcode: OpCode, - inputs: Box<[NodeId]>, - output_type: NodeId, -} - -impl Proposal { - pub fn compose( - route: Route, - context: ContextId, - first: NodeId, - second: NodeId, - output_type: NodeId, - ) -> Self { - Self { - route, - context, - opcode: OpCode::Compose, - inputs: Box::new([first, second]), - output_type, - } - } - - pub fn matrix_multiply( - route: Route, - context: ContextId, - left: NodeId, - right: NodeId, - output_type: NodeId, - ) -> Self { - Self { - route, - context, - opcode: OpCode::MatMul, - inputs: Box::new([left, right]), - output_type, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct CommitResult { - pub node: NodeId, - pub evidence: NodeId, - pub event: u32, - pub commitment: [u8; 32], -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Obstruction { - UnknownNode { - node: NodeId, - }, - UnknownContext { - context: ContextId, - }, - RouteOccupied, - ExpectedType { - node: NodeId, - }, - ExpectedFunctionType { - node: NodeId, - }, - ExpectedMatrixType { - node: NodeId, - }, - MalformedType { - node: NodeId, - }, - EvidenceOnlyType { - node: NodeId, - }, - Arity { - expected: u16, - actual: u16, - }, - FunctionBoundaryMismatch { - left: NodeId, - right: NodeId, - }, - MatrixShapeMismatch { - left_cols: u32, - right_rows: u32, - }, - MatrixElementMismatch { - left: NodeId, - right: NodeId, - }, - FunctionOutputMismatch { - output_type: NodeId, - domain: NodeId, - codomain: NodeId, - }, - MatrixOutputMismatch { - output_type: NodeId, - element: NodeId, - rows: u32, - cols: u32, - }, -} - -impl Graph { - pub fn transact(&mut self, proposal: Proposal) -> Result { - self.ensure_context(proposal.context)?; - self.ensure_type(proposal.output_type)?; - if proposal.inputs.len() != 2 { - return Err(Obstruction::Arity { - expected: 2, - actual: proposal.inputs.len() as u16, - }); - } - - let judgment_route = proposal.route.composed(JUDGMENT_LOCUS); - let witness_route = proposal.route.composed(WITNESS_LOCUS); - if self.resolve(&proposal.route).is_some() - || self.resolve(&judgment_route).is_some() - || self.resolve(&witness_route).is_some() - { - return Err(Obstruction::RouteOccupied); - } - - let left = proposal.inputs[0]; - let right = proposal.inputs[1]; - let left_node = self.ensure_node(left)?; - let right_node = self.ensure_node(right)?; - let left_ty = left_node - .ty() - .ok_or(Obstruction::ExpectedType { node: left })?; - let right_ty = right_node - .ty() - .ok_or(Obstruction::ExpectedType { node: right })?; - - match proposal.opcode { - OpCode::Compose => { - let (first_domain, first_codomain) = self.function_parts(left_ty)?; - let (second_domain, second_codomain) = self.function_parts(right_ty)?; - if first_codomain != second_domain { - return Err(Obstruction::FunctionBoundaryMismatch { - left: first_codomain, - right: second_domain, - }); - } - if !self.matches_function_type(proposal.output_type, first_domain, second_codomain) - { - return Err(Obstruction::FunctionOutputMismatch { - output_type: proposal.output_type, - domain: first_domain, - codomain: second_codomain, - }); - } - } - OpCode::MatMul => { - let (left_element, left_rows, left_cols) = self.matrix_parts(left_ty)?; - let (right_element, right_rows, right_cols) = self.matrix_parts(right_ty)?; - if left_cols != right_rows { - return Err(Obstruction::MatrixShapeMismatch { - left_cols, - right_rows, - }); - } - if left_element != right_element { - return Err(Obstruction::MatrixElementMismatch { - left: left_element, - right: right_element, - }); - } - if !self.matches_matrix_type( - proposal.output_type, - left_element, - left_rows, - right_cols, - ) { - return Err(Obstruction::MatrixOutputMismatch { - output_type: proposal.output_type, - element: left_element, - rows: left_rows, - cols: right_cols, - }); - } - } - _ => unreachable!("public proposal constructors expose executable operations only"), - } - - Ok(self.insert_proven_operation( - [proposal.route, judgment_route, witness_route], - proposal.context, - proposal.output_type, - proposal.opcode, - &proposal.inputs, - )) - } -} +include!("kernel/types.rs"); +include!("kernel/proposal.rs"); +include!("kernel/transaction.rs"); +include!("kernel/validation.rs"); diff --git a/l64-native/src/kernel/proposal.rs b/l64-native/src/kernel/proposal.rs new file mode 100644 index 0000000..63f820b --- /dev/null +++ b/l64-native/src/kernel/proposal.rs @@ -0,0 +1,197 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Proposal { + route: Route, + context: ContextId, + opcode: OpCode, + inputs: Box<[NodeId]>, + output_type: NodeId, +} + +impl Proposal { + fn binary( + route: Route, + context: ContextId, + opcode: OpCode, + left: NodeId, + right: NodeId, + output_type: NodeId, + ) -> Self { + Self { + route, + context, + opcode, + inputs: Box::new([left, right]), + output_type, + } + } + + fn unary( + route: Route, + context: ContextId, + opcode: OpCode, + input: NodeId, + output_type: NodeId, + ) -> Self { + Self { + route, + context, + opcode, + inputs: Box::new([input]), + output_type, + } + } + + pub fn compose( + route: Route, + context: ContextId, + first: NodeId, + second: NodeId, + output_type: NodeId, + ) -> Self { + Self::binary(route, context, OpCode::Compose, first, second, output_type) + } + + pub fn matrix_multiply( + route: Route, + context: ContextId, + left: NodeId, + right: NodeId, + output_type: NodeId, + ) -> Self { + Self::binary(route, context, OpCode::MatMul, left, right, output_type) + } + + pub fn add( + route: Route, + context: ContextId, + left: NodeId, + right: NodeId, + output_type: NodeId, + ) -> Self { + Self::binary(route, context, OpCode::Add, left, right, output_type) + } + + pub fn multiply( + route: Route, + context: ContextId, + left: NodeId, + right: NodeId, + output_type: NodeId, + ) -> Self { + Self::binary(route, context, OpCode::Multiply, left, right, output_type) + } + + pub fn divide( + route: Route, + context: ContextId, + left: NodeId, + right: NodeId, + output_type: NodeId, + ) -> Self { + Self::binary(route, context, OpCode::Divide, left, right, output_type) + } + + pub fn square_root( + route: Route, + context: ContextId, + input: NodeId, + output_type: NodeId, + ) -> Self { + Self::unary(route, context, OpCode::Sqrt, input, output_type) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CommitResult { + pub node: NodeId, + pub evidence: NodeId, + pub event: u32, + pub commitment: [u8; 32], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Obstruction { + UnknownNode { + node: NodeId, + }, + UnknownContext { + context: ContextId, + }, + RouteOccupied, + ExpectedType { + node: NodeId, + }, + ExpectedFunctionType { + node: NodeId, + }, + ExpectedMatrixType { + node: NodeId, + }, + ExpectedQuantityType { + node: NodeId, + }, + MalformedType { + node: NodeId, + }, + EvidenceOnlyType { + node: NodeId, + }, + Arity { + expected: u16, + actual: u16, + }, + FunctionBoundaryMismatch { + left: NodeId, + right: NodeId, + }, + MatrixShapeMismatch { + left_cols: u32, + right_rows: u32, + }, + MatrixElementMismatch { + left: NodeId, + right: NodeId, + }, + FunctionOutputMismatch { + output_type: NodeId, + domain: NodeId, + codomain: NodeId, + }, + MatrixOutputMismatch { + output_type: NodeId, + element: NodeId, + rows: u32, + cols: u32, + }, + QuantityCarrierMismatch { + left: NodeId, + right: NodeId, + }, + DimensionMismatch { + left: Dimension, + right: Dimension, + }, + DimensionOverflow, + NonIntegralDimensionRoot { + dimension: Dimension, + }, + GuardViolated { + subject: NodeId, + kind: ConstraintKind, + }, + QuantityOutputMismatch { + output_type: NodeId, + carrier: NodeId, + dimension: Dimension, + }, + MalformedConstraint { + node: NodeId, + }, + ContradictoryConstraint { + subject: NodeId, + kind: ConstraintKind, + }, + MalformedEvidence { + node: NodeId, + }, +} diff --git a/l64-native/src/kernel/transaction.rs b/l64-native/src/kernel/transaction.rs new file mode 100644 index 0000000..e759638 --- /dev/null +++ b/l64-native/src/kernel/transaction.rs @@ -0,0 +1,183 @@ +impl Graph { + pub fn transact(&mut self, proposal: Proposal) -> Result { + self.ensure_context(proposal.context)?; + self.ensure_type(proposal.output_type)?; + let expected = proposal + .opcode + .arity() + .expect("public proposal constructors expose executable operations only"); + if proposal.inputs.len() != expected as usize { + return Err(Obstruction::Arity { + expected, + actual: proposal.inputs.len() as u16, + }); + } + + let judgment_route = proposal.route.composed(JUDGMENT_LOCUS); + let evidence_route = proposal.route.composed(EVIDENCE_LOCUS); + if self.resolve(&proposal.route).is_some() + || self.resolve(&judgment_route).is_some() + || self.resolve(&evidence_route).is_some() + { + return Err(Obstruction::RouteOccupied); + } + + let evidence = self.validate_operation( + proposal.context, + proposal.opcode, + &proposal.inputs, + proposal.output_type, + )?; + + Ok(self.insert_admitted_operation( + [proposal.route, judgment_route, evidence_route], + proposal.context, + proposal.output_type, + proposal.opcode, + &proposal.inputs, + evidence, + )) + } + + pub(crate) fn validate_operation( + &self, + _context: ContextId, + opcode: OpCode, + inputs: &[NodeId], + output_type: NodeId, + ) -> Result { + let expected = opcode.arity().ok_or(Obstruction::Arity { + expected: 0, + actual: inputs.len() as u16, + })?; + if inputs.len() != expected as usize { + return Err(Obstruction::Arity { + expected, + actual: inputs.len() as u16, + }); + } + self.ensure_type(output_type)?; + + let left = inputs[0]; + let left_ty = self + .ensure_node(left)? + .ty() + .ok_or(Obstruction::ExpectedType { node: left })?; + let right = inputs.get(1).copied(); + let right_ty = right + .map(|node| { + self.ensure_node(node)? + .ty() + .ok_or(Obstruction::ExpectedType { node }) + }) + .transpose()?; + + match opcode { + OpCode::Compose => { + let right_ty = right_ty.expect("binary arity validated"); + let (first_domain, first_codomain) = self.function_parts(left_ty)?; + let (second_domain, second_codomain) = self.function_parts(right_ty)?; + if first_codomain != second_domain { + return Err(Obstruction::FunctionBoundaryMismatch { + left: first_codomain, + right: second_domain, + }); + } + if !self.matches_function_type(output_type, first_domain, second_codomain) { + return Err(Obstruction::FunctionOutputMismatch { + output_type, + domain: first_domain, + codomain: second_codomain, + }); + } + } + OpCode::MatMul => { + let right_ty = right_ty.expect("binary arity validated"); + let (left_element, left_rows, left_cols) = self.matrix_parts(left_ty)?; + let (right_element, right_rows, right_cols) = self.matrix_parts(right_ty)?; + if left_cols != right_rows { + return Err(Obstruction::MatrixShapeMismatch { + left_cols, + right_rows, + }); + } + if left_element != right_element { + return Err(Obstruction::MatrixElementMismatch { + left: left_element, + right: right_element, + }); + } + if !self.matches_matrix_type(output_type, left_element, left_rows, right_cols) { + return Err(Obstruction::MatrixOutputMismatch { + output_type, + element: left_element, + rows: left_rows, + cols: right_cols, + }); + } + } + OpCode::Add | OpCode::Multiply | OpCode::Divide => { + let right_ty = right_ty.expect("binary arity validated"); + let (left_carrier, left_dimension) = self.quantity_parts(left_ty)?; + let (right_carrier, right_dimension) = self.quantity_parts(right_ty)?; + if left_carrier != right_carrier { + return Err(Obstruction::QuantityCarrierMismatch { + left: left_carrier, + right: right_carrier, + }); + } + let dimension = match opcode { + OpCode::Add => { + if left_dimension != right_dimension { + return Err(Obstruction::DimensionMismatch { + left: left_dimension, + right: right_dimension, + }); + } + left_dimension + } + OpCode::Multiply => left_dimension + .multiply(right_dimension) + .ok_or(Obstruction::DimensionOverflow)?, + OpCode::Divide => left_dimension + .divide(right_dimension) + .ok_or(Obstruction::DimensionOverflow)?, + _ => unreachable!(), + }; + if !self.matches_quantity_type(output_type, left_carrier, dimension) { + return Err(Obstruction::QuantityOutputMismatch { + output_type, + carrier: left_carrier, + dimension, + }); + } + } + OpCode::Sqrt => { + let (carrier, dimension) = self.quantity_parts(left_ty)?; + let output_dimension = dimension + .square_root() + .ok_or(Obstruction::NonIntegralDimensionRoot { dimension })?; + if !self.matches_quantity_type(output_type, carrier, output_dimension) { + return Err(Obstruction::QuantityOutputMismatch { + output_type, + carrier, + dimension: output_dimension, + }); + } + return match self.constraint_state(_context, left, ConstraintKind::NonNegative)? { + ConstraintState::Proven => Ok(EvidencePlan::Witness), + ConstraintState::Refuted => Err(Obstruction::GuardViolated { + subject: left, + kind: ConstraintKind::NonNegative, + }), + ConstraintState::Unknown => Ok(EvidencePlan::Obligation { + kind: ConstraintKind::NonNegative, + subject: left, + }), + }; + } + _ => unreachable!("validated opcode is executable"), + } + Ok(EvidencePlan::Witness) + } +} diff --git a/l64-native/src/kernel/types.rs b/l64-native/src/kernel/types.rs new file mode 100644 index 0000000..28b8f4f --- /dev/null +++ b/l64-native/src/kernel/types.rs @@ -0,0 +1,197 @@ +pub(crate) const JUDGMENT_LOCUS: LocusWord = LocusWord(u64::MAX - 1); +pub(crate) const EVIDENCE_LOCUS: LocusWord = LocusWord(u64::MAX); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum OpCode { + TypeAtom = 1, + TypeMatrix = 2, + TypeFunction = 3, + Value = 4, + Compose = 5, + MatMul = 6, + ExtendContext = 7, + TypeJudgment = 8, + KernelWitness = 9, + TypeQuantity = 10, + Add = 11, + Multiply = 12, + Divide = 13, + Constraint = 14, + Sqrt = 15, + Obligation = 16, +} + +impl OpCode { + pub(crate) fn from_raw(value: u16) -> Option { + match value { + 1 => Some(Self::TypeAtom), + 2 => Some(Self::TypeMatrix), + 3 => Some(Self::TypeFunction), + 4 => Some(Self::Value), + 5 => Some(Self::Compose), + 6 => Some(Self::MatMul), + 7 => Some(Self::ExtendContext), + 8 => Some(Self::TypeJudgment), + 9 => Some(Self::KernelWitness), + 10 => Some(Self::TypeQuantity), + 11 => Some(Self::Add), + 12 => Some(Self::Multiply), + 13 => Some(Self::Divide), + 14 => Some(Self::Constraint), + 15 => Some(Self::Sqrt), + 16 => Some(Self::Obligation), + _ => None, + } + } + + pub(crate) fn is_type(self) -> bool { + matches!( + self, + Self::TypeAtom + | Self::TypeMatrix + | Self::TypeFunction + | Self::TypeJudgment + | Self::TypeQuantity + ) + } + + pub(crate) fn is_untyped_authority(self) -> bool { + self.is_type() || self == Self::Constraint + } + + pub(crate) fn is_persisted_node(self) -> bool { + self != Self::ExtendContext + } + + pub(crate) fn is_executable(self) -> bool { + matches!( + self, + Self::Compose | Self::MatMul | Self::Add | Self::Multiply | Self::Divide | Self::Sqrt + ) + } + + pub(crate) fn arity(self) -> Option { + match self { + Self::Compose | Self::MatMul | Self::Add | Self::Multiply | Self::Divide => Some(2), + Self::Sqrt => Some(1), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum ConstraintKind { + NonNegative = 1, +} + +impl ConstraintKind { + pub(crate) fn from_raw(value: u8) -> Option { + match value { + 1 => Some(Self::NonNegative), + _ => None, + } + } + + pub(crate) fn payload(self, holds: bool) -> u64 { + self as u64 | (u64::from(holds) << 8) + } + + pub(crate) fn from_payload(payload: u64) -> Option<(Self, bool)> { + if payload >> 9 != 0 { + return None; + } + let kind = Self::from_raw(payload as u8)?; + let truth = ((payload >> 8) & 1) != 0; + Some((kind, truth)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ConstraintState { + Proven, + Refuted, + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum EvidencePlan { + Witness, + Obligation { + kind: ConstraintKind, + subject: NodeId, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum PortRole { + Parameter = 1, + Domain = 2, + Codomain = 3, + Argument = 4, + Subject = 5, + Premise = 6, + Conclusion = 7, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct Port { + target: NodeId, + role: u8, + flags: u8, + ordinal: u16, +} + +impl PortRole { + pub(crate) fn from_raw(value: u8) -> Option { + match value { + 1 => Some(Self::Parameter), + 2 => Some(Self::Domain), + 3 => Some(Self::Codomain), + 4 => Some(Self::Argument), + 5 => Some(Self::Subject), + 6 => Some(Self::Premise), + 7 => Some(Self::Conclusion), + _ => None, + } + } +} + +impl Port { + pub(crate) fn new(target: NodeId, role: PortRole, ordinal: u16) -> Self { + Self { + target, + role: role as u8, + flags: 0, + ordinal, + } + } + + pub(crate) fn from_raw(target: NodeId, role: PortRole, flags: u8, ordinal: u16) -> Self { + Self { + target, + role: role as u8, + flags, + ordinal, + } + } + + pub fn target(&self) -> NodeId { + self.target + } + + pub fn role(&self) -> PortRole { + PortRole::from_raw(self.role).expect("stored port role is validated at insertion") + } + + pub fn ordinal(&self) -> u16 { + self.ordinal + } + + pub(crate) fn flags(&self) -> u8 { + self.flags + } +} diff --git a/l64-native/src/kernel/validation.rs b/l64-native/src/kernel/validation.rs new file mode 100644 index 0000000..aa1d3e9 --- /dev/null +++ b/l64-native/src/kernel/validation.rs @@ -0,0 +1,84 @@ +impl Graph { + pub(crate) fn validate_decoded_authority(&self) -> Result<(), Obstruction> { + self.validate_context_consistency()?; + let mut attached = vec![false; self.node_count()]; + for (route, node_id) in self.routes_raw() { + let node = self + .node(*node_id) + .ok_or(Obstruction::UnknownNode { node: *node_id })?; + if !node.opcode().is_executable() { + continue; + } + let operation_ports = self + .ports(*node_id) + .ok_or(Obstruction::MalformedEvidence { node: *node_id })?; + let inputs = operation_ports.iter().map(Port::target).collect::>(); + let output_type = node + .ty() + .ok_or(Obstruction::ExpectedType { node: *node_id })?; + let plan = + self.validate_operation(node.context(), node.opcode(), &inputs, output_type)?; + + let judgment = self + .resolve(&route.composed(JUDGMENT_LOCUS)) + .ok_or(Obstruction::MalformedEvidence { node: *node_id })?; + let evidence = self + .resolve(&route.composed(EVIDENCE_LOCUS)) + .ok_or(Obstruction::MalformedEvidence { node: *node_id })?; + let judgment_node = self + .node(judgment) + .ok_or(Obstruction::MalformedEvidence { node: judgment })?; + let judgment_ports = self + .ports(judgment) + .ok_or(Obstruction::MalformedEvidence { node: judgment })?; + if judgment_node.opcode() != OpCode::TypeJudgment + || judgment_node.payload() != node.opcode() as u64 + || judgment_ports.len() != inputs.len() + 2 + || judgment_ports[0].target() != *node_id + || judgment_ports.last().map(Port::target) != Some(output_type) + || judgment_ports[1..judgment_ports.len() - 1] + .iter() + .map(Port::target) + .ne(inputs.iter().copied()) + { + return Err(Obstruction::MalformedEvidence { node: judgment }); + } + let evidence_node = self + .node(evidence) + .ok_or(Obstruction::MalformedEvidence { node: evidence })?; + let evidence_ports = self + .ports(evidence) + .ok_or(Obstruction::MalformedEvidence { node: evidence })?; + let evidence_valid = match plan { + EvidencePlan::Witness => { + evidence_node.opcode() == OpCode::KernelWitness && evidence_ports.is_empty() + } + EvidencePlan::Obligation { kind, subject } => { + evidence_node.opcode() == OpCode::Obligation + && ConstraintKind::from_payload(evidence_node.payload()) + == Some((kind, true)) + && evidence_ports.len() == 1 + && evidence_ports[0].role() == PortRole::Premise + && evidence_ports[0].target() == subject + } + }; + if !evidence_valid || evidence_node.ty() != Some(judgment) { + return Err(Obstruction::MalformedEvidence { node: evidence }); + } + attached[judgment as usize] = true; + attached[evidence as usize] = true; + } + for (index, node) in self.nodes_raw().iter().enumerate() { + if matches!( + node.opcode(), + OpCode::TypeJudgment | OpCode::KernelWitness | OpCode::Obligation + ) && !attached[index] + { + return Err(Obstruction::MalformedEvidence { + node: index as NodeId, + }); + } + } + Ok(()) + } +} diff --git a/l64-native/src/lib.rs b/l64-native/src/lib.rs index 161cbf5..8e31bcc 100644 --- a/l64-native/src/lib.rs +++ b/l64-native/src/lib.rs @@ -2,6 +2,7 @@ mod codec; mod context; +mod dimension; mod dna; mod graph; mod journal; @@ -11,10 +12,11 @@ mod route; pub use codec::{DecodeError, canonical_bytes, decode_canonical}; pub use context::ContextDelta; +pub use dimension::Dimension; pub use dna::{DnaError, MAX_NATIVE_DNA_PAYLOAD_BYTES, decode_dna, dna_bytes}; pub use graph::{ContextId, EventId, Graph, Node, NodeId, ROOT_CONTEXT}; pub use journal::JournalEvent; -pub use kernel::{CommitResult, Obstruction, OpCode, Port, PortRole, Proposal}; +pub use kernel::{CommitResult, ConstraintKind, Obstruction, OpCode, Port, PortRole, Proposal}; pub use rna::{ MAX_NATIVE_RNA_BYTES, RnaError, compile_rna, dna_to_rna, normalize_rna, rna_bytes, rna_to_dna, }; diff --git a/l64-native/src/rna.rs b/l64-native/src/rna.rs index 1fe898c..294c7b3 100644 --- a/l64-native/src/rna.rs +++ b/l64-native/src/rna.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; use crate::{ - DnaError, Graph, LocusWord, NodeId, Obstruction, OpCode, Proposal, ROOT_CONTEXT, Route, - decode_dna, dna_bytes, + ConstraintKind, ContextId, Dimension, DnaError, Graph, LocusWord, NodeId, Obstruction, OpCode, + Proposal, ROOT_CONTEXT, Route, decode_dna, dna_bytes, }; const RNA_MAGIC: &[u8] = b"L64R1"; @@ -17,6 +17,7 @@ pub enum RnaError { InvalidNumber { line: u32 }, UnknownInstruction { line: u32 }, SlotOrder { line: u32, slot: u64 }, + ContextOrder { line: u32, context: u32 }, UnknownSlot { line: u32, slot: u64 }, UnrepresentableGraph, Graph(Obstruction), @@ -35,353 +36,7 @@ impl From for RnaError { } } -pub fn compile_rna(source: &[u8]) -> Result { - if source.len() > MAX_NATIVE_RNA_BYTES { - return Err(RnaError::SourceTooLarge); - } - - let mut lines = source.split(|byte| *byte == b'\n').enumerate(); - let (header_line, header) = next_nonempty(&mut lines).ok_or(RnaError::Empty)?; - let header_tokens = tokens(header); - if header_tokens.len() != 2 || header_tokens[0] != RNA_MAGIC { - return Err(RnaError::BadHeader); - } - let domain = parse_u64(header_tokens[1]).ok_or(RnaError::InvalidNumber { - line: line_number(header_line), - })?; - - let root = Route::root(LocusWord(domain)); - let mut graph = Graph::new(); - let mut slots = BTreeMap::new(); - let mut last_slot = None; - let mut instruction_count = 0usize; - - for (line_index, raw_line) in lines { - let line = trim_ascii(raw_line); - if line.is_empty() { - continue; - } - let parts = tokens(line); - let line = line_number(line_index); - let instruction = parts - .first() - .copied() - .ok_or(RnaError::InvalidArity { line })?; - let slot = parse_part(&parts, 1, line)?; - if let Some(previous) = last_slot - && slot <= previous - { - return Err(RnaError::SlotOrder { line, slot }); - } - let route = root.composed(LocusWord(slot)); - - let node = match instruction { - b"a" if parts.len() == 3 => { - graph.declare_atom_type(route, LocusWord(parse_part(&parts, 2, line)?))? - } - b"m" if parts.len() == 5 => { - let element = resolve_part(&slots, &parts, 2, line)?; - let rows = parse_u32_part(&parts, 3, line)?; - let cols = parse_u32_part(&parts, 4, line)?; - graph.declare_matrix_type(route, element, rows, cols)? - } - b"f" if parts.len() == 4 => { - let domain = resolve_part(&slots, &parts, 2, line)?; - let codomain = resolve_part(&slots, &parts, 3, line)?; - graph.declare_function_type(route, domain, codomain)? - } - b"v" if parts.len() == 3 => { - let ty = resolve_part(&slots, &parts, 2, line)?; - graph.insert_value(route, ROOT_CONTEXT, ty)? - } - b"c" if parts.len() == 5 => { - let first = resolve_part(&slots, &parts, 2, line)?; - let second = resolve_part(&slots, &parts, 3, line)?; - let output = resolve_part(&slots, &parts, 4, line)?; - graph - .transact(Proposal::compose( - route, - ROOT_CONTEXT, - first, - second, - output, - ))? - .node - } - b"x" if parts.len() == 5 => { - let left = resolve_part(&slots, &parts, 2, line)?; - let right = resolve_part(&slots, &parts, 3, line)?; - let output = resolve_part(&slots, &parts, 4, line)?; - graph - .transact(Proposal::matrix_multiply( - route, - ROOT_CONTEXT, - left, - right, - output, - ))? - .node - } - b"a" | b"m" | b"f" | b"v" | b"c" | b"x" => { - return Err(RnaError::InvalidArity { line }); - } - _ => return Err(RnaError::UnknownInstruction { line }), - }; - - slots.insert(slot, node); - last_slot = Some(slot); - instruction_count += 1; - } - - if instruction_count == 0 { - return Err(RnaError::Empty); - } - Ok(graph) -} - -pub fn normalize_rna(source: &[u8]) -> Result, RnaError> { - rna_bytes(&compile_rna(source)?) -} - -pub fn rna_to_dna(source: &[u8]) -> Result, RnaError> { - Ok(dna_bytes(&compile_rna(source)?)?) -} - -pub fn dna_to_rna(source: &[u8]) -> Result, RnaError> { - rna_bytes(&decode_dna(source)?) -} - -pub fn rna_bytes(graph: &Graph) -> Result, RnaError> { - if graph.context_count() != 1 { - return Err(RnaError::UnrepresentableGraph); - } - - let mut domain = None; - let mut authored = BTreeMap::new(); - for (route, node) in graph.routes_raw() { - if route.tail().len() != 1 { - continue; - } - match domain { - Some(existing) if existing != route.domain() => { - return Err(RnaError::UnrepresentableGraph); - } - None => domain = Some(route.domain()), - _ => {} - } - if authored.insert(route.tail()[0].0, *node).is_some() { - return Err(RnaError::UnrepresentableGraph); - } - } - - let domain = domain.ok_or(RnaError::Empty)?; - let expected_authored = graph - .nodes_raw() - .iter() - .filter(|node| !matches!(node.opcode(), OpCode::TypeJudgment | OpCode::KernelWitness)) - .count(); - if authored.len() != expected_authored { - return Err(RnaError::UnrepresentableGraph); - } - - let mut slots = BTreeMap::new(); - for (slot, node) in &authored { - if slots.insert(*node, *slot).is_some() { - return Err(RnaError::UnrepresentableGraph); - } - } - - let mut out = Vec::with_capacity(32 + authored.len() * 24); - out.extend_from_slice(RNA_MAGIC); - out.push(b' '); - push_hex(&mut out, domain.0); - out.push(b'\n'); - - for (slot, node_id) in authored { - let node = graph.node(node_id).ok_or(RnaError::UnrepresentableGraph)?; - if node.context() != ROOT_CONTEXT { - return Err(RnaError::UnrepresentableGraph); - } - let ports = graph.ports(node_id).ok_or(RnaError::UnrepresentableGraph)?; - match node.opcode() { - OpCode::TypeAtom if ports.is_empty() && node.ty().is_none() => { - push_prefix(&mut out, b'a', slot); - push_space_hex(&mut out, node.payload()); - } - OpCode::TypeMatrix if ports.len() == 1 && node.ty().is_none() => { - push_prefix(&mut out, b'm', slot); - push_space_decimal(&mut out, slot_for(&slots, ports[0].target())?); - push_space_decimal(&mut out, node.payload() >> 32); - push_space_decimal(&mut out, node.payload() as u32 as u64); - } - OpCode::TypeFunction if ports.len() == 2 && node.ty().is_none() => { - push_prefix(&mut out, b'f', slot); - push_space_decimal(&mut out, slot_for(&slots, ports[0].target())?); - push_space_decimal(&mut out, slot_for(&slots, ports[1].target())?); - } - OpCode::Value if ports.is_empty() => { - push_prefix(&mut out, b'v', slot); - push_space_decimal( - &mut out, - slot_for(&slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, - ); - } - OpCode::Compose | OpCode::MatMul if ports.len() == 2 => { - push_prefix( - &mut out, - if node.opcode() == OpCode::Compose { - b'c' - } else { - b'x' - }, - slot, - ); - push_space_decimal(&mut out, slot_for(&slots, ports[0].target())?); - push_space_decimal(&mut out, slot_for(&slots, ports[1].target())?); - push_space_decimal( - &mut out, - slot_for(&slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, - ); - } - _ => return Err(RnaError::UnrepresentableGraph), - } - out.push(b'\n'); - } - - Ok(out) -} - -fn next_nonempty<'a>( - lines: &mut impl Iterator, -) -> Option<(usize, &'a [u8])> { - lines.find_map(|(index, line)| { - let line = trim_ascii(line); - (!line.is_empty()).then_some((index, line)) - }) -} - -fn trim_ascii(mut bytes: &[u8]) -> &[u8] { - while bytes.first().is_some_and(u8::is_ascii_whitespace) { - bytes = &bytes[1..]; - } - while bytes.last().is_some_and(u8::is_ascii_whitespace) { - bytes = &bytes[..bytes.len() - 1]; - } - bytes -} - -fn tokens(line: &[u8]) -> Vec<&[u8]> { - line.split(|byte| byte.is_ascii_whitespace()) - .filter(|part| !part.is_empty()) - .collect() -} - -fn parse_part(parts: &[&[u8]], index: usize, line: u32) -> Result { - parts - .get(index) - .and_then(|part| parse_u64(part)) - .ok_or(RnaError::InvalidNumber { line }) -} - -fn parse_u32_part(parts: &[&[u8]], index: usize, line: u32) -> Result { - parse_part(parts, index, line)? - .try_into() - .map_err(|_| RnaError::InvalidNumber { line }) -} - -fn resolve_part( - slots: &BTreeMap, - parts: &[&[u8]], - index: usize, - line: u32, -) -> Result { - let slot = parse_part(parts, index, line)?; - slots - .get(&slot) - .copied() - .ok_or(RnaError::UnknownSlot { line, slot }) -} - -fn parse_u64(bytes: &[u8]) -> Option { - let (radix, digits) = if bytes.starts_with(b"0x") || bytes.starts_with(b"0X") { - (16u64, &bytes[2..]) - } else { - (10u64, bytes) - }; - if digits.is_empty() { - return None; - } - digits.iter().try_fold(0u64, |value, byte| { - let digit = match *byte { - b'0'..=b'9' => u64::from(*byte - b'0'), - b'a'..=b'f' if radix == 16 => u64::from(*byte - b'a' + 10), - b'A'..=b'F' if radix == 16 => u64::from(*byte - b'A' + 10), - _ => return None, - }; - if digit >= radix { - return None; - } - value.checked_mul(radix)?.checked_add(digit) - }) -} - -fn slot_for(slots: &BTreeMap, node: NodeId) -> Result { - slots - .get(&node) - .copied() - .ok_or(RnaError::UnrepresentableGraph) -} - -fn push_prefix(out: &mut Vec, opcode: u8, slot: u64) { - out.push(opcode); - push_space_decimal(out, slot); -} - -fn push_space_decimal(out: &mut Vec, value: u64) { - out.push(b' '); - push_decimal(out, value); -} - -fn push_space_hex(out: &mut Vec, value: u64) { - out.push(b' '); - push_hex(out, value); -} - -fn push_decimal(out: &mut Vec, mut value: u64) { - let mut buffer = [0u8; 20]; - let mut cursor = buffer.len(); - loop { - cursor -= 1; - buffer[cursor] = b'0' + (value % 10) as u8; - value /= 10; - if value == 0 { - break; - } - } - out.extend_from_slice(&buffer[cursor..]); -} - -fn push_hex(out: &mut Vec, mut value: u64) { - out.extend_from_slice(b"0x"); - if value == 0 { - out.push(b'0'); - return; - } - let mut buffer = [0u8; 16]; - let mut cursor = buffer.len(); - while value != 0 { - cursor -= 1; - let digit = (value & 0xf) as u8; - buffer[cursor] = if digit < 10 { - b'0' + digit - } else { - b'a' + digit - 10 - }; - value >>= 4; - } - out.extend_from_slice(&buffer[cursor..]); -} - -fn line_number(index: usize) -> u32 { - u32::try_from(index + 1).unwrap_or(u32::MAX) -} +include!("rna/compile.rs"); +include!("rna/sequence.rs"); +include!("rna/parse.rs"); +include!("rna/render.rs"); diff --git a/l64-native/src/rna/compile.rs b/l64-native/src/rna/compile.rs new file mode 100644 index 0000000..5e13694 --- /dev/null +++ b/l64-native/src/rna/compile.rs @@ -0,0 +1,134 @@ +pub fn compile_rna(source: &[u8]) -> Result { + if source.len() > MAX_NATIVE_RNA_BYTES { + return Err(RnaError::SourceTooLarge); + } + + let mut lines = source.split(|byte| *byte == b'\n').enumerate(); + let (header_line, header) = next_nonempty(&mut lines).ok_or(RnaError::Empty)?; + let header_tokens = tokens(header); + if header_tokens.len() != 2 || header_tokens[0] != RNA_MAGIC { + return Err(RnaError::BadHeader); + } + let domain = parse_u64(header_tokens[1]).ok_or(RnaError::InvalidNumber { + line: line_number(header_line), + })?; + + let root = Route::root(LocusWord(domain)); + let mut graph = Graph::new(); + let mut slots = BTreeMap::new(); + let mut last_slot = None; + let mut instruction_count = 0usize; + + for (line_index, raw_line) in lines { + let source_line = trim_ascii(raw_line); + if source_line.is_empty() { + continue; + } + let parts = tokens(source_line); + let line = line_number(line_index); + let instruction = parts + .first() + .copied() + .ok_or(RnaError::InvalidArity { line })?; + + if instruction == b"h" { + if parts.len() != 4 { + return Err(RnaError::InvalidArity { line }); + } + let context = parse_u32_part(&parts, 1, line)?; + if context as usize != graph.context_count() { + return Err(RnaError::ContextOrder { line, context }); + } + let parent = parse_u32_part(&parts, 2, line)?; + let binding = resolve_part(&slots, &parts, 3, line)?; + let created = graph.extend_context(parent, binding)?; + if created != context { + return Err(RnaError::ContextOrder { line, context }); + } + continue; + } + + let slot = parse_part(&parts, 1, line)?; + if let Some(previous) = last_slot + && slot <= previous + { + return Err(RnaError::SlotOrder { line, slot }); + } + let route = root.composed(LocusWord(slot)); + + let node = match instruction { + b"a" if parts.len() == 3 => { + graph.declare_atom_type(route, LocusWord(parse_part(&parts, 2, line)?))? + } + b"m" if parts.len() == 5 => { + let element = resolve_part(&slots, &parts, 2, line)?; + let rows = parse_u32_part(&parts, 3, line)?; + let cols = parse_u32_part(&parts, 4, line)?; + graph.declare_matrix_type(route, element, rows, cols)? + } + b"f" if parts.len() == 4 => { + let domain = resolve_part(&slots, &parts, 2, line)?; + let codomain = resolve_part(&slots, &parts, 3, line)?; + graph.declare_function_type(route, domain, codomain)? + } + b"q" if parts.len() == 10 => { + let carrier = resolve_part(&slots, &parts, 2, line)?; + let mut exponents = [0_i8; 7]; + for (axis, exponent) in exponents.iter_mut().enumerate() { + *exponent = parse_i8_part(&parts, axis + 3, line)?; + } + graph.declare_quantity_type(route, carrier, Dimension::new(exponents))? + } + b"v" if matches!(parts.len(), 3 | 4) => { + let ty = resolve_part(&slots, &parts, 2, line)?; + let context = parse_optional_context(&parts, 3, line)?; + graph.insert_value(route, context, ty)? + } + b"k" if matches!(parts.len(), 5 | 6) => { + let subject = resolve_part(&slots, &parts, 2, line)?; + let kind_raw = parse_u8_part(&parts, 3, line)?; + let kind = + ConstraintKind::from_raw(kind_raw).ok_or(RnaError::InvalidNumber { line })?; + let holds = parse_bool_part(&parts, 4, line)?; + let context = parse_optional_context(&parts, 5, line)?; + graph.declare_constraint(route, context, subject, kind, holds)? + } + b"c" | b"x" | b"+" | b"*" | b"/" if matches!(parts.len(), 5 | 6) => { + let left = resolve_part(&slots, &parts, 2, line)?; + let right = resolve_part(&slots, &parts, 3, line)?; + let output = resolve_part(&slots, &parts, 4, line)?; + let context = parse_optional_context(&parts, 5, line)?; + let proposal = match instruction { + b"c" => Proposal::compose(route, context, left, right, output), + b"x" => Proposal::matrix_multiply(route, context, left, right, output), + b"+" => Proposal::add(route, context, left, right, output), + b"*" => Proposal::multiply(route, context, left, right, output), + b"/" => Proposal::divide(route, context, left, right, output), + _ => unreachable!(), + }; + graph.transact(proposal)?.node + } + b"r" if matches!(parts.len(), 4 | 5) => { + let input = resolve_part(&slots, &parts, 2, line)?; + let output = resolve_part(&slots, &parts, 3, line)?; + let context = parse_optional_context(&parts, 4, line)?; + graph + .transact(Proposal::square_root(route, context, input, output))? + .node + } + b"a" | b"m" | b"f" | b"q" | b"v" | b"k" | b"c" | b"x" | b"+" | b"*" | b"/" | b"r" => { + return Err(RnaError::InvalidArity { line }); + } + _ => return Err(RnaError::UnknownInstruction { line }), + }; + + slots.insert(slot, node); + last_slot = Some(slot); + instruction_count += 1; + } + + if instruction_count == 0 { + return Err(RnaError::Empty); + } + Ok(graph) +} diff --git a/l64-native/src/rna/parse.rs b/l64-native/src/rna/parse.rs new file mode 100644 index 0000000..61832ae --- /dev/null +++ b/l64-native/src/rna/parse.rs @@ -0,0 +1,131 @@ +fn require_root(context: ContextId) -> Result<(), RnaError> { + (context == ROOT_CONTEXT) + .then_some(()) + .ok_or(RnaError::UnrepresentableGraph) +} + +fn push_context(out: &mut Vec, context: ContextId) { + if context != ROOT_CONTEXT { + push_space_decimal(out, u64::from(context)); + } +} + +fn next_nonempty<'a>( + lines: &mut impl Iterator, +) -> Option<(usize, &'a [u8])> { + lines.find_map(|(index, line)| { + let line = trim_ascii(line); + (!line.is_empty()).then_some((index, line)) + }) +} + +fn trim_ascii(mut bytes: &[u8]) -> &[u8] { + while bytes.first().is_some_and(u8::is_ascii_whitespace) { + bytes = &bytes[1..]; + } + while bytes.last().is_some_and(u8::is_ascii_whitespace) { + bytes = &bytes[..bytes.len() - 1]; + } + bytes +} + +fn tokens(line: &[u8]) -> Vec<&[u8]> { + line.split(|byte| byte.is_ascii_whitespace()) + .filter(|part| !part.is_empty()) + .collect() +} + +fn parse_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + parts + .get(index) + .and_then(|part| parse_u64(part)) + .ok_or(RnaError::InvalidNumber { line }) +} + +fn parse_u8_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + parse_part(parts, index, line)? + .try_into() + .map_err(|_| RnaError::InvalidNumber { line }) +} + +fn parse_u32_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + parse_part(parts, index, line)? + .try_into() + .map_err(|_| RnaError::InvalidNumber { line }) +} + +fn parse_i8_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + parts + .get(index) + .and_then(|part| parse_i8(part)) + .ok_or(RnaError::InvalidNumber { line }) +} + +fn parse_bool_part(parts: &[&[u8]], index: usize, line: u32) -> Result { + match parse_u8_part(parts, index, line)? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(RnaError::InvalidNumber { line }), + } +} + +fn parse_optional_context(parts: &[&[u8]], index: usize, line: u32) -> Result { + if parts.len() == index { + Ok(ROOT_CONTEXT) + } else { + parse_u32_part(parts, index, line) + } +} + +fn resolve_part( + slots: &BTreeMap, + parts: &[&[u8]], + index: usize, + line: u32, +) -> Result { + let slot = parse_part(parts, index, line)?; + slots + .get(&slot) + .copied() + .ok_or(RnaError::UnknownSlot { line, slot }) +} + +fn parse_i8(bytes: &[u8]) -> Option { + let (negative, digits) = if let Some(digits) = bytes.strip_prefix(b"-") { + (true, digits) + } else if let Some(digits) = bytes.strip_prefix(b"+") { + (false, digits) + } else { + (false, bytes) + }; + let magnitude = parse_u64(digits)?; + if negative { + let magnitude: i16 = magnitude.try_into().ok()?; + i8::try_from(-magnitude).ok() + } else { + magnitude.try_into().ok() + } +} + +fn parse_u64(bytes: &[u8]) -> Option { + let (radix, digits) = if bytes.starts_with(b"0x") || bytes.starts_with(b"0X") { + (16u64, &bytes[2..]) + } else { + (10u64, bytes) + }; + if digits.is_empty() { + return None; + } + digits.iter().try_fold(0u64, |value, byte| { + let digit = match *byte { + b'0'..=b'9' => u64::from(*byte - b'0'), + b'a'..=b'f' if radix == 16 => u64::from(*byte - b'a' + 10), + b'A'..=b'F' if radix == 16 => u64::from(*byte - b'A' + 10), + _ => return None, + }; + if digit >= radix { + return None; + } + value.checked_mul(radix)?.checked_add(digit) + }) +} diff --git a/l64-native/src/rna/render.rs b/l64-native/src/rna/render.rs new file mode 100644 index 0000000..4456df7 --- /dev/null +++ b/l64-native/src/rna/render.rs @@ -0,0 +1,70 @@ +fn slot_for(slots: &BTreeMap, node: NodeId) -> Result { + slots + .get(&node) + .copied() + .ok_or(RnaError::UnrepresentableGraph) +} + +fn push_prefix(out: &mut Vec, opcode: u8, slot: u64) { + out.push(opcode); + push_space_decimal(out, slot); +} + +fn push_space_decimal(out: &mut Vec, value: u64) { + out.push(b' '); + push_decimal(out, value); +} + +fn push_space_signed(out: &mut Vec, value: i8) { + out.push(b' '); + if value < 0 { + out.push(b'-'); + push_decimal(out, u64::from(value.unsigned_abs())); + } else { + push_decimal(out, value as u64); + } +} + +fn push_space_hex(out: &mut Vec, value: u64) { + out.push(b' '); + push_hex(out, value); +} + +fn push_decimal(out: &mut Vec, mut value: u64) { + let mut buffer = [0u8; 20]; + let mut cursor = buffer.len(); + loop { + cursor -= 1; + buffer[cursor] = b'0' + (value % 10) as u8; + value /= 10; + if value == 0 { + break; + } + } + out.extend_from_slice(&buffer[cursor..]); +} + +fn push_hex(out: &mut Vec, mut value: u64) { + out.extend_from_slice(b"0x"); + if value == 0 { + out.push(b'0'); + return; + } + let mut buffer = [0u8; 16]; + let mut cursor = buffer.len(); + while value != 0 { + cursor -= 1; + let digit = (value & 0xf) as u8; + buffer[cursor] = if digit < 10 { + b'0' + digit + } else { + b'a' + digit - 10 + }; + value >>= 4; + } + out.extend_from_slice(&buffer[cursor..]); +} + +fn line_number(index: usize) -> u32 { + u32::try_from(index + 1).unwrap_or(u32::MAX) +} diff --git a/l64-native/src/rna/sequence.rs b/l64-native/src/rna/sequence.rs new file mode 100644 index 0000000..6c2e663 --- /dev/null +++ b/l64-native/src/rna/sequence.rs @@ -0,0 +1,187 @@ +pub fn normalize_rna(source: &[u8]) -> Result, RnaError> { + rna_bytes(&compile_rna(source)?) +} + +pub fn rna_to_dna(source: &[u8]) -> Result, RnaError> { + Ok(dna_bytes(&compile_rna(source)?)?) +} + +pub fn dna_to_rna(source: &[u8]) -> Result, RnaError> { + rna_bytes(&decode_dna(source)?) +} + +pub fn rna_bytes(graph: &Graph) -> Result, RnaError> { + let mut domain = None; + let mut authored = BTreeMap::new(); + for (route, node) in graph.routes_raw() { + if route.tail().len() != 1 { + continue; + } + match domain { + Some(existing) if existing != route.domain() => { + return Err(RnaError::UnrepresentableGraph); + } + None => domain = Some(route.domain()), + _ => {} + } + if authored.insert(*node, route.tail()[0].0).is_some() { + return Err(RnaError::UnrepresentableGraph); + } + } + + let domain = domain.ok_or(RnaError::Empty)?; + let expected_authored = graph + .nodes_raw() + .iter() + .filter(|node| { + !matches!( + node.opcode(), + OpCode::TypeJudgment | OpCode::KernelWitness | OpCode::Obligation + ) + }) + .count(); + if authored.len() != expected_authored { + return Err(RnaError::UnrepresentableGraph); + } + + let mut slots = BTreeMap::new(); + let mut previous_slot = None; + for (node, slot) in &authored { + if previous_slot.is_some_and(|previous| *slot <= previous) { + return Err(RnaError::UnrepresentableGraph); + } + slots.insert(*node, *slot); + previous_slot = Some(*slot); + } + + let mut out = Vec::with_capacity(32 + authored.len() * 32 + graph.context_count() * 16); + out.extend_from_slice(RNA_MAGIC); + out.push(b' '); + push_hex(&mut out, domain.0); + out.push(b'\n'); + + let mut next_context = 1_u32; + for (node_id, slot) in authored { + emit_ready_contexts(graph, &slots, &mut out, &mut next_context, node_id)?; + emit_node(graph, &slots, &mut out, node_id, slot)?; + } + emit_ready_contexts(graph, &slots, &mut out, &mut next_context, u32::MAX)?; + if next_context as usize != graph.context_count() { + return Err(RnaError::UnrepresentableGraph); + } + + Ok(out) +} + +fn emit_ready_contexts( + graph: &Graph, + slots: &BTreeMap, + out: &mut Vec, + next_context: &mut ContextId, + before_node: NodeId, +) -> Result<(), RnaError> { + while (*next_context as usize) < graph.context_count() { + let context = &graph.contexts_raw()[*next_context as usize]; + let binding = context.binding().ok_or(RnaError::UnrepresentableGraph)?; + if binding >= before_node { + break; + } + out.push(b'h'); + push_space_decimal(out, u64::from(*next_context)); + push_space_decimal(out, u64::from(context.parent())); + push_space_decimal(out, slot_for(slots, binding)?); + out.push(b'\n'); + *next_context += 1; + } + Ok(()) +} + +fn emit_node( + graph: &Graph, + slots: &BTreeMap, + out: &mut Vec, + node_id: NodeId, + slot: u64, +) -> Result<(), RnaError> { + let node = graph.node(node_id).ok_or(RnaError::UnrepresentableGraph)?; + let ports = graph.ports(node_id).ok_or(RnaError::UnrepresentableGraph)?; + match node.opcode() { + OpCode::TypeAtom if ports.is_empty() && node.ty().is_none() => { + require_root(node.context())?; + push_prefix(out, b'a', slot); + push_space_hex(out, node.payload()); + } + OpCode::TypeMatrix if ports.len() == 1 && node.ty().is_none() => { + require_root(node.context())?; + push_prefix(out, b'm', slot); + push_space_decimal(out, slot_for(slots, ports[0].target())?); + push_space_decimal(out, node.payload() >> 32); + push_space_decimal(out, node.payload() as u32 as u64); + } + OpCode::TypeFunction if ports.len() == 2 && node.ty().is_none() => { + require_root(node.context())?; + push_prefix(out, b'f', slot); + push_space_decimal(out, slot_for(slots, ports[0].target())?); + push_space_decimal(out, slot_for(slots, ports[1].target())?); + } + OpCode::TypeQuantity if ports.len() == 1 && node.ty().is_none() => { + require_root(node.context())?; + push_prefix(out, b'q', slot); + push_space_decimal(out, slot_for(slots, ports[0].target())?); + let dimension = + Dimension::from_bits(node.payload()).ok_or(RnaError::UnrepresentableGraph)?; + for exponent in dimension.exponents() { + push_space_signed(out, exponent); + } + } + OpCode::Value if ports.is_empty() => { + push_prefix(out, b'v', slot); + push_space_decimal( + out, + slot_for(slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, + ); + push_context(out, node.context()); + } + OpCode::Constraint if ports.len() == 1 => { + push_prefix(out, b'k', slot); + push_space_decimal(out, slot_for(slots, ports[0].target())?); + let (kind, holds) = ConstraintKind::from_payload(node.payload()) + .ok_or(RnaError::UnrepresentableGraph)?; + push_space_decimal(out, kind as u64); + push_space_decimal(out, u64::from(holds)); + push_context(out, node.context()); + } + OpCode::Compose | OpCode::MatMul | OpCode::Add | OpCode::Multiply | OpCode::Divide + if ports.len() == 2 => + { + let opcode = match node.opcode() { + OpCode::Compose => b'c', + OpCode::MatMul => b'x', + OpCode::Add => b'+', + OpCode::Multiply => b'*', + OpCode::Divide => b'/', + _ => unreachable!(), + }; + push_prefix(out, opcode, slot); + push_space_decimal(out, slot_for(slots, ports[0].target())?); + push_space_decimal(out, slot_for(slots, ports[1].target())?); + push_space_decimal( + out, + slot_for(slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, + ); + push_context(out, node.context()); + } + OpCode::Sqrt if ports.len() == 1 => { + push_prefix(out, b'r', slot); + push_space_decimal(out, slot_for(slots, ports[0].target())?); + push_space_decimal( + out, + slot_for(slots, node.ty().ok_or(RnaError::UnrepresentableGraph)?)?, + ); + push_context(out, node.context()); + } + _ => return Err(RnaError::UnrepresentableGraph), + } + out.push(b'\n'); + Ok(()) +} diff --git a/l64-native/tests/architecture.rs b/l64-native/tests/architecture.rs index a2056ba..6b8cc5d 100644 --- a/l64-native/tests/architecture.rs +++ b/l64-native/tests/architecture.rs @@ -1,7 +1,7 @@ use core::mem::size_of; use std::{fs, path::Path}; -use l64_native::{Node, Port}; +use l64_native::{Dimension, Node, Port}; #[test] fn compact_layout_budgets_hold() { @@ -15,6 +15,11 @@ fn compact_layout_budgets_hold() { "Port grew to {} bytes", size_of::() ); + assert!( + size_of::() <= 8, + "Dimension grew to {} bytes", + size_of::() + ); } #[test] diff --git a/l64-native/tests/constraints.rs b/l64-native/tests/constraints.rs new file mode 100644 index 0000000..65a0d4a --- /dev/null +++ b/l64-native/tests/constraints.rs @@ -0,0 +1,328 @@ +use l64_native::{ + ConstraintKind, DecodeError, Dimension, Graph, LocusWord, Obstruction, OpCode, Proposal, + ROOT_CONTEXT, Route, canonical_bytes, decode_canonical, +}; + +fn route(index: u64) -> Route { + Route::root(LocusWord(0x434f4e5354524149)).composed(LocusWord(index)) +} + +fn quantity_fixture() -> (Graph, u32, u32, u32, u32, u32) { + let mut graph = Graph::new(); + let real = graph.declare_atom_type(route(1), LocusWord(0x52)).unwrap(); + let length = graph + .declare_quantity_type(route(2), real, Dimension::LENGTH) + .unwrap(); + let time = graph + .declare_quantity_type(route(3), real, Dimension::TIME) + .unwrap(); + let speed = graph + .declare_quantity_type( + route(4), + real, + Dimension::LENGTH.divide(Dimension::TIME).unwrap(), + ) + .unwrap(); + let area = graph + .declare_quantity_type( + route(5), + real, + Dimension::LENGTH.multiply(Dimension::LENGTH).unwrap(), + ) + .unwrap(); + (graph, real, length, time, speed, area) +} + +#[test] +fn dimension_vector_composes_without_schema_objects() { + let velocity = Dimension::LENGTH.divide(Dimension::TIME).unwrap(); + let acceleration = velocity.divide(Dimension::TIME).unwrap(); + assert_eq!(velocity.exponents(), [1, 0, -1, 0, 0, 0, 0]); + assert_eq!(acceleration.exponents(), [1, 0, -2, 0, 0, 0, 0]); + assert_eq!(Dimension::LENGTH.square_root(), None); + assert_eq!( + Dimension::LENGTH + .multiply(Dimension::LENGTH) + .unwrap() + .square_root(), + Some(Dimension::LENGTH) + ); +} + +#[test] +fn incompatible_addition_rejects_without_mutation() { + let (mut graph, _, length, time, _, _) = quantity_fixture(); + let left = graph.insert_value(route(10), ROOT_CONTEXT, length).unwrap(); + let right = graph.insert_value(route(11), ROOT_CONTEXT, time).unwrap(); + let before = canonical_bytes(&graph); + + let result = graph.transact(Proposal::add(route(12), ROOT_CONTEXT, left, right, length)); + + assert_eq!( + result, + Err(Obstruction::DimensionMismatch { + left: Dimension::LENGTH, + right: Dimension::TIME, + }) + ); + assert_eq!(canonical_bytes(&graph), before); + assert!(graph.resolve(&route(12)).is_none()); +} + +#[test] +fn multiplication_and_division_validate_derived_dimensions() { + let (mut graph, _, length, time, speed, area) = quantity_fixture(); + let left = graph.insert_value(route(20), ROOT_CONTEXT, length).unwrap(); + let right = graph.insert_value(route(21), ROOT_CONTEXT, length).unwrap(); + let duration = graph.insert_value(route(22), ROOT_CONTEXT, time).unwrap(); + + let product = graph + .transact(Proposal::multiply( + route(23), + ROOT_CONTEXT, + left, + right, + area, + )) + .unwrap(); + let quotient = graph + .transact(Proposal::divide( + route(24), + ROOT_CONTEXT, + left, + duration, + speed, + )) + .unwrap(); + + assert_eq!( + graph.node(product.evidence).unwrap().opcode(), + OpCode::KernelWitness + ); + assert_eq!( + graph.node(quotient.evidence).unwrap().opcode(), + OpCode::KernelWitness + ); + let bytes = canonical_bytes(&graph); + let decoded = decode_canonical(&bytes).unwrap(); + assert_eq!(canonical_bytes(&decoded), bytes); +} + +#[test] +fn decoder_rechecks_operation_law_instead_of_trusting_witness_shape() { + let (mut graph, _, length, time, speed, area) = quantity_fixture(); + let left = graph.insert_value(route(30), ROOT_CONTEXT, length).unwrap(); + let duration = graph.insert_value(route(31), ROOT_CONTEXT, time).unwrap(); + let committed = graph + .transact(Proposal::divide( + route(32), + ROOT_CONTEXT, + left, + duration, + speed, + )) + .unwrap(); + let mut bytes = canonical_bytes(&graph); + let node_record = 22 + committed.node as usize * 24; + bytes[node_record + 6..node_record + 10].copy_from_slice(&area.to_le_bytes()); + let judgment = committed.node + 1; + let conclusion_port = graph.node(judgment).unwrap().port_range().end - 1; + let port_section = 22 + graph.node_count() * 24; + let conclusion_record = port_section + conclusion_port * 8; + bytes[conclusion_record..conclusion_record + 4].copy_from_slice(&area.to_le_bytes()); + + assert_eq!( + decode_canonical(&bytes).unwrap_err(), + DecodeError::InvalidAuthority + ); +} + +#[test] +fn contradictory_context_is_rejected_before_context_mutation() { + let (mut graph, _, length, _, _, _) = quantity_fixture(); + let value = graph.insert_value(route(40), ROOT_CONTEXT, length).unwrap(); + let nonnegative = graph + .declare_constraint( + route(41), + ROOT_CONTEXT, + value, + ConstraintKind::NonNegative, + true, + ) + .unwrap(); + let positive_context = graph.extend_context(ROOT_CONTEXT, nonnegative).unwrap(); + let negative = graph + .declare_constraint( + route(42), + ROOT_CONTEXT, + value, + ConstraintKind::NonNegative, + false, + ) + .unwrap(); + let before = canonical_bytes(&graph); + let before_journal = graph.journal_len(); + + assert_eq!( + graph.extend_context(positive_context, negative), + Err(Obstruction::ContradictoryConstraint { + subject: value, + kind: ConstraintKind::NonNegative, + }) + ); + assert_eq!(canonical_bytes(&graph), before); + assert_eq!(graph.journal_len(), before_journal); +} + +#[test] +fn decoder_rejects_a_serialized_contradictory_context() { + let (mut graph, _, length, _, _, _) = quantity_fixture(); + let value = graph.insert_value(route(50), ROOT_CONTEXT, length).unwrap(); + let first = graph + .declare_constraint( + route(51), + ROOT_CONTEXT, + value, + ConstraintKind::NonNegative, + true, + ) + .unwrap(); + let first_context = graph.extend_context(ROOT_CONTEXT, first).unwrap(); + let second = graph + .declare_constraint( + route(52), + first_context, + value, + ConstraintKind::NonNegative, + true, + ) + .unwrap(); + graph.extend_context(first_context, second).unwrap(); + + let valid = canonical_bytes(&graph); + let decoded = decode_canonical(&valid).unwrap(); + assert_eq!(canonical_bytes(&decoded), valid); + + let mut contradictory = valid; + let second_record = 22 + second as usize * 24; + contradictory[second_record + 10..second_record + 18].copy_from_slice(&1_u64.to_le_bytes()); + assert_eq!( + decode_canonical(&contradictory).unwrap_err(), + DecodeError::InvalidAuthority + ); +} + +fn square_root_fixture() -> (Graph, u32, u32, u32) { + let mut graph = Graph::new(); + let real = graph.declare_atom_type(route(60), LocusWord(0x52)).unwrap(); + let length = graph + .declare_quantity_type(route(61), real, Dimension::LENGTH) + .unwrap(); + let area = graph + .declare_quantity_type( + route(62), + real, + Dimension::LENGTH.multiply(Dimension::LENGTH).unwrap(), + ) + .unwrap(); + let value = graph.insert_value(route(63), ROOT_CONTEXT, area).unwrap(); + (graph, value, length, area) +} + +#[test] +fn unresolved_square_root_guard_is_preserved_as_obligation() { + let (mut graph, value, length, _) = square_root_fixture(); + let committed = graph + .transact(Proposal::square_root( + route(64), + ROOT_CONTEXT, + value, + length, + )) + .unwrap(); + + let evidence = graph.node(committed.evidence).unwrap(); + assert_eq!(evidence.opcode(), OpCode::Obligation); + assert_eq!(graph.ports(committed.evidence).unwrap()[0].target(), value); + let bytes = canonical_bytes(&graph); + let decoded = decode_canonical(&bytes).unwrap(); + assert_eq!(canonical_bytes(&decoded), bytes); +} + +#[test] +fn discharged_square_root_guard_produces_kernel_witness() { + let (mut graph, value, length, _) = square_root_fixture(); + let condition = graph + .declare_constraint( + route(65), + ROOT_CONTEXT, + value, + ConstraintKind::NonNegative, + true, + ) + .unwrap(); + let context = graph.extend_context(ROOT_CONTEXT, condition).unwrap(); + let committed = graph + .transact(Proposal::square_root(route(66), context, value, length)) + .unwrap(); + + assert_eq!( + graph.node(committed.evidence).unwrap().opcode(), + OpCode::KernelWitness + ); +} + +#[test] +fn refuted_square_root_guard_rejects_without_mutation() { + let (mut graph, value, length, _) = square_root_fixture(); + let condition = graph + .declare_constraint( + route(67), + ROOT_CONTEXT, + value, + ConstraintKind::NonNegative, + false, + ) + .unwrap(); + let context = graph.extend_context(ROOT_CONTEXT, condition).unwrap(); + let before = canonical_bytes(&graph); + let before_journal = graph.journal_len(); + + assert_eq!( + graph.transact(Proposal::square_root(route(68), context, value, length)), + Err(Obstruction::GuardViolated { + subject: value, + kind: ConstraintKind::NonNegative, + }) + ); + assert_eq!(canonical_bytes(&graph), before); + assert_eq!(graph.journal_len(), before_journal); +} + +#[test] +fn decoder_refuses_a_forged_witness_for_an_unresolved_guard() { + let (mut graph, value, length, _) = square_root_fixture(); + let committed = graph + .transact(Proposal::square_root( + route(69), + ROOT_CONTEXT, + value, + length, + )) + .unwrap(); + let mut bytes = canonical_bytes(&graph); + let node_count = graph.node_count(); + let port_count = graph.port_count(); + let evidence_record = 22 + committed.evidence as usize * 24; + bytes[evidence_record..evidence_record + 2] + .copy_from_slice(&(OpCode::KernelWitness as u16).to_le_bytes()); + bytes[evidence_record + 22..evidence_record + 24].copy_from_slice(&0_u16.to_le_bytes()); + bytes[10..14].copy_from_slice(&((port_count - 1) as u32).to_le_bytes()); + let last_port = 22 + node_count * 24 + (port_count - 1) * 8; + bytes.drain(last_port..last_port + 8); + + assert_eq!( + decode_canonical(&bytes).unwrap_err(), + DecodeError::InvalidAuthority + ); +} diff --git a/l64-native/tests/rna.rs b/l64-native/tests/rna.rs index e4a505c..e8504a5 100644 --- a/l64-native/tests/rna.rs +++ b/l64-native/tests/rna.rs @@ -1,4 +1,7 @@ -use l64_native::{Obstruction, RnaError, compile_rna, dna_to_rna, normalize_rna, rna_to_dna}; +use l64_native::{ + ConstraintKind, Dimension, Obstruction, RnaError, compile_rna, dna_to_rna, normalize_rna, + rna_to_dna, +}; const CANONICAL_COMPOSE: &[u8] = b"L64R1 0x4c36344e41544956\na 1 0x41\na 2 0x42\na 3 0x43\nf 4 1 2\nf 5 2 3\nf 6 1 3\nv 7 4\nv 8 5\nc 9 7 8 6\n"; @@ -54,3 +57,52 @@ fn native_rna_rejects_record_like_or_named_instructions() { RnaError::UnknownInstruction { line: 2 } ); } + +const CONSTRAINT_RNA: &[u8] = b"L64R1 0x434f4e5354524149\na 1 0x52\nq 2 1 1 0 0 0 0 0 0\nq 3 1 2 0 0 0 0 0 0\nv 4 3\nr 5 4 2\nk 6 4 1 1\nh 1 0 6\nr 7 4 2 1\n"; + +#[test] +fn constraints_contexts_and_obligations_reach_exact_rna_dna_fixed_point() { + let graph = compile_rna(CONSTRAINT_RNA).unwrap(); + assert_eq!(graph.context_count(), 2); + let dna = rna_to_dna(CONSTRAINT_RNA).unwrap(); + let sequenced = dna_to_rna(&dna).unwrap(); + assert_eq!(sequenced, CONSTRAINT_RNA); + assert_eq!(rna_to_dna(&sequenced).unwrap(), dna); + assert!(!sequenced.windows(2).any(|window| window == b"{:")); +} + +#[test] +fn rna_preserves_unknown_guard_and_refuses_refuted_guard() { + let refuted = b"L64R1 0x434f4e5354524149\na 1 0x52\nq 2 1 1 0 0 0 0 0 0\nq 3 1 2 0 0 0 0 0 0\nv 4 3\nk 5 4 1 0\nh 1 0 5\nr 6 4 2 1\n"; + assert_eq!( + rna_to_dna(refuted), + Err(RnaError::Graph(Obstruction::GuardViolated { + subject: 3, + kind: ConstraintKind::NonNegative, + })) + ); +} + +#[test] +fn rna_rejects_dimensionally_invalid_addition() { + let source = b"L64R1 0x434f4e5354524149\na 1 0x52\nq 2 1 1 0 0 0 0 0 0\nq 3 1 0 0 1 0 0 0 0\nv 4 2\nv 5 3\n+ 6 4 5 2\n"; + assert_eq!( + rna_to_dna(source), + Err(RnaError::Graph(Obstruction::DimensionMismatch { + left: Dimension::LENGTH, + right: Dimension::TIME, + })) + ); +} + +#[test] +fn rna_rejects_nonsequential_context_ids() { + let source = b"L64R1 0x1\na 1 0x52\nq 2 1 0 0 0 0 0 0 0\nv 3 2\nk 4 3 1 1\nh 2 0 4\n"; + assert_eq!( + compile_rna(source).unwrap_err(), + RnaError::ContextOrder { + line: 6, + context: 2, + } + ); +} From 7c24cd007e91fec403fb7b4c7030417d2a113119 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:59:16 -0700 Subject: [PATCH 55/59] Document native constraint core factorization --- l64-native/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/l64-native/README.md b/l64-native/README.md index 1514851..0c6034e 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -33,6 +33,8 @@ The sixth boundary adds the first native constraint core without creating a para - decoder-side re-execution of operation and evidence law, preventing structurally plausible forged authority; - canonical `L64R1 → L64D → L64R1 → L64D` fixed points for both discharged and unresolved guards. +The larger implementation files are factored only at existing item boundaries into construction, typing, transaction, validation, codec, and RNA concerns. This changes review locality without introducing another authority layer or altering canonical bytes. + State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. The existing `l64-cli` command names now route `L64R1` and `L64D` directly through this native path. Legacy RNA/DNA behavior is classified as compatibility/forensic ingress and is available explicitly through `l64-cli legacy ...`; ambient fallback remains temporarily available with a mandatory deprecation warning. From 0684ad94544bc84a6c3e9d68bad1dafe80fe9aa4 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:05:08 -0700 Subject: [PATCH 56/59] Bind constraint core to Commander change chain --- l64-native/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/l64-native/README.md b/l64-native/README.md index 0c6034e..cdbf54b 100644 --- a/l64-native/README.md +++ b/l64-native/README.md @@ -33,6 +33,8 @@ The sixth boundary adds the first native constraint core without creating a para - decoder-side re-execution of operation and evidence law, preventing structurally plausible forged authority; - canonical `L64R1 → L64D → L64R1 → L64D` fixed points for both discharged and unresolved guards. +`LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens` is the promotion boundary for this sixth stage: dimension algebra, context consistency, guarded obligation, native surface, then full closure. The parent execution rail cannot advance until that chain closes on repository evidence. + The larger implementation files are factored only at existing item boundaries into construction, typing, transaction, validation, codec, and RNA concerns. This changes review locality without introducing another authority layer or altering canonical bytes. State identity is the domain-separated BLAKE3 commitment of canonical native bytes. No native name, claim identifier, theorem identifier, campaign identifier, JSON field name, or generic serialization schema participates. From cbcbfe5d9c4f34baf0ca54306c77a85ac970bd26 Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:07:21 -0700 Subject: [PATCH 57/59] Run native gate on Commander branches --- .github/workflows/native-core.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/native-core.yml b/.github/workflows/native-core.yml index 2f2d21b..6624cf6 100644 --- a/.github/workflows/native-core.yml +++ b/.github/workflows/native-core.yml @@ -1,6 +1,16 @@ name: Native core gate on: + push: + branches: + - 'chatgpt/**' + paths: + - Cargo.toml + - Cargo.lock + - l64-native/** + - LOCUS64_*_RAIL.athens + - LOCUS64_*_CHANGE_CHAIN.athens + - .github/workflows/native-core.yml pull_request: branches: - main @@ -8,6 +18,8 @@ on: - Cargo.toml - Cargo.lock - l64-native/** + - LOCUS64_*_RAIL.athens + - LOCUS64_*_CHANGE_CHAIN.athens - .github/workflows/native-core.yml workflow_dispatch: From 822756ef1d6ff68b8deca500933ac10846e7608b Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:19:16 -0700 Subject: [PATCH 58/59] Advance execution rail to proof congruence --- LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens | 7 ++--- LOCUS64_EXECUTION_COHERENCE_RAIL.athens | 30 +++++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens b/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens index d8ba4c3..44d1a06 100644 --- a/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens +++ b/LOCUS64_CONSTRAINT_CORE_CHANGE_CHAIN.athens @@ -1,7 +1,6 @@ ATHENS_DEVELOPMENT_RAIL v1 -field=key=current_stage;value=constraint-core-closure field=key=projection_authority;value=non_authoritative -field=key=rail_version;value=5 +field=key=rail_version;value=6 field=key=schema_version;value=1 gate=id=constraint-core-closure-green gate=id=context-consistency-green @@ -26,7 +25,7 @@ stage_field=stage=native-surface;key=status;value=complete stage=id=constraint-core-closure stage_field=stage=constraint-core-closure;key=depends_on;value=native-surface stage_field=stage=constraint-core-closure;key=required_gates;value=constraint-core-closure-green -stage_field=stage=constraint-core-closure;key=status;value=current +stage_field=stage=constraint-core-closure;key=status;value=complete history=from_status=current;gates=dimension-algebra-green;mode=linear_advance;stage_id=dimension-algebra;to_status=complete history=evidence=local%3A22-tests%2Bclippy%3Bdimension-law%2Bdecoder-recheck;kind=dogfood_promotion_receipt;stage_id=dimension-algebra history=from_status=current;gates=context-consistency-green;mode=linear_advance;stage_id=context-consistency;to_status=complete @@ -35,4 +34,6 @@ history=from_status=current;gates=guarded-obligation-green;mode=linear_advance;s history=evidence=local%3A28-tests%2Bclippy%3Bsqrt-witness%7Cobligation%7Crejection%2Bforgery-refusal;kind=dogfood_promotion_receipt;stage_id=guarded-obligation history=from_status=current;gates=native-surface-green;mode=linear_advance;stage_id=native-surface;to_status=complete history=evidence=local%3A32-tests%2Bclippy%3Bconstraint-rna-dna-fixed-point%2Binvalid-refusal;kind=dogfood_promotion_receipt;stage_id=native-surface +history=from_status=current;gates=constraint-core-closure-green;mode=linear_advance;stage_id=constraint-core-closure;to_status=complete +history=evidence=github-cbcbfe5d9c4f34baf0ca54306c77a85ac970bd26-run-29990484222;kind=dogfood_promotion_receipt;stage_id=constraint-core-closure END diff --git a/LOCUS64_EXECUTION_COHERENCE_RAIL.athens b/LOCUS64_EXECUTION_COHERENCE_RAIL.athens index 2d54902..95053a1 100644 --- a/LOCUS64_EXECUTION_COHERENCE_RAIL.athens +++ b/LOCUS64_EXECUTION_COHERENCE_RAIL.athens @@ -1,31 +1,33 @@ ATHENS_DEVELOPMENT_RAIL v1 -field=key=schema_version;value=1 -field=key=rail_version;value=1 -field=key=current_stage;value=native-constraint-core -field=key=next_stage;value=proof-producing-congruence +field=key=current_stage;value=proof-producing-congruence +field=key=next_stage;value=incremental-closure field=key=projection_authority;value=non_authoritative +field=key=rail_version;value=2 +field=key=schema_version;value=1 +gate=id=incremental-closure-green +gate=id=legacy-authority-quarantine-green +gate=id=native-constraint-core-green +gate=id=native-upper-projection-green +gate=id=proof-producing-congruence-green stage=id=native-constraint-core -stage_field=stage=native-constraint-core;key=status;value=current stage_field=stage=native-constraint-core;key=required_gates;value=native-constraint-core-green +stage_field=stage=native-constraint-core;key=status;value=complete stage=id=proof-producing-congruence -stage_field=stage=proof-producing-congruence;key=status;value=next stage_field=stage=proof-producing-congruence;key=depends_on;value=native-constraint-core stage_field=stage=proof-producing-congruence;key=required_gates;value=proof-producing-congruence-green +stage_field=stage=proof-producing-congruence;key=status;value=current stage=id=incremental-closure -stage_field=stage=incremental-closure;key=status;value=planned stage_field=stage=incremental-closure;key=depends_on;value=proof-producing-congruence stage_field=stage=incremental-closure;key=required_gates;value=incremental-closure-green +stage_field=stage=incremental-closure;key=status;value=next stage=id=native-upper-projection -stage_field=stage=native-upper-projection;key=status;value=planned stage_field=stage=native-upper-projection;key=depends_on;value=incremental-closure stage_field=stage=native-upper-projection;key=required_gates;value=native-upper-projection-green +stage_field=stage=native-upper-projection;key=status;value=planned stage=id=legacy-authority-quarantine -stage_field=stage=legacy-authority-quarantine;key=status;value=planned stage_field=stage=legacy-authority-quarantine;key=depends_on;value=native-upper-projection stage_field=stage=legacy-authority-quarantine;key=required_gates;value=legacy-authority-quarantine-green -gate=id=native-constraint-core-green -gate=id=proof-producing-congruence-green -gate=id=incremental-closure-green -gate=id=native-upper-projection-green -gate=id=legacy-authority-quarantine-green +stage_field=stage=legacy-authority-quarantine;key=status;value=planned +history=from_status=current;gates=native-constraint-core-green;mode=linear_advance;stage_id=native-constraint-core;to_status=complete +history=evidence=github-cbcbfe5d9c4f34baf0ca54306c77a85ac970bd26-run-29990484222;kind=dogfood_promotion_receipt;stage_id=native-constraint-core END From 55d710815d9a0f17ad059eb801b6ce1971025b6e Mon Sep 17 00:00:00 2001 From: FreshSoftware4 <99464939+FreshSoftware4@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:20:46 -0700 Subject: [PATCH 59/59] Deduplicate native branch CI --- .github/workflows/native-core.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/native-core.yml b/.github/workflows/native-core.yml index 6624cf6..aa0c6cc 100644 --- a/.github/workflows/native-core.yml +++ b/.github/workflows/native-core.yml @@ -28,6 +28,7 @@ permissions: jobs: verify: + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - name: Checkout