Orchestrata Ultimate › Gates › env-contract

Gate: env-contract

At a glance

Purpose Refuse a production entrypoint that reaches an infra-config read without reaching a registered env loader, and a second mutating dotenv loader under src/.
Where it runs The scripts/gates.py battery (registered as env-contract); scripts/health_check.py.
Trigger A battery run whose src/ fingerprint changed; a health check.
Exact command python scripts/check_env_contract.py [--repo-root PATH] [--max-depth N] [--json]
Blocks Exit 1 on any violation, 2 when the tree cannot be analysed, 0 clean.
Ruling / ADR RUL-019 (gate scripts/check_env_contract.py RUL-019); the endpoint keys it triggers on are RUL-003's.
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 production entrypoint whose import closure reaches an infra-config read (a RUL-003 endpoint key, or a raising resolver seam) without ever reaching a registered env loader — and, in the other direction, a second mutating dotenv loader appearing under src/.

Why it exists

The module docstring quotes the ruling verbatim:

"RUL-019 — Config modules use Pydantic BaseSettings, and env files must be auto-loaded by every entrypoint via _load_dotenv() (secrets-only .env, then the active ORCHESTRATA_ENV_PROFILE overlay env/{dev,test,prod}.env with override=True) — never left to direnv or manual sourcing."

And records the measured state that made it mechanizable:

"There is NO canonical two-phase loader in src/ … src/ carries three DIVERGENT copies, none of which does the profile overlay … Two production entrypoints reach a REQUIRED raising infra seam and load nothing: agents/runtime_main.py … and knowledge_graph/server.py … Both are launched by systemd ExecStart under deploy/ and survive only on EnvironmentFile= injection. Verified empirically, not theoretically: env -i .venv/bin/python -m orchestrata_ultimate.agents.runtime_main → rc=1, CXPConfigError '…define one in .env.' The process names .env in its own failure message and never reads it."

The ruling's other clause ("config modules use Pydantic BaseSettings") is explicitly NOT mechanized: "It has no falsifiable subject … That clause stays a second none (judgment) bullet on the ruling page."

How it runs

Typical failures and how to fix them

Wiring