KB-4C42 rev 2

DOT Schema-Write Guards — 4 Separable Guard Contracts (Macro-9B / 9B2 rev2)

12 min read Revision 2
dot-managemacro9bmacro9b2guardsallowlistaudit-proofproduction-untoucheddelete-fastspecremediation2026-06-20

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.