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.updated không fire; bằng chứng hiện có cho thấy đường sync là fire-and-forget và sink Directus chỉ PATCH đúng 1 row lấy bằng source_id với limit=1, nên khi cùng logical document đã có 2 row is_current_version=true thì 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-303 luôn gửi content và chỉ thêm metadata khi có title/tags. agent_data/server.py:1436-1448 phát DOCUMENT_UPDATED bằng get_event_bus().emit_fire_and_forget(...). agent_data/event_system.py:392-407 cho thấy _safe_emit() nuốt exception với log Event emit failed (swallowed): %s, nên nếu emit/listener lỗi thì caller vẫn trả 200. agent_data/directus_sync.py:41-54 cho thấy NĐ-36-01 không bị skip theo prefix vì path nằm dưới knowledge/. agent_data/directus_sync.py:124-141 tra cứu Directus bằng source_id với limit: 1; agent_data/directus_sync.py:253-286 nếu tìm thấy thì chỉ PATCH đúng directus_id đầu tiên trả về. SQL live trên VPS cho cùng source_id của NĐ-36-01:
    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"]
    
    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, 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"]
    
    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 cho Directus sync ... -> status hoặc Event 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ùng source_id.

WHY-2: Schema cho phép duplicate

  • Root cause 1 dòng: knowledge_documents chỉ khóa slug; nó không khóa logical identity (source_id, file_path, version_group_id) và writer code luôn ghi is_current_version=true mà 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:
    Command: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)
    
    Không có unique index nào trên 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-193 luôn build payload với is_current_version: True; chỉ nhánh create mới phát version_group_id = uuid4(). agent_data/directus_sync.py:214-250agent_data/directus_sync.py:266-286 chỉ có 2 hành vi: PATCH 1 row nếu tìm thấy, hoặc POST row mới nếu không tìm thấy. Không có bước SET is_current_version=false cho row cũ. Test cũng xác nhận đúng hành vi đó: tests/test_directus_sync.py:74-100 kiểm createversion_group_id, còn update thì không có. Duplicate current rows là trạng thái live, không phải giả thuyết:
    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|2
    
    NĐ-36-01 còn cho thấy 2 current rows có version_group_id khá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=500 là code tay trong project, không phải default của Directus SDK; nhiều script/composable đang lấy snapshot bị cap rồi head -1, nên cùng một pattern capped fetch + first match wins xuất hiện ở nhiều chỗ.
  • Evidence: Repo Nuxt đang dùng @directus/sdk hiệ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ền limit: -1. Source: https://docs.directus.io/reference/old-sdk Relevant doc snippet: By default API limits results to 100 và ví dụ limit: -1. Match limit=500 trong codebase: web/composables/useKnowledgeTree.ts:101 -> limit: 500, // Increased for full tree dot/bin/dot-knowledge-sync-agentdata:87 -> ...knowledge_documents?...&limit=500 dot/bin/dot-knowledge-sync-github:58 -> ...knowledge_documents?...&limit=500 Git blame cho thấy đây là code tay do project thêm vào:
    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" \
    
    Pattern cùng họ: dot/bin/dot-knowledge-sync-agentdata:148-149 -> match theo source_id rồi head -1 dot/bin/dot-knowledge-sync:108-113 -> match theo source_id, fallback slug, rồi head -1 dot/bin/dot-knowledge-sync-github:106-110 -> match slug OR file_path OR old-style flat slug, rồi head -1 Quan trọng: triệu chứng duplicate ở sidebar hiện tại không do [...slug].vue bị cap 500, vì file này đang fetch limit: -1. Evidence: web/pages/knowledge/[...slug].vue:59-69 fetch sidebar với limit: -1 web/pages/knowledge/[...slug].vue:97-103 map từng row hiện tại trực tiếp, không dedupe web/pages/knowledge/[...slug].vue:204-240 detail view lại dùng limit: 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 16 file_path đang có 2 current rows, chủ yếu trong nhóm law docs và các README.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ùng limit: -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-527 liệt kê idx_current_published, idx_version_history, ... dưới nhãn TODO - manual or separate script. git blame -L 519,524 -- scripts/0047c_migration_knowledge.ts gán phần này cho commit 36904b86 ngày 2025-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/PUT trên path cũ knowledge/dev/architecture/...draft.md, POST .../move, DELETE path cũ, rồi PUT/PATCH trên path mới knowledge/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 row 1091.

KHÔNG đề xuất fix trong báo cáo này.