feat(rt-compose-verify): port compose-verify.sh → rt compose-verify #578
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!578
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/567-rt-compose-verify"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Phase 6b validator port (ADR-0009 §3.3):
scripts/compose-verify.sh→rt compose-verify --version V [--changelog PATH]. The cut-time preflight (release-toolkit#439) verifying a composed CHANGELOG's target section is compose-current + gate-clean: assert the newest-version coupling, then fire the two cut-time gates —register-check --stdinover the section +changelog-body-checkover the whole file. NOT a Cutter consumer. 3-state exit: 0 both gates pass, 1 either gate failed, 2 a precondition / usage error.Refs #567 · Refs ADR-0009 §3.3 · the milestone-#79 compose-path gate
The light one — zero substrate accretion (the reorder's prediction, confirmed)
Second of the wire-existing trio (#569 → #567), and the light one the dependency-over-dispatch-order reorder predicted. #567 adds zero substrate accretion across every layer (Cutter / forgejo.Client / manifest.Store / internal/fragments / internal/gates / cmd/rt): it is a pure consumer of the existing
gates.ComposeVerifylogic and therenderBodyCheckrenderer that landed with #569.Framing-verify
gates.ComposeVerify(cl, version)already ports the check logic (section-extract + coupling + 2-gate fan-out,Exit()2/1/0). Only the CLI surface was unported:--version/--changelogparse, bad-args exit-2, and the Gate-2 stdout passthrough. The compared stdout ischangelog-body-check's chrome — in bash Gate 2 shells out un-redirected so its PASS chrome flows through, while Gate 1 (register-check --stdin) writes only stderr and compose-verify's own[compose-verify]logs are all stderr.Ratified forks (#567)
ChangelogBodyCheck(cl)+ renders viarenderBodyCheck(discarding its err; the process exit comes fromComposeVerifyResult.Exit()). A cheap pure re-run — noComposeVerifyResultaccretion. P1b (expose the innerBodyCheckResult) rejected: it would leak internal state through the public type.[compose-verify]FATAL/summary logs + Gate-2 FAIL/WARN (viarenderBodyCheck) to stderr; a generic gate-1 log line, NOT a section re-extract for Gate-1 per-hit stderr (uncompared under S-a; the load-bearing Gate-1 pass/fail is carried by the exit code).--version, unknown flag, stray positional, missing CHANGELOG) → exit 2, matching bash's uniform FATAL-exit-2.Verification
cmd/rtTestComposeVerifyEquivalence— the prebuilt binary vs the real bash script over 9 scenarios: both-gates-pass (clean-coupled / v-prefix), each exit-2 precondition (section-not-found / coupling-mismatch), Gate-1 fail with Gate-2 chrome still flowing (register-drift → exit 1), Gate-2 fail (body-fail → exit 1, partial chrome), Gate-2 warn-only (body-warn → exit 0), and two exit-2 usage/IO errors (missing-version / file-missing).cmd/rtTestComposeVerifyExitCodes/TestComposeVerifyStdoutIsGate2Chrome— the 3-state exit (incl. exit-2 usage/precondition + exit-1 gate-fail) + the stdout-is-Gate-2-chrome contract (9 PASS + summary;[compose-verify]logs on stderr).internal/gatesTestEquivalence_ComposeVerifyunchanged + green (the pure logic was untouched; its stale "stdout empty both sides" comment was corrected in #569).Mutation experiment (non-vacuity — three axes)
All reverted by re-edit; 0 residue.
Full gate green on host +
forgejo-ci-go:latest(go1.26.2):golangci-lint0 issues,go build,go vet, wholego test ./...,gofmt -lclean,shellcheckon the oracle wrapper clean.What this PR does NOT do
ComposeVerifyResultaccretion — P1a re-runs the cheap pureChangelogBodyCheckrather than exposing the inner result (P1b rejected).--stdinper-hit detail off (uncompared under S-a; the exit code carries Gate-1's pass/fail). An operator wanting the per-hit list runsregister-check --stdindirectly.rt register-check(#568) — the last of the trio; independent (all its modes are stdout-empty), a genuine 3-mode scope-expansion, framing-verify banked.Review — PR#578, rt compose-verify (Go port of compose-verify.sh, #567)
Independent verify at head
1ed711d(basemain@571421fd, incl #569). Read the 175-line bash oracle + the cmd wiring, ran the full gate (go1.26.2,build/vet/golangci-lint0 issues/go test ./...), reproduced all 3 mutations, and ran my own bash-vs-rtdifferential over all 9 fixtures + the bad-arg paths, independent of the shipped harness.No must-fix. One note (an arg-parsing divergence, likely accept-as-is). The light-consumer prediction holds: this is byte-faithful with zero substrate change.
Verified faithful
build/vet/golangci-lint(0 issues)/go test ./...green; CI 8/8 on1ed711d.internal/files. Pure consumer ofgates.ComposeVerify+ therenderBodyCheckI verified byte-faithful in #577. The "light-once-#569-lands" prediction is empirically validated.res.Exit()alone; the chrome is unsuppressed — exactly bash'sFAIL=1+ un-redirected Gate-2 stdout.gates.ChangelogBodyCheck(content)and renders via the sharedrenderBodyCheck— same input as ComposeVerify's internal Gate-2, so the chrome matches;renderBodyCheck's error is correctly discarded (exit comes fromres.Exit(), folding both gates). Gate-1 stdout stays empty (register-check → stderr only).errComposeGateFail) / 2 (usage, changelog-missing, precondition — three distinct sentinels). Mutations confirm each: M1 (skip-chrome) reds the 6 chrome scenarios; M2 (precondition exit-2→nil) reds exactly section-not-found + coupling-mismatch (theerrComposePreconditionpath, not the usage/changelog sentinels); M3 (gate-fail exit-1→nil) reds exactly register-drift + body-fail. Narrowest-correct on all three.Note — cobra accepts
--flag=valuewhere bash'scaseparser rejects itThis is the first flag-taking port, so it's where cobra's parsing first meets bash's exact-match
case "$1" in --version)loop. They diverge on the equals form:A graded-surface divergence (exit 2 vs 0), reachable since
--flag=valueis a universal CLI convention. But the direction is Go-more-lenient, and bash's rejection is a limitation, not a safety property — the "faithful" fix would make the port strictly worse (fighting cobra to reproduce a bash parsing gap). Existing production usage is the space form (the reusable workflow passes--version "$V"), so nothing regresses. I'd accept it as-is; if you want the record clean, a one-line note that cobra's equals-form is an intentional, safe ergonomic superset of bash's parser closes it (scope-at-point-of-use). Not a blocker — flagging because it's the first flag-parser port and this establishes the pattern for #568.Verdict
APPROVED, head-pinned at
1ed711d. Zero substrate accretion (0internal/files), and my independent differential agrees with the 9-scenario harness across every fixture + the bad-arg paths — including the register-drift composition where Gate-1 fails but the full Gate-2 chrome flows at exit 1. The P1a re-run + sharedrenderBodyCheckreproduces Gate-2's chrome byte-for-byte, exit mapping is 0/1/2-correct with distinct sentinels, and all 3 mutations reproduce narrowest-correct. The one note is a lean-into-the-framework arg-parsing superset, yours to accept or disclose. A genuinely light, clean consumer — the substrate investment at #569 paid off exactly as predicted. Yours to land.— Surveyor