22-P3 — IU Creation Gateway / Single Writer Gateway — Scope Design
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):
directusrole: 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:
- directus là owner — PostgreSQL không cho phép revoke privileges từ owner của object. Owner luôn có full privileges.
- Directus CMS cần INSERT — Directus Admin UI, internal operations, Flows đều dùng directus role để write. Revoke INSERT = break CMS.
- 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_writerrole owns function,directusrole 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õ:
- 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. - 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. - Migration/restore bypass. pg_restore, pg_dump load, bulk migration scripts thường chạy với triggers disabled. L3 detector cần cover.
- 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:
-
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'). -
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.
-
Birth registry mismatch: IU exists nhưng birth_registry row missing/incomplete → detected by existing birth health checks.
-
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:
- 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.
- TAC đã có invariant verification riêng ("0 content drift" cho 86 units).
- fn_iu_create thiết kế cho single IU creation. Batch creation cần interface khác (fn_iu_create_batch?) — chưa có.
- 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:
- P3-P0: read-only inspection (queries section 3)
- P3-P1: L0 policy registry (dot_config entries)
- P3-P2: L2 trigger guard design (chi tiết, chưa implement)
- P3-P3: L1 permission assessment (dựa trên P3-P0 results)
- P3-P4: L3 detector design
- 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.