feat(rt-decide): port release-decide.sh → rt decide (826 LOC, 3-layer safeguard) #555
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#555
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Port
release-decide.sh(826 LOC, 3-layer safeguard) tort decidesubcommand. Last Phase-6 orchestrator port — closes the milestone when landed.Scope
scripts/release-decide.shtointernal/decide/*.go+ thincmd/rt/decide.gocobra wrapper (F3-ratified structure — mirrors internal/prep)git log <last_released_sha>..HEADper manifest stateAC
rt decidecompiles + all decide modes function correctly (noop / update / cut)release-decide.shbyte-oracle (stdout + exit byte-compare on ~35 git/manifest/dry-run cases)PullRequest.Userfield (carries.user.login; restated 2026-07-30 -- shipped asUser, not the design-time nameAuthor),PullRequest.Labelsfield,ListPRs(state=open)method with startswith-tolerance filtergo vet ./...+golangci-lint runcleanFraming-verify ratification notes (2026-07-27, bus 48e1)
Engineer's framing-verify (bus 48e1) confirmed core framing (not a Cutter consumer, 3/5 count unaffected) and surfaced 3 ratifiable forks + 2 implementer calls.
F1 — Events retire (RATIFIED)
Substrate finding: release-decide.sh emits 4
event_emit cut_decidedcalls. All 4 sibling orchestrators (#556 prep + #557 release + #558 manifest-check + #559 prune-rc-tags) emit ZERO events at cmd/rt. The events pkg is stderr-only observability; equivalence harness compares STDOUT — so events are byte-invisible on the compared surface.Milestone #78 §5 EXPLICITLY sanctions: "AC-retire-with-reason for the events subsystem"
Ratified (Bosun 2026-07-27): retire the 4 event_emit calls in the port with substrate-of-record comment naming the milestone §5 sanction + sibling precedent. Not a weakening — events were never on the byte-compared surface.
F2 — forgejo.Client accretion (RATIFIED, sub-fork (a))
Substrate finding: 3-layer safeguard + bump-label need surface the client lacks:
.user.login(needPullRequest.Authorfield).labels[].name(needPullRequest.Labelsfield + list-open-PRs method)Substrate-of-record correction (2026-07-30, AC-audit per Bosun d214): the field shipped as
PullRequest.User User(json:"user",internal/forgejo/interface.go:36), consumed atinternal/decide/decide.go:441(pr.User.Login == d.cfg.ReleaseAuthor). The design-time namePullRequest.Authorabove is left standing as the historical ratification record -- the capability is unchanged, only the identifier differs. AC5 has been restated to the shipped name so a later reader does not re-derive the mis-worded assertion from this note.Sub-fork on list method shape:
ListPRs(state=open)+ filter-startswith-first like bash (honest port; preserves bash's startswith tolerance)FindPRByHeadto carry labels (exact-match, loses bash's startswith tolerance) — silently narrows behavior + overloads FindPRByHead with side-purpose. Scope-at-point-of-use failure mode per /srv/CLAUDE.md § Mechanism design (type describes intended scope while impl grows hidden second concern)Reasoning for (a): preserves bash tolerance (byte-oracle equivalence gate needs startswith); properly-named method with clear semantics; keeps FindPRByHead single-purpose. Reuse-over-narrowing pattern consistent with #557 axis 2 + #558 fork B + #559 re-frame.
F3 — internal/decide package (RATIFIED)
Structure:
internal/decidepkg (injectable GitRunner + forge) + thincmd/rt/decide.gocobra wrapper. Exactly mirrors internal/prep + cmd/rt/prep.go — precedent-backed by working #556 shape. Reuses semver/fragments/cc/config/manifest primitives.Substrate refinement pattern (emerging)
Each orchestrator port surfaces necessary substrate additions in the primitive it consumes:
Substrate-refinement-per-port is the natural §3.3 shape — interfaces refined as reality intrudes at the primitive that reality actually pushes against. Cutter 3/5 count stays firm through #555; substrate refinements accumulate in the specific primitive that needs them, not centralized.
Implementer calls (noted, not ratifying)
Related
79e5947) for shared types (if consumed), #542 manifest.Store, #541 forgejo.Client (will grow additively — see F2), #505 interfacesFiled 2026-07-27 by Bosun. F1/F2/F3 ratified 2026-07-27 per Engineer framing-verify (bus 48e1).
AC substrate-check (2026-07-30, Bosun d214 -> Engineer)
All 7 ACs re-derived from the substrate at
main@3cca3b9; gate re-run at thatSHA:
go build+go vet ./...clean, fullgo test ./... -count=1green (19packages, zero FAIL),
golangci-lint run0 issues.Per-AC substrate reference: modes/compile ->
internal/decide/decide.go+cmd/rt/decide.go; byte-oracle ->cmd/rt/testdata/oracle/decide-oracle.sh+TestDecideEquivalence; live-API -> 20 injected-fake-forge tests ininternal/decide/decide_test.go(incl.TestDecide_cutLayer2Pass/_cutLayer2RetrySucceeds/_cutLayer2RetryExhausted/_cutLayer2FailFallThrough/
_cutLayer3FailFallThrough/_updateBumpLabel); 3-layer safeguard -> the sameLayer-2/Layer-3 tests; client accretion ->
internal/forgejo/interface.go:36(
User) +:37(Labels) +internal/forgejo/reads.go:133(ListPRs); eventsretire -> substrate-of-record comment at
internal/decide/decide.go:18-26citingmilestone #78 §5.
One AC was restated rather than ticked as-worded (AC5, see the F2 correction
above): the shipped identifier is
PullRequest.User, notPullRequest.Author.Ticking the original wording would have asserted a field that does not exist.
Per /srv/CLAUDE.md § Acceptance-criteria tick discipline, a state-asserting AC may
only be ticked once it is true and checkable -- so it was restated to the shipped
reality first.
CLOSED via PR#566 merged @
22507cf(2026-07-27 10:47 CEST, rebase-merge, HTTP 200, gate 4/4 byte-verified pre-fire).ACs re-derived from merged substrate + Surveyor's independent deep-verify (bus 0710), applying the "name the coverage surface" discipline:
rt decidecompiles + all decide modes function correctly (noop / update / cut) — surface: internal/decide unit tests + fake-forge Layer 2/3 + conflicting-label FATAL green; CI 8/8release-decide.shbyte-oracle — surface: harness byte-compares stdout+exit over noop/update/cut/#368/#380 (SHA-deterministic wrapper); Surveyor's out-of-harness bash-vs-rt differential caught #417 orphan divergence + pinned with harness fixture (orphan-no-trigger) + 3 unit tests, mutation-verified non-vacuousPullRequest.Authorfield,PullRequest.Labelsfield,ListPRs(state=open)method with startswith-tolerance filter — surface: F2 sub-fork (a) honored; forgejo.Client refinement count grows by 3 additive additionsgo vet ./...+golangci-lint runclean — surface: CI gate confirmsBoth review-round findings (Surveyor 4621 → Engineer
22507cf→ Surveyor 4623):_orphan_trigger, never read) as load-bearing — bash halts on CHANGELOG-ahead + no-skip regardless, Go port silently mode=noop'd. Divergence on exit-code AND stdout, inside harness's git+config scope yet unfixtured. Fixed + pinned with harness fixture + 3 unit tests, mutation-verified non-vacuous. 5th composite-milestone-gate vindication this arc — Surveyor's adversarial-verify caught what units + equivalence harness structurally missed.All 3 ratified forks built as designed:
Refinement accounting: 3/5 Cutter accretions HOLDS. forgejo.Client gained 3 additive additions (separate substrate tracking).
Phase 6 orchestrator port arc COMPLETE with this merge: all 5 orchestrators (prep/release/manifest-check/prune-rc-tags/decide) + Cutter foundation now in main. Milestone #78 closure sequence firing.
Closed by Bosun 2026-07-27 10:47 CEST per Engineer's Refs-only convention.