From 4a1f1e64e87343e90fdd99673fb22464df84c38b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Nov 2026 23:41:59 +0000 Subject: [PATCH 1/2] dt64/quack: version source-local codes and refuse a stale code space A query bound against a source's dictionary kept its literal codes after the dictionary was renumbered. Measured on a swapped dictionary, the frame-1 literal counted 3 rows on frame-2 lanes where the answer is 1. dt64 gains `CodeTable`, the code-space sibling of `MapTable`: for each `CodeSpaceId` (full classid + canonical property) an append-only history of bijections between canonical `ValueKey`s and the source's codes. It is bound to the identity of the FrameStore the lanes are sealed under, and `advance` advances that store exactly once, so a renumbering makes every earlier lane frame non-current. `advance` refuses a foreign store and a non-bijective change set before anything changes. Reads take a `Frame`, never a bare version, and a historical frame reads the mapping in force at its version. quack gains `CodeWitness`, `Draft::bind_typed` and `TypedQuery::admit`. `bind_typed` binds exactly like `bind` and records the owner's witness for every coded field; a coded field without a witness refuses (`Unwitnessed`). `admit` re-checks every witness before the query is handed out and refuses `StaleCodeSpace` otherwise. `Draft::bind` and `Query` are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk --- crates/lance-graph-dt64/src/dr.rs | 164 ++++++++++++++++ crates/lance-graph-dt64/src/lib.rs | 5 +- crates/lance-graph-dt64/tests/dr.rs | 188 ++++++++++++++++++- crates/lance-graph-quack/src/bind.rs | 270 +++++++++++++++++++++++++++ 4 files changed, 624 insertions(+), 3 deletions(-) diff --git a/crates/lance-graph-dt64/src/dr.rs b/crates/lance-graph-dt64/src/dr.rs index 64d3b35c4..47cfe9dd2 100644 --- a/crates/lance-graph-dt64/src/dr.rs +++ b/crates/lance-graph-dt64/src/dr.rs @@ -1083,3 +1083,167 @@ pub fn coordinates(width: Width, code: Morton8x8) -> Result<(u8, u8), MapRefusal pub fn rotate_yz(code: Morton8x8) -> Morton8x8 { Morton8x8::from_xy(code.y(), code.x()) } + +// ---- code space: value key <-> source-local code --------------------------- + +/// A canonical value key: the meaning of one value of a property (for +/// example the OGAR key of the order status `shipped`). OGAR owns the key; a +/// source owns the numbers it assigns to keys ([`CodeTable`]). The key is +/// never an executable number. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ValueKey(pub String); + +/// One namespace of codes: a full classid and the canonical identity of one +/// of its properties. Two properties never share a code space, and one +/// property of two classes does not either. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CodeSpaceId { + pub class: u32, + pub property: String, +} + +/// Why a [`CodeTable`] read or advance was refused. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CodeRead { + /// The [`FrameStore`] passed to [`CodeTable::advance`] is not the one the + /// table is bound to. Nothing changed. + ForeignStore, + /// The frame was not issued by the store the table is bound to, even at + /// equal revisions and versions. A frame of the bound store is never + /// stale here: it reads the generation in force at its own version. + ForeignFrame, + /// The table holds no generation of this space at the frame's version. + NoSpace, + /// The generation maps no key to this code. + UnknownCode, + /// The generation maps no code to this key. + UnknownKey, + /// A change set maps one key to two codes, two keys to one code, or names + /// a space twice. Nothing changed. + Duplicate, +} + +#[derive(Debug)] +struct CodeGeneration { + at: RouteVersion, + by_key: BTreeMap, + by_code: BTreeMap, +} + +/// The code-space sibling of [`MapTable`]: per [`CodeSpaceId`], an +/// append-only history of generations, each a bijection between canonical +/// [`ValueKey`]s and the source's local codes `C`. +/// +/// Unlike [`MapTable`] it owns no [`FrameStore`]. It is bound to the store +/// that the lanes are sealed under, and [`CodeTable::advance`] advances THAT +/// store, so a code change cannot leave a lane [`Frame`] current +/// ([`FrameStore::check`]). Reads are keyed by a [`Frame`], never by a bare +/// version number: a historical frame reads the mapping in force at its +/// version, which says what a code meant then, not what a row held. +#[derive(Debug)] +pub struct CodeTable { + store: Weak<()>, + spaces: BTreeMap>>, +} + +impl CodeTable { + /// An empty table bound to the identity of `frames`. + pub fn new(frames: &FrameStore) -> Self { + Self { + store: Arc::downgrade(&frames.identity), + spaces: BTreeMap::new(), + } + } + + /// Replace the generation of each listed space and advance `frames` + /// exactly once. A space absent from `changes` keeps its generation. + /// The new generation of a listed space is exactly its entries. + /// + /// # Errors + /// + /// [`CodeRead::ForeignStore`] if `frames` is not the bound store, and + /// [`CodeRead::Duplicate`] if a change set is not a bijection or names a + /// space twice. Both are checked before anything changes: on `Err` the + /// table and `frames` are untouched. + pub fn advance( + &mut self, + frames: &mut FrameStore, + changes: Vec<(CodeSpaceId, Vec<(ValueKey, C)>)>, + ) -> Result<(), CodeRead> { + if !Weak::ptr_eq(&self.store, &Arc::downgrade(&frames.identity)) { + return Err(CodeRead::ForeignStore); + } + let mut seen = BTreeSet::new(); + let mut staged = Vec::with_capacity(changes.len()); + for (space, entries) in changes { + if !seen.insert(space.clone()) { + return Err(CodeRead::Duplicate); + } + let mut by_key = BTreeMap::new(); + let mut by_code = BTreeMap::new(); + for (key, code) in entries { + if by_key.insert(key.clone(), code).is_some() || by_code.insert(code, key).is_some() + { + return Err(CodeRead::Duplicate); + } + } + staged.push((space, by_key, by_code)); + } + let r = frames.revisions(); + let at = frames.advance(FrameRevisions { + source: r.source.wrapping_add(1), + ..r + }); + for (space, by_key, by_code) in staged { + self.spaces.entry(space).or_default().push(CodeGeneration { + at, + by_key, + by_code, + }); + } + Ok(()) + } + + /// The key `code` meant in `space` as of `frame`. + /// + /// # Errors + /// + /// [`CodeRead::ForeignFrame`], [`CodeRead::NoSpace`] or + /// [`CodeRead::UnknownCode`]. + pub fn key(&self, frame: &Frame, space: &CodeSpaceId, code: C) -> Result<&ValueKey, CodeRead> { + self.generation(frame, space)? + .by_code + .get(&code) + .ok_or(CodeRead::UnknownCode) + } + + /// The code `key` had in `space` as of `frame`. + /// + /// # Errors + /// + /// [`CodeRead::ForeignFrame`], [`CodeRead::NoSpace`] or + /// [`CodeRead::UnknownKey`]. + pub fn code(&self, frame: &Frame, space: &CodeSpaceId, key: &ValueKey) -> Result { + self.generation(frame, space)? + .by_key + .get(key) + .copied() + .ok_or(CodeRead::UnknownKey) + } + + fn generation( + &self, + frame: &Frame, + space: &CodeSpaceId, + ) -> Result<&CodeGeneration, CodeRead> { + if !Weak::ptr_eq(&frame.store, &self.store) { + return Err(CodeRead::ForeignFrame); + } + let gens = self.spaces.get(space).ok_or(CodeRead::NoSpace)?; + let in_force = gens.partition_point(|g| g.at <= frame.version); + in_force + .checked_sub(1) + .map(|i| &gens[i]) + .ok_or(CodeRead::NoSpace) + } +} diff --git a/crates/lance-graph-dt64/src/lib.rs b/crates/lance-graph-dt64/src/lib.rs index 2f13bdaf7..ff55f9d39 100644 --- a/crates/lance-graph-dt64/src/lib.rs +++ b/crates/lance-graph-dt64/src/lib.rs @@ -98,7 +98,10 @@ use lance_graph_contract::set_rules::{Population, Truth}; pub mod dr; -pub use dr::{Axis, Claim, Extent, RouteVersion, Width, MAX_RESERVED}; +pub use dr::{ + Axis, Claim, CodeRead, CodeSpaceId, CodeTable, Extent, RouteVersion, ValueKey, Width, + MAX_RESERVED, +}; pub mod lane; use dr::{RouteLog, RouteRefusal}; pub use lane::{CoordSpace, LaneAddr, LaneBinding, LaneRefusal, LaneRequest, LaneSource, RouteAt}; diff --git a/crates/lance-graph-dt64/tests/dr.rs b/crates/lance-graph-dt64/tests/dr.rs index 662da83b0..cea3036f8 100644 --- a/crates/lance-graph-dt64/tests/dr.rs +++ b/crates/lance-graph-dt64/tests/dr.rs @@ -4,8 +4,8 @@ use lance_graph_contract::morton8x8::Morton8x8; use lance_graph_dt64::dr::{ - address, coordinates, rotate_yz, Focus, FrameRefusal, FrameRevisions, FrameStore, GuardRefusal, - Guards, LocalMap, MapRefusal, Scope, Width, + address, coordinates, rotate_yz, CodeRead, CodeSpaceId, CodeTable, Focus, FrameRefusal, + FrameRevisions, FrameStore, GuardRefusal, Guards, LocalMap, MapRefusal, Scope, ValueKey, Width, }; fn revisions() -> FrameRevisions { @@ -600,3 +600,187 @@ mod vmap { assert_ne!(d.digest(), b.map(&fb, 1).unwrap().digest()); } } + +// ---- CodeTable: the code-space G2L ---------------------------------------- + +fn status() -> CodeSpaceId { + CodeSpaceId { + class: 0x0202_0002, + property: "status".into(), + } +} +fn priority() -> CodeSpaceId { + CodeSpaceId { + class: 0x0202_0002, + property: "priority".into(), + } +} +fn vk(s: &str) -> ValueKey { + ValueKey(s.into()) +} +fn entries(e: &[(&str, u32)]) -> Vec<(ValueKey, u32)> { + e.iter().map(|(k, c)| (vk(k), *c)).collect() +} + +/// t1: a frame reads the generation in force at its own version. +#[test] +fn code_reads_are_frame_keyed_and_historical() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + t.advance( + &mut fs, + vec![(status(), entries(&[("shipped", 3), ("cancelled", 7)]))], + ) + .unwrap(); + let f1 = fs.seal(); + t.advance( + &mut fs, + vec![(status(), entries(&[("cancelled", 3), ("shipped", 7)]))], + ) + .unwrap(); + let f2 = fs.seal(); + assert_eq!(t.key(&f1, &status(), 3), Ok(&vk("shipped"))); + assert_eq!(t.key(&f2, &status(), 3), Ok(&vk("cancelled"))); + assert_eq!(t.code(&f2, &status(), &vk("shipped")), Ok(7)); + assert_eq!(t.code(&f1, &status(), &vk("shipped")), Ok(3)); + assert_eq!( + t.code(&f1, &status(), &vk("lost")), + Err(CodeRead::UnknownKey) + ); + assert_eq!(t.key(&f1, &status(), 9), Err(CodeRead::UnknownCode)); +} + +/// t1b: a frame sealed before any generation of the space reads nothing, and +/// an unknown space reads nothing. +#[test] +fn a_frame_before_the_first_generation_has_no_space() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + let before = fs.seal(); + t.advance(&mut fs, vec![(status(), entries(&[("shipped", 3)]))]) + .unwrap(); + let after = fs.seal(); + assert_eq!( + t.code(&before, &status(), &vk("shipped")), + Err(CodeRead::NoSpace) + ); + assert_eq!( + t.code(&after, &priority(), &vk("shipped")), + Err(CodeRead::NoSpace) + ); + assert_eq!(t.code(&after, &status(), &vk("shipped")), Ok(3)); +} + +/// t2: a frame of another store is refused although its version equals. +#[test] +fn a_frame_of_another_store_is_refused_at_equal_version() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + t.advance(&mut fs, vec![(status(), entries(&[("shipped", 3)]))]) + .unwrap(); + let mut other = FrameStore::new(revisions()); + other.advance(revisions()); + let foreign = other.seal(); + let own = fs.seal(); + assert_eq!(foreign.version(), own.version()); + assert_eq!(t.code(&own, &status(), &vk("shipped")), Ok(3)); + assert_eq!( + t.code(&foreign, &status(), &vk("shipped")), + Err(CodeRead::ForeignFrame) + ); + assert_eq!(t.key(&foreign, &status(), 3), Err(CodeRead::ForeignFrame)); +} + +/// t3: advancing with a store the table is not bound to changes nothing, +/// neither the table nor either store. +#[test] +fn a_foreign_store_is_refused_before_anything_changes() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + t.advance(&mut fs, vec![(status(), entries(&[("shipped", 3)]))]) + .unwrap(); + let bound_frame = fs.seal(); + let mut other = FrameStore::new(revisions()); + let other_frame = other.seal(); + let other_rev = other.revisions(); + let r = t.advance( + &mut other, + vec![(status(), entries(&[("shipped", 9), ("cancelled", 3)]))], + ); + assert_eq!(r, Err(CodeRead::ForeignStore)); + assert_eq!(other.revisions(), other_rev); + assert_eq!(other.check(&other_frame), Ok(())); + assert_eq!(fs.check(&bound_frame), Ok(())); + assert_eq!(t.code(&bound_frame, &status(), &vk("shipped")), Ok(3)); + assert_eq!( + t.code(&bound_frame, &status(), &vk("cancelled")), + Err(CodeRead::UnknownKey) + ); +} + +/// t4: a change set that is not a bijection, or names a space twice, is +/// refused and the store is not advanced. +#[test] +fn a_duplicate_change_set_is_refused_and_the_store_is_not_advanced() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + let f = fs.seal(); + let two_keys_one_code = entries(&[("shipped", 3), ("cancelled", 3)]); + let one_key_two_codes = entries(&[("shipped", 3), ("shipped", 7)]); + for bad in [ + vec![(status(), two_keys_one_code)], + vec![(status(), one_key_two_codes)], + vec![ + (status(), entries(&[("shipped", 3)])), + (status(), entries(&[("cancelled", 7)])), + ], + // the valid space listed first must not leak when a later one fails + vec![ + (priority(), entries(&[("high", 1)])), + (status(), entries(&[("a", 1), ("b", 1)])), + ], + ] { + assert_eq!(t.advance(&mut fs, bad), Err(CodeRead::Duplicate)); + assert_eq!(fs.check(&f), Ok(()), "store must not have advanced"); + } + assert_eq!(t.code(&f, &priority(), &vk("high")), Err(CodeRead::NoSpace)); + // the silence twin: a valid set advances + t.advance(&mut fs, vec![(status(), entries(&[("shipped", 3)]))]) + .unwrap(); + assert!(fs.check(&f).is_err()); +} + +/// t5: a code change advances the store, so a lane frame sealed before it is +/// no longer current. +#[test] +fn advancing_the_code_table_retires_lane_frames() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + let lane_frame = fs.seal(); + assert_eq!(fs.check(&lane_frame), Ok(())); + t.advance(&mut fs, vec![(status(), entries(&[("shipped", 3)]))]) + .unwrap(); + assert!(fs.check(&lane_frame).is_err()); + assert_eq!(fs.check(&fs.seal()), Ok(())); +} + +/// t6: a space absent from a change set keeps its generation. +#[test] +fn an_unchanged_space_survives_an_advance_of_another() { + let mut fs = FrameStore::new(revisions()); + let mut t: CodeTable = CodeTable::new(&fs); + t.advance( + &mut fs, + vec![ + (status(), entries(&[("shipped", 3)])), + (priority(), entries(&[("high", 1)])), + ], + ) + .unwrap(); + t.advance(&mut fs, vec![(status(), entries(&[("shipped", 9)]))]) + .unwrap(); + let f = fs.seal(); + assert_eq!(t.code(&f, &status(), &vk("shipped")), Ok(9)); + assert_eq!(t.code(&f, &priority(), &vk("high")), Ok(1)); + assert_eq!(t.key(&f, &priority(), 1), Ok(&vk("high"))); +} diff --git a/crates/lance-graph-quack/src/bind.rs b/crates/lance-graph-quack/src/bind.rs index 91e160a23..a282abfc9 100644 --- a/crates/lance-graph-quack/src/bind.rs +++ b/crates/lance-graph-quack/src/bind.rs @@ -244,6 +244,16 @@ pub enum BindError { /// The descriptor's table. field: String, }, + /// [`Draft::bind_typed`]: a coded field the binder cannot witness. Fails + /// closed: a query over a code space nobody vouches for is not admitted. + Unwitnessed { + /// Field. + field: String, + }, + /// [`TypedQuery::admit`]: a code space this query was bound under has + /// since been renumbered, so its literal codes no longer mean what they + /// meant at bind time. + StaleCodeSpace, } impl Draft { @@ -286,6 +296,17 @@ impl Draft { /// /// The first [`BindError`]; nothing is minted or registered on failure. pub fn bind(&self, b: &dyn Binder) -> Result { + self.bind_with(b, &mut |_, _, _| {}) + } + + /// [`Draft::bind`], reporting `(table, lane, field name)` of every coded + /// field it binds a literal against. Calls the binder exactly as `bind` + /// does. + fn bind_with( + &self, + b: &dyn Binder, + on_code: &mut dyn FnMut(TableId, Col, &str), + ) -> Result { let t = b .table(&self.table) .ok_or_else(|| BindError::UnknownTable(self.table.clone()))?; @@ -301,6 +322,9 @@ impl Draft { } } let (leaf, f) = self.bind_pred(b, t, p)?; + if f.kind == FieldKind::Code { + on_code(t, f.col, &p.field); + } if let Some(valid) = f.validity { nullable.push((f.col, valid)); } @@ -357,6 +381,85 @@ impl Draft { } } +// ---- code-space admission -------------------------------------------------- + +/// A binder that owns code spaces and can vouch that they have not been +/// renumbered. Quack never interprets the witness: it only holds it from +/// [`Draft::bind_typed`] to [`TypedQuery::admit`] and hands it back. A +/// binder backed by a versioned store hands out that store's frame; one +/// without a store hands out whatever owner-local generation it keeps. +pub trait CodeWitness { + /// What the owner holds to recognise the code space it bound under. + type Witness: Clone; + /// The witness of the code space of the coded field `col` of `table` + /// now, or `None` if the owner cannot vouch for it. + fn code_witness(&self, table: TableId, col: Col) -> Option; + /// `Ok` iff the code space of `col` is still the one `held` witnessed. + /// + /// # Errors + /// + /// [`BindError::StaleCodeSpace`] if it has been renumbered since. + fn admit_code(&self, table: TableId, col: Col, held: &Self::Witness) -> Result<(), BindError>; +} + +/// A bound [`Query`] together with the witness of every code space its +/// literals were resolved in. The query is reachable only through +/// [`TypedQuery::admit`], which re-checks each witness against the owner. +#[derive(Debug, Clone)] +pub struct TypedQuery { + query: Query, + held: Vec<(TableId, Col, W)>, +} + +impl Draft { + /// [`Draft::bind`], plus a witness for every coded field the draft + /// compares. The bound query is identical to `bind`'s. + /// + /// # Errors + /// + /// Any error of `bind`, and [`BindError::Unwitnessed`] for a coded field + /// whose owner returns no witness. + /// + /// Witnesses are read after the literals are resolved. A binder that can + /// renumber a dictionary while this call runs must make `code` and + /// `code_witness` agree on one generation. + pub fn bind_typed( + &self, + b: &B, + ) -> Result, BindError> { + let mut fields: Vec<(TableId, Col, String)> = Vec::new(); + let query = self.bind_with(b, &mut |t, c, name| { + if !fields.iter().any(|(t2, c2, _)| *t2 == t && *c2 == c) { + fields.push((t, c, name.to_string())); + } + })?; + let mut held = Vec::with_capacity(fields.len()); + for (t, c, field) in fields { + let w = b + .code_witness(t, c) + .ok_or(BindError::Unwitnessed { field })?; + held.push((t, c, w)); + } + Ok(TypedQuery { query, held }) + } +} + +impl TypedQuery { + /// The query, iff every code space it was bound under is unchanged. + /// + /// # Errors + /// + /// [`BindError::StaleCodeSpace`] if the owner no longer vouches for any + /// one of them. + pub fn admit>(&self, b: &B) -> Result<&Query, BindError> { + for (t, c, w) in &self.held { + b.admit_code(*t, *c, w) + .map_err(|_| BindError::StaleCodeSpace)?; + } + Ok(&self.query) + } +} + // ---- schema: the same lifecycle for declarations --------------------------- /// An unbound table declaration: `CREATE TABLE WIDTH `. @@ -701,4 +804,171 @@ mod tests { Err(RegisterError::Refused { .. }) )); } + + // ---- bind_typed / admit ------------------------------------------------ + + /// An orders table whose `state` dictionary is renumbered by generation: + /// generation 1 has 3 = shipped, 7 = cancelled; generation 2 swaps them. + /// `n` is a plain `I32` lane and `tag` a coded lane the owner does not + /// vouch for. + struct Orders { + generation: Cell, + } + const LOGICAL: [&str; 4] = ["shipped", "cancelled", "cancelled", "cancelled"]; + impl Orders { + fn dict(&self) -> [(u32, &'static str); 2] { + if self.generation.get() == 1 { + [(3, "shipped"), (7, "cancelled")] + } else { + [(7, "shipped"), (3, "cancelled")] + } + } + /// The lane the owner publishes in the current generation. + fn lane(&self) -> Vec { + LOGICAL + .iter() + .map(|l| self.dict().iter().find(|(_, k)| k == l).unwrap().0) + .collect() + } + } + impl Binder for Orders { + fn table(&self, name: &str) -> Option { + (name == "orders").then_some(TableId(1)) + } + fn live(&self, _: TableId) -> Mask { + Mask(0) + } + fn field(&self, _: TableId, name: &str) -> Option { + let (col, kind) = match name { + "state" => (Col(0), FieldKind::Code), + "n" => (Col(1), FieldKind::I32), + "tag" => (Col(2), FieldKind::Code), + _ => return None, + }; + Some(BoundField { + col, + kind, + validity: None, + }) + } + fn code(&self, _: TableId, col: Col, literal: &str) -> Option { + match col { + Col(0) => self + .dict() + .iter() + .find(|(_, k)| *k == literal) + .map(|(c, _)| *c), + _ => Some(0), + } + } + } + impl CodeWitness for Orders { + type Witness = u32; + fn code_witness(&self, _: TableId, col: Col) -> Option { + (col == Col(0)).then(|| self.generation.get()) + } + fn admit_code(&self, _: TableId, _: Col, held: &u32) -> Result<(), BindError> { + if *held == self.generation.get() { + Ok(()) + } else { + Err(BindError::StaleCodeSpace) + } + } + } + + /// Rows matching `q` over a `state` lane (plane 0 = all four rows live). + fn count_state(q: &Query, lane: &[u32]) -> usize { + let n = lane.len(); + let live = vec![(1u64 << n) - 1]; + let lanes = [LaneRef::U32(lane)]; + let masks: [&[u64]; 1] = [&live]; + let planes = Planes { + n_rows: n, + masks: &masks, + lanes: &lanes, + }; + let p = lower(q).unwrap(); + let mut scratch = Scratch::for_program(&p, n).unwrap(); + match execute_into(&p, &planes, &Foreign::NONE, &mut scratch, Out::None).unwrap() { + Value::Count(c) => c, + other => panic!("count expected, got {other:?}"), + } + } + + /// q1 (the red oracle, ported): a query bound under generation 1 would + /// execute on generation-2 lanes and answer 3 where the logical answer + /// is 1. `admit` refuses it; a fresh bind answers the oracle. + #[test] + fn a_query_bound_before_a_renumbering_is_not_admitted_after_it() { + let o = Orders { + generation: Cell::new(1), + }; + let draft = table("orders").where_eq("state", "shipped").count(); + let oracle = LOGICAL.iter().filter(|l| **l == "shipped").count(); + assert_eq!(oracle, 1); + + let typed = draft.bind_typed(&o).unwrap(); + let q1 = typed.admit(&o).expect("fresh under its own generation"); + assert_eq!(count_state(q1, &o.lane()), oracle); + + o.generation.set(2); + assert_eq!(typed.admit(&o).err(), Some(BindError::StaleCodeSpace)); + // What admission prevents: the stale literal (code 3) on the + // renumbered lane counts the three cancelled rows. + let stale = draft.bind(&Orders { + generation: Cell::new(1), + }); + assert_eq!(count_state(&stale.unwrap(), &o.lane()), 3); + + let fresh = draft.bind_typed(&o).unwrap(); + let q2 = fresh.admit(&o).expect("bound under generation 2"); + assert_eq!(count_state(q2, &o.lane()), oracle); + } + + /// q2: a coded field the owner cannot witness refuses to bind typed. The + /// same draft still binds with `bind`. + #[test] + fn a_code_field_without_a_witness_is_refused() { + let o = Orders { + generation: Cell::new(1), + }; + let draft = table("orders").where_eq("tag", "x"); + assert!(draft.bind(&o).is_ok()); + assert_eq!( + draft.bind_typed(&o).err(), + Some(BindError::Unwitnessed { + field: "tag".into() + }) + ); + } + + /// q3: with the generation unchanged, `admit` returns exactly the query + /// `bind` produces, and holds one witness per coded lane however many + /// predicates read it. + #[test] + fn an_unchanged_generation_admits_the_query_bind_produces() { + let o = Orders { + generation: Cell::new(1), + }; + let draft = table("orders") + .where_eq("state", "shipped") + .where_(FieldRef::new("orders", "state").ne("cancelled")) + .where_eq("n", 5); + let typed = draft.bind_typed(&o).unwrap(); + assert_eq!(typed.held.len(), 1); + assert_eq!(typed.admit(&o).unwrap(), &draft.bind(&o).unwrap()); + } + + /// q4: a draft over no coded field holds no witness, so a renumbering of + /// the code space does not touch it. + #[test] + fn a_draft_without_coded_fields_is_unaffected_by_a_renumbering() { + let o = Orders { + generation: Cell::new(1), + }; + let typed = table("orders").where_eq("n", 5).bind_typed(&o).unwrap(); + assert!(typed.held.is_empty()); + o.generation.set(2); + assert!(typed.admit(&o).is_ok()); + } } From fba8e904fa03defeabfaaef3745b095c0566d4e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Nov 2026 23:43:13 +0000 Subject: [PATCH 2/2] board: add the D-SC semantic-continuity deliverables Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XAqKMMWgnUU9g7Ldyn6sbk --- .claude/board/STATUS_BOARD.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index ec3e0a219..9267decab 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,15 @@ +## D-SC — OGAR × DR × DT64 semantic continuity (2026-11-07) + +Execution admitted = meaning valid (OGAR/DT64) ∧ representation compatible (DR/source) ∧ access permitted now (IAM, owned by the OGAR auth session, EXO-206). Linear: EXO-202 (D, BLOCKED), EXO-204, EXO-205. + +| D-id | Deliverable | Status | +|---|---|---| +| D-SC-E1 | `MapTable::map_at` fenced as `diagnose_at` → `MapDiagnostic`; executable maps only via `map(frame)` | Shipped (#1981) | +| D-SC-E2a | dt64 `CodeTable` (versioned code space, advances the lanes' FrameStore) + quack `bind_typed` / `TypedQuery::admit` (`StaleCodeSpace`, `Unwitnessed`) | In PR | +| D-SC-E2b | per-owner adoption (`CamLabels`, dir-sim, sap): each real mutation path invalidates stale execution | Queued | +| D-SC-D | witness-only Activation + `SealedBasis` + historical typed equivalence; blocked on OGAR-P2 and evidence, not on rulings | Blocked | +| D-SC-H1 | coded and scaled facade queries (`ScaleMismatch`, `DomainMismatch`) | Queued | + ## D-LANESOCK — DR × DT64 property-to-lane socket (2026-11-06) | D-id | Deliverable | State |