Gate policy — what blocks merge, what's report-only, and the allow-skips register¶
gate.py (repo root) is the single command that must be green before trusting
the tree — see its module docstring for the full mechanics (anchored parsing,
built-artifact invocation, the full test suite). This document answers the
question gate.py --help does not: which of its components actually stop a
merge, and which are informational — plus the register that makes every
allowed skip accountable to a name and a date instead of an oral tradition.
Without this written twice, the failure mode is concrete: --allow-skips
quietly grows a new entry, nobody records why, six months pass, and the gate
reports GREEN while a real suite has been silently excluded the whole time —
"lying green" returning through the one door built to be an honest exception.
1. Blocking vs report-only¶
CI runs gate.py from two jobs in .github/workflows/gate.yml:
preflight-gate— runsgate.py --pypi delegated --allow-skips wp-conformanceinline, against the PR's own tree.smoke-pypi— a sibling job that runssmoke-pypi.ymlviaworkflow_call;gate.pyitself only printsCOMPONENT pypi-smoke: DELEGATEDfor this leg (seerun_pypi_smoke()).
Rule: only preflight-gate may be a required status check on branch
protection. smoke-pypi answers "are the already-published PyPI packages
healthy right now", not "is this PR correct" — nothing in a PR's diff can fix
a broken published release, so treating that job as merge-blocking would stop
all work on an unrelated axis (the exact tension the ticket that produced this
document was opened to resolve). smoke-pypi stays report-only: its
result is read at release time and by whoever is chasing a PyPI incident, not
enforced on every PR.
Inside preflight-gate itself, every COMPONENT line gate.py emits is
blocking by default:
| Component | What it runs | Blocking? |
|---|---|---|
rego-core |
opa test reeflex-core/policy/ |
yes |
rego-claude |
opa test reeflex-claude/policy/ |
yes |
unittest-core |
full unittest discover over reeflex-core/tests |
yes |
pytest-mcp / pytest-holds / pytest-claude |
pytest tests/ -q per package |
yes |
npm-n8n |
npm ci && npm test in n8n-nodes-reeflex/ |
yes |
entrypoints |
build wheels from the tree + invoke every published console script | yes |
drift |
fails if a test file exists outside every enumerated suite root | yes |
pypi-smoke |
fresh install from PyPI of the published packages | report-only — always DELEGATED in CI (see above); only run/skip locally |
wp-conformance |
4 PHP live-core harnesses against a real reeflex-core |
yes, when it runs — allowed to SKIP only via the register in §3 |
wp-spec-conformance |
the SPEC conformance vectors (reeflex-spec/conformance/) driven through the WordPress normalizer — no live core, no network |
yes — skips only where there is no php at all |
wp-conformance |
5 PHP live-core harnesses against a real reeflex-core (4 behavioural + conformance-decisions.php, the RFX-167 decision-level corpus) |
yes, when it runs — allowed to SKIP only via the register in §3 |
A component that FAILs turns the gate RED (exit 1) — always blocking,
no exception. A component that SKIPs without a matching --allow-skips
entry turns the gate INCOMPLETE (exit 3) — also blocking, by design (§14.6
of the WoW standard: a suite that quietly stops running is not a green gate).
Only a skip that is BOTH printed AND listed in --allow-skips lets the gate
report GREEN despite it — which is exactly why §2 exists.
2. The allow-skips discipline¶
Every --allow-skips <key> entry that ships in gate.yml (or in a release
preflight command) must have a matching row in the register below, carrying a
reason and a date. A PR that adds or changes an --allow-skips flag and
does not add/update the matching row is incomplete review bait, not a valid
change.
The register is re-read, not just written once:
- At every PR that touches
--allow-skips,gate.yml, orgate.py's skip logic — the reviewer checks the reason is still real, not rubber-stamped. - At every release preflight — a release is exactly the moment a
structurally-unrunnable-here component (like
wp-conformancein CI) most needs its live-environment counterpart actually exercised somewhere before publishing. - At the next checkpoint that touches the owning suite — if the suite behind an entry changes materially, the entry's reason is re-verified, not assumed to still hold.
A reason must state what makes the component structurally unrunnable in that context (missing live dependency, delegated by design) — never "it was failing and we didn't have time," which is a FAIL waiting to be reclassified as a skip.
3. Allow-skips register (current)¶
| Component | Reason | Added | Re-confirmed | Upgrade path |
|---|---|---|---|---|
wp-conformance |
Needs a live reeflex-core instance — the 4 PHP harnesses (conformance-demo.php, admin-holds-demo.php, fanout-regression-demo.php, hold-dedup-regression-demo.php) POST real /v1/decide / /v1/holds calls against a running engine; CI does not provision one today. See reeflex-wordpress/tests/README.md. |
2026-08-11 (PR #73) | 2026-08-20 (PR #77 / RFX-27 — scope widened from 1 harness to all 4 under the same key; all 4 measured PASS against a real local reeflex-core) |
Stand up an ephemeral reeflex-core service in gate.yml (a background process or service container) and pass --core-url, turning this from an allowed skip into a real PASS. Not scheduled; flag if picked up. |
pypi-smoke |
Delegated by design to smoke-pypi.yml via workflow_call — one copy of the fresh-install smoke, not two (DoD 4). It also structurally answers a different question than a PR review: the health of the last published release, not the tree under review — see §1 for why this also keeps it out of the required-status-check set. |
2026-08-11 (PR #73) | 2026-08-20 (PR #77 / RFX-27) | No upgrade path — this is a permanent-by-design delegation, not a placeholder gap. |
No other component is currently registered. A SKIPPED line for anything not
in this table is, by construction, not allowed and forces INCOMPLETE.
4. Adding a new allow-skips entry¶
- Add the row to §3 in the same PR that adds or widens the
--allow-skipsflag. - State the reason in terms of what is structurally unrunnable here (a missing live dependency, a delegated-by-design duplicate) — not convenience.
- State an upgrade path, or explicitly "no upgrade path — permanent by
design" (as
pypi-smokeis). - Get it reviewed like any other gate change. The reviewer's job is to confirm the reason is real, not to rubber-stamp a red suite into a quiet skip.
5. .mcpb distribution — open, owner decision¶
Whether Reeflex ships a .mcpb bundle build across a CI matrix (Windows /
macOS / Linux) is a product decision, not a mechanical one: zero .mcpb
assets have shipped across 15 releases to date, and the would-be matrix job
is deliberately not wired into CI, gated on that decision. This document
covers gate mechanics only (§1-§4) and does not resolve it — it remains open,
tracked on RFX-13, for the owner. If the answer becomes YES, the follow-up
ticket should also decide, under §1's framework, whether the .mcpb build is
blocking or report-only. If NO, the dormant scaffolding and the .mcpb
mention in the launch narrative should be removed together.