KB-9867

dot-iu-cutter v0.3 — Directus Role / Permission Matrix Design (2026-05-16)

8 min read Revision 1
dot-iu-cutterdieu44v0.3designdirectusrole-permission-matrixdesign-only

dot-iu-cutter v0.3 — Directus Role / Permission Matrix Design

document_path: knowledge/dev/laws/dieu44-trien-khai/v0.3-design/dot-iu-cutter-v0.3-directus-role-permission-matrix-design-2026-05-16.md
revision: r1
date: 2026-05-16
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.3 — DESIGN ONLY (role/permission matrix)
status: design_authored_for_gpt_review
nothing_executed: true

⛔ DESIGN ONLY — no role/policy/permission/GRANT is created or changed. This is the proposed matrix for GPT review.


§1 — Roles (proposed; NOT created)

PG_roles:
  cutter_ro:
    type: NOLOGIN group role (members granted in at execution-design time)
    purpose: read-only observability of cutter_governance
    attributes: NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT-irrelevant
                NOBYPASSRLS NOREPLICATION
    privileges: USAGE on schema cutter_governance; SELECT on the 12 tables
                (or on v_*_observe views — see §3); ALTER DEFAULT PRIVILEGES
                SELECT for future tables. NOTHING else.
  cutter_ro_full (OPTIONAL, higher-trust, NOT in v0.3 baseline):
    purpose: full-payload audit incl. sensitive columns
    status: specified for completeness; creation requires its own authorization
Directus_roles (only if MODEL-A registration later authorized):
  "Cutter Observer":
    admin_access: false      # (v11: set on the bound policy, not the role)
    app_access:   true       # read-only Data Studio
    bound_policy: "cutter-readonly"
  never bound to: public / Administrator / any existing app role

§2 — Directus Permission Matrix (per collection, MODEL-A only)

For each of the 12 collections, the cutter-readonly policy holds exactly one permission row:

action: read           # NO create / update / delete / share
collections: [canonical_address_alias, cut_change_set,
  cut_change_set_affected_row, decision_backlog_dependency,
  decision_backlog_entry, decision_backlog_history,
  decision_backlog_sweep_log, dot_pair_signature, manifest_envelope,
  manifest_unit_block, review_decision, verify_result]
permissions (row filter): {} (all rows — observability) | OR a documented
  filter if row-scoping is later required (see RLS doc)
validation: {}         # read action takes no validation
fields: per §3 projection (sensitive columns omitted from the field allow-list)
presets: none
matrix_invariant: ZERO create/update/delete/share rows for this policy on ANY
  collection (and on any other collection). Asserted in the dry-run grid.

§3 — Field Visibility Projection (per table)

VISIBLE = in the default observer field allow-list (Directus) or in the v_<table>_observe view (PG). REDACTED = excluded by default; only in the optional higher-trust projection.

canonical_address_alias:
  VISIBLE: alias_id, target_unit_id, alias_kind, valid_from, valid_until,
           created_by, alias_text
  REVIEW:  rationale, scenario_ref (narrative free-text — low risk; default VISIBLE)
cut_change_set:
  VISIBLE: change_set_id, manifest_id, manifest_version, review_decision_id,
           executor_tool_revision, verifier_tool_revision, tool_revision_match,
           state, cut_started_at, cut_committed_at, rolled_back_at,
           affected_unit_count, decision_backlog_entry_id, emitted_by,
           version, risk_class, scenario_ref, rollback_reason,
           rollback_initiated_by, executor_signature_id, verifier_signature_id
  REDACTED: rollback_key, idempotency_key, payload_summary(jsonb)
cut_change_set_affected_row:
  VISIBLE: affected_row_id, change_set_id, target_table, target_row_id,
           operation_kind, applied_at
  REDACTED: before_state_snapshot, after_state_snapshot (raw mutated data)
decision_backlog_dependency:
  VISIBLE: all 6 (dependency_id, from_entry_id, to_entry_id, dependency_kind,
           created_at, created_by)
decision_backlog_entry:
  VISIBLE: entry_id, kind, status, emitted_at, scenario_ref
  REDACTED: payload (jsonb — arbitrary)
decision_backlog_history:
  VISIBLE: history_id, entry_id, entry_version_before, entry_version_after,
           change_kind, changed_by, changed_at, rationale
  REDACTED: change_diff (jsonb)
decision_backlog_sweep_log:
  VISIBLE: sweep_id, swept_at, swept_by, trigger_kind, entries_evaluated_count,
           entries_re_surfaced_count, escalations_routed_count,
           mirror_regenerated_at, mirror_path
  REVIEW:  findings (jsonb — default REDACTED; counts already visible)
dot_pair_signature:
  VISIBLE: signature_id, signature_kind, signer_dot_id, signer_tool_revision,
           signed_at, cross_reference_change_set_id,
           cross_reference_verify_result_id, validation_state, revoked_at,
           revocation_reason, revoked_by, prior_signature_id, scenario_ref
  REDACTED: payload_hash, payload_envelope(jsonb), signature_payload (crypto)
manifest_envelope:
  VISIBLE: all 12 (envelope_id, operation_kind, status, source_doc_ref,
           escalation_ref, cut_change_set_ref, created_by, created_at,
           reviewer, reviewed_at, rationale, superseded_by_envelope_id)
manifest_unit_block:
  VISIBLE: envelope_id, unit_local_id, block_role, render_order,
           target_unit_id, proposed_canonical_address, proposed_authority,
           decision_backlog_ref, created_at
  REDACTED (default; large jsonb): source_span, payload_summary,
           candidate_edges, report_summary
review_decision:
  VISIBLE: review_decision_id, governance_event_kind, manifest_id,
           manifest_version, review_scope, manifest_unit_local_id, status,
           verdict, reviewer_class, risk_class_assessment, escalation_ref,
           cut_change_set_ref, prior_review_decision_id,
           superseded_by_review_decision_id, decision_at, decided_by,
           tool_revision, review_duration_ms, cross_signed_by_dot_verifier,
           version, created_at, updated_at
  REDACTED: findings(jsonb), reviewer_identity(jsonb, PII),
           reviewer_independence_evidence(jsonb)
verify_result:
  VISIBLE: verify_result_id, change_set_id, manifest_id, manifest_version,
           review_decision_id, verify_kind, axis_1_status, axis_1_drift_count,
           axis_1_drift_unit, axis_2_status, verdict, verdict_rationale,
           executor_signature_id, verifier_signature_id,
           executor_tool_revision, verifier_tool_revision, tool_revision_match,
           escalation_ref, verified_at, state, rollback_triggered,
           rollback_change_set_id_triggered, prior_verify_result_id,
           canonicalization_rule_used, scenario_ref
  REDACTED: findings (jsonb)
redaction_mechanism_options:
  A. Directus field-level permission (fields allow-list on the read permission)
  B. PG read VIEW v_<table>_observe exposing only VISIBLE columns; GRANT SELECT
     on the view (NOT the base table) to cutter_ro
recommended: B for the PG plane (hard column hiding, DB-enforced, Directus-
  independent); A additionally if MODEL-A registration is authorized.
"REVIEW" items: GPT to confirm default VISIBLE vs REDACTED.

§4 — Negative Guarantees (matrix invariants)

no_write_permission: zero create/update/delete rows for cutter-readonly policy
no_admin_escalation: bound policy admin_access=false; role not Administrator;
  cutter_ro PG role NOSUPERUSER NOCREATEROLE NOBYPASSRLS
no_public_binding: never attached to Directus `public` policy/role
no_other_schema: cutter_ro has zero privilege on public / sandbox_tac /
  directus_* system tables
no_ui_mutation_path: app_access read-only; no flows/webhooks/presets created
no_existing_role_modified: existing 9 roles / 8 policies / 1173 permissions
  untouched (additive only at execution time)

§5 — Non-Scope

executed: NONE. roles/policies/permissions/views/GRANTs are PROPOSED only.
no_creation_of_cutter_ro / cutter_ro_full / Directus roles: TRUE
no_collection_registration: TRUE
self_advance: PROHIBITED

End of v0.3 Directus role / permission matrix design.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.3-design/dot-iu-cutter-v0.3-directus-role-permission-matrix-design-2026-05-16.md