KB-6FDC
dot-iu-cutter v0.3 — Directus Read-Observability Design (MASTER) (2026-05-16)
10 min read Revision 1
dot-iu-cutterdieu44v0.3designdirectusread-observabilitydesign-only
dot-iu-cutter v0.3 — Directus Read-Observability Design (MASTER)
document_path: knowledge/dev/laws/dieu44-trien-khai/v0.3-design/dot-iu-cutter-v0.3-directus-read-observability-design-master-2026-05-16.md
revision: r1
date: 2026-05-16
author: Agent (Claude Code CLI, Opus 4.7 1M)
sovereign: User / anh Huyền
verifier: GPT (PENDING)
phase: v0.3 — Directus permissions / RLS / read-roles — DESIGN ONLY
authorization: GPT v0.2 closeout PASS + User "DESIGN ONLY" prompt (2026-05-16)
status: design_authored_for_gpt_review
nothing_executed: true
⛔ DESIGN ONLY. No Directus permission/role/policy change, no PG RLS, no role creation, no production mutation, no deploy. Production was inspected READ-ONLY to ground this design. Execution is gated on GPT review PASS + explicit User prompt + a separate session. Agent self-advance PROHIBITED.
§1 — Goal & Scope
goal: design a SAFE, read-only observability layer over the 12 (empty)
cutter_governance tables — no write/update/delete, no admin escalation,
no UI mutation path.
in_scope (design only):
- read-only role(s) model (Directus app layer + PG layer)
- whether Directus permissions suffice or PG RLS is also needed
- field visibility per table + sensitive-field handling
- auditability, rollback plan, dry-run/test plan, risk class, blockers
out_of_scope (hard): executing any of the above; tightening the existing app
DB role; CUT/VERIFY; data writes; Qdrant; deploy; v0.4/runtime.
§2 — Grounding Facts (read-only inspection, production, 2026-05-16)
target: container postgres / db directus / sysid 7611578671664259111
schema: cutter_governance — 12 tables, ALL 0 rows
directus_collection_registration_of_cg_tables: 0 # NONE registered
→ consequence: the 12 tables are currently INVISIBLE to the Directus API and
Data Studio. Directus only governs collections it knows
(directus_collections / directus_fields). Raw PG tables in a non-public
schema are NOT auto-exposed.
app_db_role_directus_has_SELECT_on_cg: TRUE
→ the REAL current exposure surface is the PostgreSQL privilege layer (the
app's DB connection role can already read these tables directly), NOT the
Directus permission system.
rls_enabled_on_cg_tables: 0 # none
directus_authz_model (v11): roles(9) → directus_access(9) → directus_policies(8)
→ directus_permissions(1173). In v11 admin_access / app_access / enforce_tfa
live on directus_policies (NOT directus_roles). Permissions attach to a
policy; a policy is bound to a role (or to public) via directus_access.
Design pivot from this: "Directus permissions vs PG RLS" is the wrong axis. The real axis is two independent control planes:
| Plane | Current state | Read-observability lever |
|---|---|---|
| Directus app (API/Data Studio) | tables NOT registered ⇒ no exposure at all | register 12 collections + a read-only policy/role |
| PostgreSQL privileges | app role directus already has SELECT (+ likely DML) |
a dedicated read-only PG role with SELECT-only on the 12 tables |
§3 — Two Consumer Models (the first GOV decision)
MODEL-A (Directus-native observability):
consumer: Directus Data Studio / Directus REST-GraphQL API
requires: register the 12 tables as Directus collections
(directus_collections + directus_fields metadata WRITE) +
a dedicated read-only POLICY (read-only perms on the 12
collections) bound to a dedicated observability ROLE
(no admin_access, no app write).
pro: native UI, field-level permissions, activity logging for app reads
con: collection registration is itself a Directus-metadata write; larger
surface; must curate directus_fields for redaction
MODEL-B (PG read-only role, no Directus registration):
consumer: external read-only SQL / BI / observability tool
requires: a dedicated PG role (e.g. cutter_ro) — USAGE on schema
cutter_governance + SELECT on the 12 tables ONLY; NoLogin owner,
consumed via a scoped connection; NOTHING in Directus changes.
pro: minimal surface; strongest read-only guarantee; independent of Directus
permission correctness; no Directus metadata write
con: no Directus UI; PG-level read-audit needs pgaudit or a logging view
MODEL-C (hybrid): MODEL-B PG read role now (lowest risk, unblocks observability)
+ MODEL-A registration deferred to a later, separately-authorized
sub-phase if Data Studio visibility is actually required.
recommended: MODEL-C (PG read-only role first; Directus registration deferred).
rationale: smallest reversible surface on empty tables; does not touch the
Directus authz graph; satisfies "safe read-only observability" immediately;
Data Studio is a nice-to-have that can be added under its own gate.
§4 — Recommended Permission Model (design)
PG layer (primary, MODEL-B/C):
role: cutter_ro (NOLOGIN group role; consumers GRANTed into it, or a LOGIN
service role membered into it — decided at execution design time)
grants (exact, least-privilege):
GRANT USAGE ON SCHEMA cutter_governance TO cutter_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA cutter_governance TO cutter_ro; # the 12
ALTER DEFAULT PRIVILEGES IN SCHEMA cutter_governance
GRANT SELECT ON TABLES TO cutter_ro; # future tables stay read-only by default
explicitly NOT granted: INSERT/UPDATE/DELETE/TRUNCATE/REFERENCES/TRIGGER,
CREATE on schema, any privilege on public/sandbox_tac, role membership of
workflow_admin/directus, SUPERUSER/CREATEROLE/CREATEDB, BYPASSRLS.
column-level redaction option: instead of table SELECT, GRANT SELECT on a
curated set of read VIEWS (cutter_governance.v_*_observe) that omit
sensitive columns (see §5 + RLS doc). Views give column projection without
RLS. (Views are objects, not data — still "no data write".)
Directus layer (only if MODEL-A/registration is later authorized):
role: "Cutter Observer" — admin_access=false, app_access=true (read UI only)
policy: "cutter-readonly" — for each of the 12 collections exactly one
permission row: action=read, fields curated per §5, no create/update/delete
binding: directus_access row linking role→policy; never bound to `public`
no_share, no_admin, no_app_write, enforce_tfa per org standard
§5 — Field Visibility & Sensitive Fields (per table)
Full per-column projection is in the role/permission-matrix doc. Sensitive columns identified by the read-only column inspection:
cryptographic / integrity material (DEFAULT: REDACTED from the standard observer):
dot_pair_signature.signature_payload (raw signature material — text)
dot_pair_signature.payload_envelope (jsonb signed envelope)
dot_pair_signature.payload_hash (hash)
operational control tokens (DEFAULT: REDACTED):
cut_change_set.rollback_key
cut_change_set.idempotency_key
raw mutated-state snapshots (DEFAULT: REDACTED — may echo arbitrary row data):
cut_change_set_affected_row.before_state_snapshot (jsonb)
cut_change_set_affected_row.after_state_snapshot (jsonb)
identity / PII-ish (DEFAULT: REDACTED or coarse-grained):
review_decision.reviewer_identity (jsonb)
review_decision.reviewer_independence_evidence (jsonb)
narrative free-text (DEFAULT: VISIBLE — low risk, but flag for review):
*.rationale, *.rollback_reason, *.revocation_reason,
review_decision.verdict_rationale, verify_result.verdict_rationale
large analytic jsonb (DEFAULT: VISIBLE as metadata; consider summarised view):
decision_backlog_entry.payload, *.findings, *.change_diff,
*.payload_summary, manifest_unit_block.* jsonb
default_observer_projection: identifiers + status/state/verdict + kinds +
timestamps + counts + FK refs; sensitive set above EXCLUDED. A separate
higher-trust "Cutter Auditor (full payload)" projection is specified in the
matrix doc but is NOT created and requires its own authorization.
§6 — Is PG RLS Needed?
verdict: NO — PG RLS is NOT required for v0.3 read-only observability.
why:
- the requirement is read-ALL-rows of governance tables; there is NO stated
per-row tenant/owner isolation requirement.
- column sensitivity is solved by VIEW projection or Directus field
permissions, not by RLS (RLS is row filtering, not column hiding).
- tables are empty; no current row-confidentiality boundary exists.
when_RLS_would_be_needed (documented as a forward option, NOT now):
- if observers must be row-scoped (e.g. hide certain scenario_ref / restrict
by reviewer_class) — then a SELECT-only RLS policy spec is provided in the
rls-read-policy doc as a CONDITIONAL design, gated on a future requirement.
§7 — Cross-Document Map
- role/permission matrix: dot-iu-cutter-v0.3-directus-role-permission-matrix-design-2026-05-16.md
- RLS read-policy (conditional / not-needed-now): dot-iu-cutter-v0.3-directus-rls-read-policy-design-2026-05-16.md
- risk + dry-run plan: dot-iu-cutter-v0.3-directus-risk-and-dry-run-plan-2026-05-16.md
- design report (rollup): dot-iu-cutter-v0.3-directus-read-observability-design-report-2026-05-16.md
controlling inputs: v0.2 handoff-status / structural-schema-inventory /
post-execution-backup-verification / v0.3 app-tooling routing note (all 2026-05-16)
§8 — Non-Scope & Boundaries (this document)
executed: NOTHING (production read-only inspection only)
no_directus_permission_change / no_role_create / no_policy_change: TRUE
no_PG_RLS_change / no_GRANT / no_REVOKE / no_role_create: TRUE
no_collection_registration: TRUE
no_data_write / no_CUT / no_VERIFY / no_Qdrant / no_deploy: TRUE
no_tightening_of_existing_app_db_role: TRUE (flagged as separate, out of scope)
self_advance: PROHIBITED — awaiting GPT review
End of v0.3 Directus read-observability design (MASTER).