Orchestrata Ultimate › Gates › prolog-boundary
Gate: prolog-boundary
At a glance
| Purpose | Refuse a governance-shaped Python Prolog query whose predicate/arity no consulted .pl file declares, and an un-whitelisted dynamic query. |
| Where it runs | The scripts/gates.py battery (registered as prolog-boundary), the suite preflight _run_gates() in scripts/run_tests.py, and scripts/health_check.py. |
| Trigger | A battery run whose src/ fingerprint changed; every suite run's preflight; a health check. |
| Exact command | python scripts/check_prolog_boundary.py (--source PATH for the preflight's test seam) |
| Blocks | Exit 1 on a missing predicate/arity or an un-whitelisted dynamic site; 0 otherwise. The advisory section never changes the exit. |
| Ruling / ADR | reports/proposal-wire-prolog-governance.md §7 Phase 1 (the brief); no RUL page or ADR names this gate. Neighbouring: ADR-0025 — the governance rulebase bans dynamic predicate construction. |
| 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 Python query site (query/query_once/query_bool/query_one on a
PrologBridge/PrologEngine/janus-shaped receiver) whose target predicate/arity does
not exist in the consulted .pl rule base — or an unresolvable dynamic query in a
governance-shaped directory with no justified whitelist entry.
Why it exists
Module docstring:
"Proves every Python Prolog query site targets a predicate/arity that actually exists in the consulted rule base. Phase 1 of
reports/proposal-wire-prolog-governance.md§7: a mechanical contract between the 42+ hand-written Python query strings acrosssrc/orchestrata_ultimateand the hand-written.pldeclarations they target. Eight of those sites were found querying predicates that do not exist at the queried arity/argument order (F2/F3/ F4/F6 in the report) — four of them failing silently OPEN. This gate closes that residue."
How it runs (design decisions, all binding per its brief)
- Command:
python scripts/check_prolog_boundary.py(accepts--sourcefor the preflight's integration-test seam). - When it fires: three places — the
scripts/gates.pybattery (registered asprolog-boundary), the suite preflight (run_tests.py::_run_gates), andscripts/health_check.py. - Query-site discovery is an AST walk, never a regex — including the two
indirect shapes real code uses: "a local variable assigned an f-string/literal a
few lines earlier, then passed BY NAME to the seam call, OR passed as a bare
(uncalled) method reference into
asyncio.to_thread/loop.run_in_executoralongside the query text… a same-call-site literal-only rule would miss the very sites this gate exists to catch." - The declaration side is the real engine, not a second parser: "The shipped
.plfiles are consulted into a real janus-swi engine (starting fromgovernance.prolog_client.load_governance_rules, the exact production loader, then every remaining*.plunderPROLOG_RULES_DIR…). Existence is decided withcurrent_predicate(_:F/A)— module-aware… so a predicate declared ingovernance,pam,multiagent, or the defaultusermodule all resolve." - Scope: a site only counts "if it is governance-shaped: its functor exists at
ANY arity anywhere in the consulted corpus, OR its file lives under one of the
five directories the brief names (governance/, kernel/, orchestrator/, postman/,
agents/). Everything else (Neo4j
.query()calls, Cypher text…) is silently out of scope." - Dynamic functors in governance-shaped directories "MUST have an explicit,
justified entry in
WHITELIST… an un-whitelisted one is itself a hard failure." The whitelist is keyed by (path, qualified function name), not line number — a line-keyed suppression "drifted TWICE during this gate's own development." - Known limitation, documented not solved: "F3-class argument-ORDER swaps (same functor, same arity, different argument semantics…) are invisible to an arity-only check… documented as an accepted gap."
- Inputs: fingerprint root
("src",). - Caching: the battery verdict is cached in
.cache/gates.jsonagainst_fingerprint(("src",)): a sha256 over the relative path and bytes of every file undersrc/whose suffix is.py,.toml,.pl,.json,.mdor.sh(_interesting()inscripts/gates.py), which covers both the query sites (.py) and the rule files (.pl). 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 and health-check runs are never cached.
Typical failures and how to fix them
- Exit codes: "1 if any governance-shaped site queries a predicate/arity that does not exist (or is an un-whitelisted dynamic site) in the consulted corpus; 0 otherwise. The advisory section (shipped-but-never-queried predicates) never affects the exit code."
- What a failure looks like (representative): the query site (file, qualified function) with the functor/arity it targets and the verdict that no consulted module declares it.
- Un-blocking: fix the query string (or the
.pldeclaration, if the rule was meant to exist); for a genuinely dynamic site, add a justifiedWHITELISTentry keyed by path + qualified name. - A whitelist entry that stops matching. Entries are keyed by path and qualified function name, so renaming or moving the function makes its dynamic site un-whitelisted again. Move the entry with the function.
- A suite run that stops before any test starts. This gate is one of the five preflight gates; any one failing fails the run.
Wiring
- Invokers:
scripts/gates.py(battery),scripts/run_tests.py(preflight),scripts/health_check.py. - Tests:
tests/scripts/test_check_prolog_boundary.py.
Related
- Gates master index
- Development workflow
- ADR-0025
- parallel-safety, test-purity, source-provenance and rulings-lint — the other preflight gates
- prolog-gate — rules over facts; this gate is queries over declarations
- Suite preflight family