KB-567A

dot-iu-cutter v0.3 — Directus Read-Observability Risk & Dry-Run Plan (2026-05-16)

7 min read Revision 1
dot-iu-cutterdieu44v0.3designriskdry-run-planrollbackdesign-only

dot-iu-cutter v0.3 — Directus Read-Observability Risk & Dry-Run Plan

document_path: knowledge/dev/laws/dieu44-trien-khai/v0.3-design/dot-iu-cutter-v0.3-directus-risk-and-dry-run-plan-2026-05-16.md
revision: r1
date: 2026-05-16
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.3 — DESIGN ONLY (risk class + dry-run/test + rollback plan)
status: design_authored_for_gpt_review
nothing_executed: true

⛔ DESIGN ONLY. The dry-run/rollback procedures below are authored for GPT review; none is executed. Production permission changes require GPT review PASS + explicit User prompt + a separate session.


§1 — Risk Classification

risk_class: STANDARD
rationale_for_not_LOW:
  - execution (later) touches shared auth infrastructure: PG roles/GRANTs
    and/or the Directus authz graph (roles/policies/permissions) which is
    global to the whole Directus instance (9 roles, 1173 permissions live).
  - a misconfigured policy/binding could over-expose or, if bound to `public`,
    leak — must be dry-run proven.
rationale_for_not_HIGH:
  - all 12 tables are EMPTY (0 rows) — zero data-exposure blast radius now.
  - the change is purely additive and fully reversible (DROP role/policy/views;
    REVOKE).
  - read-only intent; no write/CUT/VERIFY/Qdrant/deploy.
  - MODEL-C (PG read role + views, no Directus registration) keeps the surface
    minimal and outside the Directus authz graph.
residual_risks:
  - RR-1 over-grant (e.g. SELECT on base tables incl. sensitive cols instead of
    views) → mitigate via view-only GRANT + dry-run column assertion.
  - RR-2 Directus policy accidentally bound to public/Administrator →
    mitigate: MODEL-C avoids Directus entirely; if MODEL-A, dry-run asserts
    directus_access binding target.
  - RR-3 default-privilege drift (future tables auto-granted) → intended for
    cutter_ro SELECT only; assert ALTER DEFAULT PRIVILEGES scope = schema
    cutter_governance only.
  - RR-4 read-audit gap at PG layer (SELECTs not logged) → decision item B-3.

§2 — Blockers Before Execution (must be resolved by GPT/User)

B-1 consumer model: choose MODEL-A (Directus registration) vs MODEL-B (PG read
    role only) vs MODEL-C (hybrid; recommended). Drives the entire exec plan.
B-2 sensitive-field policy: ratify the §3 REDACTED set + the "REVIEW" items
    (rationale/scenario_ref/findings/sweep findings) default visible vs hidden.
B-3 read-audit requirement: none vs Directus activity (MODEL-A only) vs pgaudit
    / logging view at PG layer. Affects exec scope.
B-4 consumer principal: who/what connects as the read role (human SQL, BI tool,
    service) and the auth/credential handling — out of design, needed for exec.
B-5 existing app DB role `directus` already has broad PG privileges on
    cutter_governance — decide whether v0.3 also tightens that (RECOMMEND: NO,
    separate higher-risk workstream; explicitly flag, do not bundle).
B-6 view vs Directus-field redaction mechanism (recommended: PG views).
status: all OPEN — Agent does NOT self-resolve; these block any execution.

§3 — Dry-Run / Test Plan (authored; NOT executed)

environment: ephemeral, isolated, throwaway — reuse the proven restore-test
  pattern: postgres:16 container, NO published port, sysid asserted ≠
  production 7611578671664259111, restored from the v0.2 closeout backup
  (sha a432a86e…af15) so the 12 empty tables exist. (If MODEL-A is tested, a
  disposable Directus instance pointed at that PG, NOT prod Directus.)
  name: pg-dryrun-v0.3-readobs-<date> (distinct; torn down after).
steps (read-observability assertion grid):
  D-1  restore v0.2 backup into ephemeral PG; assert cg=12, rows=0
  D-2  create cutter_ro (NOLOGIN, NOSUPERUSER/NOCREATEROLE/NOBYPASSRLS)
  D-3  create v_<t>_observe views per the §3 projection (12 views)
  D-4  GRANT USAGE schema + SELECT on the 12 VIEWS to cutter_ro; assert NO
       SELECT on base tables, NO INSERT/UPDATE/DELETE anywhere, NO privilege
       on public/sandbox_tac/directus_*
  D-5  as a cutter_ro member: SELECT from each view → EXPECT success, only
       VISIBLE columns present, REDACTED columns absent
  D-6  as cutter_ro: INSERT/UPDATE/DELETE/TRUNCATE on each table → EXPECT
       permission denied (all 12 × 4)
  D-7  as cutter_ro: attempt CREATE/ALTER/DROP, role self-escalation,
       SET ROLE workflow_admin/directus → EXPECT denied
  D-8  assert existing roles/policies/permissions counts unchanged (additive
       only); no policy bound to public
  D-9  MODEL-A only: register 12 collections + cutter-readonly policy +
       Cutter Observer role on the disposable Directus; assert API read works,
       write/delete 403, fields redacted, role admin_access=false, not public
  D-10 ROLLBACK rehearsal (see §4) → assert environment back to baseline
  D-11 teardown ephemeral env; confirm prod untouched (read-only sysid check)
pass_condition: D-1..D-10 all green AND prod provably untouched.

§4 — Rollback Plan (for the later execution; design)

PG plane (MODEL-B/C):
  REVOKE SELECT ON ALL TABLES IN SCHEMA cutter_governance FROM cutter_ro;
  REVOKE USAGE ON SCHEMA cutter_governance FROM cutter_ro;
  ALTER DEFAULT PRIVILEGES IN SCHEMA cutter_governance REVOKE SELECT ON TABLES FROM cutter_ro;
  DROP VIEW IF EXISTS cutter_governance.v_<t>_observe;  (×12)
  DROP ROLE IF EXISTS cutter_ro;   # only after memberships revoked
  baseline_after_rollback: exactly the current state (no cutter_ro, no views;
    app role `directus` retains its pre-existing privileges — unchanged by v0.3)
Directus plane (MODEL-A only):
  delete directus_permissions rows of cutter-readonly; delete directus_access
  binding; delete directus_policies 'cutter-readonly'; delete "Cutter Observer"
  role; unregister directus_collections/directus_fields rows for the 12
  collections. All additive ⇒ deletion restores baseline.
properties: fully reversible; tables empty ⇒ zero data impact; no CASCADE;
  no drop of any pre-existing object; rollback is itself dry-run-rehearsed (D-10).

§5 — Non-Scope

executed: NONE (no dry-run run, no role/policy/view/grant, prod read-only only)
no_ephemeral_env_provisioned_yet: TRUE
self_advance: PROHIBITED — awaiting GPT review + explicit User prompt

End of v0.3 Directus read-observability risk & dry-run plan.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.3-design/dot-iu-cutter-v0.3-directus-risk-and-dry-run-plan-2026-05-16.md