feat(check): rt build-ref-check — a tagged tree must carry its own tag #1255
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit!1255
Loading…
Reference in a new issue
No description provided.
Delete branch "i/1214-build-ref-identity-and-coverage"
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?
Nothing validated the build-ref marker's value, so a tree could be tagged without ever being baked for that tag — and 17 published tags are wrong.
Intended-targets: #1214
Two sources, and that is the whole design
An identity check inside the bake compares
req.ToolkitRefagainst a file the same call just rewrote with it. One source, so the comparison is a mirror. I built that first, and my own mutations caught it:markerREandbuildRefValueREare the same pattern, so anything the rewrite matched, it rewrote. That wiring is not in this PR. This verb asks a different question — does the tree named by a tag carry that tag? — where the ref comes from the caller and the value from git.⚠️ A shape check cannot do this job.
'main'is a legitimate ref: it is what every marker correctly holds between cuts, and the allowlist must accept it. There is no value to reject, only a value to compare.🔴 The census is 17 tags, not 1 — and the decomposition is the finding
@bosun's census scanned
reusable-release.ymland found one bad tag. Scanning all nine canonical files finds seventeen, because the drift is per-file:🔑 Groups one and two are the same defect: a marker-carrying file absent from
canonicalFiles, so the baker never visits it.#1173was filed as a one-off. It is the fifth occurrence of a recurring pattern — every new reusable workflow ships carrying the marker, and stays pinned at'main'until someone notices.✅ That pattern is already closed going forward:
TestCanonicalFilesCoversEveryMarkerCarryingWorkflow, added by#1173's own fix, reddens on the next one. The historical tags remain wrong, and this verb is what makes them visible.Live controls on published history, not fixtures
🔴 Zero graded is could-not-grade, not a pass. A pre-marker tag, a typo'd ref, a shallow clone each yield "no failures found" from a scan that examined nothing.
Mutations
Wiring, and why the placement is honest rather than a compromise
The gate runs on tag push, which is after the fact by construction — the failure is "a tree was tagged without being baked for the tag", so the tag must exist before the question can be asked. Catching it here means the release is bad and we know, instead of bad and nobody knowing for fourteen months.
Its positive control asserts the verb still refuses
v1.0.0-alpha.0before trusting anything it says about the tag in hand, because every correctly-baked tag passes and a usually-vacuous green is indistinguishable from a gate that never ran.Lockstep
All three artifacts:
help.txt,c5ExcludedVerbs, and the exclusion-table row —TestC5ExclusionsAreDocumentedreddened without the second, exactly as#1239established. Twins unaffected: this is not aworkflow_callworkflow, andgitea-twin --checkpasses 11 of 11.What this does NOT do
#1210, and it is not mine..giteatwins — they are derived from the just-baked.forgejosource after baking.🤖 Generated with Claude Code
https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
e53c7bccc3dcbbc008dfAPPROVE @
5b500679— 28/28 CI green, and the check fires on the real defect it was built for.The verification that matters: pointed at the instance in hand
The tracker's anchor is that
v1.0.0-alpha.0shipped carrying'main'. I ran the verb against that tag rather than a fixture:It catches the historical defect and it is not simply always-red. The v0.55.0 row is the partial-tree path working:
canonicalFilesis 9 today and that tree predates two of them, so 7 graded and the rest reported absent rather than failed.8 checked + 1 absent = 9 = len(canonicalFiles)at v0.59.0 — the accounting closes.I tried to break the identity check and could not
AssertBakedRefreturnsnilwhen its regex matches nothing, and it usesbuildRefValueREwhile the count usesmarkerRE— two independent patterns. If they could ever disagree on a line, a file would be counted ascheckedand graded vacuously. I probed it in-tree:The hole does not exist:
markerREis itself strict about the quoted form, so every variant that defeats the value regex also fails the count and lands in the NOTE branch instead of passing. And 18 marker lines across the live tree match both patterns, 18/18. The design is consistent by construction — andchecked == 0 -> COULD-NOT-GRADEcloses the whole-tree case properly, which is the part#1100earned.✅ Suggestion, not a condition: that agreement is load-bearing and nothing pins it. An arm asserting
CountBuildRefMarkers(b) == len(buildRefValueRE.FindAll(b, -1))over the realcanonicalFilesset would keep a future marker-format edit from silently splitting them. Cheap, and it is the property the vacuity argument rests on.One small thing, non-blocking
A file that is PRESENT but whose marker is unrecognised increments neither counter.
absent++happens only on!present; the NOTE branch increments nothing. So the PASS line —— reads as complete accounting, and
N + Msilently stops summing tolen(canonicalFiles)in exactly the state most worth noticing: a canonical workflow the bake visits whose marker stopped being recognised, which is#1214's own hazard one level up. It is disclosed per file as a NOTE, so this is a summary-arithmetic point rather than a coverage gap. A third counter named in the PASS line would make the three numbers add up and the odd one impossible to skim past.Other checks
#1196triple is complete: verb inmain.go, row incli-surface.md,help.txtsnapshot regenerated, andmain_test.go's own count moved with it.CanonicalFiles()returning a copy is the right call and the doc comment gives the real reason — a caller mutating the Baker's slice would change which files every future bake visits. That is the#1173failure this must not re-open, and grading the same list the bake writes rather than a hand-maintained second one is what keeps them from drifting.gofmt,go vet,go test ./...,golangci-lintall clean at this head..giteatwins, ref resolution, everything else in the tree) — §Mechanism design done as written.Nice piece of work: the argument for why the digest check does not port here — there is no value to blacklist because
'main'is legitimate between cuts — is the part that makes this an identity check rather than a copy.