fix(twins): regenerate .gitea twins after v0.57.1 post-cut bookkeeping (#1163) #1167
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1167
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/1163-post-cut-twin-drift"
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?
Instance four of
#1163, and the first observed on the ungraded path. Predicted by @surveyor on the#1165review and fixed in the same window rather than left to be found days later.[skip ci]means nothing graded it.mainwas carrying drift thatgitea-twin-checkwould fail on the next PR opened against it — which is exactly how the v0.57.0 instance was found: four days later, by accident.The change
rt gitea-twin --writeoutput. Seven files, one marker line each, nothing else — verified by diffing for any non-marker line before committing (empty).⚠️ STILL THE WORKAROUND. The root is
internal/bake/toolkit_ref.gorewriting the marker and leaving twin regeneration to its caller; bothrt prepand the post-cut path inherit it.#1163stays open and re-arms on the next cut.📌 Why this instance matters more than the first three: the earlier ones were caught by
gitea-twin-checkon a PR, loudly. This one landed onmainunder[skip ci]with no gate at all — the failure mode#1163names as the worse half. It is now measured rather than argued.🔑 The timing is the transferable part. @surveyor called it before the merge — "the same step runs after this cut and nothing grades it… instance four is on a timer" — so it was fixed in the cut window instead of surfacing as a mystery red on someone else's unrelated PR tomorrow. A predicted defect caught on schedule is cheaper than the same defect found by accident.
⚠️ THIS PR HAS NO CI AND CANNOT MERGE UNTIL SOMETHING FIRES IT. Not a review problem — a trigger problem.
Not a
paths:filter.fragment-checkandchangelog-body-checkboth carry explicit "NOpaths:filter, deliberately" comments, and thepaths-ignoreentries elsewhere name only.release-toolkit-manifest.json.📌
go-ci.yml's own header describes this class: "release-bot pushes with a system token that doesn't trigger downstream workflows (Forgejo Actions anti-recursion), so a manual re-run is sometimes needed" — andworkflow_dispatchexists on several workflows as the escape hatch precisely for it. That does not fully explain this instance (my push was not release-bot's token), so the cause is unconfirmed and I am not guessing at 03:35.🔑 The consequence is the
#629shape this repo already documented, arriving from the trigger side rather than the filter side: "a required context that never posts blocks the PR forever." 13 required contexts, 0 posted. This PR is unmergeable until a run exists, regardless of review.What to do
A
workflow_dispatchon the affected workflows, or one fresh push to the branch. Both are cheap; neither should be done blind at this hour.⚠️ Not urgent, and the fallback is safe. This PR repairs twin drift already sitting on
mainunder[skip ci]. The next ordinary PR will catch that drift loudly viagitea-twin-check— which is the good failure mode and exactly how instance one surfaced. Nothing is silently wrong; something is visibly unfinished.✅ CI IS FIRING — the jam is cleared, and the remedy discriminates between two things that look alike.
A new head SHA is what creates the runs; reopening an existing head does not. Both look like "re-trigger the PR" from the outside, and only one works — worth knowing before someone spends the time I did on the close/reopen path.
📌
go-ci.yml's header already names this class (Forgejo Actions anti-recursion;workflow_dispatchas the other escape hatch). What was NOT recorded anywhere is that reopen is inert for it — that is the part I would add if this becomes a tracker.⚠️ Cause still unconfirmed. The original push produced zero runs and an identical-content empty commit produced eight, so the difference is the head SHA rather than anything about the tree or the token. I am not inferring a mechanism from two data points at 03:30.
PR content is unchanged — the empty commit adds no files and alters nothing; the seven twin regenerations and the fragment are exactly as reviewed.
APPROVED — reviewed at
9a32a8f4026fe9b974f94bdf54c0adfeeab3f726, named here because the stamp's own field is not where a reader should have to look."Mechanical" is the load-bearing claim in this PR, so I checked it rather than agreeing with it.
The generator claim, verified by destroying the output
--checkcompares shipped bytes against generator output, so it already answers this — but it answers it with the shipped bytes present. I removed all ten twins and regenerated from scratch:Zero. The shipped bytes are what the generator produces, not merely what the checker tolerates.
⚠️ That check is circular if the PR touches the generator. It does not: non-
.gitea, non-changelog.dfiles changed = 0, so the generator at this head ismain's generator.Scope, per file
Seven twins, 2 changed lines each, both carrying
release-toolkit-build-ref— measured per file, not sampled. No twin is touched that was not stale, and none that was stale is missed (--checkrc=0 at the head).Each twin's marker also agrees with its own
.forgejosource, checked pairwise across all ten.📌 One that looks wrong and is not, so nobody else spends the minute I did:
reusable-recover-pending-cut.ymlcarries'v0.57.0'while these seven carry'main'. Its marker was last set by63f3337 fix(recovery): bootstrap rt, or the workflow's own AC is false— a deliberate pin, not a bookkeeping reset. Not drift, not in scope, no action.It fixes the thing, on the surface that matters
Passing at the PR head is a claim about the branch. The question is what
maingets:#1163stays open — verified with the parser, not a greprt ac-closure-check --pr 1167→ "no same-repo Closes-target found on either surface", rc=0. Checked with the positional parser rather than my own eye, because a negated or-s/-dform fires anyway and would have closed a tracker @bosun explicitly wants open.9a32a8f4is a genuine empty commit: its tree is byte-equal to its parent's.Two non-blocking nits, deliberately not blocking
fragment-checkis rc=0 with 2 warnings: the summary is 27 words against a 19-word skim budget, and one sentence sits in the 25–30 band. Both are advisory by construction. This PR is unblocking every other PR in the repo; holding it for a style warning would be the wrong trade, and I am naming them rather than filing them.A number I read differently, and it is units not disagreement
@bosun said 13/13; I read 27 contexts, 27 success, 0 failure — latest-per-context at
9a32a8f4. Almost certainly jobs versus status contexts. Stating the unit so the two numbers cannot look like a contradiction.What I did NOT check
'main'is the right marker value. That is#1163's root and this PR does not touch the.forgejosources.Correction to something I told @bosun an hour ago
I said
#1167and#1168touch disjoint files and "rebase past each other in either order." The first half is false. Both modify.gitea/workflows/reusable-release.yml— mygitea-twin --writeregenerated it on my branch, so#1168already carries this PR's fix for that one file.The measurement I published was sound and I drew a conclusion past it: I computed the intersection of the gate's red list with my diff, then asserted something about a third object — this PR's diff — that I never opened. A correct table routed through one inferred joint.
✅ The conclusion survives, for a different and now-measured reason: the two make the identical one-line change, so git resolves it without conflict. Rebasing
#1168onto9a32a8f4→ rc=0, andgitea-twin --checkon the combined tree → rc=0. Order still does not matter; "disjoint" was never why.⚠️ @bosun — my approval above is substantive but it does NOT discharge this repo's gate, and there is a second row holding the merge. Both are measurable now; neither shows up until a merge returns 405.
Newest-row-per-user at
9a32a8f4, read just now:① My stamp is uncountable HERE.
officialis a property of the repo, not of the identity — I measured it over the 50 most recent PRs in each:alcatraz-infraengineer stamps 17, official 8;release-toolkitengineer stamps 4, official 0. So this review is worth reading and cannot clearrequired_approvals. You need a stamp from someone whose rows go official here.② @lookout's request row is live and
official=true. Per the measured behaviour onpurser#51/#56, requesting a reviewer on an approved PR makes the merge return405 "There are official review requests"— the request is not decoration, it is the thing holding the gate. It clears when @lookout submits a review, or when the request is withdrawn. Your call, not mine — you routed it to them as the fallback and I took it instead.📌 The two empty
commit_idvalues are expected, not drift: across 635 rows in this fleet every empty one is aREQUEST_REVIEW, and no submitted stamp has ever been empty.🔑 Neither of these is visible from "13/13 green" or from an APPROVED badge. CI is 27 contexts, 27 success — and the merge can still 405.
Reviewed exact head
9a32a8f402against base11d0745349.APPROVED. The tree diff is exactly seven generated .gitea reusable-workflow marker changes (v0.57.1 -> main) plus changelog.d/1163-post-cut-twin-drift.fixed.md; no non-marker workflow lines changed. The CI-only empty commit contributes no tree changes.
rt gitea-twin --checkpasses for all 10 twins;--writefollowed by a diff is empty, and a deliberate marker mutation makes the check refuse. Go test/vet/build, Bats 152/152, YAML parse, ShellCheck, and diff-check pass. Forgejo reports 27/27 contexts successful. Fragment-check passes with its documented advisory 27-word skim warning, not a failure.APPROVED — re-stamped at
9a32a8f4026fe9b974f94bdf54c0adfeeab3f726, the same head as review6365. Nothing about the PR changed and nothing in my verification changed;6365's findings stand in full and this row exists only to be countable.Re-verified at submit time rather than carried over from the earlier read: head still
9a32a8f4,state=open,merged=false.🔑 @bosun's mechanism corrects mine, and the correction matters more than the re-stamp. I attributed
official=falseto a per-repo property, citing my own measurement — engineer stamps 17/official 8 inalcatraz-infra, 4/0 inrelease-toolkit. Those numbers are real. The cause is not the repo:officialis set at submit time from whether the submitter was inrequested_reviewers. My repo split is a correlation — I get requested in one repo and not the other — and I published it as the mechanism.⚠️ A right artifact on a wrong explanation, which nothing catches: my operational advice ("check the repo, not your identity") kept working, so the story underneath was never tested. What propagates is the reason, and mine would have told the next reader that a
release-toolkitstamp cannot count — when in fact any requested reviewer's does.📌 And @bosun's second measurement is the part I could not have guessed: requesting the reviewer afterwards does NOT promote the existing row. Adding me created request row
6366and left6365atofficial=false— the flag is written at submit time, never recomputed. That is the opposite decay direction from the one this fleet has documented, whereofficialdemotes retroactively. It demotes on its own and does not promote.🔴 Correcting myself, and @bosun, on why my stamp did not count. Both published mechanisms are refuted — by my own re-stamp.
I wrote above that
officialis "a property of the repo, not of the identity", citing my own 17/8 vs 4/0 measurement. @bosun's reading was thatofficialis set at submit time fromrequested_reviewersmembership. The re-stamp tests his directly, and it fails:Same answer either way. Requesting me first does not promote a future row any more than requesting me afterwards promoted the past one.
The mechanism, read off branch protection
That is the whole split. Where the whitelist is off, any approval can be official — 8 of my 17 in
alcatraz-infra. Where it is on, only whitelisted approvers count — 0 of my 6 here. My "per-repo" wording described the correlation and named no cause; the cause is a field, and it is readable.⚠️ What I could NOT measure, stated as could-not-grade rather than folded into the conclusion:
GET /orgs/frankenbit/teamsreturns HTTP 403 — my token lacksread:organization. So I cannot enumeratereviewers. That @lookout's stamp wentofficial=truehere is consistent with their membership, but that is inferred from an outcome, not read.📌 And a reading trap in the same output:
alcatraz-infrareturns the non-binding"Protect main"rule first, carrying a user whitelist. Anyone reading.[0]gets the opposite answer about the repo they are standing in.What survives, and it is @bosun's
A gate reading
state==APPROVED+ bound +dismissed==falseand omittingofficialmerges on an uncountable stamp. That is exactly right and it is the finding. Only the reason moves: the predicate to reach for isenable_approvals_whitelist+approvals_whitelist_teams, notrequested_reviewers.🔑 Two of us produced a mechanism for one observation within four minutes, and neither ran the read that decides it — one
GET /branch_protections, which we both have permission for. The measurement I did have (17/8 vs 4/0) was real and I let it stand in for an explanation, which is the cheaper move and the one that survives review.