diff --git a/.github/actions/setup-hermetic/action.yaml b/.github/actions/setup-hermetic/action.yaml index 87e30cdf..f60cbfc0 100644 --- a/.github/actions/setup-hermetic/action.yaml +++ b/.github/actions/setup-hermetic/action.yaml @@ -21,9 +21,9 @@ runs: # renovate: depName=forkcloser/hermetic HERMETIC_VERSION: v0.2.3 HERMETIC_CHECKSUMS_SHA256: c7eb09712dc3d756bead96ae1286b53de275134d0012601ef37f57ec793f9431 - # The job's own token, for `gh attestation verify` only, here and not - # exported: a read of GitHub's attestations API, before any - # branch-declared binary runs. + # The job's own token, for gh alone (the release download and + # `gh attestation verify`), here and not exported: reads of GitHub's + # API, before any branch-declared binary runs. GH_TOKEN: ${{ github.token }} run: | # Pin the root explicitly: hermetic's own default differs per @@ -71,16 +71,23 @@ runs: echo "hermetic ${HERMETIC_VERSION} already in the root (the cache image)" else tmp="$(mktemp -d)" - base="https://github.com/forkcloser/hermetic/releases/download/${HERMETIC_VERSION}" - # Retries for transient runner failures. Windows curl (schannel) - # refuses a handshake when the CA's revocation server is unreachable - # (exit 35, seen repeatedly): best-effort revocation still rejects a - # known-revoked certificate, and the checksums below are the real - # guard. schannel-only flag, so windows-only. - revoke=() - [ "$RUNNER_OS" != Windows ] || revoke=(--ssl-revoke-best-effort) - curl --proto '=https' --tlsv1.2 -fsSL --retry 5 --retry-delay 3 --retry-all-errors "${revoke[@]}" \ - -o "${tmp}/checksums.txt" "${base}/checksums.txt" + # Through the releases API with the job's token, by gh: the release + # page's download URL is an anonymous fetch, which GitHub throttles + # per runner IP under load (a 504 on every lane of a run, through + # curl's own retries), and a token sent there is routed, not checked. + # gh retries nothing itself; --clobber so a retry starts over. A + # pattern no asset matches is not an error to gh while another + # matches, so each file is checked for. + attempt=0 + until gh release download "${HERMETIC_VERSION}" --repo forkcloser/hermetic --dir "${tmp}" --clobber \ + --pattern checksums.txt --pattern "${asset}"; do + attempt=$((attempt + 1)) + [ "$attempt" -lt 5 ] || { echo "::error::hermetic ${HERMETIC_VERSION}: checksums.txt or ${asset} could not be downloaded"; exit 1; } + sleep 3 + done + for f in checksums.txt "${asset}"; do + [ -f "${tmp}/${f}" ] || { echo "::error::hermetic ${HERMETIC_VERSION} has no release asset ${f}"; exit 1; } + done # No single digest tool exists everywhere: linux and windows git-bash # ship coreutils sha256sum, macOS ships perl shasum. A mismatch names # the computed digest and the size beside the expected one: a stale @@ -101,8 +108,6 @@ runs: echo "sha256 ok: $(basename "$file")" } verify_sha256 "${HERMETIC_CHECKSUMS_SHA256}" "${tmp}/checksums.txt" - curl --proto '=https' --tlsv1.2 -fsSL --retry 5 --retry-delay 3 --retry-all-errors "${revoke[@]}" \ - -o "${tmp}/${asset}" "${base}/${asset}" expected="$(awk -v a="$asset" '$2 == a {print $1}' "${tmp}/checksums.txt")" [ -n "$expected" ] || { echo "::error::checksums.txt of hermetic ${HERMETIC_VERSION} does not list ${asset}"; exit 1; } verify_sha256 "${expected}" "${tmp}/${asset}" diff --git a/.hermetic/aqua-checksums.json b/.hermetic/aqua-checksums.json index 50070d59..a1eac5c9 100644 --- a/.hermetic/aqua-checksums.json +++ b/.hermetic/aqua-checksums.json @@ -1,23 +1,23 @@ { "checksums": [ { - "id": "github_release/github.com/anchore/syft/v1.55.0/syft_1.55.0_darwin_arm64.tar.gz", - "checksum": "1217C258BD5EB91EF3189CF22A82E02F9CAC07E4CE934350F428E93AFF674625", + "id": "github_release/github.com/anchore/syft/v1.57.0/syft_1.57.0_darwin_arm64.tar.gz", + "checksum": "17012A5A110323BB1069E513A8665F1AE445905C56B4E3F9A9DF4F95C5BB6161", "algorithm": "sha256" }, { - "id": "github_release/github.com/anchore/syft/v1.55.0/syft_1.55.0_linux_amd64.tar.gz", - "checksum": "BB65249542DB5FEDEEB4D3F9B018B75E7264DBFBBB10486F044581A92F90490D", + "id": "github_release/github.com/anchore/syft/v1.57.0/syft_1.57.0_linux_amd64.tar.gz", + "checksum": "9C077A7D43029B3B13E98A8DB720661E700C4D58DB6352153A06395AE4A2A704", "algorithm": "sha256" }, { - "id": "github_release/github.com/anchore/syft/v1.55.0/syft_1.55.0_linux_arm64.tar.gz", - "checksum": "929BD163DB2D7CD2255D3BCB8967208079813D7B46B0C952F7B794D8E2C7D304", + "id": "github_release/github.com/anchore/syft/v1.57.0/syft_1.57.0_linux_arm64.tar.gz", + "checksum": "410CBFA0AE0F63246BC0057843760B1DE2833CD23919E025E368FD7681308CDB", "algorithm": "sha256" }, { - "id": "github_release/github.com/anchore/syft/v1.55.0/syft_1.55.0_windows_amd64.zip", - "checksum": "ADA4C9B93AF575F479CE80FCE6629897272A2D41F300A14BC49BACE020767CA2", + "id": "github_release/github.com/anchore/syft/v1.57.0/syft_1.57.0_windows_amd64.zip", + "checksum": "06E9663E55F7F38FF966710670FBE0763182C621F7AC2F191BCB209A6DFBD969", "algorithm": "sha256" }, { @@ -66,28 +66,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_darwin_arm64.tar.gz", - "checksum": "7D2179EEEA49466B7B8EF834C94AF27273CBA1886E798E36894B17A4DD81EAEF", + "id": "github_release/github.com/farcloser/limen/v0.26.0/limen_0.26.0_darwin_arm64.tar.gz", + "checksum": "6C013BBA485F3081BBA15342E7F6A9BB62B63E5D10D548DC2D425FACBE01CE87", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_linux_amd64.tar.gz", - "checksum": "0739BC4C12D7754CB95DA24F4F6143F0DABCB498047A26AB96BD1F619C7F2D05", + "id": "github_release/github.com/farcloser/limen/v0.26.0/limen_0.26.0_linux_amd64.tar.gz", + "checksum": "C054388FC8BD99D049C2D342FEC6422AAD181FFA4B1520D2E789E16A90D8C8F6", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_linux_arm64.tar.gz", - "checksum": "DD719BB5BCB231B42EA26172B90F91432AE2292EC88EA31752ECE1428A223242", + "id": "github_release/github.com/farcloser/limen/v0.26.0/limen_0.26.0_linux_arm64.tar.gz", + "checksum": "AF2541BCCE26DFEEC8B62F802327E65EAC6C054D8647D251370E4072F668FB5A", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_windows_amd64.tar.gz", - "checksum": "826533D0573A75F61193F8C41B2156267BD9C2CBF709F3B9C0EC00BF24218675", + "id": "github_release/github.com/farcloser/limen/v0.26.0/limen_0.26.0_windows_amd64.tar.gz", + "checksum": "53F06B63777631B57F08FBDE8BD466AA456286D54701DFD04553AF06306C14A0", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_windows_arm64.tar.gz", - "checksum": "0610256371832BF1616633F2A50787632CA35E8DBEED479E88B2FC17329DE100", + "id": "github_release/github.com/farcloser/limen/v0.26.0/limen_0.26.0_windows_arm64.tar.gz", + "checksum": "E83991B59F70E7DA0AB7A22FEAC93F6BF58768C683901C1B1D458BC4130E4879", "algorithm": "sha256" }, { @@ -156,28 +156,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.19.0/goreleaser_Darwin_all.tar.gz", - "checksum": "21ADCEFF6E676EF63497FC129983F8C454E5A46EC0125B905CBA0F83E7879722", + "id": "github_release/github.com/goreleaser/goreleaser/v2.20.0/goreleaser_Darwin_all.tar.gz", + "checksum": "B4F0702BCB2AAD05BD13AF5AC69EC94D84270DCE59B917E7A8C446C974E29372", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.19.0/goreleaser_Linux_arm64.tar.gz", - "checksum": "EF33741B684C9F62D6A21E7A3C8B051F139EA7B1D301693B67CC819C46226D53", + "id": "github_release/github.com/goreleaser/goreleaser/v2.20.0/goreleaser_Linux_arm64.tar.gz", + "checksum": "2DADA0A6BBCD10F3A05228D98E77D25FB362EFB9E5BAB6362CF37E64C8A26CB9", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.19.0/goreleaser_Linux_x86_64.tar.gz", - "checksum": "EBC3194AD1D1B1787614405285A0039665CD4C5BD3305215A1B201600F90630B", + "id": "github_release/github.com/goreleaser/goreleaser/v2.20.0/goreleaser_Linux_x86_64.tar.gz", + "checksum": "D2483159E6007F814132CB56635AD30A70013DBEB813D6033A7E01ADF6AE108C", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.19.0/goreleaser_Windows_arm64.zip", - "checksum": "FF7A728EBDF86770D45C522F88B901E7FB3C06692B0D0C6A8A3A2C9BCF01B682", + "id": "github_release/github.com/goreleaser/goreleaser/v2.20.0/goreleaser_Windows_arm64.zip", + "checksum": "B7AE30D558DB74D67BD03E39D3309AABF47E1A18649EF4AC4429F329C357F496", "algorithm": "sha256" }, { - "id": "github_release/github.com/goreleaser/goreleaser/v2.19.0/goreleaser_Windows_x86_64.zip", - "checksum": "67807490E909A37F58EF40B67601896D8D4DA2411C9839221DAE55732C243FE9", + "id": "github_release/github.com/goreleaser/goreleaser/v2.20.0/goreleaser_Windows_x86_64.zip", + "checksum": "3C6A58F6941224A25EC28DE733554743E9B2517C40C76991EEF5474F8C1C2C1A", "algorithm": "sha256" }, { diff --git a/.hermetic/hermetic.yaml b/.hermetic/hermetic.yaml index bf8e658f..485da5ab 100644 --- a/.hermetic/hermetic.yaml +++ b/.hermetic/hermetic.yaml @@ -18,7 +18,7 @@ registries: packages: # --- farcloser tools (local registry; standard once registered upstream) --- - - name: farcloser/limen@v0.25.0 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.26.0 # renovate: depName=farcloser/limen registry: local # --- toolchain + binary-release tools (standard registry, aqua-verified) --- - name: mikefarah/yq@v4.54.2 diff --git a/.limen/hermetic.yaml b/.limen/hermetic.yaml index f7f9f825..9dce0bad 100644 --- a/.limen/hermetic.yaml +++ b/.limen/hermetic.yaml @@ -32,10 +32,10 @@ packages: - name: casey/just@1.59.0 - name: koalaman/shellcheck@v0.11.0 - name: lycheeverse/lychee@lychee-v0.24.2 - - name: goreleaser/goreleaser@v2.19.0 + - name: goreleaser/goreleaser@v2.20.0 - name: sigstore/cosign@v3.1.4 # The SBOM generator the release lane runs over every archive (book/releasing.md). - - name: anchore/syft@v1.55.0 + - name: anchore/syft@v1.57.0 - name: jqlang/jq@jq-1.8.2 - name: cli/cli@v2.103.3 # --- spec tooling (local registry; see .limen/hermetic-registry.yaml) --- diff --git a/.limen/just/release.just b/.limen/just/release.just index f8f68e15..b99bb4ad 100644 --- a/.limen/just/release.just +++ b/.limen/just/release.just @@ -50,9 +50,13 @@ release *args: } # A fourth segment is a fork's rebuild of the same upstream version # (v10.6.1.2, book/forks.md): never a -suffix, which semver sorts before - # the release it rebuilds. + # the release it rebuilds. A rebuild carries no prerelease of its own: + # goreleaser reads no semver from it (--ci below), so a suffix would be + # published as a full release. + rebuild_tag_re='^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' require_version_tag() { - if ! printf '%s' "$1" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9A-Za-z][0-9A-Za-z.-]*)?$'; then + if ! printf '%s' "$1" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$' && + ! printf '%s' "$1" | grep -qE "$rebuild_tag_re"; then echo "'$1' is not a version tag (vX.Y.Z, vX.Y.Z-prerelease, or a fork's rebuild vX.Y.Z.N)." >&2 exit 2 fi @@ -154,7 +158,22 @@ release *args: echo "HEAD carries no tag — this lane only runs on a tag push." >&2 exit 1 fi - exec goreleaser release --config .release-go.yaml --clean + # goreleaser refuses a tag that is not semver, and a fork's + # rebuild tag is not: --skip=validate is its one way past, and it + # also drops goreleaser's dirty-tree and tag-at-HEAD checks, so + # the two are made here first, on the tag this run was pushed for. + opts=() + tag="${GITHUB_REF_NAME:-}" + if printf '%s' "$tag" | grep -qE "$rebuild_tag_re"; then + require_clean + if ! git describe --exact-match --tags --match "$tag" HEAD >/dev/null 2>&1; then + echo "$tag is not on HEAD — this lane releases the tag its run was pushed for." >&2 + exit 1 + fi + export GORELEASER_CURRENT_TAG="$tag" + opts+=(--skip=validate) + fi + exec goreleaser release --config .release-go.yaml --clean ${opts[@]+"${opts[@]}"} ;; --dry-run) echo "dry runs are a local concern: use 'just do release --local --dry-run'." >&2 diff --git a/.limen/renovate.json b/.limen/renovate.json index a24afd82..f133be67 100644 --- a/.limen/renovate.json +++ b/.limen/renovate.json @@ -19,6 +19,7 @@ "packageRules, golang/go, versioning: Go tags its releases go1.N.M, and the candidates golang/go offers are tags (it publishes no GitHub release). The regex managers' default versioning, semver-coerced, parses such a tag but calls every one unstable, since its stability check accepts only a bare or v-prefixed number; from a current version it holds unstable, Renovate lets through only a candidate with the same major, minor and patch, so no bump was ever proposed, silently. Regex versioning that carries the prefix parses the pin and the candidates alike, as lychee's does, and leaves the rc tags out by construction. Out of the aqua tools group: a toolchain patch is the standard-library fix the pin exists to receive without delay, in a pull request that names it.", "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.", "packageRules, git-for-windows: releases are tagged vX.Y.Z.windows.N, and the N moves on its own (a Git for Windows rebuild of the same Git); a semver reading drops it and would never propose that bump. The build group carries it, and release candidates (vX.Y.Z-rcN.windows.N) fall outside the pattern.", + "packageRules, own releases, versioning: a fork's rebuild of the same upstream version is tagged with a fourth segment, vX.Y.Z.N (book/forks.md); the regex managers' default versioning, semver-coerced, reads v8.22.0.2 as 8.22.0 and so never proposes a rebuild (limen stayed on forkcloser/curl v8.22.0 through v8.22.0.1 and v8.22.0.2). The build group carries the fourth segment; absent, the release compares as the one its rebuilds follow, and a -suffix stays a prerelease. The rule claims a pin by its current value, a v-prefixed version, since a packageRule's versioning overrides the one a pins.yaml entry declares for itself: a release line of another shape (ossein-kernel's X.Y.Z-ossein.N, read by its own regex in the pin) keeps its own.", "packageRules, runner images: the github-actions manager, which config:recommended enables, also proposes the runs-on images (ubuntu-24.04 to 26.04, macos-15 to 26) from the github-runners datasource. Those are product targets, not dependencies: the matrix is the set of platforms the code and the commands are proven on, set by the canonical ci.yaml seed and moved deliberately, on purpose, as a reviewed change. Off for that datasource; the actions themselves keep moving.", "customManagers, pins.yaml: the artifacts a repository fetches by hand (a kernel tarball, a toolchain release, a source archive) live in one limen-owned file, each entry naming its own datasource and depName on its renovate: line (or `manual`, the one word, for a pin no datasource serves, which the regex below then leaves alone and a hand moves), optionally an extract-version regexp and a versioning scheme on the two lines after it, the version: line that moves, and for a commit pin (git-refs: the version is the branch) a commit: line after it, captured as the digest, so Renovate proposes the branch's new head. The digest follows in the same pull request: the checksum workflow runs `limen pins refresh`, which recomputes it through the verifier the entry declares. Watched here, once, for every repository; nobody writes a regex manager per pin.", "commitBody: `just do lint commits` enforces DCO on every PR range, bot commits included.", @@ -191,6 +192,20 @@ ], "versioning": "regex:^v(?\\d+)\\.(?\\d+)\\.(?\\d+)\\.windows\\.(?\\d+)$" }, + { + "matchManagers": [ + "custom.regex" + ], + "matchDatasources": [ + "github-releases", + "github-tags" + ], + "matchDepNames": [ + "/^(farcloser|forkcloser|mycophonic)/[^/]+$/" + ], + "matchCurrentValue": "/^v\\d+\\.\\d+\\.\\d+(\\.\\d+)?(-[0-9A-Za-z.-]+)?$/", + "versioning": "regex:^v(?\\d+)\\.(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?(?:-(?[0-9A-Za-z.-]+))?$" + }, { "matchDepNames": [ "golang/go" diff --git a/AGENTS.md b/AGENTS.md index 22de83f8..db3e787a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,17 +83,22 @@ chapter; the procedure is limen's `skills/contribute`. private repository is never armed: its pull request is bound for the human, who merges by hand after approving. No review request, no review comments, no message at open. - **Your worktree goes when its pull request has merged.** Once `git status` in it is clean - and its head is on `main`, remove it with `~/.claude/bin/farclauder worktree remove `, - run as the whole Bash command: `git worktree remove` cannot finish inside the sandbox, and - the exclusion that runs farclauder outside it matches the call's text (the book's + and its head is on `main`, remove it with + `/Users/apostasie/.claude/bin/farclauder worktree remove `, run as the whole Bash + command and by that absolute path: `git worktree remove` cannot finish inside the sandbox, + and the exclusion that runs farclauder outside it matches the call's text, which the `~` + form does not (the book's [identity](https://github.com/farcloser/limen/blob/main/book/identity.md)). - **A native stack** is reviewed and opened layer by layer the same way; the project manager merges it from the top once every layer is approved, never from the middle. A lower layer merged alone comes back rebased and signed by GitHub, which `lint commits` rejects. -- **Not yours to do:** merge by hand, push to `main`, force-push a shared branch, tag a - release. A release waits for an empty queue: no tag while a pull request is open, a bot's - included, unless the human parks it by number for that tag (`PARKED_PRS`). +- **Not yours to do:** merge by hand, push to `main` (but for the empty `Birth` commit that + creates it on a new repository, the book's + [creating a repository](https://github.com/farcloser/limen/blob/main/book/github.md#creating-a-repository)), + force-push a shared branch, tag a release. A release waits for an empty queue: no tag + while a pull request is open, a bot's included, unless the human parks it by number for + that tag (`PARKED_PRS`). ## Scope