KB-127B

dot-iu-cutter v0.4 — Principal Routing Design (design only)

8 min read Revision 1
dot-iu-cutterdieu44v0.4db-adapterdesign-onlyprincipal-routingseparation-of-duty

dot-iu-cutter v0.4 — Principal Routing Design

document_path: knowledge/dev/laws/dieu44-trien-khai/v0.4-db-adapter-design/dot-iu-cutter-v0.4-principal-routing-design-2026-05-17.md
revision: r1
date: 2026-05-17
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.4 — REAL DB ADAPTER DESIGN (companion to design-master)
status: design_only_pending_gpt_review

⛔ DESIGN ONLY. No connection, no auth, no write. Routing rules below are a target that mirrors the LIVE least-privilege matrix; they neither widen nor exercise it.


§1 — Two Lanes, Bound at Construction

cutter_exec  (lane DOT-991): the AUTHORING writer — MARK, REVIEW, CUT.
cutter_verify (lane DOT-992): the INDEPENDENT writer — VERIFY (incl. the
  fail-path compensating set + escalation entry).
binding: a RealPostgresAdapter instance is constructed FOR ONE principal and
  cannot change principal for its lifetime (P-2). CutterRuntime already wires
  two principal-scoped adapters over the contract (skeleton: exec_adapter /
  verify_adapter) — this design keeps that 1:1 with the live roles.
cutter_ro: NOT a routing target. It is NOLOGIN, read-only, views-only. No
  phase routes to it. The writer reads with its OWN principal (master §4
  find()), never via cutter_ro and never via a separate read role.
forbidden principals: workflow_admin, directus, postgres are in the
  skeleton FORBIDDEN_WRITE_PRINCIPALS set and have NO adapter construction
  path. They are never a runtime identity.

§2 — Phase → Principal Routing Table (authoritative)

phase   principal      writes (per transaction-mapping doc)            granted?
MARK    cutter_exec    entry,history,dependency,sweep_log INSERT       YES (matrix)
                        + UPDATE(status) decision_backlog_entry         YES (col)
REVIEW  cutter_exec    manifest_envelope,manifest_unit_block,           YES
                        review_decision INSERT                          YES
                        + UPDATE(status) decision_backlog_entry          YES (col)
                        + UPDATE(superseded_by_review_decision_id)        YES (col,
                          on review_decision  [re-review only]            exec only)
CUT     cutter_exec    dot_pair_signature,cut_change_set,               YES
                        cut_change_set_affected_row INSERT              YES
                        + UPDATE(status) decision_backlog_entry          YES (col)
VERIFY  cutter_verify  verify_result,dot_pair_signature INSERT          YES
                        + (fail) cut_change_set,                         YES
                          cut_change_set_affected_row,                   YES
                          decision_backlog_entry(escalation),            YES
                          decision_backlog_history INSERT                YES
                        + UPDATE(status) decision_backlog_entry          YES (col)
every "granted?" cell == a tuple in the LIVE 33+3 matrix (credential PASS
2026-05-17). The adapter design adds NO write the matrix does not already
permit; conversely it uses NOTHING the matrix grants beyond these.

§3 — Phase-Principal Mismatch Is Refused (before any SQL)

R-1 the runtime selects the adapter by phase via a FIXED, table-driven map
  (the §2 table). There is no code path where MARK/REVIEW/CUT run on
  cutter_verify or VERIFY runs on cutter_exec.
R-2 defence-in-depth ladder for a mismatch (any one is sufficient; all
  present):
  (a) routing map: phase → fixed principal (compile-time/table constant).
  (b) skeleton _assert_writer(): principal must be in {cutter_exec,
      cutter_verify}; FORBIDDEN set rejects cutter_ro/workflow_admin/
      directus/postgres. (already PASSed)
  (c) per-adapter capability assertion: a cutter_verify-bound adapter MUST
      refuse an insert() targeting review_decision/manifest_*/ (and
      cutter_exec refuse verify_result) BEFORE issuing SQL — a typed
      PrincipalCapabilityError, not a caught 42501.
  (d) the LIVE PG grant matrix: even if (a)-(c) failed, PG itself returns
      42501 and the txn rolls back (server-side backstop, independent of
      the agent).
R-3 a 42501 actually reaching the client is therefore a STOP-class defect
  (the agent attempted an ungranted write — a routing/design bug), handled
  per the error doc (STOP + signal, never retry, never privilege-escalate).
R-4 NO cross-phase credential reuse: a connection authenticated as
  cutter_exec is never reused to perform VERIFY work and vice-versa; pools
  are per-principal and isolated (doc 2 §5). One phase txn = one principal =
  one connection from that principal's pool.

§4 — Separation of Duty (SoD) Mapping

SoD-1 the executor (cutter_exec) authors the change; the verifier
  (cutter_verify) independently attests it. They are DISTINCT PG roles with
  DISJOINT write surfaces:
    cutter_exec  CANNOT INSERT verify_result (no grant) → cannot self-verify.
    cutter_verify CANNOT INSERT review_decision/manifest_* (no grant) →
                  cannot author the plan it verifies.
  This is enforced by the LIVE matrix, not merely by code — code review
  alone is not the only line of defence.
SoD-2 G-VERIFY-SOD (flow doc) is satisfied structurally: VERIFY runs on a
  different role than CUT by routing construction, so the verifier signature
  (DOT-992) is necessarily produced by a different principal than the
  executor signature (DOT-991).
SoD-3 the fail-path exception: VERIFY (cutter_verify) DOES write a
  compensating cut_change_set + affected_row + an escalation
  decision_backlog_entry. This is intentional and matrix-granted: the
  verifier may record a FORWARD correction + escalate, but it still CANNOT
  author a review_decision/manifest — it cannot "approve its own redo".
  The escalation entry re-enters the backlog for an independent MARK→REVIEW
  by cutter_exec under a fresh decision. The loop's integrity holds.
SoD-4 column-scoped asymmetry: only cutter_exec holds
  UPDATE(superseded_by_review_decision_id) — review-lineage stamping is an
  authoring act, never a verifier act. cutter_verify has no such grant.

§5 — Why This Cannot Bypass Least-Privilege

- the adapter's allowed write set is the INTERSECTION of (the routing table)
  and (the live grant matrix); it can only ever be ≤ the matrix.
- there is no "admin fallback", no "retry as superuser", no "GRANT then
  write" path — the adapter has no DDL/GRANT surface at all (append-only,
  skeleton already raises AppendOnlyViolation on delete/truncate; no grant
  method exists).
- secrets are principal-scoped (doc 2 L-5): a cutter_exec adapter physically
  cannot load the cutter_verify password and vice-versa, so it cannot
  "become" the other lane to widen its reach.
- the only way to change what a principal may do is ALTER/GRANT in a
  separate, GPT-reviewed credential cycle — never the adapter at runtime.

§6 — Open Decisions (this doc)

DA-4 (shared w/ doc 2) post-connect `SELECT current_user` == bound principal
  assertion: recommend INCLUDE (turns a mis-provisioned env into an early
  STOP instead of a late 42501).
DA-12 per-adapter capability assertion (R-2c) granularity: hardcode the
  per-principal allowed-table set from the inventory (recommended, frozen)
  vs derive by querying information_schema.role_table_grants at startup
  (self-checking but adds a startup read). Recommend the frozen inventory
  constant + an OPTIONAL startup cross-check logged as advisory.

End of principal routing design (design only; mirrors live matrix; no connection; no write).

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.4-db-adapter-design/dot-iu-cutter-v0.4-principal-routing-design-2026-05-17.md