What main requires here, and what each context proves

MeshWeaver.Plugins is protected by CLASSIC branch protection, not a ruleset. Measured 2026-09-11: repos/Systemorph/MeshWeaver.Plugins/branches/main/protection answers 200 with a required_status_checks block, and the repository's one ruleset (19008635, Copilot review for default branch) carries only deletion, non_fast_forward and copilot_code_reviewno required_status_checks rule. So the classic endpoint is the whole answer here, which is the opposite of the platform repo, where it answers 404 and the rulesets hold everything (Repositories.md).

strict: false — a branch merely behind main still merges. enforce_admins: truethere is no override, not even for an owner.

The seven

Context (verbatim) Published by What it proves that nothing else does
Validate node repos ci.yml job validate-name-shim, name: Validate node repos The legacy name, kept alive by a shim so the rename of 2026-09-08 left no unguarded window. Same verdict as the row below.
validate / Validate node repos ci.yml job id validatenode-repo-validate.yml job Validate node repos Repo-shape policy: workflow timeouts ≤ 45 min, the PR-secret preflight, allow-file ratchets, manifest/lock freshness.
Compile every NodeType (vs core) ci.yml job compile-check, name: Compile every NodeType (vs core) Every Source/*.cs compiles against the resolved platform. It never runs a test.
Build + test the portal hosts ci.yml job portal-hosts-gate, name: Build + test the portal hosts The ~3,000 .NET tests of the 48 portal-host projects. Required since 2026-09-01, after a PR merged with it red and broke main for five hours.
Repo policy gates ci.yml job repo-gates, name: Repo policy gates This repo's own policy scripts.
Module bundles / All selected bundles built ci.yml job id modules-floor, name: Module bundlesnode-repo-module-pack.yml job All selected bundles built Every selected module bundle was actually built. Like the row below it is an AGGREGATE by design — the per-module legs come and go with the selection.
test-repos / Compile + render node repos (MeshWeaver from ACR) ci.yml job id test-repos (it carries no name:) → node-repo-gate.yml job verify, name: Compile + render node repos (MeshWeaver from ACR) The only lane that EXECUTES a NodeType's Tests layout area. Imports every node repo into a real mesh, asserts each NodeType reaches CompilationStatus.Ok, renders it, and runs its Tests area green. Required 2026-09-11.

🚨 Read that middle column carefully — it is the point of this page. A reusable-workflow call publishes <caller job> / <inner job name>, and the caller contributes its name: if it has one and its job id otherwise. test-repos has no name:, which is why the context begins with a lowercase hyphenated id; modules-floor does have one, which is why its context begins Module bundles and not modules-floor. Two jobs in the same file, two different rules, and neither string appears anywhere in the workflow as a literal.

Note what is not in the list and why it does not have to be: tests-ratchet (tests=skipped is debt) runs if: always() off test-repos and reads the gate log; it is a consumer of the same evidence. Gate shard i/n is deliberately never required — the shard count is an input, and a repo that required Gate shard 1/1 would wait forever the day the fan-out widened. The aggregate verify job folds the shards and refuses a run whose slices are not a disjoint cover of the discovered total, which is what makes a fan-out safe to require at all.

Why the seventh was missing, and what it cost

test-repos existed and ran on every PR — it simply could not block a merge. On 2026-09-10 #1624 auto-merged with test-repos / Compile + render node repos (MeshWeaver from ACR) reporting completed / failure, and every other required context green. That is the same shape as the 2026-09-01 incident that made Build + test the portal hosts required: a lane that measures something real, reports honestly, and decides nothing.

The property at stake is specific. compile-check compiles Source/; it never runs a test. validate's two check-test-suites steps prove the ratchet can fail and that the allow file only shrinks; neither executes a Tests area. Between #1040 retiring the old job and #1075, Store/Installer was red on main for five days (213/214, deterministic) behind an entirely green required-check wall, found only because somebody ran the gate by hand.

The measurement that justified requiring it

🚨 Under classic protection, adding a required context is either free or repo-wide fatal, and the only thing that separates the two cases is whether that exact name is ALREADY being published on every PR. A required context with no check-run at all blocks forever — "Expected — Waiting for status to be reported" — with a full green wall and no red check to point at, and with enforce_admins: true nobody can merge past it, including the change that would fix it. A skipped context, by contrast, counts as satisfied. See Renaming a Required Status Check (get Doc/Architecture/RenamingARequiredCheck).

So the name was measured before the protection was touched, over the last 45 merged pull requests (#1574 … #1622, 2026-09-09T19:27Z → 2026-09-11T05:51Z), reading check-runs on each PR's full head sha (an abbreviated sha fails the API and the empty body reads as absence):

gh api --paginate "repos/Systemorph/MeshWeaver.Plugins/commits/<full sha>/check-runs?per_page=100" \
  --jq '.check_runs[]|"\(.name) | \(.status) | \(.conclusion)"'
outcome count (of 45)
completed / success 44
completed / failure 1 — #1624, the PR that merged red
skipped 0
never reported 0

Extended to the 17 open PRs at the time of the edit: published on every one whose ci.yml run had started (14 of 17 — the other three were mid-flight or had a workflow-level startup_failure). Denominator stated on purpose: 62 head shas examined, 59 with a started ci.yml run, 59 publications, zero skips and zero silent absences.

Three properties were confirmed structurally, not just statistically, because a rate is not a guarantee:

  1. It runs on pull_request unconditionally. ci.yml's on: carries a bare pull_request: — no paths, no branches filter. The caller job's condition is if: ${{ !cancelled() }} — not a secret test, not github.actor != 'dependabot[bot]'. It runs even when a needs: job failed.
  2. Its verdict is never a grey tick. Inside node-repo-gate.yml the verify job is if: always(), and its first step turns every "cannot measure" case into a RED that names the cause: a failed plan, a gate result that disagrees with the plan, a shard that never ran. Observed live on #1623 — plan failed, gate skipped (publishing the unresolved matrix expression as its name), and verify reported failure, not skipped.
  3. A legitimately empty run still reports success, and says why. On the Dependabot PR #1544 the plan set nothing-to-gate=true, the gate skipped, and verify concluded success having asserted that the plan said so — rather than inferring it from produced-no-evidence.

The only way the context fails to publish is a workflow-level startup_failure (16 on pull_request in the last 1,000 ci.yml runs, 14 of them clustered on four branches on the evening of 2026-09-10). That case publishes zero check-runs, so all six pre-existing required contexts are absent too and the PR is already blocked. Requiring a seventh therefore adds no new deadlock class — which is the argument that made this a safe single additive edit rather than a shim-job migration.

The edit

Additive, through the sub-resource that cannot drop the others:

gh api -X POST \
  "repos/Systemorph/MeshWeaver.Plugins/branches/main/protection/required_status_checks/contexts" \
  -f 'contexts[]=test-repos / Compile + render node repos (MeshWeaver from ACR)'

Read back afterwards: seven contexts, all app_id: 15368 (GitHub Actions), strict: false, enforce_admins: true, and every other protection setting byte-identical to the pre-edit capture.

Blast radius, measured before the edit: of 17 open PRs, exactly one moved from mergeable to blocked — #1635 (draft; test-repos red, all six older contexts green). Every other PR that the new context blocks was already blocked by an older one, or had a run still in flight.

The standing rule

Never add a required context whose exact name you have not just seen published on real PR head shas. The workflow name is not the context name, the job id is not the context name, and a reusable-workflow call publishes <caller job> / <inner job name> — three different strings, one of which is right. Confirm the literal, then edit protection; never the other way round.

The expectation is also encoded, so this decision is audited rather than merely written down: Hosting/RepoHealthRepoHealthPolicy.Fleet lists these context names for this repository, and its Fleet area reports a Critical if main stops requiring one. See Repositories.md.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.