diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 88dbaf00f..431853cfe 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,7 @@ +## 2026-11-02 — main after #1926 (merged `ade765d6f`) + +- Codegraph: the G10 × G11 publication lab (L1–L6) — prepared land, fenced durable commit, crash recovery from the durable record, branch-local routing. D-TGL-PUB lab steps 1–3 are complete; EXO-148 stays locked. + ## 2026-11-02 — main after #1925 (merged `e89e61790`) - dt64: `BindStore::check_route` (read-only route check). diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index e4d3d6f55..f594c4ff3 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -1,3 +1,12 @@ +## 2026-11-02 — lance-graph PR #1926 (merged `ade765d6f`, branch `claude/clone-repositories-71a5sw`) — D-TGL-PUB step 3: G10 × G11 publication lab, falsifiers L1–L6 + +- **Added:** + - Codegraph `graph/publication.rs` (test-only): prepare (`prepare_refresh` + `check_route`), durable commit through `LabWal`, infallible publication (`commit_refresh`, `route`, watermark); recovery from the durable image in canonical cycle order; strict land-record codec. + - Post-merge record for #1925. +- **Locked:** the land record carries no positions and no `RouteVersion`; route re-application is not idempotent, so the watermark is load-bearing for DT64; physical-order replay combined with a watermark silently loses records. +- **Deferred:** production gates G9–G12 + EXO-148 (all open); D-TGB-7; the 512-byte writer ABI. +- **Confidence:** codegraph 17 lib tests; 5 disables red at the intended assertion. Lab results satisfy no production gate. + ## 2026-11-02 — lance-graph PR #1925 (merged `e89e61790`, branch `claude/clone-repositories-71a5sw`) — D-TGL-PUB steps 1–2: `check_route`, lab `WalSink`; stale-closure fix - **Added:** diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index f9e55128b..bf650c3fd 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -251,7 +251,7 @@ Plan: [d-codegraph-0-resident-codegraph-v1.md](../plans/d-codegraph-0-resident-c | **D-TGL-G12** | identities apart: W-slot, generation, ThoughtCommitId, DurableRef, RouteVersion, Lance version, source SHA (provenance only) | OPEN (production) | joins only through receipt / CommitWitness / MetaKey / land record | | **D-TGL-PUB** | G10 × G11 lab: prepared land (Codegraph + DT64 route intent), fenced durable commit through a test `WalSink`, crash recovery from the durable record, branch-local routing | SPEC: plan `g10-g11-atomic-publication-lab-v1.md`; boundaries B1–B5 located. Step 1 SHIPPED (lab-ready): `BindStore::check_route` (read-only; `route` runs the same `RouteLog::check`), 3 tests, 3 disables red. Step 2 SHIPPED (lab): `LabWal` in `lance-graph-codegraph/src/lab_wal.rs` (test-only `WalSink`: fence, one version per commit, reconcile, `HashConflict`, payload-free scan, image read, injected I/O / Ambiguous faults), 4 tests, 4 disables red; planner is a dev-dependency, one ndarray via `[patch]`. Step 3 SHIPPED (lab): `graph/publication.rs`, L1–L6 green, 5 disables red at the intended assertion (L4: Codegraph unchanged, `RouteVersion` 1 → 2; L6 without the canonical sort silently loses cycle 1). EXO-148 locked; no production land | L1–L6, each with a disable; L4 shows route re-application ticks `RouteVersion` without a watermark | | **D-TGL-EXO148** | `DurableRef` + never-reused `ThoughtCommitId` before any durable land, replay or PR record | OPEN (production, EXO-148) | reuse test (D-RST-1) | -| **D-DT64-AUTH** | DT64 / F### as the one method authority: census of production words with a two-way exception ratchet; parallel identities (`MethodSignature`, tract `Registry`, XLOG `Word`, ogar-loco `FnIndex`) stay as listed `LAB` adapters | QUEUED: plan `d-dt64-method-authority-census-v1.md` (operator decision 2026-11-02); egglog pinned to one commit in `egglog-r2il-probe`, `dr-math-binding-probe`, `xlog-relcomp-probe` | undeclared production word → red; stale exception → red; current tree green | +| **D-DT64-AUTH** | DT64 / F### as the one method authority: census of production words with a two-way exception ratchet; parallel identities (`MethodSignature`, tract `Registry`, XLOG `Word`, ogar-loco `FnIndex`) stay as listed `LAB` adapters | SHIPPED (census): `crates/lance-graph-r2il/dt64-authority-census.tsv` (2 LENS rows, 6 LAB rows), r2il census 6 tests + xlog census 1 test, 4 disables red (entry `2026-11-02-d-dt64-auth-census-shipped`); plan `d-dt64-method-authority-census-v1.md` (operator decision 2026-11-02); egglog pinned to one commit in `egglog-r2il-probe`, `dr-math-binding-probe`, `xlog-relcomp-probe` | undeclared production word → red; stale exception → red; current tree green | | **D-CODEGRAPH-0-D** | local stdio MCP over C | QUEUED | only after B and C gates | | **D-CODEGRAPH-0-E** | token measurement with and without the Codegraph | QUEUED | same revision, questions, model; nothing claimed before measured | diff --git a/.claude/board/entries/2026-11-02-d-dt64-auth-census-shipped.md b/.claude/board/entries/2026-11-02-d-dt64-auth-census-shipped.md new file mode 100644 index 000000000..0a379f067 --- /dev/null +++ b/.claude/board/entries/2026-11-02-d-dt64-auth-census-shipped.md @@ -0,0 +1,31 @@ +# D-DT64-AUTH: the method-authority census ships with a two-way ratchet (2026-11-02) + +**Status:** SHIPPED (lab-grade guard on the current tree). Plan [`d-dt64-method-authority-census-v1.md`](../plans/d-dt64-method-authority-census-v1.md). + +## What landed +- Table `crates/lance-graph-r2il/dt64-authority-census.tsv` (exactly 10 tab-separated columns per row): + - `LENS` rows pin each production behaviour lens's DT64 declaration: `POLYFILL_LENS` (`0xFFFF::0xF000`, rev 1, 17 arities) and `XLOG_LENS` (`0xFFFF::0xF100`, rev 1, 3 arities). + - `LAB` rows list the six method identities and dispatch paths DT64 does not see: `MethodSignature`, the tract-harvest `Registry`, XLOG `Word`, the `FnIndex` opcode, `FoldDialect`, and `r2il::procs`. Each has a why and a removal path. +- `lance-graph-r2il/tests/integration/dt64_census.rs`, six tests: + - it sweeps every `crates/*/src/**/*.rs` for `compose_classid(HARDCODED…` lens mints and matches them against the `LENS` rows in both directions; + - it replays the polyfill declaration against its row in a fresh `BindStore`; + - every `LAB` token must still occur (C2); + - a can-fire test (C1) and a silence test (C3). +- `lance-graph-xlog/tests/dt64_census.rs` checks the XLOG row through `lens::declare`. It lives in XLOG because `lance-graph-r2il` does not depend on `lance-graph-xlog`, so no new dependency was added. + +## Measured +- r2il census 6/6 and xlog census 1/1 pass; clippy `-D warnings` and fmt are clean. +- **Disable runs** (the census was committed first; every anchor was asserted): + - **D1:** a new lens mint in `polyfill.rs` turns 3 tests red, including the two-way sweep. + - **D2:** a stale `LAB` token turns `every_lab_exception_still_deviates` and the silence test red. + - **D3:** appending a word to `polyfill_arities()` without updating the row turns the declaration replay red. + - **D4:** the sweep comparison switched off turns C1 red. + +## Findings +- R2IL's own `src/` never declares the polyfill lens. Only tests and consumers call `declare_algebra(POLYFILL_LENS, …)`; the executor (`binding::prepare`) admits against that declaration. The row therefore pins `polyfill_arities()`, the one declaration source. +- `r2il::procs` is reached from production `src/` (`lance-graph-nal/src/nal.rs` `procs::count`) without admission. It is listed as `LAB`, to be removed once OGAR has `PROC_DEF`/`PROC_CALL`. + +## Limits +- The sweep sees only lens mints spelled `compose_classid(HARDCODED…`. A behaviour lens minted as a raw literal is not seen. +- The census pins declarations and lists exceptions. It migrates nothing and executes nothing through DT64. +- The `INVOKES_WORD` association stays with D-CODEGRAPH-0-BIN §8.3. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 699ed8730..70c0197d3 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,13 +25,14 @@ 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. -550 entries, 2026-08-06 .. 2026-11-02. +551 entries, 2026-08-06 .. 2026-11-02. | date | entry id | finding | file | |---|---|---|---| | 2026-11-02 | `p4-thought-git-codegraph-dt64-review` | | [2026-11-02-p4-thought-git-codegraph-dt64-review.md](2026-11-02-p4-thought-git-codegraph-dt64-review.md) | | 2026-11-02 | `p4-operator-decisions-lifecycle-lab` | | [2026-11-02-p4-operator-decisions-lifecycle-lab.md](2026-11-02-p4-operator-decisions-lifecycle-lab.md) | | 2026-11-02 | `D-TGL` | | [2026-11-02-d-tgl-release-fence-and-g10-g11-boundaries.md](2026-11-02-d-tgl-release-fence-and-g10-g11-boundaries.md) | +| 2026-11-02 | `D-DT64-AUTH` | | [2026-11-02-d-dt64-auth-census-shipped.md](2026-11-02-d-dt64-auth-census-shipped.md) | | 2026-11-02 | `D-CODEGRAPH-0` | | [2026-11-02-d-codegraph-0-gap-map.md](2026-11-02-d-codegraph-0-gap-map.md) | | 2026-11-02 | `codegraph-review-operator-decisions` | | [2026-11-02-codegraph-review-operator-decisions.md](2026-11-02-codegraph-review-operator-decisions.md) | | 2026-11-02 | `codegraph-dr3-dt64-ruff-architecture-review` | | [2026-11-02-codegraph-dr3-dt64-ruff-architecture-review.md](2026-11-02-codegraph-dr3-dt64-ruff-architecture-review.md) | diff --git a/.claude/plans/d-dt64-method-authority-census-v1.md b/.claude/plans/d-dt64-method-authority-census-v1.md index a6055613f..707172364 100644 --- a/.claude/plans/d-dt64-method-authority-census-v1.md +++ b/.claude/plans/d-dt64-method-authority-census-v1.md @@ -1,6 +1,7 @@ # Plan: DT64 as the one method authority — census and ratchet (2026-11-02) **D-id:** D-DT64-AUTH ([STATUS_BOARD](../board/STATUS_BOARD.md)). +**Status:** SHIPPED 2026-11-02: table `crates/lance-graph-r2il/dt64-authority-census.tsv` (const-table option chosen as a committed TSV beside the polyfill; home `lance-graph-r2il`, plus `lance-graph-xlog` for its own row since r2il does not depend on xlog); C1-C3 green with disables red ([entry](../board/entries/2026-11-02-d-dt64-auth-census-shipped.md)). **Source:** architecture review [entry](../board/entries/2026-11-02-codegraph-dr3-dt64-ruff-architecture-review.md) §2, §7 P3. diff --git a/crates/lance-graph-r2il/dt64-authority-census.tsv b/crates/lance-graph-r2il/dt64-authority-census.tsv new file mode 100644 index 000000000..4a90da86a --- /dev/null +++ b/crates/lance-graph-r2il/dt64-authority-census.tsv @@ -0,0 +1,13 @@ +# D-DT64-AUTH census (plan: .claude/plans/d-dt64-method-authority-census-v1.md). +# DT64 `0xFFFF::0xF###` is the one method authority. LENS rows pin each production behaviour lens's DT64 declaration; +# LAB rows list each method identity or dispatch path DT64 does not see, with why and a removal path. +# The census test fails both ways: an unlisted lens mint, or a listed exception whose token is gone. +# columns: kind name file token lens rev arities status why removal +LENS POLYFILL_LENS crates/lance-graph-r2il/src/polyfill.rs pub const POLYFILL_LENS: u32 = compose_classid(HARDCODED, 0xF000); 0xFFFFF000 1 2,2,2,2,2,2,2,2,2,1,1,2,2,2,1,1,2 CANONICAL - - +LENS XLOG_LENS crates/lance-graph-xlog/src/lens.rs pub const XLOG_LENS: u32 = compose_classid(HARDCODED_BEHAVIOUR, 0xF100); 0xFFFFF100 1 3,3,2 CANONICAL - - +LAB MethodSignature crates/dr-math-binding-probe/src/lib.rs pub struct MethodSignature { - - - LAB Probe-local method identity (per-method semantic law) beside the DT64 MethodRef. Bind through DT64 MethodRef or delete with the probe. +LAB tract-harvest Registry crates/tract-harvest-probe/src/registry.rs pub struct Registry { - - - LAB Versioned ONNX operator registration table that keeps its own MethodKey identity. Re-express each registration as a DT64 admitted method or delete with the probe. +LAB xlog Word crates/lance-graph-xlog/src/lib.rs pub enum Word { - - - LAB Caller-supplied reading of admitted words; the Quack lowering a word stands for is not carried by DT64 (MISSING-1). Remove when DT64 carries the lowering of the word. +LAB FnIndex opcode crates/lance-graph-r2il/src/polyfill.rs pub opcode: FnIndex, - - - LAB OGAR fold opcode identity held beside the DT64 position. Internal opcode only; stays unless it becomes a method identity. +LAB FoldDialect dispatch crates/lance-graph-r2il/src/lib.rs pub struct FoldDialect<'p> { - - - LAB Opcode dispatch DT64 never sees; a fold arm is not a method (plan section 1 rule 3). None while it stays internal. +LAB r2il procs crates/lance-graph-r2il/src/procs.rs pub fn count(d: &mut FoldDialect<'_>, stack: &mut Vec) -> Result<(), FoldError> { - - - LAB Named call sequences emitted straight into FoldDialect without admission. Route through an admitted method once PROC_DEF/PROC_CALL exists in OGAR. diff --git a/crates/lance-graph-r2il/tests/integration/dt64_census.rs b/crates/lance-graph-r2il/tests/integration/dt64_census.rs new file mode 100644 index 000000000..f79967d5e --- /dev/null +++ b/crates/lance-graph-r2il/tests/integration/dt64_census.rs @@ -0,0 +1,258 @@ +//! D-DT64-AUTH census (plan `.claude/plans/d-dt64-method-authority-census-v1.md`). +//! +//! `dt64-authority-census.tsv` lists every production behaviour lens (`LENS`) +//! and every method identity or dispatch path DT64 does not see (`LAB`). The +//! sweep is exact in both directions: a lens mint missing from the table, and +//! a listed row whose token no longer occurs, both fail. + +use std::fs; +use std::path::{Path, PathBuf}; + +use lance_graph_dt64::{AlgebraRef, BindStore, Extent, SchemaRev}; +use lance_graph_r2il::polyfill::{polyfill_arities, POLYFILL_LENS}; + +struct Row { + kind: String, + name: String, + file: String, + token: String, + lens: String, + rev: String, + arities: String, + status: String, +} + +fn rows(text: &str) -> Vec { + let mut out = Vec::new(); + for line in text.lines() { + if line.trim().is_empty() || line.starts_with('#') { + continue; + } + let cols: Vec<&str> = line.split('\t').collect(); + assert_eq!(cols.len(), 10, "census row must have 10 columns: {line:?}"); + out.push(Row { + kind: cols[0].to_string(), + name: cols[1].to_string(), + file: cols[2].to_string(), + token: cols[3].to_string(), + lens: cols[4].to_string(), + rev: cols[5].to_string(), + arities: cols[6].to_string(), + status: cols[7].to_string(), + }); + } + out +} + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn table() -> Vec { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("dt64-authority-census.tsv"); + rows(&fs::read_to_string(path).expect("census table readable")) +} + +fn walk(dir: &Path, out: &mut Vec) { + let Ok(entries) = fs::read_dir(dir) else { + return; + }; + for entry in entries.flatten() { + let p = entry.path(); + if p.is_dir() { + if p.file_name().is_some_and(|n| n == "target") { + continue; + } + walk(&p, out); + } else if p.extension().is_some_and(|e| e == "rs") { + out.push(p); + } + } +} + +/// Every `(repo-relative file, trimmed line)` that mints a behaviour lens. +fn lens_mints(root: &Path) -> Vec<(String, String)> { + let mut found = Vec::new(); + let crates = root.join("crates"); + let Ok(entries) = fs::read_dir(&crates) else { + return found; + }; + for entry in entries.flatten() { + let mut files = Vec::new(); + walk(&entry.path().join("src"), &mut files); + for f in files { + let Ok(text) = fs::read_to_string(&f) else { + continue; + }; + let rel = f + .strip_prefix(root) + .expect("under root") + .to_string_lossy() + .replace('\\', "/"); + for line in text.lines() { + let t = line.trim(); + if t.contains("compose_classid(HARDCODED") + && (t.starts_with("pub const") || t.starts_with("const")) + { + found.push((rel.clone(), t.to_string())); + } + } + } + } + found.sort(); + found +} + +/// Mints with no LENS row. +fn unlisted(mints: &[(String, String)], rows: &[Row]) -> Vec<(String, String)> { + mints + .iter() + .filter(|(f, t)| { + !rows + .iter() + .any(|r| r.kind == "LENS" && &r.file == f && &r.token == t) + }) + .cloned() + .collect() +} + +/// LENS rows with no matching mint. +fn stale_lenses(mints: &[(String, String)], rows: &[Row]) -> Vec { + rows.iter() + .filter(|r| r.kind == "LENS") + .filter(|r| !mints.iter().any(|(f, t)| f == &r.file && t == &r.token)) + .map(|r| format!("stale lens row: {} — remove its row", r.name)) + .collect() +} + +/// LAB rows whose file is missing or no longer contains the token. +fn stale_exceptions(root: &Path, rows: &[Row]) -> Vec { + rows.iter() + .filter(|r| r.kind == "LAB") + .filter(|r| { + fs::read_to_string(root.join(&r.file)) + .map(|t| !t.contains(&r.token)) + .unwrap_or(true) + }) + .map(|r| format!("stale exception: {} — remove its row", r.name)) + .collect() +} + +fn parse_lens(s: &str) -> u32 { + u32::from_str_radix(s.trim_start_matches("0x"), 16).expect("lens is hex") +} + +fn parse_arities(s: &str) -> Vec { + s.split(',') + .map(|n| n.parse().expect("arity is decimal")) + .collect() +} + +/// Red if a lens mint exists that the table does not list, or a LENS row +/// names a mint that is gone. +#[test] +fn every_lens_mint_is_listed_and_every_listed_lens_exists() { + let rows = table(); + let mints = lens_mints(&root()); + let lens_rows = rows.iter().filter(|r| r.kind == "LENS").count(); + assert!(!mints.is_empty()); + assert_eq!(mints.len(), lens_rows, "mints: {mints:?}"); + assert!(unlisted(&mints, &rows).is_empty()); + assert!(stale_lenses(&mints, &rows).is_empty()); +} + +/// Red if a word is appended to or changed in `POLYFILL` without updating the +/// row (the store refuses a second binding of the lens: `AlgebraImmutable`). +#[test] +fn the_polyfill_row_matches_the_live_declaration() { + let rows = table(); + let row = rows + .iter() + .find(|r| r.kind == "LENS" && r.name == "POLYFILL_LENS") + .expect("POLYFILL_LENS row"); + assert_eq!(row.status, "CANONICAL"); + assert_eq!(parse_lens(&row.lens), POLYFILL_LENS); + let arities = parse_arities(&row.arities); + assert_eq!(arities, polyfill_arities()); + let rev: u32 = row.rev.parse().expect("rev is decimal"); + + let mut s = BindStore::new(); + s.declare_algebra( + POLYFILL_LENS, + SchemaRev(rev), + Extent::DR64_CUBE, + arities.clone(), + ) + .expect("declare with the row"); + for (p, a) in arities.iter().enumerate() { + let word = AlgebraRef { + lens: POLYFILL_LENS, + word: u8::try_from(p).expect("position fits u8"), + }; + assert_eq!(s.arity(word), Some(*a)); + } + let past = AlgebraRef { + lens: POLYFILL_LENS, + word: u8::try_from(arities.len()).expect("len fits u8"), + }; + assert_eq!(s.arity(past), None); + s.declare_algebra( + POLYFILL_LENS, + SchemaRev(rev), + Extent::DR64_CUBE, + polyfill_arities(), + ) + .expect("live declaration is identical to the row"); +} + +/// Red if a LAB row's file or token is gone (the deviation was removed). +#[test] +fn every_lab_exception_still_deviates() { + let stale = stale_exceptions(&root(), &table()); + assert!(stale.is_empty(), "{stale:?}"); +} + +/// C1: red if `unlisted` cannot see a mint missing from the table. +#[test] +fn a_lens_mint_missing_from_the_table_is_caught() { + let rows = table(); + let mut mints = lens_mints(&root()); + mints.push(( + "crates/x/src/y.rs".to_string(), + "pub const NEW_LENS: u32 = compose_classid(HARDCODED, 0xF200);".to_string(), + )); + let missing = unlisted(&mints, &rows); + assert_eq!(missing.len(), 1); + assert_eq!(missing[0].0, "crates/x/src/y.rs"); +} + +/// C2: red if `stale_exceptions` cannot see a row whose token is gone. +#[test] +fn a_stale_exception_is_caught() { + let row = Row { + kind: "LAB".to_string(), + name: "ghost".to_string(), + file: "crates/lance-graph-r2il/src/polyfill.rs".to_string(), + token: "this token does not occur anywhere 0xDEAD".to_string(), + lens: "-".to_string(), + rev: "-".to_string(), + arities: "-".to_string(), + status: "LAB".to_string(), + }; + let stale = stale_exceptions(&root(), &[row]); + assert_eq!(stale.len(), 1); + assert!(stale[0].contains("ghost")); +} + +/// C3 silence: red if the real tree and table disagree, or if the table is +/// too small for silence to mean anything. +#[test] +fn the_current_tree_is_green() { + let rows = table(); + let mints = lens_mints(&root()); + assert!(unlisted(&mints, &rows).is_empty()); + assert!(stale_lenses(&mints, &rows).is_empty()); + assert!(stale_exceptions(&root(), &rows).is_empty()); + assert!(rows.iter().filter(|r| r.kind == "LAB").count() >= 1); + assert!(rows.iter().filter(|r| r.kind == "LENS").count() >= 2); +} diff --git a/crates/lance-graph-r2il/tests/integration/main.rs b/crates/lance-graph-r2il/tests/integration/main.rs index 84844de87..542375569 100644 --- a/crates/lance-graph-r2il/tests/integration/main.rs +++ b/crates/lance-graph-r2il/tests/integration/main.rs @@ -3,5 +3,6 @@ pub mod binding; pub mod binding_native; +pub mod dt64_census; pub mod polyfill; pub mod polyfill_views; diff --git a/crates/lance-graph-xlog/tests/dt64_census.rs b/crates/lance-graph-xlog/tests/dt64_census.rs new file mode 100644 index 000000000..86ab85c52 --- /dev/null +++ b/crates/lance-graph-xlog/tests/dt64_census.rs @@ -0,0 +1,54 @@ +//! The XLOG row of the D-DT64-AUTH census must match the live declaration. +//! +//! The r2il census test owns the two-way sweep; this test exists because +//! `lance-graph-r2il` does not depend on `lance-graph-xlog` (no new +//! dependency edge). Red if `XLOG_WORDS` or its rev changes without the row. + +use std::fs; +use std::path::Path; + +use lance_graph_dt64::{AlgebraRef, BindStore}; +use lance_graph_xlog::lens::{self, XLOG_LENS, XLOG_WORDS}; + +#[test] +fn the_xlog_row_matches_the_live_declaration() { + let path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../lance-graph-r2il/dt64-authority-census.tsv"); + let text = fs::read_to_string(path).expect("census table readable"); + let mut found = Vec::new(); + for line in text.lines() { + if line.trim().is_empty() || line.starts_with('#') { + continue; + } + let cols: Vec<&str> = line.split('\t').collect(); + assert_eq!(cols.len(), 10, "census row must have 10 columns: {line:?}"); + if cols[0] == "LENS" && cols[1] == "XLOG_LENS" { + found.push(cols); + } + } + assert_eq!(found.len(), 1); + let cols = &found[0]; + let lens = u32::from_str_radix(cols[4].trim_start_matches("0x"), 16).expect("hex lens"); + assert_eq!(lens, XLOG_LENS); + let arities: Vec = cols[6] + .split(',') + .map(|n| n.parse().expect("decimal arity")) + .collect(); + let live: Vec = XLOG_WORDS.iter().map(|w| w.columns).collect(); + assert_eq!(arities, live); + + let mut store = BindStore::new(); + lens::declare(&mut store).expect("declare"); + for (p, a) in arities.iter().enumerate() { + let word = AlgebraRef { + lens: XLOG_LENS, + word: u8::try_from(p).expect("position fits u8"), + }; + assert_eq!(store.arity(word), Some(*a)); + } + let past = AlgebraRef { + lens: XLOG_LENS, + word: u8::try_from(arities.len()).expect("len fits u8"), + }; + assert_eq!(store.arity(past), None); +}