KB-7DB6

S175 Fix Execution

15 min read Revision 1
s175fixdirectusagent-datadedupeschema

S175 Fix Execution — P0 Backup & Dedupe Plan

Date: 2026-04-09
Agent: Codex
Status: P0 COMPLETE — P1/P2/P3/P4 NOT STARTED
Stop gate: Chờ Desktop duyệt plan dedupe trước khi chạy transaction đầu tiên.

Bước 0 — Checkpoint đã đọc

  • Skill: .claude/skills/incomex-rules.md
  • KB: search_knowledge("operating rules SSOT")
  • KB: search_knowledge("hiến pháp v4.0 constitution")
  • KB: search_knowledge("S175 duplicate rows schema hardening directus agent-data")
  • Reports tham chiếu: knowledge/current-state/reports/s175-why-round2-codex.md, knowledge/current-state/reports/s175-why-round2-gemini.md

3 câu Tuyên ngôn cho mission này

  1. Vĩnh viễn?
    Có, nếu và chỉ nếu fix đủ 3 lớp: dọn duplicate current rows hiện hữu, chặn schema ở DB, và đổi writer sang atomic path. P0 này mới hoàn tất lớp chuẩn bị và chặn nhầm trước khi đụng data.

  2. Nhầm được không?
    P0 dùng snapshot SQL + bảng KEEP/ARCHIVE explicit theo từng id. Chưa có duyệt của Desktop thì không chạy UPDATE, nên hiện tại chưa có rủi ro half-state.

  3. 100% tự động?
    Sau khi được duyệt, P1/P2/P3 phải chạy bằng SQL/script có verify. Không có bước “xem rồi chỉnh tay từng row” trong production.

Executive Summary

P0 đã hoàn tất backup và inventory. Live DB ngày 2026-04-09 không còn đúng giả định 9 law docs / 18 rows trong prompt nữa. Hiện tại có:

  • 13 duplicate source_id groups trong agentdata:knowledge/dev/laws/%
  • 26 current law rows liên quan P1 nếu giữ scope theo Agent Data law sync
  • 3 duplicate README groups ngoài scope luật, tổng 6 current rows, sẽ chặn một global UNIQUE(file_path) nếu không xử lý cùng phiên
  • 15 current rows có source_id IS NULL
  • 0 current rows có file_path IS NULL

Kết luận P0 của tôi:

  • KEEP candidate cho 13 law groups là row có slug canonical dev-laws-*
  • ARCHIVE candidate là row legacy dev-architecture-* hoặc slug cũ tương đương
  • Với scope hẹp đúng prompt, P2 nên ưu tiên partial unique trên source_id cho current rows non-null, không phải file_path, vì file_path sẽ kéo thêm 3 README groups ngoài scope vào cùng migration

Tôi chưa chạy P1. Chỉ mới snapshot + lập plan + dừng đúng gate phê duyệt.

P0 Snapshot

Snapshot file: s175-knowledge_documents-pre-fix-20260409.sql
Size: 4,242,295 bytes
Timestamp local: Apr 9 13:42:42 2026

Evidence:

Command:
ssh root@38.242.240.89 "docker exec postgres pg_dump -U directus -d directus -t public.knowledge_documents --column-inserts --inserts" > reports/s175-knowledge_documents-pre-fix-20260409.sql

stat:
/Users/nmhuyen/Documents/Manual Deploy/web-test/reports/s175-knowledge_documents-pre-fix-20260409.sql 4242295 bytes Apr  9 13:42:42 2026

Evidence chính dùng để lập plan

1. Duplicate law groups thực tế

Command:
ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT source_id, file_path, count(*) FILTER (WHERE is_current_version) AS current_count, count(*) AS total_count FROM public.knowledge_documents WHERE source_id LIKE 'agentdata:knowledge/dev/laws/%' GROUP BY 1,2 HAVING count(*) FILTER (WHERE is_current_version) > 1 ORDER BY file_path;\""

Output:
agentdata:knowledge/dev/laws/dieu26-pivot-law.md|knowledge/dev/laws/dieu26-pivot-law.md|2|2
agentdata:knowledge/dev/laws/dieu28-display-technology-law.md|knowledge/dev/laws/dieu28-display-technology-law.md|2|2
agentdata:knowledge/dev/laws/dieu29-classification-law.md|knowledge/dev/laws/dieu29-classification-law.md|2|2
agentdata:knowledge/dev/laws/dieu30-regression-protection-law.md|knowledge/dev/laws/dieu30-regression-protection-law.md|2|2
agentdata:knowledge/dev/laws/dieu31-system-integrity-law.md|knowledge/dev/laws/dieu31-system-integrity-law.md|2|2
agentdata:knowledge/dev/laws/dieu32-approval-law.md|knowledge/dev/laws/dieu32-approval-law.md|2|2
agentdata:knowledge/dev/laws/dieu33-postgresql-law.md|knowledge/dev/laws/dieu33-postgresql-law.md|2|2
agentdata:knowledge/dev/laws/dieu34-workflow-law.md|knowledge/dev/laws/dieu34-workflow-law.md|2|2
agentdata:knowledge/dev/laws/dieu35-dot-governance-law.md|knowledge/dev/laws/dieu35-dot-governance-law.md|2|2
agentdata:knowledge/dev/laws/dieu37-governance-organization-law.md|knowledge/dev/laws/dieu37-governance-organization-law.md|2|2
agentdata:knowledge/dev/laws/dieu38-normative-document-law.md|knowledge/dev/laws/dieu38-normative-document-law.md|2|2
agentdata:knowledge/dev/laws/label-law.md|knowledge/dev/laws/label-law.md|2|2
agentdata:knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|2|2

2. Nullability để chọn khóa P2

Command:
ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT column_name, is_nullable FROM information_schema.columns WHERE table_schema='public' AND table_name='knowledge_documents' AND column_name IN ('source_id','file_path','is_current_version');\""

Output:
is_current_version|NO
file_path|YES
source_id|YES
Command:
ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT count(*) FILTER (WHERE is_current_version AND source_id IS NULL), count(*) FILTER (WHERE is_current_version AND file_path IS NULL), count(*) FILTER (WHERE is_current_version) FROM public.knowledge_documents;\""

Output:
15|0|622

3. File-path duplicate blocker ngoài scope luật

Command:
ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT file_path, count(*) FROM public.knowledge_documents WHERE is_current_version AND file_path IS NOT NULL GROUP BY 1 HAVING count(*) > 1 ORDER BY count(*) DESC, file_path LIMIT 30;\""

Output excerpt:
knowledge/current-state/README.md|2
knowledge/current-tasks/README.md|2
knowledge/dev/laws/dieu26-pivot-law.md|2
...
knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|2
knowledge/other/README.md|2

Chi tiết 3 README groups:

298||knowledge/current-state/README.md|current-state|Trạng thái Hiện tại|1|t|2026-03-03 00:08:54
345|agentdata:knowledge/current-state/README.md|knowledge/current-state/README.md|current-state-readme|Current State|1|t|
299||knowledge/current-tasks/README.md||Việc đang làm|1|t|2026-03-03 00:08:55
346|agentdata:knowledge/current-tasks/README.md|knowledge/current-tasks/README.md|current-tasks-readme|Việc đang làm|1|t|
301||knowledge/other/README.md|other|Tài liệu Khác|1|t|2026-03-03 00:08:58
347|agentdata:knowledge/other/README.md|knowledge/other/README.md|other-readme|Other|1|t|

4. Canonical slug rule của writer hiện tại

agent_data/directus_sync.py:60-71:

def _make_slug(doc_id: str) -> str:
    slug = doc_id
    for prefix in ("docs/", "knowledge/"):
        if slug.startswith(prefix):
            slug = slug[len(prefix):]
            break
    slug = slug.removesuffix(".md")
    slug = slug.lower().replace("/", "-").replace(" ", "-").replace("_", "-")
    slug = slug.strip("-")
    return slug

Với path hiện tại knowledge/dev/laws/dieu31-system-integrity-law.md, slug canonical tạo ra là dev-laws-dieu31-system-integrity-law, không phải dev-architecture-system-integrity-law.

5. Agent Data KB hiện tại bám slug dev-laws-*

MCP batch-read cho 13 path luật scope S175 cho thấy metadata hiện tại đều ở namespace knowledge/dev/laws/...; sampled results:

  • knowledge/dev/laws/dieu26-pivot-law.md → revision 1, title Điều 26: LUẬT PIVOT — v4.0, tags có law, dieu-26
  • knowledge/dev/laws/dieu31-system-integrity-law.md → revision 1, tags có dieu-31
  • knowledge/dev/laws/dieu38-normative-document-law.md → revision 1, tags có dieu-38
  • knowledge/dev/laws/label-law.md → revision 1, tags law, dieu-24
  • knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md → revision 5, tags enacted-p1, s175

Điểm quan trọng:

  • 12/13 docs sampled từ KB hiện tại đang revision 1, nên row dev-laws-* revision 1 không mâu thuẫn với Agent Data hiện tại
  • NĐ-36-01 là ngoại lệ: KB revision 5, còn current Directus tốt hơn trong 2 row là id=1091 revision 4; id=1074 revision 2 còn xa hơn

P0 Dedupe Plan — KEEP vs ARCHIVE

Tiêu chí KEEP

  1. file_path đúng path hiện tại trong Agent Data KB
  2. slug đúng canonical rule từ _make_slug(current_path) => dev-laws-*
  3. Với cùng path, row có canonical slug được KEEP ngay cả khi row legacy có version_number cao hơn, vì row legacy không còn có thể được current writer sinh ra từ path hiện tại nữa
  4. Riêng NĐ-36-01: KEEP row canonical và mới hơn (id=1091), dù nó vẫn stale so với Agent Data rev5; stale này sẽ được P3 sửa sau khi writer atomic vào chỗ

Tiêu chí ARCHIVE

  1. slug legacy dev-architecture-* hoặc slug cũ không còn derivable từ knowledge/dev/laws/...
  2. Row là “ghost current” của lineage cũ nhưng vẫn đang is_current_version=true
  3. P1 chỉ đổi is_current_version=false, không xoá

Bảng plan

Source ID KEEP ARCHIVE Lý do giữ
agentdata:knowledge/dev/laws/dieu26-pivot-law.md 1080 854 1080.slug=dev-laws-dieu26-pivot-law đúng canonical path hiện tại
agentdata:knowledge/dev/laws/dieu28-display-technology-law.md 1081 986 1081 đúng canonical dev-laws-*; metadata khớp KB hiện tại
agentdata:knowledge/dev/laws/dieu29-classification-law.md 1082 748 1082 đúng canonical dev-laws-*; row 748 là slug cũ dev-architecture-*
agentdata:knowledge/dev/laws/dieu30-regression-protection-law.md 1083 752 1083 đúng canonical dev-laws-*
agentdata:knowledge/dev/laws/dieu31-system-integrity-law.md 1084 763 1084 đúng canonical dev-laws-*; KB current revision là 1
agentdata:knowledge/dev/laws/dieu32-approval-law.md 1085 874 1085 đúng canonical dev-laws-*
agentdata:knowledge/dev/laws/dieu33-postgresql-law.md 1086 875 1086 đúng canonical dev-laws-*; KB current revision là 1
agentdata:knowledge/dev/laws/dieu34-workflow-law.md 1087 880 1087 đúng canonical dev-laws-*
agentdata:knowledge/dev/laws/dieu35-dot-governance-law.md 1088 960 1088 đúng canonical dev-laws-*; row 960 giữ lineage cũ
agentdata:knowledge/dev/laws/dieu37-governance-organization-law.md 1089 993 1089 đúng canonical dev-laws-*
agentdata:knowledge/dev/laws/dieu38-normative-document-law.md 1090 1004 1090 đúng canonical dev-laws-*; KB current revision là 1
agentdata:knowledge/dev/laws/label-law.md 1079 586 1079 đúng canonical dev-laws-label-law; tags mới có law,dieu-24
agentdata:knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md 1091 1074 1091 đúng canonical dev-laws-*, revision/date mới hơn 1074; 1074 là row cũ partial title

P1 Execution Proposal (CHƯA CHẠY)

Nếu Desktop duyệt đúng plan trên, P1 sẽ chạy một transaction duy nhất:

  1. BEGIN
  2. UPDATE public.knowledge_documents SET is_current_version=false WHERE id IN (854,986,748,752,763,874,875,880,960,993,1004,586,1074);
  3. Verify trong cùng session:
    • SELECT source_id, COUNT(*) FROM public.knowledge_documents WHERE is_current_version=true AND source_id IN (...) GROUP BY 1 HAVING COUNT(*)>1;
  4. COMMIT

Rollback nếu lệch:

  1. ROLLBACK nếu transaction chưa commit
  2. Nếu commit lỗi verify sau đó, dùng snapshot s175-knowledge_documents-pre-fix-20260409.sql để restore table về pre-P1 state

P2 Recommendation (CHƯA IMPLEMENT)

Recommendation

Cho scope hẹp của S175 hiện tại, tôi đề xuất:

CREATE UNIQUE INDEX CONCURRENTLY idx_kd_current_source_id_unique
ON public.knowledge_documents (source_id)
WHERE is_current_version = true AND source_id IS NOT NULL;

Vì sao chưa chọn file_path

  • file_path mạnh hơn trên lý thuyết vì hiện tại current file_path NULL = 0
  • Nhưng live DB còn 3 README duplicate groups ngoài scope prompt
  • Nếu dùng UNIQUE(file_path) WHERE is_current_version=true, migration sẽ bị block cho đến khi dọn luôn 6 README rows này
  • Prompt hiện khóa scope vào schema constraint + writer atomic + dọn duplicate rows đang bàn cho agent-data law sync, không yêu cầu mở rộng sang README cleanup

Ý nghĩa

  • source_id partial unique chặn đúng lớp bug mà S175 đang gặp: Agent Data sync cho cùng logical document tạo >1 current rows
  • README/null-source_id drift phải được ghi rõ là residual khác, không được nuốt

P3 Writer Atomic Proposal (CHƯA IMPLEMENT)

Tôi chưa sửa code, nhưng P0 xác nhận đúng chỗ phải thay:

  • agent_data/directus_sync.py:124-141 hiện lookup source_id với limit=1
  • agent_data/directus_sync.py:253-286 hiện chỉ PATCH 1 row hoặc fallback create
  • agent_data/directus_sync.py:171-193 luôn build payload is_current_version=True

P3 trong PR sau khi được duyệt phải làm:

  1. Resolve canonical current row theo source_id
  2. Trong một transaction:
    • demote current rows cũ của source_id
    • upsert canonical row
  3. Fail giữa chừng => rollback sạch

Files Evidence đã tạo trong P0

Stop Gate

P0 xong. Tôi không chạy P1 cho đến khi Desktop duyệt một trong hai hướng:

  1. Hướng hẹp đúng prompt: xử lý 13 law groups/26 rows + partial unique theo source_id
  2. Hướng rộng hơn: xử lý luôn 3 README groups để có thể dùng file_path partial unique trong cùng PR

Hiện tại không có half-state nào trên VPS.