IU Core 500x — 02 Operator-runtime substrate (migration 018)
02 — Migration 018: the governed operator-runtime substrate
1. The problem
The 500x macro shipped dot_commands.py — the DOT one-command operator
SURFACE. But its only safe action was explain (print the governed SQL,
never run it). To turn that into a guarded run the operator surface needs
a durable substrate: a place to AUDIT every runtime invocation, a SQL-visible
VOCABULARY, and a dedicated off-switch GATE. Migration 018 adds exactly that —
all additive.
2. 018_operator_runtime.sql — what it creates
| object | kind | role |
|---|---|---|
iu_core.operator_runtime_enabled |
config | the runtime APPLY gate — default false / inert |
fn_dot_iu_operator_runtime_enabled() |
function | the gate function (mirrors fn_iu_composer_enabled) |
dot_iu_command_catalog |
table | the dot_iu_* vocabulary as a QUERYABLE table |
dot_iu_command_run |
table | the append-only run LEDGER |
fn_dot_iu_command_log(...) |
function | the governed ledger append |
v_dot_iu_command_registry |
view | the operator surface DOT-visible in SQL |
v_dot_iu_command_run_health |
view | one-row operator-runtime health |
DOT: +2 tables, +2 views, +2 functions, +1 config — 106 → 113.
3. The ledger — dot_iu_command_run
One row per runtime plan / apply / verify. run_mode is
CHECK-constrained to {plan,apply,verify}; run_status to
{planned,applied,verified,refused,failed}. It carries the live
gate_snapshot and a structured evidence jsonb. Crucially params_digest
is an md5, never raw parameter values — no body text / id / secret ever
lands durably. Two btree indexes — (command_name, created_at DESC) and
(run_status, created_at DESC).
4. fn_dot_iu_command_log — fail-closed on vocabulary
The ONLY way the runtime writes the ledger. It is ungated — an audit row
(including a refused row recorded while every gate is shut) must always be
recordable. But it is fail-closed on vocabulary: once the catalog is
seeded it refuses a command_name absent from dot_iu_command_catalog. The
ledger therefore cannot record an ungoverned command — gate #8 enforced at
the SQL layer.
5. v_dot_iu_command_registry — the operator surface in SQL
The catalog LEFT JOIN'd to its run-ledger aggregates: per command, the
total_runs / applied_runs / refused_runs / failed_runs, last_run_at,
last_status. This is the durable answer to "the operator surface is itself
DOT-visible in SQL" — a UI or an auditor can query the whole vocabulary and
its activity without touching Python.
6. runtime/280 — the catalog seed, locked to Python
280_operator_runtime_catalog_seed.sql seeds the 17 catalog rows from
dot_commands.DOT_COMMANDS — the Python registry stays the SSOT, the seed
is its SQL-visible projection. test_iu_core_540x_operator_runtime.py LOCKS
the two: every (command_name, category, mutating, reversible) is asserted
to match. The seed is idempotent (ON CONFLICT (command_name) DO NOTHING).
R280 verifies catalog size 17 and that every catalogued target_function
resolves to a pg_proc row (15 distinct, all resolvable).
7. Reversibility
rollback/018 drops the 2 views, 2 functions, 2 tables and the config key
(views → functions → tables order, no dependency block) and prints a
pre-rollback row-count snapshot. runtime/rollback/280 clears the catalog
rows alone without dropping the table.
8. Production apply
018 + runtime/280 applied to the directus DB, each self-transacted.
R280_catalog = 17 rows expected_17=t; R280_targets = 15 distinct
functions all_resolvable=t. runtime/110 after apply: 113/113, every
D9 class ok=true, D8 drift 0.