Migration Reconciliation Log
Đối soát hai chiều d1_migrations (production, remote — nguồn sự thật) ↔ migrations/*.sql (đĩa), theo Audit Standard AS-04.6.1. Mục đích: biến "lệch không ai biết" thành "nợ đã khai báo" — PASS không đòi 0 lệch tuyệt đối (đó là AS-04.6.2), chỉ đòi mọi lệch đã được liệt kê và giải thích, ngày đối soát ≤ 7 ngày.
Đối soát 2026-08-20 (Audit #004 — AS-04.6.1, AS-04.6.2, AS-04.6.3)
Lệnh chạy (cùng bộ lệnh của lần trước, thêm --config vì repo nay có 9 wrangler.jsonc):
npx wrangler d1 execute nemo12-platform --remote --json --config workers/api/wrangler.jsonc \
--command "SELECT name FROM d1_migrations ORDER BY name"
ls migrations/*.sql | xargs -n1 basename | sortKết quả: d1_migrations remote 58 dòng, đĩa 57 file. Lệch = 1 dòng mồ côi, 0 file chưa áp.
Tên trong d1_migrations (production) | File hiện tại trên đĩa | Vì sao lệch | Có nguy hiểm không |
|---|---|---|---|
0046_context_event_temporal.sql | đã đổi tên thành 0052_context_event_temporal.sql (commit d4d582c, gỡ trùng số 0046 với 0046_mentor_profiles_albums.sql) | Cùng nguyên nhân với hai dòng mồ côi của lần đối soát trước: đổi tên file trên đĩa không đổi được dòng đã ghi trong d1_migrations. Nội dung đã áp hai lần dưới hai tên (một lần dưới 0046_… trước khi đổi, một lần dưới 0052_… sau khi đổi) và không hỏng gì, vì file này dựng lại bảng bằng CREATE TABLE IF NOT EXISTS + INSERT … SELECT từ bảng cũ | Không — disk - remote rỗng, không migration nào đang chờ áp, và schema-diff dưới đây khớp tuyệt đối |
Kết luận AS-04.6.1: 1/1 lệch đã biết, đã giải thích → PASS kể từ 2026-08-20.
AS-04.6.2 (0 dòng mồ côi) vẫn KHÔNG PASS. Câu dọn nợ dưới đây cố ý chưa chạy — nó là một lệnh ghi thẳng vào D1 production, mà luật hiện hành của dự án là mọi thay đổi production đi qua CI (AS-03.5.3). Để chủ dự án quyết định thời điểm:
npx wrangler d1 execute nemo12-platform --remote --config workers/api/wrangler.jsonc \
--command "DELETE FROM d1_migrations WHERE name = '0046_context_event_temporal.sql'"Schema-diff production ↔ migrate:dryrun — 2026-08-20 (AS-04.6.3)
Lần trước chạy 2026-08-15, tức đã cách 18 migration. Chạy lại:
npm run migrate:dryrun # dựng lại toàn bộ 57 migration từ D1 rỗng
# rồi dump sqlite_master cả hai phía và so theo TẬP CỘT của từng bảng| Production | migrate:dryrun | |
|---|---|---|
Số bảng (bỏ sqlite_*, _cf_*) | 92 | 92 |
| Bảng chỉ có ở một phía | 0 | 0 |
| Bảng lệch tập cột | 0 | 0 |
So sánh theo tập cột chứ không theo text DDL thô, vì cùng một schema dựng bằng hai đường khác nhau (CREATE TABLE gộp so với CREATE rồi ALTER ADD COLUMN) in ra DDL khác nhau — hạn chế đã ghi ở phần đối soát 2026-08-15 và vẫn đúng. AS-04.6.3 = PASS.
Đối soát 2026-08-15
Lệnh chạy:
npx wrangler d1 execute nemo12-platform --remote --json \
--command "SELECT name FROM d1_migrations ORDER BY name"
ls migrations/*.sql | xargs -n1 basename | sortKết quả: d1_migrations remote có 41 dòng, đĩa có 39 file. Lệch = 2 dòng mồ côi (remote có, đĩa không):
Tên trong d1_migrations (production) | File hiện tại trên đĩa | Vì sao lệch | Có nguy hiểm không |
|---|---|---|---|
0022_whale_country_detail.sql | đã đổi tên thành 0031_whale_country_detail.sql (đợt sửa trùng số 0022 — AS-04.1.1) | Đổi tên file trên đĩa không đổi được dòng đã ghi trong d1_migrations production; nội dung y hệt đã áp dưới tên mới (idempotent, CREATE TABLE IF NOT EXISTS) nên chạy lại vô hại | Không — wrangler d1 migrations list nemo12-platform --remote báo "No migrations to apply" |
0031_labs.sql | đã đổi tên thành 0037_labs.sql (đợt SRC-112 gộp cùng lúc với đợt đổi số ở trên) | Cùng nguyên nhân — đổi tên để gỡ trùng số, remote giữ tên cũ | Không — cùng lý do trên |
Kết luận đối soát 2026-08-15 (lần 1): 2/2 lệch đã biết, đã giải thích — AS-04.6.1 = PASS.
Dọn nợ AS-04.6.2 — đã thực hiện 2026-08-15
npx wrangler d1 execute nemo12-platform --remote --command \
"DELETE FROM d1_migrations WHERE name IN ('0022_whale_country_detail.sql', '0031_labs.sql')"
# → changes: 2, changed_db: trueXác minh lại ngay sau khi xoá (cùng lệnh đối soát ở trên): d1_migrations remote 39 dòng, đĩa 39 file, khớp tuyệt đối, comm -23 rỗng. wrangler d1 migrations list nemo12-platform --remote → "No migrations to apply". AS-04.6.2 = PASS kể từ đây.
Vì sao lỗi này tái diễn — bài học đã đưa vào Audit Standard v0.2
Cả hai lần đổi số migration đều xảy ra khi có nhiều phiên AI cùng chỉnh migrations/ song song mà không ai đối soát lại d1_migrations remote sau khi đổi tên file. Audit Standard v0.2 §1.4 đã đổi nguồn sự thật của toàn bộ AS-04.6 sang d1_migrations remote (không dùng git log --all, vì chính 0031_labs.sql từng được áp thẳng vào production mà chưa bao giờ commit — git log --all -- migrations/0031_labs.sql và git fsck --unreachable đều rỗng). AS-03.5.3 (Pilot Gate) cấm áp migration production ngoài CI để chặn đúng nguyên nhân gốc này.
Schema-diff thật — AS-04.6.3 (2026-08-15)
Audit #002 chấm AS-04.6.3 KHÔNG PASS (mã HH) bằng so sánh DDL text thô giữa production và migrate:dryrun cục bộ, phát hiện whale_scholarships "lệch". Đối soát lại bằng đúng cách kiểm mà chuẩn yêu cầu — chuẩn hoá trước khi so, không so text thô — cho kết quả khác:
# Production
wrangler d1 execute nemo12-platform --remote --json --command "PRAGMA table_info(whale_scholarships)"
# Local dryrun (chỉ chạy migration 0031, KHÔNG chạm production)
wrangler d1 execute nemo12-platform --local --persist-to /tmp/whale-schema-check \
--file=migrations/0031_whale_country_detail.sql
wrangler d1 execute nemo12-platform --local --persist-to /tmp/whale-schema-check --json \
--command "PRAGMA table_info(whale_scholarships)"Kết quả: 15/15 cột khớp tuyệt đối — tên, kiểu, notnull, default giống nhau từng cột, đúng thứ tự. Lý do DDL text thô khác nhau: production dựng bảng này qua CREATE TABLE gốc (chạy dưới tên cũ 0022_...) rồi ALTER TABLE ADD COLUMN 5 lần từ migration 0026 (nay no-op); migrate:dryrun cục bộ dựng lại từ đầu bằng 0031 — một CREATE TABLE gộp cả 15 cột trong một câu lệnh (đã ghi rõ trong comment đầu file 0031_whale_country_detail.sql, xác nhận đối chiếu ngày 2026-08-15). SQLite in lại DDL text khác nhau cho hai cách dựng schema cấu trúc giống hệt nhau — đây là hạn chế của so sánh text thô, không phải schema drift thật.
Kết luận: AS-04.6.3 = PASS khi so đúng cách (PRAGMA table_info, không phải diff văn bản sqlite_master.sql). Không cần sửa migration nào. Bài học cho lần chấm sau: cách kiểm "chuẩn hoá khoảng trắng" trong audit-standard/index.md chưa đủ — cần chuẩn hoá theo cấu trúc (PRAGMA table_info/index_list), không chỉ khoảng trắng, vì CREATE-gộp và CREATE+ALTER-lịch-sử luôn in ra text khác nhau dù schema giống hệt.
Cấm chạy migration production ngoài CI (AS-03.5.3)
Không ai được chạy wrangler d1 migrations apply --remote (hoặc d1 execute --remote với DDL) từ máy cá nhân hay phiên AI cục bộ. Đường duy nhất để migration chạm production là qua GitHub Actions: ci.yml tự áp khi push vào main đụng migrations/, hoặc bấm tay deploy-app.yml với app api (SRC-948 — trước 22.09.2026 là deploy-api.yml). Cả hai đều chạy bước "Apply D1 migrations (trước code — QG-004)" TRƯỚC khi deploy worker. Đây chính là nguyên nhân gốc của cả hai lần lệch ghi ở trên: 0031_labs.sql từng được áp thẳng vào production mà chưa bao giờ được commit (git log --all -- migrations/0031_labs.sql và git fsck --unreachable --no-reflog đều rỗng — xác nhận 2026-08-15). Sửa/xoá dữ liệu migration bookkeeping (như thao tác DELETE ở trên) là ngoại lệ hiếm, phải ghi lại ở file này, không lặp lại như một thói quen.
Lịch đối soát
Chạy lại đối soát này mỗi khi có đợt đổi số migration, và tối thiểu mỗi 7 ngày trong giai đoạn nhiều phiên AI cùng chỉnh migrations/ (AS-04.6.1 đòi ngày đối soát ≤ 7 ngày để PASS).