From 4cc2767461732916ab249480f44e197463d7f0e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 10 Oct 2026 07:13:51 +0000 Subject: [PATCH 1/2] chore(deps): update dependency farcloser/limen to v0.10.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 c9261c9..345985a 100644 --- a/.aqua/aqua.yaml +++ b/.aqua/aqua.yaml @@ -18,6 +18,6 @@ registries: packages: # --- farcloser tools (local registry; standard once registered upstream) --- - - name: farcloser/limen@v0.9.0 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.10.0 # renovate: depName=farcloser/limen registry: local - import: ../.limen/aqua.yaml From a6c521c7aae97532c89939bea81a925283ffe2d6 Mon Sep 17 00:00:00 2001 From: "limen-ci-forkcloser[bot]" <317468017+limen-ci-forkcloser[bot]@users.noreply.github.com> Date: Sat, 10 Oct 2026 07:15:52 +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 | 72 +++++++++++++------------- .aqua/aqua.yaml | 1 + .github/actions/setup-aqua/action.yaml | 2 +- .github/release.yml | 22 ++++++++ .github/workflows/limen-verify.yaml | 2 +- .limen/aqua-registry.yaml | 10 ++-- .limen/aqua.yaml | 14 ++--- .limen/just/lint-go.just | 7 ++- .limen/just/lint.just | 45 +--------------- .limen/just/release.just | 28 ++++++++-- .limen/just/security.just | 49 +++++++++++++++++- .limen/lychee.toml | 4 +- .limen/renovate.json | 14 +++++ AGENTS.md | 25 +++++++-- 14 files changed, 192 insertions(+), 103 deletions(-) create mode 100644 .github/release.yml diff --git a/.aqua/aqua-checksums.json b/.aqua/aqua-checksums.json index 5dde5e1..0e60825 100644 --- a/.aqua/aqua-checksums.json +++ b/.aqua/aqua-checksums.json @@ -46,33 +46,33 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_darwin_arm64.tar.gz", - "checksum": "BB92510A0019EE03BC520C58528EE828D108BD9D15279FA26F5BE99E23D53FCE", + "id": "github_release/github.com/farcloser/limen/v0.10.0/limen_0.10.0_darwin_arm64.tar.gz", + "checksum": "EEF33683C8EE70E11F7A1E602ED27B9CB026E2C1E88891B1F1B0DFF80C7B5C8A", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_linux_amd64.tar.gz", - "checksum": "7828CCC8019C6E7E582A16E9D29BB9A00F8F00F73A7B95005597129121AFCB1B", + "id": "github_release/github.com/farcloser/limen/v0.10.0/limen_0.10.0_linux_amd64.tar.gz", + "checksum": "0BF69AE78A44F0FF49742931E94A3D662290E7F67AA596853233AD188EEC61A6", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_linux_arm64.tar.gz", - "checksum": "845F202D468F23ED2FF4D8BA7E2B3328F98B40AD555B5C651F223F568065C9B7", + "id": "github_release/github.com/farcloser/limen/v0.10.0/limen_0.10.0_linux_arm64.tar.gz", + "checksum": "3F95240C2FA46D6B81C19D0195E82EA82F916CC265DE505E872B8728AD255CF3", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_windows_amd64.tar.gz", - "checksum": "250B810AC310B3A4083BB589BDB694512FB4CBD2E172B8DE000AAD698DDEB99C", + "id": "github_release/github.com/farcloser/limen/v0.10.0/limen_0.10.0_windows_amd64.tar.gz", + "checksum": "72F9F50B79D419AC42D995E4C2310EF47B7E34EF58469237008A21C61A5BA55F", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.9.0/limen_0.9.0_windows_arm64.tar.gz", - "checksum": "2DA4558D67D975137800E35005374A3523F79F2D70D19A9FFAC47B75E73A9874", + "id": "github_release/github.com/farcloser/limen/v0.10.0/limen_0.10.0_windows_arm64.tar.gz", + "checksum": "C10C30E8FA2EEF3C76F8B8BA87DFEF24E5253D81AD2EF4056465CC207AC9B903", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/ossein/v0.3.0/ossein_0.3.0_darwin_arm64.tar.gz", - "checksum": "3272F1D1117C2D4F2A8FD484963B0CE8979199D047D0FFA1D110838AB1C16E5F", + "id": "github_release/github.com/farcloser/ossein/v0.3.1/ossein_0.3.1_darwin_arm64.tar.gz", + "checksum": "82BF65F53CF064625FD09BA92E4BD91D1ADDF5DDDC1E791ABDA7F0281767DDDC", "algorithm": "sha256" }, { @@ -101,13 +101,13 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/git-for-windows/git/v2.56.0.windows.1/Git-2.56.0-64-bit.tar.bz2", - "checksum": "FDF531FB5C003FFF0FED1B3EF282134C9186518F284C2E10860214CE127A973B", + "id": "github_release/github.com/git-for-windows/git/v2.56.0.windows.2/Git-2.56.0.2-64-bit.tar.bz2", + "checksum": "16CA394BDB94B372267D79E1E10B68763674F73E03E0648202A7971A8A730A84", "algorithm": "sha256" }, { - "id": "github_release/github.com/git-for-windows/git/v2.56.0.windows.1/Git-2.56.0-arm64.tar.bz2", - "checksum": "C86AEB1AFE43D2D5AFC27C4586A10795A3E28E4DDF03CDD944DFB248526BAE3D", + "id": "github_release/github.com/git-for-windows/git/v2.56.0.windows.2/Git-2.56.0.2-arm64.tar.bz2", + "checksum": "221865B66F4E6CE8F86837ACD777C4580E23DE8BF526E99DDA274C2544CAB7A0", "algorithm": "sha256" }, { @@ -136,28 +136,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.18.2/goreleaser_Darwin_all.tar.gz", - "checksum": "4BFD26942AA0F78E275CAD3E49144A7C5CE2E8A34F7D0E9D34C718125766BA12", + "id": "github_release/github.com/goreleaser/goreleaser/v2.18.3/goreleaser_Darwin_all.tar.gz", + "checksum": "156F425536D40CE282B79C637CA00EB943792DE6AD1E2482A4B21CC4B5421F4F", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.18.2/goreleaser_Linux_arm64.tar.gz", - "checksum": "A71681B29194F08F057A68CFCAA5C6B15D907A83A2622C51900C4FAFF828F322", + "id": "github_release/github.com/goreleaser/goreleaser/v2.18.3/goreleaser_Linux_arm64.tar.gz", + "checksum": "FA7011A7F8DE3B504326B8552CF21C708ED3089519B01D372DA32EF4E8D298D7", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.18.2/goreleaser_Linux_x86_64.tar.gz", - "checksum": "0A96EDC9D9BC594E4A41CC4D59467C182062910AB24D9D1F6DD7B667D32606D3", + "id": "github_release/github.com/goreleaser/goreleaser/v2.18.3/goreleaser_Linux_x86_64.tar.gz", + "checksum": "B8DEE4728BCA1ABA0D71A832A17CD0A3799F75372878E7FFA36AEF2343BCE0BC", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.18.2/goreleaser_Windows_arm64.zip", - "checksum": "D2AEBC59BEE96078C1F529160F170E02FFC96FD5D31B5E33EEF66CDD7256F602", + "id": "github_release/github.com/goreleaser/goreleaser/v2.18.3/goreleaser_Windows_arm64.zip", + "checksum": "371DA2DDDAC0C19C729A74D26305C2D168CFF50728EBE14B581FE5B7C5D98B59", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.18.2/goreleaser_Windows_x86_64.zip", - "checksum": "DE61A8E7A064ABB14210B16C942FACD299643D83341C5CD293F19155A7C8B95F", + "id": "github_release/github.com/goreleaser/goreleaser/v2.18.3/goreleaser_Windows_x86_64.zip", + "checksum": "5E7455DF2ACCBFE69416CF9CCBC6AEFE9ABD2543525D5D6DA24426D827AA8171", "algorithm": "sha256" }, { @@ -301,28 +301,28 @@ "algorithm": "sha256" }, { - "id": "http/golang.org/dl/go1.27.1.darwin-arm64.tar.gz", - "checksum": "EE215D57E0EC269C60CC9CECA68E6BDA321BA9EE5AFE24F4B0988703C2D87D12", + "id": "http/golang.org/dl/go1.27.2.darwin-arm64.tar.gz", + "checksum": "76812B213B1B2302C978D28FA52FA92D541704B9E7D9D5DB8002C50E4018C4C5", "algorithm": "sha256" }, { - "id": "http/golang.org/dl/go1.27.1.linux-amd64.tar.gz", - "checksum": "63D339F0DA5AB53635A56F2490A7984DFE12DFCFF22AD749F63EDAF590168445", + "id": "http/golang.org/dl/go1.27.2.linux-amd64.tar.gz", + "checksum": "ECBADB99091A3F46E31F5F934B068B1864EAFA7995211B39EADDF76996045FE5", "algorithm": "sha256" }, { - "id": "http/golang.org/dl/go1.27.1.linux-arm64.tar.gz", - "checksum": "3450B45A3F9EE8568792736A5C5E70A1F2E9B36C35A8F74958C03E51D7D92BEC", + "id": "http/golang.org/dl/go1.27.2.linux-arm64.tar.gz", + "checksum": "94F3E30B8E374BC285E7DADC11E0865726B9BC6E85B841CCCEAABC0214C6B7C8", "algorithm": "sha256" }, { - "id": "http/golang.org/dl/go1.27.1.windows-amd64.zip", - "checksum": "A3911B5E0E1B1053F25ED0675F4C1C6AAD1E2BFCF253DF2B9BE4CAABD2EDD95D", + "id": "http/golang.org/dl/go1.27.2.windows-amd64.zip", + "checksum": "1314008898BD40DF77AF4B014F777F08873DBDFBCD3D92308728EE03304FE04F", "algorithm": "sha256" }, { - "id": "http/golang.org/dl/go1.27.1.windows-arm64.zip", - "checksum": "13B69B87BB0E83F96BC68560A8CACE7F0343B1E03469F1110EA18D17E3234069", + "id": "http/golang.org/dl/go1.27.2.windows-arm64.zip", + "checksum": "5C4B294F23B558619AE8A4AA94D023E3BCA03F52C8398BB41B9026002B0013DE", "algorithm": "sha256" }, { diff --git a/.aqua/aqua.yaml b/.aqua/aqua.yaml index 345985a..08c2495 100644 --- a/.aqua/aqua.yaml +++ b/.aqua/aqua.yaml @@ -20,4 +20,5 @@ packages: # --- farcloser tools (local registry; standard once registered upstream) --- - name: farcloser/limen@v0.10.0 # renovate: depName=farcloser/limen registry: local + - name: golang/go@go1.27.2 - import: ../.limen/aqua.yaml diff --git a/.github/actions/setup-aqua/action.yaml b/.github/actions/setup-aqua/action.yaml index 4d531b5..e0e7cfa 100644 --- a/.github/actions/setup-aqua/action.yaml +++ b/.github/actions/setup-aqua/action.yaml @@ -18,7 +18,7 @@ runs: AQUA_INSTALLER_VERSION: v4.0.2 AQUA_INSTALLER_SHA256: 98b883756cdd0a6807a8c7623404bfc3bc169275ad9064dc23a6e24ad398f43d # renovate: depName=aquaproj/aqua - AQUA_VERSION: v2.63.0 + AQUA_VERSION: v2.64.0 run: | # Pin the root explicitly: aqua's Windows build defaults it to # %LOCALAPPDATA%, the installer and the .justfile compute ~/.local/share, diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..f5f6a53 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,22 @@ +# DO NOT EDIT MANUALLY: content-pinned by limen, reset on drift. +# +# How GitHub groups a release's notes, which are the titles of the pull +# requests merged since the previous tag (goreleaser's `use: github-native`). +# A pull request lands in the FIRST category it matches, so the catch-all +# must exclude what a later category claims. +changelog: + categories: + - title: Breaking changes + labels: + - breaking + - title: Changes + labels: + - '*' + exclude: + labels: + - dependencies + authors: + - renovate[bot] + - title: Dependencies + labels: + - '*' diff --git a/.github/workflows/limen-verify.yaml b/.github/workflows/limen-verify.yaml index 7306ca8..3d35806 100644 --- a/.github/workflows/limen-verify.yaml +++ b/.github/workflows/limen-verify.yaml @@ -160,7 +160,7 @@ jobs: # 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 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: failure() with: name: fuzz-crashers diff --git a/.limen/aqua-registry.yaml b/.limen/aqua-registry.yaml index 0fac7dd..53cf74f 100644 --- a/.limen/aqua-registry.yaml +++ b/.limen/aqua-registry.yaml @@ -301,14 +301,16 @@ packages: # git-bash. # Linked straight to usr/bin, never through bin/bash.exe, whose PATH # prepending would let Git's own curl shadow the pin. The runtime is x86_64 - # even in the arm64 tarball. The release tag is vX.Y.Z.windows.N, the asset - # name carries X.Y.Z only. Upstream lists each asset's SHA-256 in the release - # notes: check the recorded checksums against that table at every bump. + # even in the arm64 tarball. The release tag is vX.Y.Z.windows.N; the asset + # name carries X.Y.Z for the first build and X.Y.Z.N for a rebuild + # (v2.56.0.windows.2 ships Git-2.56.0.2-64-bit.tar.bz2). Upstream lists each + # asset's SHA-256 in the release notes: check the recorded checksums against + # that table at every bump. - type: github_release repo_owner: git-for-windows repo_name: git description: Git for Windows, for its MSYS2 GNU coreutils - asset: Git-{{ regexReplaceAll "\\.windows\\.[0-9]+$" (trimV .Version) "" }}-{{.Arch}}.tar.bz2 + asset: Git-{{ regexReplaceAll "\\.windows\\.([0-9]+)$" (regexReplaceAll "\\.windows\\.1$" (trimV .Version) "") ".${1}" }}-{{.Arch}}.tar.bz2 format: tar.bz2 replacements: amd64: 64-bit diff --git a/.limen/aqua.yaml b/.limen/aqua.yaml index 0b06914..fa9662b 100644 --- a/.limen/aqua.yaml +++ b/.limen/aqua.yaml @@ -18,25 +18,27 @@ packages: # The registry entries say why, and how each is verified. - name: uutils/coreutils@0.12.0 registry: local - - name: git-for-windows/git@v2.56.0.windows.1 + - name: git-for-windows/git@v2.56.0.windows.2 registry: local - # --- toolchain + binary-release tools (standard registry, aqua-verified) --- - - name: golang/go@go1.27.1 + # --- binary-release tools (standard registry, aqua-verified) --- + # Not here: golang/go. The Go toolchain is each repository's own pin, in its + # .aqua/aqua.yaml above the import (seeded once by `limen fix`, Renovate's + # afterwards): a standard-library fix must not wait for a limen release. - name: casey/just@1.58.0 - name: koalaman/shellcheck@v0.11.0 - name: google/yamlfmt@v0.21.0 - name: lycheeverse/lychee@lychee-v0.24.2 - - name: goreleaser/goreleaser@v2.18.2 + - name: goreleaser/goreleaser@v2.18.3 - name: sigstore/cosign@v3.1.3 - name: gotestyourself/gotestsum@v1.13.0 - name: jqlang/jq@jq-1.8.2 - name: cli/cli@v2.102.0 # --- spec tooling (local registry; see .limen/aqua-registry.yaml) --- - - name: quint-co/quint@v0.33.0 # renovate: depName=quint-co/quint + - name: quint-co/quint@v0.33.0 registry: local - name: forkcloser/curl@v8.22.0 registry: local # darwin/arm64 only. Its `docker` link shadows the machine's Docker on the # hermetic PATH there; aqua skips the package everywhere else. - - name: farcloser/ossein@v0.3.0 + - name: farcloser/ossein@v0.3.1 registry: local diff --git a/.limen/just/lint-go.just b/.limen/just/lint-go.just index 96eff2e..3672ec4 100644 --- a/.limen/just/lint-go.just +++ b/.limen/just/lint-go.just @@ -160,7 +160,12 @@ nilaway: (_banner "lint go" "nilaway") (_go-tool "nilaway" "go.uber.org/nilaway/ # their location under GOROOT, reading its default from its own build. _go-tool # compiles it with the pinned toolchain, so the two agree by construction; the # explicit value keeps that true even when a project overrides GOROOT. -licenses: (_banner "lint go" "licenses") (_go-tool "go-licenses" "github.com/google/go-licenses/v2") (_per-platform 'GOROOT="$(go env GOROOT)" build/tools/go-licenses check --include_tests $(${LIMEN_LINT_GO_BIN:-limen-lint-go} flags licenses) ./...') +# +# The module's own path is ignored by the lane: the lane judges the dependency +# graph, the repository's own LICENSE is the `license` rule's, and go-licenses +# classifies a license by its text, so the Closed-source notice limen writes +# failed every package of such a module. +licenses: (_banner "lint go" "licenses") (_go-tool "go-licenses" "github.com/google/go-licenses/v2") (_per-platform 'GOROOT="$(go env GOROOT)" build/tools/go-licenses check --include_tests --ignore "$(go list -m)" $(${LIMEN_LINT_GO_BIN:-limen-lint-go} flags licenses) ./...') # Unreachable functions, by whole-program call-graph analysis (deeper than the # per-package `unused` linter in golangci). -test keeps library-only repos diff --git a/.limen/just/lint.just b/.limen/just/lint.just index fe55c12..515fc5e 100644 --- a/.limen/just/lint.just +++ b/.limen/just/lint.just @@ -10,7 +10,7 @@ set quiet # Shared private _banner recipe (see lib.just). import 'lib.just' -default: limen just aqua links yaml shell dockerfile commits +default: limen just aqua yaml shell dockerfile commits # Go linters live in their own submodule: `just do lint go` runs them all, # `just do lint go ` (e.g. `just do lint go mod`) runs one. @@ -172,49 +172,6 @@ shell: (_banner "lint" "shell") # in reality, so those hits can only be false positives of the masking. [ ${#recipes[@]} -eq 0 ] || shellcheck --rcfile .limen/.shellcheckrc --exclude=SC1010,SC2050,SC2078,SC2157,SC2194 "${recipes[@]}" -# Check documentation links with lychee (respects .gitignore, skips hidden files). -links: (_banner "lint" "links") - #!/usr/bin/env bash - set -euo pipefail - # Canonical exclusions (and their rationale) live in .limen/lychee.toml; a - # repository adds its own in a root .lint-links.toml. Both must be passed - # explicitly — any --config disables lychee's ./lychee.toml auto-discovery — - # and lychee merges them, concatenating the exclude lists. - args=(--config .limen/lychee.toml) - if [ -f .lint-links.toml ]; then - args+=(--config .lint-links.toml) - fi - # WORKAROUND, lychee through 0.24.2 (lychee-lib/src/retry.rs): a rejected - # status is judged retryable only when it is 429, so a 5xx is never - # retried whatever max_retries says, and one GitHub 503 blip fails the - # lane. Server errors alone get a second pass, re-checked once a minute - # later; any other failure is a finding at once. Drop this when lychee - # retries 5xx itself. - scratch=$(mktemp -d "${TMPDIR:-/tmp}/lint-links.XXXXXX") - trap 'rm -rf "$scratch"' EXIT - report="$scratch/report.json" - status=0 - lychee --no-progress "${args[@]}" --format json --output "$report" . || status=$? - if [ "$status" = 0 ]; then - jq -r '"\(.total) links: \(.successful) ok, \(.excludes) excluded, \(.redirects) redirected"' "$report" - exit 0 - fi - # 2 is "links failed"; anything else (a config or input error) is final. - [ "$status" = 2 ] || exit "$status" - jq -r '(.error_map, .timeout_map) | to_entries[] | .key as $file | .value[] - | "[\(.status.code // "timeout")] \(.url) (\($file):\(.span.line // 0)) | \(.status.text)"' "$report" - servers=$(jq '[.error_map[][] | select(.status.code != null and .status.code >= 500 and .status.code < 600)] | length' "$report") - failures=$(jq '[.error_map[][], .timeout_map[][]] | length' "$report") - if [ "$servers" = 0 ] || [ "$servers" != "$failures" ]; then - exit 2 - fi - # The links go back in as a document: a URL given as an input is a page to - # read links out of, not a link to check. - jq -r '[.error_map[][] | .url] | unique[] | "<\(.)>"' "$report" > "$scratch/recheck.md" - echo "only server errors, which lychee does not retry: re-checking $(wc -l < "$scratch/recheck.md" | tr -d ' ') link(s) once, in 60s" >&2 - sleep 60 - lychee --no-progress "${args[@]}" "$scratch/recheck.md" - # Validate commit hygiene — DCO sign-off, subject length, dangling whitespace — # over a commit range (default: everything ahead of the upstream default branch; # pass one explicitly otherwise, e.g. `just do lint commits v1.0.0..HEAD`). diff --git a/.limen/just/release.just b/.limen/just/release.just index 77fceac..dad1227 100644 --- a/.limen/just/release.just +++ b/.limen/just/release.just @@ -8,7 +8,8 @@ # import's settings would apply to the whole importing module. # Lanes: `vX.Y.Z` cuts the signed tag and pushes (CI builds, signs keyless, -# publishes); `--local vX.Y.Z` is the fully local, key-based lane and the +# publishes; without a .release-go.yaml, the recipe creates the release page +# itself); `--local vX.Y.Z` is the fully local, key-based lane and the # sole publisher; `--local --dry-run` is an unsigned snapshot; `--ci` is the # workflow half. usage() below and book/recipes.md have the details. # @@ -29,7 +30,7 @@ release *args: fi } usage() { - echo "usage: just do release # signed tag + push; CI builds, signs (keyless), publishes" >&2 + echo "usage: just do release # signed tag + push; CI builds, signs (keyless), publishes (no .release-go.yaml: the release page, with notes)" >&2 echo " just do release --local [--cosign-password-stdin] # fully local, key-based cosign" >&2 echo " just do release --local --dry-run # local unsigned snapshot into build/release/" >&2 echo " just do release --ci # workflow half of the default lane (GitHub Actions only)" >&2 @@ -229,7 +230,28 @@ release *args: if has_goreleaser; then echo "tag $tag pushed — the release workflow builds, signs, and publishes from here." else - echo "tag $tag pushed — no .release-go.yaml, so the signed tag is the release: nothing to build or publish." + # No artifacts to build, but every tag has a release page, + # with GitHub's notes from the merged pull requests (grouped + # by .github/release.yml): the laptop makes it, through the + # pinned gh and the human's own auth, on the repository the + # tag went to (gh would otherwise pick between origin and + # upstream by its own rules). --verify-tag refuses a tag that + # has not reached origin. GitHub starts the notes at the last + # PUBLISHED release, so a repository whose earlier tags had no + # page gets every pull request since inception unless the + # previous tag is named. A retry finds the page and leaves it. + repo="$(git remote get-url origin)" + if gh release view "$tag" -R "$repo" >/dev/null 2>&1; then + echo "tag $tag pushed — its release page already exists (a retry): nothing to build or publish." + else + opts=(--verify-tag --generate-notes) + if previous="$(git describe --tags --match "v*" --abbrev=0 "$tag^" 2>/dev/null)"; then + opts+=(--notes-start-tag "$previous") + fi + case "$tag" in *-*) opts+=(--prerelease) ;; esac + gh release create "$tag" -R "$repo" "${opts[@]}" + echo "tag $tag pushed and its release page created — no .release-go.yaml, so nothing else to build or publish." + fi fi ;; esac diff --git a/.limen/just/security.just b/.limen/just/security.just index e6b549d..dc7cec2 100644 --- a/.limen/just/security.just +++ b/.limen/just/security.just @@ -23,7 +23,54 @@ mod go 'security-go.just' # A submodule's recipe IS addressable as a dependency (go::default); only a # bare module path (go) is not. -default: go::default +default: links go::default + +# Here and not in lint: a link's verdict is the web's, not the tree's, and moves +# without a push, exactly a scan's shape; in lint it reddened every verify leg +# and gate on one host's bad minute, five times over. + +# Check documentation links with lychee (respects .gitignore, skips hidden files). +links: (_banner "security" "links") + #!/usr/bin/env bash + set -euo pipefail + # Canonical exclusions (and their rationale) live in .limen/lychee.toml; a + # repository adds its own in a root .lint-links.toml. Both must be passed + # explicitly — any --config disables lychee's ./lychee.toml auto-discovery — + # and lychee merges them, concatenating the exclude lists. + args=(--config .limen/lychee.toml) + if [ -f .lint-links.toml ]; then + args+=(--config .lint-links.toml) + fi + # WORKAROUND, lychee through 0.24.2 (lychee-lib/src/retry.rs): a rejected + # status is judged retryable only when it is 429, so a 5xx is never + # retried whatever max_retries says, and one GitHub 503 blip fails the + # lane. Server errors alone get a second pass, re-checked once a minute + # later; any other failure is a finding at once. Drop this when lychee + # retries 5xx itself. + scratch=$(mktemp -d "${TMPDIR:-/tmp}/security-links.XXXXXX") + trap 'rm -rf "$scratch"' EXIT + report="$scratch/report.json" + status=0 + lychee --no-progress "${args[@]}" --format json --output "$report" . || status=$? + if [ "$status" = 0 ]; then + jq -r '"\(.total) links: \(.successful) ok, \(.excludes) excluded, \(.redirects) redirected"' "$report" + exit 0 + fi + # 2 is "links failed"; anything else (a config or input error) is final. + [ "$status" = 2 ] || exit "$status" + jq -r '(.error_map, .timeout_map) | to_entries[] | .key as $file | .value[] + | "[\(.status.code // "timeout")] \(.url) (\($file):\(.span.line // 0)) | \(.status.text)"' "$report" + servers=$(jq '[.error_map[][] | select(.status.code != null and .status.code >= 500 and .status.code < 600)] | length' "$report") + failures=$(jq '[.error_map[][], .timeout_map[][]] | length' "$report") + if [ "$servers" = 0 ] || [ "$servers" != "$failures" ]; then + exit 2 + fi + # The links go back in as a document: a URL given as an input is a page to + # read links out of, not a link to check. + jq -r '[.error_map[][] | .url] | unique[] | "<\(.)>"' "$report" > "$scratch/recheck.md" + echo "only server errors, which lychee does not retry: re-checking $(wc -l < "$scratch/recheck.md" | tr -d ' ') link(s) once, in 60s" >&2 + sleep 60 + lychee --no-progress "${args[@]}" "$scratch/recheck.md" # Every Go binary aqua pins carries its own copy of the standard library, built # by its upstream with whatever Go they used that day: a fixed net/http or diff --git a/.limen/lychee.toml b/.limen/lychee.toml index 8074385..7d8a999 100644 --- a/.limen/lychee.toml +++ b/.limen/lychee.toml @@ -1,6 +1,6 @@ # DO NOT EDIT MANUALLY. # Canonical lychee (link checker) configuration, identical in every repository. -# A repository can add its own exclusions in a root .lint-links.toml — the `lint links` +# A repository can add its own exclusions in a root .lint-links.toml — the `security links` # recipe passes both files and lychee merges them, concatenating the exclude # lists — so this baseline only carries exclusions that apply everywhere. @@ -13,7 +13,7 @@ hidden = true # lychee's default retry window spans seconds. Widened for what lychee does # retry: timeouts and 429 rate limits. A 5xx is never retried here, whatever # these say: lychee judges a rejected status retryable only for 429. The -# `lint links` recipe re-checks server errors once, a minute later, instead. +# `security links` recipe re-checks server errors once, a minute later, instead. # Never accept the codes: a server still erroring after a minute is a finding. max_retries = 6 retry_wait_time = 5 diff --git a/.limen/renovate.json b/.limen/renovate.json index 08a193c..075d3ca 100644 --- a/.limen/renovate.json +++ b/.limen/renovate.json @@ -4,7 +4,10 @@ "The canonical Renovate configuration of every farcloser repository, content-pinned like the rest of .limen/: each repository carries this file byte for byte and its renovate.json extends it by the repository's own name (local>owner/name//.limen/renovate — Renovate refuses a relative reference in a repository's own config), so a fix here reaches every repository at its next limen bump, in the bump's diff. Reasoning: limen's book, mandatory files (CI workflows).", "extends: config:recommended, plus aqua's preset, which detects owner/repo@ver and go-module packages in .aqua/aqua.yaml and updates the standard-registry ref. Its `aqua_version` manager reads that key in lowercase only; the canonical setup-aqua action names its pin AQUA_VERSION, so aqua itself sat three minors behind for months with nothing proposing the bump. The customManager below watches that pin, in limen alone (the content-pinned rule silences the action everywhere else), so aqua moves through a limen release like every other canonical pin.", "minimumReleaseAge: supply-chain cooldown before proposing a bump; doubles as protection against a release tag whose assets are not uploaded yet.", + "packageRules, golang/go: no cooldown. The Go toolchain is each repository's own pin so that a standard-library fix reaches it without a limen release, and a Go patch release is that fix; three days of cooldown would be three days of red security lanes. Go's releases are not pulled or re-uploaded the way the cooldown guards against.", "packageRules, own releases: the cooldown guards against third parties (a tag whose assets are still uploading, a release pulled the next day). A release cut by one of our organizations carries no risk we do not already own, and its assets are published by the release workflow within minutes \u2014 so farcloser, forkcloser and mycophonic packages, in aqua's owner/repo form and Go's github.com/owner/repo form alike, are proposed as soon as they exist. A repository that wants the cooldown back on one of them adds a later packageRule in its own renovate.json; rules merge in order and the last match wins.", + "rebaseWhen behind-base-branch: a Renovate branch is rebased whenever main moves. The default, auto, rebases only on a conflict here, since the rulesets do not require a branch to be up to date, so a pull request gone red from a bug since fixed on main stayed red at its stale head until someone ticked its dashboard checkbox. The cost, per main commit per open Renovate pull request: a CI run on the rebase, the checksum workflow's re-converge, and a CI run on that, which the groups keep to a few pull requests per repository. The converge commits do not stop the rebase: each repository's renovate.json lists the bots that make them in gitIgnoredAuthors (the renovate rule keeps it so). A rebase is a push, and the rulesets dismiss a stale approval on push, so a rebase after the reviewing session's approval costs a re-approval at the new head, accepted: an approval covers one head, and Renovate re-arms auto-merge after the rebase.", + "automerge, platformAutomerge: Renovate arms GitHub's auto-merge on every pull request it opens, and GitHub merges it the moment the ruleset is satisfied: one approval, gate and security green. The approval is the decision, so the reviewing session's approval is the only gate a Renovate bump waits on; nobody clicks merge. platformAutomerge keeps the merge GitHub's (Renovate re-arms it after each rebase), automergeType pr keeps it a pull request a reviewer sees, and automergeStrategy merge-commit is the method the signatures rule relies on, the branch's commits landing verbatim. The checksum workflow's converge commit lands before the approval, since the reviewer approves the converged head.", "dependencyDashboard: the one issue Renovate keeps open on every repository it processes, updates pending or not. `limen github check` reads it as the proof that Renovate is running at all (it skips forks by default, before any preset resolves, and every configuration check stays green while it does). Stated explicitly, like vulnerabilityAlerts, so a project cannot switch it off and defeat the check unseen.", "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.", @@ -23,6 +26,11 @@ "github>aquaproj/aqua-renovate-config#2.13.0" ], "minimumReleaseAge": "3 days", + "rebaseWhen": "behind-base-branch", + "automerge": true, + "automergeType": "pr", + "automergeStrategy": "merge-commit", + "platformAutomerge": true, "dependencyDashboard": true, "vulnerabilityAlerts": { "enabled": true @@ -57,6 +65,12 @@ ], "minimumReleaseAge": "0 days" }, + { + "matchDepNames": [ + "golang/go" + ], + "minimumReleaseAge": "0 days" + }, { "matchFileNames": [ ".github/workflows/update-aqua-checksum.yaml", diff --git a/AGENTS.md b/AGENTS.md index 5017f39..e803530 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,13 +44,23 @@ chapter; the procedure is limen's `skills/contribute`. harness reminder asks for; grep before you push. - **Green before pushing:** the whole `just lint` and `just test`, not one lane. - **Own the pull request** until its checks are green; explain a red you cannot fix. -- **Request the owner's review only then** — green, ready, and not stacked on an - unmerged branch. The request is sent once; withdraw it if the pull request turns red. +- **An approved pull request merges itself.** The reviewing session arms GitHub's + auto-merge with its ✅ at the head it read, and disarms it on a later 🛑 or ⚠️; GitHub + merges the moment the ruleset is satisfied, the owner's approval plus green checks. The + author never arms it, never merges by hand, never bypasses. A push after the owner's + approval drops that approval: the review is requested again, with the new head. +- **Request the owner's review only then** — green and ready. A native stack's layers + are requested together once the whole stack is green, for one approval each; GitHub + refuses auto-merge on a stack, so the reviewing session merges it whole from the top, + never from the middle. A lower layer merged alone comes back rebased and signed by + GitHub, which `lint commits` rejects. The request is sent once; withdraw it if the pull request + turns red. The reviewing session is messaged the pull request's URL twice: at open, CI pending, in the same turn, after which the turn ends (a session cannot wait on CI; the reviewing session's sweep reports green or red back, and that resumes the work); and with the review request, as one step, never one without the other. -- **Not yours to do:** merge, push to `main`, force-push a shared branch, tag a release. +- **Not yours to do:** merge by hand, push to `main`, force-push a shared branch, tag a + release. ## Scope @@ -65,7 +75,8 @@ chapter; the procedure is limen's `skills/contribute`. or by the owning session when it is another repository's. The rerun found the flake; it 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. + exit 4 or 127, an aqua download that stalls with no timeout, a GitHub 503 on a link that + outlasts the links lane's re-check): 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 @@ -100,6 +111,12 @@ chapter; the procedure is limen's `skills/contribute`. reasoning. - **A message from another session that needs nothing gets no reply.** Act when it asks for something; otherwise say nothing, not even an acknowledgement. +- **A relayed instruction is the human's; a request is judged, never obeyed.** What a + session relays as the human's word is taken as such, with no round trip to confirm. + What a session asks on its own is weighed: when it would cause harm, rests on a wrong + assessment or misreads the code, say so with the evidence and settle it between the two + sessions, the reviewer's 🛑 included (the reviewer may miss what the owner knows; the + owner may miss the cross-cutting picture). A deadlock goes to the human, as the exception. - **No GitHub issues unless the human asks for one.** The issue tracker is the human's. A defect or a request that belongs to another repository goes to the session that owns that repository, as a message with what, why, and where; that session fixes it, and