---
url: https://docs.nemo12.com/quality/audit-standard/as-04-06.md
description: >-
  Chỉ báo AS-04 dữ liệu và migration (6 tiêu chí), AS-05 trí tuệ học tập và
  thuật toán, AS-06 nội dung và tri thức.
---

# Audit Standard · AS-04 tới AS-06

Một phần của [Audit Standard](./index.md). Chỉ báo AS-04 dữ liệu và migration (6 tiêu chí), AS-05 trí tuệ học tập và thuật toán, AS-06 nội dung và tri thức.

## AS-04 — Dữ liệu & Migration (6 tiêu chí)

| ID | Chỉ báo | Cách kiểm |
| --- | --- | --- |
| **AS-04.1** | **Kỷ luật viết migration** | |
| AS-04.1.1 | 🔴🚸 Numbering: không trùng số (lỗ được phép từ SRC-1340) | `ls migrations/*.sql \| xargs -n1 basename \| cut -c1-4 \| sort \| uniq -d` phải rỗng — đã enforce trong `ci.yml` bước 7 |
| AS-04.1.2 | `CREATE TABLE`/`CREATE INDEX` có `IF NOT EXISTS` (ADD COLUMN miễn trừ) | `grep -L "IF NOT EXISTS" migrations/*.sql` đối chiếu file có `CREATE TABLE`/`CREATE INDEX` |
| AS-04.1.3 | 0 DML phá hoại không có `WHERE` lọc NULL/trạng thái | `grep -n "DELETE FROM\|UPDATE " migrations/*.sql` đọc từng dòng, xác nhận có `WHERE` |
| AS-04.1.4 | 🔴🚸 `npm run migrate:dryrun` — toàn bộ sequence áp thành công từ D1 rỗng | chạy lệnh, exit 0 — đã enforce trong `ci.yml` bước 8 |
| AS-04.1.5 | Mọi file migration đã viết đều đã áp lên D1 production (chiều file→applied; chiều ngược — applied→file — kiểm ở AS-04.6.1) | `wrangler d1 migrations list nemo12-platform --remote` — "No migrations to apply" nghĩa là mọi file đã áp |
| **AS-04.2** | **Thiết kế schema** | |
| AS-04.2.1 | Mọi bảng có `PRIMARY KEY` khai báo | schema dump: `wrangler d1 execute nemo12-platform --remote --command "SELECT name FROM sqlite_master WHERE type='table'"` đối chiếu từng `CREATE TABLE` có PK |
| AS-04.2.2 | 🔴 `users.id` là PK định danh; email chỉ UNIQUE, không bao giờ là đích FK | `grep -n "REFERENCES users(email)" migrations/*.sql` phải rỗng |
| AS-04.2.3 | Mọi cột `*_id` có `REFERENCES`, hoặc nằm trong danh sách miễn trừ tường minh ở `data-dictionary.md` | `grep -oE "[a-z_]+_id" migrations/*.sql` đối chiếu có `REFERENCES` cùng bảng/cột |
| AS-04.2.4 | Cột `*_at` kiểu TEXT (không INTEGER/DATETIME); bảng append-only có ≥1 cột thời điểm, 0 dòng lệch ISO-8601 UTC | `grep -n "_at INTEGER\|_at DATETIME" migrations/*.sql` phải rỗng |
| AS-04.2.5 | Không `json_extract`/`LIKE` trên cột `*_json` trong WHERE/ORDER BY của `workers/api/src` (scripts/ chạy tay miễn trừ) | `grep -rn "json_extract\|LIKE '%" workers/api/src --include="*.ts"` |
| **AS-04.3** | **Ngữ nghĩa & enum** | |
| AS-04.3.1 | Mọi bảng D1 có mô tả trong `reference/data-dictionary.md` | đối chiếu — script `gen-reference.mjs` |
| AS-04.3.2 | Mỗi cột có mô tả nghiệp vụ **không rỗng và không đúng bằng tên kiểu dữ liệu lặp lại** (vd cột `status` mà mô tả = "status"/"TEXT" là KHÔNG PASS) — thay thế bản v0.1 "ý nghĩa nghiệp vụ được giải thích" không có biên rõ, xem [§1.9](./history.md#_1-9-as-02-1-5-va-as-04-3-2-bien-pass-fail-khong-ro-—-viet-lai-khong-xoa-trang) | đọc `data-dictionary.md`, script so mô tả cột với tên kiểu (case-insensitive) |
| AS-04.3.3 | Không bảng nào trong data-dictionary mà không tồn tại thật trong migrations | đối chiếu danh sách bảng |
| AS-04.3.4 | Mọi enum/trạng thái liệt kê đủ giá trị hợp lệ, khớp `CHECK(...)` ở tầng DB | `grep -n "CHECK.*IN (" migrations/*.sql` đối chiếu `data-dictionary.md` |
| AS-04.3.5 | Mỗi thực thể có cột trạng thái có trạng thái được vẽ trong `reference/state-machines.md`, tập giá trị khớp `CHECK(...)` | đối chiếu |
| **AS-04.4** | **Toàn vẹn vận hành** | |
| AS-04.4.1 | Không script trong `scripts/` ghi D1 production thiếu cờ `--prod` tường minh; script chạm production in tên DB+số dòng, dừng chờ xác nhận | đọc script — grep `--remote` không kèm confirm prompt |
| AS-04.4.2 | Mỗi seed script chạy 2 lần liên tiếp trên D1 local cho cùng số dòng (idempotent); script không chạy được local = KHÔNG PASS | chạy 2 lần, `SELECT COUNT(*)` so sánh |
| AS-04.4.3 | `PRAGMA foreign_key_check` production rỗng — chỉ có nghĩa khi AS-04.2.3 PASS (phụ thuộc tường minh) | `wrangler d1 execute nemo12-platform --remote --command "PRAGMA foreign_key_check"` |
| AS-04.4.4 | Bảng khai có khóa nghiệp vụ trong `data-dictionary.md` có UNIQUE index THẬT trong `sqlite_master` | `SELECT * FROM sqlite_master WHERE type='index'` đối chiếu |
| AS-04.4.5 | Thực thể song ngữ bắt buộc (subjects/strands/skill_nodes) 0 dòng thiếu cặp `_en` | `SELECT COUNT(*) FROM skill_nodes WHERE title_en IS NULL` = 0 |
| **AS-04.5** | **Riêng tư & lưu trữ** | |
| AS-04.5.1 | Mỗi loại dữ liệu learner có chính sách retention khai báo (retain_days>0, rationale không rỗng) | đọc docs retention (khi tồn tại) |
| AS-04.5.2 | Có đường xóa dữ liệu theo yêu cầu (account/child), được đặc tả | docs + endpoint |
| AS-04.5.3 | Model version/snapshot có chính sách giữ lịch sử rõ ràng (giữ bao lâu, bao nhiêu bản — kiểm CHÍNH SÁCH, không kiểm khối lượng dữ liệu) | `state-machines.md` mô tả chính sách |
| AS-04.5.4 | Data minimization: mỗi trường PII có lý do tồn tại được ghi (danh sách đóng, một dòng/cột) | `data-dictionary.md` §PII |
| AS-04.5.5 | 🔴🚸 (nâng từ thường lên 🔴 trong v0.2 — pilot chạm dữ liệu trẻ em thật, xem [§1.3](./history.md#_1-3-🔴-lech-pha-voi-rui-ro-that-them-🚸-pilot-gate)) Có phương án backup/khôi phục D1 được ghi lại **và đã thử ít nhất một lần**: lệnh backup, nơi lưu, lệnh restore, số bảng/dòng sau restore, ngày diễn tập gần nhất | đọc [`docs/ops/backup-restore-drill.md`](../../ops/backup-restore-drill.md) — phải có đợt gần nhất KHÔNG cũ hơn đợt migration lớn gần nhất; một đợt cũ hơn schema hiện tại là bằng chứng cho một hệ không còn tồn tại |
| **AS-04.6** | **Đối soát production** *(tiêu chí mới — xem [§1.4](./history.md#_1-4-as-04-6-đoi-soat-production-—-validated-bang-bang-chung-that-khong-phai-gia-đinh))* | |
| AS-04.6.1 | 🔴🚸 Đối soát 2 chiều `d1_migrations`(remote) ↔ `migrations/*.sql`(đĩa): mọi lệch (mồ côi hoặc chưa áp) được **liệt kê và giải thích** trong `docs/ops/migration-reconciliation.md`, ngày đối soát ≤7 ngày. Điều kiện PASS không phải "0 lệch tuyệt đối" (đó là AS-04.6.2) mà là "mọi lệch đã biết, đã ghi" — biến rủi ro "không ai biết" thành nợ đã khai báo | `wrangler d1 execute nemo12-platform --remote --command "SELECT name FROM d1_migrations ORDER BY name"` so với `ls migrations/*.sql \| xargs -n1 basename \| sort`; đối chiếu file `migration-reconciliation.md`. **Trạng thái thật 2026-08-15**: 2 dòng mồ côi (`0022_whale_country_detail.sql`, `0031_labs.sql`) chưa được ghi ở đâu → KHÔNG PASS, mã **HH** |
| AS-04.6.2 | (không 🔴 — nợ dọn dần, tách khỏi 04.6.1 để không chặn pilot vì lý do sổ sách nhẹ) Số dòng mồ côi hiện tại = 0 | cùng lệnh trên AS-04.6.1, đếm dòng lệch = 0 |
| AS-04.6.3 | 🔴🚸 Schema-diff một lần: dump schema production thật so với schema sinh từ `migrate:dryrun` cục bộ, chuẩn hoá khoảng trắng, khớp cả hai chiều — không cần script CI mới, một lần chạy tay có ghi lại là đủ | `wrangler d1 execute nemo12-platform --remote --command "SELECT sql FROM sqlite_master WHERE type='table' ORDER BY name"` so với `wrangler d1 execute nemo12-platform --local --persist-to .wrangler/dryrun-d1 --command "SELECT sql FROM sqlite_master WHERE type='table' ORDER BY name"` sau khi chạy `npm run migrate:dryrun`; ghi kết quả vào `migration-reconciliation.md` |
| AS-04.6.4 | 🔴🚸 Bất biến tương thích ngược **kể từ 2026-08-15** (không hồi tố — xem [§1.5](./history.md#_1-5-toan-bo-lich-su-→-baseline-co-moc)): 0 pattern `DROP COLUMN`/`RENAME COLUMN`/`ADD COLUMN NOT NULL` không `DEFAULT` trong migration có `applied_at` sau mốc này | với migration mới sau mốc: `grep -n "DROP COLUMN\|RENAME COLUMN" migrations/00[4-9]*.sql`; `ADD COLUMN.*NOT NULL` không kèm `DEFAULT` trên cùng dòng |
| AS-04.6.5 | (cảnh báo, không chặn cứng) Nếu một filename từng xuất hiện trong `d1_migrations` **production** (không phải git history — xem [§1.4](./history.md#_1-4-as-04-6-đoi-soat-production-—-validated-bang-bang-chung-that-khong-phai-gia-đinh)) không còn tồn tại trên đĩa và chưa có dòng giải thích trong `migration-reconciliation.md`, CI nhắc bổ sung | so `d1_migrations` remote với `ls migrations/*.sql`; nguồn sự thật là bảng remote, KHÔNG dùng `git log --all` |

***

## AS-05 — Trí tuệ học tập & Thuật toán

| ID | Chỉ báo | Cách kiểm |
| --- | --- | --- |
| **AS-05.1** | **Định nghĩa model & versioning** | |
| AS-05.1.1 | Mỗi model (Learner, Goal, Context, Retention, Planning, Parent…) có trang mô tả: đầu vào, đầu ra, cách cập nhật | `reference/models.md` |
| AS-05.1.2 | Mỗi model có định danh version (vd `plan-v1`) **ghi vào bản ghi sinh ra** | `SELECT DISTINCT model_version FROM learner_model_versions` |
| AS-05.1.3 | Mỗi lần model đổi sinh một snapshot version lưu lại, truy được theo thời gian | `SELECT COUNT(*) FROM learner_model_versions` > 0, có `created_at` |
| AS-05.1.4 | Không model nào chạy production mà thiếu trang mô tả trong reference | đối chiếu code (`workers/api/src/modules/*/models` hoặc tương đương) với `models.md` |
| AS-05.1.5 | Thay đổi thuật toán làm đổi kết quả đều tăng version, không sửa tại chỗ | `git log -p` spot-check thay đổi thuật toán gần nhất |
| **AS-05.2** | **Đường evidence → model** | |
| AS-05.2.1 | Mọi cập nhật learner model bắt nguồn từ evidence có nguồn gốc ghi lại | đọc code + `SELECT * FROM learner_evidence LIMIT 5` |
| AS-05.2.2 | 🔴 Personal state / self-report **không ghi thẳng** vào Learner Model, chỉ qua evidence | đọc code — mọi route self-report đi qua `learner_evidence` trước |
| AS-05.2.3 | Evidence có tier / độ tin cậy phân loại rõ | schema `learner_evidence.tier` + code gán tier |
| AS-05.2.4 | Lần trả lời **đầu tiên** là evidence; các lần sửa lại không tạo evidence trùng | test + code: `learning.test.ts` kiểm hành vi sửa lại |
| AS-05.2.5 | Mỗi engine run ghi lịch sử (thời điểm, đầu vào, đầu ra) chứng minh đã chạy thật | `SELECT COUNT(*) FROM engine_runs` > 0 |
| **AS-05.3** | **Engine correctness** | |
| AS-05.3.1 | Mỗi engine có unit test hành vi (input → output kỳ vọng) | `find . -name "*.test.ts"` đối chiếu 1 file/module engine |
| AS-05.3.2 | Không có test kiểu source-regex (kiểm chuỗi trong mã nguồn thay vì hành vi) | đọc test — không `readFileSync` mã nguồn rồi `.includes()` |
| AS-05.3.3 | Mỗi engine có test cho biên / ca rỗng (learner chưa có evidence nào) | đọc test — case `evidence: []` |
| AS-05.3.4 | Stopping criteria của diagnosis/adaptive có test chứng minh nó dừng | đọc test |
| AS-05.3.5 | 🚸 Test chạy trong CI và **chặn merge** khi đỏ | `ci.yml` bước 3 `npm test` — không `\|\| true`, không `continue-on-error` |
| AS-05.3.6 | 🚸 Test e2e **không kiểm đồng bộ** một thứ do mạng điền vào bất đồng bộ — dùng `expect.poll` hoặc chờ một dấu hiệu trên màn | đọc test: `expect(<mảng do route handler điền>)` không có `await` là một cuộc đua (SRC-1040) |
| **AS-05.4** | **Retention & Mastery** | |
| AS-05.4.1 | 🔴 Mastery **không** tự suy giảm theo thời gian (SDD-017 §1) | test bất biến `retention/periodic.test.ts` |
| AS-05.4.2 | Retention có decay theo thời gian với công thức được ghi lại trong reference | `reference/retention-model.md` + test |
| AS-05.4.3 | Retention được cập nhật **định kỳ** (cron), không chỉ khi có evidence | `reference/schedules.md` + wrangler cron config |
| AS-05.4.4 | Job định kỳ chỉ ghi trường phái sinh, không đụng anchor — có test bất biến | `retention/periodic.test.ts` |
| AS-05.4.5 | Tham số review window / spaced repetition được ghi trong reference, không chôn trong code | đối chiếu `reference/retention-model.md` với hằng số trong code |
| **AS-05.5** | **Giải thích được** | |
| AS-05.5.1 | Mỗi recommendation kèm lý do (reason) người dùng đọc hiểu được | UI + API response |
| AS-05.5.2 | Mỗi recommendation ghi model version (+ prompt version nếu dùng AI) | `SELECT model_version, prompt_version FROM ...` |
| AS-05.5.3 | Learning Plan mới ghi được diff so với bản trước và **vì sao đổi** | UI `/ke-hoach` + `planning.ts` |
| AS-05.5.4 | Mọi output quan trọng có confidence hoặc mức chắc chắn | schema — trường `confidence` |
| AS-05.5.5 | Learner/parent có đường xem lại lịch sử phiên bản model liên quan tới mình | UI |

***

## AS-06 — Nội dung & Tri thức

| ID | Chỉ báo | Cách kiểm |
| --- | --- | --- |
| **AS-06.1** | **Knowledge graph toàn vẹn** | |
| AS-06.1.1 | Mọi node có subject / grade / level xác định | `SELECT COUNT(*) FROM skill_nodes WHERE subject IS NULL OR grade IS NULL` = 0 |
| AS-06.1.2 | Không node mồ côi (không thuộc module/unit nào) | `SELECT COUNT(*) FROM skill_nodes WHERE strand IS NULL OR unit IS NULL` = 0 |
| AS-06.1.3 | Quan hệ tiên quyết không có chu trình | DFS trong `scripts/seed-unit-prereqs.mjs` trên `unit_prereqs` (dừng hẳn khi thấy chu trình); `skill_edges` cho các Package cũ |
| AS-06.1.4 | Mỗi unit đủ số experience tối thiểu theo SDD-010 §9 (3 assessment + 6 learning) | `SELECT unit, COUNT(*) FROM items GROUP BY unit` đối chiếu ngưỡng |
| AS-06.1.5 | Tên node/module/unit đủ cặp vi/en | `SELECT COUNT(*) FROM skill_nodes WHERE title_en IS NULL` = 0 |
| **AS-06.2** | **Chất lượng item** | |
| AS-06.2.1 | Mọi item published có quality profile đủ 8 chiều CC-QAF | schema — cột chất lượng trên `items` (khi tồn tại) |
| AS-06.2.2 | Mỗi chiều chất lượng có evidence kèm theo, không chỉ điểm số trần | đọc bản ghi |
| AS-06.2.3 | Có multi-evaluator variance check trước khi publish | đọc pipeline |
| AS-06.2.4 | Mỗi item link tới ≥1 node và ≥1 dạng bài | `SELECT COUNT(*) FROM items WHERE node_id IS NULL` = 0 |
| AS-06.2.5 | Item bị learner report có đường vào hàng đợi rà soát | UI Coral + `content_reports` nối vào hàng đợi thật |
| **AS-06.3** | **Vòng đời nội dung** | |
| AS-06.3.1 | 🔴 Nội dung production **không sửa trực tiếp**; mọi sửa đi qua candidate version | đọc code/quy trình — cột lifecycle trên `items` |
| AS-06.3.2 | Mỗi candidate có trạng thái vòng đời rõ ràng | schema |
| AS-06.3.3 | Publish chỉ xảy ra khi vượt ngưỡng chất lượng đã khai báo bằng số | đọc code |
| AS-06.3.4 | Có lịch sử: ai/cái gì publish, khi nào | `SELECT published_by, published_at FROM items` |
| AS-06.3.5 | Rollback nội dung về version trước là thao tác được hỗ trợ, không phải sửa tay DB | UI/endpoint |
| **AS-06.4** | **Đề thi thật & provenance** | |
| AS-06.4.1 | Mỗi đề thật ghi đủ: trường/hệ thi, vòng, môn, năm học | `SELECT * FROM exams WHERE source='real'` đối chiếu cột |
| AS-06.4.2 | 🔴 Lưu **URL nguồn**, không lưu file đề | schema `source_ref` + R2 — xác nhận không có file đề trong R2 |
| AS-06.4.3 | 🔴 Lời giải tự biên soạn, không sao chép nguyên văn nguồn ngoài | spot-check ≥5 đề, so văn bản với nguồn |
| AS-06.4.4 | Crawl chỉ nguồn công khai, tôn trọng robots/ToS, ghi provenance đầy đủ | đọc SDD-014 + code crawler |
| AS-06.4.5 | Đề authentic bất biến sau publish (sửa = tạo version mới) | đọc code |
| **AS-06.5** | **Media & bản quyền** | |
| AS-06.5.1 | Mọi media public đã qua moderation trước khi hiển thị | đọc pipeline |
| AS-06.5.2 | Mỗi media ghi nguồn / giấy phép | schema |
| AS-06.5.3 | Không dùng hình ảnh/logo bên thứ ba không có quyền | rà thủ công — spot-check thư mục asset |
| AS-06.5.4 | Media đúng kích thước/format tối ưu theo SDD-009 | kiểm file — `file` + kích thước |
| AS-06.5.5 | Media chứa hình ảnh trẻ em có cơ chế đồng thuận trước khi public | đọc code |

***
