Release-gate fix: split journey-ledger from goreleaser so the 0.19.6/0.20.0 cut can't be blocked#308
Merged
Conversation
…CY 1) Splits the single goreleaser job into two siblings so a never-fired Runtime-Live-E2E run on next can no longer block the cut: - journey-ledger (authored before goreleaser so the document-order guard stays green): Download / Build / Publish the cost ledger. The download step degrades to a non-fatal skip (exit 0, found=false) when no producer run exists, and the Build/Publish steps are gated on that output so a producer-less / empty-dir cut SKIPS them (job green) instead of running journey-costs over an empty dir and REDding the job (POLICY 1). Carries the one-way needs: goreleaser edge required so gh release upload runs after the Release exists. - goreleaser: cuts the release regardless of journey-ledger. Hardens assertReleaseWorkflowPublishesJourneyCosts to bind needs: to the OWNING job: a goreleaser->journey-ledger edge now REDs the guard, while the safe journey-ledger->goreleaser edge does not false-trip it. Adds the job-level skip-consequence guard plus adversarial tests for the unsafe edge and an ungated builder, and a shell-level exercise of the download skip branch. Anchors the empty-body guard extractor on release-notes.txt so it selects the right if-condition now that the download step also carries one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… guard M1 (route-back from validation cycle 1): parseNeeds only read scalar/flow needs:, so a future goreleaser re-coupling written as a YAML block-list (needs: then - journey-ledger) reported goreleaser needs=[] and evaded assertGoreleaserDoesNotNeedJourneyLedger — the guard would greenlight a real re-coupling edge. parseWorkflowJobs now consumes the block-list entries that follow a bare needs: line, and TestReleaseWorkflowGuardRejectsGoreleaserNeeds- JourneyLedger injects the edge in all three forms (scalar/flow/block-list); the safe reverse edge on journey-ledger stays tolerated. P1: the download skip-branch now tolerates a gh error / missing gh as found=false (|| true on the run_id query) instead of aborting under set -e; TestReleaseDownloadSkipBranchToleratesGhError exercises both the gh-error and missing-gh paths. go test ./internal/release/ 34/34; full offline go test ./... 1176 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ard shape evasions M1a/M1b (cycle-2 re-audit): the hand-rolled needs-parser still let real goreleaser->journey-ledger re-couplings stay GREEN — an inline trailing `# comment` evaded all three shapes (the file's own safe reverse edge is comment-documented), and a blank line between block-list entries dropped the later entries. Patching more shapes is whack-a-mole; replace the scanner with a gopkg.in/yaml.v3 parse (already a direct dep) that reads each job's needs as a normalized list — comments, blank lines, scalar/flow/block, quoting, and anchors handled for free. parseJobNeeds replaces parseNeeds entirely; parseWorkflowJobs now sources needs from it and keeps line-based step attribution. assertGoreleaserDoesNotNeedJourneyLedger keeps its contract: REJECT goreleaser->journey-ledger, TOLERATE the reverse. The rejection test now covers scalar/flow/block plus all three inline-comment variants plus a blank-line-split block list (all RED); a new TestReleaseWorkflowGuardToleratesSafeReverseEdge- InEveryShape proves the safe reverse edge stays GREEN in every shape. Mutation-confirm: neutering parseJobNeeds reds all 7 rejection forms while tolerance stays green. No regression: skip-gating, AC-1 job-level, the AC-2 release-notes.txt anchor, and the P1 || true gh-error tolerance are untouched. go test ./internal/release/ 45/45; full offline go test ./... 1187 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ity shapes can't evade the separation guard
M1/M2 (cycle-3 re-audit): the cycle-3 fix closed the needs-VALUE shapes, but
JOB IDENTITY was still a hand-rolled line-walk and two shapes beat it — an
`&anchor`/`*alias` on needs reached parseJobNeeds as an unhandled node so the
edge vanished, and a quoted job key ("goreleaser":) missed the literal
needsByJob lookup. Patching either is more whack-a-mole.
Finish the conversion: parse the entire job graph — names, alias-resolved needs,
and per-job steps — from ONE yaml.v3 pass and attribute goreleaser/builder from
that structure. The last line-walk for job identity (parseJobNeeds, jobsStart,
jobHeaderAt) is gone. yaml.v3 resolves aliases before decode and normalizes
quoted keys natively, so M1, M2, and any future job-identity shape are handled
by the parser, not a growing line scan. needs decodes through a needsList type
that accepts scalar or sequence.
Adversarial coverage: TestReleaseWorkflowGuardRejectsGoreleaserNeedsJourneyLedger-
ViaJobIdentityShapes REDs both the anchor/alias and quoted-key re-couplings; the
tolerance twin keeps the safe reverse edge green in those shapes; a new
TestReleaseWorkflowJobGraphMatchesGitHubActions pins the real-file parse to GHA
(goreleaser needs empty, journey-ledger needs only goreleaser). Mutation-confirm:
dropping sequence-edge decode reds the alias form; dropping scalar-edge decode
reds the quoted-key form.
No regression: the 7-form value table + its tolerance twin, skip-gating, AC-1
job-level, the AC-2 release-notes.txt anchor, and P1 gh-error tolerance all green.
go test ./internal/release/ 52/52; full offline go test ./... 1194 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ple action carriers Cycle-5 (4th audit): assertGoreleaserDoesNotNeedJourneyLedger overwrote a single goreleaserJob/ledgerJob string on each map-iteration match and checked needs on only the LAST-iterated carrier. Go map order is random, so a workflow with 2+ jobs running the goreleaser action — one declaring needs: journey-ledger — greenlit on the runs that happened to iterate the safe carrier last (~flaky), leaving the guard latently unsound on any multi-carrier shape. Collect ALL goreleaser-action carriers AND ALL ledger-builder carriers in one pass, then reject if ANY goreleaser carrier needs ANY ledger carrier. No last-wins overwrite, no map-order dependence — deterministic. A job that is itself both carriers cannot need itself, so the self-pair is skipped (preserves the old single-job-layout semantics). Adversarial coverage: TestReleaseWorkflowGuardRejectsMultiCarrierGoreleaserNeeds- JourneyLedger inserts a second goreleaser-action job that needs journey-ledger and asserts the guard REDs on every one of 200 runs (a last-wins regression greenlights within that loop); TestReleaseWorkflowGuardToleratesMultiCarrier- SafeShape keeps two carriers that need nothing GREEN over 200 runs. Mutation-confirm: reverting to a last-wins single-carrier check reds the rejection test. No regression: the job-identity rejections + tolerance twins + parse-matches-GHA, the 7-form value table, skip-gating, AC-1, AC-2 release-notes.txt anchor, and P1 all green; release.yml untouched this cycle. go test ./internal/release/ 54/54 (deterministic across -count=10); full offline go test ./... 1196 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clkao
added a commit
that referenced
this pull request
Jun 6, 2026
clkao
added a commit
that referenced
this pull request
Jun 6, 2026
clkao
added a commit
that referenced
this pull request
Jun 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stops a release cut from failing on a Runtime-Live-E2E gate that never auto-fires on
next— the failure that broke 0.19.5.What changed
goreleaser(noneeds:) andjourney-ledger(needs: goreleaser)found == 'true'goreleaser → journey-ledgerre-coupling in every YAML shapeEvidence
-count=10)go test ./...1196 passed; five detached adversarial audits (block-list, comment/blank-line, alias/quoted-key, map-iteration determinism) → final: no material findingsbq