Skip to content

release-train: staging -> main - #152

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

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

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 5, 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
Promoting fail-closed PII scanning and grep/API error handling changes org-wide public PR checks; once on main, unset PII_DENYLIST turns previously green pii-check jobs red across public repos (documented as intentional but operationally disruptive).

Overview
Release-train promotion of org workflow and inventory updates: the public PII reusable is rewritten to fail closed (backend#1409), .github gains FR and public PII caller workflows, and repo-inventory.yml is brought in line with recent caller/train remediations.

The public-pii-gate script now exits non-zero when the denylist is missing, Compare API reads fail, commit lists are truncated past the 250-commit cap, or matching cannot complete. Matching uses a staged haystack file and explicit grep exit handling (fixes SIGPIPE/pipefail false negatives and errexit false positives). Override labels require an exact jq match on pii-gate-override. Success logs report how many terms and commits were scanned.

.github adds fr-gate-caller.yml and public-pii-gate-caller.yml (self-references pinned @main, secrets: inherit for PII_DENYLIST). code-quality.yml gains caller documentation on omitting secrets: inherit for secretless workflows and a warning that gitleaks must stay on gitleaks git for baseline compatibility.

repo-inventory.yml marks .github, docs, model-zoo, and start-training on the release train with required callers where remediated; documents public PII on .github as presence-not-teeth until PII_DENYLIST is set; and replaces rfcs's blanket exemption text with per-caller reasons plus add-to-kanban required.

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

LukasWodkaand others added 23 commits August 3, 2026 09:05
… to SIGPIPE
The gate whose only job is keeping customer names out of public repos was
green on six public repos while having evaluated nothing. Three independent
fail-open paths (backend#1409), all now refusals.
1. An unset/empty PII_DENYLIST warned and exited 0. The secret does not
exist org-wide, so `pii-gate / pii-check` had never compared a single
term anywhere. It now exits 1: a guard that cannot check must refuse,
not pass. Same message covers a caller that forgot `secrets: inherit`.
2. `printf '%s' "$HAYSTACK" | grep -iqF` discarded matches. `grep -q` exits
on its first match and closes the pipe, printf takes SIGPIPE, and under
`pipefail` the pipeline returns 141 — so `if` read false and the hit was
thrown away. Reproduced on the runner toolchain (bash 5.2.21, GNU grep
3.11): a denylisted term in the PR title with a 200KB haystack gave
`rc=141 PIPESTATUS=141 0` and the old script printed "PII gate passed".
Now a herestring: no pipe, nothing to break. grep exiting >1 is an
operational error and is treated as "did not check", never as "clean".
3. `2>/dev/null || true` on the Compare API turned a 403, a rate limit and
a jq error into an empty commit list, and the gate passed on title+body
alone. The response is now read into a file, failures are reported with
gh's own stderr, and `commits[] < total_commits` (the API's 250 cap) is
refused as a truncated haystack rather than scanned and called clean.
Three smaller fail-opens found while walking the same error paths: a
denylist of only commas passed every PR having compared nothing; the
override test matched any label *containing* `pii-gate-override`, so a
label like `discuss-pii-gate-override-policy` disarmed the gate; and
unquoted word splitting glob-expanded any term containing `*`. The pass
message now names how many terms and commits were actually compared, so a
green check is distinguishable from a green check that did nothing.
Still open, needs org-owner rights (not this PR): create the PII_DENYLIST
org secret, make `pii-gate / pii-check` a required check on the six public
repos, and add the caller to public `.github`.
Refs tracebloc/backend#1409
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hing"
Two fail-opens in the previous commit, same class as the ones it fixed.
The haystack is staged in a named temp file instead of a `<<<"$HAYSTACK"`
herestring. Both avoid the pipe that was discarding matches, but if bash
cannot create the herestring temp file the redirection fails, grep never
runs, and the status is 1 — the one code that means "no match". Reading a
named file, grep returns 2 on any read failure, so a check that did not
happen cannot be mistaken for a clean one. It is also written once rather
than once per denylist term.
The term trim now checks sed exit status. A failed trim yielded an empty
$t, which the empty-term guard on the next line skipped silently, so the
term went unevaluated while the gate still reported a pass.
Refs tracebloc/backend#1409
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… along
Bugbot on .github#130, High. The term loop ran a bare grep and then read $?,
but Actions invokes `run:` steps as `bash -e {0}`, and `set -uo pipefail` does
not clear that -e. grep returns 1 for "no match", which is the ORDINARY result
here, so errexit terminated the step before rc was ever assigned. The 0/1/*
case could only ever see a match.
The direction is fail-closed, so nothing leaked — but the gate blocked EVERY
clean PR across all six public repos, aborting on the first denylist term that
happened to be absent. It only looked correct while the secret was unset, which
is exactly the state this PR exists to fix: the moment PII_DENYLIST is populated,
the gate would have gone red on everything.
grep is now the left operand of `||`, which exempts it from errexit while still
delivering the status to rc, so 1 ("checked, found nothing") and 2 ("could not
check") stay distinguishable — the whole point of backend#1409.
Also spells out `set -euo pipefail`. errexit was already on; writing it down
stops the next reader inferring it was off, which is how this was written.
Verified by extracting the real loop and running it under `bash -e`:
clean PR, 3 absent terms -> CHECKED=3 HIT=0, completes (was: exit 1)
term present, first slot -> CHECKED=2 HIT=1
term present, last slot -> CHECKED=3 HIT=1
empty/whitespace term -> skipped, CHECKED=2
unreadable haystack -> rc=2, fails closed with the crafted message
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(pii-gate): fail closed when unconfigured, and stop losing matches to SIGPIPE (backend#1409)
Paired with release-train#25, which enrols them. repos.yml and this inventory
are two files in two repos that must agree, and the caller-drift audit compares
them -- so neither change is complete alone.
MERGE ORDER MATTERS, and this PR is red until its pair lands. Verified locally:
with this edit and release-train/main unchanged, the audit reports exactly
model-zoo: repos.yml says on-train=False, the inventory says True
start-training: repos.yml says on-train=False, the inventory says True
That is correct behaviour, not a defect in this change. Merge release-train#25
FIRST, then re-run `audit` here and merge this. The reverse order is equally
red, just on the other side.
Both repos already have an fr-gate caller on develop and on their prod branch,
so the finding's stock warning about an ungated hop does not apply -- the same
distinction as .github#132 this morning.
Worth noting as a limitation rather than working around it: the guard has no way
to express "coordinated two-file change in flight". It compares live state to
the checkout, so any cross-repo invariant is red for the window between the two
merges. A follow-up could let an inventory entry carry a pending-PR reference,
but silence during that window would be worse than noise.
Refs #1420
…caller
The drift guard caught my own change. model-zoo#115 and start-training#32
added code-quality callers this morning; their inventory entries still said
`exempt: *code_quality_caller_missing`, so `audit` reported
model-zoo: code-quality.yml is marked `exempt` but a caller exists
start-training: code-quality.yml is marked `exempt` but a caller exists
Both flipped to `required # code-quality-caller.yml`. Audit now clean: 20 of
20 read, no drift. Selftest 42/42.
Third instance today of the same two-file coupling -- adding a caller, or
enrolling a repo, without updating the inventory that asserts the fact. The
earlier two were release_train booleans (.github#132, #135); this one is a
caller-vs-exemption pair. The shape is identical: a fact recorded in two repos
where only one got edited.
Also corrected the shared anchor's text, which still implied all nine original
citations stood. Down to three -- claude-skills, docs, release-train -- and the
anchor now names them plus what remediated the other two, so the next reader
does not have to re-derive the count from grep.
Refs #1420, #1415
.github is enrolling in the release train (D1), and the train waits on
`gate / gate` before a staging -> prod hop. This repo had no fr-gate caller at
all, so there was no such check to wait on -- #1276 correctly refused to make
`gate / gate` required here for that reason.
Pinned @main like every other caller. Open question 3 is answered in favour of
branch promotion: one standard process for every repo, because a second
promotion model is how drift starts.
Names the consequence rather than leaving it to be discovered: this repo hosts
the reusable, so a change to fr-gate.yml on develop is not gating its own PR --
main's version runs. That is exactly why the develop -> staging -> main path
matters more here than anywhere else. `staging` is the only place a new gate
runs against real board state before sixteen repos consume it at @main.
`branches:` omits `master`: this repo has never had one, and listing a branch
that does not exist is how dead filters accumulate (backend#1428).
Refs #1420, #1276
Paired with the repos.yml entry. .github now has everything the train needs:
staging branch created from main @ ee105ce, so its ancestry already
contains prod's -- no reconciliation on the first hop
staging protection 1 review, dismiss-stale, actionlint required,
conversation resolution, no force-push
fr-gate caller this PR -- there was none, which is why #1276 could not
make gate / gate required here
App bypass tracebloc-release-train on staging AND main, verified by
read-back (that write returns 200 and silently drops the
value when the App is not installed)
merge-commit-only ruleset on main + staging; develop untouched
Open question 3 answered in favour of branch promotion, so .github runs the same
develop -> staging -> main path as every other enrolled repo. One process, because
a second promotion model for one repo is how drift starts.
Refs #1420, #1405
Bugbot is right, and the problem is my sequencing, not the flip.
I bundled the inventory flip here to avoid opening a fourth PR. But caller-drift
compares live repos.yml against the checked-out inventory, so asserting
release_train: true while release-train#26 is unmerged makes this PR fail its own
validation -- and #26 is ordered AFTER #137, because the fr-gate caller has to
reach develop first or settle waits on a check that never appears.
That is circular: #137 cannot be green until #26 merges, and #26 should not merge
until #137 has. One red window between the two files is unavoidable, but it
belongs on develop where it is visible and expected, not inside a PR that cannot
go green.
So this PR is now the caller only. The flip follows #26.
Refs #1420
…ns-stale
fix(inventory): model-zoo and start-training now have a code-quality caller
ci(#1420): add the fr-gate caller so .github can join the train
The follow-up the file's own note prescribes: "Adding a caller and flipping its
entry to `required` in the same PR therefore fails, because the caller is not on
develop yet. Land the caller first, flip the entry after."
.github#137 landed the caller and release-train#26 enrolled the repo, so both
facts are now true and this records them:
release_train false -> true
fr-gate.yml exempt: *no_staging_branch_no_hop_to_gate -> required
That exemption was accurate until today -- .github genuinely had no staging
branch, so there was no hop to gate. It has one now (created from main @ ee105ce
so its ancestry already contains prod's), and RFC-BACKEND-1405 open question 3 is
answered in favour of branch promotion, which puts this repo on the same
develop -> staging -> main path as everything else.
Audit with this change: 2 findings, both rfcs entries that .github#138 fixes.
Without it: 4. Selftest 42/42.
Refs #1420, #1405
feat(inventory): .github is on the train and has an fr-gate caller
…ow has (#138)
* feat(inventory): rfcs is on the board — record the two workflows it now has
Paired with tracebloc/rfcs' board-visibility PR, which adds add-to-kanban.yml
(byte-identical copy, blob 45aa70a) and a set-pr-status caller so RFC PRs get a
card and land in `Code review`.
Seven RFCs are open, none merged, none with a human review. They stalled because
the board is how this team works and they were not on it -- rfcs had no
.github/workflows directory at all.
Also corrected every remaining exemption reason in this block. All nine said
"rfcs was created 2026-08-01 and has no .github/workflows directory yet", which
this change makes false. The seven still-absent callers are now recorded as
STRUCTURAL rather than pending: rfcs publishes no artifact, has no deploy stages
and is main-only by decision, so there is no hop for advance-deploy-env,
fr-gate, fr-pass-comment or kanban-closure-router to act on. Written down as a
decision, not carried as a debt that nobody intends to pay.
MERGE ORDER: the rfcs PR first, then this. The audit compares live caller state
against the checked-out inventory, so marking these `required` before the files
exist reports them MISSING -- correctly.
Note two of the four findings currently reported by this branch belong to
.github#136 (model-zoo / start-training code-quality exemptions), which is also
open and must land first. Three .github PRs now touch the same invariant.
Refs #1405, #1415
* fix(inventory): per-caller reasons for rfcs, not one blanket text
Bugbot on .github#138, and it is right. My first version asserted the same
"no deploy hop to act on" reason for all nine callers. That is only true for
some, and it silently overwrote reasons that were already accurate.
Corrected, using the existing shared anchors where they apply:
code-quality -> *code_quality_caller_missing (a REAL gap:
gitleaks and house-rules would do work on markdown)
kanban-closure-router -> *kanban_closure_router_caller_missing (a REAL gap:
it acts on close events, which this repo has now
that add-to-kanban creates cards)
customer-priority-bump -> *customer_priority_bump_caller_missing
public-pii-gate -> *private_repo_no_public_exposure
wip-limit-check -> *wip_limit_check_has_no_callers
fr-gate -> *no_staging_branch_no_hop_to_gate
Only three keep a bespoke structural reason, and each says why it is distinct:
advance-deploy-env (no `develop`, so no dev stage to advance into),
fr-pass-comment (no `staging`, so the column it drains is unreachable -- which
is what separates it from the repos that HAVE the stage and lack the valve), and
the stale-backlog copy (an untouched RFC is a decision nobody took, not a stale
backlog item).
The general lesson, which is why Bugbot's catch matters more than the diff: an
exemption whose stated reason does not describe the caller is worse than no
exemption. It reads as decided when it is unexamined, and #1415's whole premise
is that the reason is the finding.
Selftest 42/42.
Refs #1405, #1415
.github hosts the public-pii-gate reusable but ran no PII gate on its own
PRs — the only public repo with that gap (D1 enrolment, backend#1420).
Self-reference pinned @main, matching fr-gate-caller.yml.
Co-authored-by: Claude <noreply@anthropic.com>
…y (backend#1420) (#142)
Reflect the fleet-wide convention: callers now pass secrets: inherit even
though this reusable uses no secrets. Clarifies it's a no-op bounded by the
permissions block, adopted to clear a recurring Cursor Bugbot false positive.
Co-authored-by: Claude <noreply@anthropic.com>
…nded (backend#1420) (#143)
The caller-drift audit failed on the `.github` develop -> staging hop
(#140) with two findings, both real:
- .github: public-pii-gate.yml marked `exempt` but public-pii-gate-caller.yml exists
- docs: code-quality.yml marked `exempt` but code-quality-caller.yml exists
Both callers were added TODAY under backend#1420 (.github#141, and docs'
code-quality caller) and neither entry was flipped. This is the same
two-file coupling as repos.yml vs release_train, and the third time this
session it has been the guard rather than a human that noticed.
Flipped both to `required` with per-caller reasons rather than a bare
value, and recorded what each gate can actually do:
- docs' code-quality is that repo's ONLY content gate (off-train,
python:false / shell:false measured), so gitleaks + house-rules are
the real checks; soft-fail stays advisory until a first green run.
- .github's PII gate is PRESENCE, NOT TEETH: the reusable is unarmed
org-wide (PII_DENYLIST unset, backend#1409, now parked in the
backlog), so it runs green without inspecting anything. `required`
asserts the caller exists -- what this file tracks -- not that the
gate can fail. Saying so here beats a future reader inferring teeth
from the word `required`.
Also corrected the code_quality_caller_missing anchor: it claimed THREE
citations and named claude-skills, docs and release-train while FOUR
entries aliased it -- `rfcs` was added later in the same session and
never listed. Now three for real (claude-skills, release-train, rfcs),
with a note to trust grep over the sentence.
Verified: PyYAML parse OK (20 repos), selftest 42/42, and the real audit
run against live org state reports "No drift. Every repo read, every
entry matched." (20 of 20).
…rolment turned stale (backend#1420) (#144)
release_train: false -> true (release-train#30 landed first, per the
two-file coupling this guard audits). advance-deploy-env: the caller
existed on MAIN only (docs#70, a docs-exception direct merge) where
develop-first audits cannot see it; docs#80's backmerge fixed that.
fr-gate: staging now exists and the train polls 'gate / gate'
fail-closed, so docs#80 added the standard caller — the 2026-06-04
docs exception is NOT revoked, it lives beside the train path.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…sents RED, not green (backend#1409) (#146)
Bugbot caught the contradiction on the staging hop (#145): d248583
flipped the reusable to fail closed when PII_DENYLIST is unset, while
this note still said 'runs green without inspecting anything'. Record
how unarmed presents now, and that the red blocks nothing: pii-check
is a required status check nowhere (all 7 public repos x all branches,
measured 2026-08-04).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…d the other way (backend#1526) (#147)
Callers pass NO secrets line (RFC-BACKEND-1405 Q5): the no-op was
conditional on this file staying secretless, and @main floating means
a future secrets reference reaches every inheriting caller's full
secret set at once. Explicit per-secret passing if ever needed; the
Bugbot nag dies in per-repo BUGBOT.md rules, and the INVERSE (a caller
adding inherit) is what now deserves a finding.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ependency (backend#1404 §4) (#150)
Baseline fingerprints embed the introducing commit SHA, so they only
match git-mode scans. A future switch to dir/detect would silently
un-suppress all 277 baselined findings fleet-wide in one run, and
nothing recorded that coupling until now.
Co-authored-by: Claude Fable 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

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 0e88890. Configure here.

Comment threadrepo-inventory.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 5, 2026
@tracebloc-release-train
tracebloc-release-trainBot merged commit 8aabe41 into mainAug 5, 2026
16 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