diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 131162f..d502f38 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,45 @@ +## E-THE-PIN-THAT-CLAIMED-ONE-PLACE-CREATED-A-SECOND-1 (2026-09-05) + +**Finding.** The first CI gate this repo ever had went red on its first +run, and the two causes were both "the same thing is declared twice, and +cargo silently picks the nearer one." + +`rust-toolchain.toml` was added at the repository root carrying the +sentence *"the pin lives in exactly ONE place"* — while +`native/lgj-abi/rust-toolchain.toml` already existed, pinning `1.97.1`. +Cargo reads whichever file is nearest the working directory, so the root +file governed a command run from the root and the crate file governed a +command run from the crate. Two files, one toolchain, disagreeing by where +you happened to stand. The root file is removed; the crate file is the +authority, bumped to `1.98.1`, and it sits beside `.cargo/config.toml`, +which cargo resolves the same way. + +That bump is forced, not cosmetic: `ndarray` 0.17.2 now declares +`rust-version = "1.98"`, so a 1.97.1 resolve of this crate fails outright. +The same upstream move turned every MSRV job in `AdaWorldAPI/tract` red on +the same afternoon. + +**The second cause is the more interesting one, because a test caught it.** +`native/lgj-abi/.cargo/config.toml` pins `-Ctarget-cpu=x86-64-v4`, and +`abi::tests::the_x86_64_build_has_a_vector_baseline` exists to turn a lost +baseline into a test failure instead of a SIGILL inside a JVM downcall. +The workflow ran cargo from the repository root with `--manifest-path` +pointing into the crate — and **cargo reads `.cargo/config.toml` from the +working directory's ancestry, never from the manifest's directory.** The +baseline was therefore never applied, the manifest reported the scalar +backend, and the test fired exactly as designed. `--manifest-path` is not +equivalent to running in the crate directory, and this is the failure that +proves it. + +**Consequence.** The jobs now run with `working-directory` set to the +crate, and CI additionally exports `CARGO_BUILD_RUSTFLAGS=-Ctarget-cpu= +x86-64-v3`, because GitHub-hosted runners do not guarantee AVX-512 and a +v4 build would die on them with the very SIGILL that config comment +describes. v3 still satisfies the test's `AVX2 | AVX512` assertion, so the +gate is retargeted rather than weakened. Verified locally on both sides: +the old invocation reproduces the failure, the new one passes 138 tests +plus the G11 fence, with fmt and clippy clean. + ## E-THE-GUARD-YOU-WIRE-IS-NOT-THE-GUARD-YOU-NAMED-1 (2026-08-28) **Finding.** A wave chartered as "wire the epoch re-check" cannot deliver diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index b832cf3..0f33359 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,63 @@ +## 2026-09-05 — storno: the gate's own first run corrected two claims above + +Corrects the entry immediately below, which is left in place per the +storno rule. Two of its statements were wrong when written: + +- *"adds `rust-toolchain.toml` (this repo had none)"* — one already + existed at `native/lgj-abi/rust-toolchain.toml`, pinning `1.97.1`. +- *"the pin has exactly one spelling"* — adding the root file made two, + and cargo reads whichever is nearest the working directory. + +The root file is removed and the crate-level file is bumped to `1.98.1` +(forced: `ndarray` 0.17.2 declares `rust-version = "1.98"`). The three +jobs now run with `working-directory` set to the crate rather than +`--manifest-path` from the root, because `.cargo/config.toml` is read +from the working directory's ancestry and the v4 target-cpu baseline was +being dropped — which is what `the_x86_64_build_has_a_vector_baseline` +caught on the gate's first run. CI exports the v3 baseline explicitly, +since GitHub-hosted runners do not guarantee AVX-512. Full account: +`EPIPHANIES.md` `E-THE-PIN-THAT-CLAIMED-ONE-PLACE-CREATED-A-SECOND-1`. + +## 2026-09-05 — first CI lint gate: fmt + clippy + rust-test, Rust pinned to 1.98.1 + +**Branch `claude/ci-lint-gate`, PR #77.** This repo has never had a +`.github/workflows/` directory — every gate documented in `CLAUDE.md` (the +G11 contract-import fence, the epoch-recheck falsifiers, `ApiSurfaceTest`, +the L1/L2 semantic-leak pins) has only ever run when a session happened to +invoke `cargo test` by hand. Adds `.github/workflows/lint.yml`, three jobs +scoped to `native/lgj-abi` (the repo's only Cargo crate): `format` +(`cargo fmt --check`), `clippy` (`--all-targets -- -D warnings`), and +`rust-test` (`--all-targets`, which is what finally makes +`tests/g11_contract_import_fence.rs` run anywhere but a session's own shell — +the same gap `CLAUDE.md` names for G11 itself: "prose until 2026-09-03" +because nothing ran the test that enforces it). + +`lgj-abi` path-deps `ndarray`, `lance-graph`'s `lance-graph-contract`, and +(optional, off by default) `OGAR`'s `ogar-class-view`. Cargo resolves the +full graph including inactive optional path deps, so `clippy`/`rust-test` +check out all three siblings — the identical shape `lance-graph`'s own +`style.yml` already uses (self + each sibling into its own named +subdirectory under one runner workspace root). + +Also adds `rust-toolchain.toml` (this repo had none) pinning +`channel = "1.98.1"`, joining the sweep `ndarray`/`lance-graph`/`OGAR` +already carry. `setup-rust-toolchain@v1` is invoked with no `toolchain:` +input, so the pin has exactly one spelling. + +**Java is deliberately out of scope.** `java/` holds only `README.md` + +`.gitignore` — no Maven, no Gradle, no source. The README says so directly +("no Maven, no Gradle... `javac` and `java` are the entire Java toolchain"). +Nothing to wire a CI job against yet; filed as a named follow-up rather than +guessed at. + +**Not measured in this PR:** whether `clippy --all-targets -- -D warnings` +is actually clean on `lgj-abi` today. Disk constraints (a large dependency +tree through the contract crate) ruled out a local `cargo build`/`clippy` +run, so this PR's own CI run is the first real measurement — if it comes +back red on pre-existing debt, the follow-up is this workspace's own tiered +posture (`continue-on-error: true` + a named `TECH_DEBT.md` line), not a +bundled auto-fix. + ## 2026-09-04 — lgj_hop: the conjunction is one truth-table pass, and the lane is read as a lane (5×) **Branch `claude/pr-294-ragged-path-validation-170zcy`**, lgj-abi only — no diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index 1f5f12f..e300d3e 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -8,6 +8,66 @@ > anti-pattern the imported board rules name. Backfilled below in one > pass rather than left stale; PR #4 onward gets its entry at merge time. +## PR #77 — first CI lint gate: fmt + clippy + rust-test, Rust pinned to 1.98.1 (opened 2026-09-05, head `6d4b1a2`) + +**Added.** `.github/workflows/lint.yml` (three jobs: `format`, `clippy`, +`rust-test`, all scoped to `native/lgj-abi` via `--manifest-path`); +`rust-toolchain.toml` pinning `channel = "1.98.1"` + `rustfmt`/`clippy` +components (this repo had neither a workflow directory nor a toolchain pin +before this PR). + +**Locked.** The G11 contract-import fence (`tests/g11_contract_import_fence.rs`) +now runs on every push/PR via `rust-test`, closing the exact gap +`CLAUDE.md` names for G11 itself ("prose until 2026-09-03" — a rule +enforced by a test nothing ever ran). `clippy`/`rust-test` check out all +three path-dep siblings (`ndarray`, `lance-graph`, `OGAR`) because Cargo +resolves the full dependency graph including the inactive optional +`ogar-classview` feature's path dep — mirroring `lance-graph`'s own +`style.yml` checkout shape exactly. + +**Deferred.** A Java CI job — `java/` has no build system at all (no +Maven, no Gradle, no source), per its own README; wiring a job against a +build system that doesn't exist would guess at a command with nothing to +verify it against. Whether `-D warnings` is clean on `lgj-abi` today is +also unmeasured locally (disk constraints); this PR's own CI run is the +first real measurement, with the tiered `continue-on-error` + +`TECH_DEBT.md` fallback named in the PR body if it comes back red on +pre-existing debt. + +**Correction (2026-09-05, head `1ea5c85`).** Appended per the append-only +rule rather than edited above; the original lines stay as written. + +- *"this repo had neither a workflow directory nor a toolchain pin before + this PR"* — the workflow half is right, the toolchain half is not. + `native/lgj-abi/rust-toolchain.toml` already pinned `1.97.1`. Adding a + second file at the repository root made two pins for one toolchain, and + cargo reads whichever is nearest the working directory. The root file is + removed; the crate-level file is the authority, now `1.98.1` — forced, + since `ndarray` 0.17.2 declares `rust-version = "1.98"`. +- *"runs on every push/PR"* — the `push` trigger is limited to `main`, so + the accurate statement is every pull request and every push to `main`. +- *"all scoped to `native/lgj-abi` via `--manifest-path`"* — that scoping + is what broke the first run. Cargo reads `.cargo/config.toml` from the + working directory's ancestry, never from the manifest's directory, so + the crate's `-Ctarget-cpu` baseline was dropped and + `the_x86_64_build_has_a_vector_baseline` failed exactly as designed. The + jobs now set `working-directory` to the crate, and CI exports the v3 + baseline explicitly because GitHub-hosted runners do not guarantee + AVX-512. Each `setup-rust-toolchain` step also gained `rust-src-dir`: + `defaults.run.working-directory` governs `run:` steps only, so without it + the action reads no toolchain file and falls back to `stable`. +- *"whether `-D warnings` is clean on `lgj-abi` today is unmeasured"* — now + measured locally: clippy and fmt clean, 138 unit tests plus the G11 fence + passing, under the same commands CI runs. + +**Docs.** `LATEST_STATE.md` entry; this entry. + +**Confidence.** High that the workflow shape is correct (it is a direct +transplant of `lance-graph`'s own proven `style.yml` checkout pattern, +adapted to this repo's single-crate layout). Unmeasured whether the +`clippy`/`rust-test` jobs pass clean on first run — that is what the CI +run itself is for. + ## PR #68 — mask-risc-lowering v1 → v4: SPEC completion, the 5+3 council, voxelmasking (merged 2026-09-03 as `dfb4ab1`, head `4ad9377`) **Added.** Four commits on `.claude/plans/mask-risc-lowering-v1.md`, plan-only — no kernel, no ABI symbol, no mint, no production code. `51dcffc` completed SPEC v1 to the council bar (non-goals, pre-registered gates G1–G8, per-savant question sets); `00570a0` draft v2 (5 savants, 40 findings, 24 amendments); `0212d21` ratified v3 (3 reviewers, 2 BLOCK + 14 FIX, 2 external Codex P1s); `4ad9377` the v4 amendment — §14 voxelmasking, §3c reuse map, §15 two probes. `STATUS_BOARD` rows `D-MRL-0f` and `D-MRL-0g`. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..3731c78 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,187 @@ +name: Lint + +on: + pull_request: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# Least-privilege: these jobs only read the repo (checkout + build + lint). +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + # lgj-abi path-deps ndarray and lance-graph-contract (crates/lance-graph-contract + # in the AdaWorldAPI/lance-graph sibling), and optionally (feature `ogar-classview`, + # off by default) ogar-class-view in the AdaWorldAPI/OGAR sibling. Cargo resolves + # the full dependency graph — including inactive optional path deps — so all three + # siblings must exist on disk even though only two are compiled into the default + # build. Checked out under the runner's top-level workspace, sibling to this repo's + # own checkout, exactly as `../../../` from native/lgj-abi/Cargo.toml expects + # (matches AdaWorldAPI/lance-graph's own .github/workflows/style.yml pattern). + format: + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: lance-graph-java/native/lgj-abi + steps: + - uses: actions/checkout@v4 + with: + path: lance-graph-java + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + # `defaults.run.working-directory` applies to `run:` steps only, NOT + # to action steps: this action executes from GITHUB_WORKSPACE, where + # the checkout is a subdirectory and no toolchain file is visible. It + # would silently fall back to `stable` instead of the pin. `rust-src-dir` + # is the input that points it at the crate — which is also where + # `rust-toolchain.toml` lives, beside `.cargo/config.toml`. + rust-src-dir: lance-graph-java/native/lgj-abi + # The baseline goes HERE, not in a `CARGO_BUILD_RUSTFLAGS` env var. + # This action exports `RUSTFLAGS` from its own `rustflags` input + # (default `-D warnings`), and `RUSTFLAGS` overrides + # `build.rustflags` — which is what `CARGO_BUILD_RUSTFLAGS` sets, so a + # baseline put there is silently discarded. The first run proved it: + # the crate's own SIGILL-guard test was reached with no target-cpu at + # all. The action's default is kept alongside the baseline, not + # replaced. + # + # v3 rather than the crate's own v4: `.cargo/config.toml` pins AVX-512 + # because the production artifact is built for one known host, and + # GitHub-hosted runners do not guarantee it. That file names v3 as the + # remedy for a machine without AVX-512, and AVX2 is what + # `the_x86_64_build_has_a_vector_baseline` requires — so this + # retargets the gate rather than weakening it. + rustflags: -D warnings -Ctarget-cpu=x86-64-v3 + # No `toolchain:` input — the action reads `rust-toolchain.toml`, so the + # pinned version lives in exactly ONE place. + components: rustfmt, clippy + - name: Check formatting (lgj-abi) + run: cargo fmt -- --check + + clippy: + runs-on: ubuntu-latest + timeout-minutes: 30 + defaults: + run: + working-directory: lance-graph-java/native/lgj-abi + steps: + - uses: actions/checkout@v4 + with: + path: lance-graph-java + - name: Checkout AdaWorldAPI/ndarray (sibling dependency) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/ndarray + path: ndarray + - name: Checkout AdaWorldAPI/lance-graph (sibling dependency, lance-graph-contract) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/lance-graph + path: lance-graph + - name: Checkout AdaWorldAPI/OGAR (sibling dependency, optional ogar-classview feature) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/OGAR + path: OGAR + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + # `defaults.run.working-directory` applies to `run:` steps only, NOT + # to action steps: this action executes from GITHUB_WORKSPACE, where + # the checkout is a subdirectory and no toolchain file is visible. It + # would silently fall back to `stable` instead of the pin. `rust-src-dir` + # is the input that points it at the crate — which is also where + # `rust-toolchain.toml` lives, beside `.cargo/config.toml`. + rust-src-dir: lance-graph-java/native/lgj-abi + # The baseline goes HERE, not in a `CARGO_BUILD_RUSTFLAGS` env var. + # This action exports `RUSTFLAGS` from its own `rustflags` input + # (default `-D warnings`), and `RUSTFLAGS` overrides + # `build.rustflags` — which is what `CARGO_BUILD_RUSTFLAGS` sets, so a + # baseline put there is silently discarded. The first run proved it: + # the crate's own SIGILL-guard test was reached with no target-cpu at + # all. The action's default is kept alongside the baseline, not + # replaced. + # + # v3 rather than the crate's own v4: `.cargo/config.toml` pins AVX-512 + # because the production artifact is built for one known host, and + # GitHub-hosted runners do not guarantee it. That file names v3 as the + # remedy for a machine without AVX-512, and AVX2 is what + # `the_x86_64_build_has_a_vector_baseline` requires — so this + # retargets the gate rather than weakening it. + rustflags: -D warnings -Ctarget-cpu=x86-64-v3 + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: lance-graph-java/native/lgj-abi + - name: Clippy lgj-abi + run: cargo clippy --all-targets -- -D warnings + + rust-test: + runs-on: ubuntu-latest + timeout-minutes: 30 + defaults: + run: + working-directory: lance-graph-java/native/lgj-abi + steps: + - uses: actions/checkout@v4 + with: + path: lance-graph-java + - name: Checkout AdaWorldAPI/ndarray (sibling dependency) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/ndarray + path: ndarray + - name: Checkout AdaWorldAPI/lance-graph (sibling dependency, lance-graph-contract) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/lance-graph + path: lance-graph + - name: Checkout AdaWorldAPI/OGAR (sibling dependency, optional ogar-classview feature) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/OGAR + path: OGAR + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + # `defaults.run.working-directory` applies to `run:` steps only, NOT + # to action steps: this action executes from GITHUB_WORKSPACE, where + # the checkout is a subdirectory and no toolchain file is visible. It + # would silently fall back to `stable` instead of the pin. `rust-src-dir` + # is the input that points it at the crate — which is also where + # `rust-toolchain.toml` lives, beside `.cargo/config.toml`. + rust-src-dir: lance-graph-java/native/lgj-abi + # The baseline goes HERE, not in a `CARGO_BUILD_RUSTFLAGS` env var. + # This action exports `RUSTFLAGS` from its own `rustflags` input + # (default `-D warnings`), and `RUSTFLAGS` overrides + # `build.rustflags` — which is what `CARGO_BUILD_RUSTFLAGS` sets, so a + # baseline put there is silently discarded. The first run proved it: + # the crate's own SIGILL-guard test was reached with no target-cpu at + # all. The action's default is kept alongside the baseline, not + # replaced. + # + # v3 rather than the crate's own v4: `.cargo/config.toml` pins AVX-512 + # because the production artifact is built for one known host, and + # GitHub-hosted runners do not guarantee it. That file names v3 as the + # remedy for a machine without AVX-512, and AVX2 is what + # `the_x86_64_build_has_a_vector_baseline` requires — so this + # retargets the gate rather than weakening it. + rustflags: -D warnings -Ctarget-cpu=x86-64-v3 + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: lance-graph-java/native/lgj-abi + # Runs every #[test] in lgj-abi, including tests/g11_contract_import_fence.rs + # — the fence CLAUDE.md's "Enforcement" section names as structural: it walks + # src/ and rejects any `lance_graph_contract::` module outside the four-name + # allowlist. Per this repo's own history, "G11 was prose until 2026-09-03" + # because nothing ran the test that enforces it; this job is what runs it. + - name: Test lgj-abi + run: cargo test --all-targets diff --git a/native/lgj-abi/rust-toolchain.toml b/native/lgj-abi/rust-toolchain.toml index 652fcca..f19a0b1 100644 --- a/native/lgj-abi/rust-toolchain.toml +++ b/native/lgj-abi/rust-toolchain.toml @@ -2,5 +2,19 @@ # Stable only. Matches ndarray / lance-graph / the rest of the AdaWorldAPI # stack. No nightly features anywhere in this crate — in particular no # `core::simd` / portable-simd (abi.md §8). -channel = "1.97.1" +channel = "1.98.1" components = ["clippy", "rustfmt"] + +# Bump log (append one line per bump; the version itself is the `channel` +# line above and is deliberately not restated here): +# 1.97.1 → 1.98.1 2026-09-05: joins the workspace-wide sweep already +# carried by ndarray / lance-graph / OGAR. Forced, not +# optional: ndarray 0.17.2 now declares rustc 1.98, so +# a 1.97.1 build of this crate fails to resolve at all. +# +# This is THE toolchain file for this repo. A second one briefly existed at +# the repository root; it was removed rather than kept in sync, because two +# files pinning one toolchain is the defect this sweep exists to remove, and +# cargo reads whichever is nearest the working directory — so the two would +# disagree silently depending on where a command was run from. This file sits +# beside `.cargo/config.toml`, which cargo resolves the same way.