---
url: https://docs.nemo12.com/quality/audits/2026-08-20-002.md
description: >-
  Audit #005 chuyên đề hệ tài liệu ngày 20.08.2026: đào sâu bộ AS-01 (18/25),
  phát hiện, việc sinh ra và trace.
---

# Audit #005 — Hệ tài liệu

Audit **chuyên đề**, không phải audit toàn hệ. [Audit #004](2026-08-20.md) đã chấm cả 255 chỉ báo và cho AS-01 (Tài liệu & Đóng vết) **18/25**; bản này đào sâu đúng bộ đó bằng những phép đo mà chuẩn không có sẵn công cụ, để biến "18/25" thành danh sách việc làm được.

**Không đặt thang điểm mới.** Audit Standard đã cấm trục chấm thứ hai chồng lên PASS/KHÔNG PASS ([§1.2](../audit-standard/history.md#_1-2-g-s-m-khong-đuoc-dung-trong-v0-2)). Mỗi phát hiện dưới đây vì vậy được nối thẳng về chỉ báo AS tương ứng, hoặc ghi rõ là **ngoài phạm vi chỉ báo hiện có** — chính chỗ đó là đề xuất mở rộng chuẩn.

## 1. Hệ tài liệu đang là gì

| | |
| --- | --- |
| Số file `.md` trong `docs/` | **93** |
| Tổng dòng | **19.849** |
| Theo loại | reference 40 · sdd 32 · index 11 · quality 6 · prd 3 · convention 1 |
| Theo trạng thái | active 78 · **draft 15** |
| Dài nhất | `reference/data-dictionary.md` (2.167 dòng, sinh tự động) |

Cổng `check-docs.mjs` (QG-001) xanh: 93/93 file đủ frontmatter, `id` duy nhất, không link nội bộ gãy, trace `satisfies ↔ REQ` đóng.

## 2. Phát hiện

Xếp theo mức hại, không theo thứ tự đo.

### D1 · Sổ tiếp nhận chạy nhanh gấp đôi việc canonical hoá (AS-01.1.3)

**232/415 SRC (56%) chưa xuất hiện ở bất kỳ PRD, SDD, QG hay reference nào** ngoài chính `intake.md`.

```text
phân bố theo khoảng SRC:  0-49: 2 · 50-99: 25 · 100-149: 14 · 150-199: 35
                          200-249: 24 · 250-299: 45 · 300-349: 43 · 350-399: 34 · 400+: 10
```

Đây không phải nợ của riêng giai đoạn đầu: khoảng 250-399 (những chỉ đạo **gần đây nhất**) đóng góp 122/232. Nghĩa là tốc độ tiếp nhận đang vượt tốc độ đưa vào tài liệu chuẩn, và khoảng cách ngày càng rộng chứ không hẹp lại.

Hệ quả cụ thể, không phải lý thuyết: một quyết định chỉ nằm ở `intake.md` thì **không ai tìm ra nó khi đọc tài liệu thiết kế**. Ví dụ đang chạy: cột Parents của admin (SRC-404) và cổng phủ xác thực (SRC-412) đều có mã trên production mà không REQ nào mô tả.

### D2 · Tài liệu yêu cầu gốc vẫn là `draft` sau 438 dòng REQ (ngoài phạm vi chỉ báo)

`prd-001-nemo12-platform.md` mang `status: draft`, trong khi nó chứa **438 dòng REQ**, trong đó **258 đánh ✔ (đã làm)**. Một tài liệu mà hơn một nửa nội dung đã thi hành xong và đang chạy cho người dùng thật thì không còn là bản nháp.

Không chỉ báo AS nào bắt được điều này: AS-01.2.5 chỉ đòi `status` thuộc tập cho phép, không hỏi trạng thái ấy có còn đúng không. **Đề xuất mở rộng chuẩn.**

### D3 · `last_reviewed` là trường trang trí (ngoài phạm vi chỉ báo)

**25/93 doc có `last_reviewed` CŨ HƠN ngày sửa file thật.** Vài ca nặng:

| Doc | Khai `last_reviewed` | Sửa thật lần cuối |
| --- | --- | --- |
| `docs/index.md` | 2026-08-15 | 2026-08-20 |
| `docs/intake.md` | 2026-08-15 | 2026-08-20 |
| `traceability.md` | 2026-08-14 | 2026-08-19 |
| `prd-001-nemo12-platform.md` | 2026-08-15 | 2026-08-19 |
| `reference/engines.md` | 2026-08-14 | 2026-08-19 |

AS-01.5.1 vẫn PASS vì ngưỡng của nó là 90 ngày, quá rộng để bắt được chuyện này. Nhưng ý nghĩa của trường đã mất: người đọc thấy `last_reviewed: 2026-08-14` sẽ tưởng nội dung đã đứng yên từ hôm đó, trong khi nó vừa đổi hôm qua. **Đề xuất: cổng CI chặn commit sửa nội dung doc mà không cập nhật `last_reviewed`** — đo được, rẻ, và đóng luôn AS-01.5.2 (hiện là mã CM vì không ai spot-check nổi bằng tay).

### D4 · Một doc mồ côi, và nó đúng là doc của chỉ báo 🔴 (AS-01.3.4)

`docs/ops/backup-restore-drill.md` **không được file nào trong `docs/` trỏ tới**. Nó là tài liệu duy nhất phục vụ AS-04.5.5 🔴🚸 (diễn tập backup/restore), tức chỉ báo đang chặn Pilot Gate. Doc tồn tại nhưng không có đường đi tới thì khi cần dùng thật sẽ không ai tìm ra.

### D5 · Hai SDD `active` mô tả bảng chưa từng được xây (ngoài phạm vi chỉ báo)

Đối chiếu mọi cụm "bảng \`x\`" trong doc `status: active` với 101 bảng thật trong `migrations/`:

| Doc | Bảng được mô tả | Trạng thái thật |
| --- | --- | --- |
| `sdd-010-lab-platform.md:61` | `inquiries` | không tồn tại |
| `sdd-009-media.md:38` | `media_links` | không tồn tại |

Hai câu ấy viết ở thì khẳng định chứ không phải thì dự định, nên người đọc SDD sẽ đi tìm bảng không có. (Ba ca khác mà phép đo bắt được là **âm tính giả**, đã soi tay: `alignment_scores` được nêu trong một câu nói rõ *"không có bảng này và không nên có"*; `onboarding_learner_profiles` là bảng của hệ **chuyenchon cũ**, không phải của Nemo12; `d1_migrations` là bảng do wrangler tự tạo.)

### D6 · Ba đường dẫn mã nguồn trong docs đã chết (ngoài phạm vi chỉ báo)

| Doc | Đường dẫn | |
| --- | --- | --- |
| `sdd-001-platform.md:137` | `packages/shared/config` | không tồn tại |
| `open-questions/index.md:118` | `packages/ai` | không tồn tại |
| `intake.md:105` | `scripts/whale-data-p1..p3.json` | không tồn tại |

`check-docs.mjs` kiểm link `.md` nội bộ nhưng **không kiểm đường dẫn mã nguồn** nhắc trong văn bản. Đây là loại drift âm thầm: doc vẫn "xanh" trong khi chỉ sai.

### D7 · Con số của chính bộ chuẩn còn hai chỗ nói bản cũ (AS-01.4.x tinh thần)

`docs/index.md:34` và `docs/quality/quality-gates.md:41` vẫn ghi Audit Standard có **250 chỉ báo**, trong khi chuẩn hiện hành là **v0.2 với 255**. Hai trang này là cửa vào của cả hệ tài liệu, nên sai ở đây lan xa nhất.

### D8 · 15 doc `draft`, 12 trong số đó là SDD

`prd-001`, `open-questions`, `chuyenchon-app-requirements`, `legacy-feature-inventory`, và **SDD-012 → SDD-021**. Phần lớn SDD trong nhóm này mô tả hệ đã chạy thật (SDD-017 Retention, SDD-019 Mentor Albums, SDD-020 School Registry, SDD-021 Learning Entry Sequencing đều có mã trên production). Cùng loại vấn đề với D2: nhãn trạng thái không theo kịp thực tế.

## 3. Việc sinh ra từ audit này

| # | Việc | Đóng | Cỡ |
| --- | --- | --- | --- |
| **T1** | Sửa hai con số 250 → 255 ở `index.md` và `quality-gates.md` | D7 | rất nhỏ |
| **T2** | Nối `ops/backup-restore-drill.md` vào `docs/index.md` | D4, AS-01.3.4 | rất nhỏ |
| **T3** | Sửa ba đường dẫn chết; đổi hai câu SDD sang thì dự định hoặc xây bảng | D5, D6 | nhỏ |
| **T4** | Cổng CI: sửa nội dung doc thì bắt buộc cập nhật `last_reviewed` | D3, AS-01.5.2 | vừa |
| **T5** | Cổng CI: đường dẫn mã nguồn nhắc trong docs phải tồn tại | D6 | nhỏ |
| **T6** | Chuyển `status` của các SDD đang chạy thật từ `draft` sang `active`; quyết định riêng cho PRD-001 | D2, D8 | vừa |
| **T7** | Canonical hoá 232 SRC còn treo, bắt đầu từ SRC có mã đang chạy production | D1, AS-01.1.3, AS-02.4.2 | LỚN |

T7 là việc lớn và không làm một lần được. Đề xuất cách chia: mỗi phiên sửa mã cho một SRC thì canonical hoá luôn SRC đó, và mỗi tuần dọn một khối 20 SRC cũ theo thứ tự mới nhất trước (vì SRC mới còn nhớ được bối cảnh, SRC cũ thì phải khảo cổ).

## 4. Trace

| Nguồn | Chuẩn | Ghi vào |
| --- | --- | --- |
| SRC-416 (chỉ đạo: "audit toàn bộ hệ thống tài liệu, hệ thống admin này, tạo ra các audit report, sau đó fix dần") | [audit-standard/index.md](../audit-standard/index.md) v0.2 — bản này bổ trợ AS-01, không thay thang điểm | báo cáo này + [index](index.md) + việc T1..T7 |

## 5. Đã sửa — 2026-08-20, ngay sau khi chấm

| Việc | Kết quả kiểm được |
| --- | --- |
| **T1** | Hai chỗ nói Audit Standard có 250 chỉ báo (`index.md`, `quality-gates.md`) sửa thành **255**. Kèm dọn cây thư mục ở `index.md`, vốn còn ghi `SRC-001..111`, `Q-001..127`, `SDD-001..017` trong khi thực tế là **SRC-001..416 · Q-001..137 · SDD-001..021**, và thiếu hẳn thư mục `ops/`. Con số lấy bằng cách đếm từ chính các file, không gõ tay. → **D7 đóng** |
| **T2** | `ops/backup-restore-drill.md` và `ops/migration-reconciliation.md` nay có một dòng riêng trong bản đồ theo domain của `index.md`. Đếm lại: **0 doc mồ côi**. → **D4 đóng** |
| **T3** | Bốn câu khẳng định sai sửa thành khẳng định trung thực, mỗi câu nói rõ thứ đó **chưa dựng** và hiện đang nằm ở đâu: `packages/shared/config` (SDD-001), `packages/ai` ×2 (open-questions), bảng `media_links` (SDD-009), bảng `inquiries` (SDD-010). → **D5, D6 đóng** |
| **T5** | `scripts/check-docs-drift.mjs`, chạy trong `npm run check:docs` nên nằm trong CI. Bắt ba loại trôi mà QG-001 không thấy: đường dẫn mã nguồn đã chết · doc mô tả bảng D1 chưa tồn tại · doc mồ côi |

### Cổng chống trôi có thật sự bắt được không

Thêm một dòng drift giả vào `reference/engines.md` rồi chạy:

```text
✗ SRC-416 — 2 chỗ tài liệu đã trôi khỏi mã đang chạy:
  docs/reference/engines.md:296  đường dẫn không tồn tại: workers/api/src/khong-he-ton-tai.ts
  docs/reference/engines.md:296  mô tả bảng chưa tồn tại: bang_ma_khong_co
```

Gỡ dòng đó ra thì xanh lại. Cổng có răng, không phải xanh giả.

**Chính cổng này lỗi ngay lần đầu lên CI, và đó là bài học đáng giữ.** Nó xanh ở máy nhưng đỏ trên CI, vì nó đo bằng `existsSync` trên thư mục làm việc: `apps/data` là app **đã ngừng**, git không còn tệp nào, nhưng ở máy vẫn sót một thư mục rỗng chứa `.env` cũ. Một cổng cư xử khác nhau ở hai nơi thì **tệ hơn là không có cổng**, vì nó dạy người ta bỏ qua kết quả CI. Đã sửa: cổng nay đo theo **danh sách tệp git theo dõi**, thứ giống hệt nhau ở mọi máy. Kèm thêm nhóm từ khoá thoát hiểm cho các câu nói về thứ **đã ngừng dùng** (hai câu về `apps/data` là tài liệu đúng, không phải drift).

**Hai đường thoát hiểm cố ý**, vì không có chúng thì cổng sẽ ép tài liệu nói dối:

1. Câu văn tự nói ra rằng thứ đó **chưa dựng**, hoặc **cố ý không dựng**, thì không tính là trôi. Ví dụ SDD-020 viết *"dựng bảng `schools` mới là tạo ra hai nguồn sự thật"* — đó là một quyết định kiến trúc, không phải một lời sai.
2. **Sổ ghi chép theo thời gian được miễn trừ**: `intake.md` và toàn bộ `quality/audits/`. Chúng ghi điều đã đúng tại một thời điểm, và luật của chính hệ audit là không sửa báo cáo cũ. Bắt một cuốn sổ cập nhật theo mã hiện tại là phá đúng công dụng của nó.

| **T4** | `scripts/check-docs-fresh.mjs` thi hành đúng một luật **đã viết sẵn** ở `conventions.md` §4 (*"Khi sửa nội dung đáng kể: tăng `version`, cập nhật `last_reviewed`"*) — luật có từ đầu, chỉ chưa ai thi hành. Phép đo: `last_reviewed` không được cũ hơn ngày commit gần nhất chạm vào chính file đó. Đo bằng git chứ không bằng `mtime`, vì `mtime` đổi cả khi chỉ `git checkout`. → **D3 đóng, và AS-01.5.2 từ mã CM thành đo được bằng máy** |

**Nói thẳng về cách sửa 27 file lệch ngày:** đây là **một lần chỉnh đồng loạt cho khớp, KHÔNG phải một lần đọc lại nội dung 27 file**. Gọi nó là "đã review" thì đúng chữ mà sai nghĩa. Giá trị nằm ở chỗ khác: từ nay cổng buộc mỗi lần sửa phải tự bump trong cùng commit, nên khoản nợ này không tích tụ lại được nữa. Báo cáo audit được miễn trừ khỏi cổng: `last_reviewed` của chúng là **ngày chấm**, và bắt chúng bump mỗi lần có ai gõ một dấu phẩy là làm hỏng đúng thứ khiến một báo cáo có giá trị.

| **T6** | **9 doc chuyển `draft` → `active`**, mỗi doc kèm bằng chứng đo được chứ không kèm cảm nhận |

| Doc | Bằng chứng chuyển sang `active` |
| --- | --- |
| SDD-013 Coral | module `coral` + app Coral đã deploy |
| SDD-015 Student Portrait | module `portraits` + 6 portrait thật |
| SDD-016 Orca | module `orca` + 14 competition thật |
| SDD-017 Retention | module `retention` + 164 dòng `learner_retention` + cron 21:00 UTC |
| SDD-018 Community Events | module `events` + bảng có dòng |
| SDD-019 Mentor Albums | 4 `mentor_profiles` + `/v1/public/albums` đang phục vụ |
| SDD-020 School Registry | 3 `target_schools` + trang trường công khai đang chạy |
| SDD-021 Learning Entry Sequencing | 667 cạnh `unit_prereqs` + hé cửa đang chạy trong learn |
| `conventions.md` | chính nó là luật mà **bốn cổng CI** đang thi hành, mà lại mang nhãn "bản nháp" |

**Hai doc CỐ Ý giữ `draft`, và đó là câu trả lời đúng**: SDD-012 (Real Exam Bank) vì production có **0 dòng** `exams` nào `source_kind='real'`, và SDD-014 (Exam Acquisition) vì chưa có module crawler nào. Nhãn `draft` ở hai chỗ này đang nói đúng sự thật.

**Bốn doc để chủ dự án quyết**, vì đây là quyết định sản phẩm chứ không phải vệ sinh tài liệu: `prd-001` (438 REQ, 258 đã ✔ — nhưng đổi trạng thái tài liệu yêu cầu gốc là việc của người sở hữu nó), `open-questions/index.md`, `chuyenchon-app-requirements.md`, `legacy-feature-inventory.md`. Riêng `workflows/two-door-onboarding.md` giữ `draft` đúng: grep toàn repo không thấy mã nào thi hành luồng hai cửa.

### Hai lần cổng mới tự vấp, và vì sao đáng ghi lại

Cả hai cổng dựng trong đợt này đều **xanh ở máy, đỏ trên CI** ngay lần đầu. Cùng một nguyên nhân gốc: chúng đo một thứ **không giống nhau ở hai nơi**.

| Cổng | Đo sai chỗ nào | Sửa thành |
| --- | --- | --- |
| Chống trôi | `existsSync` trên thư mục làm việc. `apps/data` là app đã ngừng, git không còn tệp nào, nhưng ở máy còn sót thư mục rỗng chứa `.env` cũ | Đo theo `git ls-files` — danh sách giống hệt ở mọi máy |
| `last_reviewed` | `git log -1` trên bản **clone nông**. `actions/checkout@v4` mặc định `fetch-depth: 1`, nên mọi tệp trả về cùng một commit và **55 doc đỏ oan** | Nhận ra clone nông thì **bỏ qua và nói rõ**, thay vì đỏ oan; đồng thời đặt `fetch-depth: 0` cho `ci.yml` và `deploy-docs.yml` để cổng thật sự chạy ở đó |

Bài học chung, đáng đắt hơn cả hai cổng: **một cổng cư xử khác nhau giữa máy và CI thì tệ hơn là không có cổng**, vì nó dạy người ta bỏ qua kết quả CI. Và khi cổng không đủ dữ kiện để chấm thì **nói thẳng là không chấm được** vẫn tốt hơn là đỏ oan.

### T7 — đợt đầu, và một con số tự nó nói ra vấn đề

Canonical hoá **6 REQ** cho ba chỉ đạo mà mã đã chạy production nhưng tài liệu không mô tả:

| REQ mới | Biến chỉ đạo nào thành luật |
| --- | --- |
| `REQ-PLT-15` | Bảng learner của admin hiện phụ huynh, và `supporter` KHÔNG tính là phụ huynh (SRC-404) |
| `REQ-PLT-16` | Màn hình admin phân biệt ba trạng thái đang tải · rỗng · hỏng; danh sách bị cắt phải nói ra (SRC-416) |
| `REQ-PLT-17` | Hành động không hoàn tác được phải xác nhận; thao tác rủi ro thấp làm hàng loạt thì không hỏi (SRC-416) |
| `REQ-PLT-18` | Mọi endpoint vận hành có đường vào từ giao diện (SRC-416) |
| `REQ-SEC-07` | Phủ xác thực chứng minh được, đo bằng bảng route lúc chạy chứ không bằng grep (SRC-412) |
| `REQ-DOC-07` | Tài liệu không được trôi khỏi mã đang chạy (SRC-416) |

Cổng `check-docs.mjs` bắt ngay lỗi của chính đợt này: `REQ-DOC-07` lúc đầu để trống ô SDD, và luật "REQ active phải trỏ tới một doc thiết kế" chặn lại. Đúng thứ một cổng nên làm.

**Nhưng con số tổng thì đi ngược:**

| | Lúc chấm audit | Sau đợt sửa này |
| --- | --- | --- |
| Tổng SRC trong sổ | 415 | **440** |
| Chưa canonical hoá | 232 | **263** |

Đóng được 3, sổ dài thêm 25. **Khoảng cách rộng ra ngay trong chính phiên đang thu hẹp nó.** Đây là bằng chứng sống cho D1, và nó nói rằng đuổi theo bằng tay là cách sai: chừng nào việc canonical hoá còn là một việc RIÊNG làm sau, nó sẽ luôn thua tốc độ ra chỉ đạo.

**Đề xuất đổi cách, thay cho việc đuổi theo:** buộc ràng buộc vào lúc GHI SỔ chứ không phải lúc dọn dẹp — một dòng SRC mới phải khai luôn nó sẽ nằm ở REQ/SDD nào, hoặc khai rõ "chưa canonical hoá" kèm hạn. Khi đó cổng đo được, và nợ không sinh thêm nữa; phần 263 dòng cũ mới đáng đem ra dọn dần.
