Skip to content

feat(inventory): assert WHICH checks are required, not just that a caller exists (backend#1681) - #209

Merged
LukasWodka merged 2 commits into
developfrom
feat/1681-required-checks-property
Aug 11, 2026
Merged

feat(inventory): assert WHICH checks are required, not just that a caller exists (backend#1681)#209
LukasWodka merged 2 commits into
developfrom
feat/1681-required-checks-property

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

What

Adds required_checks to repo-inventory.yml's per-branch-role policy, and makes caller-drift.py assert it.

Why

The guard opened required_status_checks, kept strict, and discarded the rest. So required has meant "a caller workflow exists somewhere in this repo" — never "this check can block a merge". Measured 2026-08-11:

  • .github/staging required exactly one context (actionlint) while the inventory reported it fully conformant. Two release-train promotions merged with the contract audit red#203 (→ staging) and #205 (→ main), both with auditandgate at conclusion=failure.
  • quality / gitleaks is required on 16/16 develops but only 13/16 staging and prod — and all three gaps are public repos.

Both were invisible to the guard by construction.

Design

  • Reads both systems and both 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: the exact fail-open this guard exists to eliminate.
  • Subset, not equality. backend's Django suite and cli's golangci-lint legitimately sit on top. The policy is a floor, not a whole set — equality would turn every repo-specific gate into drift.
  • Schema-enforced. A missing value is a failure, not a default. divergent may narrow the set, but only with a written reason naming the narrower list — and is then held to that list.

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 rather than silence:

reporolesstated setwhy
docsstaging, prod[]the 2026-06-04 exemption is broader than review alone
model-zoostaging, prod["gate / gate"]content gates required on develop, neither promotion branch
start-trainingstaging, prod["gate / gate"]same — and its notebooks carry executed output cells

quality / action-pins is deliberately not in the staging/prod baseline: 16/16 on develop, 0/32 on promotion branches. That asymmetry is unexplained rather than decided, so this records the question instead of blessing it (backend#1681).

Its first live run surfaced three postures nobody had stated:claude-skills, release-train and rfcs carry a divergent prod cell for being working branches, which said nothing about required checks — so they inherited a baseline naming the fr-gate, a gate that only exists on a promotion pipeline they don't have. Now written down. release-train states its real set (actionlint, mutation, shell) rather than [], since it's the tool that performs every promotion.

Test plan / evidence

checkresult
selftest86 → 97 pass, 0 fail
live run, all 20 repos, real inventory"No drift. Every repo read, every entry matched."
mutation: same run, one impossible context added to the develop baseline16 findings, one per repo carrying that role, each naming the probe

The load-bearing new test is "a check that runs but cannot block IS a finding". Also covered: subset-not-equality, the legacy contexts 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 (bare string, non-string entry, blank context, duplicate).

The mutation run is the point: green here means something because red is reachable.

Follow-up

rulesets is the sibling property (nothing in the org audits rulesets at all today) and lands separately. Parent epic: backend#1680.


Note

Medium Risk
Changes merge-gate conformance auditing for all org repos; mis-stated baselines or read bugs could false-positive drift or miss gaps, but scope is guard tooling and inventory YAML with expanded selftests.

Overview
Adds required_checks to fleet branch-protection policy in repo-inventory.yml and teaches caller-drift.py to verify those contexts actually block merges—not merely that caller workflows exist.

Guard behavior:read_protection now unions required status-check contexts from classic protection (both checks and legacy contexts) and from ruleset required_status_checks. Evaluation uses a subset floor: policy contexts must be present; extra repo-specific checks are allowed. Missing contexts produce findings that explain the check may run red without stopping the merge.

Inventory: Measured baselines on develop (quality / gitleaks, house-rules, action-pins) and staging/prod (gate / gate plus the two content gates). Repos with narrower reality get explicit divergentrequired_checks (e.g. docs[] on promotion branches, public repos with only gate / gate, release-train with actionlint/mutation/shell).

Tests: Selftest coverage for fail-on-missing, subset-not-equality, legacy spelling, ruleset union, divergent overrides, and schema rejection of bad list shapes.

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

…ller 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>
@LukasWodkaLukasWodka self-assigned this Aug 11, 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.

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 bcda74a. Configure here.

Comment threadscripts/caller-drift.py Outdated
…#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>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

Fixed in 66c53aa — real finding, and my editing artifact: the edit that added required_checks to OVERRIDABLE re-emitted the explanatory comment that was already above it. Comment-only, no behaviour change; the block now appears exactly once. Selftest re-run after the fix: 97 pass, 0 fail.

@LukasWodka
LukasWodka merged commit 0dfbc9b into developAug 11, 2026
11 checks passed
@LukasWodka
LukasWodka deleted the feat/1681-required-checks-property branch August 14, 2026 13:53
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.

2 participants

@LukasWodka@saadqbal