Skip to content

release-train: develop -> staging - #166

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

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

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 6, 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 org-wide required checks, kanban GraphQL updates with PROJECTS_KANBAN_TOKEN, and security-sensitive PII/version gate logic consumed at @main by many repos.

Overview
This promotion bundles a large tracebloc/.github refresh: new fail-closed guards, kanban automation fixes, and org-wide standards tooling—not only a branch pointer move.

Kanban & deploy tracking:advance-deploy-env gains a dry_run input and now advances closed issues linked from promoted PRs (including cross-repo), with the same monotonic rules as PR cards. kanban-reconcileauto-applies keep-open on open issues in North Stars so the stale sweep cannot archive strategic epics.

New or tightened CI: A code-quality-caller runs the published reusable workflow in this repo with soft-fail: false and hard action-pins. conformance-gate is an always-on PR check that, when repo-inventory.yml (or related contract files) change, requires a successful caller-drift run on the same SHA. A reusable version-bump-gate mirrors release-train version_preflight at PR time, with offline selftests. standards-sync plus org-standards.md sync a managed block into fleet CLAUDE.md files.

Gate behavior fixes:public-pii-gate checks pii-gate-override before refusals and fails closed on fork PRs with fork-specific messaging. customer-priority-bump drops unused project/org/priority inputs (label-only under D5). repo-inventory.yml documents the conformance gate and marks .github as requiring code-quality; e2e-test-agent is on the release train.

Docs: README adds guidance to wire version-bump-gate-caller for version_file repos.

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

LukasWodkaand others added 4 commits August 6, 2026 08:39
…ks got impossible advice (backend#1409) (#154)
* fix(pii-gate): the override could not rescue an unset secret, and forks got impossible advice (backend#1409)
Two defects, plus the first test this gate has ever had.
1 · THE DOCUMENTED ESCAPE HATCH DID NOT WORK
The `pii-gate-override` label was checked AFTER the unset-secret refusal, so on
a PR where PII_DENYLIST is missing the refusal exited first and the label could
not rescue anything. Both this file's own header ("Override a false positive:
add the 'pii-gate-override' label") and backend#1409's remediation note ("or
apply the pii-gate-override label to an individual PR") advertise it as the
per-PR way out of exactly that state.
That state is not hypothetical: the secret still does not exist, and today's
`.github` promotion (8aabe41, 13:53) armed the fail-closed path, so all seven
public repos now carry a red check that the documented workaround cannot clear.
An override is a statement that this PR should not be evaluated, so it now
precedes every reason the evaluation might refuse.
2 · A FORK PR WAS TOLD TO GO SET A SECRET THAT CANNOT HELP IT
GitHub does not pass secrets to `pull_request` runs from a forked repository, so
DENYLIST is empty on any fork PR regardless of what the org secret contains. The
gate then took the unset-secret path, whose message asks an org admin to run
`gh secret set` — advice that cannot work, because this is a property of the
event and not of the configuration.
Forks now get their own branch with a true reason and a real remedy: a
maintainer reads the PR text and applies the override label to record it. Still
fails closed. Compared against exactly "true", so an absent head.repo (deleted
fork) falls through to the paths below, which refuse on an empty denylist and
refuse again on a Compare read they cannot complete — an unknown fork status
cannot buy a pass.
This also unblocks #1409 defect 2. Making `pii-gate / pii-check` required would
have made every fork PR unmergeable; with the fork path explicit, the check can
be required for same-repo PRs and deliberately not for forks. Measured today:
0 fork PRs across all 7 public repos, so nothing is affected retroactively.
3 · A SELFTEST, IN THE SHAPE caller-drift ALREADY USES
scripts/tests/pii-gate-selftest.sh extracts the gate's `run:` block and executes
it against a stubbed `gh`. 15 cases: both new behaviours, the unset-secret and
fork refusals, title/body/commit matching, and every fail-closed path (comma-only
denylist, truncated commit list, unreadable Compare, missing base SHA, glob-shaped
term), plus a regression case for defect 3 — an early match in a ~150KB haystack,
which is the shape that used to be discarded via SIGPIPE.
Every case asserts the exit status AND a distinguishing phrase, because a fork PR
and an unconfigured org both exit 1 and the whole point of this change is that
they must not say the same thing. A status-only test would pass while the gate
gave impossible advice.
Proof the suite bites rather than merely being green: run against the currently
live main copy it fails exactly the 4 new cases and passes the other 11, so
nothing pre-existing regressed.
The test depends on the `run:` block staying free of `${{ }}` interpolation. That
is asserted, not assumed — extraction fails loudly if a future edit inlines an
expression, rather than silently covering less.
Not fixed here, and still open on backend#1409: the secret itself does not exist
(a content decision), and the check is required on 0 of 7 public repos. Scanning
the diff rather than only PR metadata is backend#1559.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(pii-gate): pad the regression haystack via commits, not a 150KB env var
The defect-3 case exported a ~150KB PR_BODY. Linux caps a single exec
argument or environment string at 128KB (MAX_ARG_STRLEN), so every exec
after that export died with E2BIG -- grep, head, tr and even the trap rm.
macOS has a larger limit, so it passed locally and failed on the runner:
"/usr/bin/grep: Argument list too long".
Also unrealistic. GitHub caps a PR body at 65,536 characters, so no real
run could produce that env var. Padding now comes from 250 commit
messages generated straight into the fixture file, which never travel
through argv or the environment -- and that is the faithful shape anyway:
backend#1409 describes the haystack as title + body + up to 250 commit
messages, title first.
The case now asserts the haystack size (80,390 bytes) exceeds the 64KB
pipe buffer. Without that, the fixture could shrink and the case would
stop reaching the defect while staying green -- a regression test that no
longer reproduces its bug is the same class of false comfort #1409 is
about.
Proof it reaches the defect: reverting the matcher to the pre-#130 pipe
form makes this case, and only this case, fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ep-open sync (backend#1597) (#163)
stale-backlog is column-blind: a quiet North Stars epic auto-closes
after 6+8 weeks and routes to Cancelled. The sweep already exempts
keep-open - this keeps that label synced from board state nightly.
One-way by design; write failures fail the step.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ly does (backend#1597) (#164)
project-number, org and target-priority were declared and never read;
the step hard-codes the 'priority' label per D5. Measured all 16 callers
on develop and prod branches: byte-identical, secrets: inherit only, no
with: blocks - so outright removal breaks nothing. trigger-label stays
(used in the job if:). Header comment now describes the actual behavior:
trigger label in, binary 'priority' issue label out, no board field.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…4) (#162)
release-train#34 merged 2026-08-06 and listed the repo, so release_train can now
be true. It could not be set in the same PR as the caller flip (.github#153):
caller-drift.py:562 verifies this field against release-train/repos.yml, so
flipping it early fails the guard.
RFC-BACKEND-1552 D2 is the decision; it amends RFC-BACKEND-1405 D1, which had
named this repo in its exempt list.
Verified: caller-drift exits 0, 20/20 repos, no drift.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

Comment thread.github/workflows/public-pii-gate-selftest.yml Outdated
Comment thread.github/workflows/kanban-reconcile.yml
@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 6, 2026
LukasWodkaand others added 3 commits August 6, 2026 10:26
…d skips CLOSED issues (.github#166) (#167)
* sec(ci): SHA-pin checkout+setup-python in the pii-gate selftest (backend#1491, D10)
The new selftest workflow used floating @v4/@v5 while every other workflow here
pins to a 40-char SHA; this repo runs no code-quality caller to catch the drift.
* fix(reconcile): North Stars keep-open shield skips CLOSED issues (Bugbot .github#166)
The shield filtered on status+type but not issue state, so closed North Stars
issues got a sticky keep-open the classify step may then terminalize.
… has one (backend#1586) (#169)
The WHY-THIS-FILE-EXISTS note closed with a present-tense claim - "there is
no closure caller there to route it" - that the body of the same file now
contradicts. e2e-test-agent's entry reads
kanban-closure-router.yml: required # kanban-closure-routing.yml
since the e2e-test-agent#13 remediation, and the caller is really there:
tracebloc/e2e-test-agent@develop ships .github/workflows/kanban-closure-routing.yml
resolving to tracebloc/.github/.github/workflows/kanban-closure-router.yml@main.
Note the filename - `kanban-closure-routing.yml`, not `-caller.yml` - which is
exactly the mismatch the MATCHING IS ON `uses:` CONTENT paragraph below warns
about, and exactly why a reader checking the header's claim by filename would
have concluded it was still true.
This is a file whose entire purpose is being the single source of truth that
caller-drift.yml enforces. A header that misstates the live state of a repo
the body governs is the one defect it cannot afford, even in a comment: the
guard checks the entries, nobody checks the prose, so the prose is where a
stale claim survives longest.
The fix keeps the motivating history and moves it into the past tense, because
e2e-test-agent#1 really did close without routing and that is the evidence
backend#1415 was built on. What changed is not the diagnosis, it is the repo:
the motivating gap is closed, the detection gap the file exists to fix is not.
Zero runtime impact - a comment. YAML parse re-verified (20 repos load).
Found on the 2026-08-05 develop->staging hop (.github mirror #155,
repo-inventory.yml:444), landed to staging under triage. Parent backend#1405.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…1603) (#171)
* feat(ci): run the org's own code-quality suite on this repo (backend#1603)
This repo publishes code-quality.yml -- and its action-pins job -- to all 19
other repos, and was the only repo that never ran it. The exemption was
invisible because actionlint.yml looks like coverage: it type-checks every
expression, validates needs/uses wiring, and shellchecks every run: block.
What it does not do is look at action REFS. So the pinning rule this repo
defines was the one rule this repo was not subject to.
That is not theoretical. On 2026-08-06 .github#168 -- an otherwise correct
fix -- reverted actions/checkout from its pinned commit SHA back to the
mutable @v4 tag, in advance-deploy-env.yml, the most-consumed reusable
workflow in the org and one that runs with PROJECTS_KANBAN_TOKEN in scope.
action-pins exists to fail exactly that diff. It did not run. A human reading
the diff caught it, which is the review path D10 was written to stop
depending on.
Armed from day one rather than soft-failed into a backlog, because the tree
was measured first: shellcheck --severity=error clean over both shell
scripts, house-rules.sh clean over the same, and 24 `uses:` refs across 23
workflows with zero pin violations. ruff and gitleaks could not be run
locally and get their first honest look on this PR; whatever they surface is
fixed or baselined here rather than the gate being softened -- the same
bargain e2e-test-agent's caller records in its own comment.
action-pins-soft-fail is set to false explicitly rather than left to inherit
soft-fail. It is the check whose absence let #168 through, and every other
repo consumes these workflows at @main and inherits whatever refs they pin,
so this repo has a specific duty to enforce it on itself.
actionlint.yml stays separate. The two gates have deliberately different
postures -- actionlint blocks from day one against a tree cleaned in the same
change; code-quality ships soft-fail: true for repos still clearing backlog
-- and folding them together would force one posture onto both.
No paths: filter, so the check can be made required without leaving
workflow-free PRs waiting on a status that never reports.
Refs backend#1603, backend#1491 (D10), backend#1276.
* fix(ci): drop the action-pins inputs -- @main does not declare them yet
First push of this branch produced a startup_failure on the reusable call
(run 31086491251): every other check reported normally and `Code quality`
never started. Cause: `action-pins` and `action-pins-soft-fail` were added in
#159, which is on `develop`; `main` still carries the five-job version of
code-quality.yml. Callers must reference @main (Q3), and passing an input the
@main callee does not declare fails the entire call before any job runs.
Same merge-order trap as the #160 audit red: a change becomes consumable by
@main callers when it is PROMOTED, not when it is merged.
The other five jobs still run armed, so most of backend#1603 closes now. The
arming diff is written verbatim in the file so the follow-up is mechanical
once #159 reaches main.
Refs backend#1603.
@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 6, 2026
LukasWodkaand others added 5 commits August 6, 2026 11:16
… (backend#1600) (#168)
* fix(kanban): advance the ISSUES each promoted PR closes, not just the PR (backend#1600)
advance-deploy-env advanced only PRs; issues closed by a develop-merged PR were
parked at On dev by the closure-router and never advanced when their code shipped,
drifting permanently. For each PR in the push, resolve closingIssuesReferences
(cross-repo aware) and advance each closing issue with the same monotonic guard,
archived-skip and (new) dry-run behaviour. Adds a dry_run input; update_field
short-circuits under it so both the PR and issue paths can be tested without writes.
* fix(kanban): only advance CLOSED closing-issues (Bugbot .github#168 High)
PRs merge to develop (not the default branch), so closingIssuesReferences lists
still-OPEN issues; advancing those into deploy columns is wrong and reconcile
bounces them to Backlog. Gate the advancement on issue.state == CLOSED.
* sec(ci): restore the actions/checkout SHA pin (backend#1491 D10)
This branch changed `actions/checkout@11d5960a…` to the mutable `@v4` tag in
advance-deploy-env.yml — the most-consumed reusable workflow in the org, and
one that runs with PROJECTS_KANBAN_TOKEN in scope. A floating major tag can
be repointed by the upstream owner at any commit, which is exactly what D10
pinning removes.
Nothing in CI would have caught it: tracebloc/.github runs only its own
actionlint.yml, which does not inspect action refs, and it does not call the
code-quality workflow whose action-pins job exists to fail this diff. That
gap is now backend#1603 (.github#171, merged) — but action-pins itself cannot
arm here until #159 reaches main, so for the moment this restore is the only
thing standing between the unpin and every repo that consumes these
workflows at @main.
No functional change to the backend#1600 fix.
…oved to PR time (backend#1563 Layer 1) (#172)
* feat(ci): version-bump gate — the train's prod-hop refusal, moved to PR time (backend#1563 L1)
The release train already refuses to publish changed bytes under an
already-released version (promote-repo.sh:version_preflight). Correct, and
days late: backend#1561 spent a whole release leg discovering that
py-package's pyproject.toml still said 0.17.0 while v0.17.0 was tagged and
the delta touched tracebloc/*.
This asks the same question on the PR that causes it.
delta touches publish_paths AND v<version-on-this-PR> is already tagged -> red
Not "the version file changed vs base", which #1563 words it as: that would
demand a bump on every published-path PR, and one bump has always covered a
whole release cycle. A check that is wrong on the common case gets removed.
Parity is the point, so read_version and publishable_delta are copied from
promote-repo.sh rule for rule — the per-extension anchors included, since
those are what keep appVersion, a dependency pin and a leading comment out of
the answer. Two deliberate divergences, both strictly tighter: `set -f` (a
publish path is a glob, and promote-repo.sh only survives it by running where
no publish path exists), and one paginated tag listing instead of a
per-version git/ref probe that cannot tell 404 from 403.
Fails closed on every unreadable input, and `soft-fail` does not reach any of
them: it downgrades the verdict, never the machinery. Zero files scanned is a
malfunction. No paths: filter and no job-level if:, so a required check can
never go missing on a PR.
45-case decision table in scripts/tests/, run offline against a stubbed gh,
covering every fail-closed path and all five repos' version file formats.
Callers follow in one PR per repo, once this is on main.
* sec(version-gate): a branch name is not a credential — tighten the promotion exemption
Self-review before Bugbot. The release-train exemption keyed on the head ref
alone, and a head ref is chosen by whoever opens the PR: `release-train/anything`
was an exemption anybody could mint, from a fork included. It only skipped a
version check the train re-runs at the prod hop, so nothing could actually
publish through it — but "the gate is disarmed by naming your branch" is not a
property to leave in a gate.
Now all three must hold, and the extra two are facts about a real promotion
rather than claims made by its author: head ref prefix, head in THIS repository
(never a fork), base in staging/main/master. A promotion never targets develop,
so a PR that borrows the name to reach develop is evaluated normally — with a
warning naming the mismatch, because a bypass that does not apply should be
visible rather than silently absent.
Also: an EMPTY version-file read is retried on purpose (it is the exact symptom
of the Contents API lag that left py-package untagged on 2026-07-29), so the
message after three failed attempts now names both possibilities instead of
saying only "could not read".
50 cases, 4 of them new and all four red before this change.
* sec(version-gate): the fork test demands a confirmed false, not merely 'not true'
head.repo is absent on a PR from a deleted fork, so IS_FORK arrives empty and
'not true' read that as 'same repo' — granting the promotion exemption on a
field nobody could read. Exempting on an unread field is the shape of every
fail-open in backend#1409.
Three-way now: false exempts, true says fork, anything else says the head
repository could not be confirmed. Evaluating a genuine promotion by mistake
costs a re-run; exempting a fork by mistake costs the gate.
* fix(version-bump-gate): head -1 the version parse; grep -oE prints per match
Bugbot, .github#172. The yaml/toml/py branches take `head -1` on the LINE,
then run `grep -oE "$semver"` on it -- and grep -oE prints one line per match,
not per input line. A version line carrying a second semver in a trailing
comment
version = "1.2.3" # bumped from 1.0.0
therefore parses to the two-line value "1.2.3\n1.0.0". Measured, not
theorised.
Here that corrupts the regression guard: `sort -V | tail -1` answers 1.2.3
while `"$HEAD_VER"` is the whole two-line string, so the `!=` compare is true
and the gate refuses a perfectly legitimate bump. The bare-file branch already
guarded this with a trailing head -1; the other three did not.
The json branch is safe -- jq emits a single value and the pattern is anchored
with ^ -- so it is left alone rather than given a cosmetic head -1.
This same defect is in promote-repo.sh:read_version, which this file copies
rule for rule; it is fixed there in release-train#40. In the train the
consequence is worse than a false refusal: the parsed value is interpolated
into the tag-probe URL, a malformed URL 404s, and the caller reads that as
"this version is not released" -- fail-OPEN on the one check that stops a
re-publish under an existing tag.
…(backend#1602) (#170)
* feat(standards): org-standards canon + CLAUDE.md sync guard (backend#1602)
One canonical org-standards.md — the org's working rules — stamped into
every active repo's CLAUDE.md between org-standards markers, so every
Claude Code session in every repo loads the same rules.
standards-sync.py audits the fleet develop-first (weekly report-only;
a create-prs dispatch opens per-repo sync PRs against develop), with
caller-drift's design rules: never all-clear from a failed read,
absence never implicit (devex-bootstrap exempt with written reason),
malformed markers reported and never auto-spliced. Offline selftest:
19 checks over the splice logic and every fail-closed path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(standards): install PyYAML in the selftest job; exact-ref branch existence (backend#1602)
Both from Bugbot on .github#170. The selftest's good-path load_targets
check parses YAML, and a clean setup-python has no PyYAML — the job died
at the final check (mirrors caller-drift's selftest job now). And
resolve_branch now uses the exact-match git/ref/heads/develop lookup,
the same endpoint remediate() trusts for the base sha. Verified live:
backend resolves develop, release-train falls back to main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(standards): a guard crash exits 2 (could not evaluate), never 1 (drift) (backend#1602)
Bugbot on .github#170: Python exits 1 on an unhandled traceback, and 1 is
reserved for confirmed drift — so a malformed API payload or filesystem
error produced a drift report instead of the unknown-state failure. The
entry point now maps any crash to exit 2 with an ::error:: line. Pinned
by a selftest that runs the guard with gh stripped from PATH.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…on a failed audit (#173)
* feat(ci): required conformance gate — a contract change cannot merge on a failed audit
caller-drift.yml has run on PRs touching repo-inventory.yml since backend#1415,
and it correctly fails when the inventory disagrees with reality. But it is not
a required status check: .github/develop requires only actionlint. So a PR that
adds a repo to the train with drift went RED and could still be merged. The gate
existed in trigger form and had no teeth.
It cannot simply be marked required as-is. It carries a paths: filter, and a
path-filtered required check never reports on PRs that miss the filter, leaving
them pending forever - the trap actionlint.yml's own header documents. Dropping
the filter would instead run a 20-repo API audit on every PR in this repo, which
is the reason the filter is there.
So this adds a job that ALWAYS runs and ALWAYS reports. On a PR that does not
touch the contract it is green in seconds with zero audit API calls. On a PR
that does, it requires caller-drift's verdict for that exact head sha.
It keys on the WORKFLOW FILE, not the check name. Check-run names are not unique
across workflows - caller-drift.yml and standards-sync.yml both expose a job
called selftest - so polling by name would happily accept the wrong workflow's
verdict. Querying actions/workflows/caller-drift.yml/runs?head_sha=... cannot be
confused that way.
Fail-closed on every non-success branch, each with a named reason: no run found
within the poll budget, a non-success conclusion, an unreadable API, or a
changed-file list that came back empty (unreadable, not "no files"). The
--paginate on the files query matters: a PR over one page would otherwise look
like it touches only its first 30 files, and a truncated read reporting "not
guarded" is precisely the silent pass this file exists to prevent.
The path match is exact (grep -qxF), verified offline against the cases that
matter: docs/repo-inventory.yml and repo-inventory.yml.bak do NOT count as
touching the contract.
This PR edits repo-inventory.yml's header on purpose, so it exercises its own
guarded path rather than shipping a gate whose interesting branch never ran.
Refs backend#1608, backend#1415.
* fix(inventory): .github's code-quality caller is required, not exempt
Caught by the gate this PR adds, on its own first run. The audit reported:
.github: code-quality.yml is marked `exempt` but a caller exists
(code-quality-caller.yml). The exemption is stale.
Correct, and the drift is an hour old: .github#171 landed the caller for
backend#1603 and never flipped this entry. That is precisely the follow-up
caller-drift.py's docstring tells you to do -- "land the caller first, flip
the entry in a follow-up" -- because the inventory is read from the checkout
while repo state is read from the audit branch, so doing both in one commit
fails.
So the sequence worked exactly as designed: the caller landed, the entry went
stale, and the very next inventory-touching PR was refused until it was
reconciled. That is the whole point of the gate, demonstrated on itself
rather than asserted in a comment.
The exemption text is replaced with the remediation history rather than
deleted, matching how the pii-gate and e2e-test-agent entries record theirs --
the reason the exemption existed is evidence, not noise.
* fix(gate): here-string, not a pipe — SIGPIPE under pipefail turned a hit into a miss
Bugbot, .github#173. Reproduced before fixing:
20,000 filenames (~349 KB) with the guarded path FIRST
printf '%s\n' "$FILES" | grep -qxF repo-inventory.yml
-> pipeline rc 141, `if` takes the else branch, gate reports NOT GUARDED
grep -q exits on its first match and closes the pipe. With enough left to
write, printf takes SIGPIPE and exits 141; pipefail then makes the PIPELINE
141, so a real match reads as a miss. The gate would report "not guarded" and
pass GREEN on precisely the PRs most likely to matter -- the failure gets MORE
likely as the diff gets bigger, and it fails OPEN, which is the one direction
this file exists to rule out.
A here-string is a single command, so the status is grep's own. Verified: same
input, same guarded path, correctly GUARDED.
Swept the rest of the repo for the pattern. fr-gate.yml has two instances
(`echo "$LABELS" | grep -q ...`), and both are safe on two independent counts:
a PR's label JSON is orders of magnitude under the 64 KB pipe buffer, so echo
finishes before grep can exit; and a false negative there means the gate does
NOT skip, i.e. it fails CLOSED. Same pattern, opposite consequence -- left
alone deliberately rather than changed for symmetry.
The `| head -1` chains in version-bump-gate.yml and the kanban workflows are
likewise safe: each is either terminated by `|| true` or fed by output far
under the buffer.
Refs backend#1608, backend#1409 (same defect class in the PII gate).
* fix(gate): renames escaped the guard; poll budget was shorter than what it waits for
Two Bugbot findings on .github#173.
HIGH - a rename escaped the contract guard. The changed-file probe read only
.filename, and a rename reports the NEW path there. So `git mv
repo-inventory.yml elsewhere.yml` sailed through as "not guarded": the
contract file moves out from under the audit and the gate waves it past -
precisely the silent pass this workflow exists to close. Now reads BOTH
.filename and .previous_filename, which is the same reason promote-repo.sh's
publishable_delta matches both fields (a file renamed OUT of a published tree
is still a publish-path change). I had read that comment while building the
version-bump gate and failed to carry it across.
Proven on a synthetic rename payload, since no open PR has one:
old jq (.filename only) -> NOT GUARDED <- the hole
new jq (+ .previous_filename) -> GUARDED
and verified against a real PR that the `// empty` guard emits nothing extra
when previous_filename is absent.
MEDIUM - the poll budget was shorter than the thing it waits for. 40x30s = 20
minutes, while caller-drift's worst case is selftest (timeout 5m) + audit
(timeout 30m) = 35m. A legitimately slow audit would have made the gate go red
as though no verdict ever arrived, blocking a contract change until someone
re-ran it by hand. Fail-closed is right; fail-closed on a budget that cannot
cover the wait is just fail-annoying, and a gate people have to re-run by hand
is a gate they campaign to remove. Budget is now 80x30s = 40m with the job
timeout raised to 45m so the named error message surfaces rather than an
opaque job timeout.
Refs backend#1608.
…backend#1603 step 2) (#178)
The second half of backend#1603. When code-quality-caller.yml landed in #171 it
could not arm action-pins: the job and its two inputs were added in #159 and
existed on `develop` only, and a caller must reference @main (Q3). Passing an
input the @main callee does not declare kills the entire reusable call with a
startup_failure - not the one job, the whole call - measured on that branch's
first push, run 31086491251.
#159 has since promoted, and `main` now carries the six-job version with both
inputs. Verified against the @main copy before flipping, rather than assuming
the promotion carried what it looks like it carried.
Hard-armed rather than left to inherit soft-fail. This is the check whose
absence let .github#168 revert actions/checkout from a pinned commit SHA to the
mutable @v4 tag with nothing in CI to object - in advance-deploy-env.yml, the
most-consumed reusable workflow in the org, running with PROJECTS_KANBAN_TOKEN
in scope. Every other repo consumes these workflows at @main and inherits
whatever refs they pin, so this repo has a specific duty to enforce the rule it
publishes.
Re-measured the tree before arming: 24 `uses:` refs across 23 workflows, zero
violations. Arming imports no backlog.
@LukasWodkaLukasWodka self-assigned this Aug 6, 2026
@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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c35327e. Configure here.

Comment thread.github/workflows/advance-deploy-env.yml
Comment thread.github/workflows/advance-deploy-env.yml
@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 6, 2026
@tracebloc-release-train
tracebloc-release-trainBot merged commit 1a5813f into stagingAug 6, 2026
22 of 27 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