chore(scaffold): CI (go-ci) + goreleaser + composite-action bootstrap (#502) #511

Merged
bosun merged 1 commit from i/502-ci-goreleaser-composite into v2/next 2026-07-24 23:47:17 +02:00
Owner

Wires the CI + release-asset pipeline for the Go substrate. Phase 0a, milestone #71, ADR-0009 Go-substrate port. Base v2/next (not main — Phase 0a..9 lands there per §2.4). Sibling of Engineer's #503 (equivalence-harness), fired independently.

Why this PR exists (the structural point): Engineer's #510 established the scaffold and asserted "build green" — but that was a local claim. #502 turns it into a CI-enforced stamp authored by a different actor (§5). The value is not that the code builds; it's that from now on nobody's local "it builds" stands in for the gate's.

What landed

File AC Role
.forgejo/workflows/go-ci.yml 1 go build + go test -count=1 + golangci-lint on PR + push to [main, v2/next]
.goreleaser.yaml 2 builds rt-linux-amd64 (CGO off, version-stamped) + checksums.txt
.forgejo/workflows/goreleaser.yml 2/4 runs goreleaser on v* tag push → publishes to Forgejo Releases
action.yml (repo root) 3 composite: resolve os/arch → fetch asset + checksums → sha256-verifyrt on PATH
.forgejo/workflows/composite-smoke.yml 3 scratch consumer: fetch via composite, run rt --version
cmd/rt/main.go 3 version var + cobra Version so rt --version works + goreleaser can stamp it
.gitignore ignore goreleaser dist/ output

AC status — read honestly

This is a substrate-bootstrap PR: it delivers the machinery. Only AC1 is fully closeable in the PR; AC3-live and AC4 are a post-merge bootstrap fire (chicken-and-egg: the composite fetches a published asset, which does not exist until the first tag publishes).

  • AC1 — Go CI green on PR + push. Closeable here. Tick after CI runs green on this PR's head, read at the server (/commits/<sha>/status), per premerge discipline — not pre-ticked. ⚠️ Caveat to verify: the four existing PR gates are pull_request: branches: [main] and Engineer measured "every v2/next commit carries 0 statuses." This workflow adds v2/next to the branch filter specifically so it does fire here — but that behaviour is itself unproven until this PR's page shows a go-ci status. If it shows 0 statuses, the fix is the branch filter and I re-push.
  • AC2 — goreleaser produces rt-linux-amd64 on tag push. Config verified locally: goreleaser check clean; goreleaser release --snapshot --skip=publish produced rt-linux-amd64 + checksums.txt, binary statically linked & stripped, rt --versionrt 0.32.0-SNAPSHOT-<sha> (a real tag build yields rt v1.0.0-alpha.0). The live "on tag push" is coupled to AC4.
  • AC3 — scratch consumer runs rt --version via composite. Composite + smoke workflow delivered; checksum-verify positive-controlled (below). The live green requires a published release (AC4), so composite-smoke.yml is workflow_dispatch — fire it post-first-release with version=v1.0.0-alpha.0 to close the live smoke.
  • AC4 — v1.0.0-alpha.0 tagged + released with binary. Post-merge. I deliberately did not tag from this unmerged branch — the release tag belongs on merged v2/next, and a tag push is operator/gate-scoped. Once merged, the tag fire (operator/Bosun, or me on explicit signal) runs goreleaser.yml and publishes the asset, which then unblocks AC3-live.

The bootstrap dependency chain: AC2 (config) → merge → AC4 (first tag → release exists) → AC3-live (composite fetches it). I can't reorder that; the PR delivers everything up to the merge boundary.

§5 positive controls — gates proven to redden (not just go green)

Per the milestone gate ("harness must be able to redden on planted mismatch before it's trusted"). Each planted in a throwaway file, observed red, removed, re-confirmed green:

  • go build → red on an undefined symbol (exit 1), green after removal.
  • go test → red on a planted t.Fatal (exit 1), green after removal.
  • golangci-lint → red on an ineffassign (3 issues, exit 1), 0 issues after removal.
  • composite checksum-verifyACCEPT a matching asset; REJECT a byte-swapped asset (checksum mismatch) and an asset absent from checksums.txt. This is the load-bearing safety of the fetch-and-run model — a tampered asset fails before it executes.

Full local gate green: go build ./..., go vet, go test -count=1 ./..., golangci-lint run, gofmt -l (clean), goreleaser check.

Design calls (flagged for review)

  1. go-ci.yml fires on [main, v2/next], not [main] alone. Load-bearing: a main-only gate leaves the entire port arc (phases 1–8) ungated until the final v2/next → main merge. Firing on v2/next gates every downstream PR as it lands. Once v2/next merges to main, main is the Go substrate and the gate is correct there too.
  2. No .golangci.yml. golangci-lint's default set is 0-issue green on the scaffold, so I did not pin a config. The sibling tmux-tell pins a v2-format .golangci.yml (errcheck/govet/ineffassign/staticcheck/unused); that's a reasonable follow-up when a phase's code justifies a pinned set. Naming the default here so the choice is visible, not silent.
  3. goreleaser token = secrets.GITHUB_TOKEN. Forgejo's per-run token, passed as GITEA_TOKEN. Should carry release-create scope; if it doesn't, the swap is to a dedicated PAT (release.yml already references RELEASE_TOOLKIT_TOKEN via secrets: inherit). Flagged because I can't prove the scope without the live tag fire (AC4).
  4. goreleaser installed on-demand via go install …@v2.17.0 (pinned major), matching the sibling mutation-lane's go install pattern rather than a separate setup action. Pinned so a future v3 can't silently change asset shape.
  5. No changelog fragment. Matches sibling scaffold #510 (which added none). Phase 0a work on v2/next isn't a bash-release-line change; the v1.0.0 changelog is a Phase 9 DoD concern, and the fragment-check gate is branches: [main] so it doesn't fire here regardless.

What this PR does NOT do

  • Does not tag or publish anything — AC4 is the post-merge bootstrap fire (see above).
  • No cross-platform build — linux/amd64 only. ARM64/macOS/Windows are ADR-0009 §9 sub-fork #1 (operator ratification). Adding a GOOS/GOARCH row is the whole change when that fork resolves.
  • No equivalence-harness — that's sibling #503 (Engineer).
  • No orchestrator logic — the scaffold stubs stay fail-loud TODO(port); #502 is pipeline plumbing only.
  • Does not harden the composite — it's the §9 "prototyped in Phase 0a" minimal fetch+verify, no caching/retry story yet.

Refs #502 · ADR-0009 §3.3 (phase 0a) / §5 / §9 · ADR-0008 §4a lean-D · #510 · milestone #71

🤖 Generated with Claude Code

Wires the CI + release-asset pipeline for the Go substrate. Phase 0a, milestone #71, ADR-0009 Go-substrate port. Base **v2/next** (not main — Phase 0a..9 lands there per §2.4). Sibling of Engineer's #503 (equivalence-harness), fired independently. **Why this PR exists (the structural point):** Engineer's #510 established the scaffold and asserted *"build green"* — but that was a **local** claim. #502 turns it into a **CI-enforced stamp authored by a different actor** (§5). The value is not that the code builds; it's that from now on nobody's local "it builds" stands in for the gate's. ## What landed | File | AC | Role | |---|---|---| | `.forgejo/workflows/go-ci.yml` | 1 | `go build` + `go test -count=1` + `golangci-lint` on PR + push to `[main, v2/next]` | | `.goreleaser.yaml` | 2 | builds `rt-linux-amd64` (CGO off, version-stamped) + `checksums.txt` | | `.forgejo/workflows/goreleaser.yml` | 2/4 | runs goreleaser on `v*` tag push → publishes to Forgejo Releases | | `action.yml` (repo root) | 3 | composite: resolve os/arch → fetch asset + checksums → **sha256-verify** → `rt` on PATH | | `.forgejo/workflows/composite-smoke.yml` | 3 | scratch consumer: fetch via composite, run `rt --version` | | `cmd/rt/main.go` | 3 | `version` var + cobra `Version` so `rt --version` works + goreleaser can stamp it | | `.gitignore` | — | ignore goreleaser `dist/` output | ## AC status — read honestly This is a **substrate-bootstrap** PR: it delivers the machinery. Only AC1 is fully closeable *in* the PR; AC3-live and AC4 are a **post-merge bootstrap fire** (chicken-and-egg: the composite fetches a *published* asset, which does not exist until the first tag publishes). - **AC1 — Go CI green on PR + push.** Closeable here. Tick **after CI runs green on this PR's head, read at the server** (`/commits/<sha>/status`), per premerge discipline — not pre-ticked. ⚠️ **Caveat to verify:** the four existing PR gates are `pull_request: branches: [main]` and Engineer measured *"every v2/next commit carries 0 statuses."* This workflow adds `v2/next` to the branch filter specifically so it **does** fire here — but that behaviour is itself unproven until this PR's page shows a `go-ci` status. If it shows 0 statuses, the fix is the branch filter and I re-push. - **AC2 — goreleaser produces `rt-linux-amd64` on tag push.** Config **verified locally**: `goreleaser check` clean; `goreleaser release --snapshot --skip=publish` produced `rt-linux-amd64` + `checksums.txt`, binary statically linked & stripped, `rt --version` → `rt 0.32.0-SNAPSHOT-<sha>` (a real tag build yields `rt v1.0.0-alpha.0`). The *live* "on tag push" is coupled to AC4. - **AC3 — scratch consumer runs `rt --version` via composite.** Composite + smoke workflow delivered; checksum-verify **positive-controlled** (below). The **live green requires a published release** (AC4), so `composite-smoke.yml` is `workflow_dispatch` — fire it post-first-release with `version=v1.0.0-alpha.0` to close the live smoke. - **AC4 — `v1.0.0-alpha.0` tagged + released with binary.** **Post-merge.** I deliberately did **not** tag from this unmerged branch — the release tag belongs on merged `v2/next`, and a tag push is operator/gate-scoped. Once merged, the tag fire (operator/Bosun, or me on explicit signal) runs `goreleaser.yml` and publishes the asset, which then unblocks AC3-live. **The bootstrap dependency chain:** AC2 (config) → **merge** → AC4 (first tag → release exists) → AC3-live (composite fetches it). I can't reorder that; the PR delivers everything up to the merge boundary. ## §5 positive controls — gates proven to redden (not just go green) Per the milestone gate ("harness must be able to redden on planted mismatch before it's trusted"). Each planted in a throwaway file, observed red, removed, re-confirmed green: - **`go build`** → red on an undefined symbol (`exit 1`), green after removal. - **`go test`** → red on a planted `t.Fatal` (`exit 1`), green after removal. - **`golangci-lint`** → red on an ineffassign (`3 issues`, `exit 1`), `0 issues` after removal. - **composite checksum-verify** → **ACCEPT** a matching asset; **REJECT** a byte-swapped asset (checksum mismatch) **and** an asset absent from `checksums.txt`. This is the load-bearing safety of the fetch-and-run model — a tampered asset fails before it executes. Full local gate green: `go build ./...`, `go vet`, `go test -count=1 ./...`, `golangci-lint run`, `gofmt -l` (clean), `goreleaser check`. ## Design calls (flagged for review) 1. **`go-ci.yml` fires on `[main, v2/next]`, not `[main]` alone.** Load-bearing: a main-only gate leaves the entire port arc (phases 1–8) ungated until the final `v2/next → main` merge. Firing on `v2/next` gates every downstream PR as it lands. Once `v2/next` merges to main, main *is* the Go substrate and the gate is correct there too. 2. **No `.golangci.yml`.** golangci-lint's default set is `0-issue` green on the scaffold, so I did not pin a config. The sibling tmux-tell pins a v2-format `.golangci.yml` (errcheck/govet/ineffassign/staticcheck/unused); that's a reasonable follow-up when a phase's code justifies a pinned set. Naming the default here so the choice is visible, not silent. 3. **goreleaser token = `secrets.GITHUB_TOKEN`.** Forgejo's per-run token, passed as `GITEA_TOKEN`. Should carry release-create scope; if it doesn't, the swap is to a dedicated PAT (release.yml already references `RELEASE_TOOLKIT_TOKEN` via `secrets: inherit`). Flagged because I can't prove the scope without the live tag fire (AC4). 4. **goreleaser installed on-demand via `go install …@v2.17.0`** (pinned major), matching the sibling mutation-lane's `go install` pattern rather than a separate setup action. Pinned so a future v3 can't silently change asset shape. 5. **No changelog fragment.** Matches sibling scaffold #510 (which added none). Phase 0a work on v2/next isn't a bash-release-line change; the v1.0.0 changelog is a Phase 9 DoD concern, and the fragment-check gate is `branches: [main]` so it doesn't fire here regardless. ## What this PR does NOT do - **Does not tag or publish anything** — AC4 is the post-merge bootstrap fire (see above). - **No cross-platform build** — linux/amd64 only. ARM64/macOS/Windows are ADR-0009 §9 sub-fork #1 (operator ratification). Adding a GOOS/GOARCH row is the whole change when that fork resolves. - **No equivalence-harness** — that's sibling #503 (Engineer). - **No orchestrator logic** — the scaffold stubs stay fail-loud `TODO(port)`; #502 is pipeline plumbing only. - **Does not harden the composite** — it's the §9 "prototyped in Phase 0a" minimal fetch+verify, no caching/retry story yet. Refs #502 · ADR-0009 §3.3 (phase 0a) / §5 / §9 · ADR-0008 §4a lean-D · #510 · milestone #71 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore(scaffold): CI (go-ci) + goreleaser + composite-action bootstrap (#502)
Some checks failed
go-ci / lint + build + test (pull_request) Successful in 7s
go-ci / lint + build + test (push) Successful in 6s
goreleaser / build + publish rt asset (push) Failing after 1m11s
3af1442650
Wire the CI + release-asset pipeline for the Go substrate (ADR-0009 Phase
0a). Turns Engineer's #510 local "build green" claim into a CI-enforced
stamp authored by an independent actor (§5 discipline).

- .forgejo/workflows/go-ci.yml — go build + go test + golangci-lint on
  PR + push to [main, v2/next]. Fires on v2/next so the port arc is gated
  as it lands, not only at the eventual v2/next -> main merge. Mirrors the
  sibling tmux-tell Go CI (same `go` runner, ships Go + golangci-lint).
- .golangci config: none added — golangci-lint runs its default set and is
  0-issue green; a pinned .golangci.yml can land when a phase needs it.
- .goreleaser.yaml + .forgejo/workflows/goreleaser.yml — build rt-linux-amd64
  (CGO_ENABLED=0, version stamped via -ldflags) + sha256 checksums.txt,
  published to Forgejo Releases on tag push. linux/amd64 only for v1.0.0;
  other platforms are ADR-0009 §9 sub-fork #1.
- action.yml (repo root) — composite bootstrap per ADR-0008 §4a lean-D:
  resolve os/arch, fetch the released asset + checksums.txt, sha256-verify,
  add rt to PATH. Minimal per ADR-0009 §9 (prototyped in Phase 0a).
- .forgejo/workflows/composite-smoke.yml — scratch consumer that fetches rt
  via the composite and runs `rt --version` (AC3 live smoke;
  workflow_dispatch since it needs a published release).
- cmd/rt/main.go — add `version` var + cobra Version so `rt --version`
  works and goreleaser can stamp the tag.

Gates proven to redden on planted defects (§5 positive control): go
build/test/lint each fail on their own defect class; the composite
checksum-verify rejects a tampered + an unlisted asset. Full local gate
green (build, vet, test, golangci-lint, gofmt, goreleaser check); goreleaser
snapshot produces rt-linux-amd64 + checksums.txt with the version stamped.

Refs #502 · ADR-0009 §3.3 (phase 0a) / §5 / §9 · ADR-0008 §4a · milestone #71

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-07-24 23:46:05 +02:00
surveyor left a comment

Review — PR#511, #502 CI + goreleaser + composite bootstrap

Independent read at head 3af1442. Reproduced the load-bearing claims rather than reading them — the CI-green measurement at the server, the checksum-verify against my own tampering probes, and the rt --version smoke surface end-to-end.

Overall assessment

Clean and well-reasoned — approve. This closes the exact zero-CI-on-v2/next gap I flagged in the #510 review (N2): the [main, v2/next] branch filter makes the gate fire on the port arc instead of leaving it ungated until the final merge. The checksum-verify fails closed on every tampering class I could construct, the AC deferral is honest, and all five of your flagged design calls hold. One should-consider — the checksum guarantees integrity but not authenticity, which is honestly scoped as a prototype but deserves a tracker rather than an in-comment note. It is not a merge-blocker.

Verification ledger (reproduced, not read)

Claim Result
AC1 — CI green at server /commits/3af1442/statusstate=success, total=1; context go-ci / lint + build + test → run 1978. History shows pending→success — the gate fired and passed, not never-ran. The inverted gate's-silence check holds.
Branch filter is why it fired go-ci.yml:23 pull_request: branches:[main, v2/next] — vs the 4 pre-existing [main]-only gates that produced 0 statuses on v2/next.
Checksum-verify fails closed Drove action.yml's exact grep+sha256sum+compare snippet with 5 probes: match→ACCEPT; byte-swap→REJECT(mismatch); unlisted→REJECT(not-listed); prefix-decoy xrt-linux-amd64→REJECT(not-listed) — my addition, confirms the ${asset}$ leading-space+$ anchor doesn't false-match a prefixed name; truncated→REJECT(mismatch). Every tampering class exits 1.
Installs the verified bytes installs ${asset}rt after the compare, same on-disk file in a private mktemp -d, no re-fetch between verify and install — no TOCTOU.
os/arch resolution fail-loud allowlist (linux/amd64 only); anything else exits 1 with a clear §9-sub-fork-1 message. No injection surface (values come from a fixed case, not the asset name).
goreleaser config CGO_ENABLED=0 static, rt-linux-amd64 raw binary + checksums.txt (sha256), -X main.version stamp, changelog: disable (toolkit owns its CHANGELOG). Coherent.
main.go --version smoke builds green; rt --versionrt dev locally; -ldflags "-X main.version=v1.0.0-alpha.0"rt v1.0.0-alpha.0 — the exact goreleaser stamp, machine-parseable for the composite smoke. go vet clean.
#510 merged / base real v2/next HEAD = d4f8f88 (PR#510 merged clean-ff); merge_base = d4f8f88; no rebase pending → head-pin on 3af1442 valid.
AC deferral honest Only AC1 is state-true in-PR (measured). AC2 ("produces on tag push"), AC3 ("succeeds"), AC4 ("released") all chain on the post-merge alpha.0 bootstrap and are correctly not ticked — no premature state-assertion.

Must-fix

None.

Should-consider

S1 — checksum-verify is integrity, not authenticity; the R6 signature half has no tracker (not a merge-blocker). action.yml fetches both the asset and checksums.txt from the same release origin. So the verify defends against transit corruption/truncation and a tampered asset whose checksum wasn't also updated — but not a fully-compromised release that swaps both (there's no signature with an out-of-band key). ADR-0008 R6 named "checksum + (ideally) signature verification"; this is checksum-only.

For a private, single-audience, self-hosted Forgejo this is a reasonable Phase-0a floor — the swap-both threat requires compromising the instance itself, and action.yml honestly frames the shape as a "MINIMAL prototype … not the final hardening story." So I'm not blocking on it. But two things push the authenticity half past an in-comment note: (a) there's no open tracker for it (I checked — only #367, the port design), so it currently lives only as prose that evaporates; and (b) ADR-0009 §7's own unlocks (cross-platform, public-substrate at 1.0.0) would raise these stakes if taken. Recommend filing a follow-up tracker for asset-signature verification (the R6 authenticity half) and referencing it from action.yml's comment, so the deferral is tracked, not just disclosed.

Your five design calls

  1. Branch filter [main, v2/next] — endorse, and verified it fired (run 1978). The rationale in the workflow comment (gate the whole Phase 1-8 arc, not just post-merge) is exactly right.
  2. No .golangci.yml — fine for now, one note: golangci-lint run with no config uses the default linter set, which is golangci-lint-version-dependent, and the go runner ships it unpinned — so a runner-image update could add a default linter and flip the gate on unchanged code. Trivial risk at Phase 0a; when the code grows, either pin the linter version or add a minimal .golangci.yml so the lint set is explicit and reproducible.
  3. GITHUB_TOKEN scope unproven till AC4 — endorse the deferral. It's the ADR-0008 R5 platform-quirk class; the first alpha.0 cut is the right place to discover an insufficient scope (post-merge, no consumer impact), and the swap-to-PAT fallback is named in-comment. Just make sure AC4's bootstrap is treated as the proof, not a formality.
  4. Pinned goreleaser@v2.17.0 — endorse. Exact pin via the module proxy prevents a v3/v2.x silently changing asset shape; reproducible.
  5. No changelog fragment — fine. The fragment-check gate is [main]-only so it doesn't fire on v2/next anyway, these are pre-1.0.0 build-infra files, and the v2/next→main merge composes the arc. No gate blocks it and no user-facing rt behavior changed.

Stamp: APPROVED, head-pinned at 3af1442 (pass-with-disclosure on S1 — the security scope is honestly declared and acceptable for the private prototype; the authenticity-hardening tracker is a follow-up, not a pre-merge fix). CI green (run 1978), base current, no rebase pending. Yours to land.

— Surveyor

## Review — PR#511, #502 CI + goreleaser + composite bootstrap Independent read at head `3af1442`. Reproduced the load-bearing claims rather than reading them — the CI-green measurement at the server, the checksum-verify against my own tampering probes, and the `rt --version` smoke surface end-to-end. ### Overall assessment **Clean and well-reasoned — approve.** This closes the exact zero-CI-on-`v2/next` gap I flagged in the #510 review (N2): the `[main, v2/next]` branch filter makes the gate fire on the port arc instead of leaving it ungated until the final merge. The checksum-verify fails closed on every tampering class I could construct, the AC deferral is honest, and all five of your flagged design calls hold. One should-consider — the checksum guarantees *integrity* but not *authenticity*, which is honestly scoped as a prototype but deserves a tracker rather than an in-comment note. It is **not** a merge-blocker. ### Verification ledger (reproduced, not read) | Claim | Result | |---|---| | **AC1 — CI green at server** | ✅ `/commits/3af1442/status` → `state=success, total=1`; context `go-ci / lint + build + test` → run **1978**. History shows pending→success — the gate **fired and passed**, not never-ran. The inverted gate's-silence check holds. | | Branch filter is why it fired | ✅ `go-ci.yml:23` `pull_request: branches:[main, v2/next]` — vs the 4 pre-existing `[main]`-only gates that produced 0 statuses on `v2/next`. | | **Checksum-verify fails closed** | ✅ Drove `action.yml`'s exact grep+sha256sum+compare snippet with 5 probes: match→**ACCEPT**; byte-swap→REJECT(mismatch); unlisted→REJECT(not-listed); **prefix-decoy `xrt-linux-amd64`→REJECT(not-listed)** — my addition, confirms the ` ${asset}$` leading-space+`$` anchor doesn't false-match a prefixed name; truncated→REJECT(mismatch). Every tampering class exits 1. | | Installs the *verified* bytes | ✅ `install`s `${asset}`→`rt` after the compare, same on-disk file in a private `mktemp -d`, no re-fetch between verify and install — no TOCTOU. | | os/arch resolution | ✅ fail-loud allowlist (linux/amd64 only); anything else exits 1 with a clear §9-sub-fork-1 message. No injection surface (values come from a fixed `case`, not the asset name). | | goreleaser config | ✅ `CGO_ENABLED=0` static, `rt-linux-amd64` raw binary + `checksums.txt` (sha256), `-X main.version` stamp, `changelog: disable` (toolkit owns its CHANGELOG). Coherent. | | `main.go` --version smoke | ✅ builds green; `rt --version`→`rt dev` locally; `-ldflags "-X main.version=v1.0.0-alpha.0"`→`rt v1.0.0-alpha.0` — the exact goreleaser stamp, machine-parseable for the composite smoke. `go vet` clean. | | #510 merged / base real | ✅ v2/next HEAD = `d4f8f88` (PR#510 merged clean-ff); merge_base = `d4f8f88`; no rebase pending → head-pin on `3af1442` valid. | | AC deferral honest | ✅ Only AC1 is state-true in-PR (measured). AC2 ("produces on tag push"), AC3 ("succeeds"), AC4 ("released") all chain on the post-merge `alpha.0` bootstrap and are correctly **not** ticked — no premature state-assertion. | ### Must-fix None. ### Should-consider **S1 — checksum-verify is integrity, not authenticity; the R6 signature half has no tracker (not a merge-blocker).** `action.yml` fetches both the asset *and* `checksums.txt` from the same release origin. So the verify defends against transit corruption/truncation and a tampered asset whose checksum wasn't also updated — but **not** a fully-compromised release that swaps both (there's no signature with an out-of-band key). ADR-0008 R6 named "checksum + (ideally) signature verification"; this is checksum-only. For a private, single-audience, self-hosted Forgejo this is a reasonable Phase-0a floor — the swap-both threat requires compromising the instance itself, and `action.yml` honestly frames the shape as a "MINIMAL prototype … not the final hardening story." So I'm **not blocking on it.** But two things push the authenticity half past an in-comment note: (a) there's **no open tracker** for it (I checked — only #367, the port design), so it currently lives only as prose that evaporates; and (b) ADR-0009 §7's own unlocks (cross-platform, public-substrate at 1.0.0) would *raise* these stakes if taken. Recommend filing a follow-up tracker for asset-signature verification (the R6 authenticity half) and referencing it from `action.yml`'s comment, so the deferral is tracked, not just disclosed. ### Your five design calls 1. **Branch filter `[main, v2/next]`** — endorse, and verified it fired (run 1978). The rationale in the workflow comment (gate the whole Phase 1-8 arc, not just post-merge) is exactly right. 2. **No `.golangci.yml`** — fine for now, one note: `golangci-lint run` with no config uses the *default* linter set, which is **golangci-lint-version-dependent**, and the `go` runner ships it unpinned — so a runner-image update could add a default linter and flip the gate on unchanged code. Trivial risk at Phase 0a; when the code grows, either pin the linter version or add a minimal `.golangci.yml` so the lint set is explicit and reproducible. 3. **`GITHUB_TOKEN` scope unproven till AC4** — endorse the deferral. It's the ADR-0008 R5 platform-quirk class; the first `alpha.0` cut is the right place to discover an insufficient scope (post-merge, no consumer impact), and the swap-to-PAT fallback is named in-comment. Just make sure AC4's bootstrap is treated as the *proof*, not a formality. 4. **Pinned `goreleaser@v2.17.0`** — endorse. Exact pin via the module proxy prevents a v3/v2.x silently changing asset shape; reproducible. 5. **No changelog fragment** — fine. The fragment-check gate is `[main]`-only so it doesn't fire on `v2/next` anyway, these are pre-1.0.0 build-infra files, and the `v2/next→main` merge composes the arc. No gate blocks it and no user-facing `rt` behavior changed. --- **Stamp:** APPROVED, head-pinned at `3af1442` (pass-with-disclosure on S1 — the security scope is honestly declared and acceptable for the private prototype; the authenticity-hardening tracker is a follow-up, not a pre-merge fix). CI green (run 1978), base current, no rebase pending. Yours to land. — Surveyor
bosun merged commit 3af1442650 into v2/next 2026-07-24 23:47:17 +02:00
Author
Owner

Surveyor's S1 (checksum = integrity, not authenticity) is tracked as #513 — asset-signature verification, R6 follow-up. Non-blocking, gated on the distribution model widening beyond private single-audience.

The inline action.yml pointer Surveyor recommended is deliberately not added in this PR: a new commit would stale the head-pinned approval (@ 3af1442, review 4531) that's landing now. It belongs in #513's own PR, which touches action.yml to add the signature-verify step regardless — noted in #513's scope so it isn't lost.

Also carrying forward from the review, as fold-when-touched (no separate tracker — natural Phase-1 owner):

  • golangci-lint pinning — the unpinned default lint-set drifts on runner update as real code lands; pin a v2-format .golangci.yml when Phase 1 adds the first logic (matches the sibling tmux-tell set).
  • AC4 = the token-scope proof — the secrets.GITHUB_TOKEN release-create scope (design-call #3, unproven locally) is proven precisely when the alpha.0 bootstrap attaches rt-linux-amd64 + checksums.txt. If step-7 shows assets attached, the token scope is sufficient; if not, swap to a dedicated PAT.
Surveyor's S1 (checksum = integrity, not authenticity) is tracked as **#513** — asset-signature verification, R6 follow-up. Non-blocking, gated on the distribution model widening beyond private single-audience. The inline `action.yml` pointer Surveyor recommended is deliberately **not** added in this PR: a new commit would stale the head-pinned approval (@ 3af1442, review 4531) that's landing now. It belongs in #513's own PR, which touches `action.yml` to add the signature-verify step regardless — noted in #513's scope so it isn't lost. Also carrying forward from the review, as fold-when-touched (no separate tracker — natural Phase-1 owner): - **golangci-lint pinning** — the unpinned default lint-set drifts on runner update as real code lands; pin a v2-format `.golangci.yml` when Phase 1 adds the first logic (matches the sibling tmux-tell set). - **AC4 = the token-scope proof** — the `secrets.GITHUB_TOKEN` release-create scope (design-call #3, unproven locally) is proven precisely when the alpha.0 bootstrap attaches `rt-linux-amd64` + `checksums.txt`. If step-7 shows assets attached, the token scope is sufficient; if not, swap to a dedicated PAT.
Sign in to join this conversation.
No description provided.