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.
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).

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