From 296e8b7974c980704b023d8c15f5497a24bdf252 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 07:00:33 +0000 Subject: [PATCH 1/3] hotplug: being plugged in IS the declaration -- V3 for every plugged appid Operator, 2026-09-07: "plug and play already has all the domains, you could simply make the global schema for all appids already in plug-and-play pattern activate V3 and be silent about all others... local quad usage must mint any V3 settings in plug and play, regardless of the settings here... otherwise I will spend weeks until I remember that we changed it here wondering why quad 4x24 stopped working." MY OWN SEAM WAS THE LOCKSTEP IT REPLACED D-BLOCKS-HOTPLUG-1 retired a central BUILTIN_READ_MODES row per consumer because adding a frontend must not mean editing the substrate. What I built instead held `const LOCO_READ_MODES` -- ONE HARD-CODED ROW PER SEAT (0x1717 alone), answered only when the plug covered every declared seat, `&[]` otherwise. That is the same central table one level up: adding a frontend meant adding a row, and every consumer outside 0x17XX got no reading at all. Both failures are silent AND remote. A missing row breaks no build; it surfaces later as a V1 tail where V3 was expected. The quad (LegacyOutlier::WideTriple, G2 4x24) is a carving of the 12-byte content-blind payload, which exists as such only under a V3 tail -- so "somebody scoped the reading in lance-graph" and "quad 4x24 stopped working in medcare-rs" are the same event, weeks and one repo apart. THE &'static DECISION WAS THE ROOT CAUSE I typed read_modes as &'static and argued it as "plug-and-play at COMPILE time: a reading is looked up, not computed". The consequence I did not weigh: a &'static table cannot be built per plug, so the authority can only return a table it holds ALL of -- which forces all-or-nothing, which forces a per-seat table. An aesthetic constraint on a type silently dictated the architecture underneath it. Now owned, so the authority answers for exactly the ids a plug declared. THE RULE Every classid in a plug gets ReadMode::PLUG_AND_PLAY_V3 (new, in the contract, named so a session can find it). ReadMode::DEFAULT stays V1: it is the canon zero-fallback for classes nobody plugged, and it is unreachable from a hot-plug lookup. `concept_override` remains for genuine per-class deviations -- blockly's seat reads Bootstrap because it stores an ogar-loco body, not cognitive tenants -- and it is a short list of EXCEPTIONS, not a roster of participants: a forgotten entry means "no override", never "no reading". The ownership guard survived, but only because it was checked: deriving from the plug initially dropped the consumer check codex flagged on #1207. Restored as `palette_seat_owner` -- a CLAIMED seat is its owner's, an UNCLAIMED one is plug-and-play for whoever plugs it, so a new frontend at 0x1718 activates and reads V3 with no edit here. FALSIFIERS (7 tests, both guards disable-verified) * restoring palette-only scoping turns a_capability_consumer_outside_the_loco_domain_also_reads_v3 red -- a MedCare-shaped plug of 0x0901/0x0902, the case this exists for * dropping the impersonation guard turns another_consumer_cannot_activate_a_claimed_seat red * an_unplugged_concept_is_silent_not_v3 holds the other end: "V3 for all plugged appids" must not become "V3 for everything" PROCESS NOTE. The first run of disable 1 PASSED and I nearly recorded the guard as non-load-bearing. cargo fmt had reflowed the closure I was patching, so the replace matched nothing and the disable was a no-op. The re-run asserts the anchor exists before writing. A disable that does not apply is indistinguishable from a guard that does not matter -- the second such instance this session. Gates: contract 1323 lib tests, ogar 82 lib + 7 new, contract clippy -D warnings + fmt clean, ogar fmt clean, citation-decay 0 new, supersession index byte-identical. Board: E-PLUG-AND-PLAY-IS-THE-DECLARATION-NOT-A-TABLE-1. Pre-existing and untouched: 4 clippy doc errors in the workspace-EXCLUDED lance-graph-ogar (bridges/mod.rs, rbac_impl.rs), which no CI job scopes to. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011DN5odWu4eisdevH9bPWyz --- .claude/board/EPIPHANIES.md | 71 ++++ .../src/canonical_node.rs | 28 ++ crates/lance-graph-contract/src/hotplug.rs | 40 +- crates/lance-graph-ogar/src/lib.rs | 377 ++++++++++-------- 4 files changed, 330 insertions(+), 186 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 10238e80a..551a2f91f 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,74 @@ +## 2026-09-07 — E-PLUG-AND-PLAY-IS-THE-DECLARATION-NOT-A-TABLE-1 — my fix rebuilt the lockstep it was closing + +**Status:** FINDING, measured. Fixed in this PR (7 tests, both guards disable-verified). +**Confidence:** High — the failure mode is demonstrated by a test that goes red when the scoping is restored. + +**Operator ruling (2026-09-07):** *"plug and play already has all the domains, +you could simply make the global schema for all appids already in plug-and-play +pattern activate V3 and be silent about all others… local quad usage must mint +any V3 settings in plug and play, regardless of the settings here… otherwise I +will spend weeks until I remember that we changed it here wondering why quad +4x24 stopped working."* + +**The finding, and it is about my own two prior fixes.** +`D-BLOCKS-HOTPLUG-1` retired a central `BUILTIN_READ_MODES` row per consumer +because adding a frontend must not mean editing the substrate. The seam I built +to replace it held **`const LOCO_READ_MODES` — one hard-coded row per consumer +seat** (`0x1717` alone), answered only when the plug covered every declared +seat, and returned `&[]` otherwise. **That is the same central table, relocated +one level up.** Adding a frontend meant adding a row; every consumer outside +`0x17XX` got no reading at all. + +**Why that is worse than it sounds.** Both failures are silent AND remote. A +missing row does not break a build — it surfaces much later as a V1 tail where +V3 was expected. The quad (`LegacyOutlier::WideTriple`, G2 `4 × u24`) is a +carving of the 12-byte content-blind payload, which exists as such only under a +V3 tail; so "somebody scoped the reading in lance-graph" and "quad 4×24 stopped +working in medcare-rs" are the same event, weeks and one repo apart, with no +mechanical link between them. + +**The `&'static` decision was the root cause, and it was mine.** I typed +`Activation::read_modes` as `&'static` and argued it as "plug-and-play at +COMPILE time: a reading is looked up, not computed". The consequence I did not +weigh: a `&'static` table cannot be built per plug, so the authority can only +return a table it holds ALL of — which forces all-or-nothing, which forces a +per-seat table. **An aesthetic constraint on a type silently dictated the +architecture underneath it.** Now owned (`Vec`), so the authority answers for +exactly the ids a plug declared. + +**The rule now: being plugged in IS the declaration.** Every classid in a plug +gets [`ReadMode::PLUG_AND_PLAY_V3`] (V3 tail; `DEFAULT` stays V1 as the canon +zero-fallback for classes nobody plugged, and is unreachable from a hot-plug +lookup). `concept_override` remains for genuine per-class deviations — blockly's +seat reads `Bootstrap` because it stores an `ogar-loco` body, not cognitive +tenants — and it is *a short list of exceptions, not a roster of participants*: +a consumer absent from it is not absent from plug-and-play. The asymmetry is +the point — a forgotten entry means "no override", never "no reading". + +**The ownership guard survived the rewrite, but only because it was checked.** +Deriving readings from the plug initially dropped the consumer check codex +flagged on #1207, so an impostor could have activated blockly's seat. Restored +as `palette_seat_owner`: a CLAIMED seat is its owner's; an UNCLAIMED one is +plug-and-play for whoever plugs it. A new frontend at `0x1718` activates and +reads V3 with no edit here — which is the ruling — while `0x1717` stays +blockly's. + +**Falsifiers, both disable-verified.** Restoring the palette-only scoping turns +`a_capability_consumer_outside_the_loco_domain_also_reads_v3` (a MedCare-shaped +plug of `0x0901`/`0x0902`) red; dropping the impersonation guard turns +`another_consumer_cannot_activate_a_claimed_seat` red. The silence twin, +`an_unplugged_concept_is_silent_not_v3`, holds the other end: "V3 for all +plugged appids" must not become "V3 for everything". + +**Process note worth more than the fix.** The first run of disable 1 PASSED and +I nearly recorded the guard as non-load-bearing. `cargo fmt` had reflowed the +closure I was patching, so my `replace()` matched nothing and the disable was a +no-op — the code under test never changed. The re-run asserts the anchor exists +before writing. **A disable that does not apply is indistinguishable from a +guard that does not matter**, and this is the second time this session that a +disable silently failed to disable (the first: zeroing a constant whose guarded +quantity could go negative). + ## 2026-09-07 — E-A-DOC-COMMENT-IS-NOT-A-FAIL-CLOSED-MECHANISM-1 — hotplug could still land on V1 with a one-liner **Status:** FINDING, measured. Fixed in this PR (2 tests, disable-verified). diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index 818610d91..5d249e1a9 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -1375,6 +1375,34 @@ impl ReadMode { /// reading. When the POC ends, flip `value_schema` back to /// [`ValueSchema::Bootstrap`] HERE and in `ClassView` together (one revert, /// two sites — the test `read_mode_default_is_full_poc` guards the pairing). + /// The **plug-and-play** reading: what any hot-plugged appid reads as, + /// unless its authority declares an override. + /// + /// Operator, 2026-09-07: *"plug and play already has all the domains, you + /// could simply make the global schema for all appids already in + /// plug-and-play pattern activate V3 and be silent about all others"* — + /// and *"local quad usage must mint any V3 settings in plug and play, + /// regardless of the settings here."* + /// + /// **V3 is the answer for being plugged in at all, not for being listed + /// somewhere.** The alternative — a per-seat table in the authority with + /// one row per consumer — is the central lockstep `D-BLOCKS-HOTPLUG-1` + /// retired, rebuilt one level up: adding a frontend would again mean + /// editing a shared table, and a consumer whose row was missing would + /// silently lose its V3 tail *at a distance*, weeks from the edit that + /// caused it. That is the footgun this constant exists to remove. + /// + /// Differs from [`DEFAULT`](ReadMode::DEFAULT) in exactly one field — + /// the tail — because that is the field the ruling is about. `DEFAULT` + /// stays V1: it is the canon zero-fallback for a class nobody plugged, + /// and it is NOT reachable from a hot-plug lookup (an unplugged concept + /// yields `NoReadingFor`, never a defaulted reading). + pub const PLUG_AND_PLAY_V3: ReadMode = ReadMode { + tail_variant: TailVariant::V3, + value_schema: ValueSchema::Full, + edge_codec: EdgeCodecFlavor::CoarseOnly, + }; + pub const DEFAULT: ReadMode = ReadMode { tail_variant: TailVariant::V1, value_schema: ValueSchema::Full, diff --git a/crates/lance-graph-contract/src/hotplug.rs b/crates/lance-graph-contract/src/hotplug.rs index 068700f62..9719791ad 100644 --- a/crates/lance-graph-contract/src/hotplug.rs +++ b/crates/lance-graph-contract/src/hotplug.rs @@ -71,16 +71,22 @@ pub struct Activation { /// authority handed it — never by asking the canon registry about a class /// the canon does not own. /// - /// **`&'static` on purpose — this is plug-and-play at COMPILE time.** The - /// socket half is already const ([`HotPlug`] is one `const` per consumer, - /// `&'static [u16]` + `&'static [&'static str]`), and activation is by - /// Cargo presence: a build graph that pulls the authority crate gets the - /// real tables, one that does not cannot call [`CapabilityAuthority`] at - /// all. So the reading is a STATIC DESCRIPTOR the authority hands back — - /// a `const` table, no allocation, nothing resolved at runtime that was - /// not already decided by which crates are in the graph. `concepts` / - /// `capabilities` stay owned because they are derived (joined, sorted, - /// deduped); a reading is looked up, not computed. + /// **Owned, and per-plug — corrected 2026-09-07.** This was `&'static`, + /// argued as "plug-and-play at COMPILE time: a reading is looked up, not + /// computed". That argument had a consequence I did not weigh: a + /// `&'static` table cannot be built per plug, so the authority could only + /// hand back a table it already held ALL of, which forced an + /// all-or-nothing guard and, behind it, one hard-coded row per consumer + /// seat. That is the central lockstep `D-BLOCKS-HOTPLUG-1` retired, + /// rebuilt one level up — and its failure mode is the worst kind: a + /// consumer missing from the table keeps compiling and silently loses its + /// V3 tail, discoverable only weeks later and nowhere near the edit. + /// + /// Owned lets the authority answer for exactly the ids a plug declared, + /// derived from the plug rather than looked up in a shared list. Being + /// plugged in IS the declaration + /// ([`ReadMode::PLUG_AND_PLAY_V3`](crate::canonical_node::ReadMode::PLUG_AND_PLAY_V3)), + /// and an authority may still override any single concept. /// /// **Fails closed by MECHANISM, not by prose** (operator, 2026-09-07: /// *"don't silently enforce V1 fallback in hotplug, that's @@ -95,7 +101,7 @@ pub struct Activation { /// executor that mints no keys) has no reading to declare. What the type /// now guarantees is that *asking* for an absent one bangs instead of /// quietly yielding a V1 tail. - read_modes: &'static [(u16, crate::canonical_node::ReadMode)], + read_modes: Vec<(u16, crate::canonical_node::ReadMode)>, } impl Activation { @@ -106,7 +112,7 @@ impl Activation { pub fn new( concepts: Vec<(String, u16)>, capabilities: Vec, - read_modes: &'static [(u16, crate::canonical_node::ReadMode)], + read_modes: Vec<(u16, crate::canonical_node::ReadMode)>, ) -> Self { Self { concepts, @@ -148,8 +154,8 @@ impl Activation { /// slice, so scanning it and defaulting is once again expressible, and /// that is exactly what the lookup exists to avoid. #[must_use] - pub fn declared_readings(&self) -> &'static [(u16, crate::canonical_node::ReadMode)] { - self.read_modes + pub fn declared_readings(&self) -> &[(u16, crate::canonical_node::ReadMode)] { + &self.read_modes } } @@ -290,7 +296,7 @@ mod tests { plug.covered.iter().map(|s| (*s).to_string()).collect(), // This toy authority declares no reading — an authority that // has none says so, and asking it for one bangs. - &[], + Vec::new(), )) } } @@ -337,9 +343,7 @@ mod tests { value_schema: ValueSchema::Bootstrap, edge_codec: EdgeCodecFlavor::CoarseOnly, }; - const TABLE: &[(u16, ReadMode)] = &[(0x1717, V3_SEAT)]; - - let act = Activation::new(Vec::new(), Vec::new(), TABLE); + let act = Activation::new(Vec::new(), Vec::new(), vec![(0x1717, V3_SEAT)]); // Can-fire on the happy half: a declared seat resolves to its own // reading, not to some other row of the table. diff --git a/crates/lance-graph-ogar/src/lib.rs b/crates/lance-graph-ogar/src/lib.rs index cb31a4ae4..dcd1a9114 100644 --- a/crates/lance-graph-ogar/src/lib.rs +++ b/crates/lance-graph-ogar/src/lib.rs @@ -403,103 +403,114 @@ mod tests { } } -/// The storage READING for `ogar-loco`'s domain, handed back by -/// [`OgarAuthority`] on activation — **scoped to `0x17XX` only, deliberately** -/// (operator, 2026-09-07: *"additive scoped for loco blockly-rs for now, then -/// expand slowly"*). +use lance_graph_contract::canonical_node::{EdgeCodecFlavor, ReadMode, TailVariant, ValueSchema}; +use lance_graph_contract::hotplug::HotPlug; + +/// The reading for every classid a plug declares — **derived, not tabulated**. +/// +/// Operator, 2026-09-07: *"plug and play already has all the domains, you +/// could simply make the global schema for all appids already in plug-and-play +/// pattern activate V3 and be silent about all others"*, and *"local quad +/// usage must mint any V3 settings in plug and play, regardless of the +/// settings here."* +/// +/// **What this replaced, and why it was wrong.** The first cut held a +/// `const LOCO_READ_MODES` with ONE ROW per consumer seat (`0x1717` alone), +/// answered only when the plug covered every declared seat, and returned +/// `&[]` otherwise. Three faults, in increasing order of seriousness: +/// +/// 1. Adding a frontend meant adding a row to a shared table — the central +/// lockstep `D-BLOCKS-HOTPLUG-1` retired, rebuilt one level up. +/// 2. Every consumer outside `0x17XX` — `medcare-rs`'s six Health classids +/// among them — got NO reading at all, so a session asking for one hit +/// `NoReadingFor` and had to go hunting for a setting. +/// 3. Both failures are silent and REMOTE. A missing row does not break a +/// build; it surfaces much later as a V1 tail where V3 was expected — +/// *"otherwise I will spend weeks until I remember that we changed it +/// here wondering why quad 4x24 stopped working."* /// -/// A `const`, because hot-plug is plug-and-play at COMPILE time: which readings -/// exist is decided by which crates are in the build graph, not by anything -/// resolved at runtime. +/// **Now: being plugged in IS the declaration.** Every classid in the plug +/// gets [`ReadMode::PLUG_AND_PLAY_V3`], except where this authority +/// deliberately overrides one concept. Anything NOT plugged gets no entry — +/// silent, and `Activation::read_mode_for` bangs rather than defaulting. +/// +/// The quad (`LegacyOutlier::WideTriple`, G2 `4 × u24`) rides on this: it is a +/// carving of the 12-byte content-blind payload, which exists as such only +/// under a V3 tail. A plugged consumer therefore keeps its quad without +/// knowing this function exists. +fn plug_readings(plug: &HotPlug) -> Vec<(u16, ReadMode)> { + plug.classids + .iter() + .map(|&id| { + ( + id, + concept_override(id).unwrap_or(ReadMode::PLUG_AND_PLAY_V3), + ) + }) + .collect() +} + +/// Per-concept deviations from [`ReadMode::PLUG_AND_PLAY_V3`]. /// -/// **Why the reading lives here and not in `BUILTIN_READ_MODES`** -/// (D-BLOCKS-HOTPLUG-1). That registry *"holds only the canon builtins"* and -/// every entry in it is a canon DOMAIN. `0x17` is `ogar-loco`'s domain and -/// `0x1717` is `blockly-rs`'s per-frontend palette SEAT — one slot per -/// frontend. Registering each seat in the contract would make adding a -/// frontend an edit to `lance-graph-contract` plus a substrate recompile: the -/// central lockstep the retired `COUNT_FUSE` belonged to. The authority is -/// where a non-canon class's reading belongs, and this crate is where the -/// contract and OGAR already meet. +/// An override changes ONLY what a class genuinely reads differently; the +/// tail stays V3 for everything plugged, which is the ruling. `blockly-rs`'s +/// palette seat materialises no value tenants (it stores an `ogar-loco` +/// function body in the slab, not cognitive columns), so it reads +/// `Bootstrap` rather than `Full`. /// -/// Keyed by the CONCEPT half (`u16`). A consumer composes its own full `u32` -/// (`concept << 16 | its app prefix`) and reads with the mode it was handed — -/// it never asks the canon registry about a class the canon does not own. -const LOCO_READ_MODES: &[(u16, lance_graph_contract::canonical_node::ReadMode)] = &[( - // blockly-rs. `ValueSchema::Bootstrap` is CORRECT, not a placeholder: a - // stored `ogar-loco` function's 480-byte slab is the interleaved call - // lanes (`classid(4) + payload(12)` per 16-byte lane), so ZERO tenants - // materialise and the slab is wholly the class-resolved carve-out. - // `CoarseOnly` is the zero-fallback — a function node has no adjacency - // yet, block reserved and zeroed. - 0x1717, - lance_graph_contract::canonical_node::ReadMode { - tail_variant: lance_graph_contract::canonical_node::TailVariant::V3, - value_schema: lance_graph_contract::canonical_node::ValueSchema::Bootstrap, - edge_codec: lance_graph_contract::canonical_node::EdgeCodecFlavor::CoarseOnly, - }, -)]; - -/// The consumer each declared loco seat belongs to. A seat is one frontend's, -/// so activating it as somebody else is drift, not a permissive default — -/// without this the loco arm would bypass the expected-executor check that -/// `resolve_hotplug` applies on the capability path. -const fn loco_seat_consumer(classid: u16) -> Option<&'static str> { +/// This is a short list of exceptions, not a registry of participants: a +/// consumer absent from it is not absent from plug-and-play, it simply has +/// nothing unusual to say. That asymmetry is the point — the failure mode of +/// a forgotten entry is "no override", never "no reading". +const fn concept_override(classid: u16) -> Option { match classid { - 0x1717 => Some("blockly-abi"), + 0x1717 => Some(ReadMode { + tail_variant: TailVariant::V3, + value_schema: ValueSchema::Bootstrap, + edge_codec: EdgeCodecFlavor::CoarseOnly, + }), _ => None, } } -/// The reading for `plug`, or `&[]` when this authority declares none. -/// -/// Answers only when BOTH hold: -/// -/// 1. every plugged id is **DECLARED in [`LOCO_READ_MODES`]** — not merely in -/// the `0x17` domain; and -/// 2. the plug's `consumer` owns every one of those seats. +/// `true` when a classid is a consumer palette seat in the `ogar-loco` +/// `0x17` domain (`0x1717` and up; `0x1701`/`0x1702` are the substrate's own +/// node shapes and `0x1703`-`0x1716` its reserved headroom). /// -/// **Domain membership was the original guard and it was wrong** (caught in -/// review on #1207). `id >> 8 == 0x17` admits `0x1701`/`0x1702` — `ogar-loco`'s -/// OWN node shapes, which are not consumer seats — and admits an undeclared -/// seat like `0x1718`. Either way the whole table came back, so a plug asking -/// about `0x1701` was handed `0x1717`'s reading: not a partial answer but an -/// UNRELATED one, and the mixed-plug rule this function documents was violated -/// by its own code for the within-domain case (it only caught loco/non-loco -/// mixes). +/// A palette plug carries no capabilities, so it must not reach the +/// capability join — `resolve_hotplug` is pinned to answer `UnknownClassid` +/// for these ids by design. +const fn is_palette_seat(classid: u16) -> bool { + classid >= 0x1717 && (classid >> 8) == 0x17 +} + +/// The consumer a palette seat is KNOWN to belong to, when one is known. /// -/// A refused plug returns `&[]` and falls through to the capability join, -/// which fails closed for `0x17XX` — `resolve_hotplug` is pinned to answer -/// `UnknownClassid` for a palette id. +/// Deliberately a short list of CLAIMED seats, not a roster of participants. +/// `None` means "no consumer has claimed this seat", and an unclaimed seat is +/// plug-and-play for whoever plugs it — a new frontend at `0x1718` activates +/// and reads V3 with no edit here, which is the ruling. /// -/// **All-or-nothing, and that is a real constraint rather than laziness.** -/// [`Activation::read_modes`] is `&'static`, so a per-plug SUBSET cannot be -/// built at runtime without leaking; the honest options are the whole table or -/// none. Hence condition 1 is "the plugged set covers exactly the declared -/// seats". With one seat declared that means `[0x1717]` alone. Adding a second -/// seat therefore needs a per-id design (a static table per consumer, or -/// `read_modes` becoming owned) — the widening step, not a table row. -fn loco_read_modes_for( - consumer: &str, - classids: &[u16], -) -> &'static [(u16, lance_graph_contract::canonical_node::ReadMode)] { - let all_declared_and_owned = !classids.is_empty() - && classids - .iter() - .all(|&id| loco_seat_consumer(id) == Some(consumer)); - // …and the plug must cover every declared seat, since the answer is the - // whole table or nothing (see the doc comment). - let covers_every_seat = LOCO_READ_MODES - .iter() - .all(|(declared, _)| classids.contains(declared)); - - if all_declared_and_owned && covers_every_seat { - LOCO_READ_MODES - } else { - &[] +/// What the list still buys is the ownership guard codex flagged on #1207: a +/// seat somebody HAS claimed cannot be activated by a different consumer, so +/// the reading cannot be obtained by impersonating one. Without it "who owns +/// `0x1717`" would be answerable by simply asking. +const fn palette_seat_owner(classid: u16) -> Option<&'static str> { + match classid { + 0x1717 => Some("blockly-abi"), + _ => None, } } +/// The first plugged seat whose declared owner is somebody other than +/// `consumer`, if any. +fn impersonated_seat(consumer: &str, classids: &[u16]) -> Option { + classids + .iter() + .copied() + .find(|&id| matches!(palette_seat_owner(id), Some(owner) if owner != consumer)) +} + /// The generic hot-plug bridge (operator, 2026-07-07): "lance-graph-contract /// pulling into OGAR with a generic activation." The contract defines the /// zero-dep SOCKET ([`lance_graph_contract::hotplug`]); OGAR owns the data @@ -537,14 +548,21 @@ impl lance_graph_contract::hotplug::CapabilityAuthority for OgarAuthority { // This authority can therefore answer "no concepts, no capabilities, // and here is how your rows are read" without contradicting either // test — both are on `resolve_hotplug`, which is left untouched. - let loco = loco_read_modes_for(plug.consumer, plug.classids); - if !loco.is_empty() { + // + // A palette plug takes this arm: it is entirely `0x17XX` consumer + // seats, which carry no capabilities at all. + if !plug.classids.is_empty() && plug.classids.iter().all(|&id| is_palette_seat(id)) { // Fails closed on a lie: a palette plug has no capabilities to // cover, so claiming one is drift, not an empty-set no-op. if let Some(cap) = plug.covered.first() { return Err(ActivationDrift::Undeclared((*cap).into())); } - return Ok(Activation::new(Vec::new(), Vec::new(), loco)); + // …and on impersonation: a CLAIMED seat is its owner's. An + // unclaimed one is plug-and-play for whoever plugs it. + if impersonated_seat(plug.consumer, plug.classids).is_some() { + return Err(ActivationDrift::UnexpectedConsumer(plug.consumer.into())); + } + return Ok(Activation::new(Vec::new(), Vec::new(), plug_readings(plug))); } match resolve_hotplug(plug.consumer, plug.classids, plug.covered) { @@ -557,21 +575,13 @@ impl lance_graph_contract::hotplug::CapabilityAuthority for OgarAuthority { { return Err(drift); } - // Non-loco plugs get no reading yet (scoped, expanding - // slowly). Reached only when the loco arm above declined, so - // this is `&[]` by construction today — written as the call, - // not a literal, so widening the scope reaches here without a - // second edit. - // - // An empty table here is NOT a V1 fallback: a capability-only - // consumer mints no keys, and one that does ask for a reading - // gets `NoReadingFor` from `Activation::read_mode_for` rather - // than a defaulted V1 tail. - Ok(Activation::new( - concepts, - capabilities, - loco_read_modes_for(plug.consumer, plug.classids), - )) + // EVERY plugged classid gets its reading here, not just the + // `0x17XX` ones. A capability consumer (medcare-rs's six + // Health ids, tesseract-rs's OCR seats) is as plugged in as a + // palette, so it reads V3 too — that is the ruling, and it is + // what keeps a quad working without its author having to find + // this file. + Ok(Activation::new(concepts, capabilities, plug_readings(plug))) } Err(HotplugDrift::UnknownClassid(id)) => Err(ActivationDrift::UnknownClassid(id)), Err(HotplugDrift::NoCapabilitiesFor(id)) => Err(ActivationDrift::NoCapabilitiesFor(id)), @@ -652,9 +662,9 @@ mod hotplug_bridge_tests { /// D-BLOCKS-HOTPLUG-1: the storage READING rides the activation, scoped to /// `ogar-loco` (`0x17XX`) for now. #[cfg(test)] -mod loco_read_mode_arm { +mod plug_and_play_reading { use lance_graph_contract::canonical_node::{ - classid_read_mode, EdgeCodecFlavor, ReadMode, TailVariant, ValueSchema, + classid_read_mode, ReadMode, TailVariant, ValueSchema, }; use lance_graph_contract::hotplug::{ActivationDrift, CapabilityAuthority, HotPlug}; @@ -670,18 +680,13 @@ mod loco_read_mode_arm { fn a_palette_plug_activates_with_a_reading_and_no_capabilities() { let act = super::OgarAuthority .activate(&BLOCKLY) - .expect("loco plug activates"); - assert_eq!( - act.declared_readings(), - &[( - 0x1717u16, - ReadMode { - tail_variant: TailVariant::V3, - value_schema: ValueSchema::Bootstrap, - edge_codec: EdgeCodecFlavor::CoarseOnly, - } - )][..] - ); + .expect("palette plug activates"); + let mode = act.read_mode_for(0x1717).expect("its seat has a reading"); + assert_eq!(mode.tail_variant, TailVariant::V3); + // blockly is the one declared override: it stores an ogar-loco body in + // the slab, not cognitive tenants. + assert_eq!(mode.value_schema, ValueSchema::Bootstrap); + // A palette is not a capability concept; both arms are empty and that // is the correct answer, not a partial resolution. assert!(act.concepts.is_empty()); @@ -700,75 +705,111 @@ mod loco_read_mode_arm { #[test] fn the_canon_registry_still_does_not_know_the_palette_class() { assert_eq!(classid_read_mode(0x1717_1000), ReadMode::DEFAULT); - // …and DEFAULT genuinely differs from what the authority handed back, - // so the assertion above discriminates rather than being trivially - // true of every mode. assert_ne!(ReadMode::DEFAULT.tail_variant, TailVariant::V3); } - /// CAN STAY SILENT: a non-loco plug gets no reading. Without this the arm - /// could return `LOCO_READ_MODES` for everything and still look correct. - #[test] - fn a_non_loco_plug_gets_no_reading() { - assert!(super::loco_read_modes_for("blockly-abi", &[0x0805]).is_empty()); - assert!(super::loco_read_modes_for("blockly-abi", &[]).is_empty()); - // Mixed is silent too — a partial answer is worse than none, because - // silence at a call site is indistinguishable from "use the default". - assert!(super::loco_read_modes_for("blockly-abi", &[0x1717, 0x0805]).is_empty()); - // …and the loco id alone DOES resolve, so the mixed case fails on the - // mix, not because 0x1717 stopped working. - assert!(!super::loco_read_modes_for("blockly-abi", &[0x1717]).is_empty()); - } - - /// The three cases the ORIGINAL guard got wrong — it tested domain - /// membership (`id >> 8 == 0x17`) instead of declaration, so each of these - /// returned the whole table. Caught in review on #1207. + /// THE RULING: a seat nobody has claimed is plug-and-play. A new frontend + /// activates and reads V3 with NO edit to this file. /// - /// Each is paired with the `[0x1717]` positive above, so a version that - /// simply refused everything could not pass both. + /// This is the case the previous design got wrong: it held one hard-coded + /// row per seat and refused everything else, so a second frontend silently + /// lost its V3 tail until somebody remembered to add a row. #[test] - fn an_undeclared_loco_id_is_refused_even_though_it_is_in_the_domain() { - // 0x1701 / 0x1702 are ogar-loco's OWN node shapes: in-domain, but not - // consumer seats. Handing back 0x1717's reading for them is not a - // partial answer, it is an unrelated one. - assert!(super::loco_read_modes_for("blockly-abi", &[0x1701]).is_empty()); - assert!(super::loco_read_modes_for("blockly-abi", &[0x1702]).is_empty()); - // An undeclared seat is refused for the same reason. - assert!(super::loco_read_modes_for("blockly-abi", &[0x1718]).is_empty()); - // Anti-vacuity: all three ARE in the loco domain, so the old guard - // admitted every one of them. - for id in [0x1701u16, 0x1702, 0x1718] { - assert_eq!(id >> 8, 0x17, "fixture must exercise the old guard"); - } - } + fn an_unclaimed_seat_is_plug_and_play_for_whoever_plugs_it() { + let newcomer = HotPlug { + consumer: "some-future-frontend", + classids: &[0x1718], + covered: &[], + }; + let act = super::OgarAuthority + .activate(&newcomer) + .expect("an unclaimed seat activates"); + assert_eq!( + act.read_mode_for(0x1718).expect("reads V3"), + ReadMode::PLUG_AND_PLAY_V3 + ); + assert_eq!(ReadMode::PLUG_AND_PLAY_V3.tail_variant, TailVariant::V3); - /// A within-domain MIXED plug — declared seat plus undeclared id — is - /// refused. The old guard called this "all loco" and answered, violating - /// this module's own no-partial-answers rule for the within-domain case. - #[test] - fn a_declared_seat_mixed_with_an_undeclared_loco_id_is_refused() { - assert!(super::loco_read_modes_for("blockly-abi", &[0x1717, 0x1718]).is_empty()); - assert!(super::loco_read_modes_for("blockly-abi", &[0x1701, 0x1717]).is_empty()); + // Anti-vacuity: 0x1718 appears in NO table in this crate. If the + // reading came from a lookup rather than from being plugged, this + // could not resolve. + assert!(super::palette_seat_owner(0x1718).is_none()); + assert!(super::concept_override(0x1718).is_none()); } - /// A seat belongs to ONE consumer. Activating it as somebody else is drift, - /// not a permissive default — otherwise the loco arm would bypass the - /// expected-executor check `resolve_hotplug` applies on the capability path. + /// …and a CLAIMED seat still belongs to its owner. Restores the ownership + /// guard codex flagged on #1207, which the derive-don't-tabulate rewrite + /// would otherwise have dropped. #[test] - fn another_consumer_cannot_activate_blocklys_seat() { - assert!(super::loco_read_modes_for("scratch-abi", &[0x1717]).is_empty()); - assert!(super::loco_read_modes_for("", &[0x1717]).is_empty()); - // …and through the public surface, not just the helper: a wrong - // consumer falls through to the capability join, which fails closed - // for a palette id. + fn another_consumer_cannot_activate_a_claimed_seat() { let impostor = HotPlug { consumer: "scratch-abi", ..BLOCKLY }; assert!(matches!( super::OgarAuthority.activate(&impostor), - Err(ActivationDrift::UnknownClassid(0x1717)) + Err(ActivationDrift::UnexpectedConsumer(c)) if c == "scratch-abi" )); + // Two-sided on the same seat: its real owner still activates, so the + // guard discriminates rather than refusing 0x1717 outright. + assert!(super::OgarAuthority.activate(&BLOCKLY).is_ok()); + } + + /// THE MEDCARE CASE — the one this rewrite exists for. + /// + /// A capability consumer outside `0x17XX` is as plugged in as a palette, + /// so it reads V3 too. Under the previous design its activation carried NO + /// reading and a session asking for one hit `NoReadingFor` — the footgun + /// that surfaces weeks later as "why did quad 4x24 stop working", because + /// the G2 `4 × u24` carving is a reading of the 12-byte payload that only + /// exists under a V3 tail. + #[test] + fn a_capability_consumer_outside_the_loco_domain_also_reads_v3() { + let medcare = HotPlug { + consumer: "medcare-rs", + classids: &[0x0901, 0x0902], + covered: &[ + "register_patient", + "get_patient_record", + "list_patients", + "update_patient_access", + "add_diagnosis", + "get_diagnosis", + "list_diagnoses", + "delete_diagnosis", + ], + }; + let act = super::OgarAuthority + .activate(&medcare) + .expect("the health plug activates"); + + for id in [0x0901u16, 0x0902] { + assert_eq!( + act.read_mode_for(id).expect("every plugged id reads"), + ReadMode::PLUG_AND_PLAY_V3, + "0x{id:04X} must read V3 by being plugged in" + ); + } + // It really did go through the capability join, so this is the + // ordinary path and not a palette short-circuit. + assert!(!act.capabilities.is_empty()); + assert!(!act.concepts.is_empty()); + } + + /// CAN STAY SILENT: an UNPLUGGED concept still bangs. + /// + /// "V3 for all appids in plug-and-play" must not become "V3 for + /// everything". Without this half the lookup would answer for any id, and + /// a guard that fires on everything carries no information. + #[test] + fn an_unplugged_concept_is_silent_not_v3() { + let act = super::OgarAuthority.activate(&BLOCKLY).expect("activates"); + assert_eq!( + act.read_mode_for(0x0901), + Err(ActivationDrift::NoReadingFor(0x0901)), + "a concept this plug never declared has no reading" + ); + assert_eq!(act.declared_readings().len(), 1); } /// Fails closed on a lie: a palette plug claiming a capability is drift. From d09410f8931bb3b9ae539ce737c0f95eaf52e5b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 07:25:06 +0000 Subject: [PATCH 2/3] review: partition mixed plugs instead of choosing one arm Codex P2 on #1216, and CORRECT -- it catches this arm contradicting the very ruling it implements. The palette path ran only when EVERY plugged id was 0x17XX. So a mixed plug -- an unclaimed palette seat alongside ordinary capability ids, e.g. `[0x1718, 0x0901]` -- failed that test, went WHOLE to resolve_hotplug, and came back UnknownClassid(0x1718), because that resolver is pinned to refuse a palette id by design (ogar-vocab's own `a_palette_classid_does_not_resolve_as_a_hot_plug`). A consumer that legitimately has both could therefore not activate at all -- against "every plugged appid reads V3", which is the whole point of this arm. No consumer mixes today, which is exactly why it would have been found late. Fix: partition rather than choose. Palette seats and capability ids answer to different authorities, so they route separately and the results merge; the reading covers the whole plug either way, because it is DERIVED from the plug rather than looked up. The ownership check moved above the split so a claimed seat is its owner's on either path. +2 tests, the second guarding the hole the partition could have opened: * a_mixed_palette_and_capability_plug_activates_and_reads_v3_for_both -- 0x1718 + medcare's 0x0901/0x0902 all read PLUG_AND_PLAY_V3. Anti-vacuity: asserts 0x1718 IS a palette seat and the other two are NOT, so the fixture provably straddles the partition; and asserts the capability half really went through the join (non-empty capabilities, and 0x1718 absent from the resolved concepts) rather than the join being skipped wholesale. * a_mixed_plug_carrying_someone_elses_seat_is_still_refused -- 0x1717 in a mixed plug from the wrong consumer is still UnexpectedConsumer. Disable-verified: restoring `resolve_hotplug(plug.consumer, plug.classids, ..)` turns the mixed test red, 8 passed 1 failed. Anchor asserted before patching, per this session's own lesson that a disable which does not apply is indistinguishable from a guard that does not matter. Gates: contract 1323 lib tests, ogar 84 lib (9 in this module), contract clippy -D warnings clean, fmt clean both crates, citation-decay 0 new. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011DN5odWu4eisdevH9bPWyz --- crates/lance-graph-ogar/src/lib.rs | 103 ++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 9 deletions(-) diff --git a/crates/lance-graph-ogar/src/lib.rs b/crates/lance-graph-ogar/src/lib.rs index dcd1a9114..666a5cae7 100644 --- a/crates/lance-graph-ogar/src/lib.rs +++ b/crates/lance-graph-ogar/src/lib.rs @@ -549,23 +549,40 @@ impl lance_graph_contract::hotplug::CapabilityAuthority for OgarAuthority { // and here is how your rows are read" without contradicting either // test — both are on `resolve_hotplug`, which is left untouched. // - // A palette plug takes this arm: it is entirely `0x17XX` consumer - // seats, which carry no capabilities at all. - if !plug.classids.is_empty() && plug.classids.iter().all(|&id| is_palette_seat(id)) { + // PARTITION, do not choose (codex P2 on #1216). An earlier cut took + // the palette arm only when EVERY id was `0x17XX`, so a mixed plug — + // an unclaimed palette seat alongside ordinary capability ids, e.g. + // `[0x1718, 0x0901]` — went whole to `resolve_hotplug` and came back + // `UnknownClassid(0x1718)`. A consumer that legitimately has both + // could not activate at all, which contradicts the ruling this arm + // exists to implement: EVERY plugged appid reads V3. + // + // The two id kinds answer to different authorities, so they are + // routed separately and the results merged; the reading covers the + // whole plug either way, because it is derived from the plug. + let (palette, capability): (Vec, Vec) = + plug.classids.iter().partition(|&&id| is_palette_seat(id)); + + // A CLAIMED seat is its owner's, whichever arm it arrives on. An + // unclaimed one is plug-and-play for whoever plugs it. + if impersonated_seat(plug.consumer, &palette).is_some() { + return Err(ActivationDrift::UnexpectedConsumer(plug.consumer.into())); + } + + // Pure-palette plug: no capability ids at all, so the join is not + // consulted (it is pinned to refuse `0x17XX`). + if !palette.is_empty() && capability.is_empty() { // Fails closed on a lie: a palette plug has no capabilities to // cover, so claiming one is drift, not an empty-set no-op. if let Some(cap) = plug.covered.first() { return Err(ActivationDrift::Undeclared((*cap).into())); } - // …and on impersonation: a CLAIMED seat is its owner's. An - // unclaimed one is plug-and-play for whoever plugs it. - if impersonated_seat(plug.consumer, plug.classids).is_some() { - return Err(ActivationDrift::UnexpectedConsumer(plug.consumer.into())); - } return Ok(Activation::new(Vec::new(), Vec::new(), plug_readings(plug))); } - match resolve_hotplug(plug.consumer, plug.classids, plug.covered) { + // Capability ids go to the join; palette seats were removed above so + // they cannot make it refuse the whole plug. + match resolve_hotplug(plug.consumer, &capability, plug.covered) { Ok((concepts, capabilities)) => { let concepts: Vec<(String, u16)> = concepts .into_iter() @@ -796,6 +813,74 @@ mod plug_and_play_reading { assert!(!act.concepts.is_empty()); } + /// A MIXED plug — an unclaimed palette seat alongside capability ids — + /// activates, and every id gets its reading. + /// + /// Codex P2 on #1216. The arm used to take the palette path only when + /// EVERY id was `0x17XX`, so a mixed plug went whole to + /// `resolve_hotplug`, which is pinned to refuse a palette id — the + /// consumer got `UnknownClassid` and could not activate at all. That + /// contradicts the ruling this arm implements: every plugged appid reads + /// V3. + /// + /// Anti-vacuity: the palette id is asserted to be one, and the capability + /// ids are asserted NOT to be, so the fixture provably straddles the + /// partition rather than being a capability-only plug in disguise. + #[test] + fn a_mixed_palette_and_capability_plug_activates_and_reads_v3_for_both() { + let mixed = HotPlug { + consumer: "medcare-rs", + classids: &[0x1718, 0x0901, 0x0902], + covered: &[ + "register_patient", + "get_patient_record", + "list_patients", + "update_patient_access", + "add_diagnosis", + "get_diagnosis", + "list_diagnoses", + "delete_diagnosis", + ], + }; + assert!( + super::is_palette_seat(0x1718), + "fixture straddles the split" + ); + assert!(!super::is_palette_seat(0x0901)); + assert!(!super::is_palette_seat(0x0902)); + + let act = super::OgarAuthority + .activate(&mixed) + .expect("a mixed plug must activate, not bang on its palette half"); + + for id in [0x1718u16, 0x0901, 0x0902] { + assert_eq!( + act.read_mode_for(id).expect("every plugged id reads"), + ReadMode::PLUG_AND_PLAY_V3, + "0x{id:04X} must read V3 by being plugged in" + ); + } + // The capability half really went through the join, so the palette id + // was removed from it rather than the join being skipped wholesale. + assert!(!act.capabilities.is_empty()); + assert!(act.concepts.iter().all(|(_, id)| *id != 0x1718)); + } + + /// …and a CLAIMED seat is still refused on the mixed path, so the + /// partition did not open a hole around the ownership guard. + #[test] + fn a_mixed_plug_carrying_someone_elses_seat_is_still_refused() { + let impostor = HotPlug { + consumer: "medcare-rs", + classids: &[0x1717, 0x0901], + covered: &["register_patient"], + }; + assert!(matches!( + super::OgarAuthority.activate(&impostor), + Err(ActivationDrift::UnexpectedConsumer(c)) if c == "medcare-rs" + )); + } + /// CAN STAY SILENT: an UNPLUGGED concept still bangs. /// /// "V3 for all appids in plug-and-play" must not become "V3 for From b28b963aabb97903cf2c38f19bd3528ebe58ea9c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 07:42:57 +0000 Subject: [PATCH 3/3] review: PLUG_AND_PLAY_V3 was inserted between DEFAULT and its doc comment CodeRabbit minor on #1216, and CORRECT -- worse than its description. My anchor-based insert targeted `pub const DEFAULT: ReadMode = ReadMode {` without checking what preceded that line. A 20-line doc block did. So the insertion landed BETWEEN DEFAULT's doc comment and DEFAULT itself: * DEFAULT's entire doc -- the zero-fallback rationale, the RESERVE-DON'T- RECLAIM note, and the TEMPORARY 2026-06-15 POC block with its "flip value_schema back to Bootstrap HERE and in ClassView together" instruction -- reattached to PLUG_AND_PLAY_V3. * DEFAULT was left undocumented. * The orphaned text says `tail_variant = V1` while sitting on a constant whose tail is V3: directly contradictory public API docs, and the POC flip instruction pointed at the wrong constant, which is the part that could have caused a real mis-edit later. Fix: move the whole PLUG_AND_PLAY_V3 block (doc + value) to AFTER the complete DEFAULT item, so each doc attaches to its own constant. Also removed the blank line the move left between DEFAULT's doc and DEFAULT -- a doc comment separated from its item by a blank line does not attach. SECOND TIME THIS SESSION. The same mistake hit OgarAuthority on #1207 (`missing documentation for a struct`, fixed in 26ed7a8f9) -- there the compiler caught it because a lint fired; here nothing did, because the doc still attached to SOMETHING. Rule for the next insertion: when anchoring on an item declaration, check whether the line above is `///`; if so the anchor is inside another item's documentation, not before it. Verified: cargo doc -p lance-graph-contract emits 168 warnings, byte-identical to the count on the unmodified tree, and none in the touched line range. Gates: contract 1323 lib tests, ogar 84 lib, contract clippy -D warnings clean, fmt clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011DN5odWu4eisdevH9bPWyz --- crates/lance-graph-contract/src/canonical_node.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index 5d249e1a9..87545aec6 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -1375,6 +1375,12 @@ impl ReadMode { /// reading. When the POC ends, flip `value_schema` back to /// [`ValueSchema::Bootstrap`] HERE and in `ClassView` together (one revert, /// two sites — the test `read_mode_default_is_full_poc` guards the pairing). + pub const DEFAULT: ReadMode = ReadMode { + tail_variant: TailVariant::V1, + value_schema: ValueSchema::Full, + edge_codec: EdgeCodecFlavor::CoarseOnly, + }; + /// The **plug-and-play** reading: what any hot-plugged appid reads as, /// unless its authority declares an override. /// @@ -1403,12 +1409,6 @@ impl ReadMode { edge_codec: EdgeCodecFlavor::CoarseOnly, }; - pub const DEFAULT: ReadMode = ReadMode { - tail_variant: TailVariant::V1, - value_schema: ValueSchema::Full, - edge_codec: EdgeCodecFlavor::CoarseOnly, - }; - /// The **OSINT / Palantir-Gotham** read-mode ([`NodeGuid::CLASSID_OSINT`]): /// a *hot* entity graph — [`ValueSchema::Cognitive`] (Meta + Qualia + /// Fingerprint + Energy + Plasticity + EntityType, for live NARS reasoning)