S176-INVESTIGATE K2-SUPPLEMENT — KS.1+KS.2+KS.3 done
title: "S176-INVESTIGATE K2-SUPPLEMENT — 6 mục bổ sung + WHY analysis" sprint: S176 phase: INVESTIGATE block: K2-SUPPLEMENT date: 2026-04-13 status: in_progress tags: ['s176','investigate','k2','supplement','why-analysis','phaseC']
S176-INVESTIGATE · K2-SUPPLEMENT — 6 mục bổ sung
Mỗi mục: Mục tiêu → Phương pháp → Kết quả → WHY → TD đề xuất → Câu hỏi mở. Triết lý: "Mỗi lỗi tìm thấy = cơ hội học gốc rễ". Chế độ CHỈ ĐỌC. Mask mọi secret.
KS.1 — DDL events thời MySQL (cửa sổ 96 ngày H2)
Mục tiêu
Khai thác cửa sổ directus_activity 2025-12-07 → 2026-03-13 (mốc S115 migration MySQL→PG, K0) để trả lời:
- AI sửa schema thời MySQL?
- Đi qua đường nào (UI/script/curl/python)?
- Có ai bypass
directus_activity(DDL không trace) không?
Phương pháp
SSH → docker exec postgres psql -U directus -d directus → query directus_activity join directus_users. Cửa sổ: timestamp BETWEEN '2025-12-07' AND '2026-03-13'. Lọc collection IN ('directus_collections','directus_fields','directus_relations').
Kết quả
1. Tổng quan 96 ngày
| Collection | Events |
|---|---|
directus_collections |
261 |
directus_fields |
1823 |
directus_relations |
0 |
| Tổng | 2084 DDL events |
2. Phân theo user × action
| Collection | Action | User | Count |
|---|---|---|---|
| directus_fields | create | admin@example.com | 1255 |
| directus_fields | update | admin@example.com | 544 |
| directus_collections | update | admin@example.com | 130 |
| directus_collections | create | admin@example.com | 122 |
| directus_fields | delete | admin@example.com | 24 |
| directus_collections | delete | admin@example.com | 9 |
→ 100% DDL events trong 96 ngày H2 thực hiện bởi 1 user duy nhất: admin@example.com. Vi phạm AP-03 (--admin) ở quy mô 2084 lần.
3. Phân theo user agent (con đường thực tế)
| User Agent | Count | Tỉ lệ | Phân loại |
|---|---|---|---|
Python-urllib/3.11 |
872 | 41.8% | Script Python (DOT/migration) |
node |
653 | 31.3% | Script Node.js (Nuxt/DOT TS) |
curl/8.7.1 |
404 | 19.4% | Bash/dot-bin scripts |
(empty) |
106 | 5.1% | Internal Directus calls |
python-httpx/0.28.1 |
24 | 1.2% | Modern Python lib |
cloud-run-proxy node |
14 | 0.7% | Cloud Run dev tunnel |
Mozilla/5.0 ... Chrome/143 |
9 | 0.4% | Browser UI thật |
cloud-run-proxy curl |
2 | 0.1% | Tunnel |
→ 99.6% DDL events là script/bot, chỉ 0.4% (9 events) qua browser UI thật.
4. Phân theo IP / origin
- 2 nhóm nguồn:
127.0.0.1(localhost — Directus container internal hoặc DOT chạy trên cùng host): 647 events- External IPs (14.177.x, 14.162.x, 1.55.x, 59.153.x, 118.68.x, IPv6 2001:ee0:4141:* và 2405:4802:1baa:*): 1437 events — đều residential VN (ISP của owner).
originfield: 2075/2084 (99.6%) là rỗng, chỉ 9 events có originhttps://directus-test-pfne2mqwja-as.a.run.app(Cloud Run thử nghiệm).
5. Spike days (timeline H2)
| Day | Events | Đặc điểm | Giả thuyết |
|---|---|---|---|
| 2025-12-07 | 16 | 127.0.0.1 | Bootstrap Directus + DOT đầu tiên |
| 2025-12-10 | 15 | — | — |
| 2025-12-16 | 186 | IPv6 operator | Đợt khai báo collection lớn |
| 2026-01-19→2026-02-01 | 195 cộng dồn | mix IP | Sprint S128-S135 |
| 2026-02-14 | 631 | 127.0.0.1, 05:32:58 → 05:48:20 (16 phút) | Bulk script run: 490 create + 33 update fields trong 16 phút → ~2 fields/giây = chạy automated, không phải người |
| 2026-03-06 | 698 | 14.177.x (chính), 59.153.x, 1.55.x | meta_catalog collection được tạo lúc 07:03:53 + 09:22:11→09:22:32 bulk update 23 collections / 21 giây. Sprint S140 (chuẩn bị migration) |
| 2026-03-10 | 41 | 14.162.x | TRÙNG mốc system_issues + registry_changelog (KS.2 sẽ verify) |
| 2026-03-12 | 3 | — | Cuối ngày trước migration |
6. Truy collection được tạo ngày 2026-03-06
Mẫu 15 events đầu (07:03 + 09:22):
07:03:53 create meta_catalog 14.177.128.73
09:22:11 update billing 14.177.128.73
09:22:13 update block_button_groups 14.177.128.73
09:22:15 update block_buttons 14.177.128.73
09:22:16 update block_library 14.177.128.73
09:22:17 update checkpoint_instances 14.177.128.73
09:22:19 update checkpoint_rules 14.177.128.73
... (23 collection updated trong 21 giây)
→ Pattern bulk script touching 23 collections in 21s — đây là đặc trưng của một DOT script đi qua Directus API (loop qua tất cả collections + PATCH meta).
WHY (cơ hội học gốc rễ)
WHY-1 — Tại sao 100% DDL thời MySQL chỉ qua 1 user admin@example.com?
- Gốc rễ: Đến mốc DOT 100% (2026-03-30, K0), hệ thống chưa có tách quyền
agentuser vsadminuser. Mọi script đều dùngDIRECTUS_TOKENcủa admin. Cái này đã được giải quyết ở S148 với policyai-agent(xuất hiện ở.claude/settings.local.jsonpermissions list mà ta thấy ở K0), nhưng 2084 events H2 đã commit trước khi tách quyền → không thể truy "DOT nào sửa schema nào" một cách phân biệt vì user log đều là admin. - Bài học: SSOT về "ai sửa cái gì" yêu cầu phân quyền theo agent role TRƯỚC khi cho ghi, không phải sau. Hiện tại đã có ai-agent policy nhưng dữ liệu lịch sử bị "ngộ độc admin".
WHY-2 — Tại sao 99.6% DDL là bot mà chỉ 0.4% UI?
- Gốc rễ: Triết lý "DOT 100%" được áp dụng (ngay cả thời MySQL) — schema không tạo bằng UI mà bằng
dot-schema-*-ensurescripts. Đây là điểm tích cực: tự động hoá tốt. - Hệ luỵ: Khi DDL = script, người không thấy schema thay đổi qua diff UI. Cần mechanism khác (migration log, schema snapshot diff).
dot-schema-snapshot(DOT-075) tồn tại nhưng chưa rõ chạy có đều không.
WHY-3 — Tại sao directus_relations có 0 events trong khi directus_fields có 1823?
- Gốc rễ: Directus quản lý relation qua
directus_fieldsvới type=many-to-one/one-to-many, không tạo row riêng trongdirectus_relationsqua API admin schema endpoints.directus_relationsrow được tạo ngầm khi Directus build relation từ field meta. - Bài học: Audit relation phải đi qua
directus_fields(special_field=m2o/o2m) chứ không quadirectus_relations.
WHY-4 — Tại sao có cửa sổ audit cho DDL Directus nhưng KHÔNG cho DDL trực tiếp PG?
- Gốc rễ — đây là lỗ hổng lớn nhất KS.1:
directus_activitychỉ log những thao tác đi qua Directus API. Mọi DDL chạy trực tiếp quapsql(CI auto-applysql/*.sqlở K2.7,dot-pg-*-ensureở K1.2.2,dot-schema-taxonomy-pg-apply) KHÔNG xuất hiện trongdirectus_activity. - Ví dụ: nếu
s133_measurement_framework.sqlđược apply trên VPS qua CI ngày 2026-02-14, sẽ không có rowdirectus_activitychoCREATE TABLE measurement_log— invisible audit gap. - Hệ luỵ: Cửa sổ 128 ngày
directus_activitychỉ cover một phần DDL. Phần còn lại — DDL qua psql trực tiếp — mù tịt, không có log nào (PG WAL archive OFF — K0). - Bài học gốc: Để SSOT về DDL, phải có DDL event log ở tầng PG (event trigger
pg_event_trigger_ddl_commands()) — không thể tin tầng Directus. Hoặc REVOKE quyền DDL khỏi userdirectusđể buộc mọi DDL đi qua Directus admin API.
WHY-5 — Tại sao spike 2026-03-06 (698 events) tạo meta_catalog?
- Gốc rễ: Đây là sprint chuẩn bị migration S115.
meta_cataloglà collection trung tâm cho registry counting (Điều 26). Operator (IP 14.177.128.73 = home VN) chạy DOT bulk tạo + cập nhật registry trước khi cắt đường MySQL. - Không có gì sai ở event này — nhưng nó là smoking gun cho thấy mọi sprint preparation đều là 1-shot bulk, chứ không phải continuous deployment. Rủi ro: nếu script fail giữa chừng, không rollback được — dữ liệu lệch.
TD đề xuất
TD-S176-DDL-MYSQL-ERA-AUDIT (mới)
- Triệu chứng:
directus_activitychỉ log DDL qua Directus API. DDL quapsql(CI auto-apply, dot-schema--ensure, dot-pg-) không có log. Không thể phân biệt "DOT nào tạo bảng nào" thời H2. - Gốc: User PG
directuscó CREATE/ALTER/DROP. Không có PG event trigger choddl_command_end. WAL archive OFF (K0). - Fix đề xuất:
- Tạo PG event trigger ghi DDL log vào bảng
ddl_audit_log(hoặc reusesystem_issuesvới issue_class='ddl_audit'), - Hoặc REVOKE CREATE/ALTER/DROP khỏi
directususer, giao cho 1 service role chuyên DDL (định hướng Phương án C: gateway).
- Tạo PG event trigger ghi DDL log vào bảng
- WHY-cần-fix: Không có audit DDL = không thể truy gốc khi schema drift. K2-K3 phải đoán bằng grep code.
TD-S176-ADMIN-MONOPOLY-LEGACY (mới)
- Triệu chứng: 100% DDL events H2 (2084 lần) ghi user =
admin@example.com. Không phân biệt được agent/script/người. - Gốc: Trước S148, hệ thống chỉ có 1 admin token. Mọi DOT chia sẻ chung. Hiện đã có
ai-agentpolicy. - Fix đề xuất:
- Audit hiện tại còn DOT nào dùng admin token không (K3 verify),
- Backfill/note: trong báo cáo registry/audit, nhãn rõ "events <2026-03-30 không có user phân biệt" để không mislead phân tích.
- WHY-cần-fix: NT12 yêu cầu DOT cặp verify, nhưng nếu user log của writer vs verifier đều là admin thì verify mất ý nghĩa (cùng credential).
Câu hỏi mở (chuyển K3-K4)
- PG có event trigger nào hiện đang chạy không? K3 query
pg_event_trigger. dot-schema-snapshot(DOT-075) có chạy đều không? K1 thấylast_executed=bulk-set, không tin được. Có snapshot đối chiếu với spike 2026-03-06 không?14.177.128.73và các IP khác có map với người/máy nào? 1 owner hay nhiều agent? (mostly cosmetic — không cần fix, nhưng giúp audit forensic)s133_*.sql,s134_*.sql,s146_*.sqlchạy thực sự ngày nào trên VPS? (K4 sẽ check marker file)ai-agentpolicy được kích hoạt từ sprint nào — trước hay sau spike 2026-02-14? (cần cross-check vớidirectus_policieshistory)
Forward / backward link
- Backward: K0 (cửa sổ 128 ngày activity), K1 (DOT DDL writers), K2 (CI auto-apply pattern).
- Forward: KS.2 (verify mốc 2026-03-10 — đã có hint từ KS.1: 41 events ngày này từ IP 14.162.130.216), K3 (PG event trigger + permissions), K4 (markers
.sql-applied).
Trạng thái KS.1
- DONE. Đã duyệt → KS.2 dưới đây.
- 0 thao tác ghi. Toàn bộ là
SELECTPG.
KS.2 — Verify mốc 2026-03-10 (system_issues + registry_changelog)
Mục tiêu
K0 phát hiện cả system_issues lẫn registry_changelog đều có row đầu tiên ngày 2026-03-10 (3 ngày trước migration MySQL→PG). Cần trả lời: bị reset/truncate hay được tạo mới? Ai chạy? Có alert/approve không?
Phương pháp
- KS.2.1 — Trace 41 DDL events ngày 2026-03-10 từ IP
14.162.130.216(hint vàng KS.1) trêndirectus_activity. - KS.2.2 — Grep toàn repo:
CREATE/TRUNCATE/DROP TABLEtên 2 collection. - KS.2.3 —
git logcác script + PR liên quan ngày 2026-03-08 → 2026-03-12. - KS.2.4 — VPS file mtime:
/opt/incomex/deploys/web-test/.sql-applied/,/opt/incomex/dot/bin/dot-schema-*ensure. - KS.2.5 —
\dschema hiện tại +MIN/MAX/COUNTcho mỗi bảng (PG đọc trực tiếp qua SSH).
Kết quả
KS.2.1 — 41 DDL events (collections+fields) từ IP 14.162.130.216
Ngoài 41 DDL events, IP này có 1675 events tổng ngày 2026-03-10 (login+create+update+run nhiều collection). Lọc ra DDL events thì có 2 cụm thời gian:
Cụm A — registry_changelog (03:17:35 → 03:18:12 UTC = 10:17 → 10:18 +0700):
03:17:35 create directus_collections registry_changelog ← LẦN ĐẦU TẠO
03:17:35 create directus_fields 1226 (field cho registry_changelog)
03:17:38..03:18:12 create directus_fields 1227..1239 (13 fields)
03:25:35..03:25:40 update directus_fields 1227, 1232, 1233, 1229 (chỉnh sửa)
→ 14 fields tạo trong 37 giây, sau đó 4 fields được update 7 phút sau (chỉnh tweak schema).
Cụm B — system_issues (05:06:30 → 05:07:07 UTC = 12:06 → 12:07 +0700):
05:06:30 create directus_collections system_issues ← LẦN ĐẦU TẠO
05:06:30 create directus_fields 1240 (field đầu tiên)
05:06:35..05:07:07 create directus_fields 1241..1253 (13 fields)
05:08:21..05:08:27 update directus_fields 1241, 1242, 1246, 1247, 1250 (5 chỉnh sửa)
→ 14 fields tạo trong 37 giây, sau đó 5 fields được update ~1 phút sau.
Toolchain: user_agent=curl/8.7.1 cho mọi lệnh DDL → script bash gọi Directus REST API.
Sau cụm B (09:08:04 UTC): thêm 1 field cuối (id 1254) — bổ sung muộn.
→ Tổng 30+ DDL events thuộc về TẠO MỚI 2 collection, KHÔNG phải reset.
KS.2.2 — Grep CREATE/TRUNCATE/DROP toàn repo
| Pattern | Match |
|---|---|
CREATE TABLE.*system_issues |
0 trong sql/, scripts/, infra/ (vì collection được tạo qua Directus API, không qua raw SQL) |
CREATE TABLE.*registry_changelog |
0 (idem) |
TRUNCATE.*(system_issues|registry_changelog) |
0 |
DROP TABLE.*(system_issues|registry_changelog) |
0 |
DELETE FROM.*system_issues |
1: sql/s162c_cleanup_archived_orphan_issues.sql (chỉ DELETE archived orphan rows, không phải truncate) |
→ KHÔNG có script TRUNCATE/DROP nào. Chỉ có 1 cleanup DELETE archived rows (cleanup hậu kỳ, không phải reset 2026-03-10).
KS.2.3 — git log quanh ngày 2026-03-10
Commit tạo dot-schema-registry-changelog-ensure (git log --diff-filter=A):
848bc9c 2026-03-10 10:30:58 +0700 Nguyễn Minh Huyên
feat: S108-WATCHDOG — registry changelog + watchdog flows + activity UI
f66fb29 2026-03-10 10:41:19 +0700 feat: S108-WATCHDOG ... (#471) ← merge
Commit tạo dot-schema-system-issues-ensure (git log --diff-filter=A):
33cf77f 2026-03-10 12:23:24 +0700 Nguyễn Minh Huyên
feat: S108-SELFHEAL — layer integrity audit + system issues + fix counting
edf7fe7 2026-03-10 12:30:06 +0700 feat: S108-SELFHEAL ... (#473) ← merge
→ Hai PR riêng biệt cùng ngày 2026-03-10, cùng author, cùng sprint S108, lệch nhau ~2 giờ.
Header script trong repo (xác nhận tự khai):
# dot-schema-registry-changelog-ensure
# v1.0.0 (2026-03-10): Initial version for Watchdog System
# - registry_changelog: tracks all entity changes for watchdog system
# - 13 fields: code, timestamp, entity_type, entity_code, entity_name, ...
# - Idempotent: checks existence before create
# - Based on dot-schema-entity-dependencies-ensure pattern
# dot-schema-system-issues-ensure
# v1.0.0 (2026-03-10): Initial version for System Issues tracking
# - system_issues: tracks integrity issues detected by audits
# - 13 fields: code, title, description, entity_type, entity_code, ...
# - Idempotent: checks existence before create
# - Based on dot-schema-registry-changelog-ensure pattern
→ Header CHANGELOG khẳng định "Initial version" 2026-03-10 cho cả hai. KHÔNG có version trước. KHÔNG có entry "RESET data".
KS.2.4 — VPS file mtime + markers
.sql-applied/ markers (30 file):
0047c_antigravity_proposal.sql.done Mar 24 11:11
s133_fix_rerun.sql.done Mar 24 11:11
s133_measurement_framework.sql.done Mar 24 11:11
s134_pg_grants.sql.done Mar 24 ...
... (30 markers, oldest mtime Mar 24 11:11)
→ Marker system chỉ xuất hiện từ 2026-03-24 (~14 ngày sau 2026-03-10). Trước đó CI auto-apply chưa có cơ chế marker → các SQL chạy thủ công hoặc chạy lại mỗi deploy.
Script dot-schema-*ensure trên VPS (/opt/incomex/dot/bin/):
2026-04-02 16:35:50 dot-schema-system-issues-ensure
2026-04-02 16:35:50 dot-schema-registry-changelog-ensure
→ Script được rsync lên VPS 2026-04-02 (lần sync gần nhất). Nhưng đã chạy trên cloud Directus từ máy operator ngày 2026-03-10 (qua curl). Tức là operator chạy script từ local trước, push code lên git/VPS sau.
6 file SQL CHƯA có marker (bonus K4):
s175_law_dedupe_p1.sql ← UPDATE knowledge_documents (K2 phát hiện)
s175_source_id_current_unique_p2.sql
0048_link_policies_mysql.sql ← K2 hypothesis MySQL syntax sẽ fail
kb_protection_phase2_cron_checks.sql
kb_protection_phase2_full_history.sql
kb_protection_phase2_verify.sql
vps_pg_schema_incomex_metadata_kb_20260405.sql ← pg_dump snapshot
→ 0048_link_policies_mysql.sql không có marker trong khi 30 file khác có → CHƯA TỪNG được CI apply thành công, hoặc chạy fail. Cần K4 verify chi tiết.
KS.2.5 — Schema + dữ liệu hiện tại
system_issues (24 cột, sample):
id integer not null DEFAULT nextval('system_issues_id_seq')
code, title, description, entity_type, entity_code, issue_type, severity,
source, detected_at timestamp DEFAULT CURRENT_TIMESTAMP,
status, resolved_at, resolved_by, resolution,
violation_hash, source_system, issue_class, business_logic_hash, run_id,
first_seen_at, last_seen_at, occurrence_count integer DEFAULT 1, ...
Counts hiện tại (2026-04-13):
| Bảng | First row | Latest | Count |
|---|---|---|---|
system_issues |
2026-03-10 05:37:30 UTC = 12:37 +0700 | 2026-04-12 01:30:03 | 1069 |
registry_changelog |
2026-03-10 03:28:07 UTC = 10:28 +0700 | 2026-04-13 10:01:33 | 20732 |
Đối chiếu với DDL events:
registry_changelog: collection tạo lúc 03:17:35 UTC → row đầu tiên 03:28:07 UTC → gap ~10 phút (operator tự test ghi 1 row sau khi schema xong).system_issues: collection tạo lúc 05:06:30 UTC → row đầu tiên 05:37:30 UTC → gap ~31 phút.
→ Dữ liệu liên tục từ ngày tạo cho đến hiện tại — không có khoảng đứt → không bị reset/truncate sau khi tạo.
KẾT LUẬN KS.2 — Giả thuyết K0 BỊ BÁC
K0 hypothesis: "2 bảng log bắt đầu 2026-03-10, trùng mốc đáng nghi với migration 2026-03-13 → có thể bị reset hoặc TRUNCATE".
KS.2 verdict: KHÔNG. Cả 2 collection được TẠO MỚI HOÀN TOÀN ngày 2026-03-10, là sản phẩm của 2 PR cùng sprint S108 (#471 WATCHDOG + #473 SELFHEAL):
- Trước 2026-03-10: 2 collection KHÔNG TỒN TẠI (không có CREATE TABLE history nào sớm hơn, không có row data nào sớm hơn, header script tự khai "Initial version 2026-03-10").
- Tại 2026-03-10 sáng: operator chạy 2 ensure script qua curl → Directus tạo collection + 14 fields mỗi bảng, mất tổng cộng ~2 phút active work.
- Operator commit + merge PR ~13 phút sau khi chạy script.
- Migration MySQL→PG ngày 2026-03-13 cuốn theo 2 bảng vừa mới sinh + dữ liệu kế thừa (vì pg_dump/restore mang theo content).
- Mốc trùng nhau là tình cờ — cùng sprint S108 chuẩn bị infrastructure cho watchdog + self-healing trước khi cắt MySQL.
WHY (cơ hội học)
WHY-1 — Vì sao K0 đoán "có thể bị reset"?
- Gốc:
MIN(detected_at)của một bảng = ngày tạo bảng = mốc tự nhiên. K0 thấy 2 bảng cùng MIN ngày → suy đoán "trùng mốc đáng nghi". Nhưng trùng mốc cũng có thể nghĩa là cùng sinh ra 1 ngày, không phải bị reset. - Bài học: Trước khi suy diễn "reset", phải kiểm tra "có version trước không" (git log diff-filter=A) + "có row sớm hơn không". KS.2 khắc phục bằng
git log --diff-filter=A— đây là tiêu chuẩn forensic gốc trước khi đoán.
WHY-2 — Vì sao 2 collection log quan trọng (system_issues, registry_changelog) KHÔNG có trước 2026-03-10?
- Gốc: Trước S108, hệ thống chưa có "watchdog" và "self-healing" như khái niệm chính thức. Triết lý NT5 "Tự phát hiện, tự sửa" được codify ở S108 + S148 (HP v4.4.0). Đây là hạ tầng tự phát hiện được ban hành quá muộn — sau khi đã có 100k+ row
directus_activitymà không có cơ chế phát hiện vi phạm. - Bài học: Hệ thống "tự phát hiện sai" cần được dựng từ ngày đầu chứ không phải retrofit. 2026-03-10 là ngày Incomex chính thức có self-healing layer — nhưng thiết kế quá muộn → mọi vi phạm trước đó không có log nào ghi lại. Window 4 tháng tối tăm = không thể truy cứu pattern lỗi cũ.
WHY-3 — Vì sao S108 chia làm 2 PR (#471, #473) cách nhau 2 giờ?
- Gốc: PR #471 chỉ là watchdog (ghi changelog). PR #473 thêm self-healing (issues + counter fix). Operator viết tuần tự, không gộp 1 PR lớn — best practice phát triển.
- Bài học: Không có gì sai. Pattern "schema-ensure script kèm PR" là chuẩn. Nhưng hệ luỵ là: mỗi sprint có nhiều PR rải rác → khó có 1 báo cáo "ngày X tạo những bảng nào" trừ khi grep git log như KS.2 đã làm.
WHY-4 — Vì sao operator chạy script TRƯỚC khi commit (10:17 vs 10:30)?
- Gốc: Workflow "test trên cloud Directus → nếu xanh → commit → merge". Cho phép thử nghiệm nhanh. Nhưng: tạo cửa sổ 13 phút mà production schema có collection chưa có trong git. Nếu operator quên commit, schema drift ngầm.
- Bài học: Hiện tại đã có DOT-067
dot-schema-diffvà DOT-075dot-schema-snapshot, lý thuyết phải catch drift này. Nhưng K1 phát hiệnlast_executed=bulk-setcho hầu hết DOT → không tin được DOT diff/snapshot có chạy đều. Cần K4 verify.
WHY-5 — Tại sao 2 collection được tạo qua Directus API (curl) thay vì raw SQL?
- Gốc: Đây là điểm tích cực. Ensure script đi qua Directus API → có vết trong
directus_activity→ KS.2 truy được. Nếu ensure script đi qua psql trực tiếp nhưdot-schema-taxonomy-pg-apply(K1), KS.2 sẽ rơi vào INVISIBLE AUDIT GAP của KS.1 và không thể truy được. - Bài học gốc: Tuyến đường L1 qua Directus API là tuyến forensic khả thi duy nhất hiện nay. Bất kỳ DOT nào đi thẳng PG = ngày sau truy không ra. Đây là argument mạnh cho Phương án C (Single Writer Gateway): ép tất cả qua 1 cổng duy nhất để có audit trail.
TD đề xuất
TD-S176-2026-03-10-RESET (closed — không có reset)
- Verdict: KHÔNG có reset. Hai bảng được tạo mới hoàn toàn ngày 2026-03-10 trong S108 sprint. K0 hypothesis bị bác.
- Không cần TD cho mốc 2026-03-10. Đóng giả thuyết.
- Reuse câu hỏi: K0 nên cập nhật, đổi nhãn từ "đáng nghi" thành "ngày sinh hợp lệ S108-WATCHDOG/SELFHEAL".
TD-S176-SELF-HEALING-LATE (mới — bài học gốc rễ)
- Triệu chứng: System self-healing layer (
system_issues,registry_changelog, watchdog flows) chỉ tồn tại từ 2026-03-10. Trước đó 4 thángdirectus_activityđã ghi 100k events nhưng KHÔNG có cơ chế phát hiện vi phạm. - Gốc: NT5 "Tự phát hiện, tự sửa" codify quá muộn (HP v4.4.0 ban hành 2026-03-30, hạ tầng dựng 2026-03-10).
- Fix đề xuất:
- Backfill
system_issuescho 4 tháng "tối" — chạy lại các checker (DOT-110 coverage-inspector, DOT-098 dependency-scan) trên dữ liệu lịch sử để ghi vi phạm ngược. - Hoặc chấp nhận: chỉ kiểm tra forward, không backfill — và ghi nhận "data ≤ 2026-03-10 không qua self-healing layer".
- Backfill
- WHY-cần-fix: Vi phạm hiện nay có thể là di sản từ 4 tháng tối. Nếu không backfill, không bao giờ biết.
TD-S176-RUN-BEFORE-COMMIT (mới — workflow risk)
- Triệu chứng: Operator chạy script trên production trước khi commit code (gap 10-13 phút). Tạo cửa sổ schema drift nếu quên commit.
- Gốc: Workflow "test on cloud first, commit if works" — fast iteration. Không có DOT enforce "code-first".
- Fix đề xuất:
- DOT pre-commit hook: chạy
dot-schema-snapshotngay sau khi script chạy → buộc snapshot record commit cùng với script. - Hoặc daily DOT diff:
dot-schema-diffchạy cron 1 lần/ngày, alert nếu PG schema lệch git.
- DOT pre-commit hook: chạy
- WHY-cần-fix: Window drift nhỏ nhưng tích luỹ. Cần K4 verify
dot-schema-snapshotvàdot-schema-diffcó chạy thực không.
Câu hỏi mở (chuyển KS.3-K3-K4)
- Backfill self-healing data cho cửa sổ 2025-12-07 → 2026-03-10 có khả thi không? Cần
dot-orphan-scanner,dot-coverage-inspectorchạy trên snapshot cũ — nhưng K0 đã nói WAL OFF vàdirectus_activitykhông lưu schema state, chỉ events. dot-schema-diffvàdot-schema-snapshot(DOT-067, DOT-075) thực sự chạy bao lâu một lần?last_executed=2026-03-31 bulk-setkhông tin được. → KS.6 sẽ verify.- 6 file SQL chưa có marker (
s175_*,0048_link_policies_mysql.sql,kb_protection_phase2_*,vps_pg_schema_*): tại sao chưa apply? K4 verify từng file. 0048_link_policies_mysql.sqlkhông có marker xác nhận K2 hypothesis "MySQL syntax sẽ fail trên PG" — nhưng cần verify file thực sự đã thử apply hay chưa được scheduling. K4 grep CI logs.- Cửa sổ 4 tháng "tối" trước self-healing: có vi phạm nào trượt qua không? Kiểm tra
directus_revisionscũ có data lạ không.
Forward / backward link KS.2
- Backward: KS.1 (cửa sổ DDL events H2 + IP
14.162.130.216hint). - Forward:
- KS.3 sẽ cross-check 15 script unregistered K1 với git log (giống pattern KS.2.3) để xác định di tích vs cố tình.
- KS.5 sẽ check
dot-collection-healthrace risk vs S175 fix — workflow run-before-commit có thể tăng race risk. - K4 sẽ verify markers chi tiết + 5 cron không có installer.
Trạng thái KS.2
- DONE. Đã duyệt → KS.3 dưới đây.
- 0 thao tác ghi. SELECT PG +
git log --diff-filter=A+ grep +statSSH +\dschema.
KS.3 — Đối chiếu 15 script unregistered K1 (forensic + classify + WHY)
Mục tiêu
K1 §6 liệt kê 15 script trong dot/bin/ không match dot_tools qua join file_path:
- 2 bypass thuần (không metadata):
dot-cron-matrix-setup,dot-script-lint - 3 cụm doc:
dot-doc-generate,dot-doc-partition,dot-doc-render - 10 cụm nrm:
dot-nrm-{amend, binding, config, discover, enact, impact, lifecycle, retire, sync, verify}
KS.3 phân loại mỗi script theo 3 nhãn (a) cố tình bypass / (b) metadata catch-up gap / (c) di tích chết, và trả 5 WHY.
Phương pháp
- KS.3.1 —
wc -l+head -1shebang trong repo;stat -c '%y'trên VPS. - KS.3.2 —
git log --all --diff-filter=A --followcho commit đầu tiên + PR + sprint. - KS.3.3 — Grep caller:
crontab/production.crontab,infra/cron/,.github/workflows/,dot/bin/(call lẫn nhau),scripts/. Xác nhận bằngcrontab -ltrên VPS. - KS.3.4 — Query
dot_toolstìm record uppercase semantic:DOT_NRM_*,DOT_DOC_*,DOT_SCRIPT_LINT,DOT_CRON_MATRIX*. - KS.3.5 — Classify + 5 WHY.
KS.3.1 — Inventory repo + VPS
| Script | Lines | Shebang | VPS mtime |
|---|---|---|---|
| dot-cron-matrix-setup | 41 | bash | 2026-04-04 04:51:19 |
| dot-script-lint | 53 | bash | 2026-04-04 04:52:47 |
| dot-doc-generate | 30 | bash | 2026-04-04 04:39:16 |
| dot-doc-partition | 29 | bash | 2026-04-04 04:39:16 |
| dot-doc-render | 21 | bash | 2026-04-04 04:39:16 |
| dot-nrm-amend | 31 | bash | 2026-04-04 04:39:16 |
| dot-nrm-binding | 25 | bash | 2026-04-04 04:39:16 |
| dot-nrm-config | 29 | bash | 2026-04-04 04:39:16 |
| dot-nrm-discover | 31 | bash | 2026-04-04 04:39:16 |
| dot-nrm-enact | 29 | bash | 2026-04-04 04:39:16 |
| dot-nrm-impact | 36 | bash | 2026-04-04 04:39:16 |
| dot-nrm-lifecycle | 36 | bash | 2026-04-04 04:39:16 |
| dot-nrm-retire | 26 | bash | 2026-04-04 04:39:16 |
| dot-nrm-sync | 31 | bash | 2026-04-04 04:39:16 |
| dot-nrm-verify | 46 | bash | 2026-04-04 04:39:16 |
Quan sát:
- Tất cả 15 file tồn tại cả repo lẫn VPS. Kích thước nhỏ (21-53 dòng — placeholder/lean pattern).
- 13 file (
dot-nrm-*+dot-doc-*) cùng timestamp VPS 2026-04-04 04:39:16 (chính xác tới giây) → rsync 1 batch. - 2 file (
dot-cron-matrix-setup,dot-script-lint) rsync 13 phút sau (04:51-04:52) — batch riêng.
KS.3.2 — Forensic git log (first commit)
| Script | First commit | Date | Author | Sprint / PR |
|---|---|---|---|---|
| dot-cron-matrix-setup | cff4d81 / 84a262a |
2026-04-01 09:37:29 +0700 | Nguyễn Minh Huyên | S150-P1: PG Matrix Foundation — pivot_matrix() + 4 DOT tools + cron (#659) |
| dot-script-lint | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | sync: copy 35 VPS-only files to macbook (Step 2A) |
| dot-doc-generate | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-doc-partition | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-doc-render | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-amend | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-binding | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-config | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-discover | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-enact | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-impact | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-lifecycle | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-retire | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-sync | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
| dot-nrm-verify | (sync commit) | 2026-04-05 12:29:44 +0700 | Nguyễn Minh Huyên | idem |
🔴 PHÁT HIỆN SMOKING GUN — commit message:
sync: copy 35 VPS-only files to macbook (Step 2A)
14/15 script KHÔNG có first commit "tạo mới" — chúng là "sync" retroactive copy ngược từ VPS về macbook/repo 2026-04-05. Nghĩa là:
- 14 script tồn tại trên VPS trước 2026-04-05 (ngày sync).
- Chúng được tạo trực tiếp trên VPS (qua ssh/scp/manual edit) hoặc đã sync từ một nơi khác.
- Không có dấu vết
git log --diff-filter=Acho bản gốc — commit đầu tiên KHÔNG phải "add" mà là "đồng bộ ngược". - Tên commit tự thừa nhận: "VPS-only files".
→ Đây là pattern §0-AY kinh điển: writer tồn tại trên production (chạy cron thực sự) mà git, dot_tools, code review, PR gate đều không biết cho đến khi có người "dọn dẹp" kéo về.
Với dot-cron-matrix-setup: ngược lại — có commit "tạo mới" hợp lệ S150-P1 (#659) ngày 2026-04-01. Nó được tạo đúng chuẩn (qua PR), nhưng operator quên đăng ký dot_tools row cho bản thân installer (đã đăng ký 4 DOT matrix khác: DOT-312..315 nhưng không có DOT cho chính installer).
KS.3.3 — Cross-check caller thực tế
Grep repo + crontab -l trên VPS:
| Script | Repo caller | VPS crontab -l |
Status |
|---|---|---|---|
| dot-cron-matrix-setup | NONE | NONE | One-shot installer (chạy thủ công lần duy nhất để install cron con) |
| dot-script-lint | crontab/production.crontab |
30 3 * * 0 dot-script-lint --local |
LIVE cron weekly Sun 03:30 |
| dot-doc-generate | NONE | NONE | Pre-flight checkpoint (inspect source) |
| dot-doc-partition | crontab/production.crontab |
0 3 1 1,4,7,10 * dot-doc-partition |
LIVE cron quarterly |
| dot-doc-render | NONE | NONE | Pre-flight checkpoint |
| dot-nrm-amend | NONE | NONE | On-demand (meta trigger_type=on-demand) |
| dot-nrm-binding | NONE | NONE | On-demand |
| dot-nrm-config | NONE | NONE | On-demand |
| dot-nrm-discover | crontab/production.crontab |
0 4 * * 0 dot-nrm-discover |
LIVE cron weekly Sun 04:00 |
| dot-nrm-enact | NONE | NONE | On-demand |
| dot-nrm-impact | NONE | NONE | On-demand (tier-A checker, paired=amend) |
| dot-nrm-lifecycle | crontab/production.crontab |
0 6 * * * dot-nrm-lifecycle |
LIVE cron daily 06:00 |
| dot-nrm-retire | NONE | NONE | On-demand |
| dot-nrm-sync | crontab/production.crontab |
30 5 * * * dot-nrm-sync |
LIVE cron daily 05:30 |
| dot-nrm-verify | crontab/production.crontab |
0 5 * * * dot-nrm-verify |
LIVE cron daily 05:00 |
Tổng kết caller:
- 6 script LIVE trên production cron (doc-partition, nrm-discover, nrm-lifecycle, nrm-sync, nrm-verify, script-lint)
- 8 script không có cron, không có caller (6 dot-nrm-* trigger on-demand + doc-generate + doc-render)
- 1 script one-shot installer (dot-cron-matrix-setup)
Quan trọng: 6 script LIVE đang ghi PG thực sự mỗi ngày/tuần/quý mà dot_tools.file_path = ∅. Cron owner KHÔNG join được với DOT entry qua file_path → §0-AY thất thủ.
KS.3.4 — Metadata dot_tools cross-check
Query dot_tools lấy record tương ứng uppercase semantic:
| Script | Code trong dot_tools | tier | domain | paired_dot | file_path | trigger | Khớp script? |
|---|---|---|---|---|---|---|---|
| dot-cron-matrix-setup | (NONE) | — | — | — | — | — | Không có metadata |
| dot-script-lint | DOT_SCRIPT_LINT |
A | monitoring.integrity | ∅ | ∅ | cron | ✓ có meta, thiếu fp + paired |
| dot-doc-generate | DOT_DOC_GENERATE |
B | normative | DOT_DOC_PARTITION | ∅ | on-demand | ✓ có meta, thiếu fp |
| dot-doc-partition | DOT_DOC_PARTITION |
A | normative | DOT_DOC_GENERATE | ∅ | cron | ✓ có meta, thiếu fp |
| dot-doc-render | DOT_DOC_RENDER |
B | normative | DOT_DOC_PARTITION | ∅ | on-demand | ✓ có meta, thiếu fp |
| dot-nrm-amend | DOT_NRM_AMEND |
B | normative.enact | DOT_NRM_IMPACT | ∅ | on-demand | ✓ có meta, thiếu fp |
| dot-nrm-binding | DOT_NRM_BINDING |
B | normative | DOT_NRM_VERIFY | ∅ | on-demand | ✓ |
| dot-nrm-config | DOT_NRM_CONFIG |
B | normative | DOT_NRM_VERIFY | ∅ | on-demand | ✓ |
| dot-nrm-discover | DOT_NRM_DISCOVER |
A | normative | DOT_NRM_DRAFT | ∅ | cron | ✓ |
| dot-nrm-enact | DOT_NRM_ENACT |
B | normative.enact | DOT_NRM_VERIFY | ∅ | on-demand | ✓ |
| dot-nrm-impact | DOT_NRM_IMPACT |
A | normative | DOT_NRM_AMEND | ∅ | on-demand | ✓ |
| dot-nrm-lifecycle | DOT_NRM_LIFECYCLE |
A | normative | DOT_NRM_RETIRE | ∅ | cron | ✓ |
| dot-nrm-retire | DOT_NRM_RETIRE |
B | normative | DOT_NRM_LIFECYCLE | ∅ | on-demand | ✓ |
| dot-nrm-sync | DOT_NRM_SYNC |
A | normative | DOT_NRM_ENACT | ∅ | cron | ✓ |
| dot-nrm-verify | DOT_NRM_VERIFY |
A | normative | DOT_NRM_DRAFT | ∅ | cron | ✓ |
Quan sát:
- 14/15 có metadata tương ứng (tier, domain, paired_dot, trigger_type đều khai đầy đủ).
- 15/15
file_path=∅. Không một record nào link script bằng file_path. - 1/15 hoàn toàn không có metadata —
dot-cron-matrix-setup. - Cụm nrm có pairing NT12 đầy đủ trong metadata (amend↔impact, discover↔draft, lifecycle↔retire, sync↔enact, verify↔draft, binding/config/enact↔verify).
DOT_NRM_DRAFTcófile_path=opt/incomex/dot/bin/dot-nrm-draft(là script KHÁC, không nằm trong 15 unregistered — vì K1 đã match nó qua basename). Điều này chứng tỏ: operator BIẾT cách set file_path, nhưng quên set cho 14 script nrm/doc còn lại.DOT_NRM_AMENDghi domainnormative.enactnhưngdot-nrm-amendlà action "amend" — label drift: metadata đặt theo plan, script là ad-hoc.
KS.3.5 — Classification + 5 WHY analysis
Classification tổng hợp
| Script | Nhãn | Lý do |
|---|---|---|
| dot-cron-matrix-setup | (c) di tích hợp lệ (one-shot installer đã chạy xong) | Commit hợp lệ S150-P1; không có metadata; nhưng nhiệm vụ đã hoàn thành — installer chỉ cần chạy 1 lần. Không chạy tiếp = đúng thiết kế. Cần đăng ký metadata dạng "one-shot installer" để trace. |
| dot-script-lint | (b) metadata catch-up + đang chạy | LIVE cron, có meta DOT_SCRIPT_LINT, thiếu fp + paired. Ironic: linter chống hardcode nhưng bản thân là direct PG writer. |
| dot-doc-generate | (b) metadata catch-up + placeholder | Pre-flight check, chưa có writer thật (chờ docxtemplater deploy). |
| dot-doc-partition | (b) metadata catch-up + đang chạy | LIVE cron quarterly, exit early nếu document_instances chưa tồn tại. |
| dot-doc-render | (b) metadata catch-up + placeholder | Pre-flight check (chờ Carbone CE deploy). |
| dot-nrm-{discover,lifecycle,sync,verify} | (b) metadata catch-up + đang chạy (4) | LIVE cron daily/weekly, ghi PG trực tiếp qua run_pg. |
| dot-nrm-{amend,binding,config,enact,impact,retire} | (b) metadata catch-up + dormant (6) | On-demand, chạy khi agent gọi. Không chạy cron. |
Tổng phân loại:
- (a) cố tình bypass: 0/15 (0%)
- (b) metadata catch-up gap: 14/15 (93.3%) — chia thành:
- b1 đang chạy cron: 6
- b2 placeholder chờ deps: 2
- b3 on-demand dormant: 6
- (c) di tích: 1/15 (6.7%) — chỉ
dot-cron-matrix-setuplà installer đã hoàn thành
Kết luận: Không có script nào là "cố tình bypass" theo nghĩa xấu. Vấn đề chủ đạo là metadata catch-up gap — operator đã đăng ký metadata với tier/domain/paired_dot/trigger_type chuẩn chỉnh, nhưng quên set file_path khi tạo script. Script chạy trên VPS rồi mới được kéo về repo ngày 2026-04-05 qua "Step 2A sync".
WHY-A — dot-cron-matrix-setup + dot-script-lint: cố tình bỏ đăng ký?
Bằng chứng cứng:
dot-cron-matrix-setup: commit S150-P1 #659 hợp lệ, cùng PR với 4 DOT khác (DOT-312..315 matrix-{declare,update,retire,health}). 4 DOT đồng cấp được đăng ký, 1 installer không.dot-script-lint: cóDOT_SCRIPT_LINTtier-A trongdot_toolsvớitrigger_type=cron, chỉ thiếu file_path.
Gốc rễ:
dot-cron-matrix-setupkhông cố tình bỏ — nó là installer phụ trợ cho nhóm pivot (giốngdot-cron-pivot-setupDOT-308). Operator đăng ký 4 DOT "matrix-verb" nhưng coi installer như build tool, không phải "DOT thật sự". Asymmetry trong định nghĩa "DOT là gì": writer action → yes; installer one-shot → ??dot-script-lintironic self-exemption: script chống hardcode lại dùng patternrun_pgtrực tiếp. Operator nhận thức được (code có commenthardcode_violationclass) nhưng đặt mình ngoài vòng tự check.
Self-aware mâu thuẫn dot-script-lint:
- Script grep 3 pattern hardcode:
PG_CONTAINER="[a-z](không env default)-U directus,-U incomex(direct user)docker exec postgres\b(hardcoded container)
- Nhưng chính
dot-script-lintdùngPG_CONTAINER="${PG_CONTAINER:-postgres}"(pass Pattern 1 qua:-) vàdocker exec -i ${PG_CONTAINER}(pass Pattern 3 qua${}). Script lách chính luật mình detect bằng cách dùng env var wrapper thay vì giá trị literal. - Nhưng semantically vẫn là direct PG writer — ghi
system_issuesquarun_pgpsql, không qua Directus API.
→ WHY-A kết luận: Không ai cố tình bypass. cron-matrix-setup là oversight (installer không được coi là DOT), script-lint là self-aware lỗ hổng (linter tự exempt). Cả hai đều là bài học về định nghĩa DOT chưa đủ chặt.
WHY-B — Cụm dot-nrm-*: tại sao tier-A metadata có file_path=∅ mà tier-B script vẫn chạy?
Bằng chứng cứng:
- 10
dot-nrm-*scripts: 100% metadata đầy đủ trừ file_path. DOT_NRM_DRAFT(không nằm trong 15 unregistered) cófile_path=opt/incomex/dot/bin/dot-nrm-draft→ operator BIẾT cách set fp.- Commit "sync: copy 35 VPS-only files to macbook (Step 2A)" 2026-04-05 → các script sinh trực tiếp trên VPS, không qua PR/CR từ ban đầu.
Gốc rễ — sequence nhiều khả năng:
- Sprint nào đó (có thể S155-S159, S148 KG) ban hành khái niệm normative registry.
- Agent (operator) tạo record metadata
DOT_NRM_*trongdot_toolstrước — qua curl POSTitems/dot_tools— để định nghĩa DOT cặp và paired_dot ma trận. - Script thực tế viết sau — trực tiếp trên VPS (vì VPS = SSOT mã nguồn theo OR v7.57 §VI: "Agent SSH → sửa VPS →
git commit→ cron push GH 2×/ngày"). - Khi viết script trên VPS, không có pipeline tự động update
dot_tools.file_path = '...'→ skipped forever. - Ngày 2026-04-05, operator "Step 2A" chạy sync pull 35 file về macbook để đưa vào git — nhưng vẫn chưa backfill
dot_tools.file_path.
Có phải decision hay bug?
- Không phải decision — metadata khai rõ ý muốn trace (paired_dot, trigger_type set đầy đủ). Nếu cố tình exempt, sẽ để
file_path=NULL+ thêm fieldexempted_reason='normative-external'. - Là bug quy trình: VPS-first workflow không có khâu
dot-register-self/dot-metadata-linksau khi tạo script mới trên VPS. - Đ33 §13 E3 (ngoại lệ normative) — chưa verify E3 có thật sự tồn tại không (không query KB được). Nhưng dù có E3, các script nrm/doc vẫn là writer thực sự → vẫn cần audit, không nên dùng E3 để bypass §0-AY.
WHY-B kết luận: Gap là workflow bug, không phải decision. Thiếu một bước "register file_path after VPS-create".
WHY-C — Cụm dot-doc-*: tương tự nrm?
Bằng chứng:
- 3 script:
dot-doc-{generate,partition,render}. - Metadata đầy đủ: tier A/B, paired trong cụm (
generate ↔ partition,render → partition). dot-doc-generatevàdot-doc-renderlà PRE-FLIGHT CHECKPOINT (script 30/21 dòng, không phải renderer thật):dot-doc-generatecheckrequire('docxtemplater')+ có template enacted?dot-doc-rendercheck Carbone CE presence?- Cả 2 ghi
system_issuesvớiissue_class='dependency_blocked'và exit 1 → là placeholder đợi dependency.
dot-doc-partition= quarterly cron, checkdocument_instancesexist, exit 0 nếu table rỗng → live cron but quick-exit.
Gốc rễ:
- Pattern giống nrm: metadata trước, script sau. Nhưng bản chất script còn yếu hơn — 2/3 là placeholder chờ dependency bên ngoài (docxtemplater, Carbone CE).
- Cụm doc được đăng ký trong sprint thiết kế KG/Normative (S155-S159) để chiếm chỗ trước khi có engine render.
- Vừa là metadata catch-up gap + vừa là placeholder chưa có writer thật.
WHY-C kết luận: Cùng pattern (b), cộng thêm vấn đề "DOT đăng ký trước implementation" — khai tier/paired nhưng chưa có logic. Đúng NT "Sẵn sàng thay đổi" theo nghĩa khai kế hoạch trước, nhưng vi phạm NT12 vì "checker tier-A" trỏ tới "writer tier-B" đều là placeholder → pair không verify được gì.
WHY-D — Pattern chung: tại sao §0-AY không catch?
Câu hỏi gốc: §0-AY = "Writer không đăng ký dot_tools = vô hình, CẤM". Vì sao 15 script sống sót qua mọi gate?
Bằng chứng:
- K1 phát hiện cả 15 bằng cách join trên
file_path, không phải trêncode. - Join bằng file_path chỉ work cho các script có file_path set — 212/272 record có fp. 60 record
file_path=∅không thể join. - DOT cảnh sát hiện có:
dot-orphan-scan(DOT-095, tier-A) — scan birth.orphandot-orphan-scanner(DOT-115, tier-A) — scan birth.orphandot-dependency-scan(DOT-098, tier-A) — scan relationsdot-coverage-inspector(DOT-110, tier-A) — scan monitoring.integritydot-dot-coverage(DOT-COVERAGE, tier-A) — scan monitoring.dotdot-dot-health(DOT-HEALTH-DOT, tier-A) — scan monitoring.dot
- Không thấy DOT nào tên
dot-register-orphanhoặcdot-metadata-file-link-check— tức là không có cảnh sát "script trên disk vs dot_tools.file_path".
Gốc rễ:
§0-AYđịnh nghĩa theo "writer không có trong dot_tools" nhưng registry có thể có entry rỗng fp → entry "giả" thoả mãn §0-AY literal.dot-dot-coveragevàdot-dot-healthcheck dot coverage nhưng không filter "entries có file_path NULL mà script thật chạy cron".- Cảnh sát thiếu chính xác: chỉ kiểm "có trong registry không" mà không kiểm "link đúng không".
- Không có CI guard: GitHub Actions không chạy grep
dot/bin/dot-*vsdot_tools.file_pathpre-merge.
WHY-D kết luận: §0-AY bị vô hiệu hoá bởi metadata shells (row tồn tại nhưng file_path rỗng). Cần một DOT tier-A mới: dot-file-path-orphan-check — cross ref script disk vs dot_tools.file_path, báo cáo file_path=NULL cho mọi metadata trỏ tới executable thật.
WHY-E — Bài học gốc: pattern "metadata trước, link sau, quên link" có lặp lại ở cụm khác?
Bằng chứng — quét lại K1 §2 metadata distribution:
- K1 phát hiện 60/272 record
file_path=∅(22%). - 14/60 đã giải thích ở KS.3 (nrm × 10 + doc × 3 + script-lint × 1 = 14). 46 record còn lại
file_path=∅chưa biết. - Trong phần tier-A K1 đã liệt kê:
DOT-312, DOT-313, DOT-314(tier A, paired=DOT-315) đềufp=∅→ cụm matrix pivot cùng pattern (metadata 4 DOT, chỉDOT-315có file_path — hoặc cả 4 cũng thiếu).
Let me verify:
DOT-315 = dot-matrix-healthvớifile_path=bin/dot/dot-matrix-health.ts(K1 có thấy).DOT-312, DOT-313, DOT-314= dot-matrix-declare/update/retire, theo KS.3.4 metadata query:file_path=∅.
→ Pattern metadata-trước-link-sau lặp lại ít nhất 4 cụm:
- Cụm NRM (10): dot-nrm-*
- Cụm DOC (3): dot-doc-*
- Cụm MATRIX (3): DOT-312..314 (dot-matrix-declare/update/retire — cần K4 verify)
- Cụm KG (~20+): K1 cho thấy
DOT_KG_*tier-A vớifile_path=None— chưa kiểm tra từng cái.
Dự đoán con số cuối (chưa verify): nếu 46 record còn lại đa phần là KG + một số matrix + một số MISC, thì pattern phổ biến trong ~60/272 = 22% dot_tools record — gần 1/4 registry bị "rỗng file_path".
Gốc rễ hệ thống:
- Workflow VPS-first (OR §VI) tối ưu cho speed: sửa thẳng VPS, push git sau. Nhưng không có hook sau
dot-auth/curl items/dot_toolsđể force set file_path. - Luật DOT mới (Đ35 v5.0 S155) yêu cầu DOT registry complete, nhưng triển khai không kèm migration tool để backfill legacy records.
- DOT 100% ban hành 2026-03-30 nhưng không có "registry sanity check" trước khi ban hành → registry đã dirty từ trước.
WHY-E kết luận: Pattern lặp lại ≥4 cụm, ảnh hưởng ~22% registry. Đây không phải bug riêng lẻ mà là hệ quả cấu trúc: registry không có trường bắt buộc file_path NOT NULL và không có CI gate kiểm exists(file_path). Cần fix kiến trúc chứ không phải vá từng script.
TD đề xuất (phần áp đảo = (b) metadata catch-up)
TD-S176-METADATA-CATCHUP (mới — áp đảo 14/15 case)
- Triệu chứng: 14/15 unregistered script thực ra CÓ metadata
DOT_*trongdot_toolsvới tier/domain/paired/trigger đầy đủ, nhưngfile_path=NULL. Cross-ref "script disk vs registry" bằng file_path fail. Join by file_path cho ra 15 false-positive "unregistered" khi thực tế là metadata-link gap. - Gốc:
- Workflow VPS-first (OR §VI): sửa thẳng VPS, push git sau → không có pipeline force set
file_pathtại thời điểm tạo script. - Không có CI gate kiểm
dot_tools.file_path IS NOT NULLcho mọi record có code DOT-xxx hoặc DOT_xxx liên kết với script thực. §0-AYđịnh nghĩa "không register" nhưng để registry có shell rỗng fp → check bị lách bằng "khai code trước, link sau".- Không có DOT cảnh sát đối chiếu
ls dot/bin/dot-*vsSELECT file_path FROM dot_tools WHERE file_path IS NOT NULL.
- Workflow VPS-first (OR §VI): sửa thẳng VPS, push git sau → không có pipeline force set
- Fix đề xuất:
- Hạ tầng chặn hơn dặn dò (Câu tuyên ngôn #2): thêm PG constraint
CHECK (file_path IS NOT NULL OR status='placeholder')trêndot_tools→ không thể khai "đăng ký" nếu không link file. - DOT mới
dot-file-path-orphan-check(tier A, cron daily): cross-ref disk vs registry, ghisystem_issuesmỗi filefile_path IS NULL AND code LIKE 'DOT_%'có script thật trên disk. - CI pre-merge hook: grep
dot/bin/dot-*mới + check tương ứng có row với file_path set trongdot_toolschưa. Fail PR nếu thiếu. - Backfill script (one-shot L2): tự động set
file_pathcho 46 record có code match basename script. Sau khi xong, retire script.
- Hạ tầng chặn hơn dặn dò (Câu tuyên ngôn #2): thêm PG constraint
- WHY-cần-fix: Registry không tin được = K3-K4 phải chạy nhiều DOT cảnh sát đặc biệt. Nếu fix gốc, mọi audit sau này chỉ cần join 1 lần.
TD-S176-VPS-FIRST-NO-HOOK (mới — gốc rễ workflow)
- Triệu chứng: 14/15 script có first commit là
sync: copy 35 VPS-only files to macbook (Step 2A)ngày 2026-04-05 → script chạy trên VPS nhiều ngày/tuần trước khi xuất hiện trong git. - Gốc: OR §VI quy định "Agent SSH → sửa VPS →
git commit→ cron push GH 2×/ngày". Cron chạy push nhưng không có khâu validate "mọi dot- có metadata đầy đủ"* trước khi push. - Fix đề xuất:
- Cron push (2×/ngày) thêm bước gọi
dot-file-path-orphan-checktrước khi git push → alert nếu có script orphan. - Hoặc: VPS pre-commit hook
.git/hooks/pre-committrong repo VPS-clone của web-test kiểm tra orphan.
- Cron push (2×/ngày) thêm bước gọi
- WHY-cần-fix: Dead window giữa "script live trên VPS" và "git biết" = không ai check được quality gate trong window đó.
TD-S176-LINTER-SELF-EXEMPT (mới — self-awareness gap)
- Triệu chứng:
dot-script-lint(DOT_SCRIPT_LINT tier-A monitoring.integrity) detect 3 pattern hardcode nhưng bản thân dùng direct PG write (run_pgpsql docker exec). - Gốc: Linter detect theo REGEX string pattern, không detect semantic (direct-PG-write vs via-Directus).
${VAR:-default}bypass pattern 1 literal match. - Fix đề xuất:
- Mở rộng pattern detection: grep
run_pg\|psql.*-U.*-d\|docker exec.*postgres psqlkể cả khi dùng env var wrapping. - Hoặc: chuyển
dot-script-lintsang ghi system_issues qua Directus API (không qua psql).
- Mở rộng pattern detection: grep
- WHY-cần-fix: Linter tự exempt chính là mâu thuẫn nội tại — tự không tuân luật mình áp đặt = mất uy tín rule enforcement.
TD-S176-INSTALLER-NOT-DOT (mới — định nghĩa DOT chưa đủ chặt)
- Triệu chứng:
dot-cron-matrix-setuplà installer one-shot, có commit S150-P1 #659 hợp lệ, nhưng không có row trongdot_tools.dot-cron-pivot-setupcùng loại CÓ đăng ký (DOT-308). Asymmetry. - Gốc: Định nghĩa "DOT là gì" mơ hồ: writer/checker thì rõ, nhưng installer/bootstrap/one-shot thì ??
- Fix đề xuất:
- Thêm
kindvàodot_tools(writer, checker, installer, bootstrap, one-shot) → installer one-shot được coi là DOT nhưng với vòng đời đặc biệt. - Hoặc: bắt buộc mọi script trong
dot/bin/đều phải có row, ngay cả one-shot —coverage_status=one_shot_completed.
- Thêm
- WHY-cần-fix: Nếu không định nghĩa rõ, nhiều installer khác sẽ lặp lại pattern này.
Câu hỏi mở (chuyển KS.4, KS.5, KS.6, K3, K4)
- KS.4 — DOT MA: 60 record
file_path=∅+ những record có file_path nhưng script không tồn tại (chiều ngược) — dự đoán ≥5 record có file_path đã lỗi thời. - KS.5 — dot-collection-health: trong 15 này không có collection-health (có trong K1 SELECT-then-INSERT list). Pattern 14 unregistered dùng
run_pg/ON CONFLICTcơ bản. Cần verify: trong 9 S175 writer, có bao nhiêu writer dùng SELECT-then-INSERT thực sự? KS.5 sẽ giao điểm. - KS.6 — Cron installer mapping: KS.3.3 xác nhận 6 cron LIVE cho nrm/doc/script-lint. Ai cài 6 cron này? Không có DOT installer registered. Nếu operator cài thủ công qua
crontab -e, đây là đơn bypass cron management. - K3:
14.177.128.73,14.162.130.216,127.0.0.1cho DDL events —14.162.130.216(hint KS.1/KS.2) tạo 2 collection 2026-03-10; còn IP14.177.128.73là ai? Cần K3 verify quadirectus_usersmetadata. - K4 —
.sql-applied/markers: 6 SQL file chưa marker + cụm này cần check từng file trên disk còn tồn tại không. - E3 Đ33 ngoại lệ normative: có thật không? Nếu có, phải tra vào constitution/law để xác minh WHY-B gap có hợp pháp không (mặc dù gap vẫn là bug workflow).
Forward / backward link KS.3
- Backward: KS.2 (pattern
git log --diff-filter=Ađã thành công + hint IP forensic). - Forward:
- KS.4 dùng cùng pattern check chiều ngược:
SELECT file_path FROM dot_tools WHERE file_path IS NOT NULL→ SSH check file tồn tại không → ra bảng DOT MA. - KS.5 quan tâm cụm nrm/doc nếu có ai dùng SELECT-then-INSERT thay vì ON CONFLICT.
- KS.6 verify 6 cron nrm/doc/script-lint ai cài — installer DOT missing hay manual?
- K3 check PG user dùng cho
run_pg()trong 14 script (tất cả dùngPG_USER:-directus). - K4 check placeholder
dot-doc-generate,dot-doc-rendercó phải dead code hay đang chờ dep thực sự.
- KS.4 dùng cùng pattern check chiều ngược:
Trạng thái KS.3
- KS.3 DONE — chờ duyệt KS.4.
- 0 thao tác ghi. Toàn bộ là
SELECTDirectus,git log,grep,crontab -l,statSSH.