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.