Skip to content

release-train: develop -> staging - #217

Merged
tracebloc-release-train[bot] merged 4 commits into
stagingfrom
release-train/to-staging
Aug 11, 2026
Merged

release-train: develop -> staging#217
tracebloc-release-train[bot] merged 4 commits into
stagingfrom
release-train/to-staging

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

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


Note

High Risk
Changes branch-protection contract enforcement and CI gate behavior org-wide; misconfigured required_checks or gate paths could block merges or allow contract edits without audit until fixed.

Overview
backend#1681 tightens the org repo contract and the drift guard so promotion branches cannot look compliant while checks only run (not block), and so the conformance gate cannot be weakened in a green-only PR.

repo-inventory.yml adds fleet required_checks baselines per branch role (develop vs staging/prod), documents narrower sets via divergent cells (e.g. model-zoo, start-training, docs, release-train), lists version-bump-pr.yml with org-wide exempt reasons, and corrects stale .github notes on armed action-pins.

caller-drift.py now unions required status contexts from classic protection (both checks and legacy contexts) and rulesets, enforces inventory required_checks as a floor (subset, not exact match), and check_source_reusables fails startup if any workflow_call in the source repo is missing from or phantom in reusables.

Conformance gate includes itself in GUARDED and caller-drift path filters include conformance-gate.yml, closing the two-PR path where only the gate file changed and no audit ran. Selftests cover required_checks and source reusable enumeration.

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

LukasWodkaand others added 4 commits August 11, 2026 11:37
…ller exists (backend#1681) (#209)
* feat(inventory): assert WHICH checks are required, not just that a caller exists (backend#1681)
The guard opened `required_status_checks`, kept `strict`, and threw the rest
away. So `required` has meant "a caller workflow exists somewhere in this repo",
never "this check can block a merge" -- and the two are not the same thing.
What that cost, measured 2026-08-11:
* `.github/staging` required exactly ONE context (`actionlint`) while the
inventory reported it conformant. Two release-train promotions merged with
the contract audit RED -- .github#203 (-> staging) and #205 (-> main), both
with `audit` and `gate` at conclusion=failure.
* `quality / gitleaks` is required on 16/16 develops but only 13/16 staging
and prod, and the three gaps are public repos.
This adds `required_checks` as a per-branch-role policy key:
* READ from BOTH protection systems and BOTH API spellings -- classic
`checks` (modern) and `contexts` (legacy), plus ruleset-supplied
`required_status_checks`. Reading only the modern spelling would report a
branch's required set as EMPTY, which is the fail-open shape this guard
exists to eliminate.
* COMPARED AS A SUBSET, not equality: backend's Django suite and cli's
golangci-lint sit on top of the baseline legitimately. The policy is a floor.
* SCHEMA-ENFORCED, so a missing value is a failure rather than a default, and
`divergent` may narrow the set only with a written reason naming it.
Baselines are the measured status quo, not aspiration: develop asserts the three
contexts already required in all 16 repos; staging/prod assert fr-gate plus the
two content gates. The real gaps become NAMED divergences instead of silence --
`docs` ([], its 2026-06-04 exemption is broader than review alone), `model-zoo`
and `start-training` (["gate / gate"], content gates missing on both promotion
branches). `quality / action-pins` is deliberately NOT in the staging/prod
baseline: 16/16 on develop and 0/32 on promotion branches is an asymmetry nobody
has decided, so the file records the question rather than blessing it.
Its first live run also surfaced three postures nobody had stated: claude-skills,
release-train and rfcs carry a `divergent` prod cell for being working branches,
which never said anything about required checks, so they inherited a baseline
naming the fr-gate -- a gate that only exists on a promotion pipeline they do not
have. Now written down. release-train states its REAL set (actionlint, mutation,
shell) rather than [], since it is the tool that performs every promotion.
Verified, not asserted:
* selftest 86 -> 97 pass, 0 fail. The load-bearing case is "a check that runs
but cannot block IS a finding"; also covered are subset-not-equality, the
legacy spelling, ruleset-supplied contexts, a divergent cell judged against
its own list (and still failing when that list is unmet), and four schema
shapes that would silently assert less than they appear to.
* LIVE against all 20 repos: "No drift. Every repo read, every entry matched."
* MUTATION: the same run with one impossible context added to the develop
baseline produces 16 findings, one per repo carrying that role, each naming
it. Green here means something because red is reachable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(caller-drift): drop a duplicated comment block (Bugbot, .github#209)
My edit adding `required_checks` to OVERRIDABLE re-emitted the explanatory
comment that was already above it. Comment-only; no behaviour change.
Selftest re-run: 97 pass, 0 fail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…d (backend#1668) (#215)
`.github`'s own entry said "action-pins itself stays unarmed until #159 reaches
main". #159 reached main and action-pins was armed on 2026-08-06 (backend#1603
step 2), but the line was never updated -- so the single source of truth denied a
control the repo actually has.
Measured on origin/develop: this repo's `code-quality-caller.yml` passes
`action-pins: true` + `action-pins-soft-fail: false`, and `quality / action-pins`
is a REQUIRED status check on develop.
The original caveat is kept rather than deleted, because it explains WHY arming
had to wait: a caller may only pass inputs the @main callee declares, or the
whole call dies with startup_failure.
Comment-only; selftest re-run: 97 pass, 0 fail.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…end#1681) (#213)
* fix(conformance-gate): the gate was not in its own GUARDED list (backend#1681)
A PR touching ONLY `.github/workflows/conformance-gate.yml` matched nothing in
GUARDED, took the "does not touch the repo contract" branch and exited 0. So the
gate could be defanged on a green check -- drop `repo-inventory.yml` from the
list, or replace the poll with `exit 0` -- and a FOLLOW-UP PR then edited the
contract against a gate that no longer guarded it. Two PRs, both green, no audit
ever demanded.
Adding the file to its own list closes that path: a change to the gate now
requires caller-drift to have PASSED on that exact head, the same as a change to
the inventory or the checker.
Verified offline against the real matcher (`grep -qxF` over the changed-file
list), both directions: a gate-only PR now yields TOUCHED=conformance-gate.yml,
an unrelated PR still yields nothing, and a near-miss path
(`workflows/conformance-gate.yml`) correctly does not match, since the comparison
is whole-line. This PR is also its own live test - it touches the file it adds,
so the gate must now demand the audit on it.
NOT claimed, and written into the file so nobody reads more into it: `pull_request`
workflows run from the PR's merge ref, so the gate evaluating a PR is that PR's
version of the gate, and caller-drift.py is likewise supplied by the head. This
family's integrity still rests on the required human review. This removes the
SILENT path; it does not make the gate self-hosting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(caller-drift): trigger on conformance-gate.yml too (Bugbot #213)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ntory list (backend#1681) (#214)
`reusables` is a hand-written list and the audit iterates it, so a reusable added
to tracebloc/.github but never added to that list was compared against no repo
and reported by nothing. That is the one direction of drift this guard could not
see: it checked that every LISTED reusable is handled, never that every SHIPPED
reusable is listed.
`version-bump-pr.yml` is how it surfaced. It exists, has ZERO callers org-wide,
and cannot be called as written -- it requires a `pr-token` secret no repo
supplies. It is Layer 2 of backend#1563, the automated post-release bump-PR
opener meant to prevent the version staleness that stalled tracebloc-py-package's
prod leg (backend#1561). It shipped, was never wired up, and nothing said so.
`check_source_reusables()` now walks the source workflow directory, parses each
file, and refuses in BOTH directions:
* a `workflow_call` workflow absent from the list (checked against nobody)
* a listed name that is not a reusable there (a rename or delete leaves every
repo's row asserting a ghost)
Deliberately a die(), not a finding: the inventory is the contract, and a
contract that does not mention half the artifacts it governs cannot be audited
against. `on:` is read as both `"on"` and `True`, because YAML 1.1 parses the
bare key as a boolean.
version-bump-pr.yml is now listed and exempt in all 20 repos behind one shared
reason that states plainly that it was never wired -- the same posture as
wip-limit-check. That turns an invisible gap into a written decision: wire it or
delete it.
Verified:
* selftest 97 -> 102 pass, 0 fail. New cases: an unlisted reusable is refused,
a listed-but-absent one is refused, a push-triggered workflow is correctly
NOT demanded, a missing workflows dir is refused rather than passed, and a
fully-tracked tree passes.
* Against the real source dir BEFORE the inventory rows: died naming
version-bump-pr.yml. AFTER: passes.
* Mutation: removing any single entry from the list makes it die (exit 2).
* ruff --isolated --select E4,E7,E9,F scripts/ -> All checks passed!
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 5075572. 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 11, 2026
@tracebloc-release-train
tracebloc-release-trainBot merged commit 288bca8 into stagingAug 11, 2026
25 checks passed
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