From 5d75fbcf3dfd6eaed747cc1f74bdc42842dac15a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:33:03 +0000 Subject: [PATCH 1/2] Update dependency farcloser/limen to v0.9.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .aqua/aqua.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.aqua/aqua.yaml b/.aqua/aqua.yaml index 7dbe0c0..7132aca 100644 --- a/.aqua/aqua.yaml +++ b/.aqua/aqua.yaml @@ -26,6 +26,6 @@ packages: # are go.mod tools"). aqua's go_install would build it once per tool # version, with whichever go ran first, and share that binary. # --- farcloser tools (local registry; standard once registered upstream) --- - - name: farcloser/limen@v0.8.0 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.9.0 # renovate: depName=farcloser/limen registry: local - import: ../.limen/aqua.yaml From 446f2c0aaed1f74cfc0e94de99ac12ec75504597 Mon Sep 17 00:00:00 2001 From: "limen-ci-forkcloser[bot]" <317468017+limen-ci-forkcloser[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:33:32 +0000 Subject: [PATCH 2/2] chore: refresh checksums and converge the limen baseline Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .aqua/aqua-checksums.json | 20 +-- .github/workflows/limen-verify.yaml | 198 ++++++++++++++++++++++++++++ .github/workflows/security.yaml | 9 +- .limen/renovate.json | 5 +- AGENTS.md | 12 +- 5 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/limen-verify.yaml diff --git a/.aqua/aqua-checksums.json b/.aqua/aqua-checksums.json index b644470..3688ad5 100644 --- a/.aqua/aqua-checksums.json +++ b/.aqua/aqua-checksums.json @@ -46,28 +46,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_darwin_arm64.tar.gz", - "checksum": "E73ED78C7076867ACE43D7EBC1A1254CCAE50D5992095B405CAD7F2F10787B89", + "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_darwin_arm64.tar.gz", + "checksum": "BB92510A0019EE03BC520C58528EE828D108BD9D15279FA26F5BE99E23D53FCE", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_linux_amd64.tar.gz", - "checksum": "63A637D15AB78CE8C1ED4CC64CBEE419E467BAADB4FF290C04228D106B0BFF22", + "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_linux_amd64.tar.gz", + "checksum": "7828CCC8019C6E7E582A16E9D29BB9A00F8F00F73A7B95005597129121AFCB1B", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_linux_arm64.tar.gz", - "checksum": "E2D2BB0DE0D81092D2D590DAB80E71979A0EA883E5F81A3DF59F034703F4E0ED", + "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_linux_arm64.tar.gz", + "checksum": "845F202D468F23ED2FF4D8BA7E2B3328F98B40AD555B5C651F223F568065C9B7", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_windows_amd64.tar.gz", - "checksum": "9FF90A269A63C6739FD1AB73FD5262BB98D36145916E0D28D3E873453BDD6D57", + "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_windows_amd64.tar.gz", + "checksum": "250B810AC310B3A4083BB589BDB694512FB4CBD2E172B8DE000AAD698DDEB99C", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.8.0/limen_0.8.0_windows_arm64.tar.gz", - "checksum": "EF61BC2AD757370C00AD313F2BBD247D7847E8C1A1F8E14FB72EBBCB19FEB12A", + "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_windows_arm64.tar.gz", + "checksum": "2DA4558D67D975137800E35005374A3523F79F2D70D19A9FFAC47B75E73A9874", "algorithm": "sha256" }, { diff --git a/.github/workflows/limen-verify.yaml b/.github/workflows/limen-verify.yaml new file mode 100644 index 0000000..7306ca8 --- /dev/null +++ b/.github/workflows/limen-verify.yaml @@ -0,0 +1,198 @@ +# DO NOT EDIT MANUALLY: content-pinned by limen, reset by `limen fix`. +# The shared CI lanes every repository runs, called from its own ci.yaml +# (`uses: ./.github/workflows/limen-verify.yaml`). A change here reaches every +# repository with its limen bump; the project's own jobs live in its ci.yaml. +# +# Minimal glue: GitHub's own actions pinned by SHA, aqua at .aqua/aqua.yaml's +# pins, `just` running the recipes a laptop runs. No third-party actions, +# hence no egress-filtering action either. +name: limen-verify + +on: + workflow_call: + inputs: + os: + # Pinned images, never -latest. macOS is not redundant: its /bin/bash + # is 3.2, the floor the recipes target. Both windows legs run under + # git-bash. These names are NOT the ruleset's required contexts (the + # caller's `gate` is), so a project may trim or extend the list. + description: The verify matrix's runners, as a JSON list. + type: string + default: '["ubuntu-24.04", "ubuntu-24.04-arm", "macos-15", "windows-2025", "windows-11-arm"]' + +# No default token permissions: each job states what it needs, within what +# the caller grants (contents: read). +permissions: {} + +# Explicit bash everywhere: windows defaults to PowerShell and never sees +# git-bash's environment; explicit bash also brings -eo pipefail. +defaults: + run: + shell: bash + +jobs: + verify: + strategy: + fail-fast: false + matrix: + os: ${{ fromJSON(inputs.os) }} + runs-on: ${{ matrix.os }} + # Generous for the windows legs, which are markedly slower. + timeout-minutes: 45 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The token is not left behind in .git/config: nothing in this + # workflow talks to GitHub after checkout. + persist-credentials: false + # Full history and refs: `just do lint commits` validates the commit + # range against the PR's base branch, which a shallow clone lacks. + fetch-depth: 0 + + # Windows keeps every cache below in one disk image, cached as a single + # file: restored as files, they cost minutes of small-file writes there. + # Before setup-aqua, which takes the AQUA_ROOT_DIR it sets; the hash is + # taken here because hashFiles cannot walk into the attached image. + - id: image + if: runner.os == 'Windows' + uses: ./.github/actions/windows-cache-image + with: + mode: attach + prefix: win-image-${{ runner.os }}-${{ runner.arch }}- + hash: ${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua.yaml', '.limen/aqua-registry.yaml', '**/go.sum') }} + + - name: Install aqua (pinned, checksum-verified) + uses: ./.github/actions/setup-aqua + + # aqua's package store, keyed on the exact pins and per architecture + # (the store holds native binaries). A warm store turns every lazy first + # use below into a link; the fallback key keeps the tools a pin bump did + # not touch, and the changed one downloads and verifies as before. + - if: runner.os != 'Windows' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ env.AQUA_ROOT_DIR }}/pkgs + key: aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua-registry.yaml') }} + restore-keys: | + aqua-pkgs-${{ runner.os }}-${{ runner.arch }}- + + - name: Install pinned tools + # Link-only: tools download lazily on first use, checksum-verified; a + # job pays only for what its recipes run. + run: aqua install --only-link + + # The Go build and module caches, plus the linter's (build/cache/, see + # main.just). The per-platform analysis legs compile the whole module + # graph five times, cold, on every run without this. Paths come from the + # pinned go itself — they differ per OS — and the key carries its version + # and every go.sum; a stale restore is harmless (the build cache is + # content-addressed, the module cache is checksum-verified), so the + # fallback key is always worth taking. + - id: gocache + if: runner.os != 'Windows' + run: | + echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" + echo "mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" + echo "version=$(go env GOVERSION)" >> "$GITHUB_OUTPUT" + - if: runner.os != 'Windows' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ${{ steps.gocache.outputs.build }} + ${{ steps.gocache.outputs.mod }} + build/cache + key: go-${{ runner.os }}-${{ runner.arch }}-${{ steps.gocache.outputs.version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + go-${{ runner.os }}-${{ runner.arch }}-${{ steps.gocache.outputs.version }}- + go-${{ runner.os }}-${{ runner.arch }}- + + - name: Lint + run: just lint + + - name: Test + run: just test + + # On a miss, the image this run filled becomes the key's; a hit needs no + # save, since a cache key is never overwritten. + - if: runner.os == 'Windows' && steps.image.outputs.cache-hit != 'true' + uses: ./.github/actions/windows-cache-image + with: + mode: save + key: ${{ steps.image.outputs.key }} + + # One linux leg, not the matrix: fuzzing explores the same corpus wherever + # it runs, so more legs cost CPU and add nothing. The recipe passes on a + # tree with no targets or no Go module, so this job is safe in every + # repository. + 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 cached corpus is what makes fuzzing cumulative across runs. Keyed + # on the fuzz sources so a changed target restarts its corpus; + # restore-keys keep the rest. + - 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 + + # Every pin resolves. The verify legs link only, and a link verifies + # nothing: an asset that is missing from a release, or that fails its + # checksum, would merge green and fail at first use. One linux leg pays for + # the real install; aqua's package store is cached on the exact pins, so a + # warm key costs seconds and a changed pin is always a real install. (The + # Go-built tools are tools/go.mod directives, built by the verify legs.) + tools: + 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 + + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ env.AQUA_ROOT_DIR }}/pkgs + # The same key as the verify legs', so this job and the linux leg + # share one store — and no fallback key here: a changed pin must be + # a real install, which is what this job exists to prove. + key: aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua-registry.yaml') }} + + - name: Install every pinned tool + run: aqua install diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 062d739..3c58631 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -1,6 +1,5 @@ -# Seeded once by `limen fix`, then the project's own: limen never updates it. -# Realign it by hand when limen's canonical moves, and comment every project -# line so the next alignment keeps it. +# DO NOT EDIT MANUALLY: content-pinned by limen, reset on drift. A project's +# own scans go in its `security` recipe in the root .justfile, which this runs. # # The security lane, apart from ci: a scan's verdict moves with the # vulnerability database, not with the tree, so this runs on a schedule as @@ -51,7 +50,7 @@ jobs: - name: Install aqua (pinned, checksum-verified) uses: ./.github/actions/setup-aqua - # aqua's package store, keyed on the exact pins (see ci.yaml). + # aqua's package store, keyed on the exact pins (see limen-verify.yaml). - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ env.AQUA_ROOT_DIR }}/pkgs @@ -64,7 +63,7 @@ jobs: run: aqua install --only-link # The Go build and module caches: the scan compiles the module graph - # once per supported platform, cold without this (see ci.yaml). + # once per supported platform, cold without this (see limen-verify.yaml). - id: gocache run: | echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" diff --git a/.limen/renovate.json b/.limen/renovate.json index 626a109..08a193c 100644 --- a/.limen/renovate.json +++ b/.limen/renovate.json @@ -9,7 +9,7 @@ "vulnerabilityAlerts: fix PRs from GitHub's Dependabot alerts (the baseline keeps the alerts on for exactly this; Dependabot's own update PRs are off, Renovate is the one bot). Stated explicitly because enabled: true here is what reaches // indirect Go modules, which the gomod manager skips by default. Fix PRs skip the cooldown and take the lowest fixed version, both Renovate defaults for this object.", "postUpdateOptions gomodTidy: a Go module bump rewrites go.mod; without tidy, go.sum keeps the old module's lines and `just do lint go mod` (go mod tidy, diffed) fails every gomod PR. gomodUpdateImportPaths is what keeps tidy running on a MAJOR bump: without it Renovate skips tidy on every major (its guard for the /vN import-path rewrite, which must precede tidy), and a v0 to v1 bump is major while changing no path, so the PR landed a go.sum with the new go.mod hash, no h1 line and the old pseudo-version lines still in it, green for Renovate and red on the tidy diff. With the option, v0 to v1 does no path rewrite and tidies; a real /vN bump rewrites the imports with marwan-at-work/mod (which Renovate go-installs at run time, no image support needed) and then tidies.", "constraints gomodMod: the version Renovate go-installs marwan-at-work/mod at for that rewrite; without it the install is @latest, a third-party binary that rewrites import paths across the tree fetched at whatever is newest the day a /vN bump lands. Pinned means by digest, and this lever takes only a v-prefixed tag, so this is the nearest the lever allows: a version pin, its content fixed and transparency-logged by the checksum database that go install verifies against. Named here as that exception rather than left implicit. Renovate's own config manager marks this constraint unsupported and never scans a preset, so limen's renovate.json watches the value with a custom manager and proposes its bumps there.", - "packageRules, content-pinned files: the checksum-update workflow, the setup-aqua action and everything under .limen/ are limen's byte for byte; a bump Renovate makes inside one is drift the next `limen check` rejects. Their pins move in limen's own repository, the one place Renovate keeps touching them, and reach every other repository through a limen release.", + "packageRules, content-pinned files: the checksum-update workflow, the shared CI lanes (limen-verify.yaml), the security workflow, the setup-aqua and windows-cache-image actions and everything under .limen/ are limen's byte for byte; a bump Renovate makes inside one is drift the next `limen check` rejects. Their pins move in limen's own repository, the one place Renovate keeps touching them, and reach every other repository through a limen release.", "packageRules, Go-built tools: the tool directives under tools/ (git-validation, godolint, dot, the Go-source analyzers deadcode, govulncheck, go-licenses, and golangci-lint in tools/golangci-lint/go.mod, a module of its own so its analyzers' dependencies stay upstream's), which Go lists as // indirect and the gomod manager would skip; re-enabled exactly, by module. dot is forkcloser's own tagged module (upstream's cmd/dot is a nested module without tags), so it moves like the others, and as one of ours it is proposed without the cooldown.", "packageRules, groups: one pull request per repository per kind, not one per dependency — a registry ref that moves most days and a dozen tool pins were arriving as a dozen pull requests a month per repository, each reviewed alone. Three groups: `aqua tools` (every third-party pin in .aqua/aqua.yaml, the standard-registry ref and aqua itself included), `github actions`, and `go modules` (non-major gomod bumps, the tools/go.mod directives included). Renovate keeps the one open pull request current as further versions arrive instead of opening another. Grouped, not scheduled: a bump still arrives as soon as its cooldown ends. Outside the groups on purpose: our own releases (immediate and alone, so a limen bump is its own reviewed change), major Go bumps (code may have to move), pins.yaml entries (each a deliberate supply-chain change), and security fixes, which Renovate's vulnerabilityAlerts defaults keep ungrouped. A repository that wants a dependency out of a group adds a later packageRule with groupName null.", "packageRules, lychee: releases are tagged lychee-vX.Y.Z next to lychee-lib-vX.Y.Z and nightly; regex versioning that carries the prefix parses the pin and the candidates alike and excludes the others by construction.", @@ -61,6 +61,9 @@ "matchFileNames": [ ".github/workflows/update-aqua-checksum.yaml", ".github/actions/setup-aqua/**", + ".github/actions/windows-cache-image/**", + ".github/workflows/limen-verify.yaml", + ".github/workflows/security.yaml", ".limen/**" ], "matchRepositories": [ diff --git a/AGENTS.md b/AGENTS.md index f45364b..5017f39 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,10 @@ chapter; the procedure is limen's `skills/contribute`. human's own work, the human is the author. No scratchpads (`AUDIT.md` and its kind): they live under `_scratch/` at the repository root, which `.gitignore` ignores, and nowhere else in the tree. +- **The pull request's title is its release note.** A release's notes are the titles of + the pull requests it merged, so a title says what changed for a consumer; no + `CHANGELOG.md` is kept by hand. One that breaks a consumer carries the `breaking` label + (`gh label create breaking` the first time a repository needs it). - **One commit per thing.** Different things get different commits; iteration on the same thing — a review round, a fix to your own commit — is squashed into the commit it amends before the review is requested. Never a stack of fix-ups for one change. @@ -59,8 +63,9 @@ chapter; the procedure is limen's `skills/contribute`. - **A flake is fixed when it is noticed.** A check that fails, then passes on a rerun, gets its root cause and its fix at once, in a pull request of its own: by whoever noticed it, or by the owning session when it is another repository's. The rerun found the flake; it - did not fix it. The one exception is a flake whose cause is known and whose fix was - declined, documented as such (windows-11-arm's silent exit 4 or 127): it is rerun, and named. + did not fix it. The exceptions are flakes whose cause is known and whose fix was + declined, documented as such in the book's known upstream bugs (windows-11-arm's silent + exit 4 or 127, an aqua download that stalls with no timeout): each is rerun, and named. - **Doctrine can lose the argument, never silently.** A fix that cuts against the book is named as such and argued; it is decided, not discovered. - **Broken tooling is reported, never worked around in silence.** The rig — limen, the @@ -77,6 +82,9 @@ chapter; the procedure is limen's `skills/contribute`. `just lint` and `just test` green, the commit message written — ready to commit and push when the human is back, and say so once. Signing that never worked in the session is broken tooling (above). +- **Read what the work needs, never the whole disk.** A targeted read outside the + repositories is fine when the work calls for it; a filesystem-wide walk is not: no + `find /`, `find ~`, disk-wide `mdfind`, or recursive grep over `/` or `~`. ## Communication