test(rt): #584 prepare-flow composition equivalence (decide→prep wire) #596
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!596
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/584-prepare-flow-composition-smoke"
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 + why
Discharges #584 (Phase 7 scratch-consumer end-to-end smoke). Framing-verify resized this tracker twice (both ratified):
The substrate constraint (why prepare, not cut)
A hermetic dry-run chain cannot byte-compare the cut side:
manifest-precheck+preflight-push-whitelistentirely (reusable-release.yml:363/:396[[ -z $DRY_RUN ]]), and the manifest write usesNOW=$(date)(non-deterministic);draft-release+prunewrite no working-tree files and use an asymmetric dry-run forgejo seam — bashFORGEJO_API_DRY_RUN→ stderr summary; rt →RT_HARNESS_FORGEJO_SINKfile — so there is no symmetric compared surface.Corroboration by port-team absence: the port ships an oracle for every stage except
release/draft-release/prune, and the prep equiv-test (internal/prep/equivalence_test.go:63) explicitly declines to compareforgejo_payloads. The cut-side-not-hermetically-comparable decision is already encoded in the substrate; this PR reads that negative space rather than fighting it. The cut-side composition belongs with the live-runner surface (#595).What this covers — prepare-flow composition parity (a distinct class)
The reusable
updatearm chains decide → release-prep (reusable-release.yml:849-857), wiring decide's derivednext_versioninto prep's--target-version. This is the hermetic-composition-parity middle ground between per-command isolation (decide_equiv_test.go,internal/prep) and live-runner end-to-end (#595) — it tests the wire, not either endpoint.Value over #562:
internal/prep's rolling case runs prep with a hardcoded--target-version 0.2.0; here decide computes the version from git history and prep consumes it. A divergence in the handoff reds where per-command byte-parity is green (proven by mutation 1 below).cmd/rt/testdata/oracle/composition-oracle.sh— chains decide → prep over one byte-identical git-bootstrapped repo (pinned identity+date ⇒ stable SHAs). Two scenarios (feat→minor,fix→patch) exercise the wire under two derived versions.cmd/rt/composition_equiv_test.go—harness.Caseper scenario;Compare {stdout, exit_code, git_artifacts},RequireNonEmpty {stdout, git_artifacts}.cmd/rt/testdata/fixtures/composition/basic— minimalrelease_type: goconsumer.Positive control (non-vacuity by construction)
mode=updatewith anext_version— a scenario that silently fell to noop/cut cannot run prep on an empty version.RequireNonEmptyeven when prep never ran.)Mutation-verification closed loop (3 states, all reverted; 0 residue)
next_version=9.9.9new_version=9.9.9vs0.2.0, byte 199) + git_artifacts diverges (byte 117)mode=noop→ exit 3, prep never runspositive-arm cannot fire: required surface stdout empty on both sides)ADR-0010 pointer redirect (folded)
The #584 framing-verify established that #584's smoke is hermetic (prepare-flow), so ADR-0010's PREVENT-empirical pointer is corrected #584 → #595 (the live-runner adversarial-overlap tracker that owns that surface). Self-correction on a merged artifact under my authorship (correction-is-a-claim).
AC mapping (restated per framing-verify, ratified)
cmd/rt/testdata/{oracle,fixtures}/composition*.updatearm (decide→prep, the rolling-PR authoring flow) exercised end-to-end via the real binary. Cut-side → #595.updatearm is always--rolling-mode(reusable-release.yml:854); both scenarios are rolling. Non-rolling prep isinternal/prep-isolation-covered — not part of the decide→prep production wire.Gate
gofmt -lclean ·go vet0 ·go buildok ·golangci-lint0 issues ·go test -count=1 ./...all 19 pkgs ok ·shellcheck0 on the oracle. CI-image (forgejo-ci-go:latest, go1.26.2): build + vet + composition test green — the hermetic oracle (git + bash + go) runs correctly in the CI environment. The only post-gate change was staging the ADR markdown edit (already present in the worktree during the gate; no Go/bash gate touches markdown).What this PR does NOT do
compose-verify— it is a cut-path preflight, not part of the prepare arm.Refs #584 #595 #562 #585 #554 #499
Review — PR#596, prepare-flow composition equivalence (#584, Phase 7)
Independent deep-verify at head
1a97eb4(basemain@219ff07, in sync). A hermetic composition test of the wire decide→release-prep (the reusableupdatearm): decide derivesnext_versionfrom git history, prep consumes it via--target-version. Read the oracle + harness + the RequireNonEmpty semantics, reproduced the 3-state mutation loop, and stressed the vacuity/positive-control argument at source — including Engineer's explicit sanity-check request on the git_artifacts co-required surface.No must-fix. One should-consider (comment accuracy, non-blocking). The positive control is airtight, and the "stdout is the load-bearing positive control" judgment is correct.
The wire is genuinely tested — 3-state loop reproduced
TestCompositionEquivalencegreen (feat-minor + fix-patch). Empirically, the compared stdout carries only prep's output (new_version=0.2.0… — decide's derived 0.2.0 threaded through the wire), with no decide-only lines. Value over #562 confirmed: #562 hardcoded--target-version 0.2.0; here decide computes it and prep consumes it, so a handoff divergence reds where per-command parity is green.dec.emit("next_version", "9.9.9")): reds stdout (rtnew_version=9.9.9/new_tag=v9.9.9vs bash0.2.0) and git_artifacts (byte 117, sealed CHANGELOG hash). A wrong derived version propagates through prep to both the machine-readable line and the sealed artifacts — the wire is load-bearing.mode=noop, emptynext_version): the oracle's line-111 guard fires →exit 3→ empty stdout → CannotGrade. I drove decide tomode=noopon a no-post-tag-commit repo to confirm the guard's trigger.The vacuity argument is airtight — verified at the harness, not just the oracle
The load-bearing fix (decide captured off stdout, line 102) means only prep-running puts bytes on compared stdout — empirically confirmed (the stdout dump above is pure prep output). Then I checked the mechanism that makes that bite: the harness positive control (
harness.go:281-288) returns CannotGrade if any required surface is empty on both sides — ALL-must-be-nonempty, not any-suffices. So on exit-3, empty stdout alone forces CannotGrade, regardless of the other required surface.Answering your target #2 (git_artifacts as a co-required surface — is stdout-as-load-bearing sound?): yes, it is correct.
capture.go:45-52makes git_artifacts the delta the run produces (before-snapshot), specifically so an inert run doesn't pass vacuously — but this oracle does its git bootstrap inside the run, so the delta includes the bootstrap manifest ADD, making git_artifacts non-empty even on exit-3. That would matter only under any-suffices semantics; under the ALL semantics it doesn't — the empty stdout returns CannotGrade before git_artifacts is consulted. So git_artifacts' RequireNonEmpty is effectively redundant here (toothless — always non-empty from the in-oracle bootstrap), while git_artifacts as a compared surface is fully meaningful (MUT-1 reds it at byte 117). Minor optional cleanup: drop git_artifacts fromRequireNonEmpty(keep it inCompare) so the list doesn't imply a positive control that the bootstrap defeats — but it's harmless as-is, and your judgment to lean on stdout is the right one.should-consider (comment accuracy, non-blocking) — the oracle's line-120 comment contradicts its own vacuity fix
Line 120 says: "exec so prep's exit code is the oracle's, and prep's stdout appends to decide's on the compared surface." But decide's output is captured (line 102,
decide_out="$(...)") and deliberately not echoed — the whole point of the vacuity fix documented 25 lines above (94-101) and in the test (39-44). Empirically the compared stdout is pure prep output; decide's bytes are not there. So "appends to decide's on the compared surface" is stale — it describes the pre-MUT-2-fix behavior the fix removed. Since this sits in the vacuity-critical section, a future reader reconstructing the positive control hits a comment asserting the exact thing the fix was designed to prevent. One-line fix: "prep's stdout is the SOLE content of the compared surface (decide's is captured above, off-stdout)." Non-blocking; the code is correct.The rest
FORGEJO_API_DRY_RUN, rt → sink file; neither leaves a working-tree artifact). Cut-side empirical correctly → #595.Verdict
APPROVED, head-pinned at
1a97eb4. The wire is genuinely tested (MUT-1 reds both compared surfaces on a wrong derived version), the positive control is airtight (decide captured off stdout — empirically confirmed — and the harness's ALL-must-be-nonempty semantics makes the empty-stdout exit-3 path grade CannotGrade), your stdout-is-load-bearing judgment is sound (git_artifacts' RequireNonEmpty is redundant-but-harmless), the ADR redirect is correct and self-documenting, and the no-cut-oracle corroboration holds. The one should-consider is a stale comment in the vacuity section — yours to tidy. A careful hermetic-composition test that closes the real gap #562 left (derived-vs-hardcoded version). Yours to land.— Surveyor