KB-3DE4
S175 Why Round 2 Codex
11 min read Revision 1
s175rcadirectusagent-datacodex
WHY-1: Event sync miss
- Root cause 1 dòng: Chưa đủ evidence để kết luận
document.updatedkhông fire; bằng chứng hiện có cho thấy đường sync làfire-and-forgetvà sink Directus chỉPATCHđúng 1 row lấy bằngsource_idvớilimit=1, nên khi cùng logical document đã có 2 rowis_current_version=truethì update rev mới chỉ có thể chạm 1 row còn row stale vẫn sống. - Evidence:
Code path MCP -> server:
mcp_server/server.py:288-303luôn gửicontentvà chỉ thêmmetadatakhi cótitle/tags.agent_data/server.py:1436-1448phátDOCUMENT_UPDATEDbằngget_event_bus().emit_fire_and_forget(...).agent_data/event_system.py:392-407cho thấy_safe_emit()nuốt exception với logEvent emit failed (swallowed): %s, nên nếu emit/listener lỗi thì caller vẫn trả200.agent_data/directus_sync.py:41-54cho thấy NĐ-36-01 không bị skip theo prefix vì path nằm dướiknowledge/.agent_data/directus_sync.py:124-141tra cứu Directus bằngsource_idvớilimit: 1;agent_data/directus_sync.py:253-286nếu tìm thấy thì chỉPATCHđúngdirectus_idđầu tiên trả về. SQL live trên VPS cho cùngsource_idcủa NĐ-36-01:
Query field-level cho đúng 2 row đó:Command:ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT id,slug,file_path,source_id,version_group_id,version_number,is_current_version,date_updated,tags FROM public.knowledge_documents WHERE source_id='agentdata:knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md' ORDER BY id;\""Output:1074|dev-architecture-nd-36-01-semantic-relationship-infrastructure-draft|knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|agentdata:knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|eaa4c033-2b61-4deb-85e3-8c8b0b9cc3b2|2|t|2026-04-06 12:33:20.945|["law", "nd-36-01", "semantic", "infrastructure", "approved-p1", "s170"]1091|dev-laws-nd-36-01-semantic-relationship-infrastructure|knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|agentdata:knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|15faa507-9b18-4f2f-bd6c-84c4a3043cb9|4|t|2026-04-06 14:35:27.654|["law", "nd-36-01", "semantic", "infrastructure", "approved-p1", "s170"]
Runtime log chứng minh có update activity trên đúng logical doc này, nhưng không còn preserved log nào choCommand:ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT id, version_number, title, (content ILIKE '%BAN HÀNH%')::int AS has_ban_hanh, (content ILIKE '%THÔNG QUA%')::int AS has_thong_qua, tags FROM public.knowledge_documents WHERE source_id='agentdata:knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md' ORDER BY id;\""Output:1074|2|NĐ-36-01 v1.2 — Xây dựng bản quan hệ ngữ nghĩa (Phần 1 THÔNG QUA)|0|1|["law", "nd-36-01", "semantic", "infrastructure", "approved-p1", "s170"]1091|4|NĐ-36-01 v1.2 — Xây dựng bản quan hệ ngữ nghĩa (FULL — Phần 1 THÔNG QUA)|1|1|["law", "nd-36-01", "semantic", "infrastructure", "approved-p1", "s170"]Directus sync ... -> statushoặcEvent emit failed (swallowed):Command:ssh root@38.242.240.89 "docker logs incomex-agent-data 2>&1 | grep 'nd-36-01-semantic-relationship-infrastructure' | tail -40"Output excerpt:INFO: 172.18.0.7:34894 - "PUT /documents/knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md HTTP/1.1" 200 OKINFO: 172.18.0.7:56790 - "PATCH /documents/knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md HTTP/1.1" 409 ConflictINFO: 172.18.0.7:34632 - "PATCH /documents/knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md HTTP/1.1" 200 OKINFO: 172.18.0.7:53596 - "PATCH /documents/knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md HTTP/1.1" 200 OKINFO: 172.18.0.7:34150 - "GET /documents/knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md?full=true&search=false HTTP/1.1" 200 OKINFO: 172.18.0.7:51506 - "PUT /documents/knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md HTTP/1.1" 200 OK - Mức độ chắc chắn: TRUNG BÌNH
- Nếu thấp: cần preserved
event_log/structured listener logs đúng cửa sổ update NĐ-36-01 để khẳng định event thực sự bị miss ở emit stage hay listener stage; với evidence hiện tại tôi chỉ khẳng định chắc phần sink chỉ cập nhật được 1 row trong khi DB đã có 2 current rows cho cùngsource_id.
WHY-2: Schema cho phép duplicate
- Root cause 1 dòng:
knowledge_documentschỉ khóaslug; nó không khóa logical identity (source_id,file_path,version_group_id) và writer code luôn ghiis_current_version=truemà không hạ row cũ trong cùng transaction, nên DB cho phép 2 row current cho cùng document logic. - Evidence:
Schema live trên VPS:
Không có unique index nào trênCommand:ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -c \"SELECT indexname,indexdef FROM pg_indexes WHERE schemaname='public' AND tablename='knowledge_documents' ORDER BY indexname;\""Output:idx_knowledge_documents_knowledge_documents_content_request_id_ | CREATE INDEX ... (content_request_id)idx_knowledge_documents_knowledge_documents_parent_document_id_ | CREATE INDEX ... (parent_document_id)idx_knowledge_documents_knowledge_documents_slug_unique | CREATE UNIQUE INDEX ... (slug)knowledge_documents_pkey | CREATE UNIQUE INDEX ... (id)source_id,file_path,version_group_id, hay partial unique kiểu(source_id, is_current_version) WHERE is_current_version. Writer path hiện tại:agent_data/directus_sync.py:171-193luôn build payload vớiis_current_version: True; chỉ nhánh create mới phátversion_group_id = uuid4().agent_data/directus_sync.py:214-250vàagent_data/directus_sync.py:266-286chỉ có 2 hành vi:PATCH1 row nếu tìm thấy, hoặcPOSTrow mới nếu không tìm thấy. Không có bướcSET is_current_version=falsecho row cũ. Test cũng xác nhận đúng hành vi đó:tests/test_directus_sync.py:74-100kiểmcreatecóversion_group_id, cònupdatethì không có. Duplicate current rows là trạng thái live, không phải giả thuyết:
NĐ-36-01 còn cho thấy 2 current rows cóCommand:ssh root@38.242.240.89 "docker exec postgres psql -U directus -d directus -Atc \"SELECT file_path,count(*) FILTER (WHERE is_current_version), count(*) FROM public.knowledge_documents GROUP BY 1 HAVING count(*) FILTER (WHERE is_current_version) > 1 ORDER BY 2 DESC,1 LIMIT 20;\""Output excerpt:knowledge/dev/laws/dieu26-pivot-law.md|2|2knowledge/dev/laws/dieu31-system-integrity-law.md|2|2knowledge/dev/laws/dieu38-normative-document-law.md|2|2knowledge/dev/laws/label-law.md|2|2knowledge/dev/laws/nd-36-01-semantic-relationship-infrastructure.md|2|2version_group_idkhác nhau, nghĩa là hệ thống đã tạo hai version streams độc lập cho cùng logical doc. - Mức độ chắc chắn: CAO
- Nếu thấp: Không áp dụng.
WHY-3: limit=500 hardcode
- Root cause 1 dòng:
limit=500là code tay trong project, không phải default của Directus SDK; nhiều script/composable đang lấy snapshot bị cap rồihead -1, nên cùng một patterncapped fetch + first match winsxuất hiện ở nhiều chỗ. - Evidence:
Repo Nuxt đang dùng
@directus/sdkhiện tại:web/package.json:46->"@directus/sdk": "^19.1.0". Directus docs chính thức nói API mặc định giới hạn 100 item, và muốn lấy hết phải truyềnlimit: -1. Source: https://docs.directus.io/reference/old-sdk Relevant doc snippet:By default API limits results to 100và ví dụlimit: -1. Matchlimit=500trong codebase:web/composables/useKnowledgeTree.ts:101->limit: 500, // Increased for full treedot/bin/dot-knowledge-sync-agentdata:87->...knowledge_documents?...&limit=500dot/bin/dot-knowledge-sync-github:58->...knowledge_documents?...&limit=500Git blame cho thấy đây là code tay do project thêm vào:
Pattern cùng họ:web/composables/useKnowledgeTree.ts:101138d1cf5 (Nguyễn Minh Huyên 2026-03-04 16:04:27 +0700 101) limit: 500, // Increased for full treedot/bin/dot-knowledge-sync-agentdata:876a091451 (Nguyễn Minh Huyên 2026-02-09 18:00:30 +0700 87) curl -s "$DIRECTUS_URL/items/knowledge_documents?fields=id,source_id,slug,title,file_path&limit=500" \dot/bin/dot-knowledge-sync-github:581ac4395f (Nguyễn Minh Huyên 2026-02-05 17:03:28 +0700 58) curl -s "$DIRECTUS_URL/items/knowledge_documents?fields=id,slug,file_path,source_id&limit=500" \dot/bin/dot-knowledge-sync-agentdata:148-149-> match theosource_idrồihead -1dot/bin/dot-knowledge-sync:108-113-> match theosource_id, fallbackslug, rồihead -1dot/bin/dot-knowledge-sync-github:106-110-> matchslug OR file_path OR old-style flat slug, rồihead -1Quan trọng: triệu chứng duplicate ở sidebar hiện tại không do[...slug].vuebị cap500, vì file này đang fetchlimit: -1. Evidence:web/pages/knowledge/[...slug].vue:59-69fetch sidebar vớilimit: -1web/pages/knowledge/[...slug].vue:97-103map từng row hiện tại trực tiếp, không dedupeweb/pages/knowledge/[...slug].vue:204-240detail view lại dùnglimit: 1 - Mức độ chắc chắn: CAO
- Nếu thấp: Không áp dụng.
Observations phụ
- Pattern duplicate không riêng NĐ-36-01. Query live cho thấy ít nhất
16file_pathđang có2current rows, chủ yếu trong nhóm law docs và cácREADME.md. - Sidebar duplicate ở Nuxt là hậu quả trực tiếp của DB duplicate chứ không phải do cap
500:[...slug].vueđã dùnglimit: -1, sau đó render tất cả row current trả về. - Detail page lại dùng
limit: 1, nên cùng lúc sidebar có thể thấy 2 row còn nội dung trang chỉ lấy row đầu tiên Directus trả về. Đây là một nguồn lệch pha UI độc lập với event bus. - Có dấu vết thiết kế index đã được viết ra nhưng chưa áp dụng xuống DB live:
scripts/0047c_migration_knowledge.ts:517-527liệt kêidx_current_published,idx_version_history, ... dưới nhãnTODO - manual or separate script.git blame -L 519,524 -- scripts/0047c_migration_knowledge.tsgán phần này cho commit36904b86ngày2025-12-08 13:29:04 +0700. - NĐ-36-01 có lịch sử lifecycle phức tạp hơn update thường: log runtime cho thấy có cả
PATCH/PUTtrên path cũknowledge/dev/architecture/...draft.md,POST .../move,DELETEpath cũ, rồiPUT/PATCHtrên path mớiknowledge/dev/laws/...md. Tôi coi đây là dấu hiệu risk cao, nhưng evidence hiện tại chưa đủ để quy một-một chuỗi này thành nguyên nhân duy nhất tạo ra row1091.