From 075e5854106008fae32076ea327bec2c7bb9f60b Mon Sep 17 00:00:00 2001 From: apostasie Date: Sat, 15 Aug 2026 22:32:55 -0700 Subject: [PATCH 1/3] Bump limen to v0.0.14 and apply its remediation limen fix on the new pin: aqua packages rewritten to the two-line form Renovate's dedicated managers can bump (five of them were in one-line name@version form, which it cannot), aqua-checksums.json regenerated, and the content-pinned setup-aqua action reset to the canonical baseline (its installer no longer trusts `uname -m` on the windows arm64 runners, where Git Bash is x86_64 and the answer is wrong). Signed-off-by: apostasie Co-Authored-By: Claude Fable 5 --- .github/actions/setup-aqua/action.yaml | 36 +++++++++++++++++++++++++- aqua-checksums.json | 20 +++++++------- aqua.yaml | 17 +++++++----- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/actions/setup-aqua/action.yaml b/.github/actions/setup-aqua/action.yaml index d8189724..465d5fbe 100644 --- a/.github/actions/setup-aqua/action.yaml +++ b/.github/actions/setup-aqua/action.yaml @@ -44,7 +44,41 @@ runs: echo "${AQUA_INSTALLER_SHA256} ${tmp}/aqua-installer" | shasum -a 256 -c - fi chmod +x "${tmp}/aqua-installer" - "${tmp}/aqua-installer" -v "${AQUA_VERSION}" + # The installer picks aqua's build from `uname -m`. On the windows + # arm64 runners that answer is WRONG: Git for Windows' arm64 build + # ships an x86_64 Git Bash (git.exe is native; bash, coreutils and + # uname are not — its release notes say so), so under this shell + # `uname -m` is x86_64, the installer fetches aqua_windows_amd64, and + # that aqua then resolves EVERY package for windows/amd64: go, just, + # gotestsum, the race runtime — the whole leg emulated, and no arm64 + # coverage at all. The runner knows what it is (RUNNER_ARCH); tell the + # installer through the one input it reads. A shim on PATH, for the + # installer's process only, answering `uname -m` with aarch64: the + # installer's own checksum table already covers aqua_windows_arm64 + # and its bootstrap aqua then installs the pinned version natively — + # every byte still verified the same way, only the choice corrected. + # Self-retiring: skipped once the shell's uname is honest. + if [ "${RUNNER_OS:-}" = "Windows" ] && [ "${RUNNER_ARCH:-}" = "ARM64" ] && [ "$(uname -m)" != "aarch64" ]; then + echo "windows/arm64 runner under an emulated shell (uname -m: $(uname -m)); steering the installer to arm64" + mkdir -p "${tmp}/shim" + real_uname="$(command -v uname)" + printf '#!/usr/bin/env bash\nif [ "${1:-}" = "-m" ]; then echo aarch64; else exec "%s" "$@"; fi\n' "${real_uname}" >"${tmp}/shim/uname" + chmod +x "${tmp}/shim/uname" + PATH="${tmp}/shim:${PATH}" "${tmp}/aqua-installer" -v "${AQUA_VERSION}" + # Assert, do not assume: read the PE machine field of the installed + # binary (0xAA64 = ARM64, 0x8664 = AMD64). If this ever fails, the + # leg is silently emulated again — better red than slow and false. + exe="${AQUA_ROOT_DIR}/bin/aqua.exe" + pe_offset="$(od -An -tu4 -j 60 -N 4 "${exe}" | tr -d ' ')" + machine="$(od -An -tx2 -j "$((pe_offset + 4))" -N 2 "${exe}" | tr -d ' ')" + if [ "${machine}" != "aa64" ]; then + echo "installed aqua is not an arm64 binary (PE machine 0x${machine}); the windows/arm64 leg would run emulated amd64 tooling" >&2 + exit 1 + fi + echo "aqua: native windows/arm64 (PE machine 0x${machine})" + else + "${tmp}/aqua-installer" -v "${AQUA_VERSION}" + fi rm -rf "${tmp}" echo "${AQUA_ROOT_DIR}/bin" >>"$GITHUB_PATH" - name: Authorize the committed aqua policy diff --git a/aqua-checksums.json b/aqua-checksums.json index b975ef14..1fb56628 100644 --- a/aqua-checksums.json +++ b/aqua-checksums.json @@ -46,28 +46,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.13/limen_0.0.13_darwin_arm64.tar.gz", - "checksum": "8739EA67AE2404A33E56EC9E89DE228C4DB73BFE37E2E814D7592289576FDBF8", + "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_darwin_arm64.tar.gz", + "checksum": "1FFF3D8E80D4AF0405A201E6604107388079DEAF6CEA0EB82633DF8541C41EC4", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.13/limen_0.0.13_linux_amd64.tar.gz", - "checksum": "C5A3990CF1307CB14DA8986FF45AD634A569DD35B3E536F7694C7BD9E9BF4557", + "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_linux_amd64.tar.gz", + "checksum": "88F8765F656DFB3ACDBE4EB0B56DECBA3B466C86FEADB5445B968B2A146CB77B", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.13/limen_0.0.13_linux_arm64.tar.gz", - "checksum": "6D19477293359E262C2E6E882974355B1F339A5249EBC1D42620783BBD23B3B0", + "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_linux_arm64.tar.gz", + "checksum": "74AF8EE83F1BBF9AC126FFA3EDEA1AAB64BE3337A56BA0E00E2C58037CC0495D", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.13/limen_0.0.13_windows_amd64.tar.gz", - "checksum": "942522F4AB937B9DDDCFE2E369E2AF13AF64C1041934CA20D098AA9C8321FC20", + "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_windows_amd64.tar.gz", + "checksum": "0158C0450648F3BA156F3FC62A123130F271BF698ADB79E260E8F305F06AC43F", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.13/limen_0.0.13_windows_arm64.tar.gz", - "checksum": "C331B8BA5495406BC1A5C6DB62549B4AB562FDD808DFB71B18D3FD6ADE3FCE42", + "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_windows_arm64.tar.gz", + "checksum": "7DBFEB62FAF7C7A7B1E58A35B1CEC407000C98E29512FB9AB5CB2697D2E7C21F", "algorithm": "sha256" }, { diff --git a/aqua.yaml b/aqua.yaml index 95d59148..ff4a0ec4 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -18,9 +18,11 @@ registries: packages: # --- go install tools (local registry, GOSUMDB-verified) --- - - name: github.com/google/go-licenses/v2@v2.0.1 + - name: github.com/google/go-licenses/v2 + version: v2.0.1 # renovate: depName=_go/github.com/google/go-licenses/v2 registry: local - - name: github.com/vbatts/git-validation@v1.2.2 + - name: github.com/vbatts/git-validation + version: v1.2.2 # renovate: depName=_go/github.com/vbatts/git-validation registry: local - name: golang.org/x/vuln/cmd/govulncheck@v1.5.0 registry: local @@ -29,13 +31,15 @@ packages: # Pseudo-version: the nested cmd/dot module carries no tags upstream. - name: github.com/goccy/go-graphviz/cmd/dot@v0.0.0-20251129032125-76e04975df88 registry: local - - name: github.com/farcloser/godolint/cmd/godolint@v0.1.0 + - name: github.com/farcloser/godolint/cmd/godolint + version: v0.1.0 # renovate: depName=_go/github.com/farcloser/godolint/cmd/godolint registry: local # --- farcloser tools (local registry; standard once registered upstream) --- - - name: farcloser/limen@v0.0.13 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.0.14 # renovate: depName=farcloser/limen registry: local # --- toolchain + binary-release tools (standard registry, aqua-verified) --- - - name: golang/go@go1.26.5 + - name: golang/go + version: go1.26.5 # renovate: depName=golang/go - name: casey/just@1.57.0 - name: koalaman/shellcheck@v0.11.0 - name: golangci/golangci-lint@v2.12.2 @@ -44,5 +48,6 @@ packages: - name: goreleaser/goreleaser@v2.17.1 - name: sigstore/cosign@v3.1.1 - name: gotestyourself/gotestsum@v1.13.0 - - name: jqlang/jq@jq-1.8.2 + - name: jqlang/jq + version: jq-1.8.2 # renovate: depName=jqlang/jq - name: cli/cli@v2.96.0 From 8cbfb40386237c097aaf95a7baa097d7500cda69 Mon Sep 17 00:00:00 2001 From: apostasie Date: Sat, 15 Aug 2026 22:32:55 -0700 Subject: [PATCH 2/3] ci: adopt the canonical fuzz job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tree has carried five Fuzz* targets — a serial reader, the single-stream and parallel readers, a serial-vs-parallel differential, and a round-trip — that ran nowhere: the workflow was the pre-fuzz canonical seed, verify and gate only, and ci.yaml is seeded once, so the limen bump that brought the fuzz job to the canonical workflow could not bring it here (book/github.md, "Migration"). This workflow had no project-specific content — its only difference from the current canonical file was the fuzz job itself — so it is brought level with the canonical file rather than patched by hand: one linux leg running `just do test go fuzz`, corpus cached across runs, crashers uploaded as an artifact, and gate asserting the result. All five targets pass at the CI budget locally. Signed-off-by: apostasie Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yaml | 69 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2067ef77..76037a11 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -83,6 +83,64 @@ jobs: - name: Test run: just test + # A short, coverage-guided fuzz of every Fuzz* target, on one linux leg. + # One leg, not the matrix: fuzzing explores the same code from the same + # corpus wherever it runs, so five legs would spend five times the CPU on + # one corpus and add no coverage — unlike unit tests, whose per-OS + # behaviour is the point of the matrix. The recipe is limen's + # `do::test::go::fuzz`: `go test -fuzz` per target (only the go tool's own + # fuzz build compiles in the coverage counters — a prebuilt test binary + # mutates blind), the verdict taken from whether a crasher was written + # under testdata/fuzz/ rather than from the exit code (the + # coordinator reports a spurious "context deadline exceeded" when a worker + # is mid-iteration as the budget expires; that is retried once), and a + # tree with no Fuzz* targets reporting so and passing. So this job is safe + # in every project: it fuzzes what there is, and is a one-line no-op where + # there is nothing. + fuzz: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install aqua (pinned, checksum-verified) + uses: ./.github/actions/setup-aqua + + - name: Install pinned tools + run: aqua install --only-link + + # The generated corpus is what makes fuzzing cumulative: each run + # starts from every input earlier runs found interesting rather than + # from the seeds, so a short budget per run compounds into depth over + # months. Keyed on the fuzz sources so a changed target restarts its + # own corpus; restore-keys keep the rest. GitHub's own action, pinned + # by SHA like checkout. + - id: fuzzdir + run: echo "dir=$(go env GOCACHE)/fuzz" >> "$GITHUB_OUTPUT" + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.fuzzdir.outputs.dir }} + key: fuzz-corpus-${{ runner.os }}-${{ hashFiles('**/*_fuzz_test.go', '**/fuzz_test.go') }} + restore-keys: | + fuzz-corpus-${{ runner.os }}- + + - name: Fuzz + run: just do test go fuzz + + # A crasher is a bug with a reproducer attached; the log names the + # target, the input file is what reproduces it locally. Uploaded only + # on failure, and only if any was written. + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: failure() + with: + name: fuzz-crashers + path: '**/testdata/fuzz/' + if-no-files-found: ignore + # The ONE required status check (see defaultRequiredChecks in # internal/github/audit.go). Branch protection names contexts as strings, so # requiring the matrix legs directly would bake this workflow's runner list @@ -91,22 +149,25 @@ jobs: # whatever its shape, to a single stable name: change the legs above freely, # the ruleset never moves. gate: - needs: [verify] + needs: [verify, fuzz] # always(), and the result asserted explicitly. Without always() a failed # or cancelled dependency SKIPS this job instead of failing it, and a # skipped required check does not block a merge — branch protection that # quietly stops protecting. `needs.verify.result` is success only when - # every matrix leg succeeded. + # every matrix leg succeeded; fuzz is asserted alongside so a crasher + # blocks a merge the same way a failing test does. if: always() runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: {} steps: - - name: Every verify leg succeeded + - name: Every verify leg and fuzz succeeded env: # Via env, never interpolated into the script: the shell sees data, # not something the expression layer can rewrite into code. RESULT: ${{ needs.verify.result }} + RESULT_FUZZ: ${{ needs.fuzz.result }} run: | printf 'verify: %s\n' "$RESULT" - [ "$RESULT" = "success" ] + printf 'fuzz: %s\n' "$RESULT_FUZZ" + [ "$RESULT" = "success" ] && [ "$RESULT_FUZZ" = "success" ] From aa5a183a7692c1c42791339c42a983ce7944b444 Mon Sep 17 00:00:00 2001 From: apostasie Date: Sat, 15 Aug 2026 23:12:54 -0700 Subject: [PATCH 3/3] Bump limen to v0.0.15 and apply its remediation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit limen fix on the new pin: the shared test-go recipes reset to the canonical baseline — `race` now asks the toolchain whether the host has a race detector and, where it does not (windows/arm64), says so loudly and passes rather than fail every push on a Go limitation — and the content-pinned update-aqua-checksum workflow and setup-aqua action reset to the baseline. aqua-checksums.json regenerated. Signed-off-by: apostasie Co-Authored-By: Claude Fable 5 --- .github/actions/setup-aqua/action.yaml | 17 ++++++++++++ .github/workflows/update-aqua-checksum.yaml | 8 ++++++ .limen/just/test-go.just | 30 ++++++++++++++++++++- aqua-checksums.json | 20 +++++++------- aqua.yaml | 2 +- 5 files changed, 65 insertions(+), 12 deletions(-) diff --git a/.github/actions/setup-aqua/action.yaml b/.github/actions/setup-aqua/action.yaml index 465d5fbe..112008d4 100644 --- a/.github/actions/setup-aqua/action.yaml +++ b/.github/actions/setup-aqua/action.yaml @@ -1,6 +1,12 @@ name: Setup aqua description: > Install aqua (pinned, checksum-verified), put its bin directory on PATH, and authorize the repository's committed policy. aqua itself is the one tool aqua cannot pin. Same pins as limen-install: the installer script is fetched at an exact tag and checksum-verified, and it installs an exact aqua version. +inputs: + github-token: + description: > + Token aqua authenticates its GitHub API calls with (exported as AQUA_GITHUB_TOKEN for the rest of the job). aqua resolves every github_release package through the API — the release by tag, its assets, the checksum file — and unauthenticated calls are limited to 60/hour PER IP, an IP the hosted runners share with every other tenant: "403 API rate limit exceeded" on the first tool a lint leg touches is that limit, not this repository's usage. The job's own token lifts it to a per-token budget. Default: the job's token, whose permissions the job declares (contents: read on every canonical read-only job). Pass "" to opt out — the write-capable checksum workflow does, by doctrine. + required: false + default: ${{ github.token }} runs: using: composite steps: @@ -86,3 +92,14 @@ runs: # must be allowed — every caller needs this, whatever it runs next. shell: bash run: aqua policy allow aqua-policy.yaml + - name: Authenticate aqua's GitHub API calls + # See the github-token input. Via env, never interpolated into the + # script; GITHUB_ENV so every later step's aqua (and the shims it links) + # sees it — that is where the downloads actually happen, lazily, on + # first use. AQUA_GITHUB_TOKEN rather than GITHUB_TOKEN: only aqua reads + # it, nothing else in the job inherits a credential it did not ask for. + if: inputs.github-token != '' + shell: bash + env: + TOKEN: ${{ inputs.github-token }} + run: echo "AQUA_GITHUB_TOKEN=${TOKEN}" >>"$GITHUB_ENV" diff --git a/.github/workflows/update-aqua-checksum.yaml b/.github/workflows/update-aqua-checksum.yaml index 15a0aa59..4da45ef4 100644 --- a/.github/workflows/update-aqua-checksum.yaml +++ b/.github/workflows/update-aqua-checksum.yaml @@ -89,6 +89,14 @@ jobs: - name: Install aqua (pinned, checksum-verified) uses: ./.github/actions/setup-aqua + with: + # No token for aqua here, by doctrine: this job's token carries + # contents:write, and the update steps below run a branch-declared + # binary (the branch's limen pin). Unauthenticated API calls are + # rate-limited per runner IP; this is one job per Renovate branch, + # and a 403 here fails loudly and is rerun — a write token in the + # environment of branch-controlled code would not fail loudly. + github-token: "" - name: Regenerate aqua-checksums.json # No tool install: update-checksum only reads the manifest and hashes diff --git a/.limen/just/test-go.just b/.limen/just/test-go.just index 9631be91..64e9a24b 100644 --- a/.limen/just/test-go.just +++ b/.limen/just/test-go.just @@ -47,8 +47,36 @@ unit: (_banner "test go" "unit") # ("relocation target stderr not defined") — see golang/go#52690, #54313, # #58619. On macOS with Xcode 15+, ld emits "has malformed LC_DYSYMTAB" # warnings for race builds; cosmetic, the binaries are correct (golang/go#61229). +# +# The detector does not exist everywhere. It is a prebuilt LLVM +# ThreadSanitizer runtime that Go vendors per platform (runtime/race/*.syso), +# and LLVM ships none for windows/arm64 among others — so on those hosts +# `-race` is not a slow option but a missing one, and `go build -race` +# refuses before compiling a line ("-race is not supported on GOOS/GOARCH"). +# That refusal is the probe: ask the toolchain, rather than keep a platform +# list that drifts as Go and LLVM add targets. The check is host-level, made +# before any package is looked at, so probing `unsafe` — a package with no +# source to compile — costs milliseconds and answers exactly the same as +# probing the module would. On such a host the recipe +# says so, loudly, and passes: a red leg would re-report a Go limitation on +# every push, and a silent green would hide a coverage gap that is real — +# this leg's race coverage is absent (the other legs' is not: the linux and +# macos arm64 legs run the detector natively). The skip is keyed to that +# exact message: any other failure of the probe build is a real one and +# fails the recipe as before. race: (_banner "test go" "race") - CGO_ENABLED=1 gotestsum -- -count=1 -timeout "${TEST_GO_TIMEOUT:-10m}" -ldflags=-linkmode=external -race ./... + #!/usr/bin/env bash + set -euo pipefail + export CGO_ENABLED=1 + if ! probe="$(go build -race -o /dev/null unsafe 2>&1)"; then + if printf '%s\n' "$probe" | grep -q -- '-race is not supported'; then + echo "race: SKIPPED — no race detector for $(go env GOOS)/$(go env GOARCH); ${probe}" >&2 + exit 0 + fi + printf '%s\n' "$probe" >&2 + exit 1 + fi + gotestsum -- -count=1 -timeout "${TEST_GO_TIMEOUT:-10m}" -ldflags=-linkmode=external -race ./... # Benchmarks, with allocation stats. -run '^$' deselects unit tests so only # benchmarks run — `just do test go` already covers the tests themselves. diff --git a/aqua-checksums.json b/aqua-checksums.json index 1fb56628..e4ad97f9 100644 --- a/aqua-checksums.json +++ b/aqua-checksums.json @@ -46,28 +46,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_darwin_arm64.tar.gz", - "checksum": "1FFF3D8E80D4AF0405A201E6604107388079DEAF6CEA0EB82633DF8541C41EC4", + "id": "github_release/github.com/farcloser/limen/v0.0.15/limen_0.0.15_darwin_arm64.tar.gz", + "checksum": "2B5134579B4CEB587815C8040B46DB018D15ECA7CC89E1886BC60557224B8A71", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_linux_amd64.tar.gz", - "checksum": "88F8765F656DFB3ACDBE4EB0B56DECBA3B466C86FEADB5445B968B2A146CB77B", + "id": "github_release/github.com/farcloser/limen/v0.0.15/limen_0.0.15_linux_amd64.tar.gz", + "checksum": "1D8D568FBC83606B3308942D7A140A99E35FAFB4AD88776D2B50DD9B80A951F8", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_linux_arm64.tar.gz", - "checksum": "74AF8EE83F1BBF9AC126FFA3EDEA1AAB64BE3337A56BA0E00E2C58037CC0495D", + "id": "github_release/github.com/farcloser/limen/v0.0.15/limen_0.0.15_linux_arm64.tar.gz", + "checksum": "53AF14EC37198DF904CD41DB8D161AAC490AECCE641718102BCC65793D1F2120", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_windows_amd64.tar.gz", - "checksum": "0158C0450648F3BA156F3FC62A123130F271BF698ADB79E260E8F305F06AC43F", + "id": "github_release/github.com/farcloser/limen/v0.0.15/limen_0.0.15_windows_amd64.tar.gz", + "checksum": "4CEA9B938B3B166B6365BABF2ECA4CADEF2433B7DBF3584B636172A0EF6DCDEB", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.14/limen_0.0.14_windows_arm64.tar.gz", - "checksum": "7DBFEB62FAF7C7A7B1E58A35B1CEC407000C98E29512FB9AB5CB2697D2E7C21F", + "id": "github_release/github.com/farcloser/limen/v0.0.15/limen_0.0.15_windows_arm64.tar.gz", + "checksum": "948C4AF4713B73543FFEA1C06B11D235DBB01002626259313713253464F3D896", "algorithm": "sha256" }, { diff --git a/aqua.yaml b/aqua.yaml index ff4a0ec4..a05dcdbb 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -35,7 +35,7 @@ packages: version: v0.1.0 # renovate: depName=_go/github.com/farcloser/godolint/cmd/godolint registry: local # --- farcloser tools (local registry; standard once registered upstream) --- - - name: farcloser/limen@v0.0.14 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.0.15 # renovate: depName=farcloser/limen registry: local # --- toolchain + binary-release tools (standard registry, aqua-verified) --- - name: golang/go