Kế hoạch tách repo (cách 3)
16.10.2026: chủ repo quyết một cuộc tách KHÁC kế hoạch này: www.conan.school sang repo conan-web cùng bộ CMS mới. Xem Hai repo: conanplatform và conan-web.
Trạng thái (14.10.2026): Đợt 0, PR-A đang làm: runner gác
scripts/run-guards.mjs, bộ chọn jobscripts/ci/select-jobs.mjs, sparse checkout, chỉnpm ci, gáclockfilesvàworkspace-deps. Xem CI và gác. A8 (gộp job vệ tinh) hoãn: chỉ có tác dụng ở run chạy đủ, mà A1 + A3 đã làm run chạy đủ hiếm đi; đo lại sau 14 ngày. A9 (ci-gate) chờ quyết định nâng GitHub Pro.Số liệu đo bằng 6 agent song song trên 1.000 run (07.09 tới 13.10.2026), kế hoạch qua hai vòng phản biện độc lập (chữa đúng chỗ đau không; cái gì sẽ vỡ).
Ba cách tách đã cân nhắc: (1) mỗi site một repo, (2) tách theo tầng (core / web / studio / docs), (3) giữ monorepo lõi, tách vệ tinh, CI theo phạm vi ảnh hưởng. Chọn cách 3, và số liệu cho thấy phần lớn giá trị của nó nằm ở việc làm TRONG monorepo, không ở việc tách.
Tóm tắt
- CI không chậm. ci-checks có p50 là 1,52 phút, từ PR tới production mất khoảng 3 phút. Thời gian job đi vào checkout 42%, còn tsc cộng build chỉ 14%.
- "Hay lỗi" không phải do test chập chờn. Cả 25 lượt ci-checks đỏ đều tất định: 18 lượt (72%) do gác chính sách, 0 lượt ở job vệ tinh. Có 9 lượt chập chờn thật, nhưng nằm ở deploy và bị lần chạy lại che đi.
- Nỗi đau lớn nhất nằm ở tài khoản GitHub. Repo dùng khoảng 2.200 phút tính tiền mỗi 30 ngày, trong khi gói Free chỉ có 2.000, nên sinh ra 15
startup_failure. Gói Free cũng không cho bật required check (API trả 403), nên đã có 2 PR merge khi đỏ và 2 PR merge khi CI không chạy. - Thứ tự làm:
- Đợt 0: cắt phút về ≤1.600 và dựng cổng merge.
- Đợt 1: dựng nền tích hợp.
- Đợt 2: pnpm workspaces cộng Turborepo trong lõi, làm sau một spike đo trước.
- Đợt 3: lưu trữ 3 thư mục, rồi đưa 4 vệ tinh sang 1 repo.
- Đợt 4: không hẹn ngày.
- Tách vệ tinh không giảm được lượt đỏ nào đo được, mà tốn thêm khoảng 14 tới 30 PR mỗi 60 ngày. Đó là việc làm gọn ranh giới, nên đặt sau cùng và chỉ làm khi đủ điều kiện.
Số liệu đo (07.09 tới 13.10.2026)
| Mảng | Chỉ số | Giá trị |
|---|---|---|
| Tốc độ | ci-checks p50 / p90; thời gian xếp hàng | 1,52 / 1,90 phút; 3 giây |
| Tốc độ | Mở PR tới merge p50; merge tới deploy xong p50 / p90 | 2,0 phút; 1,2 / 2,3 phút |
| Cấu thành job | checkout / npm ci / setup-node / tsc+build | 42% / 16% / 6% / 14% |
| Checkout | Job app checkout đủ, so với job deploy dùng sparse | 18 giây so với 3 giây. Riêng migrations là 303 MB trên tổng 582 MB |
| Lỗi | Lượt ci-checks đỏ; lượt chập chờn | 25/351 (7,1%); 0 |
| Lỗi | Gác chính sách (Supabase 6, qc-scan 5, gạch dài 5, khác 2) / mã thật / test hành vi | 18 / 7 (3 trong đó là phụ thuộc chéo giữa gói) / 0 |
| Lỗi | Lượt đỏ ở job vệ tinh | 0/25 |
| Lỗi lộ dần | Lượt đỏ ở job changes làm skip mọi job build | 16/25; 3 nhánh tốn thêm 4 push |
| Chập chờn bị che | Attempt đỏ, chạy lại thì xanh | 9 (Cloudflare/D1 4, runner 4, npm 1); bước "Apply D1 migrations" đỏ 3 lần |
| Tài khoản | startup_failure; số ngày Actions bị tắt | 15; 5 ngày |
| Cổng merge | Trên 200 PR: merge khi đỏ / khi CI không chạy / khi CI chưa xong | 2 / 2 / 8. Branch protection trả 403 |
| Chi phí | Phút tính tiền mỗi 30 ngày; phần của ci-checks; phần đội lên do làm tròn từng job | khoảng 2.200; 65%; 36% |
| Chi phí | Run chạy đủ 16 job | 22%. 9/10 trường hợp là do sửa thật ci-checks.yml. Từ 01.09 có 42 commit sửa file này, 19 trong đó chỉ thêm gác hoặc chỉ sửa chú thích |
| Chi phí | Job api vẫn chạy ở PR chỉ đổi migration hoặc content / chỉ đổi scripts/ | 134 / 19 trên 530 PR |
| Bộ lọc chọn job | Job thừa do dùng diff hai chấm; khe hở | 8/36 job ở 5/32 run; 3 khe |
| Máy | Worktree; ENOSPC ngay lúc đo; check:guards | 21 cái, 26,6 GB; 2 lần; 63-136 giây |
| Phụ thuộc | Lockfile; tham chiếu thoát khỏi thư mục | 31 lockfile, không workspaces; 46 tham chiếu |
| Tích hợp | Môi trường; migration lỗi khi phát lại từ đầu; bảng không có nguồn trong git; test tích hợp | Chỉ có production; 93%; 286/642; 3/4 đã mục |
| Ràng buộc | PR chạm docs hoặc .claude (60 PR gần nhất); PR của studio có sửa kèm api; PR phát sinh nếu tách 4 vệ tinh | 92%; 16/21; khoảng 30 mỗi 60 ngày nếu docs ở lõi, khoảng 14 nếu docs theo vệ tinh |
Chẩn đoán
| Triệu chứng | Gốc | Tách repo có chữa được không | Đòn bẩy (đợt) |
|---|---|---|---|
| CI chậm | Không chậm. 64% thời gian là dựng máy | Không. Vệ tinh chưa tới 15 MB trên 610 MB | Sparse checkout (0) |
| Hay đỏ ở PR | Gác tất định, cộng cấu trúc job làm lỗi lộ ra từng cái | Không. Gác quét toàn repo theo luật | Hook lúc viết (#420: từ đó có 9 run, 0 đỏ). Runner gác báo đủ lỗi trong một lượt (0) |
| Lỗi mã thật | Dùng gói mà không khai phụ thuộc, 31 lockfile | Không | Gác khai phụ thuộc (0), pnpm chặt (2) |
| Lỗi lọt lên main | Không có cổng merge vì gói Free | Không. Thêm repo là thêm cổng phải dựng | Nâng Pro, hoặc hook cổng merge (0) |
startup_failure | Dùng quá hạn mức phút | Không. Repo vệ tinh private ăn chung quota, và tách ra sinh thêm job nhỏ nên phần làm tròn còn tăng | Cắt phút (0), Pro hoặc spending limit |
| Deploy chập chờn | Cloudflare/D1 lỗi tạm, không có retry | Không | Retry có điều kiện (0) |
| Hỏng và chậm ở máy | Ổ đầy, mỗi worktree tự cài 3-4 GB node_modules | Không trực tiếp | Bootstrap kiểm chỗ trống (0), store dùng chung của pnpm (2) |
| Tích hợp | Chỉ có production, lược đồ không tái tạo được từ git | Không. Tách còn thêm hợp đồng liên repo | Ảnh chụp lược đồ, test tích hợp (1) |
Cách 3 có giá trị ở hai phần. Phần workspaces cộng đồ thị task trong lõi đem lại tính đúng và giải được chuyện ổ đĩa: nên làm. Phần tách vệ tinh chỉ làm gọn ranh giới: có làm hay không là do người dùng quyết.
Repo đích
| Thư mục | Đích | Lý do (số liệu) | Đợt |
|---|---|---|---|
| api, com (tokyo), www, admin (gồm legacy), admin-router, packages/*, docs.conan.school, scripts, .claude | conanplatform (lõi, giữ nguyên tên, lịch sử, secrets) | api và tokyo đổi cùng nhau ở 86 PR. 25% PR gần đây sửa api/src cùng một frontend. docs đi kèm 56-64% số PR | Giữ |
| studio | Lõi | 16/21 PR sửa kèm api; dữ liệu vào D1 qua 17 migration nằm trong api | Đợt 4, chỉ khi catalog chuyển sang file tĩnh |
| media, mam | Lõi | media dùng chung media-catalog với api. mam có backend nằm ở api và 0 PR tính năng | Không tách |
| play | Lõi | Gắn route api.conan.school/play/* | Đợt 4, sau khi có tên miền riêng |
| conan-tool-plane | Lõi | CONAN_DB đọc D1 production; token của CI đang làm secret của Worker | Đợt 4 |
| video, video2, video-studio, kudo | conan-satellites (một repo) | video-studio 0/6 PR chạm lõi; 0 run đỏ; kudo chỉ có 60 dòng | 3b, có điều kiện |
| content (landing Vite), docs/, website-standards | Lưu trữ: gắn tag rồi gỡ | content không còn tên miền; docs/ có 8/8 PR là PR quét toàn repo | 3a, làm được ngay sau Đợt 0 |
| feature-executor | Lưu trữ nếu người dùng xác nhận nó không còn chạy | api vẫn gọi FEATURE_EXECUTOR_URL | 3a, có điều kiện |
Các đợt
Đợt 0: CI rẻ hơn, cổng merge, deploy bền (2 PR, một PR nhỏ cho máy, và việc của người dùng)
PR-A: ci-checks báo đủ lỗi và rẻ hơn (sửa ci-checks.yml, thêm scripts/run-guards.mjs, sửa scripts/qc-scan.mjs)
- A1. Gác chạy qua runner G1, gọi bằng đúng một step ở cuối job
changes. Không tách gác ra job riêng: chú thích ởci-checks.yml:133ghi rằng hai job được gộp chính vì GitHub làm tròn phút theo từng job. - A2. Đổi điều kiện của job build thành
if: ${{ !cancelled() && needs.changes.outputs.X == 'true' }}. Bước chọn job phải chạy trước runner. Cần một PR thử để chắc rằng output củachangesvẫn đọc được khi bước gác đỏ. - A3. Bộ lọc chọn job:
- Diff theo merge-base thay cho diff hai chấm.
- Chỉ chạy đủ mọi job khi diff merge-base của PR có
ci-checks.ymlvà phần đổi không chỉ là chú thích. - Bỏ
api.conan.school/migrations/khỏi điều kiện bật job api. Thu hẹp^scripts/về đúng các script mà job api thật sự gọi. - Bịt 3 khe:
rich-docvào deploy-admin và deploy-com;design-systemvào deploy-media, deploy-video và matrixsites;media-catalogbật job api và media trong ci-checks. - Có mẫu tự kiểm G4.
- A4. Sparse checkout:
- 11 job app chỉ lấy
<app> packages scriptscùng các file ở gốc. - Job api chỉ loại
migrations/, vìbmc.ts:14-31importcontent/bmc. - Job
changesvẫn checkout đủ, vì gác đọc migrations; thêmfilter: blob:none.
- 11 job app chỉ lấy
- A5. Gỡ fallback
npm ci || npm install: gỡ cả 12 chỗ trong ci-checks và chỗ ở_deploy-cloudflare.yml:82trong cùng một PR, sau khi đếm số warning "npm ci thất bại" trong log 60 ngày. Lockfile lệch sẽ đỏ ngay ở PR, và sửa bằng một lệnh cài lại. - A6. Thêm gác G3.
- A7.
.nvmrcghi22.22, không ghi dạng khoảng; CI đọc quanode-version-file. Gộp vào PR này để khỏi tốn thêm một run chạy đủ. - A8. Gộp các job vệ tinh chạy dưới 40 giây thành một job tuần tự. Chỉ giữ thay đổi này nếu đo thấy job gộp vẫn ≤3 phút.
- A9. Chỉ làm khi đã có Pro: thêm job
ci-gatevớiif: always(), đỏ khichangeskhông success, dùng here-string chứ không dùngecho | grep(khuôn SIGPIPE). Kèm gác G5. Nếu không có Pro thì không dựng.
PR-B: deploy bền (sửa apply-d1-migrations.mjs, _deploy-cloudflare.yml, deploy-api/www/com)
B1. Đọc log của 3 lượt đỏ ở bước "Apply D1 migrations" trước, để biết lỗi xảy ra trước hay sau khi D1 bắt đầu chạy file.
B2. Retry khi áp migration: tối đa 3 lần, có backoff, ghi từng lần vào job summary. Chỉ retry hai loại:
- Câu SELECT đọc sổ
schema_migrations. --file, nhưng chỉ khi một gác tĩnh xác nhận mọi câu trong file đều idempotent.
Mọi trường hợp khác dừng đỏ như hiện nay, vì
:127ghi rằng một file hỏng giữa chừng để lại các câu đầu đã chạy. Mã10000(lỗi xác thực) không bao giờ được retry.- Câu SELECT đọc sổ
B3. Retry
wrangler deploy/pages deploytối đa 2 lần, chỉ với lỗi 5xx hoặc 7009.B4. Smoke sau deploy: là một step nằm trong job deploy, chỉ cho api, www và tokyo.
- Không theo redirect; kiểm một chuỗi riêng của từng site.
- www: gọi một route SSR.
- tokyo: gọi
/api/...và tải asset có hash đầu tiên.
B5. Chạy lại một run đỏ để xác nhận error-ledger (#423) đã ghi lượt attempt đỏ. Chỉ viết báo cáo attempt riêng nếu nó chưa ghi.
Gỡ khỏi bản nháp:
- Bỏ bước deploy worker khi merge chỉ có migration: bớt dưới 1 phút tính tiền mỗi lượt, mà lại mở lại khuôn api đóng băng của 02-03.09.
- Retry
npm ci: chỉ có 1 lượt ECONNRESET trong 37 ngày.
0.M Máy (PR nhỏ sửa scripts/cloud-bootstrap.sh, cộng việc tay)
- Bootstrap kiểm chỗ trống trước khi chạy
npm ci. Dưới ngưỡng thì dừng và báo rõ, không thoát 0 trong im lặng. - Bootstrap chỉ cài thư mục mà phiên cần, qua
CONAN_BOOTSTRAP_DIRS; mặc định là 4 app lõi. - Worktree mới dùng sparse, bỏ
migrations/(303 MB), và clone node_modules bằngcp -ckhi lockfile trùng. Ở worktree sparse không chạy đượccheck:library-docs, nên gác đó do CI lo.
Ngân sách phút (ước tính, đo lại 14 ngày sau khi merge)
| Mục | Thay đổi, phút/30 ngày | Căn cứ |
|---|---|---|
| A1 + A3: run chạy đủ từ 22% xuống ≤12% | -400 | Run chạy đủ khoảng 16 phút, run thường khoảng 2. 19/42 lượt sửa yml không còn cần nữa |
A3: bỏ migrations và thu hẹp scripts/ khỏi job api | -80 | 153/387 PR đang bật job api |
| A3: diff theo merge-base | -55 | 8 job thừa trên 32 run |
| A4: job www từ 64 giây xuống khoảng 49 | -80 | Chỉ job vượt 60 giây mới bớt được phút tính tiền |
| A8: gộp job vệ tinh | -100 tới -150 | Chỉ có tác dụng ở run chạy đủ |
| B4: smoke là step trong job | 0 tới +50 | |
A9: ci-gate (chỉ khi có Pro) | +285 | 1 phút mỗi run |
| Tổng | khoảng -700 nếu ở Free; khoảng -420 nếu có Pro | ci-checks từ khoảng 1.450 xuống khoảng 750 |
Mục tiêu: tổng ≤1.600 phút mỗi 30 ngày nếu ở Free (chừa 20% so với hạn mức), ≤1.900 nếu có Pro. Cách lùi: revert từng PR; hook tắt bằng một dòng trong settings.
Đợt 1: nền tích hợp (2-3 PR, thêm khoảng 30-60 phút mỗi 30 ngày)
- 1.1 Ảnh chụp lược đồ.
- Lấy bằng
SELECT type, name, tbl_name, sql FROM sqlite_master. Đây là lệnh chỉ đọc, đúng luật repo. Không dùngd1 export, vì bảng ảo của hệ ngoài có thể làm lệnh export đỏ. - Lưu vào
api.conan.school/d1/schema-snapshot.sql, kèmschema-owners.jsonchia hai danh sách: bảng của repo, và bảng nguồn ngoài (garage_, compass27_, agasa_, các bảng của sutucon và simba). - Gọi đây là "ảnh chụp", không gọi "mốc nền", để khỏi lẫn với mốc nền của sổ
schema_migrations.
- Lấy bằng
- 1.2 Gác trôi lược đồ chạy mỗi đêm. Bảng của repo lệch thì đỏ. Bảng nguồn ngoài lệch thì chỉ ghi vào báo cáo.
- 1.3 Test tích hợp.
- Khung chạy:
wrangler dev --localvới D1 cục bộ dựng từ ảnh chụp; fixture cũng sinh từ ảnh chụp. - Test mới viết cho miền từng có sự cố: public/jtbd (lỗi 502 do slug có dấu), communities (gác Pro ở API), events.
test-mode-guardvào CI ngay.- Bốn test cũ của miền learner/Simba: giữ
classmateslàm mẫu. Ba test đã mục thì sửa nếu fixture mới làm chúng xanh, không thì gỡ (miền này 60 ngày chỉ có 2 commit và 0 sự cố). - Chạy trong job api, và làm sau A4 để job vẫn dưới 60 giây.
- Khung chạy:
- 1.4 (tuỳ chọn)
check:d1-replay. Chỉ chạy khi PR có migration. Dùngwrangler d1 execute --local, kèm gác tĩnh về độ dài câu lệnh, vì SQLite của Node không có các giới hạn của D1. Ưu tiên thấp: 37 ngày chỉ có 1 lỗi migration, và lỗi đó đã bị bắt trước merge. - 1.5 (tuỳ chọn, người dùng quyết) D1 staging. Thêm D1 staging và
[env.staging]để thử được Workflow và AI trước khi merge, thay chowrangler.migration-smoke.toml(file đang bật TEST_MODE trên D1 và KV production).
Đợt 2: pnpm workspaces cộng Turborepo trong lõi (spike, rồi 3-4 PR)
2.0 Spike. Làm trên một nhánh, chạy bằng
workflow_dispatch, không merge. Đo trên CI:- Thời gian cài cả lõi, và cài lọc bằng
pnpm install --filter <gói>..., so với npm workspaces. - Dung lượng của một worktree.
- Phát lại 3 commit lỗi phụ thuộc (34187793914, 34189257742, 34386591101), xem có đỏ trước merge không.
- Bản TypeScript mà
@react-router/devcủa www và mam nhìn thấy.
Điều kiện đi tiếp: PR chỉ đổi api cài trong ≤15 giây (hiện là 9), bắt được 3/3 lỗi cũ, node_modules mỗi worktree còn ≤1/3 hiện nay. Không đạt thì dừng ở mức gác G3.
- Thời gian cài cả lõi, và cài lọc bằng
2.1 Tên gói duy nhất. Đổi về dạng
@conan/<site>. Hiện video và video2 cùng tên, và còn các tên chung chung nhưfrontend,backend,mam-temp. Tên Worker và tên project Pages không đổi.2.2 Khai phụ thuộc thật.
@import "@conan/design-system/theme.css"thay cho đường dẫn tương đối.rich-dockhaipeerDependencieslà react.course-standarddùngworkspace:*.- Thêm
package.jsonchomedia-catalog. - G3 chuyển từ mốc chặn sang cổng 0.
2.3 Một lockfile duy nhất.
- Gác cấm lockfile nằm ngoài gốc.
- Bootstrap chỉ cài ở gốc.
launch.jsondùngpnpm --filter <gói> dev.- Kiểm xem
legacy/admin-*có cần node_modules riêng không; không cần thì gỡ 0,9 GB.
2.4 Sửa công cụ trong cùng PR đổi import.
dead-code-scanphải đọcmain/exportslàm điểm vào. Nếu không,RichDoc.tsxbị đánh dấu chết và 7 ngày sau bị xoá.- Thêm assert rằng
@react-router/devcủa www và mam phân giải ra TypeScript 5.x.
2.5 Turborepo thay bộ lọc bash cho các gói trong workspace.
- Các cạnh không phải import (scripts, workflow, studio đọc
api/content) khai trongci/extra-edges.json. - Job đỏ khi danh sách affected rỗng mà diff có file không phải tài liệu.
- Trước khi bỏ bộ lọc bash, phát lại offline trên 530 danh sách file PR (
prfiles/*.txt). Yêu cầu: 0 ca turbo chọn ít gói hơn. - G1 vẫn quét toàn repo. Tắt remote cache ở CI.
- Các cạnh không phải import (scripts, workflow, studio đọc
2.6 Deploy giữ mô hình theo từng app.
- Sparse checkout; cwd ở thư mục app để giữ
functions/của www và tokyo. - Cài bằng
pnpm install --frozen-lockfile --filter @conan/<app>.... Không dùngturbo prune. - Paths của workflow deploy thêm lockfile gốc. Workflow quyết có deploy hay không bằng cách so với SHA của lần deploy xanh gần nhất, để nâng phụ thuộc cũng lên tới production.
- Sparse checkout; cwd ở thư mục app để giữ
Cách lùi: tag
pre-workspacesgiữ đủ 31 lockfile, rồi revert.
Đợt 3: lưu trữ (3a) và repo conan-satellites (3b)
3a làm được ngay sau Đợt 0:
- docs/: trước khi gỡ, chuyển
docs/architecture/LEARNER_STREAK_STANDARD.mdsang docs.conan.school, rồi sửa luật 15 của AGENTS.md,CLAUDE.md:294vànightly-qc:118. - content.conan.school/: gỡ thư mục cùng
deploy-content.yml. Tên miền content.conan.school vẫn do Worker kudo phục vụ. - feature-executor: chỉ gỡ khi người dùng xác nhận nó không còn chạy. Gỡ kèm bước
node --check(dòng 238) và mục trongnpm testở gốc. - Gắn tag
archive/<tên>-2026-10, gỡ cặp token-sync tương ứng, thêm các thư mục này vào G6.
3b. Điều kiện vào:
- Đợt 0-2 đã xong.
- Người dùng chấp nhận số PR phát sinh: khoảng 14 mỗi 60 ngày nếu docs đi theo vệ tinh (kèm sửa Luật thường trực cho repo đó), hoặc khoảng 30 nếu docs ở lại lõi.
- Video đã nguội (#416 và #417 vừa merge ngày 13.10).
- App GitHub của Claude Code đã được cấp quyền vào repo mới.
3b. Các bước, làm trong cùng một buổi (đóng băng các thư mục liên quan):
- Trong lõi, làm trước:
- Sửa
video2.conan.school/wrangler.tomltừname = "video-conan-school"thànhvideo2-conan-school. - Đóng hoặc rebase các PR đang mở chạm 4 thư mục này. #256, #331, #418 cần kiểm.
- Sửa
- Tách lịch sử:
git filter-repo --pathcho 4 thư mục, kèm--message-callbackviết lại(#123)thành(dac2205/conanplatform#123). Viết.gitignoregốc cho vệ tinh (.dev.vars,.env,*.tsbuildinfo) trước commit đầu tiên. - Vệ tinh mang theo:
vendor/design-system/, đồng bộ qua G7.- Các gác, chép đúng đường dẫn (G2).
- Hook và bootstrap.
- CLAUDE.md đầy đủ, không trỏ về lõi. Lý do: phiên cloud chỉ clone repo vệ tinh, và auto-memory gắn theo đường dẫn. Nội dung gồm các luật chữ, màu, icon, UI, deploy chỉ qua CI, chỉ dùng Chrome, chỉ merge khi xanh, cộng hai hợp đồng runtime:
- kudo đang phục vụ
content.conan.school, và trang chủ www dẫn tới đó (ContentAgentPromo.tsx:10); rich-doc.tscủa api chấp nhận URL dạngvideo.conan.school/<hex>.
- kudo đang phục vụ
- Các mục
.claude/memoryliên quan, cùng 3 skill: design-system, ci-guards, deploy-migrate. - Luật: sửa vệ tinh thì mở phiên trong repo vệ tinh, vì
post-edit-guards.mjs:24bỏ qua đường dẫn bắt đầu bằng...
- Secrets và CI:
- Token Cloudflare mới, hẹp: Pages Edit cho video và video2; Workers Scripts và Routes cho kudo. Không chép token của lõi.
- Đầu job deploy, thăm dò quyền bằng thao tác thật (GET project, GET script), vì
/user/tokens/verifykhông trả danh sách quyền. - Không đọc được production branch thì đỏ, không rơi về
mainnhư dòng 158-160 đang làm. - Smoke kudo cho content.conan.school chỉ cảnh báo, vì origin là của bên thứ ba.
- Chuyển giao deploy:
- Vệ tinh deploy thử vào preview.
- Lõi chạy
gh workflow disablecho các workflow sắp gỡ, để nút Re-run trên run cũ không deploy lại mã cũ. - Một PR ở lõi gỡ cùng lúc: deploy-video, deploy-video2, deploy-kudo; job
video(dòng 555); video2 và content khỏiSITES_ALL; cặp token-sync của video2; mục video trongDEPLOY_WORKFLOWS(conan-tool-plane/src/executors/cloudflare.ts:31); danh sách sub-domain trong CLAUDE.md, AGENTS.md, skills-map, docs. - Vệ tinh bật deploy production.
- Kiểm
wrangler pages deployment list: commit hash phải thuộc repo vệ tinh.
- Dọn ở lõi: xoá khoá mốc của các thư mục đã đi, vì
check-color-tokensgiữ khoá cũ (dòng 131). Bật G6.
Cách lùi: revert PR gỡ ở lõi, tắt Actions và archive repo vệ tinh. Các project Cloudflare không đổi.
Đợt 4: có điều kiện, không hẹn ngày
- tool-plane: cần file hợp đồng cho các bảng và cột đọc qua
CONAN_DB, cộng gác ở lõi. Nhưngprovision-tool-plane.yml:209đang dùng token CI làm secret của Worker, và quyền D1 tính theo cả tài khoản, nên không có token hẹp. Để lại tới khi giải được chuyện này. - play: chuyển sang tên miền riêng trước.
- studio: chỉ tách khi catalog được phục vụ bằng file tĩnh. Nếu không,
catalog.tssẽ ghi migration vào chỗ không có CI nào áp. - media, mam: không tách.
Chọn công cụ: pnpm workspaces cộng Turborepo
Sửa một khẳng định sai của bản nháp. Turborepo không kiểm import nên không "buộc khai phụ thuộc". Thứ bắt được 3/7 lỗi mã thật là gác G3 và cách pnpm bày node_modules chặt. Ngược lại, npm workspaces hoist gói lên gốc, nên lỗi kiểu www dùng
@tiptap/coremà không khai sẽ biến mất ở CI và chỉ nổ lúc deploy.pnpm hơn npm workspaces ở 3 điểm đo được:
- Store dùng hard link: giải chuyện 26,6 GB worktree và 2 lần ENOSPC.
- Phân giải peer theo từng gói dùng: www và mam giữ được TS 5.9 mà không cần
legacy-peer-depscho cả repo. - Cài lọc theo gói: job api giữ được thời gian cài khoảng 9 giây.
Spike 2.0 sẽ quyết cuối cùng.
Turborepo hơn Nx:
- Turborepo gọi lại các script npm sẵn có, không thêm
project.json, plugin hay daemon. - Nx dựng được đồ thị mà vẫn giữ 31 lockfile, nhưng không chữa được ổ đĩa và lỗi resolve.
- Các thế mạnh còn lại của Nx giải bài toán repo chưa có: repo chỉ có 6 file test, CI chạy 1,5 phút.
- Turborepo gọi lại các script npm sẵn có, không thêm
Turborepo đem lại tính đúng, không đem lại tốc độ. Nó thay bộ lọc grep đã hỏng 2 lần và đang có 3 khe hở.
- Remote cache tắt ở CI, vì trúng cache sai là kiểu hỏng im lặng. Cache cục bộ ở máy thì bật, có ích nhất cho www (48-106 giây ở máy).
- Strict env mode: khai
envtrong turbo.json, và bước build assert các biến bắt buộc, vì admin đang rơi vềVITE_API_URL || ''. turbovàpackageManagerghim đúng bản mới nhất lúc cài.
{ "globalDependencies": [".nvmrc", "pnpm-lock.yaml"],
"tasks": {
"typecheck": { "dependsOn": ["^typecheck"] },
"build": { "dependsOn": ["^build"], "outputs": ["dist/**", "build/**", ".vitepress/dist/**"], "env": ["VITE_*", "API_GATEWAY_URL"] },
"test": { "dependsOn": ["^build"] },
"check": { "passThroughEnv": ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"] } } }Gác dùng chung
- G1. Runner gác
scripts/run-guards.mjs.- Giữ một danh sách gác duy nhất. Chạy hết mọi gác, tổng hợp lỗi, chỉ thoát khác 0 ở cuối.
- CI,
check:guardsvàqc-scancùng dùng danh sách này.qc-scanđọc từ runner thay cho regex ởqc-scan.mjs:138, và đỏ khi danh sách rỗng. - Ở máy có chế độ
--changed: gác theo file chỉ soi file khác merge-base; gác theo mốc chỉ soi thư mục có file đổi. CI luôn quét toàn repo. - Thêm gác là sửa runner, không phải sửa yml.
- G2. Chống gác soi rỗng mà vẫn xanh.
- Mỗi gác in số file đã soi và đỏ khi số đó bằng 0.
- Gốc repo lấy từ
git rev-parse --show-toplevel, không lấy từimport.meta.url. Hiện 6 gác đang tính gốc từ vị trí file của chính nó. - CI cài một file mồi vi phạm cho mỗi gác và assert gác đỏ.
- Vệ tinh chép gác vào đúng đường dẫn
scripts/. Hook của vệ tinh được chạy với một payload chứa\u2014, và phải thoát mã 2. Nếu thiếu script, hook thoát mã 1 và chỉ là cảnh báo.
- G3.
check:workspace-deps. Mọi import theo tên gói phải được khai trong package.json gần nhất, kể cảimport(),@importtrong CSS và plugin trongvite.config. Đường dẫn thoát ra khỏi gói giữ mốc 46, chỉ được hạ, về 0 ở Đợt 2. - G4. Mẫu tự kiểm cho bộ lọc chọn job. Ít nhất 6 ca: chỉ docs, merge main vào nhánh, yml chỉ đổi chú thích, yml đổi thật, chỉ migration, chỉ scripts. Viết bằng here-string (luật SIGPIPE,
ci-checks.yml:103-108). - G5. Cổng merge.
- Hook PreToolUse khớp lệnh
gh pr mergevà gọiscripts/hooks/merge-gate.mjs. - Script đòi một run ci-checks success trên đúng SHA head. Thiếu run, run đỏ, hoặc run chưa xong thì chặn bằng mã 2.
- Mẫu tự kiểm dùng lịch sử thật: head của #416 (đỏ), #283 (không có run), và một PR xanh.
- Nếu có Pro: thêm
ci-gate, một gác soneedscủa gate với mọi job, required check, và bậtenforce_adminsđể chặn--admin.
- Hook PreToolUse khớp lệnh
- G6.
check-departed-paths. Đỏ khi có file được theo dõi nằm dưới một thư mục đã chuyển đi hoặc đã lưu trữ. Chạy cả trong hook Stop. Tiền lệ: thư mục mentors vẫn còn 27 file??sau #420. - G7. Đồng bộ theme.css với vệ tinh.
- Một job ở lõi chạy khi
packages/design-systemđổi và chạy hằng tuần. Job dùng một PAT chỉ đọc, chỉ cấp cho repo vệ tinh, để so bản chép trong vệ tinh và các script gác đã chép. Lệch thì đỏ và mở issue. - Vệ tinh cấm khai
--color-primaryở bất kỳ đâu ngoàivendor/, trừ bản chép tay của video2 đã có trong manifest. - G7 thay cho GitHub Packages: không phải đổi scope sang
@dac2205, không phải đặt token ở 4 nơi.
- Một job ở lõi chạy khi
- Mốc: mốc của vệ tinh lấy theo số hiện tại. Ở cả hai repo, mốc chỉ được hạ.
Rủi ro
Các rủi ro dưới đây đều thuộc loại hỏng mà hệ vẫn trông như đang chạy.
| Rủi ro | Tiền lệ | Chặn bằng |
|---|---|---|
Output của changes không đọc được khi gác đỏ, nên build vẫn bị skip | 16/25 lượt đỏ skip build | A2, cộng PR thử |
| Chính kế hoạch sửa yml nhiều lần, làm tăng số run chạy đủ | 42 commit sửa yml từ 01.09 | G1; gộp các lần sửa yml vào PR-A |
| Retry chạy lại nửa file migration, hoặc che lỗi token | apply-d1-migrations.mjs:127; lỗi 7403 | B2 |
| Smoke xanh giả: redirect sang trang đăng nhập Access, SPA chỉ trả vỏ HTML | admin nằm sau Cloudflare Access | B4 |
| Cổng merge bị vượt | #416 merge khi đỏ dù đã có memory "chỉ merge khi xanh" | G5 |
Hết phút, startup_failure đánh mọi repo của tài khoản | 15 lượt; ngày 04.09 tiêu 1.803/2.000 phút | Ngân sách của Đợt 0; mỗi đợt ghi rõ thay đổi số phút |
| Phụ thuộc ma: CI xanh, deploy đỏ | www dùng @tiptap/core không khai | G3, pnpm |
@react-router/dev của www thấy TS 7 | peer khai ^5||^6 | 2.4 |
| Chỉ lockfile gốc đổi nên production không được deploy | (mới) | 2.6 |
| Turbo chọn thiếu gói vì có cạnh không phải import | 3 khe lọc hiện có | 2.5 |
| dead-code-scan xoá mã đang sống | dead-code-scan.mjs:82 | 2.4 |
| Gác của vệ tinh soi rỗng mà vẫn xanh | 6 gác tính gốc từ vị trí file | G2 |
| Thư mục đã chuyển đi mọc lại trong lõi | mentors | G6 |
| Vệ tinh deploy nhầm sang preview, hoặc video2 đè lên video | known-risks#play-domain-lech | 3b bước 1 và 4 |
| Tool Plane hoặc nút Re-run deploy mã cũ của video từ lõi | cloudflare.ts:31 | 3b bước 5 |
| Luật lặng lẽ ngừng áp ở vệ tinh | Luật màu không có gác: 618 lên 3.445 chỗ trong 5 ngày | CLAUDE.md đầy đủ, G2, G7 |
| Gác trôi lược đồ đỏ thường trực vì hệ ngoài | 3 hệ ngoài cùng ghi vào D1 | 1.2 |
| Bootstrap cài đua ở hai nơi mà vẫn thoát 0 | cloud-bootstrap.sh | 0.M, 2.3 |
Chỉ số thành công
| Chỉ số | Mốc | Mục tiêu | Đợt | Đo bằng |
|---|---|---|---|---|
| Phút tính tiền / 30 ngày | khoảng 2.200 | ≤1.600 (Free), ≤1.900 (Pro) | 0 | Billing API |
startup_failure | 15 / 37 ngày | 0 | 0 | gh run list |
| Merge khi CI không xanh hoặc không chạy | 4 (thêm 8 khi CI chưa xong) / 200 PR | 0 | 0 | So PR với run theo SHA |
| Run chạy đủ / job thừa do diff | 22% / 8 trên 36 | ≤12% / 0 | 0 | Log của job changes |
| Nhánh phải push thêm vì lỗi lộ dần | 3 nhánh, 4 push | 0 | 0 | Cặp SHA đỏ và xanh |
| ci-checks p50 / p90 | 1,52 / 1,90 phút | ≤2,0 / 2,5 phút | 0-2 | a1.py |
| Checkout mỗi job app | 18 giây | ≤5 giây | 0 | jobs/*.json |
| Khe hở bộ lọc / lỗi phụ thuộc chéo | 3 / 3 | 0 / 0 | 0, 2 | G4, redjobs/ |
| Lượt đỏ do gác chính sách | 18/25 | Đo 4 tuần sau #420, không đặt mục tiêu 0 | redjobs/ | |
| Attempt > 1 | 9, không ai thấy | 100% có trong error-ledger; deploy-api ≤2 lượt / 37 ngày | 0 | API theo attempt |
| Workflow deploy có smoke | 0/16 | 3/3 (api, www, tokyo) | 0 | grep workflow |
| ENOSPC; node_modules mỗi worktree | 2 lần; 3-4 GB | 0; ≤1/3 hiện nay | 0, 2 | du, log |
| Test hành vi chạy trong CI | 2 file | ≥5 file, job api vẫn tính 1 phút | 1 | Log job api |
| Lược đồ trôi | 286/642 bảng không có nguồn | 7 đêm, 0 lượt đỏ giả, bắt được 1 mẫu lệch cố ý | 1 | Gác trôi lược đồ |
Lockfile / tham chiếu ../packages | 31 / 46 | 1 / 0 | 2 | find, G3 |
| PR phát sinh vì tách | ước 14-30 / 60 ngày | Đo bằng cochange.py sau 30 ngày; vượt gấp đôi ước tính thì dừng Đợt 4 | 3 | cochange.py |
Nếu sau Đợt 0 và Đợt 1 các chỉ số trên đã đạt, có thể hoãn Đợt 3b mà không mất gì đo được.
Việc cần người dùng quyết
- Nâng GitHub Pro hay không. Pro khoảng 4 USD/tháng, cho 3.000 phút, branch protection và rulesets. Nếu ở lại Free thì dùng hook G5 và trần 1.600 phút. Tôi đề xuất nâng Pro, bật required check kèm
enforce_admins. Cũng có thể đặt spending limit lớn hơn 0 để vượt hạn mức thì trả tiền, thay vì gặpstartup_failure. - Thêm quyền
zone.analytics.readcho token của error-ledger: 2 lượt đỏ, và #423 đang đỏ vì thiếu quyền này. - Ổ đĩa và số phiên: dọn 21 worktree (26,6 GB), đặt ngưỡng chỗ trống cho bootstrap, và giới hạn số phiên chạy song song trên máy.
- pnpm hay npm workspaces: quyết sau spike 2.0.
- Bảng của hệ ngoài: đưa vào ảnh chụp dưới danh sách "nguồn ngoài" (tôi đề xuất cách này), hay bỏ hẳn.
- D1 staging (mục 1.5): có làm không.
- feature-executor có còn chạy không. Nó chạy ở 127.0.0.1 sau tunnel, repo không cho biết.
- Đợt 3b: có làm không. Nếu làm thì docs của vệ tinh nằm ở đâu, có sửa Luật thường trực cho repo đó không, và cấp quyền cho app Claude Code vào repo mới.
- studio: có chuyển catalog sang file tĩnh không. Đây là điều kiện để tách ở Đợt 4.
Phản biện đã bác hoặc thu hẹp
- Bỏ deploy worker ở merge chỉ có migration, rồi gác bằng SHA (breakage): tôi gỡ hẳn đề xuất này thay vì vá. Nó chỉ bớt dưới 1 phút tính tiền mỗi lượt, không đáng mở lại khuôn api đóng băng.
- Dependabot thiếu token, alias
@dac2205(breakage 1.1): không còn áp dụng, vì đã bỏ GitHub Packages (G7). turbo prunelàm mất Pages Functions (breakage, đợt 2): không còn áp dụng, vì deploy vẫn chạy theo từng app (2.6).legacy-peer-depscộng hoist (breakage, đợt 2): thu hẹp lại, vì pnpm phân giải peer theo từng gói dùng. Vẫn giữ assert ở 2.4 phòng khi spike chọn npm.- Gói gác dùng chung soi rỗng (breakage 1.2): đã đổi thiết kế sang chép gác vào cùng đường dẫn, nhưng vẫn giữ đủ ba biện pháp của G2, vì bản chép cũng có thể soi rỗng.
- Ngoài các mục trên, tôi không bác phản biện mức cao hay mức vừa nào; tất cả đã được đưa vào các đợt.