Orchestrata Ultimate › Gates › Suite preflight

Gate family: Suite preflight

At a glance

Purpose Refuse to start (or to trust) a suite run that would test the wrong source, an unwired fleet, or a parallel-unsafe or impure test corpus.
Where it runs scripts/run_tests.py::_main, before the test phase: freshness guard, affected resolution (for --affected), start_test_services.py, _run_gates(), the RUL-032 green ledger, then the capped test phase and the solo-retry round.
Trigger Every run_tests.py invocation, including the pre-push hook's run_tests.py --affected <range>.
Exact command python scripts/run_tests.py [paths] [--affected REV] [--affected-decision TEXT] [--solo-retry-cap N] [--skip-freshness] [--skip-infra] [--skip-gates]
Blocks Any static gate failing, a freshness abort, a required service (the kernel above all) failing, or an --affected refusal stops the run before tests execute.
Ruling / ADR User ruling 2026-07-10 ("no violations allowed"); RUL-032 — tests run once per code state; RUL-040 — scope measured at symbol granularity; RUL-010; RUL-001; ADR-0088 — one shared green ledger per repo; ADR-0111 — test runs never create containers; ADR-0120 — test subprocesses import from the installed environment.

Diagram projects-orchestrata-ultimate-gates-family-suite-preflight-1

Every authoritative test run goes through python scripts/run_tests.py, and before a single test executes, that runner does two kinds of gating:

  1. Infrastructure preflight (scripts/start_test_services.py) — is the real fleet actually up and wired?
  2. Static safety gates (run_tests.py::_run_gates) — five blocking, read-only analyses that refuse to start a run that would produce a worthless or flaky verdict.

Both exist for the same reason: a suite verdict is only evidence about the world it actually ran against. A run against the wrong checkout, an unwired kernel, or a parallel-unsafe test corpus produces a green or red that means nothing.


1. Infrastructure preflight — scripts/start_test_services.py

From its module docstring:

"Checks all four services (Redis, Graph, Gateway, Kernel) in parallel. Starts any that are down, verifies each with a functional test, and prints a colour-coded report. Idempotent — safe to run any number of times.

A fifth, non-parallel check (Link) runs after the four above have settled: it asks the gateway, over its real WebSocket transport, whether it is actually wired to the kernel over gRPC (gateway.info's "kernel" block) — because 'the gateway answers gateway.ping' and 'the kernel accepts a connection' can both be true while the gateway is silently running against an in-process fallback with zero real traffic reaching the kernel."

python scripts/start_test_services.py            # Check + start + verify
python scripts/start_test_services.py --status   # Check only (no start)
python scripts/start_test_services.py --json     # JSON report
python scripts/start_test_services.py --skip KERNEL,GATEWAY

The kernel is REQUIRED. Project law (CLAUDE.md Key Rules, docs/kernel-is-required.md): the Agent Runtime kernel (runtime_main, gRPC on CXP_GRPC_PORT/50053) is marked required=True by this script; its failure aborts the run. The only sanctioned opt-out is an explicit --skip kernel, which makes the run non-authoritative — a suite run that does not start and gRPC-verify the kernel is invalid/false-green, because kernel-dependent paths go silently untested.


2. The five static safety gates — run_tests.py::_run_gates

From the function's own docstring: "Run blocking static gates… all BLOCKING (user ruling 2026-07-10: no violations allowed)… All … gates are independent, read-only static analyses with disjoint outputs, so they run CONCURRENTLY (a ThreadPoolExecutor around the subprocess.run calls…). Still fully blocking: any gate failing fails the run."

An honesty note on counting: the docstring enumerates four gates, but the code's gates list carries five — source provenance was added at the head of the list after the docstring was written, with its own inline rationale ("First, because every other gate's verdict is meaningless if the suite is about to import a different checkout's source than the one it is testing").

--skip-gates skips this stage entirely (for iteration, never for an authoritative run).

2.1 source-provenance — scripts/check_source_provenance.py

Refuses: a suite run whose imported package was not installed from this checkout, or resolves anywhere but site-packages.

Updated 2026-10-23. Since 9b4ac72b4 (2026-10-21, "proper-install: re-base the source-provenance gate on PEP 610 direct_url.json") the "resolves to src/" check described in the rest of this section is gone; _install_origin()'s docstring calls it "permanently false" now that the repo installs non-editable into site-packages. main() makes two checks (read through the AST toolkit):

  1. Install origin. The url in the orchestrata-ultimate distribution's PEP 610 direct_url.json must equal this checkout's file:// URI. Otherwise it prints "THE SUITE WOULD TEST THE WRONG SOURCE TREE", the installed from origin, and "Another checkout re-synced this venv. Fix: run uv sync --dev --no-editable from THIS checkout." and exits 1.
  2. Resolves under site-packages. The imported package's directory must have site-packages among its path parts. Otherwise it prints expected source and actual source, any shadowing sys.path entries and PYTHONPATH, and exits 1. When the two source lines are identical, this checkout's own src/ won the import — the editable-install case a plain uv run re-sync produces (derived from the code).

The full gate page is source-provenance.

Why it exists (docstring, verbatim incident): "On 2026-07-27 the 'final gate' run in a worktree reported 30 failures that were pure artifacts: an exported PYTHONPATH=/home/mohamed/workspace/orchestrata-ultimate-architecture/src leaked into the run and landed at sys.path[1], outranking the worktree's own editable .pth entry at sys.path[6]. The run executed the worktree's TESTS against a DIFFERENT BRANCH's SOURCE. Both directions of that verdict were worthless."

How it runs: imports the package the way the suite does, resolves where it came from, fails when it did not come from this repository's src/. Exit 0 = resolves inside this checkout; exit 1 = resolves elsewhere or cannot be imported at all. It is deliberately process-global (it verifies THIS interpreter's import path), so it takes no target-tree argument.

Tests: tests/scripts/test_check_source_provenance.py, tests/scripts/test_check_source_provenance_errors.py.

2.2 parallel-safety — scripts/gate_parallel_safety.py (sub-gates 0.1–0.6)

Also a registered battery gate — full detail on its own page. Summary of the sub-gates, from the script's own section markers:

Sub-gate Detects
0.1 Cypher CREATE on constrained labels without test_id
0.2 Cypher DETACH DELETE / global edge DELETE without scope
0.3 Session-scope fixtures (mutable state shared across tests)
0.4 Redis writes without test_id namespacing
0.5 Filesystem writes outside isolation — ADVISORY ("static analysis cannot prove a write target")
0.6 wait-for-port guards ("a shared port wearing a disguise")

Waivers: a finding on a line carrying (or directly below) # gate-ok: <reason> is suppressed; "a reason is required; a bare # gate-ok: does not waive." In the preflight, the runner can point it at a target tree via --tests-dir (integration- test seam only; production always analyzes the full tree).

Tests: tests/scripts/test_gate_parallel_safety.py.

2.3 test-purity — scripts/audit_test_purity.py

Also a registered battery gate — full detail on its own page. In the preflight it runs over tests/ and blocks on any mock/fake/stub/monkeypatch/skip pattern. The _run_gates docstring records why it joined the preflight: it "previously only ran inside the manual health_check, so its findings could not block a suite run."

Tests: tests/scripts/test_audit_test_purity.py.

2.4 prolog-boundary — scripts/check_prolog_boundary.py

Also a registered battery gate — full detail on its own page. Preflight role (per _run_gates): "every Python Prolog query site must target a predicate/arity that exists in the consulted rule base" (reports/proposal-wire-prolog-governance.md §7 Phase 1).

Tests: tests/scripts/test_check_prolog_boundary.py.

2.5 rulings-lint — scripts/rulings_lint.py

Refuses: a ruling in the knowledge-wiki rulings canon that declares no real enforcement mechanism.

Why it exists (docstring): "Rulings enforcement lint — closes the prose-only loophole (SPEC-023 Phase 2)… A ruling with no ## Enforcement section, or with a declared mechanism that does not actually exist, is a FAILURE (exit 1) — this is the mechanism that keeps a ruling from silently regressing to prose-only (RUL-014)."

How it runs: reads rulings/index.md + rulings/RUL-*.md from the git-backed wiki store on the filesystem (default ~/workspace/knowledge-wiki/rulings, override via RULINGS_DIR) — "a live Wiki.js server must never be a dependency of this gate." Each ruling's ## Enforcement bullets must match one of five grammars:

- hook <path>                    file must exist and be executable
- gate <script> <rule-id>        script must exist AND <rule-id> must be a literal substring of the script text
- banned-deps <name>             <name> must be a (non-comment) line in .claude/banned-libraries.txt
- constitution <article-text>    literal substring of src/orchestrata_ultimate/prolog/constitutional.pl
- none (judgment)                always passes; reported as judgment-only

Failure semantics: exit 1 on a prose-only or falsely-declared ruling. Missing RULINGS_DIR is a SKIP, not a FAIL (exit 0 with a warning): "a machine that has not cloned knowledge-wiki must not go false-red on this gate — same honesty rule as --skip kernel."

Wiring beyond the preflight: scripts/check_wiring.py (W4) imports this module's rulings-directory resolution to find which scripts a ruling declares as its gate mechanism; scripts/health_check.py also invokes it.

Tests: tests/scripts/test_rulings_lint.py.


Typical failures and how to fix them

4. The --affected (pre-push) path — classification now runs before infra

Updated 2026-10-23. The order described in the table below was the state on 2026-10-21. Commit 68024751d (2026-10-21, "GATE-AFFECTED — classify affected files before infra so pure non-code pushes skip the service stack", on origin/master) moved affected resolution to step 0.75, ahead of the infra preflight. _main now runs, in order: 0 freshness guard; 0.5 basetemp prune; 0.75 _resolve_affected() → _plan_affected_run(); 1 infrastructure; 2 safety gates; 2c the RUL-032 green ledger; 3 the test phase; 4 report. At 0.75: an unbounded change with no --affected-decision aborts as REFUSED; if _affected_all_inert(resolved) is true the run prints "--affected: all changes inert (non-code) — no services or tests required; passing.", writes a canonical report with "non_code": true and the note "all changed files inert (non-code) — skipped infra + gates + smoke floor", and returns 0. The test phase prints "Selected: N tests in M modules (K modules reused from ledger) · hard cap 300s" with the default cap. The "known inefficiency" paragraph below is therefore closed. Read from run_tests.py::_main through the AST toolkit on 2026-10-23.

The pre-push hook invokes run_tests.py --affected <range> (the RUL-040 blast-radius gate). That path runs the SAME preflight as a full run, then resolves the changed files to a test subset. The order, measured in run_tests.py::_main (2026-10-21, before 68024751d):

Step What Cost
0–0.5 freshness guard, basetemp prune fast
1 infra preflight (start_test_services.py --ensure-worker-dbs=N) ~3 min, unconditional
2 five static gates fast
2b _resolve_affected → affected_tests.py — the changed-file verdict fast, no services needed
3 resolved subset / smoke floor minimal for non-code

The classification (2b) is pure git + static analysis and needs no services — yet it runs after the infra preflight (1). So a pure non-code push (docs, skills, reports, config) still boots the kernel + graph + gateway and provisions all worker databases, then resolves the files as INERT and runs only a smoke floor.

Known inefficiency (filed 2026-10-21, closed by 68024751d the same day): a pure non-code push could short-circuit — run affected_tests.py first; if every changed file is INERT, skip infra (step 1), the static gates (step 2), and the smoke floor entirely, and pass. The reordering is a small change to run_tests.py::_main (move step 2b ahead of step 1). Tracked in plans/briefs/gate-affected-classify-before-infra.md.