Orchestrata Ultimate › Gates › parallel-safety
Gate: parallel-safety
At a glance
| Purpose | Refuse test code that will collide under parallel execution, found statically by six sub-gates (0.1–0.6) without running a test. |
| Where it runs | The scripts/gates.py battery (registered as parallel-safety) and the suite preflight _run_gates() in scripts/run_tests.py. |
| Trigger | A battery run whose tests/ fingerprint changed; every suite run's preflight. |
| Exact command | python scripts/gate_parallel_safety.py (--gate 0.N, --json, --strict, --tests-dir PATH) |
| Blocks | Exit 1 when any blocking sub-gate (0.1, 0.2, 0.3, 0.4, 0.6) has a finding; 0.5 is advisory unless --strict. Exit 0 otherwise. |
| Ruling / ADR | User ruling 2026-07-10, "no violations allowed" (quoted in the _run_gates() docstring); none recorded as a RUL page or ADR. Neighbouring: RUL-001, RUL-002. |
| Last recorded verdict | ok: true in the primary checkout's .cache/gates.json (file last written 2026-09-22 10:01 local). wt-MAP-PRECOMMIT has no .cache/gates.json. |
What it refuses
A test pattern that is guaranteed to collide when the suite runs in parallel: an
unscoped Cypher CREATE on a constrained label, a global DETACH DELETE, a
session-scope fixture holding mutable state, an un-namespaced Redis write, a shared
port disguised as a wait loop.
Why it exists
Module docstring:
"Gate: Parallel Safety Audit — deterministic static analysis for test flakiness. …gates that detect guaranteed parallel collisions without running tests. Each gate finds patterns that WILL cause failures under parallel execution (threadpool runner, multiple workers sharing the graph/Redis/filesystem)."
The suite runs 8 workers over shared real infrastructure (no mocks), so isolation is
carried by conventions — test_id properties on graph nodes, test_id-namespaced
Redis keys, function-scope fixtures, per-test tmp dirs. This gate makes those
conventions mechanical. A single originating incident is not recorded in the module
docstring — origin not recorded in the source beyond the flakiness class it
names.
The sub-gates (0.1–0.6, from the script's own section markers)
| Sub-gate | Detects | Blocking? |
|---|---|---|
| 0.1 | Cypher CREATE on a constrained label (File, Project, Module, Intent, Session, Spec, Artifact, Portfolio, WorkItem, Checkpoint, Verification, Run, Requirement) without a test_id: property |
yes |
| 0.2 | Cypher DETACH DELETE / global edge DELETE without scope |
yes |
| 0.3 | Session-scope fixtures (mutable state shared across tests — the repo law is "function-scope fixtures only") | yes |
| 0.4 | Redis writes without test_id namespacing |
yes |
| 0.5 | Filesystem writes outside isolation | ADVISORY — the script's own note: "Gate 0.5 (filesystem) is ADVISORY: static analysis cannot prove a write target" |
| 0.6 | wait-for-port guards — "a shared port wearing a disguise" | yes |
How it runs
- Commands:
python scripts/gate_parallel_safety.py # all gates, exit 1 on violation python scripts/gate_parallel_safety.py --gate 0.1 # single gate python scripts/gate_parallel_safety.py --json # JSON output for CI python scripts/gate_parallel_safety.py --strict # advisory 0.5 blocks too - When it fires: twice per authoritative flow —
- the
scripts/gates.pybattery (registered asparallel-safety, argv[sys.executable, "scripts/gate_parallel_safety.py"], no--strict); - the suite preflight (
scripts/run_tests.py::_run_gates, blocking — "user ruling 2026-07-10: no violations allowed"), where the integration-test seam can point it at a fixture tree via--tests-dir._run_gates()now runs five gates concurrently in aThreadPoolExecutor: source provenance, parallel safety, test purity, prolog boundary and rulings lint (its docstring still says "Four gates"; the list in the code has five, read through the AST toolkit on 2026-10-23).
- the
- Inputs: fingerprint root
("tests",). - Caching: the verdict is cached in
.cache/gates.jsonagainst_fingerprint(("tests",)): a sha256 over the relative path and bytes of every file undertests/whose suffix is.py,.toml,.pl,.json,.mdor.sh(_interesting()inscripts/gates.py). There is no per-gate suffix mask: this page previously quoted one, andgit log --all -S GATE_MASKS -- scripts/gates.pyfinds no commit that ever contained aGATE_MASKStable (checked 2026-10-23). The preflight run is never cached.
Waivers
From the docstring — the one battery gate with a disciplined waiver channel:
"A finding on a line carrying (or directly below) a
# gate-ok: <reason>comment is suppressed. This exists for tests whose FIXTURE DATA is deliberately-unsafe Cypher/source fed to a tool under test (preflight checker, isolation auditor, code reviewer) — the string is analyzed, never executed. A reason is required; a bare# gate-ok:does not waive."
Two readers apply the convention inside this script (refs through the AST
toolkit, 2026-10-23). An earlier version of this page said the shared
core.gate_waivers.waived_lines was the one reader; in the code it serves
sub-gate 0.4 only. Sub-gates 0.1, 0.2, 0.5 and 0.6 call the script's own
_is_waived(), whose regex #\s*gate-ok:\s*\S checks the flagged line and the
line above it, so a bare tag does not waive there either. No waiver call was found
in the 0.3 session-fixture audit.
Typical failures and how to fix them
- Exit codes: "Exit code 0 = all gates pass, 1 = violations found."
main()returns 1 only when a blocking sub-gate has findings; 0.5 findings print asWARN (n)and do not change the exit unless--strictis passed.--jsonoutput keeps the same exit code. - What a failure looks like (representative): per finding — the sub-gate id, the
test file and line, and the offending pattern (e.g. a
CREATE (:Session {...})with notest_id:in its property map). - Un-blocking: add the
test_idscoping / narrow the delete / drop the fixture to function scope — or, for genuine fixture-data cases only, a# gate-ok: <reason>with a real reason. - A suite run that stops before any test starts. The preflight runs this gate
with the other four; any one failing fails the run. Read the preflight's gate
lines, fix the finding, and re-run. Running the gate alone first
(
python scripts/gate_parallel_safety.py --gate 0.N) is the quick loop.
Wiring
- Invokers:
scripts/gates.py(battery),scripts/run_tests.py(preflight). - Related auditors:
scripts/test_isolation_audit.py(shared state / hardcoded names / missing test_id, the broader isolation sweep) andscripts/flaky_test_hunter.py(dynamic detection by repeated runs) — this gate is the static, blocking member of that trio. - Tests:
tests/scripts/test_gate_parallel_safety.py.
Related
- Gates master index
- Development workflow
- Suite preflight family
- test-purity — purity rather than flakiness, same preflight
- source-provenance and rulings-lint — the other preflight gates
- prolog-boundary
- RUL-001 — No xdist
- RUL-002 — No mocks, stubs, fakes, skips or monkeypatch