Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions .github/required-contexts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,27 @@
# it, no `if:` gated on a non-PR event) BEFORE it lands here or in branch protection.

# --- ci.yml ---------------------------------------------------------------------------------------
# NOTE: `CI gate` is NOT in this list. It was removed from branch protection on 2026-07-29 and the
# reason is not recorded here -- this file states what the server enforces, not why. It matters,
# because that roll-up is what gated ci.yml's CONDITIONAL/matrix legs (ci.yml, job `ci-gate`,
# `if: always()` over its `needs:`). The three `test` legs below are required in their own right and
# are unaffected; what no longer gates a merge is everything the roll-up covered beyond them. If the
# removal was incidental, restoring it is one POST to the contexts endpoint plus a line here.
# `CI gate` (ci.yml, job `ci-gate`) is a ROLL-UP, not a test. It is `if: always()` over six legs that
# are PATH-GATED or nightly -- sqlserver-store, postgres-store, load-test, load-test-sqlserver,
# windows-service-smoke, changes -- and fails if any of them failed or was cancelled.
#
# Requiring the roll-up is the only way to gate those six. They cannot be required directly: a
# path-gated job does not report on a PR that touches none of its paths, which is the
# required-but-absent trap and would wedge every such PR forever. The roll-up always reports, so it is
# safe to require -- verified on a run where all six legs SKIPPED and `CI gate` still returned success.
#
# It matters because those six cover exactly what the three `test` legs SKIP: the store suites skip
# unless a leg supplies the database. Without the roll-up required, a PR touching the SQL Server or
# Postgres store, the load path, or the Windows service could merge with those legs red and nothing on
# the merge path would notice.
#
# Removed from protection 2026-07-29 (reason unrecorded) and RESTORED 2026-07-30 after checking its
# record: 18 success / 2 failure over 20 runs, and neither failure would have blocked a PR --
# * one was a `schedule` (nightly) run whose sqlserver load-test legs failed: not a PR context;
# * one was a superseded re-push where every job was CANCELLED. `CI gate` fails on `cancelled` by
# design, but branch protection gates on the LATEST head SHA, so a cancelled old run does not
# block the current head (see ci.yml's concurrency note).
CI gate
test (ubuntu-latest, py3.14)
test (windows-2022, py3.14)
test (windows-2025, py3.14)
Expand Down
1 change: 1 addition & 0 deletions docs/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ would otherwise wedge the PR — see the gotcha below).
The stable contexts required on `main` are — mirroring
[`.github/required-contexts.txt`](../.github/required-contexts.txt), which is the file to edit:

- `CI gate`
- `test (ubuntu-latest, py3.14)`
- `test (windows-2022, py3.14)`
- `test (windows-2025, py3.14)`
Expand Down
2 changes: 1 addition & 1 deletion scripts/hooks/push_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
publication, immediately and irreversibly (deleting a ref later does not un-publish content that was
fetched, mirrored or indexed in between).

Branch protection on the server requires a PR and 12 status checks, and `strict` is ON (a PR must be
Branch protection on the server requires a PR and 13 status checks, and `strict` is ON (a PR must be
up to date with ``main`` to merge). ``enforce_admins`` was enabled 2026-07-28 and DISABLED again on
2026-07-29 via the escape hatch in the HISTORY note below, so ``gh pr merge --admin`` works once more
and a direct push to ``main`` is refused only by this hook. That makes it the ONLY guard again for
Expand Down
2 changes: 1 addition & 1 deletion tests/test_push_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Since the MEFORORG cutover this repository IS the published artifact — a push to ``main`` is
publication, immediately, with no publish step left to catch anything. Server-side branch protection
requires a PR and 12 status checks. ``enforce_admins`` was ON from 2026-07-28 but is OFF again as of
requires a PR and 13 status checks. ``enforce_admins`` was ON from 2026-07-28 but is OFF again as of
2026-07-29 (the documented escape hatch in the hook's own HISTORY note), so the owner CAN bypass. This hook is now defence-in-depth: it fails FAST and LOCALLY with an
explanation rather than after a round-trip, and it covers ``cla-signatures``, which branch protection
does not. (It was written while ``enforce_admins`` was false and the hook WAS the only guard.) The
Expand Down
2 changes: 1 addition & 1 deletion tests/test_required_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def test_the_canonical_file_parses_and_names_the_live_set() -> None:
)
# Pinned so that ADDING or REMOVING a required check is a deliberate, reviewed edit here rather
# than a silent one. Verified against `gh api repos/MEFORORG/MessageFoundry/branches/main/protection`.
assert len(contexts) == 12, (
assert len(contexts) == 13, (
f"the canonical required set changed to {len(contexts)} contexts. If branch protection really "
"changed, update this count AND every claim this suite checks; if it did not, revert the file."
)
Expand Down
6 changes: 3 additions & 3 deletions tests/test_security_posture.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def test_required_jobs_carry_no_continue_on_error() -> None:
name = (step or {}).get("name") or (step or {}).get("uses") or "<unnamed step>"
offenders.append(f"{wf}:{key} — step {name!r} has continue-on-error")
# Liveness receipt. NOT `examined == len(required_contexts())`: the three `test (<os>, py3.14)`
# contexts are ONE matrix job, so 12 contexts collapse to 10 jobs. Pinned rather than derived so
# contexts are ONE matrix job, so 13 contexts collapse to 11 jobs. Pinned rather than derived so
# that a change in the collapse — a matrix split, or a context that quietly stops resolving —
# forces a look here instead of passing on a self-consistent count.
#
Expand All @@ -211,8 +211,8 @@ def test_required_jobs_carry_no_continue_on_error() -> None:
f"[security-posture] examined {examined} distinct jobs backing "
f"{len(required_contexts())} required contexts"
)
assert examined == 10, (
f"expected the 12 required contexts to resolve to 10 distinct jobs (the 3 `test` legs share one "
assert examined == 11, (
f"expected the 13 required contexts to resolve to 11 distinct jobs (the 3 `test` legs share one "
f"matrix job); got {examined}. If the workflow layout genuinely changed, update this count."
)
assert not offenders, (
Expand Down
Loading