feat: the release gate scaffold wires a managed repo's own CI and binds the receipts to the release they gate - #716
Merged
Merged
Conversation
…wiring Assisted-by: Claude:claude-opus-5-5
The derived content commit is not bound to the release it gates, so an earlier release's receipts admit a later release that recorded none; and commands/launch.md describes the receipt gate at a position it no longer holds (after the tag, deriving <merge>^2^). Refs: iss-2609251755386183, iss-2609251751298408 Assisted-by: Claude:claude-opus-5-5
…gates record-lint --derive-content-sha armed the semantic receipt gate against the nearest commit on the released lineage that a receipts directory names, and never checked that the commit belonged to this release. A roll that recorded no receipts of its own derived the previous release's cut, whose PROMOTE receipts are valid, and the gate admitted a release nobody reviewed (reproduced on a scratch clone at the v0.10.0 tip). The derivation now requires the derived commit to carry the released tree's own newest dated CHANGELOG version, read from each commit's blob, and fails closed naming both versions otherwise. The rehearsal and the batched-queue shape are unaffected: both compare equal versions. Refs: iss-2609251755386183 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609251755386183 Assisted-by: Claude:claude-opus-5-5
…emit step with its protocol itd-93 AC7 and AC8 (spc-2609230613193436). `abcd launch receipts` is the release job's semantic-receipt gate, run on the release branch before the merge. It shares the job's reader rather than modelling it: lint.CheckReleaseReceipts reads the required gates from the committed release.yml (the list the job enforces), derives the content commit with DeriveReleaseContentSha, arms with ArmReceiptGate and judges with checkReceiptGate — the three functions record-lint runs. It names each missing or non-PROMOTE receipt and the commit it must name, refuses on an uncommitted receipt change (the job reads the committed tree), and exits 0/1/2. A test runs the workflow's own step, lifted from the committed release.yml against a record-lint built from this tree, beside the verb over five repository states and holds them to one verdict and one set of reasons. The emit step of `abcd launch ship` ends with the receipts protocol as a numbered checklist, composed in core (release.ReceiptsProtocolFor) from the same workflow list and carried in --json as receipts_protocol; a workflow that arms no gate gets a checklist that requires no receipt. commands/launch.md documents the verb and the checklist, and its account of where the gate runs is brought to the workflow as it is (in verify, before the tag on the auto-release path; content commit from the receipts directory). Refs: iss-2609251751298408 Assisted-by: Claude:claude-opus-5-5
…te where it runs Resolves: iss-2609251751298408 Assisted-by: Claude:claude-opus-5-5
… the reviews charter
itd-93 AC1 (the missing half) and AC5 (the missing half), spc-2609230613193436.
`launch scaffold` reads the managed repo's pull-request and merge-queue
workflows and derives the check names they report (DeriveCIChecks): a
job's own name, else its id; a matrix job, an expression-named job and a
reusable-workflow call are omitted, never guessed; every name passes an
allowlist that excludes every YAML and shell metacharacter before it is
written. The names reach the runbook's new merge-gate section (the
contexts to require on the default branch), the bare release.yml verify
header, and the report (ci_checks). abcd's own rendering is unchanged, so
self-scaffold parity holds.
The scaffold also writes .abcd/development/release-gate/check-reviews.sh,
the reviews charter (RD001): dated review directories keep their shape,
and sha-keyed receipt directories (full SHA-1 or SHA-256) are exempt. The
bare verify job runs it as deterministic gate 6, and the runbook numbers
it from the same source. The runbook's semantic-gate account and
procedure now teach the receipts-directory derivation and the two-commit
protocol with `launch receipts`.
Audit: zizmor is not installed where the tests run and is not a
dependency, so TestScaffoldedWorkflowsPassTheWorkflowAudit asserts its two
named classes with the repository's own tools over every profile — no
duplicate mapping key, no ${{ }} inside a run script, and every
expression resolving under the strict actionsexpr evaluator against a
context set no pull-request author writes.
Assisted-by: Claude:claude-opus-5-5
…ge path to a published release itd-93 AC2 (spc-2609230613193436). The test runs the rendered workflows themselves: a small runner reads the jobs and steps out of the rendered YAML, evaluates every if/env/with/output expression with the strict actionsexpr evaluator, and runs every run script with bash in a fresh clone. Only the edge is faked — a local bare repository is the forge's remote, a fake gh keeps releases and attestations in a directory, checkout is a clone, setup-go is the local toolchain, attest records subject digests, and `go run ./cmd/record-lint` runs as a record-lint built from this tree. No real forge is contacted. Two profiles, each: scaffold, commit, run the workflow_dispatch rehearsal green (verify and rehearsal succeed, tag and release skipped, nothing tagged or published), roll the CHANGELOG on a release branch (plus the receipts commit for the semantic profile), merge --no-ff, run auto-release. Asserted: v0.1.0 is published from the merged commit under a tag at that commit, and with a semantic gate the receipt gate armed against the roll — the reviewed content commit — not the tagged merge commit, and the receipts it admitted were attested. Watched red on a scratch copy with the verify step armed against `git rev-parse HEAD` (the self-reference): auto-release fails and nothing publishes. Assisted-by: Claude:claude-opus-5-5
The remainder spec's five criteria are delivered: the scaffold wired to the managed repo's own CI check names with an audit-clean render (AC1), the merge path driven to a published release against a fake forge (AC2), the scaffolded reviews charter exempting sha-keyed receipt directories (AC5), `launch receipts` sharing the release job's reader and proven equal to it (AC7), and the emit step ending with the receipts protocol (AC8). spc-14 delivered the rest. itd-93 declares no impact; closed as additive. Delivers: itd-93 Assisted-by: Claude:claude-opus-5-5
…I checks as [] The protocol named the receipt key a 40-character sha, which a SHA-256 repository's commits are not; the gate and the charter accept both. The scaffold's --json reported an absent CI-check list as null; it is []. Assisted-by: Claude:claude-opus-5-5
The review of the release-gate wiring (fix round 1 of lane gatewire) named two security holes in the content-commit derivation's version binding, two false refusals, a wording slip and a follow-up audit. Refs: iss-2609251939461459, iss-2609251939468296, iss-2609251939460232, iss-2609251939466588, iss-2609251939476304, iss-2609251939472371 Assisted-by: Claude:claude-opus-5-5
The content-commit derivation bound the nearest receipts directory to the released tree's CHANGELOG version with a lenient reader, and judged the nearest candidate only. Four ways past or into it: - A released tree with no dated heading, or no CHANGELOG.md, read as "" and an earlier commit's receipts matched on "" == "". The released tree must now name a version, or the derivation refuses. - A pre-release, build-metadata or undated head was skipped, so the binding compared against the previous release, whose PROMOTE receipts admitted this one. A new strict reader, changelog.ReleasedVersionIn, takes the newest "## [" heading other than [Unreleased] and refuses one the dated-heading reader does not parse. - Candidates are filtered by version first and the nearest taken from those, so a co-batched pull request's own receipts directory can no longer tie with or shadow the roll's and wedge a correct cut. - receiptShaRe requires a full 40- or 64-hex sha, as the charter, protocol and runbook say, so an abbreviated twin is a stray entry, not a tying candidate. This also narrows --release-gate to full shas; the release job passes the derived full sha. Item 2's shape: refuse an unreadable newest heading, rather than pass the tag in and require tag == CHANGELOG == receipts. Refusal lives in the one reader both front doors share, so `abcd launch receipts` (which has no tag) and the release job keep one verdict (itd-93 AC7). Passing the tag would split them and change the pinned workflow's verify step. The rehearsal simulated its roll as "## [0.0.0-rehearsal]", which the gate read past, landing on the previous version. The strict reader refuses it, so the rehearsal rolls "## [0.0.0]" in release.yml and in both branches of the scaffold template. TestScaffoldedGateCutsAFirstReleaseThatPublishes/semantic-gate was RED on it and is green again. Refs: iss-2609251939461459, iss-2609251939468296, iss-2609251939460232, iss-2609251939466588 Assisted-by: Claude:claude-opus-5-5
… refuses Resolves: iss-2609251939461459 Assisted-by: Claude:claude-opus-5-5
…fuses Resolves: iss-2609251939468296 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609251939460232 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609251939466588 Assisted-by: Claude:claude-opus-5-5
…he tagged merge The roll step called the content commit 'the commit the release publishes from'. The release publishes from the tagged merge, as the runbook's last step says; the content commit is the one the reviewers read and every receipt names. Refs: iss-2609251939476304 Assisted-by: Claude:claude-opus-5-5
…erge Resolves: iss-2609251939476304 Assisted-by: Claude:claude-opus-5-5
abcd's tests audit every rendered workflow profile for duplicate keys and template injection, and only abcd's own workflows run under zizmor in CI. The launch page and the scaffolded runbook now say that action pinning, job permissions and credential handling go unchecked for the bare profile, and tell the operator to run their own workflow auditor. Running zizmor over a rendered bare profile in CI stays open as a follow-up. abcd's own release-gate runbook also describes the stricter content-commit derivation: version filter before nearest, full-sha entries, a strictly read released heading, and the rehearsal's plain 0.0.0 roll. Refs: iss-2609251939472371, iss-2609251939460232, iss-2609251939466588, iss-2609251939468296, iss-2609251939461459 Assisted-by: Claude:claude-opus-5-5
Found while choosing the shape of the pre-release-head fix. The shape chosen refuses an unreadable newest heading in the reader both front doors share, so the tag is not consulted. A hand-pushed tag naming another version therefore still passes under the CHANGELOG version's receipts. Closing that needs a ruling on the verify step and on the local check's parity, so it is filed rather than fixed in this round. Refs: iss-2609251945586202 Assisted-by: Claude:claude-opus-5-5
A hand-pushed tag naming a version other than the released tree's newest dated CHANGELOG heading (v0.1.1, v0.2.0 or v0.1.0-rc.1 on a 0.1.0 tree) passed a scaffolded semantic profile's verify job under the receipts of the CHANGELOG's version, and published under a version nobody reviewed. record-lint gains --released-version, a standalone mode that prints the version the released tree (HEAD) names, read by the strict reader the receipt derivation binds with (lint.ReleasedVersion over changelog.ReleasedVersionIn), and fails closed with no output where the derivation does. The template's verify job gains a step, in the semantic block, before the receipts gate, guarded like the archive step (github.event_name != 'workflow_dispatch'): it refuses unless TAG equals "v" plus that version. The comparison is string equality in the workflow, which holds the tag; no heading is read in bash, so no second reader exists to drift (anchor.go). The derivation and the tag-less launch receipts are untouched (AC7). Chosen over --derive-content-sha --expect-tag: the assertion then rides the receipts step, which is also dormant on a private repository, and couples the tag check to a derivation it does not need. abcd's own profile was already closed by launch archive --tag (cli/archive.go), which refuses a tag that is not the newest dated version before the receipts step. The step rides into abcd's own release.yml for free through template parity. TestScaffoldedGateRefusesATagNamingAnotherVersion drives the rendered workflow on a tag push through the fake forge: each mismatched tag fails verify at the binding with the receipts gate skipped and nothing published, and v0.1.0 on the same commit publishes. Refs: iss-2609251945586202 Assisted-by: Claude:claude-opus-5-5
commands/launch.md's scaffold section said the scaffolded release.yml
arms its gate against HEAD^2^ on the merge path and HEAD^ on a direct
tag. It derives the content commit from the receipts directory of the
released tree, as the same page says in its Ship and Semantic receipts
sections; the bullet says so, applies it to the semantic profile the
receipt gate exists in, and names the tag binding verify makes first.
The same stale claim sat in the bare profile's rehearsal step comment
("mirrors release.yml's resolve step exactly"): that profile arms no
receipt gate, so release.yml has no resolve step to mirror. The comment
says what the step proves. The runbook template and the release-gate
README carry no such phrase.
Assisted-by: Claude:claude-opus-5-5
…eased version The record carries its correction: the hole was confined to scaffolded profiles, since abcd's own verify job refuses a mismatched tag at launch archive --tag before the receipts step. Resolves: iss-2609251945586202 Assisted-by: Claude:claude-opus-5-5
The reviewer's sharpening (only the roll, whose first parent carries a different version, is the content commit) would refuse a release branch whose receipts name a later CHANGELOG revision, so it waits on a ruling rather than landing in this fix round. Refs: iss-2609252024442310 Assisted-by: Claude:claude-opus-5-5
Refs: iss-2609252029191920 Assisted-by: Claude:claude-opus-5-5
…ding renders The tag-binding step added to verify is a deterministic gate, and gate_lockstep refused abcd's own release-gate README for not listing it. The README lists it as gate 11 and says what it guards; the runbook template lists it after the archive gate in every semantic profile. TestRunbookGateListMatchesVerifySteps holds the rendered runbook's numbered list equal to the verify job's gate steps in every profile. It also found that the abcd profile rendered no list at all: the trim markers around the non-abcd merge-gate and audit sections swallowed both newlines around the skipped block, gluing "## Deterministic gates" onto the end of the rehearsal paragraph. The opening marker now trims after itself instead of before, so the paragraph keeps its blank line in every profile. Refs: iss-2609252029191920 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609252029191920 Assisted-by: Claude:claude-opus-5-5
…tocol ruling Refs: iss-2609252024442310 Assisted-by: Claude:claude-opus-5-5
…ld-wiring The payload diff's landing head carries main's sentences, --route and the consolidated verbs. The ship emit step keeps both halves: it attaches the composer's request block (nil on a refused cut) and ends with the receipts protocol, now read from the checkout root the cut reads. The scaffold drops its Short for the sentence manifest; the scaffold test names `abcd lint docs` and this branch's scripts/ path. `launch receipts` gains its sentence in the same shape as its siblings. Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
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.
With this change,
abcd launch scaffoldwires a managed repository's release gate to its own CI checks and writes its reviews charter. A scaffolded repository can now take its first release through the merge path to a published release. The release job's receipt gate is bound to the release it gates. With this, itd-93 ships.[]. The scaffold's workflow audit is described as what it is: not a full zizmor run.launchruns the release job's receipt gate locally. Its emit step ends with the receipts protocol, which says the release publishes from the tagged merge and asks for a full sha.record-lint --released-versionprints the version, and it fails closed. In scaffolded profiles, a verify step compares the tag against that version before the receipts step runs. A mismatched tag,-rc,+build, a four-part version, an Unreleased-only tree or a missing CHANGELOG are all refused, and nothing publishes. abcd's own release.yml carries the step through template parity, after its archive step.Open and deferred out loud: iss-2609252024442310, a minor finding. Receipts that name a post-roll CHANGELOG revision are still admitted, and sharpening that waits on a protocol ruling (S). The record carries
deferred_after: v0.10.0and its reason.Pre-existing residuals noted by the review: verify does not check that the tagged sha is on the default branch, and there is no tag ruleset, so the release environment reviewer is the stop.
Review: Fable 5.1, three rounds. Rounds 1 and 2 asked for fixes (eight findings, then the tag binding). Round 3 verified: SHIP. Reading-corpus change. It lands after the launch gate suite (itd-65) and the payload diff (itd-66).
Resolves: iss-2609251751298408
Resolves: iss-2609251755386183
Resolves: iss-2609251939460232
Resolves: iss-2609251939461459
Resolves: iss-2609251939466588
Resolves: iss-2609251939468296
Resolves: iss-2609251939476304
Resolves: iss-2609251945586202
Resolves: iss-2609252029191920
Refs: iss-2609252024442310
Delivers: itd-93
Assisted-by: Claude:claude-opus-5-5