Skip to content

release-train: staging -> main - #318

Merged
tracebloc-release-train[bot] merged 22 commits into
mainfrom
release-train/to-main
Aug 25, 2026
Merged

release-train: staging -> main#318
tracebloc-release-train[bot] merged 22 commits into
mainfrom
release-train/to-main

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed release-train/to-main branch (a mirror of staging), so it never collides with a human PR. Merged only when the fr-gate is green.


Note

High Risk
Changes fleet CI authentication scopes, kanban board writes, and org conformance inventory—mis-scoped tokens or promotion logic can fail silently or block merges across many repos.

Overview
This promotion bundles org-wide GitHub Actions hardening and kanban automation fixes in tracebloc/.github.

Kanban triage: The reusable customer-priority-bump.yml gains a bug-to-ready job so adding work-type:bug promotes a card from BacklogReady (with monotonic board checks, GraphQL fail-closed behavior, and retries for add-to-kanban races). A thin customer-priority-bump-caller.yml wires the trigger for this repo; docs note three repos still lack the caller.

PR linking:set-pr-status.yml adds a separate closing-ref job running closing-ref-gate.py, which flags PRs whose title names a ticket but whose body does not link it via closing keywords (fixing silent kanban closure misses). The PR template checklist is updated to match.

Credentials: Several workflows now mint narrow tracebloc-release-train App tokens (documented per call site), including kanban-closure-router keeping contents: read for cross-repo .kanban.yml fetches. merge-settings-drift and bricked-prs scopes were measured rather than guessed.

Governance & inventory: CI runs make reason-citations against live repo-inventory.yml ticket citations; repo-inventory.yml flips many callers to required (e.g. stale-backlog, customer-priority-bump), adds bugbot-gate rollout exemptions, design-system-v2, and corrected rfcs / claude-skills rows. New branch_owner.py defines branch ownership by PR author, not tip commit. org-standards.md points branch models at the inventory and documents the bug-label automation caveat.

Reviewed by Cursor Bugbot for commit 416da9b. Bugbot is set up for automated code reviews on this repo. Configure here.

LukasWodkaand others added 6 commits August 23, 2026 14:06
…lands last (#293)
* ci(1979): arm the stale sweep — this repo joins the wave, and the flip lands last
The final PR of the backend#1979 wave, and the last exit-criterion work for
backend#1680.
Two things, in one PR because they cannot be separated:
1. THIS REPO GETS ITS CALLER. It was deliberately left out of .github#288
(Bugbot): `exempt` plus a caller on the audited branch is itself a
finding (caller-drift.py:2260), so shipping the caller with the reusable
would have gone green on a repo the audit was told to skip.
2. ALL 17 MIGRATION ENTRIES FLIP `exempt` -> `required`, and the
`stale_backlog_migration_in_flight` anchor is DELETED rather than left
behind. Its own text says so: "if this anchor is still here after that,
it has become the same finding as wip_limit_check_has_no_callers -- a
reusable that shipped and was never wired up, with a written excuse."
`release-train` and `rfcs` keep their exemptions untouched, because those
rest on different reasons that are still true: release-train because
enrolling the repo that drives promotions into an unattended closing sweep
is a decision somebody should take deliberately (still UNDECIDED, and the
note says so), and rfcs because it holds decision documents rather than a
backlog -- an RFC nobody has acted on is a decision nobody has taken.
ORDERING, which is the whole reason this is a separate PR: flipping to
`required` before a repo caller lands would redden the org audit for every
repo still waiting -- the drift window made to look permanent, which is how
a tier teaches people to ignore it. So this merges LAST, after the 16
caller PRs.
Verified: 17 entries required and 2 deliberately exempt (asserted by
reading the parsed inventory, not by counting the diff), the spent anchor
at zero occurrences, `make check` green (41/41 house-rules cases), and
caller-drift-selftest 197/0.
backend#1979
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci(1979): move .github's own caller to its own PR, so this one can go green
Bundling the caller with the flip deadlocks: the audit reads caller presence from
the audited branch via the API, so `.github`'s caller is absent from `develop`
until it merges, and `gate` is a required context. The caller now lands in
.github#307; this PR is purely the 17-entry flip.
Refs backend#1979
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…he fleet again (backend#2284) (#312)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#2348) (#313)
* feat(2348): a bug-labelled issue lands in Ready, not Backlog (backend#2348)
The org rule -- "bugs get `work-type:bug` and go straight into `Ready`
(defects skip refinement)" -- was written in org-standards.md and
implemented nowhere: add-to-kanban puts every issue at `Backlog` and no
workflow read the label. Measured 2026-08-22: seven bug-labelled tickets
in one day, all seven left in `Backlog`.
Implemented as a job on `customer-priority-bump.yml`, the only reusable
already triggered by `issues: labeled` and already called by 16 of 19
repos -- so it reaches the fleet with no per-repo edit and touches none of
conformance-gate's GUARDED contract files.
MONOTONIC: promotes only from `Backlog` or from no Status, and asks the
BOARD for the direction (col_index byte-identical to the closure router's)
rather than holding a rank table. An inverted Backlog/Ready order refuses
rather than performing a demotion.
FAILS CLOSED like advance-deploy-env, not like the closure router: an
unreadable board, a truncated page, an unplaceable column or a card that
never reaches the project are all RED. Declining quietly here would
reproduce the very defect being fixed -- and a `labeled` event fires once,
where a cron gets another go next week.
Tests: 46 assertions, extracted from the YAML by `# selftest:` markers and
run verbatim; the Status vocabulary is derived from advance-deploy-env's
rank() and cross-checked against the sibling suite, so all twelve columns
are driven. 21 mutations, 0 stale, 0 uncaught. `make check` green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2348): the Status write goes through the same errors[] rejection as the reads (backend#2348)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The develop-first rule named `docs` as its sole exception. `rfcs` has the same
shape and the same reason -- it publishes no artifact, has no deploy stages, and
is not on the release train -- so a promotion gate there has nothing to gate.
Decided on backend#2347.
This line was the last place still asserting otherwise. `repo-inventory.yml` has
described `rfcs` as a single-branch repo all along: `release_train: false`, so
`audit_branch: develop-first-on-train` already resolves to the default branch,
and the `develop` role carries `exempt: *no_develop_branch_single_branch_repo` --
"single-branch repo: `main` is the only integration branch, so there is no
`develop` to protect. Structural, not a gap."
So the machine-readable side needs no change; the prose was the drift. Stated as
a class with its members named, and pointed at the inventory, because a bare list
drifts and a bare class invites argument.
Why nobody caught it: the conformance guard flags a stale exemption only when the
role's branch appears AND carries protection. `rfcs/develop` existed and was
completely unprotected, so the contradiction between "there is no develop" and a
live develop branch was invisible by construction. rfcs#45 deletes that branch and
makes reality match what this file and the inventory both now say.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…scribing two repos (#306)
* fix(2242): say why rfcs is exempt, accurately, and stop one anchor describing two repos
The `rfcs` block asserted "this repo has no `develop`" in two places. It has one,
and it takes merges. Corrected both, without touching the `required` flip that is
still blocked on the callers reaching rfcs' default branch.
Refs backend#2242, tracebloc/rfcs#41
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2242): name the surviving precedent, not the anchor #293 deleted (backend#2242)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit cff3b74. Configure here.

@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Aug 24, 2026
LukasWodkaand others added 7 commits August 24, 2026 10:35
…its caller (#320)
The conformance audit went red and blocked the .github prod promotion (#318):
release-train: customer-priority-bump.yml is marked `exempt` but a caller
exists (customer-priority-bump.yml). The exemption is stale - promote it to
`required` or delete the caller.
backend#2396 wired that caller in release-train this morning (c0c6cf7, 'so a bug
lands in Ready'); this file still claimed the repo had none. The audit is right
and the inventory was wrong.
Flipped release-train's entry to `required`. The shared anchor STAYS: it is still
cited by two other repos that genuinely have no caller, so deleting it would be
wrong -- this is the opposite of the kanban_closure_router case documented a few
lines below, where the anchor is cited once and a flip orphans it.
Also corrected the citation count in that neighbouring comment from 3x to 2x, and
recorded why the anchor survives. That count is the only thing telling the next
person whether a flip orphans the anchor, and nothing checks it -- no check flags
an orphaned YAML anchor, as the same comment says. A stale count there is how the
next flip deletes a live anchor or keeps a dead one.
Sequencing is per this file's own note: land the caller first, flip the entry
after. The caller is already on release-train's develop.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#314)
* ci(2364): a PR whose title names a ticket must link it (backend#2364)
Merging a fix never closed or advanced its ticket. `closingIssuesReferences`
was 0 on 7 of 7 sampled merged PRs (release-train#109/#108, .github#304/#300,
backend#2266, client#774, docs#131). The house convention puts the ticket in
the PR TITLE; GitHub creates a closing link ONLY from a keyword in the BODY, so
a title reference is inert. `kanban-closure-router.yml` fires, finds no linked
issue, and correctly does nothing -- every kanban workflow green, every card
unmoved.
Adds a `closing-ref` job to the EXISTING `set-pr-status.yml` reusable: parse the
real title, assert the real `closingIssuesReferences` contains what it names.
Derived, not restated (rule 1): two live reads, no list of tickets, repos or
authors. The four title forms are measured, not imagined. A bare `#N` outside
parentheses is deliberately NOT read as a ticket -- backend#2309's `#2271` is
prose about a PR, and scanning loose `#N` would redden a compliant PR.
Fails closed (rule 3): a blank title, a GraphQL error, `pullRequest: null`, a
null/ownerless node, or `totalCount > len(nodes)` all exit 2 as "cannot tell",
never a pass and never a finding against the author. The truncation test is
load-bearing beyond pagination -- a link to an issue the token cannot read comes
back missing from `nodes` while `totalCount` still counts it, which is
indistinguishable from "not linked".
The cross-repo trap is its own verdict: `WRONG_REPO` is reported apart from
`MISSING` because the remedies differ -- a bare `Closes#304` in `.github` links
`.github#304`, closing the wrong issue on merge, and needs the line rewritten
rather than added.
Fixtures are measured bytes (the backend#2114 lesson), captured with
`gh api graphql` and re-verified against the live API before commit.
Tests: 102 selftest assertions; 34 mutations, 0 stale, 0 uncaught. The mutation
harness edits the real gate and re-runs the real suite -- no inline copy of any
rule (rule 9, .github#114/#115). Every anchor must match exactly once, which is
the assertion that it actually applied. Refusals are asserted by their own
message, never a catch-all (rule 10). The commit-type vocabulary is derived out
of org-standards.md and the derivation fails closed if it finds nothing (rule 6).
Arming: `closing-ref` is a required status check NOWHERE -- measured across 19
repos x develop/staging/main/master x both classic protection and rulesets -- so
a finding blocks no merge (rule 4). Callers trigger on
opened/reopened/ready_for_review/converted_to_draft, not `synchronize`, so the
13 open PRs that would report a finding are not reddened by a push.
Touches no file in `conformance-gate.yml`'s GUARDED list, and needs no
`repo-inventory.yml` row: the inventory tracks callers, one row per reusable,
and `set-pr-status.yml` already has its rows.
Closestracebloc/backend#2364
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2364): the remedy stops guessing a repo it cannot know (backend#2364)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2364): the scope pattern admits a leading dot too, so .github stops depending on a coincidence (backend#2364)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…restating it (backend#2422) (#321)
The bullet named its own exceptions -- "`docs` and `rfcs` are main-only" --
one sentence after naming `repo-inventory.yml` as the authority and saying
"this line follows it, not the reverse." It then drifted from that authority
on BOTH examples:
* `docs` has been on the release train since 2026-08-04 (release_train:
true, develop: required, staging present) while this line called it
main-only.
* `rfcs` had a `develop` taking merges, measured 2026-08-22 in backend#2242
/ .github#306, while this line called it main-only.
That is the restated-rule defect the file's own meta-rule warns about, and it
is not theoretical: I read this line on 2026-08-19, concluded `rfcs` should be
main-only, created a `develop` there to satisfy the first bullet, and forked
the repo -- 9 commits on develop against 5 on main over four days, both sides
editing README.md, reconciled by hand in rfcs#50.
So the enumeration is removed rather than corrected. A list of repo names in
prose will drift again; a pointer to the file that is measured will not.
Also records the trap the inventory found and no check catches: a `develop`
created on a non-train repo and left unprotected is invisible to the guards
(`develop_unprotected_non_train` -- "a `develop` created and left UNPROTECTED
is not flagged ... no check was going to surface it"). Creating one to satisfy
the train bullet forks the repo silently. Stating it here because the guard
cannot, which is exactly what this file is for.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…f the flip (backend#2396) (#316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ey emptied (#319)
* fix(2396): release-train rides the label trigger, so its exemption stops being stale (backend#2396)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci(2396): flip .github and rfcs to required, and delete the anchor they emptied
Both callers are live on their default branches (.github#316, rfcs#47), so the
`exempt` rows became the stale-exemption finding caller-drift.py:2309 exists to
catch. Flipping them takes the anchor to zero citations, so its definition is
deleted in the same PR rather than left as dead prose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…p commit (backend#2365) (#323)
* fix(scripts): branch ownership comes from the PR author, never the tip commit (backend#2365)
A "my branches" list built from `%(authorname)` on the ref answers "who touched
it last", not "whose work is this". Pushing a review fixup onto someone else's
branch rewrites the tip author and changes nothing about who owns it -- and the
next command in that workflow is `push origin --delete`. Measured: `client`
PRs #395 and #592 are @shujaatTracebloc's and both showed Lukas as tip author.
backend#2001's per-person table is NOT affected -- checked first, and it
attributed by PR author already (`inventory.py`: "merged/closed PR author wins,
else the tip's git identity"). The defect is that the rule lived in a throwaway
script, so every later query re-derives it and the obvious derivation is wrong.
`branch_owner.py` is the one place that answers it: PR author first (exact head
oid, then head name), the oldest commit not on the default branch for a branch
with no PR, and `unattributable` otherwise. `attribute()` takes no tip-author
parameter, so no path can return one. Fails closed -- an unread or truncated PR
list, an ambiguous head name and a deleted PR author all refuse rather than
guess -- and `origin/HEAD` is verified against the remote before the
first-commit signal is used, because a stale default corrupts that signal
exactly.
`CreateEvent` is not used for the no-PR case despite the ticket proposing it:
/repos/{repo}/events caps at 300 events, which on backend spans under four hours
and holds two branch creations.
71 assertions and 26 mutations, wired into the already-required `selftests`
context; three mutations were uncaught on the first run and the tests were
strengthened rather than the mutations dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(scripts): a withheld check is not a finding, and an absent tip is not a mismatch (backend#2365)
Both of Bugbot's findings on #323, and both are the same shape as the defect
already fixed in the first commit: a message asserting a comparison that never
ran. Reproduced by construction before changing anything.
1. The multi-author reuse refusal ended "and none is at the current tip" even
with no tip in hand -- reachable for a branch named on the command line that
this clone has not fetched, whose head name two people have used. The single
-author `pr` arm already split those two sentences; this arm now does too.
2. When the default branch could not be confirmed, `main` withheld the
first-commit signal by passing "" and `attribute` then reported "no commit on
this branch that is not already on the default branch" -- a fact about the
branch, from a check nobody ran. Appending the reason did not retract it.
`attribute` takes `first_commit_problem` now, so a withheld signal is reported
as unmeasured with its reason, and the measured-and-empty wording is pinned
apart from it.
77 assertions, 29 mutations, 0 stale, 0 uncaught.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(scripts): a branch list that could not be read is not an empty clone (backend#2365)
Bugbot's medium-severity finding on 9102520, and it is the sharpest one yet
because it is the fail-closed rule turned on this module's own third seam.
`remote_branches` collapsed a failed `git for-each-ref` into `[]` with no problem
string, and `main` printed that as `0 branch(es)`, exit 0 -- a clean bill of
health from a read that never happened, while the other two seams
(`pull_requests`, `default_branch`) refuse explicitly. "I could not read the
branch list" and "this clone has no branches" are different answers and only one
of them is a fact about the clone.
It returns `(refs, problem)` now and `main` refuses with exit 2, because without
the ref list there is no tip for any branch and no way to tell whether a named
one exists -- so every verdict below it would be weaker than it looked. A
genuinely empty remote still reports no problem, pinned by its own case so the
two cannot collapse back together.
Bugbot's other comment on that head is stale: it restates the absent-tip finding
already fixed in 9102520 and its own footer names commit 6f45cdc. Verified by
construction on the current tree -- the refusal reads "and no tip was supplied to
break the tie with".
80 assertions, 30 mutations, 0 stale, 0 uncaught.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(scripts): audit the other reads, and pin both empties apart at the seam and in main (backend#2365)
Saqlain's requested change on #323 landed in 7a87b83, before his review arrived.
This is the rest of what he asked for, and it found a fourth instance he had not
seen.
AUDITED ALL FOUR READS rather than fixing only the reported one. `pull_requests`
and `default_branch` already refuse with a problem string. `remote_branches` was
fixed in 7a87b83. `first_commit_author` had the SAME defect and worse: its
condition was `if rc != 0 or not out`, conflating a FAILED `git log` with a
branch that genuinely has no commits off the default branch, and `attribute` then
reported the second one -- "no commit on this branch that is not already on the
default branch" -- for both. A fact about the branch, from a read that never ran.
It returns `(author, problem)` now, and `main` carries that reason through.
THE TWO EMPTIES ARE PINNED APART IN BOTH DIRECTIONS, for both reads: collapse the
failure into the success and the mutation reddens; report the genuine empty AS a
failure and it reddens too. Asserting only "something empty came back" would have
passed in a world where both refuse identically.
AND `main` IS NOW TESTED, which is what made this worth doing. Dropping the one
argument that carries a seam's problem into the rule reverted the whole fix while
every isolated case stayed green -- the mutation run caught it as UNCAUGHT, so
the entry point gets driven directly: a failed history read renders as
unmeasured, a genuine empty renders as no-unique-commits, a failed enumeration
exits 2 with no rows, and a genuinely empty remote exits 0.
92 assertions, 35 mutations, 0 stale, 0 uncaught.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(scripts): a default branch the clone never fetched is one fact, not N failed ranges (backend#2365)
Bugbot's `Failed first-commit read looks empty` names the seam already fixed in
6665d13, and two sub-cases with it. Both were verified by construction: an
unfetched default branch and a CLI-named branch absent from the clone now report
"the oldest-commit signal was not measured", naming the failed command, rather
than "no commit not already on the default branch".
The root cause behind the first one was still open, though. `default_branch`
trusted `gh` for the NAME and never asked whether this clone has the ref, so a
checkout that never fetched the remote's default produced one failed
`default..branch` range per branch -- honest since 6665d13, but blaming N ranges
for one missing ref, and unreadable on a repo with hundreds of branches. It is
verified with `rev-parse --verify` now and reported once, as its own fact, with
the fetch that fixes it.
95 assertions, 36 mutations, 0 stale, 0 uncaught.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2700b9e. Configure here.

@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Aug 24, 2026
LukasWodkaand others added 5 commits August 24, 2026 15:47
…ured" (backend#2157) (#324)
* fix(2157): scope the four kanban mints whose reason was "not yet measured" (backend#2157)
Four of `mint-scope.py`'s ten exemptions said, in as many words, "scopes not
yet measured". This measures them and drops the rows.
Each scope set is derived from the workflow's OWN calls -- every `gh api`,
`gh issue`, and GraphQL query/mutation the App token touches -- and from
GitHub's documented per-endpoint requirement, not copied between files:
advance-deploy-env issues:read pull-requests:read org-projects:write
customer-priority-bump issues:write
fr-pass-comment issues:write pull-requests:read org-projects:write
kanban-closure-router issues:write pull-requests:read org-projects:write
`contents:write` -- the grant that makes a leaked token able to push -- drops
from all four, along with administration/actions/checks read.
Two derivations are worth naming because they are the ones a reader would
guess wrong:
* labelling a PULL REQUEST (kanban-closure-router's sibling-merge marker)
needs only `issues: write`. The label endpoints document "at least one of"
Issues write / Pull requests write, and every shared action on a PR is
served by the Issues endpoints. So pull-requests stays at READ.
* the issue-comment REACTION endpoint (fr-pass-comment's thumbs) documents
`Issues: write` ALONE -- no pull-requests alternative -- which is what
forces a write grant in that file rather than a read one.
`repositories:` is left unnarrowed everywhere: advance-deploy-env's
closing-issue advancement and kanban-closure-router's closer lookup are
deliberately cross-repo, and repo narrowing is measured in this org for a
READ and not for a WRITE.
Six exemptions remain, each for a stated reason rather than for lack of time,
and the note above EXEMPT now says which is which.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2157): the board mappers read another repo over the API, so contents:read stays (backend#2157)
Bugbot High, confirmed by saadqbal: the router mint dropped contents
entirely while branch_status_map.py fetches the CALLER repo .kanban.yml
with that token. The mapper refuses on any non-404 fetch failure and the
call sites fall to the holding state, so a 403 would park every merged PR
at override-unusable with a green job.
advance-deploy-env.yml has the same three call sites and was flagged by
nobody; scoping one and not the other would have shipped the same silent
mis-routing through the other writer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2157): drop the contents grant advance-deploy-env never uses (backend#2157)
saadqbal on .github#324, and he is right. I added contents:read there by
analogy with kanban-closure-router without checking the second instance.
advance-deploy-env has ONE mapper call, at :94 in the env step, which runs
BEFORE the mint and under github.token -- the comment at :169 already said
so. It also maps $GITHUB_REPOSITORY, its own repo, where the router maps
$REPO_FULL, the caller.
Same script, different token, different repo: the scope is required there
and inert here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…red (#325)
`repo-inventory.yml` marked `advance-deploy-env.yml` and
`kanban-closure-router.yml` as `exempt` under `rfcs`, while both callers
exist on the branch the guard reads. `caller-drift` reports it as two
stale-exemption findings, which fails `conformance-gate` -- a required
check -- and blocks .github#318, the staging -> main promotion.
Both exemptions were explicitly STAGED, not structural, with a written
flip condition: "once rfcs#41 has reached rfcs' DEFAULT branch, which is
`main`". That condition is met. rfcs#41 merged to `develop` 2026-08-23
and rfcs#45 reconciled it into `main` 2026-08-24, so both callers are on
`main` -- which is the audited branch here, because `rfcs` is
`release_train: false` and `audit_branch: develop-first-on-train` falls
back to the default branch for a repo the train does not promote.
The `kanban_closure_router_caller_missing` anchor is deleted in the same
commit: this was its only citation, so the flip would otherwise leave it
defined and uncited, and no check flags an orphaned YAML anchor. Both
staging notes are deleted rather than reworded, per their own clause that
a sequencing note outliving its landing "has stopped being a sequencing
note and become a written excuse".
No check logic changed -- caller-drift.py derives caller presence from
each repo's `uses:` values, so this is data catching up to reality.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Their exemption reason was that branch protection can return LESS to a
narrower token rather than erroring -- a 200 with a field missing, which a
checker reads as "not configured" on a green run. Right worry, so it was
measured instead of argued, and it landed on the other workflow.
bricked-prs: protection does NOT degrade silently. Without administration:read
it answers 403 and read_protection() reports an error. Narrowed to five READ
scopes; bricked-prs.py over four repos gives output identical to the full
grant. actions:read is required and non-obvious -- it is what lets GraphQL
resolve statusCheckRollup -> commit.status on a private repo.
merge-settings-drift: reads no branch protection at all. It reads GET /repos,
and that endpoint IS the silent one -- the four merge-setting fields are
returned only to a caller with PUSH access, 200 either way. contents:write is
the one grant it measurably cannot lose; five others go.
Every dropped permission was re-added one at a time to prove it load-bearing.
Refs tracebloc/backend#2157
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ckend#2449) (#329)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@tracebloc-release-traintracebloc-release-trainBot added the gate-nudge Toggled by the release train to (re-)fire the fr-gate label Aug 25, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1212781. Configure here.

@tracebloc-release-traintracebloc-release-trainBot removed the gate-nudge Toggled by the release train to (re-)fire the fr-gate label Aug 25, 2026
…h them (#332)
* fix(2498): rfcs' two stale exemptions, and the reason that rotted with them
`caller-drift` reported 2 findings on the prod mirror .github#318, both on
`rfcs`, and both the same shape as the ones fixed on 2026-08-24: the fleet moved
and `repo-inventory.yml` did not.
* `code-quality.yml` was `exempt: *code_quality_caller_missing`, and the caller
is NOT missing -- `code-quality-caller.yml` is on `rfcs/main`, which is the
branch this guard reads for a `release_train: false` repo. Measured, because
the branch decides the answer: it is NOT on `develop`, so checking the wrong
ref reads the opposite way.
* `protection.develop` was `exempt: *develop_unprotected_non_train`, whose text
says `develop` "carries no branch protection". Measured today it matches the
develop baseline on EVERY key -- classic_protection, min_reviews 1,
bypass_reviews [], strict false, force-push and deletion blocks,
conversation resolution, and exactly the three `quality / *` contexts -- so
it is `required` with no `divergent` cell. `enforce_admins` is false and the
baseline does not assert it on develop (backend#1276 D1), which is
conformant rather than divergent.
That second one is the anchor's own warning coming true. Its text says a
`develop` created and left UNPROTECTED "is not flagged ... no check was going to
surface it" -- and none did. The row went stale silently and only the promotion
audit caught it.
AND THE THIRD EDIT, which no finding asked for. The `prod` divergent cell drops
the three `quality / *` contexts "BECAUSE the producer is absent". Flipping the
caller row makes that sentence false, so it is corrected here rather than left
behind a fixed field. The cell itself stays `[]` -- that IS the measured state,
`rfcs/main` requires ZERO contexts -- so only the justification had rotted, which
is exactly the failure backend#2449 added the citation guard for, one field over.
Left as a named open question rather than decided quietly: now that the caller
exists and reports those contexts on every `rfcs` PR, `main` COULD require them.
That is a protection change, not an inventory correction.
Verified: `caller-drift-selftest.py` 197 pass / 0 fail, and a live audit against
the whole org now reports "No drift. Every repo read, every entry matched."
Closes #2498
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2498): stop asserting a citation count that has now rotted twice
Bugbot, and saadqbal reviewing .github#332.
The `code_quality_caller_missing` anchor said "Down to THREE citations
(claude-skills, release-train, rfcs)". Flipping `rfcs` to `required` leaves TWO
-- claude-skills and release-train -- so the count and the named list are false
the moment this PR merges.
THE FIX IS TO DELETE THE TALLY, NOT TO DECREMENT IT. That sentence has now been
wrong twice in the same way: it previously said THREE and named
claude-skills/docs/release-train while FOUR entries pointed at the anchor, and it
says THREE and names `rfcs` after this PR removes it. Its own last line already
told the reader to trust `grep -n` over the sentence -- and the list outlived the
advice. Nothing checks a prose count: `reason-citations.py` verifies that an
issue cited in a reason is still OPEN, not that a tally is still right.
So the roster is derived, and the grep that derives it is written down. The
remediation history stays, because that is measurement rather than a tally --
model-zoo/start-training (backend#1420), docs, and now rfcs (backend#2498), each
having gone stale the same way.
A PR whose thesis is "stale prose rots silently" should not ship new stale prose
in the file it is fixing, which is saadqbal's point and it is a fair one.
AND THE OPEN QUESTION IS NOW A TICKET (backend#2501), not a comment. Naming it in
this file is the same mechanism that just failed; a cited issue is at least
checkable by `reason-citations.py`. The comment now points at it.
Verified: caller-drift-selftest 197/0, reason-citations clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2498): anchor the derivation, which over-counted exactly what it replaced
saadqbal and Bugbot on .github#332, and the defect is the one this hunk was
written to remove.
The anchor now tells a reader to derive the roster with
grep -n "exempt: *code_quality_caller_missing" repo-inventory.yml
and run verbatim that returns FOUR lines: two real aliases (claude-skills,
release-train) and two COMMENTS that mention the anchor by name. So the
prescribed derivation over-counts by exactly the two entries just removed from
the sentence, and a reader following it lands back on "three or four" -- which is
where this sentence started.
Anchored to value position at line start, it returns 2:
grep -nE '^\s*exempt: \*code_quality_caller_missing\s*$' repo-inventory.yml
The class is worth naming, because saadqbal counts this as the fifth instance
today of a check or instruction that greps source text and cannot tell code from
commentary (client#808, e2e-test-agent#233 twice, tracebloc-engine#732, this).
A derivation is only better than a tally if it derives the right set.
Verified on this head: unanchored 4, anchored 2. Selftest 197/0, reason-citations
clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(2498): enter design-system-v2, the repo that appeared mid-review
The `audit` failure on this PR was never about this PR's diff.
`tracebloc/design-system-v2` was created 2026-08-25 11:35, WHILE this was in
review, and `caller-drift` went red on
design-system-v2: active in tracebloc but absent from repo-inventory.yml
That is backend#2498's second half in one sentence: the audit reads the LIVE
FLEET rather than the diff, so `.github` can only merge at an instant when every
repo in the org is conformant -- and the org does not hold still.
IT COULD NOT BE ENTERED WHEN IT FIRST APPEARED, which is why this took two
attempts and one revert. While the repo was empty the guard refused to audit it
at all ("An empty repo is a legitimate state, but it is not one this guard can
audit"), so adding an entry only moved the finding from `absent from
repo-inventory.yml` to `repo has no branches`. Neither is fixable from this file.
The entry became possible only once the repo got its first branch -- the right
order, since the inventory records measured reality and there was nothing to
measure.
Measured on `develop`, its DEFAULT and only branch: private, unprotected, no
`staging` and no `main`, ten org callers present (version-bump-gate,
advance-deploy-env, code-quality, customer-priority-bump, fr-gate, stale-backlog,
fr-pass-comment, kanban-closure-router, set-pr-status, plus the add-to-kanban
copy), the three unrolled-out ones absent, and both quality files present.
Verified: live org audit now reports "No drift. Every repo read, every entry
matched." Selftest 197/0, reason-citations clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Aug 25, 2026
LukasWodkaand others added 3 commits August 25, 2026 17:24
…y so (backend#2284) (#333)
claude-skills#36 merged at 13:46, putting the first bugbot-gate caller
anywhere in the org onto claude-skills/develop. That makes its exempt row
stale (caller-drift.py:2309) and falsifies the anchor in the same move.
The anchor claimed "bugbot-gate has ZERO callers anywhere in the org".
True when written on 2026-08-23, false from 13:46 today -- the reason
rotted at the moment the thing it described changed, which is the shape
backend#2449 exists for.
Its replacement derivation is anchored (backend#2507): the bare identifier
also matches the paragraph describing it, so an unanchored grep would
count its own documentation.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…quired (#335)
* fix(inventory): claude-skills' code-quality caller, and the reason it falsifies
REBASED ONTO develop AND NARROWED. This branch originally also flipped
`claude-skills`' `bugbot-gate.yml` row and re-measured the
`bugbot_gate_rollout_pending` anchor. .github#333 (backend#2284) landed both while
this was open, and did the anchor better -- it names `claude-skills#36` as the
first caller. Those edits are dropped rather than merged: two PRs asserting the
same fact in different words is how the next reader learns to distrust both.
What is left is the half #333 did not cover, confirmed by running the audit
against current develop:
1 drift finding(s):
- claude-skills: code-quality.yml is marked `exempt` but a caller exists
So:
1. Flip that row to `required`. `code-quality-caller.yml` is present in
`tracebloc/claude-skills/.github/workflows`.
2. Fix the reason the flip falsifies. `claude-skills` prod `required_checks` said
the three `quality / *` contexts are dropped "BECAUSE the producer is absent",
citing the very exemption being removed. The measured reason is narrower:
`claude-skills/main` carries no required status checks at all (classic
protection, empty `contexts`, measured 2026-08-25), because it is non-train
infra whose `main` is a working branch. `required_checks: []` was and stays
correct; only its cause was wrong.
`release-train` carries the SAME wording and keeps it -- verified it has no
code-quality caller, so its producer really is absent. One comment became
false; the other did not.
`caller-drift.py` against the live org: 1 finding before, "No drift. Every repo
read, every entry matched." after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(inventory): the anchor's own counts, falsified by this PR's flip
Bugbot Medium on .github#335, and correct. Removing the `claude-skills` alias
made the `code_quality_caller_missing` anchor's own prose false:
said: FOUR lines -- two real aliases and two comments
measured: THREE lines -- ONE real alias (release-train) and two comments
Both numbers re-measured and stated, plus the remediation history extended with
`claude-skills` (2026-08-25, .github#335, caller added by claude-skills#36) --
the thread flagged that omission too.
The anchor stays rather than being deleted: `release-train`'s citation is
genuine, verified again here (no code-quality caller in its workflows).
WHY THIS KEEPS RECURRING, since this is the third time in one PR that a flip
falsified a sentence. A count written into prose is falsified by the NEXT flip,
every time -- this anchor's text already carried that scar ("A reader following
it lands back on 'three or four', which is where this sentence started") and
acquired a fresh one anyway. The note now says so explicitly, so the next
person flipping a row knows the count is theirs to re-measure.
Not proposing a mechanism here. But a guard that parses the anchor's stated
counts and compares them to the two greps would end the class, and it is the
same shape as the citation guard .github#329 already landed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(inventory): claude-skills has no fr-gate caller, and cannot use one
UNRELATED PRE-EXISTING DRIFT, surfaced mid-review and fixed here because it is
what keeps `audit` (and therefore the required `gate`) red on this PR. Nothing in
this branch touched the fr-gate row -- `git diff origin/develop..HEAD` does not
mention it -- the audit simply started reporting:
claude-skills: MISSING required caller for fr-gate.yml on develop.
Measured: `fr-gate-caller.yml` is absent from `tracebloc/claude-skills`, while the
inventory carried `fr-gate.yml: required`. So the row asserts a caller that is not
there.
FLIPPED TO EXEMPT RATHER THAN RESTORING THE CALLER, and the reason is in the file
already: this entry's own `rulesets.promotion_merge_commit_only` exemption states
that claude-skills has "no staging branch and no promotion PRs". The fr-gate only
reports on a PR targeting staging/main/master AS A PROMOTION, so there is nothing
here for it to gate -- structurally the same case as release-train's fr-gate
exemption. Requiring a context nothing produces is the client#665 defect this file
warns about two entries above.
The remedy runs both ways and the reason says so: if the caller was removed by
mistake, restore it and flip this back. The audit reports either direction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 416da9b. Configure here.

@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Aug 25, 2026
@tracebloc-release-train
tracebloc-release-trainBot merged commit 8eab81c into mainAug 25, 2026
24 checks passed
@tracebloc-release-train
tracebloc-release-trainBot deleted the release-train/to-main branch August 25, 2026 17:53
@tracebloc-release-train
tracebloc-release-trainBot restored the release-train/to-main branch August 27, 2026 07:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LukasWodka