Orchestrata Ultimate › Gates › validate-ci

Gate: validate-ci

At a glance

Purpose Refuse a .gitlab-ci.yml / docker/docker-compose.yml, or source feeding them, that breaks any of 14 hard requirements.
Where it runs The scripts/gates.py battery (registered as validate-ci); scripts/health_check.py.
Trigger A battery run whose fingerprint changed; a health check.
Exact command python scripts/validate_ci.py
Blocks Exit 1 on the first failed requirement (fail-fast, message on stderr); 0 with "OK: All 14 CI validation gates passed".
Ruling / ADR Gate 4: RUL-001 — no xdist. Gates 11–12: RUL-003, RUL-011 — ports from env, no defaults and the user ruling of 2026-08-03. No ADR names this gate.
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 CI configuration (.gitlab-ci.yml, docker/docker-compose.yml) — or source feeding it — that violates any of its 14 hard requirements: a test job bypassing the canonical runner, pytest-xdist, hardcoded production IPs, stale AST artifacts, and more.

Why it exists

Module docstring: "Deterministic CI configuration validator. Verifies .gitlab-ci.yml and docker-compose.yml against hard requirements." Individual gates carry their own recorded incidents — e.g. Gate 11's comment names "the exact shape of the bug a 2026-08 audit found in scripts/e2e_run.py and scripts/refactor.py (os.environ.setdefault('REDIS_HOST', '10.53.1.10'), the PRODUCTION Redis)". The gate's own caching has a recorded incident too (gates.py registry comment):

"Fingerprinting only _SRC meant editing .gitlab-ci.yml or Dockerfile.ci left the gate cached as 'unchanged since last PASS' and skipped -- the exact false-green the cache exists to prevent. Caught 2026-08-07 while changing both files."

(An earlier version of this page quoted that comment with docker-compose.yml in place of Dockerfile.ci; the text above is the comment as it stands in scripts/gates.py, read through the AST toolkit on 2026-10-23.)

The 14 gates (from the module's own section markers)

# Requirement
1 YAML parseable
2 Required stages present
3 No stale coverage path
4 pytest-xdist is BANNED ("deadlocks this suite ~78%")
5 Timeout present
6 Service containers or env vars for Redis/Neo4j
7 Architecture checker or mypy present
8 Docker Compose uses env vars, not hardcoded IPs
9 Test-execution jobs must invoke the canonical runner (scripts/run_tests.py)
10 AST outputs exist and are not stale
11 No hardcoded PRODUCTION 10.53.1.x connection literals in scripts/src
12 No literal fallback for REQUIRED infra-connection env keys (user ruling 2026-08-03)
13 Every env-profile overlay loader honours ONE ownership set
14 Env-key ownership claims must have a real declare_owned()

Gate 9's matcher is deliberately precise: it fires on real test execution (pytest, the threadpool runner, or the coverage target) "without also calling scripts/run_tests.py", and deliberately NOT on a bare tests/ path — "lint/static jobs (ruff/black/mypy over 'src/ tests/') legitimately mention that path without executing the suite, and must not trip this gate."

Gate 11 is line-scanned with comment lines skipped, "so explanatory comments/docstrings referencing a retired or historical address (there are many, deliberately) do not trip this gate — only a literal actually wired up as a connection default."

How it runs

Typical failures and how to fix them

Wiring