Home / Projects / Orchestrata Ultimate / Decisions / ADR index / ADR-0025
ADR-0025: The governance rulebase bans dynamic predicate construction
| field | value |
|---|---|
| Status | Accepted |
| Date | 2026-07-24 |
| Register | factory (docs/adr, 4-digit ids) |
| Source | docs/adr/0025-ban-dynamic-predicate-construction.md — permalink at bdafa201a (unchanged on master since that commit) |
| Wiki register entry | ADR-0025 (wiki status: Accepted) |
The record
- Status: Accepted
- Date: 2026-07-24
- Spec: SPEC-033 §1, §4
Context
Two independent mechanisms depend on statically analysing the rulebase's call graph:
- ADR-0006 — projection required-predicates are derived by static analysis, transitively through the Prolog call graph, and a completeness meta-check runs before the rulebase evaluates.
- ADR-0010 — the stratification meta-lint must prove that no constitutional invariant depends (via
negation, or positively in a permit rule) on an
extension_point/1predicate.
Both are unsound if the call graph is not statically knowable. call/N on an unbound goal, and
assert/retract, make the set of predicates a rule may consult undecidable at analysis time — so the
projection can silently under-select (false green, ADR-0006's exact failure) and the stratification lint
can pass a rule whose real dependencies it never saw.
This constraint is load-bearing for two mechanisms and appears nowhere in the spec.
Decision
In the governance rulebase (constitution, patterns, project rules — not application code):
- Ban
call/Non unbound goals and any dynamic goal construction. - Ban
assert/retract— facts come from the projection (ADR-0005), never from rule side-effects. - Ban the cut (
!) — it makes rule meaning order-dependent, which defeats reordering, caching, partial evaluation and independent testability. - Enforce all of the above in the meta-lint (task B17), alongside stratification.
Alternatives rejected
- Leave it implicit — the status quo; both dependent mechanisms are then unsound in a way that fails silently rather than loudly.
- Analyse dynamically instead — a runtime-discovered call graph cannot support a pre-evaluation completeness check, which is the whole point of ADR-0006.
Consequences
- Governance rules become statically analysable, decidable, reorderable, and independently testable.
- Some expressiveness is lost; if a policy genuinely needs dynamic goal construction, that is a signal the logic belongs in application code, not in governance.
Note — the engine question this raised is settled (ADR-0027)
Prior-art research observed that these restrictions are exactly the static checks production policy engines enforce natively (Soufflé's stratified negation, Rego's recursion ban, Cedar's decidable SMT fragment), and asked whether hand-rolling them meant Prolog was the wrong engine.
ADR-0027 settles that: we retain Prolog. Rego and Cedar are disqualified because they remove recursion, which our transitive-closure knowledge-graph queries require; Soufflé has no incremental evaluation, which SWI's incremental tabling already gives us in production. So this ADR stands on its own merits — the ban is what makes ADR-0006's projection-completeness check and ADR-0010's stratification lint sound — and not as a symptom of a wrong engine choice.
Current state of compliance
A survey of the 16 rulebases (~3,096 lines) on 2026-07-24 found the discipline largely already practised:
zero call(, and assert/retract appearing almost entirely in comments documenting that Python
asserts only ground facts. The outstanding remediation backlog is 121 \+ occurrences requiring
stratification review and 18 cuts to remove.
Provenance
Round-3 Opus (#12) and Fable (C-6), both noting the constraint is load-bearing for two mechanisms and stated nowhere. The engine question, raised by the 2026-07-24 prior-art research, is resolved in ADR-0027.
Related ADRs
Rulings cited
None.