From ac889ba2b37c4fc405c00e746644ee0d8c75f66b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 18:32:03 +0000 Subject: [PATCH 1/3] =?UTF-8?q?contract:=20the=20rung=20=C3=97=20tenant=20?= =?UTF-8?q?cross,=20and=20the=20SPOG=20census=20+=20mask=20surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.claude/temporal/03-alpha-channel-state.md` (#1198) records two absences, both measured rather than supposed: "The rung × tenant cross does NOT exist … `SpogTenants` exposes no mask surface of its own … No structure crosses them, in code or in any plan." and names the object that would: "a 10×N mask matrix over one allocation; each cell is one `AlphaMask` AND … it needs no new stored state (both masks are recomputed projections)". This builds exactly that, plus the two smaller things a consumer had to hand-roll for want of it. ## Why now — the consumer measurement A consumer walking a baked ontology needed "which graph did attention touch" and the substrate could not answer, so it wrote the DOMAIN into the alpha stamp's `rung` byte (`rung = domain ordinal + 1`) and filtered one flat scanpath back apart by it. Two axes in one field, and lossy: measured on that consumer's real artifacts, 16 distinct graphs project onto 6 domains, and 3 graphs — 266 579 rows, 35 % of the largest bake — resolve to no domain at all, land on the null rung, and drop out of every projection. The fix is not a rule against the shortcut; it is making the cross available. ## What lands `alpha_focus` (new) `AlphaFocus::cross(&AlphaTunnel, &SpogTenants)` — refuses two allocations (`FocusError::DifferentAllocations`) by base-slice identity, because a mask is a set of BASE ORDINALS and two allocations are two coordinate systems; `ptr::eq` answers "the same coordinate", equality of contents does not. `cell(rung, concept)` — the AND. `matrix()` — the sparse reading (most of a 10×N grid is empty by construction). `rung_reach(rung)`. And the interesting one, `unlooked(concept)` = tenant mask AND NOT any-rung mask: "this graph was addressable and no rung ever looked", the question no log can answer. `spog_tenants` `census(rows)` — the tenant list as a READING of the spine's own keys, never a table beside it that could disagree. This is the module's own "tenant bindings are DATA" line made structural: here the data is the bake. `over_census(alloc, cycle)` — the no-configuration constructor. `block_of(concept)` + `tenants_in_block(block)` — grouping as a shift on the key; several graphs routinely share a block. What a block MEANS stays with the consumer. `tenant_mask` / `attended_mask` / `allocation` — the mask surface the audit found missing. `tenant_mask` is `None` for an undeclared graph, never an empty mask: absent and unlooked-at must not read alike. Nothing is stored. Every cell is an AND of two masks recomputed from the shadows; the struct gained one borrowed allocation reference so an empty aufstellung can still answer "nothing attended, out of N addresses". ## Falsifiers (each verified red under the disable named in its doc comment) the_cross_separates_rung_from_graph cell() returns the lane mask alone a_graph_no_rung_ever_looked_at_... unlooked() drops the and_not crossing_two_allocations_is_refused cross() skips the identity check Each carries its can-stay-silent half: cells whose axes never met must be EMPTY, a graph both legs reached must NOT report as unlooked, and equal-content allocations must still be refused. 1328 contract tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PFnYKqw6d7TTiB9cT8eFdK --- .../lance-graph-contract/src/alpha_focus.rs | 329 ++++++++++++++++++ crates/lance-graph-contract/src/lib.rs | 1 + .../lance-graph-contract/src/spog_tenants.rs | 103 +++++- 3 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 crates/lance-graph-contract/src/alpha_focus.rs diff --git a/crates/lance-graph-contract/src/alpha_focus.rs b/crates/lance-graph-contract/src/alpha_focus.rs new file mode 100644 index 000000000..ad56c34bb --- /dev/null +++ b/crates/lance-graph-contract/src/alpha_focus.rs @@ -0,0 +1,329 @@ +//! **The rung × tenant cross — where attention went, and where it did not.** +//! +//! `.claude/temporal/03-alpha-channel-state.md` records this as an absence, +//! measured: *"The rung × tenant cross does NOT exist … `SpogTenants` exposes +//! no mask surface of its own, and a grep for `rung.*tenant|tenant.*rung` +//! returns nothing but that signature. No structure crosses them, in code or +//! in any plan."* It also names what the object is — *"a 10×N mask matrix over +//! one allocation; each cell is one `AlphaMask` AND … it needs no new stored +//! state (both masks are recomputed projections)"*. This module is that. +//! +//! # Why the cross is the point, and not a convenience +//! +//! Two axes already exist over the SAME allocation: +//! +//! | axis | who owns it | reading | +//! |---|---|---| +//! | rung `0..=9` — level of PROCESSING | [`AlphaTunnel::lane`] | `.attended_mask()` | +//! | graph / tenant — the G of a quad | [`SpogTenants::tenant`] | `.attended_mask()` | +//! +//! Neither can express the other's question. And because they cannot, a +//! consumer that wants "which domain did this walk touch" is pushed to encode +//! the domain *into the rung byte* — which is exactly what a measured consumer +//! did (`rung = domain ordinal + 1`, then filtering one flat scanpath back +//! apart by it). That collapses two axes into one field and is lossy in a way +//! that was also measured: on one real artifact **16 graphs projected onto 6 +//! domains, and 3 graphs (35 % of the rows) resolved to no domain at all** — +//! they landed on the null rung and were filtered out of every projection. +//! +//! The fix is not a rule against the shortcut. It is making the cross +//! *available*, so the rung stays the rung. +//! +//! # The absence is the interesting cell +//! +//! [`AlphaFocus::unlooked`] is `tenant_mask AND NOT any_rung_mask` — "this +//! graph was addressable and no rung ever looked at it". That is the question +//! no log can answer, because absence leaves no line; it is the same argument +//! [`AlphaOverlay::unattended`](crate::alpha::AlphaOverlay::unattended) makes +//! one axis down. +//! +//! # Nothing is stored +//! +//! Every cell is an AND of two masks that are themselves recomputed from the +//! shadows. Constructing a focus costs no rows and no bits beyond the mask it +//! is asked for; dropping it discards nothing that was not derivable again. + +use crate::alpha::AlphaMask; +use crate::alpha_tunnel::AlphaTunnel; +use crate::spog_tenants::SpogTenants; + +/// Why two readings could not be crossed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FocusError { + /// The tunnel and the tenants do not stand over the SAME allocation. + /// + /// Refused rather than intersected: a mask is a set of BASE ORDINALS, so + /// two masks from two allocations are two different coordinate systems and + /// their AND is arithmetic on unrelated numbers. It would return a + /// plausible mask and mean nothing. + DifferentAllocations, +} + +impl std::fmt::Display for FocusError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::DifferentAllocations => write!( + f, + "the tunnel and the tenants stand over different allocations; \ + their ordinals are not the same coordinate" + ), + } + } +} + +impl std::error::Error for FocusError {} + +/// One cell of the cross — a rung, a graph, and the population where both +/// looked. +#[derive(Debug, Clone)] +pub struct FocusCell { + /// The processing rung. + pub rung: u8, + /// The graph (tenant concept id). + pub concept: u16, + /// How many addresses this rung attended within this graph. + pub count: u32, +} + +/// **The 10×N cross** over ONE allocation, borrowed. +pub struct AlphaFocus<'a, 'b> { + tunnel: &'b AlphaTunnel<'a>, + tenants: &'b SpogTenants<'a>, +} + +impl<'a, 'b> AlphaFocus<'a, 'b> { + /// Cross a tunnel with an aufstellung of tenants. + /// + /// # Errors + /// [`FocusError::DifferentAllocations`] when the two do not share one + /// address space. Checked by identity of the base slice, not by its + /// contents: two allocations over equal-but-distinct slices still index + /// their own copies, and `ptr::eq` is the only thing that answers "the + /// same coordinate system" rather than "the same values". + pub fn cross( + tunnel: &'b AlphaTunnel<'a>, + tenants: &'b SpogTenants<'a>, + ) -> Result { + let same = tunnel.lane(0).is_some_and(|l| { + std::ptr::eq(l.allocation().base(), tenants.allocation().base()) + }); + if !same { + return Err(FocusError::DifferentAllocations); + } + Ok(Self { tunnel, tenants }) + } + + /// The population one rung attended INSIDE one graph — the cell. + /// + /// [`None`] when the rung is out of range or the graph has no tenant; an + /// absent axis is not an empty cell. + #[must_use] + pub fn cell(&self, rung: u8, concept: u16) -> Option { + let lane = self.tunnel.lane(rung)?.attended_mask(); + let tenant = self.tenants.tenant_mask(concept)?; + Some(lane.and(&tenant)) + } + + /// Every non-empty cell, in `(rung ascending, declaration order)` — the + /// matrix as a sparse reading, which is what a caller almost always wants + /// (most of a 10×N grid is empty by construction). + #[must_use] + pub fn matrix(&self) -> Vec { + let mut out = Vec::new(); + for rung in 0..crate::rung_schedule::LEVELS { + let rung = u8::try_from(rung).unwrap_or(u8::MAX); + for concept in self.tenants.concepts() { + if let Some(m) = self.cell(rung, concept) { + let count = m.count(); + if count > 0 { + out.push(FocusCell { + rung, + concept, + count, + }); + } + } + } + } + out + } + + /// Everything any rung of the tunnel attended. + #[must_use] + pub fn any_rung_mask(&self) -> AlphaMask { + let base = self.tenants.allocation().base().len(); + let mut m = AlphaMask::empty(base); + for rung in 0..crate::rung_schedule::LEVELS { + if let Some(l) = self.tunnel.lane(u8::try_from(rung).unwrap_or(u8::MAX)) { + m = m.or(&l.attended_mask()); + } + } + m + } + + /// **The absence.** Addresses of `concept` that were allocated and that + /// NO rung ever looked at. + /// + /// Note what this is not: it is not "the tenant's unclaimed addresses". It + /// is the tenant's ATTENDED population minus everything the tunnel + /// reached — so it answers "the graph leg saw these, the rung ladder never + /// did", which is the disagreement worth reading. For the never-addressed + /// remainder use [`AlphaOverlay::unattended`](crate::alpha::AlphaOverlay::unattended). + /// + /// [`None`] for an undeclared graph. + #[must_use] + pub fn unlooked(&self, concept: u16) -> Option { + let tenant = self.tenants.tenant_mask(concept)?; + Some(tenant.and_not(&self.any_rung_mask())) + } + + /// How far one rung reached across ALL graphs. + #[must_use] + pub fn rung_reach(&self, rung: u8) -> Option { + let lane = self.tunnel.lane(rung)?.attended_mask(); + Some(lane.and(&self.tenants.attended_mask())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::alpha::AlphaAllocation; + use crate::canonical_node::{EdgeBlock, NodeGuid, NodeRow}; + use crate::spog_tenants::{block_of, census}; + + /// Two graphs sharing a block, one in another block — the shape the + /// consumer measurement found (several vocabularies under one domain). + fn base() -> Vec { + let mut rows = Vec::new(); + for (g, n) in [(0x9101u32, 3u32), (0x9102, 2), (0x9202, 2)] { + for i in 0..n { + rows.push(NodeRow { + key: NodeGuid::new(g << 16, 0, 0, 0, 0, (g << 4) + i + 1), + edges: EdgeBlock::default(), + value: [0u8; 480], + }); + } + } + rows + } + + /// **The premise, measured on the fixture.** One spine, several graphs — + /// and the census reads them out of the keys rather than being told. + /// Anti-vacuity: a single-graph spine would make every cross below + /// trivially one column wide. + #[test] + fn the_census_reads_several_graphs_out_of_one_spine() { + let b = base(); + assert_eq!(census(&b), vec![0x9101, 0x9102, 0x9202]); + assert!(census(&b).len() >= 2, "anti-vacuity: more than one graph"); + // Grouping is a shift: two of the three share a block. + let block = block_of(0x9101); + let same: Vec = census(&b).into_iter().filter(|&c| block_of(c) == block).collect(); + assert_eq!(same, vec![0x9101, 0x9102]); + assert_ne!(block_of(0x9202), block, "and the third does not"); + } + + /// **THE falsifier of this module.** The cross separates what one flat + /// scanpath cannot: the same rung across two graphs, and two rungs within + /// one graph, are four distinguishable cells. + /// + /// Two-sided: the cells must be non-trivially populated AND a cell whose + /// two axes never met must be EMPTY. Disable-verified: making `cell` + /// return the lane mask alone (ignoring the tenant) collapses the graph + /// axis and fails the emptiness arm. + #[test] + fn the_cross_separates_rung_from_graph() { + let b = base(); + let alloc = AlphaAllocation::over(&b); + let mut tunnel = AlphaTunnel::over(&alloc, 1); + let mut tenants = SpogTenants::over_census(&alloc, 1); + + // rung 3 looks at two rows of 0x9101; rung 5 at one row of 0x9202. + for (rung, row) in [(3u8, 0usize), (3, 1), (5, 5)] { + tunnel.lane_mut(rung).unwrap().claim(b[row].key, rung).unwrap(); + assert!(tenants.claim(b[row].key, rung).routed()); + } + + let f = AlphaFocus::cross(&tunnel, &tenants).expect("one allocation"); + + assert_eq!(f.cell(3, 0x9101).unwrap().count(), 2, "rung 3 in 0x9101"); + assert_eq!(f.cell(5, 0x9202).unwrap().count(), 1, "rung 5 in 0x9202"); + // The cells whose axes never met — the half a collapsed encoding loses. + assert_eq!(f.cell(5, 0x9101).unwrap().count(), 0, "rung 5 never entered 0x9101"); + assert_eq!(f.cell(3, 0x9202).unwrap().count(), 0, "rung 3 never entered 0x9202"); + + let m = f.matrix(); + assert_eq!(m.len(), 2, "exactly the two populated cells: {m:?}"); + assert_eq!((m[0].rung, m[0].concept, m[0].count), (3, 0x9101, 2)); + assert_eq!((m[1].rung, m[1].concept, m[1].count), (5, 0x9202, 1)); + } + + /// **The absence.** A graph the tenant leg attended and the rung ladder + /// never reached is readable — and a graph both reached is NOT reported as + /// absent (the can-stay-silent half). + #[test] + fn a_graph_no_rung_ever_looked_at_is_readable() { + let b = base(); + let alloc = AlphaAllocation::over(&b); + let mut tunnel = AlphaTunnel::over(&alloc, 1); + let mut tenants = SpogTenants::over_census(&alloc, 1); + + // The tenant leg sees 0x9102 (row 3); no rung ever does. + assert!(tenants.claim(b[3].key, 0).routed()); + // Both legs see 0x9101 (row 0). + assert!(tenants.claim(b[0].key, 2).routed()); + tunnel.lane_mut(2).unwrap().claim(b[0].key, 2).unwrap(); + + let f = AlphaFocus::cross(&tunnel, &tenants).expect("one allocation"); + assert_eq!( + f.unlooked(0x9102).unwrap().count(), + 1, + "0x9102 was addressable and no rung looked" + ); + assert_eq!( + f.unlooked(0x9101).unwrap().count(), + 0, + "0x9101 was reached — silence here, or the reading fires on everything" + ); + assert!(f.unlooked(0x0999).is_none(), "an undeclared graph is absent, not empty"); + } + + /// The rung stays the rung: a claim's stamp carries the PROCESSING rung + /// the caller named, in whichever graph it lands. This is the property the + /// domain-in-rung shortcut destroys. + #[test] + fn the_rung_byte_is_the_processing_rung_in_every_graph() { + let b = base(); + let alloc = AlphaAllocation::over(&b); + let mut tenants = SpogTenants::over_census(&alloc, 1); + const RUNG: u8 = 7; + for row in [0usize, 3, 5] { + assert!(tenants.claim(b[row].key, RUNG).routed()); + } + let touched: Vec = tenants + .concepts() + .into_iter() + .filter(|&c| tenants.tenant(c).is_some_and(|s| s.claimed_len() > 0)) + .collect(); + assert_eq!(touched.len(), 3, "anti-vacuity: three graphs touched"); + for (_, st) in tenants.merge() { + assert_eq!(st.rung, RUNG, "the graph never leaks into the rung byte"); + } + } + + /// Two allocations are two coordinate systems — refused, not intersected. + #[test] + fn crossing_two_allocations_is_refused() { + let b = base(); + let other = base(); + let alloc_a = AlphaAllocation::over(&b); + let alloc_b = AlphaAllocation::over(&other); + let tunnel = AlphaTunnel::over(&alloc_a, 1); + let tenants = SpogTenants::over_census(&alloc_b, 1); + match AlphaFocus::cross(&tunnel, &tenants) { + Err(FocusError::DifferentAllocations) => {} + Ok(_) => panic!("equal contents are not the same coordinate"), + } + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 0dc887859..460088009 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -146,6 +146,7 @@ pub use qualia::{ QUALIA_DIMS, QUALIA_I4_DIMS, QUALIA_I4_LABELS, ZERO, }; pub mod alpha; +pub mod alpha_focus; pub mod alpha_tunnel; pub mod fusion; pub mod materialize; diff --git a/crates/lance-graph-contract/src/spog_tenants.rs b/crates/lance-graph-contract/src/spog_tenants.rs index 6d35526cd..96e954167 100644 --- a/crates/lance-graph-contract/src/spog_tenants.rs +++ b/crates/lance-graph-contract/src/spog_tenants.rs @@ -30,7 +30,10 @@ //! as config via ogar ogar-vocab"* — tenant bindings are DATA resolved //! through the codebook, never hardcoded literals in any crate. -use crate::alpha::{AlphaAddr, AlphaAllocation, AlphaClaim, AlphaError, AlphaOverlay, AlphaStamp}; +use crate::alpha::{ + AlphaAddr, AlphaAllocation, AlphaClaim, AlphaError, AlphaMask, AlphaOverlay, AlphaStamp, +}; +use crate::canonical_node::NodeRow; /// The graph coordinate of an address — the canon-high concept half of its /// classid. No fourth column: G is read from the key. @@ -39,6 +42,44 @@ pub const fn graph_of(addr: AlphaAddr) -> u16 { (addr.classid() >> 16) as u16 } +/// The **block** a tenant belongs to — the high byte of its concept id. +/// +/// A concept is `block:vocabulary` (`0x9101` = block `0x91`, vocabulary +/// `0x01`), so several tenants routinely share one block: measured on a real +/// consumer artifact, five distinct graphs resolved to one block. Grouping is +/// therefore a SHIFT on the key, never a second stored coordinate — the same +/// economy `graph_of` itself is. +/// +/// What a block MEANS stays with the consumer that loaded the domain. This +/// crate groups by it and never interprets it. +#[must_use] +pub const fn block_of(concept: u16) -> u8 { + (concept >> 8) as u8 +} + +/// **The tenant list as a census of the artifact, never as a table.** +/// +/// Every row carries its graph in its own key, so the set of tenants a spine +/// needs is a *reading* of that spine — not a configuration beside it that +/// could disagree with it. This is what the module's "tenant bindings are +/// DATA" line buys structurally: here the data IS the bake. +/// +/// Ascending, so the declaration order — which [`SpogTenants::merge`] makes +/// load-bearing — follows from the keys and never from a hash iteration. +/// +/// This exists because the shape it answers to was measured rather than +/// assumed: a consumer's baked artifacts carry **5, 8 and 16 distinct graphs +/// in ONE file** (2026-09-07, over 60 478 / 7 641 / 762 041 rows). Bakes are +/// not one-per-graph, which is precisely the case [`SpogTenants`] exists for — +/// N tenants over ONE allocation, never N bakes and never N copies. +#[must_use] +pub fn census(rows: &[NodeRow]) -> Vec { + let mut seen: Vec = rows.iter().map(|r| graph_of(r.key)).collect(); + seen.sort_unstable(); + seen.dedup(); + seen +} + /// What became of one tenant-routed claim. #[derive(Debug)] pub enum TenantClaim { @@ -64,6 +105,11 @@ pub struct SpogTenants<'a> { /// `(concept, shadow)` in the caller's declaration order — which is the /// merge order, so the caller's order is load-bearing and deterministic. tenants: Vec<(u16, AlphaOverlay<'a>)>, + /// The ONE allocation every shadow borrows. Held so the mask surface has + /// a base length even when no tenant was declared — an empty aufstellung + /// must still answer "nothing attended, out of N addresses" rather than + /// have no answer at all. + alloc: &'a AlphaAllocation<'a>, } impl<'a> SpogTenants<'a> { @@ -78,7 +124,20 @@ impl<'a> SpogTenants<'a> { tenants.push((c, AlphaOverlay::over_shared(alloc, cycle))); } } - Self { tenants } + Self { tenants, alloc } + } + + /// **The no-configuration constructor**: one shadow per graph the + /// allocation's own spine carries ([`census`]). + /// + /// With this there is no list to keep in step with the bake, so + /// [`TenantClaim::NoTenant`] becomes structurally unreachable for any + /// address of THIS spine — a claim can only miss a tenant if the caller + /// declared a narrower set on purpose. + #[must_use] + pub fn over_census(alloc: &'a AlphaAllocation<'a>, cycle: u32) -> Self { + let concepts = census(alloc.base()); + Self::over(alloc, cycle, &concepts) } /// Route a claim to the tenant owning `graph_of(addr)`. @@ -108,6 +167,46 @@ impl<'a> SpogTenants<'a> { self.tenants.iter().map(|(k, _)| *k).collect() } + /// The tenants of one block, in declaration order — grouping by + /// [`block_of`], a shift on the key. + #[must_use] + pub fn tenants_in_block(&self, block: u8) -> Vec { + self.tenants + .iter() + .map(|(k, _)| *k) + .filter(|&c| block_of(c) == block) + .collect() + } + + /// The allocation every shadow borrows — the ONE address space. + #[must_use] + pub fn allocation(&self) -> &'a AlphaAllocation<'a> { + self.alloc + } + + /// **One tenant's population, as a mask.** [`None`] for an undeclared + /// graph — an absent tenant is not an empty one, and answering an empty + /// mask would make "this graph has no shadow" indistinguishable from + /// "this graph was never looked at". + #[must_use] + pub fn tenant_mask(&self, concept: u16) -> Option { + self.tenant(concept).map(AlphaOverlay::attended_mask) + } + + /// Everything any tenant attended, as one mask — the SPOG half of the + /// rung × tenant cross ([`crate::alpha_focus`]). + /// + /// Recomputed, never stored: the shadows are the truth and a cached union + /// would be a second reading of them. + #[must_use] + pub fn attended_mask(&self) -> AlphaMask { + let mut m = AlphaMask::empty(self.alloc.base().len()); + for (_, s) in &self.tenants { + m = m.or(&s.attended_mask()); + } + m + } + /// Total claims across all shadows. #[must_use] pub fn claimed_len(&self) -> usize { From de085909eb0689273e2d0d3e5479a9e82d5a8237 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 18:35:06 +0000 Subject: [PATCH 2/3] contract: SpogTenants replays the interleaved saccade, not only the grouping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `merge()` groups a thought BY GRAPH, and that is the only reading the shadows can produce on their own: each shadow numbers its claims from 0, so per-shadow `seq` is exact WITHIN a graph and meaningless BETWEEN graphs. The interleaving of a saccade that crosses tenants is destroyed by the split and no reading of the shadows recovers it — which makes routing by G lossy against a single flat overlay for every order-sensitive consumer, and a scanpath is exactly that. `merge_in_claim_order()` is the missing reading, and `route: Vec` its only possible home. It stores WHICH TENANT took the n-th fresh claim and nothing else — no address, because a tenant's own scanpath already carries which address in order, so tenant id plus a per-tenant cursor reconstructs the global saccade exactly. Revisits are excluded by construction: a revisit adds no position to any scanpath, so recording one desynchronises the cursors. This is not a second projection of stored state — it is the sole home of a fact the split would otherwise destroy, which is the distinction the zero-copy law turns on. `u16` per fresh claim. ## The falsifier's SHAPE is part of its coverage `claim_order_replays_the_interleaved_saccade_that_declaration_order_loses` is two-sided: the two readings must hold the SAME addresses (nothing invented, nothing dropped) and must NOT be in the same order — a fixture where they agree proves nothing. Its first version revisited at the END of the walk and the disable run stayed GREEN. The extra route entry merely ran the revisited tenant's cursor off its own scanpath and the surplus was silently dropped, so the output was identical and the test could not see the defect. A revisit is only observable when ANOTHER tenant claims after it and the revisited tenant claims again. The fixture now has that shape, and the disable (drop the `c.fresh` guard) fails it on the order arm. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PFnYKqw6d7TTiB9cT8eFdK --- .../lance-graph-contract/src/alpha_focus.rs | 34 +++- .../lance-graph-contract/src/spog_tenants.rs | 158 +++++++++++++++++- 2 files changed, 182 insertions(+), 10 deletions(-) diff --git a/crates/lance-graph-contract/src/alpha_focus.rs b/crates/lance-graph-contract/src/alpha_focus.rs index ad56c34bb..65310c587 100644 --- a/crates/lance-graph-contract/src/alpha_focus.rs +++ b/crates/lance-graph-contract/src/alpha_focus.rs @@ -104,9 +104,9 @@ impl<'a, 'b> AlphaFocus<'a, 'b> { tunnel: &'b AlphaTunnel<'a>, tenants: &'b SpogTenants<'a>, ) -> Result { - let same = tunnel.lane(0).is_some_and(|l| { - std::ptr::eq(l.allocation().base(), tenants.allocation().base()) - }); + let same = tunnel + .lane(0) + .is_some_and(|l| std::ptr::eq(l.allocation().base(), tenants.allocation().base())); if !same { return Err(FocusError::DifferentAllocations); } @@ -219,7 +219,10 @@ mod tests { assert!(census(&b).len() >= 2, "anti-vacuity: more than one graph"); // Grouping is a shift: two of the three share a block. let block = block_of(0x9101); - let same: Vec = census(&b).into_iter().filter(|&c| block_of(c) == block).collect(); + let same: Vec = census(&b) + .into_iter() + .filter(|&c| block_of(c) == block) + .collect(); assert_eq!(same, vec![0x9101, 0x9102]); assert_ne!(block_of(0x9202), block, "and the third does not"); } @@ -241,7 +244,11 @@ mod tests { // rung 3 looks at two rows of 0x9101; rung 5 at one row of 0x9202. for (rung, row) in [(3u8, 0usize), (3, 1), (5, 5)] { - tunnel.lane_mut(rung).unwrap().claim(b[row].key, rung).unwrap(); + tunnel + .lane_mut(rung) + .unwrap() + .claim(b[row].key, rung) + .unwrap(); assert!(tenants.claim(b[row].key, rung).routed()); } @@ -250,8 +257,16 @@ mod tests { assert_eq!(f.cell(3, 0x9101).unwrap().count(), 2, "rung 3 in 0x9101"); assert_eq!(f.cell(5, 0x9202).unwrap().count(), 1, "rung 5 in 0x9202"); // The cells whose axes never met — the half a collapsed encoding loses. - assert_eq!(f.cell(5, 0x9101).unwrap().count(), 0, "rung 5 never entered 0x9101"); - assert_eq!(f.cell(3, 0x9202).unwrap().count(), 0, "rung 3 never entered 0x9202"); + assert_eq!( + f.cell(5, 0x9101).unwrap().count(), + 0, + "rung 5 never entered 0x9101" + ); + assert_eq!( + f.cell(3, 0x9202).unwrap().count(), + 0, + "rung 3 never entered 0x9202" + ); let m = f.matrix(); assert_eq!(m.len(), 2, "exactly the two populated cells: {m:?}"); @@ -286,7 +301,10 @@ mod tests { 0, "0x9101 was reached — silence here, or the reading fires on everything" ); - assert!(f.unlooked(0x0999).is_none(), "an undeclared graph is absent, not empty"); + assert!( + f.unlooked(0x0999).is_none(), + "an undeclared graph is absent, not empty" + ); } /// The rung stays the rung: a claim's stamp carries the PROCESSING rung diff --git a/crates/lance-graph-contract/src/spog_tenants.rs b/crates/lance-graph-contract/src/spog_tenants.rs index 96e954167..7473609df 100644 --- a/crates/lance-graph-contract/src/spog_tenants.rs +++ b/crates/lance-graph-contract/src/spog_tenants.rs @@ -110,6 +110,25 @@ pub struct SpogTenants<'a> { /// must still answer "nothing attended, out of N addresses" rather than /// have no answer at all. alloc: &'a AlphaAllocation<'a>, + /// **Which tenant took the n-th FRESH claim** — the one fact the shadows + /// cannot hold. + /// + /// Each shadow numbers its own claims from 0, so per-shadow `seq` is + /// exact WITHIN a graph and meaningless BETWEEN graphs: the interleaving + /// of a saccade that crosses tenants is destroyed by the split, and no + /// reading of the shadows can recover it. So it is recorded here, and + /// only here. + /// + /// This is not a second projection of something already stored. It is the + /// sole home of a fact that would otherwise be lost — the distinction the + /// zero-copy law turns on. It costs `u16` per fresh claim and NO address: + /// a tenant's own scanpath already carries which address, in order, so + /// the tenant id plus a per-tenant cursor reconstructs the global saccade + /// exactly ([`Self::merge_in_claim_order`]). + /// + /// Revisits are absent by construction — a revisit adds no position to any + /// scanpath, so recording one here would desynchronise the cursors. + route: Vec, } impl<'a> SpogTenants<'a> { @@ -124,7 +143,11 @@ impl<'a> SpogTenants<'a> { tenants.push((c, AlphaOverlay::over_shared(alloc, cycle))); } } - Self { tenants, alloc } + Self { + tenants, + alloc, + route: Vec::new(), + } } /// **The no-configuration constructor**: one shadow per graph the @@ -147,7 +170,12 @@ impl<'a> SpogTenants<'a> { return TenantClaim::NoTenant(g); }; match shadow.claim(addr, rung) { - Ok(c) => TenantClaim::Routed(g, c), + Ok(c) => { + if c.fresh { + self.route.push(g); + } + TenantClaim::Routed(g, c) + } Err(e) => TenantClaim::Substrate(e), } } @@ -213,6 +241,43 @@ impl<'a> SpogTenants<'a> { self.tenants.iter().map(|(_, s)| s.claimed_len()).sum() } + /// **The saccade as it happened**, across tenants — visit order, not + /// declaration order. + /// + /// The sibling of [`merge`](Self::merge), and the two answer different + /// questions: `merge` groups a thought BY GRAPH (every claim of one + /// tenant together, which is what a per-graph reading wants); this + /// replays it IN TIME (what attention did, in the order it did it), which + /// is what a scanpath consumer and any order-sensitive replay wants. + /// + /// Reconstructed from [`Self::route`] plus each tenant's own scanpath — + /// one cursor per tenant, advanced as its id comes up. `seq` is re-issued + /// as the global position, so it means the same thing it means in a + /// single overlay. + #[must_use] + pub fn merge_in_claim_order(&self) -> Vec<(AlphaAddr, AlphaStamp)> { + let mut cursor: Vec<(u16, usize)> = self.tenants.iter().map(|(k, _)| (*k, 0)).collect(); + let mut out = Vec::with_capacity(self.route.len()); + for &g in &self.route { + let Some((_, shadow)) = self.tenants.iter().find(|(k, _)| *k == g) else { + continue; + }; + let Some(slot) = cursor.iter_mut().find(|(k, _)| *k == g) else { + continue; + }; + let Some(addr) = shadow.scanpath().nth(slot.1) else { + continue; + }; + slot.1 += 1; + if let Some(row) = shadow.get(addr) { + let mut st = crate::alpha::stamp_of(row); + st.seq = u32::try_from(out.len()).unwrap_or(u32::MAX); + out.push((addr, st)); + } + } + out + } + /// Merge the shadows into one deterministic scanpath: declaration order, /// then per-shadow seq. Tenants are DISJOINT by construction (an address /// routes only to its own graph's shadow), so no cross-tenant revisit @@ -321,6 +386,95 @@ mod tests { assert_eq!(st.rung, 1, "the first stamp is kept"); } + /// **The order falsifier.** `merge` groups a thought BY GRAPH; the + /// interleaved saccade — what attention did, in time — is a different + /// sequence, and only [`SpogTenants::merge_in_claim_order`] has it. + /// + /// Two-sided on purpose: the two readings must hold the SAME addresses + /// (nothing invented, nothing dropped) and must NOT be in the same order + /// (otherwise this method is decoration and the fixture is one that + /// cannot tell them apart). A revisit must add no position to either. + /// + /// Disable-verified: dropping the `c.fresh` guard on `route.push` — so a + /// revisit records a position — desynchronises the cursors and replays the + /// saccade in the WRONG ORDER. + /// + /// The first version of this fixture revisited at the END and the disable + /// stayed GREEN: the extra route entry simply ran the revisited tenant's + /// cursor off its own scanpath, and the surplus was dropped. A revisit + /// only produces an observable defect when another tenant claims after it + /// and the revisited tenant claims again — so the fixture's SHAPE is part + /// of what this test covers, not just its values. + #[test] + fn claim_order_replays_the_interleaved_saccade_that_declaration_order_loses() { + let b = base(); + let alloc = AlphaAllocation::over(&b); + // Declaration order is deliberately NOT the visit order below. + let mut t = SpogTenants::over(&alloc, 4, &[0x0900, 0x0302, 0x0301]); + + // Attention crosses tenants, RETURNS mid-way, and then goes on — and + // that exact shape is what makes the revisit rule falsifiable. A + // revisit followed by nothing, or followed only by more of the same + // tenant, is absorbed by the cursor running off its own scanpath and + // proves nothing; the wrong position only surfaces when a DIFFERENT + // tenant claims after the revisit and the revisited one claims again. + assert!(t.claim(b[0].key, 2).routed()); // 0x0301 + assert!(t.claim(b[4].key, 2).routed()); // 0x0302 + assert!(t.claim(b[0].key, 9).routed()); // 0x0301 AGAIN — not a position + assert!(t.claim(b[7].key, 2).routed()); // 0x0900 + assert!(t.claim(b[1].key, 2).routed()); // 0x0301, after the crossing + + let timed: Vec = t.merge_in_claim_order().iter().map(|(a, _)| *a).collect(); + assert_eq!( + timed, + vec![b[0].key, b[4].key, b[7].key, b[1].key], + "the saccade replays in the order it happened, revisit adding nothing" + ); + + let grouped: Vec = t.merge().iter().map(|(a, _)| *a).collect(); + assert_eq!( + grouped, + vec![b[7].key, b[4].key, b[0].key, b[1].key], + "declaration order groups by graph" + ); + + // Same population, different sequence — the whole point. + let mut a = timed.clone(); + let mut c = grouped.clone(); + a.sort_unstable_by_key(|k| (k.classid(), k.identity())); + c.sort_unstable_by_key(|k| (k.classid(), k.identity())); + assert_eq!(a, c, "nothing invented, nothing dropped"); + assert_ne!( + timed, grouped, + "anti-vacuity: a fixture where both readings agree proves nothing" + ); + + // seq is the global position in each reading. + let seqs: Vec = t + .merge_in_claim_order() + .iter() + .map(|(_, s)| s.seq) + .collect(); + assert_eq!(seqs, vec![0, 1, 2, 3]); + assert_eq!( + t.merge_in_claim_order().len(), + 4, + "one position per FRESH claim" + ); + // ...and the RUNG is still the first visit's, never the revisit's. + assert_eq!(t.merge_in_claim_order()[0].1.rung, 2, "first stamp kept"); + assert_eq!( + t.merge_in_claim_order()[0].1.visits, + 2, + "the return is counted" + ); + assert_eq!( + t.merge_in_claim_order(), + t.merge_in_claim_order(), + "deterministic" + ); + } + /// Merge is deterministic and ordered by tenant DECLARATION order, then /// per-shadow visit order; seq is re-issued globally. #[test] From 72c5821c3056163f26b37ebd85e0203144490295 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 18:47:22 +0000 Subject: [PATCH 3/3] contract: SpogTenants gains get/stamp, allocated_len and unattended MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three readings a consumer had to reach around the type for, each of which belongs to the aufstellung rather than to any one shadow. `get(addr)` / `stamp(addr)` route by `graph_of` — the SAME rule a claim routes by. Making a reader re-derive which shadow holds an address would be a second routing rule that can drift from the first. Falsifier is two-sided: an address claimed in a LATER-declared tenant must still be found (so the reading is not a scan of the first shadow), and an allocated-but-unclaimed address must be `None` (so it is not answering from the allocation instead of the shadows). `unattended()` must be asked of the AUFSTELLUNG. A single shadow's own `unattended` reports every address of every OTHER graph as unattended too — true of that shadow, useless as a reading of the thought. The falsifier asserts exactly that contrast, so the method cannot be quietly reduced to the single-shadow answer. `allocated_len()` is the allocation's size, never how many rows a shadow holds. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PFnYKqw6d7TTiB9cT8eFdK --- .../lance-graph-contract/src/spog_tenants.rs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/crates/lance-graph-contract/src/spog_tenants.rs b/crates/lance-graph-contract/src/spog_tenants.rs index 7473609df..9a00a8cbe 100644 --- a/crates/lance-graph-contract/src/spog_tenants.rs +++ b/crates/lance-graph-contract/src/spog_tenants.rs @@ -189,6 +189,26 @@ impl<'a> SpogTenants<'a> { .map(|(_, s)| s) } + /// The row at `addr`, found through the SAME routing a claim used. + /// + /// A reader should not have to know which shadow holds an address — + /// `graph_of` already answers that, and making the caller re-derive it + /// would be a second routing rule that can drift from the first. + /// [`None`] when the graph has no tenant or the address was never claimed; + /// the two are deliberately not distinguished here, because a reader + /// asking "was this attended" wants one answer. Use + /// [`tenant`](Self::tenant) when the difference matters. + #[must_use] + pub fn get(&self, addr: AlphaAddr) -> Option<&NodeRow> { + self.tenant(graph_of(addr))?.get(addr) + } + + /// The stamp at `addr`, routed as [`get`](Self::get) routes. + #[must_use] + pub fn stamp(&self, addr: AlphaAddr) -> Option { + self.get(addr).map(crate::alpha::stamp_of) + } + /// The declared tenant concepts, in declaration order. #[must_use] pub fn concepts(&self) -> Vec { @@ -235,6 +255,31 @@ impl<'a> SpogTenants<'a> { m } + /// How many addresses exist — the allocation's size. Never how many rows + /// any shadow holds. + #[must_use] + pub fn allocated_len(&self) -> usize { + self.alloc.len() + } + + /// **The absence within the aufstellung**: allocated addresses no tenant + /// ever claimed. + /// + /// The SPOG sibling of [`AlphaOverlay::unattended`], and it must be asked + /// of the aufstellung rather than of any single shadow: a shadow's own + /// `unattended` reports every address of every OTHER graph as unattended + /// too, which is true of that shadow and useless as a reading of the + /// thought. + #[must_use] + pub fn unattended(&self) -> Vec { + self.alloc + .base() + .iter() + .map(|r| r.key) + .filter(|a| self.get(*a).is_none()) + .collect() + } + /// Total claims across all shadows. #[must_use] pub fn claimed_len(&self) -> usize { @@ -370,6 +415,54 @@ mod tests { assert_eq!(t.claimed_len(), 3, "the stray claim landed nowhere"); } + /// `get` routes by the SAME rule a claim routes by — a reader never has + /// to know which shadow holds an address. + /// + /// Two-sided: an address claimed in a LATER-declared tenant must still be + /// found (so the reading cannot be a scan of the first shadow), and an + /// allocated-but-unclaimed address must be `None` (so it cannot be + /// answering from the allocation instead of the shadows). + #[test] + fn get_routes_by_graph_the_way_claim_does() { + let b = base(); + let alloc = AlphaAllocation::over(&b); + let mut t = SpogTenants::over(&alloc, 1, &[0x0900, 0x0302, 0x0301]); + assert!(t.claim(b[0].key, 6).routed()); // 0x0301 — declared LAST + assert_eq!(t.stamp(b[0].key).expect("found via routing").rung, 6); + assert!(t.get(b[1].key).is_none(), "allocated, never claimed"); + assert!(t.get(b[9].key).is_none(), "0x0777 has no tenant at all"); + } + + /// The absence is asked of the AUFSTELLUNG, never of one shadow — a + /// single shadow calls every other graph's addresses unattended, which is + /// true of it and useless as a reading of the thought. + /// + /// Two-sided: the unattended set shrinks by exactly the claim, and the + /// claimed address is NOT in it. + #[test] + fn unattended_is_a_reading_of_the_aufstellung_not_of_one_shadow() { + let b = base(); + let alloc = AlphaAllocation::over(&b); + let mut t = SpogTenants::over(&alloc, 1, &[0x0301, 0x0302, 0x0900]); + assert_eq!(t.allocated_len(), b.len()); + assert_eq!(t.unattended().len(), b.len(), "nothing attended yet"); + + assert!(t.claim(b[4].key, 1).routed()); // 0x0302 + let un = t.unattended(); + assert_eq!(un.len(), b.len() - 1, "exactly the one claim"); + assert!(!un.contains(&b[4].key), "the claimed address is not absent"); + assert!(un.contains(&b[0].key), "another graph's address still is"); + + // The single-shadow reading would say something else entirely. + let lone = t.tenant(0x0302).unwrap().unattended().count(); + assert_eq!( + lone, + b.len() - 1, + "one shadow counts every foreign address as unattended — the reason \ + this method exists on the aufstellung" + ); + } + /// One substrate, never two shadows for one graph: duplicate concepts /// collapse; a revisit is counted in the ONE shadow's `visits`. #[test]