Sổ sự cố vận hành · Mục 1 đến 8 (21.08 đến 25.08.2026)
Xem cách đọc sổ ở trang tổng.
1. SRC-431 — deploy-docs đỏ, chặn deploy tài liệu (2026-08-21)
- Triệu chứng. CI job
deploy-docsđỏ ở commit2c8e8e1; tài liệu không xuất bản được. Chạy cùng cổng ấy trên máy thì xanh. - Nguyên nhân gốc. Hai câu trong SDD-013 và trang open-questions còn nói
apps/datanhư mã đang có, trong khi thư mục đó đã gỡ khỏi repo. Cổng kiểm bằngexistsSync. Trên máy vẫn còn mộtapps/datarơi rớt (đã gỡ khỏi Git, chỉ sót.envvàdistkhông được theo dõi) nênexistsSynctrả về đúng; chỉ bản checkout sạch của CI mới thấy sự thật. - Cách sửa. Sửa câu văn cho khớp thực tế (
2c760f4), rồi kiểm lại bằng cách tạm chuyển thư mục rơi rớt đi và chạy lại cổng. - Cổng/luật. Kết quả cổng chỉ có giá trị khi vùng liên quan sạch trong Git. Trước khi tin một lần chạy xanh, chạy
git status --short <thư-mục>— còn dòng??nào là kết quả chưa nói lên gì. Cách kiểm chứng cho lỗi họ này: gỡ tạm thứ chỉ-có-trên-máy rồi chạy lại, đừng chạy lại y nguyên.
2. SRC-457 — nhiều phiên chạy song song đè nhau (2026-08-21)
Hai tai nạn riêng biệt, cùng một nguyên nhân họ: nhiều phiên Claude làm việc cùng lúc trên cùng một cây làm việc, và cả hai lần đều im lặng.
- Triệu chứng (a) — số SRC cấp trùng. Hai phiên cùng đọc con trỏ
> Nguồn tiếp theo: SRC-450rồi cùng lấy 450; SRC-448/450 bị lấy trùng. - Triệu chứng (b) — dòng sổ chung biến mất. Một phiên đọc cả
docs/intake.mdvào bộ nhớ, sửa, rồi ghi cả file ra — dòng mà phiên khác vừa chèn giữa chừng biến mất không dấu vết. - Nguyên nhân gốc. Cấp số dựa trên con trỏ đọc trước rồi +1 là thao tác không nguyên tử. Và ghi-cả-file là phép "last writer wins" trên một sổ chung chỉ được phép chèn.
- Cách sửa (
7537cbe,a17aaa3).scripts/src-new.mjscấp số nguyên tử: khoá bằngmkdir, số = max đang có + 1 (không tin con trỏ), đọc lại ngay trước khi ghi, rename nguyên tử.- QG-001 thêm hai bất biến: không trùng số và con trỏ phải đi trước max.
scripts/check-no-clobber.mjschặn commit làm mất một dòng SRC/REQ đang có trong ref gốc..githooks/pre-commitchạycheck-no-clobber+check-docscho mọi commit đụngdocs/, vàscripts/install-hooks.mjschạy qua vòng đờiprepaređểnpm installtự bật hook — quên bật tay là hàng rào biến mất mà không báo gì, dạng hỏng tệ nhất.- CI chặn lần hai (so với
HEAD~1), nên--no-verifyhoặc máy chưa bật hook cũng không lọt. scripts/worktree.mjs(npm run wt) cho mỗi phiên một cây riêng trên nhánhsession/<tên>.
- Cổng/luật (bản đầy đủ ở
CLAUDE.mdtại gốc repo, cố ý KHÔNG đặt link vì file đó nằm ngoàisrcDircủa VitePress nên link sẽ làm đỏ cả build — đây là bản rút gọn để tra):- Cấp số SRC chỉ bằng
node scripts/src-new.mjs "<nguồn>" "<canonical>"; không bao giờ đọc con trỏ rồi +1. - Sổ chung (
docs/intake.md,docs/traceability.md, bảng REQ trong PRD-001) chỉ được CHÈN. Dùng Edit trên đúng đoạn; cấm đọc cả file rồi ghi cả file. git add -Alà cấm — nó cuốn theo file đang dở của phiên khác. Chỉgit addđúng đường dẫn mình sửa.- Đọc lại file ngay trước khi sửa nếu giữa chừng có gọi tool khác. Hệ thống báo "changed on disk" thì bản trên đĩa là gốc, đừng khôi phục bản cũ của mình.
- Cố ý xoá dòng sổ chung phải nói ra:
NEMO12_ALLOW_LEDGER_DELETE=1hoặc[ledger-delete]trong message commit — escape hatch để lại vết đọc được. - Hook không cứu được file code. Sổ chung mất một dòng là dấu hiệu máy đọc được;
App.tsxbị đè thì không ai biết (đã xảy ra vớiProgressViews.tsxngày 2026-08-21, phải áp lại tay). Việc dài hoặc đụng nhiều file code → làm trong worktree riêng.
- Cấp số SRC chỉ bằng
- Kiểm chứng đã chạy. 8 tiến trình cấp số đồng thời ra 8 số liên tiếp không mất dòng; tạo dòng trùng → QG-001 FAIL đúng;
--no-verifyxoá dòng SRC-446 → CI-mode bắt được; thêm[ledger-delete]→ cho qua.
3. SRC-464 — "no such column" trên production dù deploy-api xanh (2026-08-21)
- Triệu chứng. Cột mới báo
no such columntrên production, trong khi workflowdeploy-apibáo xanh. Lộ ra tình cờ khi làm SRC-463. - Nguyên nhân gốc. Bước áp migration trong
deploy-api.ymlchỉ chạy khi dispatch tay (04929dd, cố ý — để token deploy thường không cần quyền D1). Push thường chỉ deploy code. Hệ quả:0064_referralsvà0065nằm PENDING nhiều giờ mà không ai biết, vì mọi chỉ dấu đều xanh. - Cách sửa. Dispatch
deploy-apiqua GitHub (đúng luật cấmwranglerthẳng lên production), áp cả hai migration; sửa con trỏ số trongmigrations/README.md(0038 → 0066, lệch từ lâu) và ghi thành luật (f802cf8). - Cổng/luật.
- Merge migration xong PHẢI dispatch
deploy-apibằng tay, rồi kiểmwrangler d1 migrations list --remotecho ra "No migrations to apply". Xanh của push thường không chứng minh migration đã áp. - Đây là mặt còn lại của QG-004: "migration tương thích deploy trước code". Chính vì code có thể lên trước migration nên migration phải idempotent và code phải chịu được cột chưa tồn tại — nếu không, khoảng trễ này là downtime. Quy trình an toàn ở skill
d1-migrate. - Con trỏ số migration là thứ lệch âm thầm; kiểm lại nó mỗi lần đụng thư mục migrations.
- Merge migration xong PHẢI dispatch
4. SRC-490 & SRC-491 — canonical hoá là gì, và tại sao mọi trao đổi phải vào docs
Không phải sự cố, nhưng nằm ở đây vì nó là cách phòng cho cả họ lỗi "kiến thức chỉ còn trong commit message hoặc trong lịch sử chat" — đúng thứ khiến file này phải được viết.
- Bối cảnh (SRC-490). Chủ dự án hỏi: "chuyển toàn bộ những gì đã trao đổi vào docs.nemo12.com. Việc này gọi là tạo các canonical à?" — đúng tên gọi, nhưng có hai bước khác nhau:
- Ghi intake:
docs/intake.mdgiữ nguyên văn yêu cầu và quyết định, kèm mãSRC-xxx. Đây là lịch sử, không phải thiết kế. - Canonical hoá: đưa nội dung ấy thành REQ trong PRD và thiết kế trong SDD, có trace hai chiều. Đích đến là: đọc PRD/SDD là đủ, không phải lần lại lịch sử chat.
- Ghi intake:
- Việc đã làm. Đợt SRC-490 canonical hoá SRC-476..489: thêm REQ-VIS-11..15 và REQ-LRN-41..44 vào PRD-001; viết SDD-008 §8–§12, SDD-010 §5.1, SDD-021 §7.1–§7.2 (
cd18f53). - SRC-491 là mặt thi hành của cùng nguyên tắc: "mô tả và gợi ý thêm về các việc còn sót, quyết luôn giúp tôi rồi làm luôn càng tốt". Hai việc treo từ 2026-08-17 được chốt và làm ngay — ô "Nhắc lại" trong màn làm bài (
55c0c27), và chốt không dựng cộthint_virỗng mà dùng chữ Pearl đã có (812/822 node), thiếu chữ thì ẩn nút. - Luật. Quy trình sáu bước ở conventions.md §6 là bắt buộc: ghi intake → xác định PRD/SDD → thêm/cập nhật REQ → cập nhật
sources/satisfies→ cập nhật traceability. Hai hệ quả thực hành rút từ SRC-490/491:- Một dòng intake chưa canonical hoá thì việc chưa xong. Commit message không phải nơi lưu thiết kế; nó không có trace, không grep được theo REQ, và không ai đọc lại.
- Chốt bằng cách không dựng cột rỗng. Khi dữ liệu đã có ở chỗ khác, dùng nó và ẩn UI khi thiếu — thêm một cột rỗng là thêm một nguồn sự thật thứ hai phải nuôi.
5. SRC-500 — docs.nemo12.com build ĐỎ sau khi đảo chiều nguồn dữ liệu (2026-08-22)
- Triệu chứng. Ngay sau khi cây năng lực chuyển vào
docs/đểdocs/thành nguồn, toàn bộ docs.nemo12.com không build được. - Nguyên nhân gốc. VitePress chặn cả bản build khi có link chết. Các trang curriculum vừa chuyển vào trỏ sang những chùm bài luận chỉ tồn tại trên site Pearl (
/ideas/…,/mental-models/…,/decisions/…,/hieu/…,/learning/…) — trên docs chúng là liên kết ngoài site. Hệ quả méo mó: một chùm bài luận thiếu chỗ ở kho khác làm sập toàn bộ docs, đúng lúc docs vừa thành nguồn. - Cách sửa (
8d36e93). KhaiignoreDeadLinkstrongapps/docs/.vitepress/config.mtstheo tiền tố (regex trên các nhánh Pearl), không tắt kiểm link. - Cổng/luật.
- Khi một cổng chặn quá rộng, thu hẹp phạm vi cổng, đừng tắt cổng. Link gãy trong tài liệu quản trị vẫn phải đỏ như cũ — đó là giá trị còn lại của cổng.
- Chuyển một cây nội dung vào
docs/là đổi tập link hợp lệ. Chạy build docs trước khi coi việc chuyển nguồn là xong.
6. SRC-504 — 8 test e2e gãy trên main sau thay đổi của phiên khác (2026-08-21)
- Triệu chứng. CI trên
mainđỏ: 8 test e2e gãy, sau khi phiên khác đổi bản đồ (SRC-499) và popover (SRC-503) mà không cập nhật test. - Nguyên nhân gốc. Hành vi đổi có chủ ý, test bị bỏ lại. Nó chỉ nổ ở
mainvì mỗi phiên chỉ chạy phần của mình. - Cách sửa (
0563d43). Sửa TEST cho khớp luật mới, không sửa giao diện của phiên kia:- Bản đồ nay mặc định chỉ bày Package → test nào chạm Module/Unit phải mở sâu trước; gói thành một hàm
moBanDoToiUnitkhớp cả nhãn VI lẫn EN, để lần sau đổi mức xem chỉ sửa một chỗ. - Nhãn ngôi sao Focus đổi theo mức xem.
- Popover xác nhận bỏ link "Ở lại đây" → cách đóng nay là bấm lớp phủ, và phải bấm vào góc lớp phủ chứ không phải giữa, vì giữa là chính popover.
- Test "Dashboard bày sẵn cả ba tầng" viết lại thành "mặc định chỉ Package, mở sâu mới tới Unit": luật cũ SRC-445 (một cách bày) vẫn đúng, cái đổi là bày TỚI ĐÂU.
- Bản đồ nay mặc định chỉ bày Package → test nào chạm Module/Unit phải mở sâu trước; gói thành một hàm
- Cổng/luật.
- Đổi hành vi UI thì cập nhật test trong cùng lần đổi. Test gãy vì hành vi đổi có chủ ý là nợ, không phải nhiễu.
- Sửa test cho khớp luật mới, không sửa giao diện của phiên khác cho khớp test cũ — trừ khi xác định được luật mới là sai.
- Gói thao tác điều hướng lặp lại thành một helper. Tám test gãy vì tám chỗ tự mở bản đồ.
- CI đỏ trên
mainlà việc của mọi phiên, không phải của riêng ai gây ra: cổng chặn deploy nên một phiên để đỏ là mọi phiên đứng (đã có lần năm SRC của ba phiên bị kẹt).
7. SRC-546 — cách làm một đợt "còn việc gì nữa": liệt kê → xếp ưu tiên → làm P1 (2026-08-24)
Ghi ở đây như quy trình mẫu, vì nó là cách phát hiện ra loại nợ không ai báo cáo.
- Yêu cầu. "còn việc gì cần làm nữa, liệt kê, đánh giá ưu tiên rồi làm dần" → đợt P1 gồm ba mảnh khép vòng chuẩn phủ (
dd8f3af,b9ac303). - Ba mảnh và vì sao chúng là P1.
- 10 dây
unit_prereqscho node mới của SRC-537 (dấu hiệu chia hết làm nền cho ƯCLN-BCNN và đồng dư; chuỗi hình khối bình hành → tam giác-thang → hộp → lăng trụ → chóp → trụ-nón-cầu; xác suất cần cả chắc-chắn-có-thể lẫn kiểm đếm). Không có dây thì trang Chỗ hổng và lối cứu giữa bài không trỏ về được unit mới — chúng tồn tại mà bộ kê đơn không biết đường. - 14 dòng chuẩn phủ đổi 🚧 → ✅ sau khi 106 câu qua cổng chất lượng. Bảng Toán 1–9 còn 81 ✅ · 0 🚧 · 1 ✍️ (số La Mã, cố ý bỏ).
- Dòng sót phát hiện khi rà lại — kí hiệu khoa học lớp 7 — chèn thêm, kèm lộ ba node luỹ thừa ra khỏi strand ẩn.
- 10 dây
- Cổng/luật.
- Nội dung mới chưa nối dây prereq là nội dung vô hình. Seed node xong mà không seed
unit_prereqsthì bộ kê đơn không bao giờ trỏ tới — không có lỗi nào nổ, chỉ là im lặng. - Bảng chuẩn phủ sót dòng thì CHÈN, không xoá và viết lại. Cùng luật chỉ-chèn với sổ chung (mục 2).
- Ô cố ý bỏ phải mang dấu riêng (✍️), khác ô chưa làm (🚧) — nếu không, mỗi đợt rà lại tốn một lần điều tra cùng một ô.
- Sau seed phải xuất lại dữ liệu tri thức để tài liệu khớp D1 (
b9ac303).
- Nội dung mới chưa nối dây prereq là nội dung vô hình. Seed node xong mà không seed
8. SRC-583 — gọi API ít thôi, hit database ít thôi (2026-08-25)
- Triệu chứng. Chủ dự án: "tối ưu đi, gọi API ít thôi, hit vào database ít thôi". Bảng chỉ số ghép ở client:
progress+cockpit+retention/summarycho TỪNG môn, tức 1 + 2N request HTTP, mỗi request lại vài truy vấn D1. Với 5 môn là 11 request và hơn 30 lượt chạm D1 chỉ để về 20 con số — mà số môn đang tăng (đã 11 môn). - Nguyên nhân gốc. Chi phí tuyến tính theo số môn, giấu trong tầng ghép ở client, nên nó lớn dần một cách âm thầm — không lần nào đủ chậm để ai báo.
- Cách sửa (
23b2451).GET /v1/learners/{id}/metricsdùng số truy vấn cố định (6), không phụ thuộc số môn: thống kê gộp theo môn · mục tiêu mỗi môn · blueprint mặc định cho môn chưa có mục tiêu · trọng số một câuIN· bảng mastery theo node · hàng retention mỗi môn. Thêm môn thứ 12 không làm bảng chậm thêm một nhịp. Tiện thể gỡ N+1 có sẵn trong/progress(đọc danh sách môn rồi chạy thêm một truy vấn cho từng môn) bằngGROUP BY+HAVING. - Cổng/luật hiệu năng đã áp.
- Số truy vấn phải cố định, không tăng theo số môn / số learner / số node. Đây là bất biến cần phát biểu ra khi thiết kế endpoint, không phải chỉ số đo sau.
- Không ghép dữ liệu ở client bằng nhiều request. Ghép ở server thành một endpoint; tầng ghép ở client là nơi N+1 trốn giỏi nhất vì không log nào cho thấy nó.
- Soát N+1 quanh mọi vòng lặp có truy vấn bên trong.
GROUP BY+HAVINGthường thay được nguyên vòng lặp. - Endpoint gộp phải dùng lại chính hàm tính của engine —
computeReadiness, cùng luật chọn blueprint vớibuildCockpit, nhãn retention từ chínhgroupLabel. Công thức chỉ sống ở một nơi; nếu không, hai màn sẽ nói hai con số khác nhau. - Placeholder đánh số
?1..?n, không dùng?tràn. Testkit của repo chỉ hiểu dạng đánh số:?tràn chạy được trên D1 thật mà trống trong test — tức là mất lưới an toàn, đúng họ "local nói dối" ở đầu file. - Test một endpoint gộp phải khoá đúng những chỗ dễ sai của việc gộp (7 test đã viết): coverage không đếm node thuộc Package tạm ẩn · mastery không pha loãng bởi bài chưa học · chưa đạt mục tiêu thì readiness là
nullchứ không phải 0 · bài chưa từng vùng không lọt vào mẫu số retention · 401 khi không có phiên. - Đổi API là đổi hợp đồng: sinh lại contract snapshot (
3020e68,109a532).