KB-3600

22-P3 — IU Creation Gateway / Single Writer Gateway — Scope Design

15 min read Revision 1
pack-22p3designgatewaysingle-writerfn-iu-createtrigger-guarddetector

22-P3 — IU Creation Gateway / Single Writer Gateway — Scope Design

Date: 2026-05-06 Author: Opus (Claude) Controlling: GPT P3 gateway redirection directive Prior: 22-p3-iu-native-create-adapter-scope.md (superseded — adapter framing quá hẹp) Status: DESIGN — chờ GPT/User review. Không implement, không mutate runtime. Nguyên tắc vận hành: TỰ ĐỘNG HOÀN TOÀN — không ai cần nhớ quy trình.


1. P2 Closure

Item Fact
fn_iu_create Exists, VOLATILE, SECDEF, owner=directus, complete-or-nothing
fn_iu_create_plan Exists, STABLE, SECDEF, owner=directus, dry-run
Birth mechanism Existing trigger-based (fn_birth_registry_auto) — fn_iu_create KHÔNG raw insert birth_registry
P2 pilot pilot.p2.20260506-045033.e0ae7ec5 — retained, 3-layer verify all_pass
Auxiliary engine Vẫn tồn tại cho bypass/legacy/drift/import — không phải happy path
Incomplete existing states health/remediation, không phải success

2. Creation Gateway — 4 Layers

Mục tiêu: mọi IU mới đi qua canonical path (fn_iu_create). Sai đường → chặn tự động + hướng dẫn đúng đường. Lọt qua → detector báo health incident. Không ai cần nhớ phải gọi fn_iu_create — hệ thống tự enforce.

Nôm na: P2 xây xong phòng sinh chuẩn. P3 thiết kế cổng bệnh viện — bảng hướng dẫn, bảo vệ chặn cửa sau, đội tuần tra phát hiện ca lọt.

L0 — Policy / README Registry

Đăng ký canonical path vào nơi mà mọi agent/tool/script có thể tra cứu tự động.

Key Value
Canonical create function public.fn_iu_create(text,text,text,text,text,text,text,text,uuid)
Plan/dry-run function public.fn_iu_create_plan(text,text,text,text,text,text,text,text,uuid)
Forbidden path INSERT INTO public.information_unit trực tiếp (non-canonical)
README path Nơi lưu hướng dẫn canonical (dot_config key hoặc policy table)
Remediation path fn_iu_verify_invariants → health/remediation states → auxiliary engine

Lưu trữ ở đâu?

Câu hỏi mở cho P3-P0 inspection: dot_config? collection_registry? policy table mới? Hiện dot_config đã tồn tại và được dùng cho vocab/config. Ưu tiên dùng dot_config trước khi tạo table mới (Assembly First).

Ví dụ dot_config entries:

iu_create.canonical_function = 'public.fn_iu_create'
iu_create.plan_function = 'public.fn_iu_create_plan'
iu_create.forbidden_direct_insert = 'true'
iu_create.readme_path = 'knowledge/dev/laws/dieu44-trien-khai/design/22-p3-iu-creation-gateway-scope.md'

Opus note: Không hardcode message trong trigger. Trigger nên đọc từ dot_config hoặc trả key/path để caller tự tra. Khớp §0-AU (cấm hardcode).

L1 — Permission Gate

Kiểm soát ai có quyền INSERT trực tiếp vs chỉ có EXECUTE trên canonical function.

Hiện trạng (cần P3-P0 inspect xác nhận):

  • directus role: owner/primary user cho Directus CMS. Có full privileges trên tất cả tables (bao gồm INSERT information_unit).
  • fn_iu_create: SECURITY DEFINER, owner=directus. Chạy với quyền của directus.
  • PUBLIC: revoked trên fn_iu_create/fn_iu_create_plan.

⚠️ Opus phản biện — Tension quan trọng:

Revoke INSERT trên information_unit khỏi directus role rất rủi ro:

  1. directus là owner — PostgreSQL không cho phép revoke privileges từ owner của object. Owner luôn có full privileges.
  2. Directus CMS cần INSERT — Directus Admin UI, internal operations, Flows đều dùng directus role để write. Revoke INSERT = break CMS.
  3. fn_iu_create chạy AS directus — nếu directus mất INSERT (giả sử possible), function cũng mất.

Giải pháp khả thi (cần inspect trước):

  • Nếu directus owns information_unit → KHÔNG THỂ revoke INSERT từ directus. Permission gate phải dùng L2 trigger guard thay thế.
  • Nếu muốn permission-based isolation thật → cần role architecture mới: iu_writer role owns function, directus role chỉ EXECUTE. Nhưng đây là thay đổi lớn, cần đánh giá kỹ.
  • Thực tế nhất: L1 chỉ đảm bảo PUBLIC không có quyền (đã done) + xác nhận danh sách roles có INSERT. Chặn thật bằng L2.

P3-P0 inspection cần trả lời:

  • Ai own information_unit table? (likely directus)
  • Roles nào có INSERT/UPDATE/DELETE trên information_unit?
  • Có role nào ngoài directus có INSERT không?
  • Directus CMS có dùng direct INSERT trên information_unit trong internal operations không?

L2 — Trigger Guard / Wrong-door Blocker

Cơ chế tự động: fn_iu_create đánh dấu session → BEFORE INSERT trigger kiểm tra dấu → sai đường → RAISE EXCEPTION với hướng dẫn.

Thiết kế:

-- Trong fn_iu_create, TRƯỚC khi INSERT:
PERFORM set_config('app.canonical_writer', 'fn_iu_create', true);
-- true = local to transaction

-- BEFORE INSERT trigger trên information_unit:
CREATE FUNCTION fn_iu_insert_guard() RETURNS trigger AS $$
BEGIN
  IF COALESCE(current_setting('app.canonical_writer', true), '') != 'fn_iu_create' THEN
    RAISE EXCEPTION 'IU creation must use canonical path: public.fn_iu_create(...). '
      'Direct INSERT blocked. See: %',
      COALESCE(
        (SELECT value FROM public.dot_config WHERE key = 'iu_create.readme_path'),
        'knowledge/dev/laws/dieu44-trien-khai/design/22-p3-iu-creation-gateway-scope.md'
      );
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_iu_insert_guard
  BEFORE INSERT ON public.information_unit
  FOR EACH ROW EXECUTE FUNCTION fn_iu_insert_guard();

⚠️ Opus phản biện — Giới hạn cần ghi rõ:

  1. Không phải security boundary. Bất kỳ SQL session nào có thể SET app.canonical_writer = 'fn_iu_create' rồi INSERT trực tiếp. Đây là "speed bump" — chặn sai lầm vô ý, không chặn bypass có chủ đích.
  2. Superuser bypass. Superuser có thể disable triggers (ALTER TABLE DISABLE TRIGGER). Nhưng superuser bypass mọi thứ — đây là PG limitation, không phải design flaw.
  3. Migration/restore bypass. pg_restore, pg_dump load, bulk migration scripts thường chạy với triggers disabled. L3 detector cần cover.
  4. Directus CMS bypass. Nếu Directus Admin UI cho phép create row trên information_unit, nó sẽ INSERT trực tiếp → bị trigger chặn. Đây có thể là behavior mong muốn (chặn UI create) hoặc không mong muốn (break admin workflow). Cần P3-P0 inspect xác nhận Directus có dùng direct INSERT không.

Opus bổ sung — Exempt list:

Một số paths hợp pháp cần bypass trigger:

  • Migration scripts (controlled, audited)
  • TAC pipeline (nếu quyết định giữ direct INSERT — xem section 4)
  • Data restore

Exempt mechanism: migration role có thể SET app.canonical_writer = 'migration:<ticket_id>' — trigger chấp nhận prefix migration: ngoài fn_iu_create. Hoặc dùng SET app.canonical_writer_exempt = 'true' với audit log.

Opus strong opinion: Exempt list phải hẹp và mỗi exempt phải có lý do + expiry. Không tạo "cửa hậu vĩnh viễn". Exempt cho migration = OK. Exempt cho "tiện" = KHÔNG.

L3 — Detector / Auxiliary Engine Monitor

Phát hiện IU tạo mà không qua canonical path — dù đã có L1 + L2. Cover các bypass không chặn được: superuser, restore, trigger disabled, migration không đánh dấu.

Detection strategies:

  1. Missing canonical marker: IU row tồn tại nhưng không có trace trong audit/log rằng fn_iu_create đã tạo nó. Cần fn_iu_create ghi marker (ví dụ: identity_profile->>'created_via' = 'fn_iu_create').

  2. Failed invariants on recent IUs: Chạy fn_iu_verify_invariants định kỳ trên IUs mới tạo (ví dụ: created trong 24h gần nhất). Nếu all_pass=false → health incident.

  3. Birth registry mismatch: IU exists nhưng birth_registry row missing/incomplete → detected by existing birth health checks.

  4. Auxiliary engine touching fresh objects: Nếu backfill/repair/DOT-118 phải sửa IU mới tạo (< 1h old), đó là signal rằng main path đã fail hoặc bị bypass. Flag as HEALTH INCIDENT, không phải routine maintenance.

Opus note: L3 không chặn — nó phát hiện và báo. Chặn là việc của L1 + L2. L3 là lưới an toàn cuối cùng.

Implementation options (design only, chưa chọn):

  • PG cron job chạy detection query mỗi N phút
  • system_health_checks entry cho IU creation audit
  • Trigger AFTER INSERT ghi audit entry (nhẹ hơn cron, nhưng trigger lại bị disable cùng lúc với L2 guard)

3. P3-P0 Read-only Inspection Proposal

Trước khi enforce bất kỳ layer nào, cần inspect hiện trạng. P3-P0 là read-only, không mutate.

Queries cần chạy:

-- 1. Owner của information_unit / unit_version
SELECT tableowner FROM pg_tables WHERE tablename IN ('information_unit', 'unit_version');

-- 2. Privileges trên information_unit
SELECT grantee, privilege_type FROM information_schema.table_privileges
WHERE table_name = 'information_unit' AND table_schema = 'public';

-- 3. Roles có INSERT/UPDATE/DELETE
SELECT grantee, privilege_type FROM information_schema.table_privileges
WHERE table_name = 'information_unit' AND table_schema = 'public'
AND privilege_type IN ('INSERT', 'UPDATE', 'DELETE');

-- 4. Current triggers trên information_unit
SELECT tgname, tgtype, proname FROM pg_trigger t
JOIN pg_proc p ON t.tgfoid = p.oid
WHERE t.tgrelid = 'public.information_unit'::regclass AND NOT t.tgisinternal;

-- 5. Current triggers trên unit_version
SELECT tgname, tgtype, proname FROM pg_trigger t
JOIN pg_proc p ON t.tgfoid = p.oid
WHERE t.tgrelid = 'public.unit_version'::regclass AND NOT t.tgisinternal;

-- 6. Tổng IU hiện có + created_by distribution
SELECT created_by, count(*) FROM public.information_unit GROUP BY created_by ORDER BY count(*) DESC;

-- 7. IU có birth_registry entry vs không
SELECT
  (SELECT count(*) FROM public.information_unit) AS total_iu,
  (SELECT count(*) FROM public.birth_registry WHERE collection_name = 'information_unit') AS with_birth;

-- 8. dot_config keys liên quan iu_create
SELECT key, value FROM public.dot_config WHERE key LIKE 'iu_create.%' OR key LIKE 'vocab.%' ORDER BY key;

-- 9. fn_iu_create SECURITY DEFINER confirmation
SELECT proname, proowner::regrole, prosecdef, proconfig
FROM pg_proc WHERE proname IN ('fn_iu_create', 'fn_iu_create_plan');

Output cần: Report P3-P0 với kết quả 9 queries trên. Dựa trên kết quả mới quyết định L1 feasibility và L2 exempt list.


4. TAC Pipeline — First-class Decision

TAC pipeline (P10A/P10B) đã tạo 86+ IUs qua direct INSERT. Đây là integration surface lớn nhất.

3 options:

Option T1: Chuyển TAC sang fn_iu_create

TAC pipeline gọi fn_iu_create thay vì direct INSERT.

  • Pro: Mọi IU đi qua canonical path. Invariants tự verify. Birth tự fire. Consistency.
  • Con: TAC pipeline có logic riêng (multi-document validation, content hash pipeline, section parsing, batch processing). Wrapping trong fn_iu_create có thể conflict hoặc duplicate logic (ví dụ: fn_iu_create tự compute content_hash, TAC cũng compute content_hash).
  • Risk: Nếu fn_iu_create reject vì vocab/preflight issue, TAC batch bị block giữa chừng.

Option T2: Giữ TAC là controlled import path

TAC giữ direct INSERT nhưng với:

  • Exempt marker trong L2 trigger guard: SET app.canonical_writer = 'tac_pipeline:<batch_id>'

  • TAC tự verify invariants sau batch (đã có trong P10A/P10B: "0 content drift")

  • L3 detector chấp nhận TAC marker, không flag as incident

  • Pro: TAC pipeline không bị thay đổi. Performance tốt hơn (batch INSERT vs từng fn_iu_create call).

  • Con: Hai đường tạo IU song song. TAC có thể drift khỏi fn_iu_create contract theo thời gian.

  • Risk: Exempt trở thành cửa hậu nếu không kiểm soát.

Option T3: Hybrid — TAC mới dùng fn_iu_create, TAC cũ giữ exempt

IU mới từ TAC pipeline → fn_iu_create. 86 IU hiện có → grandfathered, L3 detect nhưng không flag.

  • Pro: Dần chuyển sang canonical path. Không break existing.
  • Con: Complexity quản lý 2 trạng thái.

Opus opinion: T2 (controlled import path) cho hiện tại, T3 cho dài hạn. Lý do:

  1. TAC pipeline có logic domain-specific (multi-document validation, content drift detection) mà fn_iu_create không cover. Forcing TAC qua fn_iu_create hoặc duplicate logic hoặc lose domain validation.
  2. TAC đã có invariant verification riêng ("0 content drift" cho 86 units).
  3. fn_iu_create thiết kế cho single IU creation. Batch creation cần interface khác (fn_iu_create_batch?) — chưa có.
  4. Exempt marker + L3 detection đủ để kiểm soát. TAC không phải "cửa hậu" — nó là "cửa nhập hàng" với kiểm soát riêng.

Câu hỏi cho GPT/User: Chấp nhận T2 hay yêu cầu T1?


5. Adapter Options — Reframed

Adapter là downstream, không phải core. Gateway enforcement trước, adapter sau.

Option Mô tả Khi nào
A SQL-only canonical call Hiện tại — Agent gọi psql trực tiếp
B Thin CLI/DOT wrapper Khi cần standardize cho nhiều agents/cron
C Directus/API endpoint Khi cần UI-facing hoặc external API
D Gateway enforcement first, adapter later Khuyến nghị
E Full gateway + adapter Khi use case rõ ràng

Opus khuyến nghị: Option D — gateway first.

Staged plan:

  1. P3-P0: read-only inspection (queries section 3)
  2. P3-P1: L0 policy registry (dot_config entries)
  3. P3-P2: L2 trigger guard design (chi tiết, chưa implement)
  4. P3-P3: L1 permission assessment (dựa trên P3-P0 results)
  5. P3-P4: L3 detector design
  6. Adapter: defer — xét sau khi gateway design approved

6. Authoring Workflow Policy

Opus giữ quan điểm: đây là policy/vision, không phải P3 implementation.

Giai đoạn Workflow
Now (pre-gateway) Dual-form: rendered document + IU map. AI soạn document, IU-ize trước enacted.
After gateway (post-P3 implement) AI/Agent tạo IU qua fn_iu_create. Rendered document = output từ IU data.
Legacy/import Document → pipeline cắt thành IU (TAC hoặc tương tự).
New production IU Canonical path only — fn_iu_create.

Policy ghi nhận, không enforce trong P3. Enforce khi render layer (P10D) sẵn sàng.


7. Boundaries — P3 Design Phase

  • ❌ Không mutation runtime
  • ❌ Không revoke permissions
  • ❌ Không tạo triggers
  • ❌ Không DDL
  • ❌ Không function changes
  • ❌ Không adapter implementation
  • ❌ Không DOT registration
  • ❌ Không cleanup pilot
  • ❌ Không Pack 2C
  • ❌ Không default seed, không IU rows

P3-P0 inspection = READ-ONLY queries only.


8. Opus Assessment Summary

GPT directive point Opus response
Reframe adapter → gateway ✅ Đồng ý — gateway framing đúng hơn
4 layers (L0-L3) ✅ Đồng ý cấu trúc
L0 Policy/README ✅ Thực hiện — dot_config entries proposed
L1 Permission Gate ⚠️ Đồng ý nguyên tắc, PHẢN BIỆN feasibility — directus role tension
L2 Trigger Guard ✅ Đồng ý + BỔ SUNG exempt mechanism + GHI RÕ giới hạn (speed bump, không security boundary)
L3 Detector ✅ Đồng ý mạnh — lưới an toàn cuối cùng
P3-P0 inspection first ✅ Đồng ý — 9 queries proposed
TAC first-class decision ✅ Đồng ý — đề xuất T2 (controlled import)
Authoring workflow ⚠️ Ghi nhận policy, không phải P3 scope
Boundaries no mutation ✅ Đồng ý

22-P3 Creation Gateway Design | 2026-05-06 | Opus | Chờ GPT/User review. Không dispatch.