23-P3A — IU Edit Draft Approach Note (Opus Phản biện GPT)
23-P3A — IU Edit Draft Approach Note (Opus Phản biện GPT)
Date: 2026-05-06 Author: Opus (Claude) Status: APPROACH NOTE — chờ GPT/User review. Không implement. Supersedes: 23-P3 design pack (Model D hybrid) — kiến trúc gốc giữ lại, thuật ngữ và framing thay đổi.
Mục tiêu bài toán
AI/Agent cần sửa miếng thông tin (IU) một cách an toàn: tạo bản nháp → thảo luận → duyệt/áp dụng → version mới tự động. Agent không cần nhớ version_seq, birth, anchor, gateway marker, hay direct table write.
Nôm na: Sổ biên tập — ai muốn sửa thì viết bản nháp lên sổ, đồng nghiệp ghi chú góp ý, tổng biên tập duyệt xong thì bản chính tự cập nhật. Ai sửa gì, ai góp ý gì, lúc nào — đều có ghi.
Non-goals (Phase 1)
- Không xây Git: không branch, không 3-way merge, không diff engine, không rebase.
- Không xây review UI: review qua function call, không cần giao diện phức tạp.
- Không concurrent conflict resolution: stale base = từ chối, tạo draft mới.
- Không vector update khi apply.
- Không role separation (self-apply OK Phase 1).
- Không edit/delete comment (append-only).
Opus phản biện GPT — 10 câu hỏi
Q1. Tên bảng: unit_edit_draft / unit_edit_comment hay unit_proposal?
Đồng ý GPT — dùng unit_edit_draft + unit_edit_comment.
Lý do:
- "draft" là ngôn ngữ biên tập, Agent hiểu ngay: "tạo bản nháp, duyệt bản nháp, áp dụng bản nháp".
- "proposal" gợi Git/PR — đúng kỹ thuật nhưng tạo kỳ vọng sai (Agent nghĩ phải có branch, diff, rebase...).
- Tên bảng = API surface cho Agent. Dễ hiểu = ít lỗi gọi.
Namespace: unit_edit_draft và unit_edit_comment nhất quán với information_unit, unit_version — prefix unit_ cho domain IU.
Q2. Reuse bảng có sẵn hay tạo mới?
Đề xuất tạo mới. Không reuse Directus internal.
Tôi đã kiểm tra qua inspection 23-P2: Directus có directus_revisions (item snapshot), directus_activity (audit log), directus_notifications. Nhưng:
directus_revisionsgắn chặt Directus collection/item ID. IU dùng canonical_address + PG function path, không qua Directus CRUD. Reuse = phải fake Directus item context hoặc bypass Directus internal logic.directus_activitylà audit trail của Directus operations, không phải domain comment.- Coupling domain logic vào Directus internal = fragile. Directus upgrade có thể break.
Assembly First check: PG CREATE TABLE + 2 functions = chi phí thấp, ổn định, không phụ thuộc external. Đây đúng là "tận dụng PG native" — relational table là primitive cơ bản nhất.
Q3. Replacement body hay diff/patch?
Đồng ý GPT — Phase 1 = replacement body toàn phần.
Lý do Assembly First:
- PG không có diff engine native. Muốn diff phải custom code hoặc extension → vi phạm "code custom là phương án cuối".
- Replacement body: 1 column text, đọc thẳng, so sánh bằng content_hash, apply = copy thẳng sang UV.
- Diff chỉ là view concern — hiển thị cho người dùng "thay đổi gì" — có thể compute runtime bằng PG
regexp_split_to_arrayhoặc Nuxt client-side, không cần lưu.
Q4. Khi apply draft, các draft khác xử lý sao?
Không đồng ý GPT hoàn toàn. Đề xuất: mark stale_base thay vì supersede.
GPT đưa 3 options. Phân tích:
| Option | Ưu | Nhược |
|---|---|---|
| Supersede all active | Sạch, dễ hiểu | Draft bị đóng vĩnh viễn. Nếu Phase 2 có rebase, phải tạo lại từ đầu. |
| Mark stale_base | Thông tin hơn, giữ draft sống | Drafts tích lũy nếu không dọn |
| Keep open + warning | Linh hoạt nhất | Phức tạp, dễ nhầm |
Đề xuất: Mark stale_base — cùng đơn giản như supersede (1 UPDATE), nhưng giữ ngữ nghĩa rõ hơn:
- stale_base = "base đã cũ, draft này không áp dụng được nữa trừ khi tạo lại".
- Phase 1: stale_base = effectively closed (không có rebase).
- Phase 2: stale_base có thể rebase — không cần recreate.
Status flow:
draft → open → applied
→ stale_base (khi draft khác được apply cùng IU)
→ withdrawn (tác giả tự rút, Phase 2)
Đơn giản hơn 23-P3 cũ (bỏ rejected, superseded, proposed — quá nhiều trạng thái kiểu Git).
Q5. Approval comment bắt buộc Phase 1?
Đồng ý GPT — policy hook, không bắt buộc global.
Phase 1:
- Default =
auto_apply(Agent tạo draft + apply cùng transaction qua fn_iu_edit). - Protected/enacted IU:
require_review→ draft chờ, phải có apply riêng. - Policy source: dot_config key
iu_edit.default_apply_policy = auto_apply. Per-IU override quaidentity_profile->>'edit_policy'.
Approval comment không bắt buộc cho apply. Reviewer gọi fn_iu_apply_edit_draft là đủ — review_note trong function signature ghi lý do.
Q6. Gateway allow-list cần function nào?
Đồng ý GPT — chỉ function ghi IU/UV cần marker.
| Function | Ghi bảng nào | Cần gateway marker? |
|---|---|---|
| fn_iu_create | IU + UV | ✓ Đã có |
| fn_iu_apply_edit_draft | UV + IU anchors | ✓ Cần thêm |
| fn_iu_edit | Wrapper gọi apply bên trong | ✓ Cần thêm |
| fn_iu_create_edit_draft | unit_edit_draft only | ✗ Không |
| fn_iu_comment_edit_draft | unit_edit_comment only | ✗ Không |
Allow-list = 3 markers: fn_iu_create, fn_iu_apply_edit_draft, fn_iu_edit.
Giống kết luận 23-P3 cũ, chỉ đổi tên fn_iu_merge_edit → fn_iu_apply_edit_draft.
Q7. Title edit?
Đề xuất: Phase 1 cho draft_title nullable.
Lý do: title là metadata quan trọng, sửa title cùng lúc body là use case thường xuyên. Tách thành 2 operations = phiền.
Apply logic: nếu draft_title IS NOT NULL → update identity_profile = jsonb_set(identity_profile, '{title}', to_jsonb(draft_title)) cùng transaction.
Chi phí: 1 column nullable + 1 dòng logic trong apply function. Rất thấp.
Q8. Sort order: column hay JSONB?
Giữ quan điểm 23-P3: core column sort_order INT trên information_unit.
Lý do đã nêu:
- Greenfield 5 rows = ALTER TABLE miễn phí.
- "Vĩnh viễn hay tạm?" → sort_order là vĩnh viễn.
- Render hot path =
ORDER BY sort_order— native index, không cast, không expression index. - JSONB bây giờ + migrate core column sau = effort gấp đôi.
-- Design only:
ALTER TABLE information_unit ADD COLUMN sort_order integer;
CREATE INDEX idx_iu_sort_order
ON information_unit(parent_or_container_ref, sort_order)
WHERE parent_or_container_ref IS NOT NULL;
Q9. Assembly First / PG First compliance?
Đề xuất GPT đạt chuẩn Assembly First. Tốt hơn 23-P3 cũ.
Checklist:
| Nguyên tắc | 23-P3 cũ (Model D) | GPT đề xuất mới | Đánh giá |
|---|---|---|---|
| Q0: PG đã giải quyết chưa? | ✓ PG function + table | ✓ Tương đương | Ngang |
| Tận dụng existing gateway | ✓ Allow-list patch | ✓ Tương đương | Ngang |
| Code custom tối thiểu | ⚠ 6 functions | ✓ 5 functions (bỏ reject riêng) | Tốt hơn |
| Agent API đơn giản | ⚠ Thuật ngữ Git (propose/merge/reject) | ✓ Thuật ngữ biên tập (draft/comment/apply) | Tốt hơn |
| Comment system | ✗ Không có trong 23-P3 | ✓ Append-only comment | Tốt hơn |
| Status model | ⚠ 5 trạng thái (draft/proposed/merged/rejected/superseded) | ✓ 3 trạng thái (open/applied/stale_base) | Đơn giản hơn |
Điểm lệch nhỏ cần lưu ý: Tạo 2 bảng mới (draft + comment) thay vì 1 (proposal). Nhưng comment append-only = bảng cực đơn giản (INSERT only, no UPDATE/DELETE), chi phí bảo trì gần zero.
Q10. Chia phase
Đồng ý GPT, điều chỉnh nhỏ:
| Phase | Nội dung | Ghi chú |
|---|---|---|
| P3A | Gateway allow-list patch | 3 markers. Nhỏ, test riêng. |
| P3B | DDL: unit_edit_draft + unit_edit_comment + sort_order column | Schema only. |
| P3C | Functions: create_draft, comment, apply, edit wrapper | Logic chính. |
| P3D | Pilot: tạo parent + children IU, edit 1 child, verify | End-to-end. |
Lý do tách B/C: schema trước, function sau = inspect schema → GPT review → viết function trên nền đã verify. Đúng pattern Pack 22.
Reuse / Existing Assets
| Asset | Tái dùng | Ghi chú |
|---|---|---|
| fn_iu_create | Nguyên trạng | Tạo IU mới (parent/child) |
| fn_iu_create_plan | Nguyên trạng | Dry-run IU creation |
| fn_iu_verify_invariants | Gọi trong apply | Không sửa, chỉ gọi |
| fn_content_hash | Gọi trong create_draft + apply | SHA-256 compute |
| fn_iu_gateway_write_guard | Patch allow-list | Sửa nhỏ: exact → membership |
| dot_config | Thêm keys | allowed_marker_values, default_apply_policy |
| Gateway README | Update | Thêm section edit/apply path |
Schema tối thiểu (design only)
unit_edit_draft
| Column | Type | Null | Default | Mục đích |
|---|---|---|---|---|
| id | uuid | NO | gen_random_uuid() | PK |
| unit_id | uuid | NO | — | FK → information_unit |
| base_version_ref | uuid | NO | — | FK → unit_version — bản chính tại thời điểm tạo draft |
| base_content_hash | text | NO | — | Fast stale detection |
| draft_body | text | NO | — | Nội dung nháp |
| draft_content_hash | text | NO | — | SHA-256 của draft_body |
| draft_title | text | YES | NULL | Title mới (NULL = giữ nguyên) |
| draft_status | text | NO | 'open' | open / applied / stale_base / withdrawn |
| author_ref | text | NO | — | Ai tạo draft |
| reason | text | YES | NULL | Tại sao sửa |
| metadata | jsonb | NO | '{}' | Extensible |
| created_at | timestamptz | NO | now() | |
| applied_at | timestamptz | YES | NULL | Khi nào apply (NULL nếu chưa) |
| applied_by | text | YES | NULL | Ai apply |
| applied_version_ref | uuid | YES | NULL | FK → unit_version tạo ra khi apply |
Constraints: PK(id), FK(unit_id→IU), FK(base_version_ref→UV), CHECK(draft_status IN (...)). Indexes: btree(unit_id, draft_status), btree(base_version_ref).
unit_edit_comment
| Column | Type | Null | Default | Mục đích |
|---|---|---|---|---|
| id | uuid | NO | gen_random_uuid() | PK |
| draft_id | uuid | NO | — | FK → unit_edit_draft |
| author_ref | text | NO | — | Ai comment |
| author_type | text | NO | 'agent' | user / agent / system |
| comment_body | text | NO | — | Nội dung |
| comment_kind | text | NO | 'general' | general / review / approval / change_request / system |
| target_path | text | YES | NULL | Vị trí cụ thể trong body (Phase 2 use) |
| metadata | jsonb | NO | '{}' | Extensible |
| created_at | timestamptz | NO | now() |
Constraints: PK(id), FK(draft_id→unit_edit_draft), CHECK(author_type IN (...)), CHECK(comment_kind IN (...)). Indexes: btree(draft_id, created_at).
Append-only: Không UPDATE, không DELETE trên unit_edit_comment. INSERT only.
Function surface tối thiểu
| # | Function | Ghi IU/UV? | Gateway marker? | Phase |
|---|---|---|---|---|
| 1 | fn_iu_create_edit_draft(address, body, actor, reason, title) | Không | Không | 1 |
| 2 | fn_iu_comment_edit_draft(draft_id, author, author_type, body, kind) | Không | Không | 1 |
| 3 | fn_iu_apply_edit_draft(draft_id, reviewer, review_note) | Có (UV + IU) | Có | 1 |
| 4 | fn_iu_edit(address, body, actor, reason, title) | Có (qua apply) | Có | 1 |
| 5 | fn_iu_edit_plan(address, body, actor) | Không (read-only) | Không | 1 |
5 functions. So với 23-P3 cũ: bỏ fn_iu_reject_edit (thay bằng status update đơn giản nếu cần), thêm comment function. Net = tương đương complexity.
Policy model
dot_config:
iu_edit.default_apply_policy = auto_apply -- Phase 1 default
iu_edit.stale_draft_action = mark_stale -- khi apply 1 draft, các draft khác cùng IU
Per-IU override:
identity_profile->>'edit_policy' = 'require_review' -- cho enacted/protected IU
fn_iu_edit kiểm tra policy:
- auto_apply → create_draft + apply trong 1 transaction
- require_review → create_draft only, return {status: 'draft_created', requires_review: true}
Scale / metadata evolution
- JSONB trên draft + comment cho metadata mở rộng.
- Hot-path fields (draft_status, unit_id, base_version_ref) là column riêng + index.
- Draft/comment tích lũy theo thời gian → retention policy Phase 2 (archive applied drafts > N ngày).
- Governance path: dot_config cho allowed metadata keys khi cần.
Multi-axis composition
Giữ nguyên từ 23-P3:
- Document/workflow containment: parent_or_container_ref + sort_order column
- Domain traceability: universal_edges UPPERCASE typed relations
- Không gom: 2 trục tách biệt trong schema, đúng user directive
Risks
| # | Risk | Mitigation |
|---|---|---|
| 1 | Birth trigger fire khi fn_iu_apply_edit_draft INSERT UV | Kiểm tra P3B — birth trigger trên UV hay chỉ IU? |
| 2 | UV lifecycle_status mới ('merged' hay giữ 'draft'?) | Apply set lifecycle_status = giá trị phù hợp. Cần chốt. |
| 3 | Stale drafts tích lũy | Phase 2 retention/archive. Phase 1: ít agent = ít draft. |
| 4 | Comment spam | Append-only + author tracking. Phase 2: rate limit nếu cần. |
| 5 | Title update qua JSONB patch | jsonb_set atomic trong transaction. An toàn. |
Tổng kết phản biện
| Điểm | Đồng ý GPT? | Ghi chú Opus |
|---|---|---|
| Tên draft/comment thay proposal | ✓ Đồng ý | Dễ hiểu hơn cho Agent |
| Tạo bảng mới, không reuse Directus | ✓ Đồng ý | PG native, stable |
| Replacement body Phase 1 | ✓ Đồng ý | Diff = view concern |
| Supersede all active drafts | ✗ Đề xuất mark stale_base | Giữ draft sống, thông tin hơn |
| Approval không bắt buộc global | ✓ Đồng ý | Policy hook đủ |
| Gateway chỉ cho IU/UV writers | ✓ Đồng ý | 3 markers |
| Draft title nullable | ✓ Đồng ý | Chi phí thấp, hữu ích |
| Sort order | ✗ Core column, không JSONB | Greenfield, làm đúng từ đầu |
| Assembly First compliance | ✓ Đề xuất GPT đạt chuẩn | Tốt hơn 23-P3 cũ |
| Phase split | ✓ Đồng ý, chỉnh nhỏ | Tách schema (B) và function (C) |
23-P3A Approach Note | 2026-05-06 | Opus phản biện GPT | Chờ review