From df2da01338f13295a49fed06c76c1ef0cbadf2ac Mon Sep 17 00:00:00 2001 From: apostasie Date: Mon, 3 Aug 2026 00:25:55 -0700 Subject: [PATCH 1/3] Ignore build Signed-off-by: apostasie --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5aff377f..4fd8bc95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # .gitignore +build + TODO.html README.html @@ -28,4 +30,4 @@ cmd/xb/xb enwik8* # file generated by example -example.xz \ No newline at end of file +example.xz From 6f0270c8b9c84e5f486448d8bd06050405d2732a Mon Sep 17 00:00:00 2001 From: apostasie Date: Mon, 3 Aug 2026 17:37:55 -0700 Subject: [PATCH 2/3] ci: fold the inherited workflows into the limen pipeline `just test` was an empty recipe, so ci.yaml's Test step ran nothing and go-test-platforms.yml was the only thing testing this repo. Wire test to the shared unit and race recipes plus a 386 leg, and add bench. The 386 leg turned out to be broken: a WriterConfig{DictCap: 1 << 40} test case does not compile where int is 32 bits, so that job had been failing since it was added. A capacity above MaxDictCap (1<<32-1) is representable only on 64-bit, so guard the case and use math.MaxInt. Drop go-test-platforms.yml (ci.yaml's matrix covers more, and now runs the 386 leg too) and codeql.yml: the baseline SAST posture is golangci's gosec/staticcheck plus govulncheck per GOOS, and limen wants CodeQL as default setup opted into via limen.yaml, not a workflow pinned by tag. Signed-off-by: apostasie --- .github/workflows/codeql.yml | 100 ------------------------ .github/workflows/go-test-platforms.yml | 34 -------- Justfile | 20 ++++- TODO.md | 3 +- malformed_test.go | 19 ++++- 5 files changed, 37 insertions(+), 139 deletions(-) delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/go-test-platforms.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 7c1e2245..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,100 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL Advanced" - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - schedule: - - cron: '24 14 * * 3' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - - language: go - build-mode: autobuild - # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Add any setup steps before running the `github/codeql-action/init` action. - # This includes steps like installing compilers or runtimes (`actions/setup-node` - # or others). This is typically only required for manual builds. - # - name: Setup runtime (example) - # uses: actions/setup-example@v1 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/go-test-platforms.yml b/.github/workflows/go-test-platforms.yml deleted file mode 100644 index 07d58616..00000000 --- a/.github/workflows/go-test-platforms.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Test on Linux/amd64, Linux/x86, and Windows/amd64 - -permissions: - contents: read - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - go-test-linux: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - run: go test -race ./... - - # The -race detector is not available on 386, so let's not use it here: - # https://go.dev/doc/articles/race_detector - - run: GOARCH=386 go test ./... - - go-test-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - run: go test -race ./... diff --git a/Justfile b/Justfile index 46821ec7..91d8dafe 100644 --- a/Justfile +++ b/Justfile @@ -6,4 +6,22 @@ import '.limen/just/main.just' # The FIRST recipe defined here becomes `just`'s default. lint: do::lint::go::default do::lint::default fix: do::fix::go::default do::fix::default -test: +test: do::test::go::unit do::test::go::race test-386 +bench: do::test::go::bench + +# 32-bit coverage: this codebase is int-width sensitive — dictionary and buffer +# arithmetic, int64 stream sizes narrowed to int, and index records whose +# declared sizes are bounded against the address space — and none of that is +# exercised by a 64-bit run. No development machine is 32-bit, but 386 binaries +# execute natively on amd64 hosts, so the amd64 CI legs run this for free; +# other hosts skip it loudly rather than silently reporting a pass they never +# obtained. The race detector does not support 386. +[doc('Run the tests as GOARCH=386 (executes natively on amd64 hosts; skipped elsewhere)')] +test-386: + #!/usr/bin/env bash + set -euo pipefail + if [ "$(go env GOHOSTARCH)" != "amd64" ]; then + echo "GOARCH=386 binaries need an amd64 host to execute; skipping" + exit 0 + fi + CGO_ENABLED=0 GOARCH=386 go test -count=1 -timeout "${TEST_GO_TIMEOUT:-10m}" ./... diff --git a/TODO.md b/TODO.md index b1657980..e2525206 100644 --- a/TODO.md +++ b/TODO.md @@ -74,7 +74,8 @@ * Update README.md * xb copyright . in xz directory to ensure all new files have Copyright header * `VERSION= go generate github.com/forkcloser/xz/...` to update version files -* Execute [test for Linux/amd64, Linux/x86 and Windows/amd64](.github/workflows/go-test-platforms.yml). +* Run `just lint` and `just test` (the CI matrix runs the same recipes on + linux, macos and windows, amd64 and arm64, plus the 386 leg). * Update TODO.md - write short log entry * `git checkout main && git merge dev` * `git tag -a ` diff --git a/malformed_test.go b/malformed_test.go index 1a88f150..3c099e94 100644 --- a/malformed_test.go +++ b/malformed_test.go @@ -9,8 +9,11 @@ import ( "errors" "hash/crc32" "io" + "math" "math/rand" "testing" + + "github.com/forkcloser/xz/lzma" ) // The parser's error branches are most of its code and were among its least @@ -282,12 +285,22 @@ func TestHeaderAndFooterValidation(t *testing.T) { // difference between a clear failure at construction and a confusing one // later. func TestWriterConfigValidation(t *testing.T) { - for name, c := range map[string]WriterConfig{ + cases := map[string]WriterConfig{ "negative block size": {BlockSize: -1}, "bad checksum": {CheckSum: 0x7}, "tiny dict": {DictCap: 1}, - "huge dict": {DictCap: 1 << 40}, - } { + } + // A dictionary capacity above lzma.MaxDictCap (1<<32 - 1) is representable + // only where int is 64 bits wide. On a 32-bit platform every positive int + // is a legal capacity, so there is no over-range value to reject — and + // spelling one as a constant would not compile there. Both branches are + // type-checked on every platform, so math.MaxInt is what keeps this + // portable: it is in range for the int of whatever platform builds it. + if math.MaxInt > lzma.MaxDictCap { + cases["huge dict"] = WriterConfig{DictCap: math.MaxInt} + } + + for name, c := range cases { t.Run(name, func(t *testing.T) { cfg := c if err := cfg.Verify(); err == nil { From 85daf5b173cf328608127a6c225d18139254dd7d Mon Sep 17 00:00:00 2001 From: apostasie Date: Sat, 15 Aug 2026 18:12:50 -0700 Subject: [PATCH 3/3] Bump limen Signed-off-by: apostasie --- .github/workflows/update-aqua-checksum.yaml | 99 ++++++++++++++++----- aqua-checksums.json | 20 ++--- aqua.yaml | 2 +- 3 files changed, 90 insertions(+), 31 deletions(-) diff --git a/.github/workflows/update-aqua-checksum.yaml b/.github/workflows/update-aqua-checksum.yaml index 9a3eb0e4..15a0aa59 100644 --- a/.github/workflows/update-aqua-checksum.yaml +++ b/.github/workflows/update-aqua-checksum.yaml @@ -25,13 +25,23 @@ # code the branch controls. `aqua update-checksum` only downloads and # hashes declared artifacts; the converge step executes exactly one # binary, the checksum-pinned limen release the branch declares. -# - The push step runs only git, with the token scoped to that single step. +# - The commit step runs only runner-provided tools (git, jq, base64, +# curl) — no branch-pinned binary ever touches the token, which is +# scoped to that single step. # - The branch name reaches the shell via env, never template interpolation # (script-injection hygiene). -# - No loop: a push made with the default GITHUB_TOKEN triggers no further -# workflows — and the no-change early exit terminates recursion +# - No loop: a commit made with the default GITHUB_TOKEN triggers no +# further workflows — and the no-change early exit terminates recursion # regardless. # +# The commit is created through the GraphQL createCommitOnBranch mutation, +# never a local `git commit` + push: GitHub signs the mutation's commits, and +# the `limen:main` ruleset requires signatures — an unsigned fix-up commit +# made every aqua-bump PR unmergeable (see the commit step for the full +# argument). Consequence of the mutation: the commit's author IS the token's +# identity, always — with the default token that is github-actions[bot], +# already listed in the canonical renovate.json5's gitIgnoredAuthors. +# # Known trade of the default token: GitHub suppresses workflow runs for # commits it pushes, so the PR's CI does not re-run on the checksum commit. # To get CI on the final state of Renovate PRs, register a GitHub App — @@ -42,6 +52,10 @@ # long-lived broad credential and nothing that expires on a calendar. A # fine-grained PAT with contents:write as UPDATE_AQUA_CHECKSUM_TOKEN is the # drop-in alternative; the token preference order is App, PAT, default. +# With an App or PAT the commit is authored as that identity (the mutation +# offers no override), so it must be added to gitIgnoredAuthors in +# renovate.json5 — or Renovate treats the branch as human-modified and +# stops rebasing it. name: update-aqua-checksum on: @@ -111,27 +125,72 @@ jobs: # happens to hold now or later. permission-contents: write - - name: Push the update, if any + - name: Commit the update through the API, if any env: BRANCH: ${{ github.ref_name }} TOKEN: ${{ steps.app-token.outputs.token || secrets.UPDATE_AQUA_CHECKSUM_TOKEN || github.token }} run: | [ -z "$(git status --porcelain)" ] && { echo "checksums and baseline already in sync"; exit 0; } - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # `--all` is deliberate, not sloppiness. The tree is fully accounted - # for: a fresh checkout, aqua rooted outside the workspace, and - # exactly two writers between checkout and here — update-checksum - # and the pinned limen's fix. Whatever is dirty IS the payload. An - # enumerated path list would be wrong: the converge step's job is to - # commit whatever the NEW limen's baseline says, and a list baked - # into the older running workflow cannot know that surface (the - # updated workflow arrives in the very commit being built). Nor - # would a list add safety — the only writer that could plant a file - # is limen fix itself, and .limen/.github would be on any list. - git add --all + # The commit is made through GraphQL createCommitOnBranch, not a + # local `git commit` + push, because commits made through that + # mutation are signed by GitHub — and the `limen:main` ruleset + # requires signatures on everything landing on the default branch. + # A plain git push is unsigned, and one unsigned commit on the + # branch made every aqua-bump PR unmergeable: a merge commit would + # land it, GitHub disables rebase while signatures are required, + # and squash of a bot-authored PR is refused to everyone but the + # bot (see limen's book/github.md). + # + # Enumerating the whole dirty tree is deliberate, not sloppiness. + # The tree is fully accounted for: a fresh checkout, aqua rooted + # outside the workspace, and exactly two writers between checkout + # and here — update-checksum and the pinned limen's fix. Whatever + # is dirty IS the payload. An enumerated path list would be wrong: + # the converge step's job is to commit whatever the NEW limen's + # baseline says, and a list baked into the older running workflow + # cannot know that surface (the updated workflow arrives in the + # very commit being built). Nor would a list add safety — the only + # writer that could plant a file is limen fix itself, and + # .limen/.github would be on any list. + # + # Limit of the mutation, accepted: FileAddition carries no file + # mode, so an executable bit cannot travel — both writers here + # only ever produce plain configuration files. + additions="[]" + deletions="[]" + while IFS= read -r -d '' entry; do + path="${entry:3}" + if [ -f "${path}" ]; then + additions="$(jq --arg path "${path}" --arg contents "$(base64 -w0 "${path}")" \ + '. + [{path: $path, contents: $contents}]' <<<"${additions}")" + else + deletions="$(jq --arg path "${path}" '. + [{path: $path}]' <<<"${deletions}")" + fi + done < <(git status --porcelain -z) # Signed-off-by: `just do lint commits` enforces DCO on the PR range, # bot commits included. - git commit -m "chore: update aqua checksums and converge the limen baseline" \ - -m "Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" - git push "https://x-access-token:${TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:${BRANCH}" + payload="$(jq -n \ + --arg query 'mutation ($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }' \ + --arg repository "${GITHUB_REPOSITORY}" \ + --arg branch "${BRANCH}" \ + --arg head "$(git rev-parse HEAD)" \ + --argjson additions "${additions}" \ + --argjson deletions "${deletions}" \ + '{query: $query, variables: {input: { + branch: {repositoryNameWithOwner: $repository, branchName: $branch}, + expectedHeadOid: $head, + message: { + headline: "chore: update aqua checksums and converge the limen baseline", + body: "Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + }, + fileChanges: {additions: $additions, deletions: $deletions}}}}')" + # GraphQL reports failure in-body over HTTP 200, so success is the + # presence of the new commit oid, never the HTTP status. + response="$(curl -sS -X POST -H "Authorization: bearer ${TOKEN}" \ + -d "${payload}" https://api.github.com/graphql)" + oid="$(jq -r '.data.createCommitOnBranch.commit.oid // empty' <<<"${response}")" + if [ -z "${oid}" ]; then + echo "${response}" >&2 + exit 1 + fi + echo "pushed ${oid}" diff --git a/aqua-checksums.json b/aqua-checksums.json index b87383d7..04d5fc86 100644 --- a/aqua-checksums.json +++ b/aqua-checksums.json @@ -46,28 +46,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.11/limen_0.0.11_darwin_arm64.tar.gz", - "checksum": "D02B0AD949E5DFDA70BA7E8A65DC52EF1FBA30D61E714539D7A8124B13471CBA", + "id": "github_release/github.com/farcloser/limen/v0.0.12/limen_0.0.12_darwin_arm64.tar.gz", + "checksum": "EDCA1AF957C0F14178F6B29D473EC899F479BB3AF2A240020FAD5965F044AC99", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.11/limen_0.0.11_linux_amd64.tar.gz", - "checksum": "F53D6F8D32A05CE23E22F3603FDA224571949421F6AFDFED4EEC441CBA94242C", + "id": "github_release/github.com/farcloser/limen/v0.0.12/limen_0.0.12_linux_amd64.tar.gz", + "checksum": "551BC67781A4FD18F1941A9BE68320DB730B982DA62BBF41988F88E721B5EE65", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.11/limen_0.0.11_linux_arm64.tar.gz", - "checksum": "B16B8D8CCBF36E4C9FBAD5D1F5450E6A0ECEF347FDF8AFD1634A86950C16E076", + "id": "github_release/github.com/farcloser/limen/v0.0.12/limen_0.0.12_linux_arm64.tar.gz", + "checksum": "13C508CC4ECE232D033AA4E336DA96EFAE35E6C247F7BFE52F8E38824EC502B1", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.11/limen_0.0.11_windows_amd64.tar.gz", - "checksum": "2582F2A161D8E21BF0EA7EBA8C538438419FA333E1ACBC741CB7454603E9670F", + "id": "github_release/github.com/farcloser/limen/v0.0.12/limen_0.0.12_windows_amd64.tar.gz", + "checksum": "3CFE919FF7CBD49C0DACA0397B5E76330861C96CE4575E997B9D3C4833AE05E0", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.0.11/limen_0.0.11_windows_arm64.tar.gz", - "checksum": "0E87B3F45818510D3B2C83586664E04C43B947E88689B04A0FDDD03F9A2E71AB", + "id": "github_release/github.com/farcloser/limen/v0.0.12/limen_0.0.12_windows_arm64.tar.gz", + "checksum": "8DA23959424BABC09D0AED2798F1E3393A7DC2F594AE4BC8A098BADDAB03F210", "algorithm": "sha256" }, { diff --git a/aqua.yaml b/aqua.yaml index 4ae8f9f8..9ea8106e 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -32,7 +32,7 @@ packages: - name: github.com/farcloser/godolint/cmd/godolint@v0.1.0 registry: local # --- farcloser tools (local registry; standard once registered upstream) --- - - name: farcloser/limen@v0.0.11 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.0.12 # renovate: depName=farcloser/limen registry: local # --- toolchain + binary-release tools (standard registry, aqua-verified) --- - name: golang/go@go1.26.5