docs: the dry-run fidelity guarantee is one-directional but reads as two #896
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!896
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/662-dry-run-fidelity-docs"
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?
Closes frankenbit/release-toolkit#662
The finding, and why the fix isn't the literal suggested rewrite
#662 quoted a bash-era sentence and a real 2026-08-06 merge-hold incident:
rt decide's dry-run could reportmode=cutfor a run that would resolvemode=updatelive, because Layer-2/3 returnedskipunder--dry-runrather than actually checking.That specific gap no longer exists. #689 (closed 2026-08-18) fixed it at the code level: Layer-2/3 now genuinely query the Forgejo API under
--dry-run— not a summary. Verified againstinternal/decide/decide.go:opts.DryRunis never referenced in the decision logic at all, andrt decideperforms no writes (confirmed — nod.forge.Create*/Update*calls, only reads), so--dry-runis a complete computational no-op for this command. Implementing #662's literal suggested text would have re-described an asymmetry that doesn't exist anymore.What this PR does instead
docs/integration.md § Dry-run preview, including the "computational no-op" framingFORGEJO_API_DRY_RUN=1 propagates through— grepped the Go tree; that env var isn't read anywhere, only referenced in comments about the retired bash mechanismrt release's idempotency pre-check (GetReleaseByTag,internal/forgejo/reads.go:89-98) is faked under--dry-run— always returns "not found" — so a dry-run preview always shows a create, even against a tag whose live run would resolveidempotent=1. This is the same shape of bug #662 reported (a preview diverging from live in one direction), just on a different command, and it's currently live.decide,prep) vs. payload-summary-only (release)mode=blocked, not onlymode=noop/mode=update) — caught this while re-reading the surrounding paragraph, per CLAUDE.md's "re-read what a correction was holding up" disciplineVerification
go build ./.../go test ./...clean, no Go source touchedinternal/decide/decide.go(Layer2/3,detectCut,updateDecision, the #711 fall-through),internal/forgejo/reads.go(GetReleaseByTag's dry-run short-circuit),cmd/rt/decide.go(forge client construction — confirms nodryRunflag reaches the client at all forrt decide)changelog.d/662-dry-run-fidelity-docs.fixed.mdadded;rt fragment-check changelog.dpasses✅ APPROVED — one-pass, spent on the absence claim, because that is the one that can be silently wrong
+8 −5, two files. The headline is "opts.DryRunis never read in the decision logic" — an absence claim, so it gets a control rather than a grep.① The absence holds, and
#689left its own witness✅ And the corroboration I did not expect:
internal/decide/safeguard_dryrun_test.gocarriesTestDecide_689DryRunDoesNotSkipLayer2andTestDecide_689DryRunMatchesRealRun. The fix is pinned by tests named for the issue — so "already closed at the code level by#689" is not an inference from absence, it is a claim with a guard behind it.🔑 Not implementing
#662's literal suggested rewrite is the right call and the most valuable thing in this PR. Re-describing a gap that no longer exists would have been correct against the tracker and wrong about the world — and nothing in the tracker would have told the next reader.②
FORGEJO_API_DRY_RUN— I nearly filed a false finding here, so the working is on the recordMy first pass grepped file names and got two
.gohits,internal/forgejo/reads.goandinternal/harness/harness.go. That reads as "it IS used in the Go port", which would have been a red on a correct retirement.Reading the matched lines:
Both are comments, naming the bash lineage of
c.dryRun. The env var is never read. ✅ The retirement is correct. A count of files is not a count of uses, and I had the file list before I had the evidence.③ The new gap is real, and the code states it more strongly than the doc does
internal/forgejo/reads.go:90-97—GetReleaseByTagunder dry-run:Unconditional
ErrNotFound. So "rt release's preview always previews a create" is exact, deliberate (mirroring bash,#557), and documented in the source — and undocumented for the adopter until this PR. Naming a live gap on a different command while retiring a dead one on the first is the substantive win here.④ The
#711footnote fix is current5021d13 fix(decide): a declined cut-safeguard blocks instead of resolving to noopis onmain, anddecide.go:1025emitsmode=blocked. The footnote no longer implies decline resolves only tonoop/update.Nothing blocking, no nits. Fragment shape matches the convention.
Bound by omitting
commit_id.