From bf9a71ae75d47b9b754e7553dc248a0c420c2487 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:26:47 +0000 Subject: [PATCH 1/7] ci: dispatch the Java gate -- 765 checks that were gating nothing `main` was RED and nothing said so. `GraphHopTest` reported 1 FAILED / 65 passed at `bb81d80` and survived a merge, because `.github/workflows/lint.yml` held three Rust-only jobs and nothing in the repository compiled a single line of Java. The core suite and all four consumer mains were LOCAL gates -- run by whoever remembered. A gate that exists and is never dispatched is indistinguishable from no gate. Filed as ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE, whose stated FIRST task was not "add a job" but scoping whether a runner can obtain the JDK at all. That scoping is done, and it resolves by a mechanism the container does not use -- which is why the issue was right to refuse to generalise from the local acquisition ladder. Each link read from a primary source: 1. setup-java's `normalizeVersion`: `if (version.endsWith('-ea')) { ... stable = false }` 2. its temurin installer: `const releaseType = this.stable ? 'ga' : 'ea'` 3. Adoptium's API, queried live: `release_type=ea` + `version=[28,29)` + linux/x64/jdk serves `jdk-28+16-ea-beta`, the same build this container runs -- and `/v3/info/available_releases` omits 28 while naming it `most_recent_feature_version`, which is why the `-ea` suffix carries the whole request. The container's blocker was gateway-blocked distribution hosts, hence its GitHub-release download path. That was never a runner's constraint. The version is NOT pinned to +16: internal head pins are forbidden, an upstream EA build is an external dependency whose purpose is to move, and what the suite requires is JEP 401 under preview -- which every 28 EA build carries. The job shells out to raw javac/java with the four command strings `java/README.md` documents, verbatim, so a contributor's local command and CI's command are the same string. No build tool was introduced; there still is none, by design. Measured on `origin/main` (`aef2382`) before the job was written, every command run verbatim: core 612 checks / 17 suites / 0 failures (abi 0.12, avx512, release), bricks 70, graph 68, trades 3 and 12 -- 765 checks across 5 entry points, all exit 0. Two counts were wrong on the way in. It is FOUR consumer mains, not three -- `trades` carries two, and running one of them would have been precisely the no-gate-with-extra-steps outcome the issue names. And root CLAUDE.md briefed every session with `409 checks / 16 suites / abi 0.11` against an actual 612 / 17 / 0.12. That is fixed STRUCTURALLY rather than re-pinned: the live count now exists in exactly ONE dated place and every other site had its number removed, because one measurement restated in eight places goes stale in eight places -- which is what had happened. The three surviving 409s are dated historical measurements, kept as evidence; the Valhalla-flip comparison in particular is only meaningful against the baseline it was taken against and must not be restated forward. The falsifier the issue demands -- re-introduce the stale pin, confirm red -- is deliberately NOT in this commit: a disable run must happen on committed work, because a `git checkout` restore is what ends it. It follows next. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .claude/board/ISSUES.md | 55 ++++++++++++++++- .github/workflows/lint.yml | 118 +++++++++++++++++++++++++++++++++++++ .gitignore | 3 + CLAUDE.md | 31 +++++++--- 4 files changed, 199 insertions(+), 8 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 05e7e51..e065e7e 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,4 +1,4 @@ -## ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE (2026-09-22) — OPEN +## ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE (2026-09-22) — RESOLVED 2026-09-22 (job landed; dispatch proven by its own first run) `main` was RED and nothing said so. `GraphHopTest` reported 1 FAILED / 65 passed at `bb81d80`, established by running it from a clean worktree, not @@ -38,6 +38,59 @@ confirm the job goes red. A job that builds the consumers but never runs their mains would pass, and would be the no-gate-with-extra-steps outcome this entry exists to name. +--- + +**RESOLUTION (2026-09-22).** `java-suites` in `.github/workflows/lint.yml`: +twelve steps, same three-sibling checkout as `rust-test`, `cargo build +--release` for the artifact, then the four `javac`/`java` command strings +`java/README.md` documents, verbatim. No build tool was introduced — there +still is none, by design. + +**The first task — acquisition — is ANSWERED, and by a different mechanism +than the local one, which is why this entry was right to refuse to generalise +from the container's ladder.** Chain, each link read from a primary source +rather than inferred: + +1. `actions/setup-java`'s `normalizeVersion`: `if (version.endsWith('-ea')) { + ... stable = false }` — so `java-version: '28-ea'` is not-stable. +2. its temurin installer: `const releaseType = this.stable ? 'ga' : 'ea'`. +3. Adoptium's API, queried live: `release_type=ea` + `version=[28,29)` + + linux/x64/jdk serves `jdk-28+16-ea-beta` — the same build this container + runs. And `/v3/info/available_releases` omits 28 while naming it + `most_recent_feature_version`, which is *why* the `-ea` suffix is load-bearing. + +The container's blocker (gateway-blocked distribution hosts, hence the +GitHub-release download path) is a **this-container** constraint and never +applied to a runner. The version is deliberately NOT pinned to `+16`: internal +head pins are forbidden, and an upstream EA build is an external dependency +whose purpose is to move — what the suite needs is JEP 401 under preview, which +every 28 EA build carries. + +**Two counts in the entry above were wrong, measured on `origin/main` +(`aef2382`) before the job was written:** it is **four** consumer mains, not +three (`trades` carries both `TradesAllocationTest` and `TradesParityTest`, and +running one of two would have been precisely the no-gate-with-extra-steps +outcome this entry names). The core suite is **612 checks / 17 suites**, which +this entry had right while root `CLAUDE.md` still briefed every session with +`409 / 16 suites / abi 0.11`. That drift is fixed in the same commit, and fixed +STRUCTURALLY: the live count now exists in exactly one dated place and every +other site had its number removed rather than re-pinned. One measurement +restated in eight places goes stale in eight places. + +Measured, all four command strings run verbatim: core **612** (0 failures, abi +0.12, avx512, release), bricks **70**, graph **68**, trades **3** and **12** — +**765 checks across 5 entry points**, every one exit 0. + +**FALSIFIER: PENDING at the time of this commit** — deliberately, because the +disable run must happen on committed work (a `git checkout` restore is what +ends it, and this repo has lost uncommitted work to exactly that). The run and +its result are recorded in the commit that follows this one. What a local run +CANNOT prove is +dispatch: whether a runner starts the job and reaches JDK 28. That is proven by +this PR's own CI run, which is the right falsifier because it is +self-executing — and if `28-ea` fails to resolve on the runner, the job goes red +on the step that resolves it and says so. + ## ISS-LGJ-TOOLCHAIN-MUST-BE-JDK28-VALHALLA-PANAMA — UNBLOCKED; JDK 28 installed and the flip proven (2026-09-19) ⊘ The entry below says the migration is blocked because no JDK 28 can be diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3731c78..92317c3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -185,3 +185,121 @@ jobs: # because nothing ran the test that enforces it; this job is what runs it. - name: Test lgj-abi run: cargo test --all-targets + + # THE JAVA GATE. Until this job existed, `main` could be RED and nothing said + # so: `GraphHopTest` reported 1 FAILED / 65 passed at bb81d80 and survived a + # merge, because the three jobs above compile Rust only and nothing in + # `.github/` compiled a single line of Java. The 612-check core suite and the + # four consumer mains were LOCAL gates — run by whoever remembered. A gate that + # exists and is never dispatched is indistinguishable from no gate. + # Tracked as ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE. + # + # There is no build tool in this repo — no Maven, no Gradle, no wrapper — by + # design (`java/README.md`: "`javac` and `java` are the entire Java + # toolchain"). So this job shells out to raw javac/java with exactly the + # commands that README documents, which is what keeps the two from drifting: + # a contributor's local command and CI's command are the same string. + java-suites: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + path: lance-graph-java + # Same three siblings as `rust-test`, for the same reason: cargo resolves + # the whole graph including the inactive optional path dep, so all three + # must exist on disk before the artifact this job needs can be built. + - 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: + rust-src-dir: lance-graph-java/native/lgj-abi + # x86-64-v3, matching the jobs above: `.cargo/config.toml` pins AVX-512 + # for the one known production host and GitHub-hosted runners do not + # guarantee it. No Java test pins a backend name — `AbiContractTest` + # asserts only that one was reported, `FusionParityTest` merely notes + # it, and `DoctrineFenceTest` counts source lines — so the suite is + # backend-agnostic by construction and an AVX2 artifact gates the same + # behaviour. That is the `simdBackend()` "diagnostic only" rule holding. + rustflags: -D warnings -Ctarget-cpu=x86-64-v3 + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: lance-graph-java/native/lgj-abi + # The suite needs the real artifact. `AllTests` exits 2 when it is absent + # rather than reporting a failure — deliberately, so a missing library does + # not read as a broken test. In CI that distinction is moot: any non-zero + # exit fails the step, and here an absent artifact IS a defect. + - name: Build liblgj_abi.so (release) + working-directory: lance-graph-java/native/lgj-abi + run: cargo build --release + # `28-ea`, not `28`. JDK 28 is not GA: Adoptium's own + # /v3/info/available_releases lists it as most_recent_feature_version and + # NOT in available_releases. The `-ea` suffix is what makes setup-java ask + # for it — `normalizeVersion` sets `stable = false` on that suffix, and the + # temurin installer then queries `release_type=ea`, which serves + # jdk-28+16-ea-beta: the same build the development container runs. + # + # The version is deliberately NOT pinned to +16. This repo forbids internal + # head pins, and an upstream EA build is an external dependency whose whole + # purpose is to move; pinning it would mean a checksum bump per EA drop with + # nothing verified in exchange. What the suite actually requires is JEP 401 + # value classes under preview, and every 28 EA build carries those. + - name: Set up JDK 28 (early access) + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '28-ea' + # --enable-preview is for JEP 401 (value classes), NOT for Panama, which is + # final since 22. The flag is repo-wide and deliberate: mixing previewed and + # non-previewed classfiles is what poisons a build. + - name: Compile the core suite (main + test) + working-directory: lance-graph-java/java + run: | + javac --release 28 --enable-preview -d out \ + $(find src/main/java src/test/java -name '*.java') + # -Dlgj.library is an EXPLICIT request, and `Abi.locateLibrary` refuses to + # fall back to a search path when it cannot be honoured. That is what stops + # this job from silently measuring some other artifact than the one the step + # above just built. + - name: Run the core suite (AllTests) + working-directory: lance-graph-java/java + run: | + java --enable-preview --enable-native-access=ALL-UNNAMED \ + -Dlgj.library="$GITHUB_WORKSPACE/lance-graph-java/native/lgj-abi/target/release/liblgj_abi.so" \ + -cp out com.adaworldapi.lancegraph.AllTests + - name: Compile the consumer suites + working-directory: lance-graph-java + run: | + javac --release 28 --enable-preview -cp java/out -d consumers/out \ + $(find consumers -name '*.java') + # FOUR mains, not three. `trades` carries two — an allocation floor and a + # parity suite — and running only one of them would be the + # no-gate-with-extra-steps outcome: compiling a consumer proves it builds, + # never that its assertions hold. + - name: Run the consumer suites + working-directory: lance-graph-java + run: | + for main in com.adaworldapi.bricks.BricksAuthTest \ + com.adaworldapi.graph.GraphHopTest \ + com.adaworldapi.trades.TradesAllocationTest \ + com.adaworldapi.trades.TradesParityTest ; do + echo "::group::$main" + java --enable-preview --enable-native-access=ALL-UNNAMED \ + -Dlgj.library="$GITHUB_WORKSPACE/lance-graph-java/native/lgj-abi/target/release/liblgj_abi.so" \ + -cp "java/out:consumers/out" "$main" + echo "::endgroup::" + done diff --git a/.gitignore b/.gitignore index d822913..317c5ea 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ Cargo.lock.bak *.class /java/out/ /java/target/ +# The java-suites CI job's consumer class tree (it compiles consumers/**/src +# against java/out). Pure build residue, same footing as /java/out/. +/consumers/out/ /bench/out/ # gate-run.sh's two API builds (before/after) + its bench build. Its results/gate-*.csv are # NOT ignored: a real gate run is banked deliberately, by the results commit §5 requires. diff --git a/CLAUDE.md b/CLAUDE.md index 484446e..73e9e44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,21 @@ all report `isValue() == true` at runtime, and the full suite is **409 checks, 0 failures** under `--release 28 --enable-preview` against a freshly built `liblgj_abi.so` (abi 0.11, `ndarray::simd avx512`) — byte-for-byte the same 409 as the unflipped baseline on the same JDK. `bench/run.sh` carries the pin -and the flag. Measured facts and the obtain route (most JDK hosts are +and the flag. + +**THE CURRENT FIGURE LIVES HERE, AND ONLY HERE.** Measured 2026-09-22 on +`origin/main` (`aef2382`): the core suite is **612 checks across 17 suites, +0 failures**, against `abi 0.12, ndarray::simd avx512, profile release`; the +four consumer mains add **153** (bricks 70, graph 68, trades 3 + 12). The 409 / +16-suite / abi-0.11 figures above and below are the 2026-09-19 and 2026-09-16 +measurements, true on their dates and kept as the dated evidence they are — the +Valhalla-flip comparison in particular is only meaningful against the baseline +it was taken against, so it must NOT be restated forward. ⊘ Every OTHER site in +this file that carried a live count has had the number REMOVED rather than +re-pinned: one measurement restated in eight places goes stale in eight places, +which is exactly what happened here (the count had drifted 409 -> 612 and the +file still briefed every session with 409). A session that needs the current +number runs the suite; the command is four minutes and is written out below. Measured facts and the obtain route (most JDK hosts are egress-blocked here; GitHub release *download* paths are not) live in `.claude/knowledge/jdk-toolchain-facts.md`. @@ -597,7 +611,7 @@ to PR on that basis. > ⊘ **The first version of this section, written earlier the same day, led with > the apt route and was itself misleading.** Re-checked: **`/opt/jdks/jdk-26.0.2` -> was present the whole time** (26.0.2.1), the suite runs `ALL PASSED (409 +> was present the whole time** (26.0.2.1), the suite ran `ALL PASSED (409 > checks)` on it, and `.claude/knowledge/jdk-toolchain-facts.md` had already > named that exact path. Nothing needed installing. The session looked at > `java -version` and `/usr/lib/jvm` — **neither of which sees `/opt/jdks`** — @@ -664,7 +678,7 @@ hunting a bug that is not there). So: build the `.so`, `javac`, `java`. # 1. the native artifact cd native/lgj-abi && cargo build --release # -> target/release/liblgj_abi.so -# 2. compile main + test together (56 files) +# 2. compile main + test together (58 files) J=/usr/lib/jvm/temurin-26-jdk-amd64/bin # or java-25-openjdk-amd64 find java/src/main java/src/test -name '*.java' > /tmp/srcs.txt $J/javac -d /tmp/jout @/tmp/srcs.txt @@ -689,7 +703,7 @@ the artifact you meant. | need | reachable here? | source | |---|---|---| | **Panama FFM** (`java.lang.foreign`, the whole `internal/ffm` membrane) | **YES** — final since 22 | Ubuntu `openjdk-25-jdk-headless`, or Adoptium `temurin-26-jdk` | -| the 16-suite `AllTests` run (409 checks) | **YES**, verified on 25 AND 26 | either of the above | +| the full `AllTests` run | **YES**, verified on 25 AND 26 | either of the above | | `valhalla-lab/src/stable` (records, JDK 26) | **YES** | Adoptium `temurin-26-jdk` | | `valhalla-lab/src/valhalla` (**value** records, JDK 27 EA) | not via apt | `https://jdk.java.net/valhalla/` — reachable, `HTTP 200` | @@ -700,12 +714,15 @@ is a Valhalla EA feature, not a mainline one. `https://jdk.java.net/valhalla/` **is reachable through this environment's proxy** (verified `HTTP 200`), so that build is a download away rather than a blocker. `bench/` and `valhalla-lab/` are measurement arms, NOT gates; the merge gate is the Rust -suite plus the 409-check Java run, and both are reachable here. +suite, the full `AllTests` run, and the four consumer mains — all three +dispatched by the `java-suites` job in `.github/workflows/lint.yml`, and all +three reachable here. (Before that job existed they were LOCAL gates and `main` +could be red unnoticed, which it was; see `ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE`.) **Consequence for every future session:** "the Java side could not be verified" is no longer an acceptable status line for this repo. The Rust half -(`cargo test` in `native/lgj-abi`) and the Java half (409 checks) are BOTH -runnable in this container, and a PR that claims the Java surface is unverified +(`cargo test` in `native/lgj-abi`) and the Java half (`AllTests`, plus the four +consumer mains) are BOTH runnable in this container, and a PR that claims the Java surface is unverified is claiming something that takes about four minutes to falsify. ## Missing-capability STOP rule From be46705da19d8eae7bb91d7253c935ea8dc4e00f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:28:15 +0000 Subject: [PATCH 2/7] ci: collect consumer failures instead of stopping at the first A `run:` step is `bash -e`, so a bare `java` in the loop aborts the whole step at the first red main and never reaches the rest -- verified locally: a three-member loop with the middle member failing exits 1 without running the third. The gate is still correct that way, but one broken consumer would MASK the other three, and each CI round would reveal exactly one of them. `AllTests` itself does not work like that: it runs every suite and exits 1 at the end. The loop now matches it -- `|| failed="$failed $main"` collects, and the step exits 1 with every failing main named in a `::error::` line. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .github/workflows/lint.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 92317c3..bb12e73 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -290,9 +290,18 @@ jobs: # parity suite — and running only one of them would be the # no-gate-with-extra-steps outcome: compiling a consumer proves it builds, # never that its assertions hold. + # + # NOT fail-fast, deliberately. A `run:` step is `bash -e`, and a bare + # `java` in this loop would abort the whole step at the first red main — + # verified, the loop does not reach its remaining members — so one broken + # consumer would mask the other three and each CI round would reveal + # exactly one of them. `|| failed=...` collects instead, and the step still + # exits non-zero. This mirrors `AllTests`, which runs every suite before + # exiting 1 rather than stopping at the first failure. - name: Run the consumer suites working-directory: lance-graph-java run: | + failed="" for main in com.adaworldapi.bricks.BricksAuthTest \ com.adaworldapi.graph.GraphHopTest \ com.adaworldapi.trades.TradesAllocationTest \ @@ -300,6 +309,11 @@ jobs: echo "::group::$main" java --enable-preview --enable-native-access=ALL-UNNAMED \ -Dlgj.library="$GITHUB_WORKSPACE/lance-graph-java/native/lgj-abi/target/release/liblgj_abi.so" \ - -cp "java/out:consumers/out" "$main" + -cp "java/out:consumers/out" "$main" || failed="$failed $main" echo "::endgroup::" done + if [ -n "$failed" ]; then + echo "::error::consumer suites FAILED:$failed" + exit 1 + fi + echo "all four consumer suites passed" From c4a2e8cd71517774ed3da9b23637295b59b381b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:29:16 +0000 Subject: [PATCH 3/7] board: record the falsifier run -- red-then-green on the verbatim step body The disable run ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE demands, executed on committed work so the `git checkout` restore could not eat it. The loop body was extracted from the committed workflow by a YAML parse rather than retyped, because a retyped approximation of a step is not evidence about the step. Under `bash -e`, with the stale `0` pin restored in `GraphHopTest`: all four mains ran, `::error::consumer suites FAILED: ...GraphHopTest` named the culprit, exit 1. With the pin back: 70 + 68 + 3 + 12, "all four consumer suites passed", exit 0. That is both properties from one run -- it catches the `bb81d80` defect (`1 FAILED, 67 passed`), and it does not stop at the first failure, so one red consumer cannot mask three others. One measurement-apparatus note, because it nearly produced a false green: the first restore check read `${PIPESTATUS[0]}` after an `&&` chain whose earlier `grep -c` had returned 1 on zero matches, so the chain short-circuited, the loop never ran, and the reported "exit 0" belonged to a different pipeline altogether. Re-run with the exit captured directly. A null-shaped green is a claim about the harness until the harness is checked. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .claude/board/ISSUES.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index e065e7e..533e5ac 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -81,11 +81,24 @@ Measured, all four command strings run verbatim: core **612** (0 failures, abi 0.12, avx512, release), bricks **70**, graph **68**, trades **3** and **12** — **765 checks across 5 entry points**, every one exit 0. -**FALSIFIER: PENDING at the time of this commit** — deliberately, because the -disable run must happen on committed work (a `git checkout` restore is what -ends it, and this repo has lost uncommitted work to exactly that). The run and -its result are recorded in the commit that follows this one. What a local run -CANNOT prove is +**FALSIFIER: RUN, red-then-green, in the shape this entry demanded.** The stale +`0` pin was re-introduced in `GraphHopTest` and the consumer step's `run:` body +— extracted VERBATIM from the committed workflow via a YAML parse, not retyped — +was executed under `bash -e`: + +| run | mains reached | step exit | +|---|---|---| +| stale `0` pin | all four; `::error::consumer suites FAILED: …GraphHopTest` | **1** | +| restored | bricks 70, graph 68, trades 3 + 12, "all four consumer suites passed" | **0** | + +Two properties, one run. It catches the exact defect that survived a merge +(`1 FAILED, 67 passed` — the `bb81d80` shape), AND it reaches every main +regardless: a bare `java` under `bash -e` aborts the step at the first red one, +so one broken consumer would have masked the other three and each CI round would +have revealed exactly one of them. `AllTests` does not behave that way either, +and now neither does this loop. + +What a local run CANNOT prove is dispatch: whether a runner starts the job and reaches JDK 28. That is proven by this PR's own CI run, which is the right falsifier because it is self-executing — and if `28-ea` fails to resolve on the runner, the job goes red From af58fbdcdda10005057a9e55cf67a74a0c13107d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:29:47 +0000 Subject: [PATCH 4/7] board: LATEST_STATE -- the Java gate is dispatched Records what exists now: the job, the acquisition chain and where each link was read, the 765-check measured baseline, the two counts that were wrong on the way in, the structural fix for the restated-measurement drift, and the red-then-green falsifier. Keeps one thing OPEN rather than closing it: dispatch is unproven locally and cannot be proven locally. Whether a runner starts the job and resolves `28-ea` is settled by this PR's own first run. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .claude/board/LATEST_STATE.md | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 1ad05d2..326dbf1 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,44 @@ +## 2026-09-22 — the Java gate is DISPATCHED, and one measurement stops being restated in eight places + +`ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE` is RESOLVED. `java-suites` in +`.github/workflows/lint.yml` builds `liblgj_abi.so`, then runs the four +`javac`/`java` command strings `java/README.md` documents, verbatim — no build +tool was introduced, there still is none by design. + +- **The issue's first task was acquisition, not the job**, and it resolves by a + mechanism this container does not use — which is why the entry was right to + refuse to generalise from the local ladder. Read from primary sources: + setup-java's `normalizeVersion` sets `stable = false` on an `-ea` suffix, its + temurin installer then asks `release_type=ea`, and Adoptium's API (queried + live) serves `jdk-28+16-ea-beta` — the same build this container runs. The + container's blocker was gateway-blocked distribution hosts; a runner never had + that constraint. NOT pinned to `+16`: internal head pins are forbidden, and + what the suite needs is JEP 401 under preview, which every 28 EA build carries. +- **Measured on `origin/main` (`aef2382`), every command verbatim:** core **612 + checks / 17 suites / 0 failures** (abi 0.12, avx512, release), bricks **70**, + graph **68**, trades **3** and **12** — **765 checks across 5 entry points**, + all exit 0. All of it was gating nothing. +- **Two counts were wrong on the way in.** FOUR consumer mains, not three + (`trades` carries two; running one of two would have been the + no-gate-with-extra-steps outcome the issue names). And root `CLAUDE.md` + briefed every session with `409 / 16 suites / abi 0.11`. Fixed + **structurally**, not re-pinned: the live count now exists in exactly ONE + dated place and every other site had its number REMOVED. One measurement + restated in eight places goes stale in eight places — which is what had + happened. The three surviving 409s are dated historical measurements kept as + evidence; the Valhalla-flip comparison is only meaningful against its own + baseline and must not be restated forward. +- **Falsifier run, red-then-green, on the step body extracted from the + committed YAML rather than retyped.** Stale `0` pin: all four mains reached, + `::error::` named the culprit, exit 1. Restored: exit 0. Not fail-fast + deliberately — a bare `java` under `bash -e` aborts at the first red main, so + one broken consumer would mask three others and each round would reveal one. + +**OPEN:** dispatch itself is unproven locally and cannot be — whether a runner +starts the job and resolves `28-ea` is proven only by this PR's own first run, +which is the right falsifier because it is self-executing. If `28-ea` does not +resolve there, the job goes red on the step that resolves it and says so. + ## 2026-09-22 — D-LGJ-FOLD-5: the consumer stops asking sixteen times, and a red pin on `main` gets root-caused The consumer half of minor 12. `BricksQuery.sumBy()` was sixteen queries — From 5f309746e34f1c77d7fefdcef3d52e03d5a4a3c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:33:55 +0000 Subject: [PATCH 5/7] board: the first run settles dispatch, and measures the backend claim `java-suites completed success` on af58fbd, 13/13 steps. The runner's log closes the acquisition chain by execution rather than by reading: `Downloading Java 28.0.0+16.0.ea (Temurin-Hotspot)` from `adoptium/temurin28-binaries/.../jdk-28+16-ea-beta`, the exact release the Adoptium API named, into the hosted toolcache. That was the one thing a local run structurally could not prove, so the issue's residue and LATEST_STATE's OPEN both close here. It also measured a claim that had only been reasoned about. The workflow comment argued the suite is backend-agnostic from READING the tests -- `AbiContractTest` asserts only that a backend was reported, `FusionParityTest` notes it, `DoctrineFenceTest` counts source lines. The runner reports `avx2 (x86-64-v3)` where this host reports `avx512`, and all 765 counts come back byte-identical: core 612, bricks 70, graph 68, trades 3 and 12. Read-from-tests is now observed-across-two-backends. What this job does NOT fix, named so it is not mistaken for closed: `lint.yml` fires on this repository only, so an upstream-only merge in a sibling cannot start it and the break stays invisible until someone pushes here. That is the trigger gap E-THE-CI-GAP-WAS-THE-TRIGGER-NOT-THE-COVERAGE-1 already names, and the Java half now inherits it too. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .claude/board/ISSUES.md | 19 +++++++++++++++---- .claude/board/LATEST_STATE.md | 20 ++++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 533e5ac..5c42178 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -99,10 +99,21 @@ have revealed exactly one of them. `AllTests` does not behave that way either, and now neither does this loop. What a local run CANNOT prove is -dispatch: whether a runner starts the job and reaches JDK 28. That is proven by -this PR's own CI run, which is the right falsifier because it is -self-executing — and if `28-ea` fails to resolve on the runner, the job goes red -on the step that resolves it and says so. +dispatch: whether a runner starts the job and reaches JDK 28. **That is now +SETTLED by the first run (#86, `af58fbd`): `java-suites completed success`, all +13 steps.** The runner's own log closes the acquisition chain end to end — +`Downloading Java 28.0.0+16.0.ea (Temurin-Hotspot) from +.../adoptium/temurin28-binaries/releases/download/jdk-28%2B16-ea-beta/...`, the +exact release the Adoptium API named, resolved into +`/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/28.0.0-ea.16.0.ea/x64`. + +It also MEASURED something that had only been reasoned about. The runner reports +`abi 0.12, simd ndarray::simd avx2 (x86-64-v3)` against this host's `avx512`, +and every count comes back byte-identical — core **612**, bricks **70**, graph +**68**, trades **3** and **12**. The `simdBackend()` "diagnostic only" rule was +asserted here from reading the tests (`AbiContractTest` asserts only that a +backend was reported, `FusionParityTest` notes it, `DoctrineFenceTest` counts +source lines); it is now observed across two different backends. ## ISS-LGJ-TOOLCHAIN-MUST-BE-JDK28-VALHALLA-PANAMA — UNBLOCKED; JDK 28 installed and the flip proven (2026-09-19) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 326dbf1..44ecdae 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -34,10 +34,22 @@ tool was introduced, there still is none by design. deliberately — a bare `java` under `bash -e` aborts at the first red main, so one broken consumer would mask three others and each round would reveal one. -**OPEN:** dispatch itself is unproven locally and cannot be — whether a runner -starts the job and resolves `28-ea` is proven only by this PR's own first run, -which is the right falsifier because it is self-executing. If `28-ea` does not -resolve there, the job goes red on the step that resolves it and says so. +**The one OPEN item CLOSED on the first run** (#86, `af58fbd`): +`java-suites completed success`, 13/13 steps. The runner downloaded +`Java 28.0.0+16.0.ea (Temurin-Hotspot)` from +`adoptium/temurin28-binaries/.../jdk-28+16-ea-beta` — the exact release the +Adoptium API had named, so the chain is closed by execution and not only by +reading. Bonus measurement: the runner reports `avx2 (x86-64-v3)` where this +host reports `avx512`, and all 765 counts are byte-identical, which turns the +backend-agnostic claim from something read out of the tests into something +observed across two backends. + +**OPEN:** nothing on this. The remaining gap is elsewhere and unchanged — +`lint.yml` fires `on: {pull_request, push}` for THIS repository only, so an +upstream-only merge in `lance-graph` or `ndarray` cannot start the workflow and +a sibling break stays invisible until someone pushes here. That is the trigger +gap `E-THE-CI-GAP-WAS-THE-TRIGGER-NOT-THE-COVERAGE-1` already names; this job +inherits it rather than fixing it, and now inherits it for the Java half too. ## 2026-09-22 — D-LGJ-FOLD-5: the consumer stops asking sixteen times, and a red pin on `main` gets root-caused From 2a300398611fcc86ec0703f805508f9c6c9feae6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:46:27 +0000 Subject: [PATCH 6/7] review: no persisted checkout credentials, and the documented command runs Two CodeRabbit findings on #86, both correct and both mine. 1. SECURITY -- persisted checkout credentials (zizmor `artipacked`). On a `pull_request`, checkout leaves the workflow token in the clone's git config, and every job here runs `cargo`, which executes any `build.rs` the PR added -- a read path to the token before post-job cleanup. Fixed on ALL THIRTEEN checkout steps, not the four the comment named. The other nine were exposed identically; fixing a third of a uniform hole would have guaranteed the same finding on the next PR that touched those jobs. Verified free rather than assumed: no step in this file pushes, the workflow declares `contents: read`, nothing reads a secret, and the sibling checkouts are public reads -- so nothing needed a persisted credential. `persist-credentials` had appeared NOWHERE in the file before this. 2. The documented suite command could not run. My own paragraph one screen up says "a session that needs the current number runs the suite; the command is written out below", and the command below selected JDK 25/26 without the preview flags. Six types in java/src/main are `public value record`, so measured it fails: `error: value classes are a preview feature and are disabled by default`, exit 1. Fixing it surfaced three more defects in the same block, each measured: - `/usr/lib/jvm` now holds ONLY Java 21 -- `temurin-26-jdk-amd64` does not exist here at all, and `/opt/jdks` is down to `jdk-28+16` (26 and the 27 Valhalla EA build are gone). So the reachability table's "verified on 25 AND 26" is DATED EVIDENCE, not a description of this container; it is now labelled as such rather than re-pinned, the same treatment the 409-vs-612 drift got. - The block mixed working directories: step 1 `cd`-ed into native/lgj-abi and step 2 then used root-relative paths, so running it top-to-bottom died on `find: 'java/src/main': No such file or directory`. - `-Dlgj.library=$PWD/target/release/...` was ambiguous. From the repo root it names an incidental leftover `target/` (there is no root Cargo.toml) that is a SEPARATE inode from what step 1 builds -- byte-identical today, free to diverge, absent in a fresh clone. An ambiguous path is precisely what `locateLibrary`'s no-fallback rule exists to rule out. And one defect I INTRODUCED while fixing that, caught only by executing the block: replacing the `cd` with `--manifest-path` broke the toolchain, because rustup reads `rust-toolchain.toml` from the CWD, not from the manifest -- `error: rustc 1.94.1 is not supported ... requires rustc 1.97`, exit 101. The `cd` was load-bearing; it is back, in a subshell, with the reason recorded. Same trap as `setup-rust-toolchain`'s `rust-src-dir` input, one layer down. Also nearly filed a FALSE finding: the two `.so` paths have different mtimes, which read as a stale artifact. They are md5-identical. Checking the content instead of the timestamps is what stopped it. Verified by extracting the fenced block from CLAUDE.md by parse -- not retyped -- and running it under `bash -e` from the repo root: exit 0, `ALL PASSED (612 checks)`, and the runtime line names native/lgj-abi/target/release, proving it loaded the artifact step 1 built. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .github/workflows/lint.yml | 26 +++++++++++++++++++ CLAUDE.md | 52 ++++++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bb12e73..8d1d088 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,8 +33,22 @@ jobs: run: working-directory: lance-graph-java/native/lgj-abi steps: + # `persist-credentials: false` on EVERY checkout in this file (13 of them). + # Without it, a `pull_request` checkout leaves the workflow token in the + # clone's local git config, and every job here then runs `cargo`, which + # executes any `build.rs` the PR added — a read path to that token before + # the post-job cleanup. Flagged by zizmor as `artipacked`; raised by + # CodeRabbit against the `java-suites` checkouts on #86, and the other + # nine were already exposed identically, so this is the root-cause fix + # rather than four of thirteen. + # + # Provably free: no step in this file pushes, the workflow declares + # `contents: read`, and nothing reads a secret — so nothing here needs a + # persisted credential, and the sibling checkouts of ndarray / + # lance-graph / OGAR are public reads that never used one. - uses: actions/checkout@v4 with: + persist-credentials: false path: lance-graph-java - uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -76,20 +90,24 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: lance-graph-java - name: Checkout AdaWorldAPI/ndarray (sibling dependency) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/ndarray path: ndarray - name: Checkout AdaWorldAPI/lance-graph (sibling dependency, lance-graph-contract) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/lance-graph path: lance-graph - name: Checkout AdaWorldAPI/OGAR (sibling dependency, optional ogar-classview feature) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/OGAR path: OGAR - uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -133,20 +151,24 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: lance-graph-java - name: Checkout AdaWorldAPI/ndarray (sibling dependency) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/ndarray path: ndarray - name: Checkout AdaWorldAPI/lance-graph (sibling dependency, lance-graph-contract) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/lance-graph path: lance-graph - name: Checkout AdaWorldAPI/OGAR (sibling dependency, optional ogar-classview feature) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/OGAR path: OGAR - uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -205,6 +227,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: lance-graph-java # Same three siblings as `rust-test`, for the same reason: cargo resolves # the whole graph including the inactive optional path dep, so all three @@ -212,16 +235,19 @@ jobs: - name: Checkout AdaWorldAPI/ndarray (sibling dependency) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/ndarray path: ndarray - name: Checkout AdaWorldAPI/lance-graph (sibling dependency, lance-graph-contract) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/lance-graph path: lance-graph - name: Checkout AdaWorldAPI/OGAR (sibling dependency, optional ogar-classview feature) uses: actions/checkout@v4 with: + persist-credentials: false repository: AdaWorldAPI/OGAR path: OGAR - uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/CLAUDE.md b/CLAUDE.md index 73e9e44..2540b83 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -675,22 +675,58 @@ nothing ran** — a missing library reported as a failure would send the reader hunting a bug that is not there). So: build the `.so`, `javac`, `java`. ```sh -# 1. the native artifact -cd native/lgj-abi && cargo build --release # -> target/release/liblgj_abi.so - -# 2. compile main + test together (58 files) -J=/usr/lib/jvm/temurin-26-jdk-amd64/bin # or java-25-openjdk-amd64 +# Every path below is relative to the REPO ROOT; stay there for all three steps. +# (The block used to `cd native/lgj-abi` in step 1 and then use root-relative +# paths in step 2, so running it top-to-bottom failed on +# `find: 'java/src/main': No such file or directory`.) +SO=$PWD/native/lgj-abi/target/release/liblgj_abi.so + +# 1. the native artifact. The `cd` is LOAD-BEARING and must stay: rustup reads +# `rust-toolchain.toml` from the CWD, and that file lives in native/lgj-abi, +# so `--manifest-path` alone builds with whatever rustc is ambient -- measured +# 2026-09-22, `error: rustc 1.94.1 is not supported ... requires rustc 1.97`, +# exit 101. Same trap as `setup-rust-toolchain`'s `rust-src-dir` input in +# .github/workflows/lint.yml, one layer down. The subshell keeps the CWD from +# leaking into step 2, which is root-relative. +( cd native/lgj-abi && cargo build --release ) + +# 2. compile main + test together (58 files). BOTH flags are required, not +# stylistic: six types in java/src/main are `public value record`, so a +# javac without --enable-preview fails with "value classes are a preview +# feature and are disabled by default" (measured 2026-09-22, exit 1). And +# the JDK must be 28 -- see the inventory note below before reaching for a +# /usr/lib/jvm path. +J=/opt/jdks/jdk-28+16/bin find java/src/main java/src/test -name '*.java' > /tmp/srcs.txt -$J/javac -d /tmp/jout @/tmp/srcs.txt +$J/javac --release 28 --enable-preview -d /tmp/jout @/tmp/srcs.txt # 3. run. `-Dlgj.library` is an EXPLICIT request and `Abi.locateLibrary` # refuses to fall back to a search path if it cannot be honoured — by # design, so a run can never silently measure a different artifact. -$J/java --enable-native-access=ALL-UNNAMED \ - -Dlgj.library=$PWD/target/release/liblgj_abi.so \ +# $PWD/target/release/... was the OLD spelling and was ambiguous: from +# native/lgj-abi it is right, but from the repo root it names an incidental +# leftover `target/` (there is no root Cargo.toml) that is a SEPARATE inode +# from the artifact step 1 builds — byte-identical today, free to diverge, +# and absent entirely in a fresh clone. An ambiguous path is exactly what +# the no-fallback rule exists to rule out, so $SO is absolute. +$J/java --enable-preview --enable-native-access=ALL-UNNAMED \ + -Dlgj.library="$SO" \ -cp /tmp/jout com.adaworldapi.lancegraph.AllTests ``` +> **⊘ CONTAINER JDK INVENTORY, measured 2026-09-22 — the 25/26 routes below are +> RECOVERY instructions, not a description of this container.** `/opt/jdks` +> holds exactly `jdk-28+16` (`jdk-26.0.2` and the `jdk-27` Valhalla EA build +> are both GONE), and `/usr/lib/jvm` holds **only Java 21**, which cannot +> compile this repo at all — value records need 28. So the `temurin-26-jdk-amd64` +> path the command above used to name did not exist, and the reachability table +> further down reads "verified on 25 AND 26" as DATED EVIDENCE from 2026-09-16, +> never as a statement about what is installed now. The rung-1 check +> (`ls -d /opt/jdks/*/`) remains the answer; rungs 3-4 (apt) are what you run +> when rung 1 comes back empty. Same failure shape as the 409-vs-612 drift this +> file already carries: a present-tense claim about a measured environment goes +> stale silently, and the fix is to date it rather than to re-pin it forward. + **Measured 2026-09-16 on BOTH `Temurin 26.0.2.1` and `openjdk 25.0.4`: `ALL PASSED (409 checks)`** across all 16 suites, against `abi 0.11, simd ndarray::simd avx512, profile release` — identical on the two JDKs. The From b1189cb2144c63080204905caead7fb583e3caf0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 10:49:43 +0000 Subject: [PATCH 7/7] board: the credential fix is verified at runtime, not just in YAML `java-suites` is green on 2a30039 (`ALL PASSED (612 checks)`, all four consumer suites), so the persist-credentials change is proven not to break the four sibling checkouts. The stronger evidence is in the runner log: each checkout prints `persist-credentials: false`, then `Setting up auth`, then `Removing auth` INSIDE the checkout step. So auth exists only long enough to clone and is gone before the `cargo build` that can execute a repository-controlled `build.rs`. Without the flag there is no in-step `Removing auth` at all and the config survives to post-job cleanup, spanning every intervening step. That is the difference between asserting a config value and measuring the mechanism. Also records the second finding's full chain, including the defect I introduced while fixing it and the false finding I nearly filed from mtimes alone. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG --- .claude/board/LATEST_STATE.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 44ecdae..079ad59 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -44,6 +44,35 @@ host reports `avx512`, and all 765 counts are byte-identical, which turns the backend-agnostic claim from something read out of the tests into something observed across two backends. +**Review follow-up (`2a30039`), both findings CodeRabbit-confirmed.** Two real +defects, both mine: + +- **Persisted checkout credentials** (zizmor `artipacked`). Fixed on ALL + THIRTEEN checkout steps, not the four named — `persist-credentials` appeared + nowhere in the file, every job runs `cargo`, so the `build.rs` read path was + identical in `format`/`clippy`/`rust-test`. Verified free first: nothing + pushes, `contents: read`, no secret read. **Verified at RUNTIME, not just in + YAML** — each of the four checkouts logs `persist-credentials: false`, then + `Setting up auth`, then `Removing auth` INSIDE the checkout step, so the + credential no longer spans the later `cargo build`. Without the flag there is + no in-step `Removing auth` and the config survives to post-job cleanup. +- **The documented suite command could not run.** My own pointer sentence aimed + at a JDK 25/26 command without the preview flags; measured `error: value + classes are a preview feature and are disabled by default`, exit 1. Fixing it + surfaced three more in the same block (`/usr/lib/jvm` now holds only Java 21 + so `temurin-26-jdk-amd64` does not exist here; the block mixed working + directories and died on `find: 'java/src/main'`; `-Dlgj.library=$PWD/target/...` + named an incidental leftover `target/` that is a separate inode from what + step 1 builds) — and one I INTRODUCED, caught only by executing it: + `--manifest-path` instead of `cd` broke the toolchain, because rustup reads + `rust-toolchain.toml` from the CWD (`rustc 1.94.1 is not supported`, exit + 101). The `cd` is load-bearing and is back in a subshell. Verified by + extracting the fenced block by PARSE and running it under `bash -e`: exit 0, + `ALL PASSED (612 checks)`. + I also nearly filed a FALSE finding — the two `.so` paths have different + mtimes, which read as a stale artifact; they are md5-identical. Checking + content instead of timestamps stopped it. + **OPEN:** nothing on this. The remaining gap is elsewhere and unchanged — `lint.yml` fires `on: {pull_request, push}` for THIS repository only, so an upstream-only merge in `lance-graph` or `ndarray` cannot start the workflow and