ci(gate): close all-required name-vs-coverage hole via cross-workflow drift check (F4) #2474
Reference in New Issue
Block a user
Delete Branch "fix/all-required-aggregate-fail-closed"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
CI / all-required (pull_request)is a fail-closed aggregator over the CI workflow's OWN jobs (plainneeds:+ an explicit per-needresult == successassertion — that mechanism is correct and unchanged). But its name implies it covers every branch-protection required check, which it does not and cannot: Gitea Actions has no cross-workflowneeds:, so the sibling required workflowsE2E API Smoke Test(e2e-api.yml) andHandlers Postgres Integration(handlers-postgres-integration.yml) emit their own status contexts thatall-requiredstructurally cannot gate.The latent hole (verified)
On core PR #1086 @
9136d05a,CI / all-requiredpostedsuccess(status id 52) afterE2E API Smoke Test(id 48) andHandlers Postgres Integration(id 47) had already postedfailure. The aggregate does not fail-closed on those inputs because they are not (and cannot be) in itsneeds:.Not currently exploitable —
mainbranch protection requires all three contexts independently:But if BP were ever trimmed to just
CI / all-requiredon the false assumption that "all" means all, a red-CI PR would look mergeable. This is the falsely-GREEN-aggregate class (inverse of #2448's green-but-empty).Why the aggregator itself is NOT the bug
The job is fail-closed for its scope. The real gap is the missing cross-workflow coverage guarantee.
ci-required-drift.py's F2 was meant to catch BP contexts with no emitter, but it is scoped to the hard-coded lowercase literalci /prefix + bare job-keys — a shape that does not match this repo (workflow isname: CI; CI jobs set their ownname:), so F2 is effectively dormant here.Fix (robust, minimal, behavior-based)
ci-required-drift.py: parse every workflow's realname:+ each job'sname|keyand assert that every BPstatus_check_contextsentry has a live emitting workflow. This is the case-correct, repo-wide generalization of F2 and closes the inverse-of-F2 hole — a renamed/deleted sibling workflow that BP still requires now fails drift loudly instead of degrading to a silent absent-as-pending advisory gate. This is what keeps theall-requiredname honest at the repo level.all-requiredjob inci.yml: it covers CI's own jobs only; E2E + Handlers MUST stay required in BP independently; do not trim BP to justCI / all-required(cites the #1086 evidence).detect_driftF4 silent-when-emitted, fires-on-stale-cross-workflow-context, and no-false-positive-on-lone-context.Verification
test_ci_required_drift.py: 23 passed (16 existing + 7 new).test_ci_workflow_bookkeeping.py: 28 passed.lint-workflow-yaml: clean (59 files, 0 warnings).🤖 Generated with Claude Code
Security+correctness 5-axis — APPROVE (head
44ab45720f).CI gate-integrity hardening (+247/-0, additive) — adds drift failure-class F4: a BP-required
status_check_contextthat NO workflow in .gitea/workflows/ emits. Closes the inverse-of-F2 hole (a renamed/deleted sibling workflow still required by BP → Gitea treats absent-as-pending → silent advisory gate where a RED PR can look mergeable). This is the safeguard that makes the misleadingly-namedCI / all-requiredaggregator safe at the repo level (it only covers CI’s own jobs; cross-workflow siblings like E2E-API/Handlers-PG have no Giteaneeds:and MUST be BP-listed — F4 verifies each still has a live emitter).workflow_emitted_contextsbuilds{wf.name} / {job.name|key} (pull_request)from authoritative parsed YAML (byte-equal to Gitea’s context naming — unlike the dormant F2 which hard-codes literalci+bare-keys).all_emitted_contextsunions across*.yml; F4 =[c for c in BP_contexts if c not in repo_emitted]→ finding. Fail-closed (finding ⇒ drift flagged). ✓::warning::+ skip (doesn’t blind F4 to the rest; parse-validity gated separately).if:-gated jobs still count as emitters (F4 asserts emitter EXISTENCE, not that it ran) — avoids false positives. ✓*.ymlonly (not*.yaml) — matches repo convention.CI green: CI/all-required ✓, Handlers-PG ✓, Platform(Go) ✓, trusted sop-checklist(pt) ✓ (E2E-API path-skipped: scripts-only change). Author devops-engineer (≠ me). Sound — APPROVE. Needs a 2nd distinct genuine lane; review-only, I do not merge.
qa-team-20 5-axis — APPROVED (CR-B, qa lane; 2nd distinct genuine with Claude-A's security). Head
44ab4572. REVIEW-ONLY (do NOT merge — Local Provision E2E + sop pending).Correctness: Sound gate-integrity hardening (F4). ci-required-drift.py computes the set of pull_request status-check contexts each workflow ACTUALLY emits (from real workflow + each job's → ) and asserts every BP-required status_check_context corresponds to a real emitter — catching the renamed/deleted-while-still-required hole (which otherwise leaves BP demanding a green that can never emit → silent absent-as-pending). This correctly closes the name-vs-coverage gap (all-required is fail-closed over CI's OWN jobs but can't cover sibling required workflows). Directly addresses the #1086 dedicated-gate-integrity class.
Tests (+122, non-vacuous): test_ci_required_drift.py exercises the emitter-computation + the F4 missing-emitter detection. Security: content-sec N/A (CI script); security-review-pt = SUCCESS.
Blockers are NOT code: Local Provision Lifecycle E2E (inherited pre-#2500 / staging) + sop-checklist (author-ack) — gate-state, not defects.
Verdict: APPROVED. On 2-genuine + dedicated-green (post rebase/staging/sop) → merge by whoever's the merger (author devops-engineer≠me).