DOT Schema-Write Guards — 4 Separable Guard Contracts (Macro-9B / 9B2 rev2)
DOT Schema-Write Guards — 4 Separable Guard Contracts
Artifact type: guard/reject contract (engineering). Macro-9B deliverable C4, Macro-9B2 remediation.
Status: AUTHORED — NOT REGISTERED, NOT WIRED, NOT RUN. REGISTRATION_HOLD.
Date: 2026-06-19. Macro-9B2 remediation (2026-06-20): Guard 3 is now an executable verdict (PASS/FAIL/UNKNOWN over explicit before/after evidence) that the router enforces before any real-run write-intent; Guard 4 now shares a pure _validate_target helper with Guard 1 instead of calling Guard 1 (separability restored). Evidence: dot-r2-b2-validator-test-run-v2.txt (64/64 PASS, 0 fail-open), superseding the rev1 37/37.
Companion: dot-r2-b2-staging-schema-shell.contract.md (primary DOT), dot-r2-b2-staging-schema-shell.validator.py (reference logic, rev2).
Authorizes nothing. Engineering PASS ≠ Owner authority PASS. Default = HOLD.
Four components from DOT §18 rows 2–5. Each is separately generated, inspectable, testable, replaceable, and rollbackable; they compose with the primary DOT only through the explicit dict contract (input → reject_codes / verdict / plan / audit). No shared mutable state. Guard 1 and Guard 4 share one pure, stateless helper (
_validate_target); no guard imports another.
Guard 1 — DOT_SCHEMA_WRITE_ALLOWLIST_GUARD (pre-write, fail-closed)
| Input | { target_schema, run_id } (via the router; the manual-lane/channel rejects below are enforced by the router before Guard 1) |
| Does | Validate the target schema through the shared pure _validate_target helper: reject empty/whitespace; reject any whitespace/control character (MALFORMED_SCHEMA_CHARS); reject protected/shared/production targets; require strict full-string allowlist r2_b2_wb_[a-z0-9]+(_[a-z0-9]+)* via re.fullmatch (never re.match(...$)); require the name to embed run_id. Default deny. |
| Output | reject_codes[] (empty ⇒ pass) |
| Rejects | MISSING_TARGET_SCHEMA · MALFORMED_SCHEMA_CHARS · PROTECTED_SCHEMA_TARGET (public/iu_core/cutter_governance/sandbox_tac/information_schema/pg_catalog/pg_toast/pg_temp) · NON_ALLOWLIST_SCHEMA · SCHEMA_RUNID_MISMATCH |
| Router-level companions | MISSING_CHANNEL / FORBIDDEN_MANUAL_CHANNEL / UNKNOWN_CHANNEL / DIRECTUS_GENERIC_FORBIDDEN (manual/Directus-generic lanes) are enforced by the router in the same fail-closed pass. |
| Audit | feeds guard 2 with the decision |
| Boundary | This is the lock that makes the §3 "DOT-only zone" enforceable, not just documented. Uses the shared _validate_target helper — does not call any other guard. |
Guard 2 — DOT_SCHEMA_WRITE_AUDIT_PROOF (record for every decision)
| Input | request + reject_codes[] + write_intent[] + production_untouched_verdict |
| Does | Emit an audit envelope for every decision — accept or reject. No write-enabled DOT may run without leaving this proof. |
| Output | { dot_code, actor, run_id, mode, target_schema, owner_authorization_ref, channel, decided_at, reject_codes[], write_intent[], production_untouched_verdict, before_snapshot_ref, after_snapshot_ref } |
| Rejects | any unlogged write (a write-enabled mode that cannot emit this envelope ⇒ abort) |
| Boundary | Append-only evidence; production_untouched_verdict carries the Guard 3 verdict; before/after_snapshot_ref populated only for write modes. |
Guard 3 — DOT_PRODUCTION_UNTOUCHED_VERIFY (executable verdict; abort-on-drift) — REMEDIATED
| Input | production_untouched_evidence = { before:{...}, after:{...} } — explicit read-only snapshots of the protected surfaces, supplied by the caller/runtime (Guard 3 itself does no DB I/O) |
| Does | Compare before vs after over the required surfaces and emit a verdict: PASS (all required surfaces present and equal), FAIL (any inequality ⇒ drift), UNKNOWN (evidence missing / not a dict / incomplete). The router requires PASS before any real-run write-intent; FAIL/UNKNOWN/missing ⇒ reject (PROD_UNTOUCHED_FAIL / PROD_UNTOUCHED_UNKNOWN). |
| Output | { verdict: PASS|FAIL|UNKNOWN, drift[], reason, plan } |
| Required surfaces | public.object_count, iu_core.object_count, birth_registry.certified_count, birth_registry.max_date_certified, governance_object_ownership.count, universal_edges.count, universal_edges.provenance_count, dot_tools.count, directus_collections.count, directus_fields.count, directus_relations.count. (a) structural object-inventory equality on public/iu_core catches a stray object placed outside r2_b2_wb_*; (b) frozen invariants; (c) append-only tables (event_outbox, system_issues, directus_activity/_revisions, registry_changelog, measurement_log) are checked by run write-set = empty, NOT raw count — they grow from unrelated background flow (a naive count would false-positive). |
| Rejects (via router) | PROD_UNTOUCHED_FAIL (drift) · PROD_UNTOUCHED_UNKNOWN (missing/incomplete evidence) |
| Boundary | Designed to avoid false-PASS (the dangerous direction): UNKNOWN (not PASS) when evidence is absent or incomplete, so a real-run cannot proceed on no evidence. Scope honesty: Guard 3 verifies the supplied evidence; it does not itself read the live DB and is not a runtime drift proof. The runtime is responsible for gathering true before/after snapshots; this is validated by the local simulation (S01–S04, S10–S11), not by a live run. |
Guard 4 — DOT_STAGING_SCHEMA_DELETE_FAST (teardown, allowlist-guarded) — REMEDIATED
| Input | { target_schema, run_id, mode ∈ {teardown_plan, teardown_real_run}, owner_authorization_ref } |
| Does | Re-run the same allowlist enforcement as Guard 1 via the shared pure _validate_target helper (it does not call allowlist_guard/Guard 1) before any DROP; then one-command DROP SCHEMA <r2_b2_wb_run> CASCADE for the run-scoped workbench only (gated, on a future authorized real-run). |
| Output | teardown_plan ddl preview, or (gated) teardown_real_run write-intent |
| Rejects | MISSING_TARGET_SCHEMA · MALFORMED_SCHEMA_CHARS · PROTECTED_SCHEMA_TARGET · NON_ALLOWLIST_SCHEMA · SCHEMA_RUNID_MISMATCH · INVALID_GATE_TYPE / REAL_RUN_GATE_CLOSED (teardown_real_run gate not exactly boolean True) · PROD_UNTOUCHED_FAIL/PROD_UNTOUCHED_UNKNOWN (teardown_real_run requires Guard 3 PASS) |
| Boundary | Teardown is DOT-only and allowlist-guarded — dropping public/iu_core/cutter_governance/sandbox_tac/any shared schema is rejected with the same codes as creation. Separability: Guard 4 and Guard 1 are independently replaceable because the shared logic lives in a pure helper, not in either guard. |
Composition (boundary discipline)
request ──▶ primary router (DOT_R2_B2_STAGING_SCHEMA_SHELL)
├─ _validate_target (shared pure helper: target allowlist/protected/malformed/run-id)
│ ├─ used by guard 1 DOT_SCHEMA_WRITE_ALLOWLIST_GUARD (create modes)
│ └─ used by guard 4 DOT_STAGING_SCHEMA_DELETE_FAST (teardown modes)
├─ guard 3 DOT_PRODUCTION_UNTOUCHED_VERIFY (verify + REQUIRED PASS before real_run)
└─ guard 2 DOT_SCHEMA_WRITE_AUDIT_PROOF (every decision)
No guard imports another; the router calls each through the dict contract, and Guards 1/4 share only the stateless _validate_target helper. Replace/roll back any one guard without touching the rest. No registry, no graph, no pipeline engine.
Manual-block hardening assessment (DOT §18 principles 1–10) — FRESH runtime evidence 2026-06-19 (unchanged)
Read-only query_pg against directus DB. Nothing changed — assessment only. (Macro-9B2 made no role/grant/policy/runtime change; this table is carried forward from Macro-9B.)
| # | Principle | Current state (fresh, read-only) | Desired state | Verdict |
|---|---|---|---|---|
| 1 | Default agent/runtime roles read-only for DDL | context_pack_readonly, incomex, cutter_ro/exec/verify all have db_create=false, public CREATE=false |
keep read-only | HOLD-OK (already read-only) |
| 2 | No generic role holds CREATE/ALTER/DROP on prod | directus (app role) has db_create=true + public CREATE=true; workflow_admin is SUPERUSER |
revoke schema-create from the generic directus app role; isolate to a dedicated executor |
GAP — hardening required |
| 3 | No generic Directus API path may create collections for schema work | Directus 11.5 running; generic create tools exist | block generic Directus schema/collection create; DOT-only | GAP — policy stated, not enforced |
| 4 | Write isolated to authorized DOT-executor role only | No dedicated DOT-executor role exists; write sits with directus + workflow_admin |
create one minimal-privilege DOT-executor role scoped to r2_b2_wb_* |
GAP — role missing |
| 5 | DOT carries dot_code, run_id, mode, owner_authorization_ref | enforced by primary contract §1 (validator B20–B30 run_id/mode/dot_code, B26–B27 owner-auth, B38–B42 channel/actor) | keep | DESIGN-OK (contract); not yet runtime |
| 6 | Target matches allowlist r2_b2_wb_* only |
enforced by Guard 1 / shared helper (validator B07–B19, incl. control-char B14–B19) | keep | DESIGN-OK; not yet runtime |
| 7 | Target never public/iu_core/cutter_governance/sandbox_tac/prod | enforced by Guard 1 (validator B01–B06) | keep | DESIGN-OK; not yet runtime |
| 8 | All DDL/DML audit-logged | enforced by Guard 2 (audit on every decision, now incl. production_untouched_verdict) |
wire to a durable audit sink at registration | DESIGN-OK; sink not yet wired |
| 9 | Any drift aborts before write / rolls back | enforced by Guard 3 — now an executable verdict; real-run requires PASS, drift ⇒ FAIL ⇒ reject (validator S01–S04, S10–S11) | wire real before/after snapshots around real_run | DESIGN-OK (verdict logic); live snapshotting not yet runtime |
| 10 | Teardown DOT-only + allowlist-guarded | enforced by Guard 4 via shared helper (validator B45/B47, S10–S12) | keep | DESIGN-OK; not yet runtime |
Hardening conclusion: principles 5–10 are satisfied at the design/contract level and proven fail-closed by the local validator rev2 run (64/64, 0 fail-open); principles 1–4 are partly enforced (default read role is read-only) but carry runtime GAPS — the generic directus app role still holds schema-create, no isolated DOT-executor role exists, and generic Directus create is not yet policy-blocked. Closing principles 2/3/4 is a role/grant/policy change = a runtime write and is therefore out of scope for this macro (no grant/revoke/role/gate change performed). They are recorded as required preconditions for any future registration/real-run. Note that Guard 3's verdict logic is engineering-proven over supplied evidence; it is not a live runtime drift proof.
End of guards contract. Default HOLD. Manual SQL / psql / docker exec psql / Directus generic create remain forbidden (§3) and are not an authorized path for any guard.