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.