From 80577bd3a6de6e29e270ee1cfb260b0c9808bc66 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:02:54 +0000 Subject: [PATCH 01/10] fat-jar policy: describe llama 5.2.0's natives jars jllama's all-- jars are now plain merges of natives jars (no jllama-backends.txt manifest), the natives jars are declared in java-llama.cpp/.github/natives.csv, and srcmorph's classifier fat jars are the CPU fat jar plus one backend. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- policies/fat-jar-release-assets.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/policies/fat-jar-release-assets.md b/policies/fat-jar-release-assets.md index 970e67e..0eebd3d 100644 --- a/policies/fat-jar-release-assets.md +++ b/policies/fat-jar-release-assets.md @@ -157,8 +157,8 @@ model — it uses the already-cached 1.5B tool model on CPU, no download.) | Repo | Fat jar(s) | Kept off Central by | Built + signed by | |---|---|---|---| -| **jllama** (`java-llama.cpp`) | Multi-backend **`all--`** jars (default CPU + every GPU backend of that OS/arch in `net/ladenthin/llama////` subdirs, runtime-selected by `LlamaLoader` via the `jllama-backends.txt` manifest) + the default CPU fat jar | The Central `deploy` runs **without** the `assembly` profile; the fat jars are assembled by a separate `package-fatjars` job | `.github/package-fatjars.sh` assembles them; `.github/sign-fatjars.sh` GPG-signs each (`.asc`) in the `github-release-signed` / `github-snapshot` attach jobs (which declare `environment: maven-central` + `checkout`). `.sha256` **and** `.asc`. **Plus the agent jar** `llama-atmosphere-agent--jar-with-dependencies.jar` — built **without** the core (`-P assembly` of the standalone `llama-atmosphere-agent/` project, never deployed anywhere), uploaded by the model-free agent job and downloaded by the same attach jobs into the same directory, so the same `sign-fatjars.sh` run signs it. | -| **srcmorph** (`srcmorph-cli`) | One CLI fat jar **per `net.ladenthin:llama` classifier** (default all-platform CPU + one per GPU classifier: `cuda13-*`, `vulkan-*`, `opencl-*`, `rocm-*`, `sycl-*`, `openvino-*`, `msvc-windows`) named `srcmorph-cli--jar-with-dependencies[-].jar` | `srcmorph-cli/pom.xml` sets `false` on the assembly execution → built into `target/` but never installed/deployed | The `publish-{release,snapshot}` jobs loop over the classifier set (`mvn -pl srcmorph-cli -am -Dllama.classifier= package`), rename per classifier (default built **last** = unsuffixed CPU jar), collect them into the asset dir, then sign via `.github/sign-fatjars.sh`. `.asc` only. | +| **jllama** (`java-llama.cpp`) | Multi-backend **`all--`** jars (the default fat jar reduced to its own OS/arch, plus every natives jar of that OS/arch; each backend is its own `net/ladenthin/llama////` directory, tried by `LlamaLoader` in its fixed priority order, falling back to `cpu`) + the default CPU fat jar | The Central `deploy` runs **without** the `assembly` profile; the fat jars are assembled by a separate `package-fatjars` job | `.github/package-fatjars.sh` assembles them; `.github/sign-fatjars.sh` GPG-signs each (`.asc`) in the `github-release-signed` / `github-snapshot` attach jobs (which declare `environment: maven-central` + `checkout`). `.sha256` **and** `.asc`. **Plus the agent jar** `llama-atmosphere-agent--jar-with-dependencies.jar` — built **without** the core (`-P assembly` of the standalone `llama-atmosphere-agent/` project, never deployed anywhere), uploaded by the model-free agent job and downloaded by the same attach jobs into the same directory, so the same `sign-fatjars.sh` run signs it. | +| **srcmorph** (`srcmorph-cli`) | One CLI fat jar **per `net.ladenthin:llama` natives jar** besides the CPU ones (default all-platform CPU + one per GPU/`msvc` natives jar: `cuda13-*`, `vulkan-*`, `opencl-*`, `rocm-*`, `sycl-*`, `openvino-*`, `msvc-windows-*`), each the default jar **plus** that one backend (the CPU natives stay as the loader's fallback), named `srcmorph-cli--jar-with-dependencies[-].jar` | `srcmorph-cli/pom.xml` sets `false` on the assembly execution → built into `target/` but never installed/deployed | The `publish-{release,snapshot}` jobs loop over the classifier set (`mvn -pl srcmorph-cli -am -Dllama.classifier= package`), rename per classifier (default built **last** = unsuffixed CPU jar), collect them into the asset dir, then sign via `.github/sign-fatjars.sh`. `.asc` only. | | **BAF** (`BitcoinAddressFinder`) | **Single** fat jar (LWJGL ships one `natives-*` classifier jar per platform, but they may all sit on one classpath — LWJGL picks the match at runtime — so there is still no classifier split) | The Central `deploy` runs `-P release` **without** `assembly`; the fat jar is built by a **second** invocation that stops at `verify` (never reaching `deploy`), so `central-publishing`'s deploy-bound publish goal never runs | `mvn -P release,assembly verify` in the `publish-{release,snapshot}` jobs; `maven-gpg-plugin` (bound to `verify`) signs the attached fat jar → `.asc`. | | **sb** (`streambuffer`) | ➖ N/A — a pure library with no runnable entry point, so no fat jar is produced or shipped. Its **thin** jar is still smoke-tested before release (`smoke-jar`, see above) and still carries a `.asc` | — | — | @@ -176,12 +176,13 @@ its Java baseline. ## Keep-in-sync notes -- **jllama / srcmorph classifier lists.** The set of GPU classifiers is defined by - `java-llama.cpp/llama/pom.xml`'s `` entries. jllama's `package-fatjars.sh` - enumerates it from the pom and **fails loud** on any new classifier until it is consciously - ranked/excluded. srcmorph's `publish.yml` hardcodes the classifier array — **on a - `net.ladenthin:llama` version bump, re-check that list against the pom** (and confirm every - classifier is actually published on Central for the pinned version). +- **jllama / srcmorph classifier lists.** The natives jars are declared once, in + `java-llama.cpp/.github/natives.csv`; jllama's pom executions, `llama-platform`, workflow + artifacts and loader priority are checked against it (`check-natives.py`), and + `package-fatjars.sh` reads it. srcmorph's `publish.yml` hardcodes its classifier array (the rows + with `platform=no` except the Android ones) — **on a `net.ladenthin:llama` version bump, re-check + that array against `natives.csv`** (and confirm every natives jar is actually published on Central + for the pinned version); `verify-classifier-fatjars.sh` fails on an unmapped classifier shape. - **Shared signing script (`.github/sign-fatjars.sh`).** jllama and srcmorph sign their loose fat jars with a **byte-identical** `.github/sign-fatjars.sh` (dual-licensed `MIT OR Apache-2.0`, the cross-repo-synced-file convention) — it imports the key into an ephemeral keyring and produces a From e827354fb1636046e7f79c739a3367dbd97629e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:38:39 +0000 Subject: [PATCH 02/10] Shared files: link each repository's manifest and job instead of a hash table The byte-identical files are now listed, with their SHA-256, in each repository's .github/shared-files.sha256 and checked by its shared-files job (a copy changed alone fails, a sibling's differing copy warns). crossrepostatus.md links those instead of carrying its own table and manual sha256sum command, and records why copies with a checksum were chosen over a shared actions repository. ci-test-diagnostics section 3 points at the shared print-crash-logs.sh; the fat-jar and lombok policies point at the manifests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 70 +++++++++++++++--------------- policies/ci-test-diagnostics.md | 39 +++++------------ policies/fat-jar-release-assets.md | 19 ++++---- policies/lombok-config.md | 11 +++-- 4 files changed, 61 insertions(+), 78 deletions(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 2c89f64..cc71a72 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -59,46 +59,48 @@ Legend: ✅ done · 🚧 in progress · ❌ open · ➖ N/A · 📌 standing pol | ArchUnit standard set (`noSystemExit` / `noNewRandom` / `Thread.sleep` / sun-com.sun-jdk.internal bans / public-fields-final / `noTestFrameworksInProduction` / `noPackageCycles`) | All 4 ✅ | | `javac -Werror` + `-Xlint:all,-serial,-options,-classfile,-processing` | All 4 ✅ | | Full `layeredArchitecture()` + per-module banned-imports | BAF/jllama/srcmorph ✅ (flat root package split into layered packages, strict rule enforced per repo — see each repo's own `TODO.md` "Done"); sb ➖ (single package) | -| GPG signing-key preflight (gpg + Gradle/BouncyCastle) | All 4 wired byte-identically in `publish.yml`: two standalone jobs (no `needs:`, run in parallel at pipeline start, `environment: maven-central`) reproduce what `maven-gpg-plugin` and Gradle's `signing` plugin do at deploy/publish time, so a bad/expired key or wrong passphrase reds in seconds instead of failing the publish stage. Prints only public key metadata; **red-by-design on every `pull_request` run, not just fork PRs** — the jobs declare `environment: maven-central` and GitHub withholds environment secrets from any PR context, including a branch pushed to the repo itself. Verified on BAF run 33866409744, where both preflight jobs failed within a second on a same-repository Dependabot branch. Green evidence therefore only ever comes from a `push`/dispatch run. The `.github/signing-selftest/` Gradle project used by the second job is byte-identical across all 4 (checksums below). | -| Class-file floor on the built jars (`verify-bytecode-version.sh`) | All 4 wired in `publish.yml` with the **byte-identical** `.github/verify-bytecode-version.sh` (checksums below). `maven.compiler.release` governs only the code *we* compile; a dependency built for a newer Java lands in the jar untouched and surfaces as `UnsupportedClassVersionError` on a consumer's JVM. It has happened twice: **checker-qual 4.x** (Java 11, `@Retention(RUNTIME)`, so any reflection over an annotated element loads it) and **logback-classic 1.4.0+**, whose `LogbackServiceProvider` SLF4J's `ServiceLoader` loads at startup. `--max-major` is passed from the workflow so the ceiling lives next to the release it belongs to: **52** in jllama/srcmorph/sb, **65** in BAF (Java 21). Placement is per-repo, on whatever job first has the jars: jllama `package` + `smoke-fatjar-linux` (module jars *and* the reassembled all-backends asset), srcmorph/BAF `smoke-fatjar`, sb `smoke-jar` (its deliverable is the plain jar, not a fat jar). `module-info.class` and `META-INF/versions/**` are skipped unconditionally — a classpath JVM never loads either. Exit 2 on an empty scan, so a build that produced no jars cannot read as a pass. | +| GPG signing-key preflight (gpg + Gradle/BouncyCastle) | All 4 wired byte-identically in `publish.yml` (the gpg job's body is the shared `.github/verify-signing-key.sh`): two standalone jobs (no `needs:`, run in parallel at pipeline start, `environment: maven-central`) reproduce what `maven-gpg-plugin` and Gradle's `signing` plugin do at deploy/publish time, so a bad/expired key or wrong passphrase reds in seconds instead of failing the publish stage. Prints only public key metadata; **red-by-design on every `pull_request` run, not just fork PRs** — the jobs declare `environment: maven-central` and GitHub withholds environment secrets from any PR context, including a branch pushed to the repo itself. Verified on BAF run 33866409744, where both preflight jobs failed within a second on a same-repository Dependabot branch. Green evidence therefore only ever comes from a `push`/dispatch run. The `.github/signing-selftest/` Gradle project used by the second job is byte-identical across all 4 (checksums below). | +| Class-file floor on the built jars (`verify-bytecode-version.sh`) | All 4 wired in `publish.yml` with the **byte-identical** `.github/verify-bytecode-version.sh` (listed in each repo's `shared-files.sha256`, see below). `maven.compiler.release` governs only the code *we* compile; a dependency built for a newer Java lands in the jar untouched and surfaces as `UnsupportedClassVersionError` on a consumer's JVM. It has happened twice: **checker-qual 4.x** (Java 11, `@Retention(RUNTIME)`, so any reflection over an annotated element loads it) and **logback-classic 1.4.0+**, whose `LogbackServiceProvider` SLF4J's `ServiceLoader` loads at startup. `--max-major` is passed from the workflow so the ceiling lives next to the release it belongs to: **52** in jllama/srcmorph/sb, **65** in BAF (Java 21). Placement is per-repo, on whatever job first has the jars: jllama `package` + `smoke-fatjar-linux` (module jars *and* the reassembled all-backends asset), srcmorph/BAF `smoke-fatjar`, sb `smoke-jar` (its deliverable is the plain jar, not a fat jar). `module-info.class` and `META-INF/versions/**` are skipped unconditionally — a classpath JVM never loads either. Exit 2 on an empty scan, so a build that produced no jars cannot read as a pass. | | Informational steps in the `report` job never gate the release | **srcmorph ✅ · jllama / BAF / sb in open PRs as of 2026-09-09 (#424 / #364 / #157) — this row becomes "All 4 ✅" when those merge, and must not be read as parity before then.** Keep in sync; this one is easy to lose and expensive to lose. The rule: a third-party step in `report` that only *reports* (dependency graph, coverage upload) carries `continue-on-error: true`; a step that is a **gate** does not. **Why it is not cosmetic:** the pipelines are deliberately built so the GitHub assets land even when the Central publish fails — `github-snapshot`/`github-release*` run on `(needs.publish-*.result == 'success' \|\| == 'failure')`. That `if:` tolerates **`failure`** but not **`skipped`**, and `report` gates the chain: a failed `report` *skips* `check-snapshot` (its `if:` is a plain event/ref condition with no `always()`/`!cancelled()` escape), which skips `publish-snapshot`, which leaves `publish-snapshot.result == 'skipped'` — matching neither arm, so **`github-snapshot` does not run and the assets are lost**. An unguarded informational action therefore does not merely delay a Central publish; it silently defeats the one guarantee the release path exists to provide. **History — how it drifted:** srcmorph got the guard *in passing* from `ee2ae49` ("ci: report unsigned assets without ever withholding them"), whose actual feature was the unsigned-asset reporting. That feature **was** ported to the other three the same day (jllama `90dd21a`, BAF `2e14f5c`, sb `2ed13ff` — each a clean `68 insertions(+)` change), but the port reproduced the *feature*, not the *commit*, so the unrelated drive-by line never travelled and sat missing for nine days. Being closed by jllama #424, BAF #364, sb #157 — open at the time of writing. **What stays bare, on purpose:** `checkout`/`setup-java` (infrastructure, must work) and streambuffer's PIT steps (gates, meant to block). **Verifiable:** the 5-line block — rationale comment + `uses:` + flag — is byte-identical in all four; see the block-level check below. | -### Cross-repo byte-identical files — checksum drift check +### Cross-repo byte-identical files — the `shared-files` job -Files kept **byte-identical across repos** (sync any edit to every copy AND the hash here): +Files kept **byte-identical across repos** are listed, with their SHA-256, in **each repository's own +manifest** — that manifest is the reference for what must stay equal, and each repository's +`shared-files` job checks it on every run (a copy changed in one repository alone **fails**; a copy +another repository's default branch lists with a different hash **warns**, since a sync lands one +repository at a time). This file only links; the lists live next to the files: -| File | SHA-256 | Copies | +| Repo | Manifest | Job | |---|---|---| -| `.github/signing-selftest/build.gradle.kts` | `ab45f5c102b47dd16c325d4d9c283d158ba90c05f484eac45b2767885c4462f9` | all 4 repos | -| `.github/signing-selftest/settings.gradle.kts` | `9b2ea5b5ff8d48607e26e4e211ad6d496f7660e71c84e42caaa82b84f7001710` | all 4 repos | -| `.github/sign-fatjars.sh` | `3a240faac46c35d3ac4a11dc2969648e2134906b90a79b990ce2b713c7a96b36` | jllama + srcmorph (see [`policies/fat-jar-release-assets.md`](policies/fat-jar-release-assets.md)) | -| `.github/verify-bytecode-version.sh` | `88555f1ebe2ab52418b5ef628ffc078f132473b8000a3340b5bb73460a0b185d` | all 4 repos (class-file floor on the built jars; `--max-major` comes from the workflow, so the value lives next to the release it belongs to: 52 in the three Java 8 repos, 65 in BAF) | -| `.github/smoke-fatjar-cli.sh` | `4d1cc65cbd84a38f0d2015f55c0b2ba9027c72794b2c2ed578693a58d702efd2` | BAF + srcmorph (the two CLI fat jars; jllama's server/native smokes are repo-specific — see [`policies/fat-jar-release-assets.md`](policies/fat-jar-release-assets.md) "No release asset is attached that CI has not run") | -| `lombok.config` (jllama: `llama/lombok.config`) | `42f1842270af691bdfe561355bee4eb9ae326383f1852db19763abb888d6b90e` | the 3 Lombok repos: jllama + BAF + srcmorph (sb has no Lombok). Canonical content in [`policies/lombok-config.md`](policies/lombok-config.md) | -| `.github/ISSUE_TEMPLATE/bug_report.md` | `7232b092d3ba49b97bee7b539aaf6ee4c698e86bd3d4dd256e8ae2f85f653ee9` | all 4 repos | -| `.github/ISSUE_TEMPLATE/feature_request.md` | `0f08122e597f93dbbdc9c80e88984b4bf4738951d5902813df3d4640cdb11bac` | all 4 repos | -| `.github/PULL_REQUEST_TEMPLATE.md` | `ebfcc0adf59f5858bbe4dc077c906304a197f72a55256f0d5aac669bee5e871f` | all 4 repos | - -Verify from the `workspace` repo root (siblings checked out alongside): - -```bash -# Repo names are listed explicitly in each {…} — bash runs brace expansion BEFORE -# variable expansion, so a $var inside {…} would not expand into the repo list. -sha256sum ../{java-llama.cpp,BitcoinAddressFinder,srcmorph,streambuffer}/.github/signing-selftest/build.gradle.kts \ - ../{java-llama.cpp,BitcoinAddressFinder,srcmorph,streambuffer}/.github/signing-selftest/settings.gradle.kts \ - ../{java-llama.cpp,srcmorph}/.github/sign-fatjars.sh \ - ../{BitcoinAddressFinder,srcmorph}/.github/smoke-fatjar-cli.sh \ - ../{java-llama.cpp,BitcoinAddressFinder,srcmorph,streambuffer}/.github/verify-bytecode-version.sh \ - ../java-llama.cpp/llama/lombok.config ../{BitcoinAddressFinder,srcmorph}/lombok.config \ - ../{java-llama.cpp,BitcoinAddressFinder,srcmorph,streambuffer}/.github/ISSUE_TEMPLATE/bug_report.md \ - ../{java-llama.cpp,BitcoinAddressFinder,srcmorph,streambuffer}/.github/ISSUE_TEMPLATE/feature_request.md \ - ../{java-llama.cpp,BitcoinAddressFinder,srcmorph,streambuffer}/.github/PULL_REQUEST_TEMPLATE.md -# each file's copies must all show the hash in the table above; a mismatch = drift (re-sync) or an -# intentional edit (update every copy AND this table in the same change set). -``` +| java-llama.cpp | [`.github/shared-files.sha256`](https://github.com/bernardladenthin/java-llama.cpp/blob/main/.github/shared-files.sha256) | `shared-files` in [`publish.yml`](https://github.com/bernardladenthin/java-llama.cpp/blob/main/.github/workflows/publish.yml) | +| BitcoinAddressFinder | [`.github/shared-files.sha256`](https://github.com/bernardladenthin/BitcoinAddressFinder/blob/main/.github/shared-files.sha256) | `shared-files` in [`publish.yml`](https://github.com/bernardladenthin/BitcoinAddressFinder/blob/main/.github/workflows/publish.yml) | +| srcmorph | [`.github/shared-files.sha256`](https://github.com/bernardladenthin/srcmorph/blob/main/.github/shared-files.sha256) | `shared-files` in [`publish.yml`](https://github.com/bernardladenthin/srcmorph/blob/main/.github/workflows/publish.yml) | +| streambuffer | [`.github/shared-files.sha256`](https://github.com/bernardladenthin/streambuffer/blob/main/.github/shared-files.sha256) | `shared-files` in [`publish.yml`](https://github.com/bernardladenthin/streambuffer/blob/main/.github/workflows/publish.yml) | + +**Changing a shared file:** change it in every repository whose manifest lists it, then run +`python3 .github/check-shared-files.py --write` in each (it recomputes the hashes of the listed +files). Forgetting one is not a disaster — its job says so and the history shows it — but nobody can +edit a shared script without learning that it is shared. **Adding one:** put the file in each +repository and a `sha256sum` line for it in each manifest. + +**Why copies with a checksum, not a shared actions/library repository:** every consumer of a remote +action would pin it by SHA (Scorecard's pinned-dependencies rule), it would need a release process +of its own, and a checkout of a second repository in CI; copies keep each repository self-contained, +and the manifest makes the duplication explicit instead of accidental. The shared set: the build-check +library (`.github/buildcheck/{__init__,workflow,releasegate,sharedfiles}.py`, its tests, and the +`check-release-gate.py` / `check-shared-files.py` entry points), `print-crash-logs.sh`, +`verify-signing-key.sh`, `verify-bytecode-version.sh`, the signing self-test (`.github/signing-selftest/`), +the issue/PR templates, `sign-fatjars.sh` (jllama + srcmorph), `smoke-fatjar-cli.sh` (BAF + srcmorph) +and `lombok.config` (jllama at `llama/lombok.config`; matched by file name there). Canonical content +of `lombok.config` in [`policies/lombok-config.md`](policies/lombok-config.md). + +**The same job runs the release-gate check** (`check-release-gate.py`): every job of `publish.yml` +must gate both publish jobs, unless the repository's `.github/release-gate-exemptions.txt` names it +with a reason. Introducing it found `vmlens` gating nothing in all four repositories. **Block-level check — the `report` job's dependency-submission step.** Not a whole file, so it is -not in the table above, but it is kept byte-identical for the same reason and drifts the same way +not in the manifests above, but it is kept byte-identical for the same reason and drifts the same way (see the parity row "Informational steps in the `report` job never gate the release"). The 5 lines are the three-line rationale comment, the `uses:` line and `continue-on-error: true`: @@ -223,7 +225,7 @@ Differences below are intentional design decisions, not gaps to close. srcmorph (`config_Plan.json`, mock provider), jllama (`smoke-fatjar-linux`/`-windows` server smokes, plus `smoke-fatjar-macos` closing the gap that let a corrupt dylib ship), and — since 2026-09-01 — **sb (`smoke-jar`)**. BAF and srcmorph share a byte-identical - `.github/smoke-fatjar-cli.sh` (in the checksum table above); jllama keeps its own scripts because + `.github/smoke-fatjar-cli.sh` (listed in both repos' `shared-files.sha256`); jllama keeps its own scripts because its Main-Class is a server that never exits and the macOS assertion is native loadability, not a CLI exit code. Rule, rationale and the per-repo assertion table live in [`policies/fat-jar-release-assets.md`](policies/fat-jar-release-assets.md). Each smoke is diff --git a/policies/ci-test-diagnostics.md b/policies/ci-test-diagnostics.md index 7af4c98..9498174 100644 --- a/policies/ci-test-diagnostics.md +++ b/policies/ci-test-diagnostics.md @@ -88,31 +88,7 @@ Two equivalent shapes are in use (both correct): - name: Print crash logs (on failure) # § 3.1 — MUST precede the upload if: failure() shell: bash - run: | - shopt -s nullglob - found=0 - for f in ; do - found=1 - echo "===== $f (first 200 lines; full file in the uploaded artifact) =====" - sed -n '1,200p' "$f" - done - for f in ; do - found=1 - echo "===== $f =====" - cat "$f" - done - if [ "$found" = 0 ]; then - echo "No hs_err_pid*.log and no surefire dump/dumpstream was written." - echo - echo "For an ordinary test failure that is EXPECTED, not a finding: this step runs on" - echo "any job failure, and an assertion failure, a timeout or a compile error writes no" - echo "crash log. Read the surefire output above for the real cause." - echo - echo "It points at a JVM-level abort only if the log ALSO shows a fork ending abnormally" - echo "- 'The forked VM terminated without properly saying goodbye', or an exit with no" - echo "test results. In that case the abort bypassed the JVM error handler (a native" - echo "exit()/terminate() rather than a raised signal), which is why no file was written." - fi + run: bash .github/print-crash-logs.sh ... # default: . - name: Upload crash & surefire dumps if: failure() uses: actions/upload-artifact@v7 @@ -156,9 +132,16 @@ aborts the forked JVM on all six of jllama's Java test platforms, and the aborting frame was unreachable for exactly this reason while every other avenue (local reproduction) was also closed. -The step above therefore **precedes** the upload and echoes the same files. It -is `shell: bash` on every platform including Windows (GitHub's Windows runners -ship Git Bash), so one snippet covers the whole matrix. Two deliberate details: +The step above therefore **precedes** the upload and echoes the same files. The +logic is the shared script **`.github/print-crash-logs.sh`**, byte-identical in all +four repos (listed in each repo's `.github/shared-files.sha256`, checked by its +`shared-files` job) — it used to be pasted into every test job (eight copies in +jllama alone), and a copy edited in one place is exactly the drift the manifest now +catches. Arguments are the module directories to look in (`.` by default; srcmorph +passes its three modules, jllama `llama` or the sibling module the job tests). It +always exits 0, so it can never replace the job's real failure. It runs under +`shell: bash` on every platform including Windows (GitHub's Windows runners ship Git +Bash), so one line covers the whole matrix. Two deliberate details: - **hs_err is truncated to 200 lines, the dumps are not.** An hs_err's diagnostic core (fatal-error line, signal, problematic frame, Java frames, diff --git a/policies/fat-jar-release-assets.md b/policies/fat-jar-release-assets.md index 0eebd3d..4ac8dd8 100644 --- a/policies/fat-jar-release-assets.md +++ b/policies/fat-jar-release-assets.md @@ -132,7 +132,7 @@ BAF and srcmorph share a **byte-identical `.github/smoke-fatjar-cli.sh`** (` [args…]`, plain `java -jar`, no extra JVM flags — the contract under test is that the published artifact runs as-is). Both CLIs derive from the same `cli.Main` pattern and log `Main#run end.`, which is what lets the marker be identical too. **Sync any edit to both -copies and to the checksum table in [`../crossrepostatus.md`](../crossrepostatus.md).** jllama needs +copies, then `check-shared-files.py --write` in each** (both list it in `.github/shared-files.sha256`). jllama needs its own scripts: its Main-Class is a server that never exits, so "exit 0" is not a contract it can satisfy, and on macOS the assertion that matters is native loadability rather than any CLI behaviour. @@ -187,18 +187,17 @@ its Java baseline. jars with a **byte-identical** `.github/sign-fatjars.sh` (dual-licensed `MIT OR Apache-2.0`, the cross-repo-synced-file convention) — it imports the key into an ephemeral keyring and produces a verified detached armored `.asc` for every `*-jar-with-dependencies*.jar` in a directory. **Sync - any edit to both copies, and update the recorded checksum below** (same discipline as the - byte-identical `verify-signing-key` job). + any edit to both copies**; both repos list it in `.github/shared-files.sha256`, which their + `shared-files` job checks (same discipline as the shared `verify-signing-key.sh`). BAF does **not** use it: its single fat jar is an *attached* Maven artifact, so `maven-gpg-plugin` signs it directly during the `verify` run. - **Signature convention.** New fat-jar-shipping surfaces should sign with a detached armored `.asc` using the `maven-central`-scoped key, in a dispatch-gated job, reusing `sign-fatjars.sh`. -## Drift check — `sign-fatjars.sh` checksum +## Drift check — `sign-fatjars.sh` -The shared script must be **byte-identical** in both repos (jllama + srcmorph). Its canonical -SHA-256 and a one-line verify command live in the single consolidated drift-check table — -**"Cross-repo byte-identical files — checksum drift check"** in -[`../crossrepostatus.md`](../crossrepostatus.md) (alongside the `signing-selftest` `.kts` files). -On any intentional edit to `sign-fatjars.sh`, update both copies **and** that table in the same -change set. +The shared script must be **byte-identical** in both repos (jllama + srcmorph). Both list it in +`.github/shared-files.sha256`; each repo's `shared-files` job fails when its copy changed alone and +warns when the other repo's copy differs (see "Cross-repo byte-identical files" in +[`../crossrepostatus.md`](../crossrepostatus.md)). On an intentional edit, change both copies and run +`python3 .github/check-shared-files.py --write` in each. diff --git a/policies/lombok-config.md b/policies/lombok-config.md index f6a928d..9fe724d 100644 --- a/policies/lombok-config.md +++ b/policies/lombok-config.md @@ -123,10 +123,9 @@ supplement rather than weakening the workspace policy. The file lives in each sibling repo's root (jllama: `llama/lombok.config`) and the canonical content above is the source of truth. Since 2026-07-24 all three copies are **byte-identical** to this block — the repo-specific rationale that used to drift in -per-file comments now lives only here (that is why the file can be identical). The SHA-256 -is recorded in the **"Cross-repo byte-identical files — checksum drift check"** table in -[`../crossrepostatus.md`](../crossrepostatus.md), verifiable from the workspace repo. +per-file comments now lives only here (that is why the file can be identical). Each of the three +repos lists it in `.github/shared-files.sha256` (jllama as `llama/lombok.config`), and its +`shared-files` job fails when the copy changed alone and warns when another repo's differs. -Manual sync is the only mechanism today — when this canonical content changes, update every -sibling repo's `lombok.config` **and** the checksum row in `../crossrepostatus.md` in the same -series of commits. +When this canonical content changes, update every sibling repo's `lombok.config` and run +`python3 .github/check-shared-files.py --write` in each, in the same series of commits. From 2713b1b5bd70c6ec850ea8e603c3d120d7e5f484 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:54:12 +0000 Subject: [PATCH 03/10] jllama fat-jar smokes are now one smoke-fatjar matrix Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 6 +++--- policies/fat-jar-release-assets.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index cc71a72..11b4dd6 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -60,7 +60,7 @@ Legend: ✅ done · 🚧 in progress · ❌ open · ➖ N/A · 📌 standing pol | `javac -Werror` + `-Xlint:all,-serial,-options,-classfile,-processing` | All 4 ✅ | | Full `layeredArchitecture()` + per-module banned-imports | BAF/jllama/srcmorph ✅ (flat root package split into layered packages, strict rule enforced per repo — see each repo's own `TODO.md` "Done"); sb ➖ (single package) | | GPG signing-key preflight (gpg + Gradle/BouncyCastle) | All 4 wired byte-identically in `publish.yml` (the gpg job's body is the shared `.github/verify-signing-key.sh`): two standalone jobs (no `needs:`, run in parallel at pipeline start, `environment: maven-central`) reproduce what `maven-gpg-plugin` and Gradle's `signing` plugin do at deploy/publish time, so a bad/expired key or wrong passphrase reds in seconds instead of failing the publish stage. Prints only public key metadata; **red-by-design on every `pull_request` run, not just fork PRs** — the jobs declare `environment: maven-central` and GitHub withholds environment secrets from any PR context, including a branch pushed to the repo itself. Verified on BAF run 33866409744, where both preflight jobs failed within a second on a same-repository Dependabot branch. Green evidence therefore only ever comes from a `push`/dispatch run. The `.github/signing-selftest/` Gradle project used by the second job is byte-identical across all 4 (checksums below). | -| Class-file floor on the built jars (`verify-bytecode-version.sh`) | All 4 wired in `publish.yml` with the **byte-identical** `.github/verify-bytecode-version.sh` (listed in each repo's `shared-files.sha256`, see below). `maven.compiler.release` governs only the code *we* compile; a dependency built for a newer Java lands in the jar untouched and surfaces as `UnsupportedClassVersionError` on a consumer's JVM. It has happened twice: **checker-qual 4.x** (Java 11, `@Retention(RUNTIME)`, so any reflection over an annotated element loads it) and **logback-classic 1.4.0+**, whose `LogbackServiceProvider` SLF4J's `ServiceLoader` loads at startup. `--max-major` is passed from the workflow so the ceiling lives next to the release it belongs to: **52** in jllama/srcmorph/sb, **65** in BAF (Java 21). Placement is per-repo, on whatever job first has the jars: jllama `package` + `smoke-fatjar-linux` (module jars *and* the reassembled all-backends asset), srcmorph/BAF `smoke-fatjar`, sb `smoke-jar` (its deliverable is the plain jar, not a fat jar). `module-info.class` and `META-INF/versions/**` are skipped unconditionally — a classpath JVM never loads either. Exit 2 on an empty scan, so a build that produced no jars cannot read as a pass. | +| Class-file floor on the built jars (`verify-bytecode-version.sh`) | All 4 wired in `publish.yml` with the **byte-identical** `.github/verify-bytecode-version.sh` (listed in each repo's `shared-files.sha256`, see below). `maven.compiler.release` governs only the code *we* compile; a dependency built for a newer Java lands in the jar untouched and surfaces as `UnsupportedClassVersionError` on a consumer's JVM. It has happened twice: **checker-qual 4.x** (Java 11, `@Retention(RUNTIME)`, so any reflection over an annotated element loads it) and **logback-classic 1.4.0+**, whose `LogbackServiceProvider` SLF4J's `ServiceLoader` loads at startup. `--max-major` is passed from the workflow so the ceiling lives next to the release it belongs to: **52** in jllama/srcmorph/sb, **65** in BAF (Java 21). Placement is per-repo, on whatever job first has the jars: jllama `package` + the Linux rows of `smoke-fatjar` (module jars *and* the reassembled all-backends asset), srcmorph/BAF `smoke-fatjar`, sb `smoke-jar` (its deliverable is the plain jar, not a fat jar). `module-info.class` and `META-INF/versions/**` are skipped unconditionally — a classpath JVM never loads either. Exit 2 on an empty scan, so a build that produced no jars cannot read as a pass. | | Informational steps in the `report` job never gate the release | **srcmorph ✅ · jllama / BAF / sb in open PRs as of 2026-09-09 (#424 / #364 / #157) — this row becomes "All 4 ✅" when those merge, and must not be read as parity before then.** Keep in sync; this one is easy to lose and expensive to lose. The rule: a third-party step in `report` that only *reports* (dependency graph, coverage upload) carries `continue-on-error: true`; a step that is a **gate** does not. **Why it is not cosmetic:** the pipelines are deliberately built so the GitHub assets land even when the Central publish fails — `github-snapshot`/`github-release*` run on `(needs.publish-*.result == 'success' \|\| == 'failure')`. That `if:` tolerates **`failure`** but not **`skipped`**, and `report` gates the chain: a failed `report` *skips* `check-snapshot` (its `if:` is a plain event/ref condition with no `always()`/`!cancelled()` escape), which skips `publish-snapshot`, which leaves `publish-snapshot.result == 'skipped'` — matching neither arm, so **`github-snapshot` does not run and the assets are lost**. An unguarded informational action therefore does not merely delay a Central publish; it silently defeats the one guarantee the release path exists to provide. **History — how it drifted:** srcmorph got the guard *in passing* from `ee2ae49` ("ci: report unsigned assets without ever withholding them"), whose actual feature was the unsigned-asset reporting. That feature **was** ported to the other three the same day (jllama `90dd21a`, BAF `2e14f5c`, sb `2ed13ff` — each a clean `68 insertions(+)` change), but the port reproduced the *feature*, not the *commit*, so the unrelated drive-by line never travelled and sat missing for nine days. Being closed by jllama #424, BAF #364, sb #157 — open at the time of writing. **What stays bare, on purpose:** `checkout`/`setup-java` (infrastructure, must work) and streambuffer's PIT steps (gates, meant to block). **Verifiable:** the 5-line block — rationale comment + `uses:` + flag — is byte-identical in all four; see the block-level check below. | ### Cross-repo byte-identical files — the `shared-files` job @@ -222,8 +222,8 @@ Differences below are intentional design decisions, not gaps to close. - **No release asset is attached that CI has not run** (✅ landed in **all four** repos). Every repo that attaches a release asset now launches it in a `smoke-*` job that gates both publish jobs: BAF (`config_AddressFilesToLMDB.json`, also exercises the lmdbjava natives), - srcmorph (`config_Plan.json`, mock provider), jllama (`smoke-fatjar-linux`/`-windows` server - smokes, plus `smoke-fatjar-macos` closing the gap that let a corrupt dylib ship), and — since + srcmorph (`config_Plan.json`, mock provider), jllama (the `smoke-fatjar` matrix, one server smoke per + all-backends jar, plus `smoke-fatjar-macos` closing the gap that let a corrupt dylib ship), and — since 2026-09-01 — **sb (`smoke-jar`)**. BAF and srcmorph share a byte-identical `.github/smoke-fatjar-cli.sh` (listed in both repos' `shared-files.sha256`); jllama keeps its own scripts because its Main-Class is a server that never exits and the macOS assertion is native loadability, not a diff --git a/policies/fat-jar-release-assets.md b/policies/fat-jar-release-assets.md index 4ac8dd8..60889dd 100644 --- a/policies/fat-jar-release-assets.md +++ b/policies/fat-jar-release-assets.md @@ -123,7 +123,7 @@ would be worse than the duplication it saves: |---|---|---|---| | BAF | `smoke-fatjar` | `.github/smoke-fatjar-cli.sh` → `config_AddressFilesToLMDB.json` | exit 0 + `Main#run end.`; also exercises the **lmdbjava natives** out of the jar | | srcmorph | `smoke-fatjar` | `.github/smoke-fatjar-cli.sh` → `config_Plan.json` | exit 0 + `Main#run end.`; `mock` provider, so no GGUF/GPU/network | -| jllama | `smoke-fatjar-linux` / `-windows` | `smoke-test-fatjar.{sh,ps1}` → real `java -jar` server | `/health` 200 + a `/v1/chat/completions` choice + the backend-selection log line | +| jllama | `smoke-fatjar` (matrix, one row per `all--` jar) | `smoke-test-fatjar.{sh,ps1}` → real `java -jar` server | `/health` 200 + a `/v1/chat/completions` choice + the backend-selection log line | | jllama | `smoke-fatjar-macos` | `smoke-native-macos.sh` → `codesign` + `NativeLoadSmoke.java` | signature matches its own pages + the JVM loads the dylib and crosses JNI | | jllama | `smoke-agent-linux` | `smoke-agent-jar.sh` → `java -jar` on the agent jar **next to** the `all-linux-x86-64` fat jar | bytecode ≤ 65 + the jar alone fails for the missing core + `--help` + a one-shot answer + a `read_file` round surfacing a marker (cached tool model) | | sb | `smoke-jar` | `smoke-jar.sh` → `java -cp StreamBufferSmoke.java` | jar carries `module-info.class` + a real write/read/EOF round-trip through the API, exit 0 + marker | From 1f040139931718fecfde3c7d08f3f113fea3577c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 06:34:03 +0000 Subject: [PATCH 04/10] crossrepostatus: shared-files also checks identical workflow jobs Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 11b4dd6..a0c0437 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -80,7 +80,11 @@ repository at a time). This file only links; the lists live next to the files: **Changing a shared file:** change it in every repository whose manifest lists it, then run `python3 .github/check-shared-files.py --write` in each (it recomputes the hashes of the listed -files). Forgetting one is not a disaster — its job says so and the history shows it — but nobody can +files). **Jobs, too:** an entry `.github/workflows/publish.yml#` hashes one job of the workflow, so +the jobs kept identical inside four otherwise different `publish.yml` files (`startgate`, +`shared-files`, `verify-signing-key`, `check-snapshot`, `check-tag`; in the three Maven-only repos also +`verify-signing-key-gradle`, `github-snapshot`, `github-release`) are checked like files instead of by +convention. Forgetting one is not a disaster — its job says so and the history shows it — but nobody can edit a shared script without learning that it is shared. **Adding one:** put the file in each repository and a `sha256sum` line for it in each manifest. From fcd660a2e7f52c6b94cd3da5dda914fdcd15b1f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 06:37:31 +0000 Subject: [PATCH 05/10] crossrepostatus: the tool-version identity is now checked (check-versions.py) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index a0c0437..99a139b 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -48,7 +48,7 @@ Legend: ✅ done · 🚧 in progress · ❌ open · ➖ N/A · 📌 standing pol |---|---| | Error Prone `-Xep::ERROR` promotions | Identical 13-pattern set in all 4 poms | | NullAway `-XepOpt` options | Identical 6 standard options (`CheckOptionalEmptiness`, `AcknowledgeRestrictiveAnnotations`, `AcknowledgeAndroidRecent`, `AssertsEnabled`, `OnlyNullMarked`, strict JSpecify). Plugin additionally has `ExcludedFieldAnnotations=…@Parameter,@Component` — correct repo-local exception for Mojo POJOs. | -| Tool versions | Identical across all 4 (re-derived from every pom on `origin/main` 2026-09-27, not copied forward): Checker 4.2.3, fb-contrib 7.7.4, findsecbugs 1.14.0, spotbugs-maven-plugin 4.10.4.1, spotless 3.10.3, palantir 2.99.0, errorprone 2.50.0, nullaway 0.14.2, surefire 3.6.0, archunit 1.5.1, junit-jupiter 6.1.3, hamcrest 3.0, pitest-maven 1.30.0 (pitest-junit5-plugin 1.2.3). **All latest stable** — this row is the **canonical cross-repo tool-version matrix** (policy files point here rather than re-pinning). See "Dependency / plugin freshness" below for how this is kept current. **This row has now gone stale twice, the same way both times, and the note added after the first occurrence did not prevent the second — so treat the numbers here as evidence only when the audit date above is recent.** 2026-08-29: spotless 3.10.0, palantir 2.96.0, pitest-maven 1.25.9 and nullaway 0.13.8 were behind while the row claimed "all latest stable". 2026-09-05: Checker 4.2.2, spotbugs 4.10.4.0, spotless 3.10.1, nullaway 0.14.0 and surefire 3.5.6 were behind, again under the same claim. 2026-09-20: palantir 2.97.0 was behind (→ 2.98.0), a third time under the same claim. 2026-09-27: spotless 3.10.2, palantir 2.98.0, nullaway 0.14.1 and archunit 1.5.0 were behind — a fourth time; NullAway and palantir are exactly the two the versions plugin cannot see (annotation-processor path and a formatter version inside a plugin ``), so only the direct `maven-metadata.xml` probe caught them. Every time the *identity* half held (all four repos really did carry the same values) and only the recorded numbers lagged. The mechanism is the same each time: a per-repo Dependabot bump lands in one repo, the other three are brought back in step by a follow-up commit, and nothing in that sequence touches this file. **What would actually fix it is a check that derives this row from the poms rather than a reminder to update it by hand** — until that exists, re-derive before citing (the loop under "Dependency / plugin freshness" does it in one command). Every value above was additionally cross-checked against Maven Central's own `maven-metadata.xml` on 2026-09-27 and is the latest **stable** release of its artifact. | +| Tool versions | Identical across all 4 (re-derived from every pom on `origin/main` 2026-09-27, not copied forward): Checker 4.2.3, fb-contrib 7.7.4, findsecbugs 1.14.0, spotbugs-maven-plugin 4.10.4.1, spotless 3.10.3, palantir 2.99.0, errorprone 2.50.0, nullaway 0.14.2, surefire 3.6.0, archunit 1.5.1, junit-jupiter 6.1.3, hamcrest 3.0, pitest-maven 1.30.0 (pitest-junit5-plugin 1.2.3). **All latest stable** — this row is the **canonical cross-repo tool-version matrix** (policy files point here rather than re-pinning). See "Dependency / plugin freshness" below for how this is kept current. **This row has now gone stale twice, the same way both times, and the note added after the first occurrence did not prevent the second — so treat the numbers here as evidence only when the audit date above is recent.** 2026-08-29: spotless 3.10.0, palantir 2.96.0, pitest-maven 1.25.9 and nullaway 0.13.8 were behind while the row claimed "all latest stable". 2026-09-05: Checker 4.2.2, spotbugs 4.10.4.0, spotless 3.10.1, nullaway 0.14.0 and surefire 3.5.6 were behind, again under the same claim. 2026-09-20: palantir 2.97.0 was behind (→ 2.98.0), a third time under the same claim. 2026-09-27: spotless 3.10.2, palantir 2.98.0, nullaway 0.14.1 and archunit 1.5.0 were behind — a fourth time; NullAway and palantir are exactly the two the versions plugin cannot see (annotation-processor path and a formatter version inside a plugin ``), so only the direct `maven-metadata.xml` probe caught them. Every time the *identity* half held (all four repos really did carry the same values) and only the recorded numbers lagged. The mechanism is the same each time: a per-repo Dependabot bump lands in one repo, the other three are brought back in step by a follow-up commit, and nothing in that sequence touches this file. **What would actually fix it is a check that derives this row from the poms rather than a reminder to update it by hand** — the *identity* half exists now: every repository's `shared-files` job runs `check-versions.py`, which compares each `groupId:artifactId` the POMs use (incl. NullAway's processor path and the Spotless palantir version) with the siblings' default branches and warns per difference. Whether the shared value is the *latest* is still a manual question — re-derive before citing (the loop under "Dependency / plugin freshness" does it in one command). Every value above was additionally cross-checked against Maven Central's own `maven-metadata.xml` on 2026-09-27 and is the latest **stable** release of its artifact. | | Release hygiene (CHANGELOG footer + version sites) | Audited 2026-08-29 while preparing java-llama.cpp 5.1.0 and BAF 1.8.0. **jllama ✅ / streambuffer ✅** — released headings, footer compare-links and `v*` tags are the same set in both. **BAF ➖ → fixed**: `v1.7.0` shipped with no `[1.7.0]` compare-link at all and `[Unreleased]` still pointing at `v1.6.1`; both repaired in the 1.8.0 prep. **srcmorph ➖ → fixed**: `v1.1.0` was tagged and released with **no CHANGELOG section and no link** — the chain jumped `1.0.2 → 1.1.1`, so a shipped version was undocumented. Repaired in the 1.2.0 prep (`ff68cfc`): the section was reconstructed, the compare-link added, and `[1.1.1]` re-pointed to compare from `v1.1.0`. Re-verified 2026-08-31 on srcmorph `main` — release headings and footer links are now the same set (`1.0.0`…`1.2.0`), with `[Unreleased]` kept as the anchor for the next cycle. The footer is the most-missed step because nothing builds or renders it — the mechanical headings/links/tags check and the per-repo table of version sites now live in [`workflows/release-process.md`](workflows/release-process.md) under "What actually gets missed". Related: BAF enforces its 24 launcher-script jar names with `ExampleRunScriptJarVersionTest` (a pom bump reds the build until they follow) and srcmorph is structurally immune via a glob — a repo that hardcodes jar names with neither guard is the gap to close. | | `dependencyConvergence` pinning convention | All 4 Maven repos (+ srcmorph's 3 reactor modules) enable maven-enforcer's ``. Convention, the `excludedScopes=[test,provided]` default gotcha, and merge-discipline guidance are in [`policies/dependency-convergence-pinning.md`](policies/dependency-convergence-pinning.md). | | Maven Enforcer `bannedDependencies` | Identical 7-entry list | @@ -84,7 +84,8 @@ files). **Jobs, too:** an entry `.github/workflows/publish.yml#` hashes one the jobs kept identical inside four otherwise different `publish.yml` files (`startgate`, `shared-files`, `verify-signing-key`, `check-snapshot`, `check-tag`; in the three Maven-only repos also `verify-signing-key-gradle`, `github-snapshot`, `github-release`) are checked like files instead of by -convention. Forgetting one is not a disaster — its job says so and the history shows it — but nobody can +convention. The same job runs `check-versions.py`, which warns where a Maven dependency or plugin is +used in another version than in a sibling. Forgetting one is not a disaster — its job says so and the history shows it — but nobody can edit a shared script without learning that it is shared. **Adding one:** put the file in each repository and a `sha256sum` line for it in each manifest. From 40b9ea040baf979af50a373f9128deac3edc7fd0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 07:08:53 +0000 Subject: [PATCH 06/10] crossrepostatus: the shared-files job also parses the workflows' bash run scripts Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 99a139b..0bd1b14 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -85,7 +85,8 @@ the jobs kept identical inside four otherwise different `publish.yml` files (`st `shared-files`, `verify-signing-key`, `check-snapshot`, `check-tag`; in the three Maven-only repos also `verify-signing-key-gradle`, `github-snapshot`, `github-release`) are checked like files instead of by convention. The same job runs `check-versions.py`, which warns where a Maven dependency or plugin is -used in another version than in a sibling. Forgetting one is not a disaster — its job says so and the history shows it — but nobody can +used in another version than in a sibling, and `check-run-scripts.py`, which fails when a bash +`run:` script of a workflow or composite action does not parse (`bash -n`). Forgetting one is not a disaster — its job says so and the history shows it — but nobody can edit a shared script without learning that it is shared. **Adding one:** put the file in each repository and a `sha256sum` line for it in each manifest. From 4dff648dad38016fd05432a8801cc94fbc15a6b5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 07:23:17 +0000 Subject: [PATCH 07/10] crossrepostatus: CI files licensed MIT OR Apache-2.0; small workflows shared Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 0bd1b14..05c70da 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -94,11 +94,15 @@ repository and a `sha256sum` line for it in each manifest. action would pin it by SHA (Scorecard's pinned-dependencies rule), it would need a release process of its own, and a checkout of a second repository in CI; copies keep each repository self-contained, and the manifest makes the duplication explicit instead of accidental. The shared set: the build-check -library (`.github/buildcheck/{__init__,workflow,releasegate,sharedfiles}.py`, its tests, and the -`check-release-gate.py` / `check-shared-files.py` entry points), `print-crash-logs.sh`, +library (`.github/buildcheck/{__init__,workflow,releasegate,sharedfiles,versions,runscripts}.py`, its +tests, and the `check-*.py` entry points), `print-crash-logs.sh`, `verify-signing-key.sh`, `verify-bytecode-version.sh`, the signing self-test (`.github/signing-selftest/`), -the issue/PR templates, `sign-fatjars.sh` (jllama + srcmorph), `smoke-fatjar-cli.sh` (BAF + srcmorph) -and `lombok.config` (jllama at `llama/lombok.config`; matched by file name there). Canonical content +the issue/PR templates, `CODE_OF_CONDUCT.md`, `sign-fatjars.sh` (jllama + srcmorph), `smoke-fatjar-cli.sh` +(BAF + srcmorph), `lombok.config` (jllama at `llama/lombok.config`; matched by file name there), and +in BAF/srcmorph/streambuffer `claude.yml`, `claude-code-review.yml`, `scorecard.yml`, `reuse.yml`, +`osv-scanner.yml` and `dependabot.yml` (java-llama.cpp's copies differ only in their license header, +which still names the upstream author). Every `.github` file carrying only the owner's copyright is +licensed `MIT OR Apache-2.0` in all four repositories, so a shared file needs no per-repository header. Canonical content of `lombok.config` in [`policies/lombok-config.md`](policies/lombok-config.md). **The same job runs the release-gate check** (`check-release-gate.py`): every job of `publish.yml` From 6923a1754da08999aa5ead7f4eb9bf92c1b3e3a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 07:26:01 +0000 Subject: [PATCH 08/10] crossrepostatus: the small workflows are shared by all four repositories Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 05c70da..04b4cdc 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -98,11 +98,10 @@ library (`.github/buildcheck/{__init__,workflow,releasegate,sharedfiles,versions tests, and the `check-*.py` entry points), `print-crash-logs.sh`, `verify-signing-key.sh`, `verify-bytecode-version.sh`, the signing self-test (`.github/signing-selftest/`), the issue/PR templates, `CODE_OF_CONDUCT.md`, `sign-fatjars.sh` (jllama + srcmorph), `smoke-fatjar-cli.sh` -(BAF + srcmorph), `lombok.config` (jllama at `llama/lombok.config`; matched by file name there), and -in BAF/srcmorph/streambuffer `claude.yml`, `claude-code-review.yml`, `scorecard.yml`, `reuse.yml`, -`osv-scanner.yml` and `dependabot.yml` (java-llama.cpp's copies differ only in their license header, -which still names the upstream author). Every `.github` file carrying only the owner's copyright is -licensed `MIT OR Apache-2.0` in all four repositories, so a shared file needs no per-repository header. Canonical content +(BAF + srcmorph), `lombok.config` (jllama at `llama/lombok.config`; matched by file name there), +`claude.yml`, `claude-code-review.yml`, `scorecard.yml`, `reuse.yml`, `osv-scanner.yml` and +`dependabot.yml`. Every own `.github` file is licensed `MIT OR Apache-2.0` in all four repositories, so +a shared file needs no per-repository header. Canonical content of `lombok.config` in [`policies/lombok-config.md`](policies/lombok-config.md). **The same job runs the release-gate check** (`check-release-gate.py`): every job of `publish.yml` From 69815ebe3eaac1fe171e9830099d91cea4defb53 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 07:39:33 +0000 Subject: [PATCH 09/10] crossrepostatus: .java-version and codeql.yml shared by all four repositories Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 04b4cdc..7a234ad 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -99,8 +99,8 @@ tests, and the `check-*.py` entry points), `print-crash-logs.sh`, `verify-signing-key.sh`, `verify-bytecode-version.sh`, the signing self-test (`.github/signing-selftest/`), the issue/PR templates, `CODE_OF_CONDUCT.md`, `sign-fatjars.sh` (jllama + srcmorph), `smoke-fatjar-cli.sh` (BAF + srcmorph), `lombok.config` (jllama at `llama/lombok.config`; matched by file name there), -`claude.yml`, `claude-code-review.yml`, `scorecard.yml`, `reuse.yml`, `osv-scanner.yml` and -`dependabot.yml`. Every own `.github` file is licensed `MIT OR Apache-2.0` in all four repositories, so +`claude.yml`, `claude-code-review.yml`, `codeql.yml`, `scorecard.yml`, `reuse.yml`, `osv-scanner.yml`, +`dependabot.yml` and `.java-version` (the JDK every workflow reads via `java-version-file`). Every own `.github` file is licensed `MIT OR Apache-2.0` in all four repositories, so a shared file needs no per-repository header. Canonical content of `lombok.config` in [`policies/lombok-config.md`](policies/lombok-config.md). From 887eaf314ce311dc92191546b01f8077cfcb1513 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 07:50:59 +0000 Subject: [PATCH 10/10] crossrepostatus: more shared files, ?repo entries Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- crossrepostatus.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crossrepostatus.md b/crossrepostatus.md index 7a234ad..2ad9808 100644 --- a/crossrepostatus.md +++ b/crossrepostatus.md @@ -100,7 +100,13 @@ tests, and the `check-*.py` entry points), `print-crash-logs.sh`, the issue/PR templates, `CODE_OF_CONDUCT.md`, `sign-fatjars.sh` (jllama + srcmorph), `smoke-fatjar-cli.sh` (BAF + srcmorph), `lombok.config` (jllama at `llama/lombok.config`; matched by file name there), `claude.yml`, `claude-code-review.yml`, `codeql.yml`, `scorecard.yml`, `reuse.yml`, `osv-scanner.yml`, -`dependabot.yml` and `.java-version` (the JDK every workflow reads via `java-version-file`). Every own `.github` file is licensed `MIT OR Apache-2.0` in all four repositories, so +`dependabot.yml`, `.java-version` (the JDK every workflow reads via `java-version-file`), `.editorconfig`, +`.gitattributes`, `FUNDING.yml`, `CODEOWNERS`, `LICENSES/{MIT,Apache-2.0}.txt`, the job +`verify-signing-key-gradle` (all four now on Gradle 9.8.0; the siblings were on 9.6.1), `.mvn/jvm.config` +(all but BAF) and `.mvn/settings.xml` (all but jllama). Entries ending in `?repo` cover files identical up +to the repository name (hashed with it replaced by `{repo}`): `SUPPORT.md`, `ISSUE_TEMPLATE/config.yml`, +`CITATION.cff` (not jllama: its license differs), `sonarqube.yml` (srcmorph + streambuffer) and the +`code-style` job (BAF + streambuffer). Every own `.github` file is licensed `MIT OR Apache-2.0` in all four repositories, so a shared file needs no per-repository header. Canonical content of `lombok.config` in [`policies/lombok-config.md`](policies/lombok-config.md).