From f73802d0f537208f3938cf9052efe17f0f16d538 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 17:58:57 +0000 Subject: [PATCH 1/5] plan: v3-mandatory-hotplug-reading-v1 (D-V3M-0) Operator ruling 2026-10-10: V3 mandatory; the reading comes only from the plug (HotPlug -> OgarAuthority -> Activation::resolve_for_context) and the slab declaration; lockstep tables deprecated; a concept is an immutable address and names in other languages are labels. Plan W1-W6 retires classid_read_mode / BUILTIN_READ_MODES, which selects V3 by the 0x1000 marker and falls back to V1 for an unknown classid. Caller inventory across lance-graph, q2, MedCare-rs and OGAR included; stored 0x1000 keys stay readable. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019fbZ92dFKQ5BJHrA3YcH9A --- .claude/board/INTEGRATION_PLANS.md | 6 ++ .claude/board/STATUS_BOARD.md | 14 ++++ ...2026-10-10-v3-mandatory-hotplug-reading.md | 21 ++++++ .claude/board/entries/README.md | 3 +- .../plans/v3-mandatory-hotplug-reading-v1.md | 69 +++++++++++++++++++ 5 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 .claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md create mode 100644 .claude/plans/v3-mandatory-hotplug-reading-v1.md diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index 01d164a86..d4946abb5 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,9 @@ +## 2026-10-10 (2) — v3-mandatory-hotplug-reading-v1 — the reading comes from the plug, never from the classid value → `.claude/plans/v3-mandatory-hotplug-reading-v1.md` + +**Status:** ACTIVE (D-V3M-0..6). Operator DECISION 2026-10-10: V3 mandatory, hotplug.rs + OGAR plug-and-play is the only reading path, lockstep deprecated, concepts are immutable addresses and language names are labels. + +- Retires `classid_read_mode` / `BUILTIN_READ_MODES` (selects V3 by the `0x1000` marker; V1 fallback for unknown ids) in six waves; stored `0x1000` keys stay readable. + ## 2026-10-10 — cross-glove-business-parity-v1 — Odoo × SAP over one Quack algebra → `.claude/plans/cross-glove-business-parity-v1.md` **Status:** PROPOSAL (D-XGP-0..7). No code authorized. Addendum to `.claude/plans/sap-glove-quack-v1.md` (baseline, committed verbatim; W0–W6 preserved). **Corrected same day (plan §C):** the contract bullet below is superseded. The landing is shipped `OntologyRegistry` attribute rows plus a CATS `impl Binder` (missing) behind a registry front; no new contract module. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 1f6b056de..b31d39d1d 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,17 @@ +## D-V3M — V3 mandatory; reading from the plug only (2026-10-10) + +Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`. + +| D-id | Deliverable | Status | Gate | +|---|---|---|---| +| **D-V3M-0** | Ruling + plan + caller inventory | In PR | — | +| **D-V3M-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | Queued | new form returns the handed reading even where `classid_read_mode` disagrees | +| **D-V3M-2** | Canon domain readings move into `OgarAuthority::concept_override` | Queued | `read_mode_for(concept)` == today's `classid_read_mode()` per domain | +| **D-V3M-3** | Consumers move (lance-graph, q2, MedCare); `classid_read_mode` deprecated | Queued | no production caller left | +| **D-V3M-4** | No new mints at classview `0x1000` or `0x0000`–`0x000C` as meaning | Blocked on O1 | — | +| **D-V3M-5** | Stored legacy keys read via `SlabDeclaration`; V3 keys leave `BUILTIN_READ_MODES` | Queued | corpus proof | +| **D-V3M-6** | No V1 default for an unknown classid; `guid-v3-tail` not a gate | Queued | unknown classid → `NoReadingFor` | + ## D-XGP — Cross-glove business parity: Odoo × SAP over one Quack algebra (2026-10-10) Plan: `plans/cross-glove-business-parity-v1.md`. Board entry: `entries/2026-10-10-d-xgp-0-cross-glove-inventory.md`. diff --git a/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md b/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md new file mode 100644 index 000000000..59bb65452 --- /dev/null +++ b/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md @@ -0,0 +1,21 @@ +# D-V3M-0 — V3 mandatory; the reading comes from the plug; concepts are immutable addresses (2026-10-10) + +**Status:** DECISION (operator, 2026-10-10). Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`. + +> "Yes V3 mandatory, everything else in regards to capabilities hotplug.rs plug and play > ogar pattern making sure lockstep is deprecated" +> +> "Any concept is immutable. That's why Ontologies exist. Different languages are a label. The concept needs to be immutable adress" + +- **V3 is mandatory:** no V1 mint, no silent V1 fallback. +- **One resolution path:** the reading comes from `HotPlug` → `OgarAuthority` → `Activation::resolve_for_context` plus the slab's `SlabDeclaration`. Lockstep tables are deprecated. +- **A concept is an immutable address.** Names in other languages ("Stundenzettel", "TimeSheet", "Zeiterfassung") are labels of it. +- **The classview fossils retire** (`0x1000` V3 marker, per-app render prefixes). Keys already stored with them stay readable. +- **Residue to remove:** `classid_read_mode` / `BUILTIN_READ_MODES` (`ISS-CLASSID-READ-MODE-IS-A-SECOND-RESOLUTION-PATH`). It selects V3 by the `0x1000` marker and falls back to V1 for an unknown classid. Callers: 4 contract modules, 4 lance-graph crates, q2 osint-bake/geo, MedCare cohorts, OGAR osm/ro/dismech/loco (plan table). +- **Waves W1–W6:** + - W1: the contract takes the reading as a parameter. + - W2: the canon domains' readings move into `concept_override`. + - W3: consumers move, then `#[deprecated]`. + - W4: stop minting at the fossil classviews. + - W5: legacy reads go through `SlabDeclaration`. + - W6: no V1 default. +- **Open (operator):** O1, the classview of a new canon mint once `0x1000` retires; O2, which WoA row "Stundenzettel" labels. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 45ba6ed4e..3cf97f40d 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,11 +25,12 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -297 entries, 2026-08-06 .. 2026-10-10. +298 entries, 2026-08-06 .. 2026-10-10. | date | entry id | finding | file | |---|---|---|---| | 2026-10-10 | `D-XGP-8` | | [2026-10-10-woa-anchors-timesheet.md](2026-10-10-woa-anchors-timesheet.md) | +| 2026-10-10 | `D-V3M-0` | | [2026-10-10-v3-mandatory-hotplug-reading.md](2026-10-10-v3-mandatory-hotplug-reading.md) | | 2026-10-10 | `render-classid-reversal-audit` | | [2026-10-10-render-classid-reversal-audit.md](2026-10-10-render-classid-reversal-audit.md) | | 2026-10-10 | `E-W2-SPACE-AXIS-CUSTOM-SPACE-LEAVES-THE-CLASSID-1` | W2's space axis is decided: fixed spaces 0–3 stay in the classid, custom spaces move to the payload or edge | [2026-10-10-e-w2-space-axis-custom-space-leaves-the-classid-1.md](2026-10-10-e-w2-space-axis-custom-space-leaves-the-classid-1.md) | | 2026-10-10 | `D-XGP-3-BILLABLE` | | [2026-10-10-d-xgp-3-billable-converted.md](2026-10-10-d-xgp-3-billable-converted.md) | diff --git a/.claude/plans/v3-mandatory-hotplug-reading-v1.md b/.claude/plans/v3-mandatory-hotplug-reading-v1.md new file mode 100644 index 000000000..ade2c9f8b --- /dev/null +++ b/.claude/plans/v3-mandatory-hotplug-reading-v1.md @@ -0,0 +1,69 @@ +# v3-mandatory-hotplug-reading-v1 — the reading comes from the plug, never from the classid value + +**Status:** ACTIVE (D-V3M-0..6). DECISION by the operator, 2026-10-10. W0 (this plan + the ruling record) lands first; W1 onward are code. + +## The ruling (operator, 2026-10-10, verbatim) + +> "Yes V3 mandatory, everything else in regards to capabilities hotplug.rs plug and play > ogar pattern making sure lockstep is deprecated" +> +> "Any concept is immutable. That's why Ontologies exist. Different languages are a label. The concept needs to be immutable adress" + +Earlier the same day: *"only the hotplug.rs and Ontology slab metadata envelope define if 32 + 96 is in storage or 128"* and *"1000 V3 Marker is a fossil too"* (plan `cross-glove-business-parity-v1.md` §C.9.3). + +What it means, in rules: + +1. **V3 is mandatory.** No new key is minted with a V1 tail, and no reading falls back to V1 silently. +2. **One resolution path.** How a row is read (tail, value schema, edge codec, `Facet96` vs `Register128`) comes from the plug (`HotPlug` → `CapabilityAuthority::activate` → `Activation::resolve_for_context`) plus the slab's own `SlabDeclaration`. Nothing else selects a reading. Lockstep tables (a central list every consumer must be added to) are deprecated. +3. **A concept is an immutable address.** Its id never changes meaning. This is what ontologies are for. +4. **Names in different languages are labels of one concept, not separate concepts.** "Stundenzettel", "TimeSheet", "Zeiterfassung" and "TimeEntry" are labels; the concept is the address (here `0x0103` / `ogit.WorkOrder:TimeSheet`). A label table maps names to the address; it never mints. +5. **The classview fossils retire.** The `0x1000` V3 marker and the per-app render prefixes `0x0000`–`0x000C` stop carrying meaning. Keys already written with them stay readable. + +## Where things stand (measured 2026-10-10 on `origin/main` `feb21972`) + +**The plug path already exists.** `lance-graph-ogar::plug_readings` (`lib.rs:452`) gives every plugged concept `ReadMode::PLUG_AND_PLAY_V3` unless `concept_override` (`lib.rs:477`) names an exception. `Activation::read_mode_for` fails closed (`NoReadingFor`), never defaulting. + +**The second path that must go:** `canonical_node::classid_read_mode(classid)` (`canonical_node.rs:1813`) looks the full `u32` up in `BUILTIN_READ_MODES` (`:1743`). It has two faults: + +- the V3 reading is chosen by the `0x1000` marker in the classview bits (the `*_V3` keys); +- an unknown classid falls through to `ReadMode::DEFAULT`, which is a **V1** tail. That is exactly the silent V1 fallback the 2026-09-07 ruling forbade on the plug path. + +**Callers of `classid_read_mode` / the `*_V3` constants** (`grep -rn`, then each file opened): + +| repo | file | use | +|---|---|---| +| lance-graph | `contract/src/ocr.rs:105,124` | `to_node_row(classid, identity)` reads schema + tail by classid | +| lance-graph | `contract/src/aiwar.rs:81,119` | mints OSINT rows on `CLASSID_OSINT_V3`, tail by classid | +| lance-graph | `contract/src/nan_projection.rs:167` | the non-`_resolved` variants look the schema up by classid | +| lance-graph | `contract/src/soa_graph.rs:204,457` | `DomainSpec` consts on `*_V3` classids, tail by classid | +| lance-graph | `contract/src/hhtl.rs`, `selection.rs`, `ogar_codebook.rs`, `classid_scan.rs` | tests / docs / legacy-form classification | +| lance-graph | `callcenter/src/graph_table.rs`, `deepnsm-v2/src/promote.rs`, `lance-graph/tests/canonical_witness_identity_probe.rs`, `contract/tests/v3_mint_reachability_probe.rs` | mint or read on `*_V3` | +| lance-graph | `lance-graph-ogar/src/lib.rs:735` | a test pinning that the old path yields V1 for an unplugged id | +| q2 | `osint-bake/src/bin/{fma,body}.rs`, `tools/body-soa-wire` | bake FMA on `CLASSID_FMA_V3`, tail by classid | +| q2 | `geo/src/bso2.rs` | `CLASSID_GEO_V3 = 0x0F01_1000` | +| q2 | `cockpit-server/src/osint_classview.rs:782` | a literal `0x0701_1000` key in a fixture | +| MedCare-rs | `medcare-cohorts/src/graph_feed.rs:25`, `spog_masks.rs` | `CLASSID_CPIC_V3`; mask tests over `0x0701_1000` | +| OGAR | `ogar-osm` (`CLASSVIEW_V3_SUBSTRATE`), `ogar-ro`, `ogar-dismech`, `ogar-loco` tests | mint concepts at classview `0x1000` | + +**Persisted data carrying `0x1000` keys:** q2's FMA / body `.soa` bakes (`0x0A01_1000`), osm slabs (`0x0F01_1000`), the MedCare medication lane (`CPIC_V3`). These must stay readable (rule 5). + +## Waves + +- [x] **W0 — D-V3M-0: record.** This plan, the ruling (`entries/2026-10-10-v3-mandatory-hotplug-reading.md`), `INTEGRATION_PLANS.md`, `STATUS_BOARD.md`. +- [ ] **W1 — D-V3M-1: contract takes the reading as a parameter.** `ocr`, `aiwar`, `nan_projection`, `soa_graph` get variants that take a `ReadMode` (or a `ResolvedReading`) instead of looking the classid up. `DomainSpec` carries its reading. The classid-lookup forms stay, documented as legacy, until W3. Additive: no consumer breaks. + - Falsifier: a test per module that the new form yields the reading it was handed, including one where the handed reading differs from what `classid_read_mode` would return (so a caller that silently re-looks-up fails). +- [ ] **W2 — D-V3M-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved. + - Falsifier: `read_mode_for(concept)` equals today's `classid_read_mode()` for each canon domain, so the move changes no reading. +- [ ] **W3 — D-V3M-3: consumers move.** lance-graph callcenter / deepnsm-v2 / probes, then q2 osint-bake + geo, MedCare cohorts. Each takes its reading from its `Activation`. Then `classid_read_mode` gets `#[deprecated]`. +- [ ] **W4 — D-V3M-4: stop minting at the fossil classviews.** New mints stop using classview `0x1000` (OGAR osm / ro / dismech / loco, q2 geo) and new code stops depending on `APP_PREFIX` as a classview value. Which classview a new mint uses instead is an open operator question (O1). +- [ ] **W5 — D-V3M-5: legacy reads.** Stored `0x1000` and pre-flip keys are read through a `SlabDeclaration`, not through `BUILTIN_READ_MODES`. The table's V3 keys are removed once no persisted slab needs them (corpus proof, as for the existing `*_LEGACY` aliases). +- [ ] **W6 — D-V3M-6: V1 cannot be reached by default.** `ReadMode::DEFAULT` stops being the fallback for an unknown classid (it becomes `NoReadingFor`, as on the plug path), and `guid-v3-tail` stops being a feature gate. + +## Open questions (operator) + +- **O1.** With the `0x1000` marker retired, what classview does a new mint of a canon domain use: `0x0000` (shared canonical core), or an app-owned value? +- **O2.** Does WoA's `ogit.WorkOrder:TimeSheet` resolve to `0x0103`, with "Stundenzettel" as its label, instead of today's `TimesheetActivity` (plan `cross-glove-business-parity-v1.md` §C.9.5)? Rule 4 settles that the German name is a label; it does not settle which WoA row the label names. + +## What this plan does not do + +- It does not re-key stored data. Rule 5 keeps old keys readable. +- It does not change the `domain:appid` layout (§C.9.4 audit: do not reverse `render_classid`). From cef3c8083ffcba7682bbb69784047f7c8c8d6c02 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 18:03:51 +0000 Subject: [PATCH 2/5] contract: take the reading as a parameter (D-V3M-1) ocr::to_node_row_with, aiwar::aiwar_node_rows_with, soa_graph::{project_snapshot_with, nearest_anchor_with} and nan_projection::project_energy_nonfinite_plugged take the reading the caller's plug resolved instead of looking the classid up in BUILTIN_READ_MODES. The plugged NaN sweep fails closed: an unplugged concept is NoReadingFor, never a V1 default. The classid-lookup forms keep their behaviour and delegate. Each new form has a test on a classid the lookup misreads (V1/Full default), so a form that silently re-looks-up fails. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019fbZ92dFKQ5BJHrA3YcH9A --- crates/lance-graph-contract/src/aiwar.rs | 36 +++++++- .../src/nan_projection.rs | 83 ++++++++++++++++++- crates/lance-graph-contract/src/ocr.rs | 57 +++++++++++-- crates/lance-graph-contract/src/soa_graph.rs | 66 ++++++++++++++- 4 files changed, 232 insertions(+), 10 deletions(-) diff --git a/crates/lance-graph-contract/src/aiwar.rs b/crates/lance-graph-contract/src/aiwar.rs index 42d212248..aa828e2b0 100644 --- a/crates/lance-graph-contract/src/aiwar.rs +++ b/crates/lance-graph-contract/src/aiwar.rs @@ -13,7 +13,7 @@ //! `GraphSnapshot` to the Quadro-2 visual. Run it on the real graph with //! `cargo run -p lance-graph-contract --example aiwar_family_poc`. -use crate::canonical_node::{classid_read_mode, EdgeBlock, NodeGuid, NodeRow}; +use crate::canonical_node::{classid_read_mode, EdgeBlock, NodeGuid, NodeRow, ReadMode}; use crate::literal_graph::LiteralGraph; use std::collections::{BTreeMap, BTreeSet}; @@ -81,7 +81,19 @@ pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { let osint_classid = NodeGuid::CLASSID_OSINT_V3; #[cfg(not(feature = "guid-v3-tail"))] let osint_classid = NodeGuid::CLASSID_OSINT; + aiwar_node_rows_with(graph, osint_classid, classid_read_mode(osint_classid)) +} +/// [`aiwar_node_rows`] with the classid and reading supplied by the caller, +/// the reading resolved from its plug (`Activation::read_mode_for`) rather +/// than looked up by classid. The form new code uses (plan +/// `v3-mandatory-hotplug-reading-v1`, D-V3M-1); the lookup in +/// [`aiwar_node_rows`] is legacy. +pub fn aiwar_node_rows_with( + graph: &LiteralGraph, + osint_classid: u32, + mode: ReadMode, +) -> Vec { let view = AiwarClassView::from_graph(graph); let ids = graph.all_node_ids(); let fam_of = @@ -116,7 +128,7 @@ pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { // Masked to u16 — fits both the V3 basin tail and the V1 fallback // (POC cardinalities are tiny: category count + node count). key: NodeGuid::mint_for( - classid_read_mode(osint_classid).tail_variant, + mode.tail_variant, osint_classid, 0, 0, @@ -213,4 +225,24 @@ mod tests { assert_eq!(row.value, [0u8; 480], "head-only: value slab stays zero"); } } + + /// D-V3M-1: `aiwar_node_rows_with` reproduces `aiwar_node_rows` when handed + /// the same reading, and follows a different reading when handed one. + #[cfg(feature = "guid-v3-tail")] + #[test] + fn with_form_follows_the_handed_reading() { + use crate::canonical_node::{EdgeCodecFlavor, TailVariant, ValueSchema}; + let g = ingest_aiwar_json(SAMPLE).unwrap(); + let keys = |rows: &[NodeRow]| rows.iter().map(|r| r.key).collect::>(); + let legacy = aiwar_node_rows(&g); + let same = aiwar_node_rows_with(&g, NodeGuid::CLASSID_OSINT_V3, ReadMode::OSINT_V3); + assert_eq!(keys(&same), keys(&legacy)); + let v1 = ReadMode { + tail_variant: TailVariant::V1, + value_schema: ValueSchema::Cognitive, + edge_codec: EdgeCodecFlavor::CoarseOnly, + }; + let other = aiwar_node_rows_with(&g, NodeGuid::CLASSID_OSINT_V3, v1); + assert_ne!(keys(&other), keys(&legacy)); + } } diff --git a/crates/lance-graph-contract/src/nan_projection.rs b/crates/lance-graph-contract/src/nan_projection.rs index 3db893ee6..0059f24c9 100644 --- a/crates/lance-graph-contract/src/nan_projection.rs +++ b/crates/lance-graph-contract/src/nan_projection.rs @@ -52,6 +52,7 @@ //! [`classid_read_mode`]: crate::canonical_node::classid_read_mode use crate::canonical_node::{classid_read_mode, NodeRow, ValueSchema, ValueTenant}; +use crate::hotplug::{Activation, ActivationDrift}; /// `true` iff an `f32` bit pattern is non-finite (Inf or NaN): the exponent /// field is all-ones. No float materialised. @@ -151,6 +152,18 @@ pub fn energy_all_finite_resolved(rows: &[NodeRow], schema: ValueSchema) -> bool /// Split `rows` into maximal runs of equal classid, resolving each run's /// value schema once. Yields `(start index, run, schema)`. fn schema_runs(rows: &[NodeRow]) -> impl Iterator { + classid_runs(rows).map(|(at, run)| { + ( + at, + run, + classid_read_mode(run[0].key.classid()).value_schema, + ) + }) +} + +/// Split `rows` into maximal non-empty runs of equal classid. Yields +/// `(start index, run)`. +fn classid_runs(rows: &[NodeRow]) -> impl Iterator { let mut start = 0usize; core::iter::from_fn(move || { if start >= rows.len() { @@ -164,10 +177,40 @@ fn schema_runs(rows: &[NodeRow]) -> impl Iterator Result { + let mut report = NanReport::default(); + for (at, run) in classid_runs(rows) { + let schema = act + .resolve_tenant_reading(run[0].key, None)? + .read_mode + .value_schema; + let r = project_energy_nonfinite_resolved(run, schema); + report.total += r.total; + report.skipped += r.skipped; + report + .nonfinite + .extend(r.nonfinite.into_iter().map(|i| i + at as u32)); + } + Ok(report) +} + /// Project a batch of canonical boards onto the NaN-detection surface by reading /// each one's `Energy` tenant — schema-gated per row (see module docs). Read-only; /// returns the indices of non-finite boards among those actually inspected. @@ -376,4 +419,42 @@ mod tests { // the FMA rows alone are skipped, not read assert!(energy_all_finite(&[rows[0], rows[2]])); } + + /// D-V3M-1: the plugged form reads each run under the plug's reading. The + /// classid is unknown to the lookup, which answers `Full` (has `Energy`) + /// and so flags the NaN; the plug declares `Bootstrap` (no `Energy`), so + /// the plugged form skips it. An unplugged concept is an error, never a + /// default reading. + #[test] + fn plugged_form_reads_under_the_plug_and_refuses_unplugged_rows() { + use crate::canonical_node::{EdgeCodecFlavor, ReadMode, TailVariant}; + let classid = 0x1718_0000; + let rows = [board_with_classid(classid, f32::NAN)]; + assert_eq!(project_energy_nonfinite(&rows).nonfinite, vec![0]); + + let mode = |value_schema| ReadMode { + tail_variant: TailVariant::V3, + value_schema, + edge_codec: EdgeCodecFlavor::CoarseOnly, + }; + let act = |schema| Activation::new(Vec::new(), Vec::new(), vec![(0x1718, mode(schema))]); + + let skipped = + project_energy_nonfinite_plugged(&rows, &act(ValueSchema::Bootstrap)).unwrap(); + assert_eq!((skipped.skipped, skipped.count()), (1, 0)); + + let flagged = + project_energy_nonfinite_plugged(&rows, &act(ValueSchema::Cognitive)).unwrap(); + assert_eq!(flagged.nonfinite, vec![0]); + + let unplugged = Activation::new( + Vec::new(), + Vec::new(), + vec![(0x1719, mode(ValueSchema::Full))], + ); + assert_eq!( + project_energy_nonfinite_plugged(&rows, &unplugged), + Err(ActivationDrift::NoReadingFor(0x1718)) + ); + } } diff --git a/crates/lance-graph-contract/src/ocr.rs b/crates/lance-graph-contract/src/ocr.rs index bafe43f8b..ed3dee943 100644 --- a/crates/lance-graph-contract/src/ocr.rs +++ b/crates/lance-graph-contract/src/ocr.rs @@ -1,7 +1,7 @@ //! OCR contract. Zero-dep. use crate::canonical_node::{ - classid_read_mode, EdgeBlock, NodeGuid, NodeRow, ValueTenant, VALUE_SLAB_LEN, + classid_read_mode, EdgeBlock, NodeGuid, NodeRow, ReadMode, ValueTenant, VALUE_SLAB_LEN, }; use core::future::Future; @@ -99,10 +99,17 @@ impl<'a> LayoutBlock<'a> { /// geometry live in an external content store keyed by `identity`). The node /// is the *identity that points to* the OCR content, never the content's /// register. - /// - /// [`ReadMode`]: crate::canonical_node::ReadMode pub fn to_node_row(&self, classid: u32, identity: u32) -> NodeRow { - let schema = classid_read_mode(classid).value_schema; + self.to_node_row_with(classid, identity, classid_read_mode(classid)) + } + + /// [`to_node_row`](Self::to_node_row) with the reading supplied by the + /// caller, resolved from its plug (`Activation::read_mode_for` / + /// `Activation::resolve_for_context`) rather than looked up by classid. + /// This is the form new code uses (plan `v3-mandatory-hotplug-reading-v1`, + /// D-V3M-1); the classid lookup in `to_node_row` is legacy. + pub fn to_node_row_with(&self, classid: u32, identity: u32, mode: ReadMode) -> NodeRow { + let schema = mode.value_schema; let mut value = [0u8; VALUE_SLAB_LEN]; if schema.has(ValueTenant::EntityType) { @@ -121,7 +128,7 @@ impl<'a> LayoutBlock<'a> { // drives the key layout — never a hardcoded `new` (ISS-V1-TAIL-RESIDUE). // Minting HEEL/HIP/TWIG + family is the OGAR follow-up. key: NodeGuid::mint_for( - classid_read_mode(classid).tail_variant, + mode.tail_variant, classid, 0, 0, @@ -289,4 +296,44 @@ mod tests { // Both presets are layout-preserving — riding either needs no ENVELOPE_LAYOUT_VERSION bump. assert!(compressed.is_layout_preserving() && full.is_layout_preserving()); } + + /// D-V3M-1: `to_node_row_with` mints with the tail and value schema it is + /// handed. The classid is unknown to the lookup, which would answer the + /// V1 / Full default; the handed reading is V3 / Bootstrap. + #[test] + fn to_node_row_with_follows_the_handed_reading() { + use crate::canonical_node::{classid_read_mode, EdgeCodecFlavor, TailVariant}; + let classid = 0x1718_0000; + let looked_up = classid_read_mode(classid); + assert_eq!(looked_up.value_schema, ValueSchema::Full); + let mode = ReadMode { + tail_variant: TailVariant::V3, + value_schema: ValueSchema::Bootstrap, + edge_codec: EdgeCodecFlavor::CoarseOnly, + }; + let blk = heading("Invoice", 0.97); + let row = blk.to_node_row_with(classid, 0x42, mode); + assert_eq!( + row.key, + NodeGuid::mint_for( + TailVariant::V3, + classid, + 0, + 0, + 0, + 0, + NodeGuid::FAMILY_DEFAULT, + 0x42 + ) + ); + assert!( + row.value.iter().all(|&b| b == 0), + "Bootstrap writes no tenant" + ); + let legacy = blk.to_node_row(classid, 0x42); + assert!( + legacy.value.iter().any(|&b| b != 0), + "the lookup's Full schema writes tenants" + ); + } } diff --git a/crates/lance-graph-contract/src/soa_graph.rs b/crates/lance-graph-contract/src/soa_graph.rs index ad70cda45..1f0879b06 100644 --- a/crates/lance-graph-contract/src/soa_graph.rs +++ b/crates/lance-graph-contract/src/soa_graph.rs @@ -240,13 +240,24 @@ fn identity_of(guid: &NodeGuid, tail: TailVariant) -> u32 { /// edges. Touches ONLY the 32-byte head of each row (`key` + `edges`); never the /// value slab. pub fn project_snapshot(rows: &[NodeRow], domain: &DomainSpec) -> GraphSnapshot { + project_snapshot_with(rows, domain, domain_tail(domain)) +} + +/// [`project_snapshot`] with the tail supplied by the caller, taken from the +/// reading its plug resolved (`Activation::read_mode_for(..)?.tail_variant`) +/// rather than looked up by classid. The form new code uses (plan +/// `v3-mandatory-hotplug-reading-v1`, D-V3M-1). +pub fn project_snapshot_with( + rows: &[NodeRow], + domain: &DomainSpec, + tail: TailVariant, +) -> GraphSnapshot { // codex P1: a classid IS the class — project only rows of THIS domain, so a // mixed-class board can't leak other domains' nodes/edges into the view. let domain_rows: Vec<&NodeRow> = rows .iter() .filter(|r| r.key.classid() == domain.classid) .collect(); - let tail = domain_tail(domain); // family → member count, and a COLLISION-AWARE family-low-byte → family map. // codex P1: with >256 families two ids can share a low byte; a duplicate @@ -387,12 +398,21 @@ pub struct AnchorHop { /// tree walk: smaller hops ⇔ deeper shared `classid_lo·HEEL·HIP·TWIG` prefix. /// Ranking (nearest anchor) is what callers use; the absolute value is even. pub fn nearest_anchor(rows: &[NodeRow], domain: &DomainSpec) -> Vec { + nearest_anchor_with(rows, domain, domain_tail(domain)) +} + +/// [`nearest_anchor`] with the tail supplied by the caller (see +/// [`project_snapshot_with`]). +pub fn nearest_anchor_with( + rows: &[NodeRow], + domain: &DomainSpec, + tail: TailVariant, +) -> Vec { // codex P1: only rank rows of THIS domain (classid IS the class). let domain_rows: Vec<&NodeRow> = rows .iter() .filter(|r| r.key.classid() == domain.classid) .collect(); - let tail = domain_tail(domain); // Representative HHTL path per anchor family (first member encountered). let mut anchor_paths: Vec<(u32, NiblePath)> = Vec::new(); for row in &domain_rows { @@ -759,4 +779,46 @@ mod tests { }; assert_eq!(key(&a), key(&b)); } + + /// D-V3M-1: the `_with` forms use the tail they are handed, not the classid + /// lookup. The classid here is unknown to `BUILTIN_READ_MODES`, so the + /// lookup answers V1; the plug says V3. Only the handed tail decodes the V3 + /// basin family correctly. + #[cfg(feature = "guid-v3-tail")] + #[test] + fn with_forms_use_the_handed_tail_not_the_classid_lookup() { + use crate::canonical_node::classid_read_mode; + let classid = 0x1718_0000; + assert_eq!(classid_read_mode(classid).tail_variant, TailVariant::V1); + let dom = DomainSpec { + classid, + name: "plugged", + anchor_families: &[], + in_family_edge: "a", + out_family_edge: "b", + member_edge: "m", + }; + let g = NodeGuid::mint_for(TailVariant::V3, classid, 1, 0, 0, 0xAAAA, 0xBBBB, 0xCCCC); + let rows = [NodeRow { + key: g, + edges: EdgeBlock::default(), + value: [0u8; 480], + }]; + let family = |snap: &GraphSnapshot| { + snap.nodes + .iter() + .filter(|n| n.kind == "Family") + .flat_map(|n| n.props.iter()) + .find(|(k, _)| k == "family") + .map(|(_, v)| v.clone()) + }; + let plugged = project_snapshot_with(&rows, &dom, TailVariant::V3); + assert_eq!(family(&plugged).as_deref(), Some("00bbbb")); + let looked_up = project_snapshot(&rows, &dom); + assert_ne!(family(&looked_up), family(&plugged)); + assert_eq!( + nearest_anchor_with(&rows, &dom, TailVariant::V3).len(), + nearest_anchor(&rows, &dom).len() + ); + } } From 94fe22224a8e3d44230830e9c0b833aaa2a9c2b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 18:05:24 +0000 Subject: [PATCH 3/5] board: D-V3M-1 shipped in #1464 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019fbZ92dFKQ5BJHrA3YcH9A --- .claude/board/STATUS_BOARD.md | 4 ++-- .claude/plans/v3-mandatory-hotplug-reading-v1.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index b31d39d1d..fac4a7ccd 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -4,8 +4,8 @@ Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`. | D-id | Deliverable | Status | Gate | |---|---|---|---| -| **D-V3M-0** | Ruling + plan + caller inventory | In PR | — | -| **D-V3M-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | Queued | new form returns the handed reading even where `classid_read_mode` disagrees | +| **D-V3M-0** | Ruling + plan + caller inventory | In PR (#1464) | — | +| **D-V3M-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | In PR (#1464; 4 `_with`/`_plugged` forms, disable runs 4/4 red) | new form returns the handed reading even where `classid_read_mode` disagrees | | **D-V3M-2** | Canon domain readings move into `OgarAuthority::concept_override` | Queued | `read_mode_for(concept)` == today's `classid_read_mode()` per domain | | **D-V3M-3** | Consumers move (lance-graph, q2, MedCare); `classid_read_mode` deprecated | Queued | no production caller left | | **D-V3M-4** | No new mints at classview `0x1000` or `0x0000`–`0x000C` as meaning | Blocked on O1 | — | diff --git a/.claude/plans/v3-mandatory-hotplug-reading-v1.md b/.claude/plans/v3-mandatory-hotplug-reading-v1.md index ade2c9f8b..3317dc3cb 100644 --- a/.claude/plans/v3-mandatory-hotplug-reading-v1.md +++ b/.claude/plans/v3-mandatory-hotplug-reading-v1.md @@ -49,8 +49,9 @@ What it means, in rules: ## Waves - [x] **W0 — D-V3M-0: record.** This plan, the ruling (`entries/2026-10-10-v3-mandatory-hotplug-reading.md`), `INTEGRATION_PLANS.md`, `STATUS_BOARD.md`. -- [ ] **W1 — D-V3M-1: contract takes the reading as a parameter.** `ocr`, `aiwar`, `nan_projection`, `soa_graph` get variants that take a `ReadMode` (or a `ResolvedReading`) instead of looking the classid up. `DomainSpec` carries its reading. The classid-lookup forms stay, documented as legacy, until W3. Additive: no consumer breaks. +- [x] **W1 — D-V3M-1: contract takes the reading as a parameter.** `ocr`, `aiwar`, `nan_projection`, `soa_graph` get variants that take a `ReadMode` (or a `ResolvedReading`) instead of looking the classid up. `DomainSpec` carries its reading. The classid-lookup forms stay, documented as legacy, until W3. Additive: no consumer breaks. - Falsifier: a test per module that the new form yields the reading it was handed, including one where the handed reading differs from what `classid_read_mode` would return (so a caller that silently re-looks-up fails). + - **Shipped:** `ocr::LayoutBlock::to_node_row_with`, `aiwar::aiwar_node_rows_with`, `soa_graph::{project_snapshot_with, nearest_anchor_with}`, `nan_projection::project_energy_nonfinite_plugged` (resolves each run through `Activation::resolve_tenant_reading`; an unplugged concept is `NoReadingFor`). Tests use classid `0x1718_0000`, which the lookup reads as V1 / `Full`; each is red when its form is changed to re-look-up (disable runs, 4/4). - [ ] **W2 — D-V3M-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved. - Falsifier: `read_mode_for(concept)` equals today's `classid_read_mode()` for each canon domain, so the move changes no reading. - [ ] **W3 — D-V3M-3: consumers move.** lance-graph callcenter / deepnsm-v2 / probes, then q2 osint-bake + geo, MedCare cohorts. Each takes its reading from its `Activation`. Then `classid_read_mode` gets `#[deprecated]`. From 9f5d3dfe8a59363c86812cf6c66dbd0d393dc302 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 18:07:43 +0000 Subject: [PATCH 4/5] contract: document the 0x1000 marker and classid_read_mode as deprecated, kept Operator 2026-10-10: "1000 isn't hurting, just document and Mark it as deprecated. No need to force evict." Doc notes on the five CLASSID_*_V3 constants and on classid_read_mode point new code at the plug reading; existing keys and callers keep working. A doc note rather than #[deprecated], which would fail every existing caller under -D warnings. Plan W4 done (documentation), W5 reworded: legacy keys keep resolving, no forced removal. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019fbZ92dFKQ5BJHrA3YcH9A --- .claude/board/STATUS_BOARD.md | 4 +- ...2026-10-10-v3-mandatory-hotplug-reading.md | 2 + .../plans/v3-mandatory-hotplug-reading-v1.md | 8 ++-- .../src/canonical_node.rs | 37 +++++++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index fac4a7ccd..f0e73e07d 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -8,8 +8,8 @@ Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`. | **D-V3M-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | In PR (#1464; 4 `_with`/`_plugged` forms, disable runs 4/4 red) | new form returns the handed reading even where `classid_read_mode` disagrees | | **D-V3M-2** | Canon domain readings move into `OgarAuthority::concept_override` | Queued | `read_mode_for(concept)` == today's `classid_read_mode()` per domain | | **D-V3M-3** | Consumers move (lance-graph, q2, MedCare); `classid_read_mode` deprecated | Queued | no production caller left | -| **D-V3M-4** | No new mints at classview `0x1000` or `0x0000`–`0x000C` as meaning | Blocked on O1 | — | -| **D-V3M-5** | Stored legacy keys read via `SlabDeclaration`; V3 keys leave `BUILTIN_READ_MODES` | Queued | corpus proof | +| **D-V3M-4** | `0x1000` / render prefixes documented as deprecated, kept (no eviction) | In PR (#1464; doc notes on `CLASSID_*_V3` + `classid_read_mode`) | — | +| **D-V3M-5** | Stored legacy keys keep resolving; a `SlabDeclaration` may override for its own data; no forced removal | Queued | — | | **D-V3M-6** | No V1 default for an unknown classid; `guid-v3-tail` not a gate | Queued | unknown classid → `NoReadingFor` | ## D-XGP — Cross-glove business parity: Odoo × SAP over one Quack algebra (2026-10-10) diff --git a/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md b/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md index 59bb65452..e5e1221d6 100644 --- a/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md +++ b/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md @@ -19,3 +19,5 @@ - W5: legacy reads go through `SlabDeclaration`. - W6: no V1 default. - **Open (operator):** O1, the classview of a new canon mint once `0x1000` retires; O2, which WoA row "Stundenzettel" labels. + +**Refined (operator, 2026-10-10):** *"1000 isn't hurting, just document and Mark it as deprecated. No need to force evict."* The `0x1000` marker and `classid_read_mode` are documented as deprecated and kept; stored keys and existing callers are not migrated by force. Done as doc notes, not `#[deprecated]`, which would fail every existing caller under `-D warnings`. diff --git a/.claude/plans/v3-mandatory-hotplug-reading-v1.md b/.claude/plans/v3-mandatory-hotplug-reading-v1.md index 3317dc3cb..43aefd143 100644 --- a/.claude/plans/v3-mandatory-hotplug-reading-v1.md +++ b/.claude/plans/v3-mandatory-hotplug-reading-v1.md @@ -16,7 +16,7 @@ What it means, in rules: 2. **One resolution path.** How a row is read (tail, value schema, edge codec, `Facet96` vs `Register128`) comes from the plug (`HotPlug` → `CapabilityAuthority::activate` → `Activation::resolve_for_context`) plus the slab's own `SlabDeclaration`. Nothing else selects a reading. Lockstep tables (a central list every consumer must be added to) are deprecated. 3. **A concept is an immutable address.** Its id never changes meaning. This is what ontologies are for. 4. **Names in different languages are labels of one concept, not separate concepts.** "Stundenzettel", "TimeSheet", "Zeiterfassung" and "TimeEntry" are labels; the concept is the address (here `0x0103` / `ogit.WorkOrder:TimeSheet`). A label table maps names to the address; it never mints. -5. **The classview fossils retire.** The `0x1000` V3 marker and the per-app render prefixes `0x0000`–`0x000C` stop carrying meaning. Keys already written with them stay readable. +5. **The classview fossils are deprecated, not evicted** (operator, 2026-10-10: *"1000 isn't hurting, just document and Mark it as deprecated. No need to force evict"*). The `0x1000` V3 marker and the per-app render prefixes `0x0000`–`0x000C` stop carrying meaning for new work. Keys, constants and callers that already use them keep working; nothing is migrated by force. ## Where things stand (measured 2026-10-10 on `origin/main` `feb21972`) @@ -55,13 +55,13 @@ What it means, in rules: - [ ] **W2 — D-V3M-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved. - Falsifier: `read_mode_for(concept)` equals today's `classid_read_mode()` for each canon domain, so the move changes no reading. - [ ] **W3 — D-V3M-3: consumers move.** lance-graph callcenter / deepnsm-v2 / probes, then q2 osint-bake + geo, MedCare cohorts. Each takes its reading from its `Activation`. Then `classid_read_mode` gets `#[deprecated]`. -- [ ] **W4 — D-V3M-4: stop minting at the fossil classviews.** New mints stop using classview `0x1000` (OGAR osm / ro / dismech / loco, q2 geo) and new code stops depending on `APP_PREFIX` as a classview value. Which classview a new mint uses instead is an open operator question (O1). -- [ ] **W5 — D-V3M-5: legacy reads.** Stored `0x1000` and pre-flip keys are read through a `SlabDeclaration`, not through `BUILTIN_READ_MODES`. The table's V3 keys are removed once no persisted slab needs them (corpus proof, as for the existing `*_LEGACY` aliases). +- [x] **W4 — D-V3M-4: document the fossils as deprecated.** Done in `lance-graph-contract`: the five `CLASSID_*_V3` constants and `classid_read_mode` carry a "Deprecated, kept" doc note. A doc note, not `#[deprecated]`: the attribute would turn every existing caller red under `-D warnings`, which is the forced migration the ruling rules out. OGAR (`ogar-osm` `CLASSVIEW_V3_SUBSTRATE`, ro, dismech, loco) and q2 `geo` get the same note when next touched; existing mints stay. +- [ ] **W5 — D-V3M-5: legacy reads stay.** Stored `0x1000` and pre-flip keys keep resolving through `BUILTIN_READ_MODES`; no forced removal. A slab may additionally declare its reading (`SlabDeclaration`), which wins for its own data. Removing a table key is optional and only after nothing reads it. - [ ] **W6 — D-V3M-6: V1 cannot be reached by default.** `ReadMode::DEFAULT` stops being the fallback for an unknown classid (it becomes `NoReadingFor`, as on the plug path), and `guid-v3-tail` stops being a feature gate. ## Open questions (operator) -- **O1.** With the `0x1000` marker retired, what classview does a new mint of a canon domain use: `0x0000` (shared canonical core), or an app-owned value? +- **O1.** What classview should a NEW mint of a canon domain use instead of `0x1000`: `0x0000` (shared canonical core), or an app-owned value? Not blocking: existing mints keep `0x1000`. - **O2.** Does WoA's `ogit.WorkOrder:TimeSheet` resolve to `0x0103`, with "Stundenzettel" as its label, instead of today's `TimesheetActivity` (plan `cross-glove-business-parity-v1.md` §C.9.5)? Rule 4 settles that the German name is a label; it does not settle which WoA row the label names. ## What this plan does not do diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index d690b0fc2..901bd67f3 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -109,6 +109,12 @@ impl NodeGuid { /// [`classid_concept_domain`](crate::ogar_codebook::classid_concept_domain) /// routes [`Osint`](crate::ogar_codebook::ConceptDomain::Osint) off the /// canon half. Resolves to [`ReadMode::OSINT_V3`]. + /// + /// **Deprecated, kept (operator, 2026-10-10):** the `0x1000` classview + /// marker is a fossil. V3 is mandatory and the reading comes from the plug + /// (`Activation::resolve_for_context`), not from this value. Existing keys + /// and callers keep working and nothing is evicted; new code should not + /// give classview `0x1000` a meaning (plan `v3-mandatory-hotplug-reading-v1`). #[cfg(feature = "guid-v3-tail")] pub const CLASSID_OSINT_V3: u32 = 0x0701_1000; /// Pre-flip stored form of [`CLASSID_OSINT_V3`] (marker HIGH, canon @@ -123,6 +129,12 @@ impl NodeGuid { /// [`classid_concept_domain`](crate::ogar_codebook::classid_concept_domain) /// routes [`Anatomy`](crate::ogar_codebook::ConceptDomain::Anatomy). /// Resolves to [`ReadMode::FMA_V3`] (same cold `Compressed` model as legacy FMA). + /// + /// **Deprecated, kept (operator, 2026-10-10):** the `0x1000` classview + /// marker is a fossil. V3 is mandatory and the reading comes from the plug + /// (`Activation::resolve_for_context`), not from this value. Existing keys + /// and callers keep working and nothing is evicted; new code should not + /// give classview `0x1000` a meaning (plan `v3-mandatory-hotplug-reading-v1`). #[cfg(feature = "guid-v3-tail")] pub const CLASSID_FMA_V3: u32 = 0x0A01_1000; /// Pre-flip stored form of [`CLASSID_FMA_V3`] — read-only legacy alias. @@ -149,6 +161,12 @@ impl NodeGuid { /// why the value model is [`ValueSchema::Compressed`] (a fixed reference frame, /// not a hot lifecycle); Phase 2 shapes the V3 tenants — gene expression as the /// coordinate *value* — on top. + /// + /// **Deprecated, kept (operator, 2026-10-10):** the `0x1000` classview + /// marker is a fossil. V3 is mandatory and the reading comes from the plug + /// (`Activation::resolve_for_context`), not from this value. Existing keys + /// and callers keep working and nothing is evicted; new code should not + /// give classview `0x1000` a meaning (plan `v3-mandatory-hotplug-reading-v1`). #[cfg(feature = "guid-v3-tail")] pub const CLASSID_CPIC_V3: u32 = 0x0E01_1000; /// Pre-flip stored form of [`CLASSID_CPIC_V3`] (marker HIGH, canon @@ -170,6 +188,12 @@ impl NodeGuid { /// [`classid_concept_domain`](crate::ogar_codebook::classid_concept_domain) /// routes [`ProjectMgmt`](crate::ogar_codebook::ConceptDomain::ProjectMgmt). /// Resolves to [`ReadMode::PROJECT_V3`] (same hot `Cognitive` model as legacy PROJECT). + /// + /// **Deprecated, kept (operator, 2026-10-10):** the `0x1000` classview + /// marker is a fossil. V3 is mandatory and the reading comes from the plug + /// (`Activation::resolve_for_context`), not from this value. Existing keys + /// and callers keep working and nothing is evicted; new code should not + /// give classview `0x1000` a meaning (plan `v3-mandatory-hotplug-reading-v1`). #[cfg(feature = "guid-v3-tail")] pub const CLASSID_PROJECT_V3: u32 = 0x0101_1000; /// Pre-flip stored form of [`CLASSID_PROJECT_V3`] (marker HIGH, canon @@ -185,6 +209,12 @@ impl NodeGuid { /// [`classid_concept_domain`](crate::ogar_codebook::classid_concept_domain) /// routes [`Commerce`](crate::ogar_codebook::ConceptDomain::Commerce). /// Resolves to [`ReadMode::ERP_V3`] (same hot `Cognitive` model as legacy ERP). + /// + /// **Deprecated, kept (operator, 2026-10-10):** the `0x1000` classview + /// marker is a fossil. V3 is mandatory and the reading comes from the plug + /// (`Activation::resolve_for_context`), not from this value. Existing keys + /// and callers keep working and nothing is evicted; new code should not + /// give classview `0x1000` a meaning (plan `v3-mandatory-hotplug-reading-v1`). #[cfg(feature = "guid-v3-tail")] pub const CLASSID_ERP_V3: u32 = 0x0202_1000; /// Pre-flip stored form of [`CLASSID_ERP_V3`] (marker HIGH, canon `0x0200` @@ -1805,6 +1835,13 @@ pub(crate) fn reset_read_mode_lookups() { READ_MODE_LOOKUPS.with(|c| c.set(0)); } +/// **Deprecated, kept (operator, 2026-10-10):** a classid-keyed reading is +/// the legacy path. New code takes its reading from the plug +/// (`Activation::read_mode_for` / `Activation::resolve_for_context`), and the +/// contract's `_with` / `_plugged` forms accept it as a parameter. This lookup +/// keeps working for existing callers and stored keys; nothing is evicted +/// (plan `v3-mandatory-hotplug-reading-v1`). +/// /// Resolve a `classid` to its [`ReadMode`] — the single source both consumers /// and OGAR inherit. Reads the [`BUILTIN_READ_MODES`] registry, falling through /// to [`ReadMode::DEFAULT`] for any unconfigured classid (the key's own From 7cdc18f75800ad111b7209449ac9f8252aeaff6c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 18:11:22 +0000 Subject: [PATCH 5/5] plan ids D-HPR (match the D-id pattern); gate the V3 OCR test added-plans-have-dids failed: D-V3M does not match the D-id pattern (two letters before a digit), so the plan read as carrying no D-id. Renamed to D-HPR everywhere. Codex P2: to_node_row_with_follows_the_handed_reading mints a V3 tail, which mint_for refuses without the tail feature, so the --no-default-features suite panicked. Gated on guid-v3-tail like the aiwar and soa_graph tests. No-default-features lib suite: 1445 pass. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019fbZ92dFKQ5BJHrA3YcH9A --- .claude/board/INTEGRATION_PLANS.md | 2 +- .claude/board/STATUS_BOARD.md | 16 ++++++++-------- .../2026-10-10-v3-mandatory-hotplug-reading.md | 2 +- .claude/board/entries/README.md | 2 +- .claude/plans/v3-mandatory-hotplug-reading-v1.md | 16 ++++++++-------- crates/lance-graph-contract/src/aiwar.rs | 4 ++-- .../lance-graph-contract/src/nan_projection.rs | 4 ++-- crates/lance-graph-contract/src/ocr.rs | 8 +++++--- crates/lance-graph-contract/src/soa_graph.rs | 4 ++-- 9 files changed, 30 insertions(+), 28 deletions(-) diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index d4946abb5..8129d0df0 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,6 +1,6 @@ ## 2026-10-10 (2) — v3-mandatory-hotplug-reading-v1 — the reading comes from the plug, never from the classid value → `.claude/plans/v3-mandatory-hotplug-reading-v1.md` -**Status:** ACTIVE (D-V3M-0..6). Operator DECISION 2026-10-10: V3 mandatory, hotplug.rs + OGAR plug-and-play is the only reading path, lockstep deprecated, concepts are immutable addresses and language names are labels. +**Status:** ACTIVE (D-HPR-0..6). Operator DECISION 2026-10-10: V3 mandatory, hotplug.rs + OGAR plug-and-play is the only reading path, lockstep deprecated, concepts are immutable addresses and language names are labels. - Retires `classid_read_mode` / `BUILTIN_READ_MODES` (selects V3 by the `0x1000` marker; V1 fallback for unknown ids) in six waves; stored `0x1000` keys stay readable. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index f0e73e07d..588d324bc 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,16 +1,16 @@ -## D-V3M — V3 mandatory; reading from the plug only (2026-10-10) +## D-HPR — V3 mandatory; reading from the plug only (2026-10-10) Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`. | D-id | Deliverable | Status | Gate | |---|---|---|---| -| **D-V3M-0** | Ruling + plan + caller inventory | In PR (#1464) | — | -| **D-V3M-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | In PR (#1464; 4 `_with`/`_plugged` forms, disable runs 4/4 red) | new form returns the handed reading even where `classid_read_mode` disagrees | -| **D-V3M-2** | Canon domain readings move into `OgarAuthority::concept_override` | Queued | `read_mode_for(concept)` == today's `classid_read_mode()` per domain | -| **D-V3M-3** | Consumers move (lance-graph, q2, MedCare); `classid_read_mode` deprecated | Queued | no production caller left | -| **D-V3M-4** | `0x1000` / render prefixes documented as deprecated, kept (no eviction) | In PR (#1464; doc notes on `CLASSID_*_V3` + `classid_read_mode`) | — | -| **D-V3M-5** | Stored legacy keys keep resolving; a `SlabDeclaration` may override for its own data; no forced removal | Queued | — | -| **D-V3M-6** | No V1 default for an unknown classid; `guid-v3-tail` not a gate | Queued | unknown classid → `NoReadingFor` | +| **D-HPR-0** | Ruling + plan + caller inventory | In PR (#1464) | — | +| **D-HPR-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | In PR (#1464; 4 `_with`/`_plugged` forms, disable runs 4/4 red) | new form returns the handed reading even where `classid_read_mode` disagrees | +| **D-HPR-2** | Canon domain readings move into `OgarAuthority::concept_override` | Queued | `read_mode_for(concept)` == today's `classid_read_mode()` per domain | +| **D-HPR-3** | Consumers move (lance-graph, q2, MedCare); `classid_read_mode` deprecated | Queued | no production caller left | +| **D-HPR-4** | `0x1000` / render prefixes documented as deprecated, kept (no eviction) | In PR (#1464; doc notes on `CLASSID_*_V3` + `classid_read_mode`) | — | +| **D-HPR-5** | Stored legacy keys keep resolving; a `SlabDeclaration` may override for its own data; no forced removal | Queued | — | +| **D-HPR-6** | No V1 default for an unknown classid; `guid-v3-tail` not a gate | Queued | unknown classid → `NoReadingFor` | ## D-XGP — Cross-glove business parity: Odoo × SAP over one Quack algebra (2026-10-10) diff --git a/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md b/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md index e5e1221d6..740f217b6 100644 --- a/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md +++ b/.claude/board/entries/2026-10-10-v3-mandatory-hotplug-reading.md @@ -1,4 +1,4 @@ -# D-V3M-0 — V3 mandatory; the reading comes from the plug; concepts are immutable addresses (2026-10-10) +# D-HPR-0 — V3 mandatory; the reading comes from the plug; concepts are immutable addresses (2026-10-10) **Status:** DECISION (operator, 2026-10-10). Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 3cf97f40d..9ceeb67e1 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -30,7 +30,7 @@ exactly one of them, never both. | date | entry id | finding | file | |---|---|---|---| | 2026-10-10 | `D-XGP-8` | | [2026-10-10-woa-anchors-timesheet.md](2026-10-10-woa-anchors-timesheet.md) | -| 2026-10-10 | `D-V3M-0` | | [2026-10-10-v3-mandatory-hotplug-reading.md](2026-10-10-v3-mandatory-hotplug-reading.md) | +| 2026-10-10 | `D-HPR-0` | | [2026-10-10-v3-mandatory-hotplug-reading.md](2026-10-10-v3-mandatory-hotplug-reading.md) | | 2026-10-10 | `render-classid-reversal-audit` | | [2026-10-10-render-classid-reversal-audit.md](2026-10-10-render-classid-reversal-audit.md) | | 2026-10-10 | `E-W2-SPACE-AXIS-CUSTOM-SPACE-LEAVES-THE-CLASSID-1` | W2's space axis is decided: fixed spaces 0–3 stay in the classid, custom spaces move to the payload or edge | [2026-10-10-e-w2-space-axis-custom-space-leaves-the-classid-1.md](2026-10-10-e-w2-space-axis-custom-space-leaves-the-classid-1.md) | | 2026-10-10 | `D-XGP-3-BILLABLE` | | [2026-10-10-d-xgp-3-billable-converted.md](2026-10-10-d-xgp-3-billable-converted.md) | diff --git a/.claude/plans/v3-mandatory-hotplug-reading-v1.md b/.claude/plans/v3-mandatory-hotplug-reading-v1.md index 43aefd143..b1557d0eb 100644 --- a/.claude/plans/v3-mandatory-hotplug-reading-v1.md +++ b/.claude/plans/v3-mandatory-hotplug-reading-v1.md @@ -1,6 +1,6 @@ # v3-mandatory-hotplug-reading-v1 — the reading comes from the plug, never from the classid value -**Status:** ACTIVE (D-V3M-0..6). DECISION by the operator, 2026-10-10. W0 (this plan + the ruling record) lands first; W1 onward are code. +**Status:** ACTIVE (D-HPR-0..6). DECISION by the operator, 2026-10-10. W0 (this plan + the ruling record) lands first; W1 onward are code. ## The ruling (operator, 2026-10-10, verbatim) @@ -48,16 +48,16 @@ What it means, in rules: ## Waves -- [x] **W0 — D-V3M-0: record.** This plan, the ruling (`entries/2026-10-10-v3-mandatory-hotplug-reading.md`), `INTEGRATION_PLANS.md`, `STATUS_BOARD.md`. -- [x] **W1 — D-V3M-1: contract takes the reading as a parameter.** `ocr`, `aiwar`, `nan_projection`, `soa_graph` get variants that take a `ReadMode` (or a `ResolvedReading`) instead of looking the classid up. `DomainSpec` carries its reading. The classid-lookup forms stay, documented as legacy, until W3. Additive: no consumer breaks. +- [x] **W0 — D-HPR-0: record.** This plan, the ruling (`entries/2026-10-10-v3-mandatory-hotplug-reading.md`), `INTEGRATION_PLANS.md`, `STATUS_BOARD.md`. +- [x] **W1 — D-HPR-1: contract takes the reading as a parameter.** `ocr`, `aiwar`, `nan_projection`, `soa_graph` get variants that take a `ReadMode` (or a `ResolvedReading`) instead of looking the classid up. `DomainSpec` carries its reading. The classid-lookup forms stay, documented as legacy, until W3. Additive: no consumer breaks. - Falsifier: a test per module that the new form yields the reading it was handed, including one where the handed reading differs from what `classid_read_mode` would return (so a caller that silently re-looks-up fails). - **Shipped:** `ocr::LayoutBlock::to_node_row_with`, `aiwar::aiwar_node_rows_with`, `soa_graph::{project_snapshot_with, nearest_anchor_with}`, `nan_projection::project_energy_nonfinite_plugged` (resolves each run through `Activation::resolve_tenant_reading`; an unplugged concept is `NoReadingFor`). Tests use classid `0x1718_0000`, which the lookup reads as V1 / `Full`; each is red when its form is changed to re-look-up (disable runs, 4/4). -- [ ] **W2 — D-V3M-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved. +- [ ] **W2 — D-HPR-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved. - Falsifier: `read_mode_for(concept)` equals today's `classid_read_mode()` for each canon domain, so the move changes no reading. -- [ ] **W3 — D-V3M-3: consumers move.** lance-graph callcenter / deepnsm-v2 / probes, then q2 osint-bake + geo, MedCare cohorts. Each takes its reading from its `Activation`. Then `classid_read_mode` gets `#[deprecated]`. -- [x] **W4 — D-V3M-4: document the fossils as deprecated.** Done in `lance-graph-contract`: the five `CLASSID_*_V3` constants and `classid_read_mode` carry a "Deprecated, kept" doc note. A doc note, not `#[deprecated]`: the attribute would turn every existing caller red under `-D warnings`, which is the forced migration the ruling rules out. OGAR (`ogar-osm` `CLASSVIEW_V3_SUBSTRATE`, ro, dismech, loco) and q2 `geo` get the same note when next touched; existing mints stay. -- [ ] **W5 — D-V3M-5: legacy reads stay.** Stored `0x1000` and pre-flip keys keep resolving through `BUILTIN_READ_MODES`; no forced removal. A slab may additionally declare its reading (`SlabDeclaration`), which wins for its own data. Removing a table key is optional and only after nothing reads it. -- [ ] **W6 — D-V3M-6: V1 cannot be reached by default.** `ReadMode::DEFAULT` stops being the fallback for an unknown classid (it becomes `NoReadingFor`, as on the plug path), and `guid-v3-tail` stops being a feature gate. +- [ ] **W3 — D-HPR-3: consumers move.** lance-graph callcenter / deepnsm-v2 / probes, then q2 osint-bake + geo, MedCare cohorts. Each takes its reading from its `Activation`. Then `classid_read_mode` gets `#[deprecated]`. +- [x] **W4 — D-HPR-4: document the fossils as deprecated.** Done in `lance-graph-contract`: the five `CLASSID_*_V3` constants and `classid_read_mode` carry a "Deprecated, kept" doc note. A doc note, not `#[deprecated]`: the attribute would turn every existing caller red under `-D warnings`, which is the forced migration the ruling rules out. OGAR (`ogar-osm` `CLASSVIEW_V3_SUBSTRATE`, ro, dismech, loco) and q2 `geo` get the same note when next touched; existing mints stay. +- [ ] **W5 — D-HPR-5: legacy reads stay.** Stored `0x1000` and pre-flip keys keep resolving through `BUILTIN_READ_MODES`; no forced removal. A slab may additionally declare its reading (`SlabDeclaration`), which wins for its own data. Removing a table key is optional and only after nothing reads it. +- [ ] **W6 — D-HPR-6: V1 cannot be reached by default.** `ReadMode::DEFAULT` stops being the fallback for an unknown classid (it becomes `NoReadingFor`, as on the plug path), and `guid-v3-tail` stops being a feature gate. ## Open questions (operator) diff --git a/crates/lance-graph-contract/src/aiwar.rs b/crates/lance-graph-contract/src/aiwar.rs index aa828e2b0..f5503c1ee 100644 --- a/crates/lance-graph-contract/src/aiwar.rs +++ b/crates/lance-graph-contract/src/aiwar.rs @@ -87,7 +87,7 @@ pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { /// [`aiwar_node_rows`] with the classid and reading supplied by the caller, /// the reading resolved from its plug (`Activation::read_mode_for`) rather /// than looked up by classid. The form new code uses (plan -/// `v3-mandatory-hotplug-reading-v1`, D-V3M-1); the lookup in +/// `v3-mandatory-hotplug-reading-v1`, D-HPR-1); the lookup in /// [`aiwar_node_rows`] is legacy. pub fn aiwar_node_rows_with( graph: &LiteralGraph, @@ -226,7 +226,7 @@ mod tests { } } - /// D-V3M-1: `aiwar_node_rows_with` reproduces `aiwar_node_rows` when handed + /// D-HPR-1: `aiwar_node_rows_with` reproduces `aiwar_node_rows` when handed /// the same reading, and follows a different reading when handed one. #[cfg(feature = "guid-v3-tail")] #[test] diff --git a/crates/lance-graph-contract/src/nan_projection.rs b/crates/lance-graph-contract/src/nan_projection.rs index 0059f24c9..7190cdafa 100644 --- a/crates/lance-graph-contract/src/nan_projection.rs +++ b/crates/lance-graph-contract/src/nan_projection.rs @@ -184,7 +184,7 @@ fn classid_runs(rows: &[NodeRow]) -> impl Iterator { /// [`project_energy_nonfinite`] with each run's reading resolved through the /// plug (`Activation::resolve_tenant_reading`, once per run of equal classid) /// instead of the classid lookup. The form new code uses (plan -/// `v3-mandatory-hotplug-reading-v1`, D-V3M-1). +/// `v3-mandatory-hotplug-reading-v1`, D-HPR-1). /// /// # Errors /// @@ -420,7 +420,7 @@ mod tests { assert!(energy_all_finite(&[rows[0], rows[2]])); } - /// D-V3M-1: the plugged form reads each run under the plug's reading. The + /// D-HPR-1: the plugged form reads each run under the plug's reading. The /// classid is unknown to the lookup, which answers `Full` (has `Energy`) /// and so flags the NaN; the plug declares `Bootstrap` (no `Energy`), so /// the plugged form skips it. An unplugged concept is an error, never a diff --git a/crates/lance-graph-contract/src/ocr.rs b/crates/lance-graph-contract/src/ocr.rs index ed3dee943..704e1e09f 100644 --- a/crates/lance-graph-contract/src/ocr.rs +++ b/crates/lance-graph-contract/src/ocr.rs @@ -107,7 +107,7 @@ impl<'a> LayoutBlock<'a> { /// caller, resolved from its plug (`Activation::read_mode_for` / /// `Activation::resolve_for_context`) rather than looked up by classid. /// This is the form new code uses (plan `v3-mandatory-hotplug-reading-v1`, - /// D-V3M-1); the classid lookup in `to_node_row` is legacy. + /// D-HPR-1); the classid lookup in `to_node_row` is legacy. pub fn to_node_row_with(&self, classid: u32, identity: u32, mode: ReadMode) -> NodeRow { let schema = mode.value_schema; let mut value = [0u8; VALUE_SLAB_LEN]; @@ -297,9 +297,11 @@ mod tests { assert!(compressed.is_layout_preserving() && full.is_layout_preserving()); } - /// D-V3M-1: `to_node_row_with` mints with the tail and value schema it is + /// D-HPR-1: `to_node_row_with` mints with the tail and value schema it is /// handed. The classid is unknown to the lookup, which would answer the - /// V1 / Full default; the handed reading is V3 / Bootstrap. + /// V1 / Full default; the handed reading is V3 / Bootstrap. Gated: minting + /// a V3 tail needs the tail feature (`mint_for` refuses it otherwise). + #[cfg(feature = "guid-v3-tail")] #[test] fn to_node_row_with_follows_the_handed_reading() { use crate::canonical_node::{classid_read_mode, EdgeCodecFlavor, TailVariant}; diff --git a/crates/lance-graph-contract/src/soa_graph.rs b/crates/lance-graph-contract/src/soa_graph.rs index 1f0879b06..c15f9369d 100644 --- a/crates/lance-graph-contract/src/soa_graph.rs +++ b/crates/lance-graph-contract/src/soa_graph.rs @@ -246,7 +246,7 @@ pub fn project_snapshot(rows: &[NodeRow], domain: &DomainSpec) -> GraphSnapshot /// [`project_snapshot`] with the tail supplied by the caller, taken from the /// reading its plug resolved (`Activation::read_mode_for(..)?.tail_variant`) /// rather than looked up by classid. The form new code uses (plan -/// `v3-mandatory-hotplug-reading-v1`, D-V3M-1). +/// `v3-mandatory-hotplug-reading-v1`, D-HPR-1). pub fn project_snapshot_with( rows: &[NodeRow], domain: &DomainSpec, @@ -780,7 +780,7 @@ mod tests { assert_eq!(key(&a), key(&b)); } - /// D-V3M-1: the `_with` forms use the tail they are handed, not the classid + /// D-HPR-1: the `_with` forms use the tail they are handed, not the classid /// lookup. The classid here is unknown to `BUILTIN_READ_MODES`, so the /// lookup answers V1; the plug says V3. Only the handed tail decodes the V3 /// basin family correctly.