Skip to content

SDD-027 · Mặt phẳng sản xuất, preflight, vá từng phần và màn theo dõi ​

Một phần của SDD-027.

9. Mặt phẳng sản xuất — địa chỉ, hàng chờ, ưu tiên (SRC-634) ​

Chỉ đạo chủ dự án 2026-08-28 gồm năm việc; §9-§13 là chỗ ở của chúng.

Địa chỉ thật thay cho nhãn chữ. content_runs mang course_id · unit_seq · lesson_seq · part (migration 0166). Trước đó chỉ có target_label là một chuỗi tiếng Việt, nên câu hỏi "khoá này đã chạy những lượt nào" phải LIKE trên chuỗi — sai một dấu cách là mất dòng. Địa chỉ cũng chính là khoá của mọi luật chống chạy loạn ở §13.

Brief dựng từ D1, không gõ tay. db.ts đọc course · unit · key concepts · Misconception Map · các bài anh em theo địa chỉ rồi dựng LessonBrief. Trước SRC-634 brief nằm trong body của POST /runs: gõ thiếu key concepts thì model không tuân được CD-4.4 mà không ai biết, gõ sai tên unit thì bài sinh ra lạc khỏi khoá. Nay dữ liệu vào xưởng là đúng dữ liệu learner đang đọc.

Hàng chờ được TÍNH RA, không lưu. priority.ts xếp mọi bài còn thiếu nội dung theo (rank môn, rank bậc, ladder, seq khoá, seq unit, seq bài). Một bảng hàng chờ lưu sẵn sẽ ôi thiu ngay khi người soạn bổ sung một unit hoặc một lượt chạy xong, và không ai biết. Đổi lại là một truy vấn nặng hơn — ở quy mô vài trăm bài đây là đổi chác đúng.

Việc là gì: một bài đã có tên trong kế hoạch unit mà nội dung chưa đủ sàn CD-8 (chưa có story, dưới 3 hiểu lầm, dưới 2 lỗi, dưới 3 hướng gỡ, chưa có học liệu, dưới 2 câu kiểm). Xưởng không tự đẻ ra unit và không tự đặt tên bài — đó là việc của người soạn unit (/course-design).

Ưu tiên là dữ liệu, không phải code. Bảng content_priority(dimension, key, rank, enabled), mặc định theo đúng chỉ đạo: math 1 · vietnamese 2 · informatics 3 · english 4; bậc 1→5 theo thứ tự tăng dần (bậc nền trước). Hàng chờ JOIN vào bảng này, nên enabled=0 là biến mất khỏi hàng chờ — tắt một môn là một thao tác trên Coral, không phải một lần deploy.

10. Báo cáo kiểm tra đầu vào (preflight) ​

Chỉ đạo: "mỗi workflow, rất cần có báo cáo kiểm tra xem những dữ liệu đầu vào đã đủ hay chưa, hay là cần bổ sung thêm". preflight.ts chấm chín mục và phân biệt chặn với nhắc:

MụcThiếu thìVì sao
Khoá · Unit · Big Idea · Essential Question · tên bài · key concepts 3-5CHẶNModel không thể đúng khi thiếu: không có key concepts thì CD-4.4 (concepts ⊆ key concepts) là bất khả thi, bài chắc chắn trượt L16
Misconception Map của unit · Unit Outcomes · các bài anh emnhắcBài vẫn ra được nhưng đứt mối nối (unit_code rỗng) hoặc dễ trùng phạm vi

Một hàm, hai người gọi — và đó là chủ đích: Coral hỏi trước ("khoá này bấm sinh được chưa"), xưởng dùng để chặn (chưa đủ thì không khởi động workflow). Đây là lớp chống tốn tiền rẻ nhất trong cả hệ: một lượt chạy trên brief thiếu key concepts là trả tiền cho kết quả đã biết trước là hỏng. Bản báo cáo tại thời điểm chạy được chụp lại vào cột content_runs.preflight — tính lại tươi thì mọi lượt chạy cũ đều hoá ra "đủ" sau khi người ta bổ sung dữ liệu.

11. Vá từng phần, không sinh lại từ đầu ​

Chỉ đạo: "làm sao để fix được từng phần, từng content, thay vì lại phải sinh lại từ đầu".

POST /runs nhận thêm part ∈ {guiding_question, concepts, outcomes, story, misconceptions, errors, interventions, materials, checks} kèm reasons (lời người chê). Khi đó:

  1. Bài gốc đọc từ D1 (loadBaselineDraft) — vá là sửa cái đang có, không phải sinh cái mới.
  2. Prompt mang cả bài để model biết ngữ cảnh, kèm lệnh cấm sửa phần khác; model chỉ trả về {"<part>": …}. Bảy phần kia được ghép lại bằng code, model không có cơ hội chạm vào.
  3. Mảnh vá bị ép đúng ràng buộc của chính trường ấy trong lessonDraftSchema (một nguồn sự thật: partSpec lấy schema con từ schema bài, không chép tay lần hai).
  4. Chấm lại CẢ BÀI sau khi ghép, không chấm trên mảnh: một mảnh đúng vẫn có thể phá bất biến toàn bài (vá checks làm tụt số mcq xuống dưới số misconception — L13).
  5. Rẻ hơn hẳn: trần output 2048 thay vì 8192, và mặc định một model rẻ nhất thay vì cả sổ — vá là việc nhỏ có đúng/sai rõ ràng, không phải cuộc thi giữa các model.

curriculum_lessons không có nội dung nào thì không vá được: xưởng trả 409 và bảo sinh cả bài trước, thay vì lặng lẽ sinh cả bài (người bấm "sửa câu chuyện" mà nhận về một bài khác hẳn là kiểu bất ngờ tệ nhất).

12. Lượt chạy này đổi những gì (changeset) ​

Chỉ đạo: "mỗi lần chạy thì cái gì được tạo ra hoặc bị thay đổi". Bảng content_run_changes (0166): mỗi lượt sinh 9 dòng, mỗi dòng một phần của bài, action ∈ created | updated | unchanged.

  • So với bản ĐANG CÓ trong curriculum_*, không so với lượt chạy trước: người vận hành hỏi "chạy xong thì bài của con đổi gì", không hỏi "hai bản nháp khác nhau ra sao".
  • Ghi cả phần KHÔNG đổi. Một lượt vá mà bảng chỉ hiện một dòng thì không phân biệt được với một lượt sinh cả bài đã làm hỏng tám phần kia; nhìn thấy tám dòng "giữ nguyên" mới là bằng chứng.
  • So bằng JSON đã chuẩn hoá khoảng trắng: báo đổi oan thì mọi lượt đều hiện 9 dòng đỏ và bảng mất sạch giá trị.

13. Năm chốt chống chạy loạn ​

Chỉ đạo: "làm sao đừng để hệ thống liên tục chạy toán loạn workflows, vì như vậy rất là tốn tiền". Mọi lượt chạy — bấm tay hay do hàng chờ lấy việc — đi qua cùng một hàm startRun; hai đường riêng là hai bộ chốt và bộ nào đó sẽ thiếu một chốt. Xếp rẻ trước: chốt nào không cần gọi model để biết là sai thì phải chặn trước.

#ChốtTrả vềTrần (vars, đổi phải deploy)
1Đầu vào chưa đủ (§10)422 kèm báo cáo—
2Bài này đang có lượt chạy409—
3Vừa chạy, chưa hết nghỉ429FOUNDRY_TARGET_COOLDOWN_MIN = 30
4Đã thử quá số lần mà vẫn thiếu409, đòi người xemFOUNDRY_MAX_ATTEMPTS_PER_TARGET = 3
5Trần song song / trần chi phí ngày429FOUNDRY_MAX_CONCURRENT_RUNS = 2 · FOUNDRY_DAILY_USD_CAP = 5

Thêm hai lớp nữa: hàng chờ tự loại việc đang chạy / vừa chạy / thử quá nhiều lần (nên bấm "lấy việc" liên tục cũng không sinh hai lượt cho cùng một bài), và trần lô FOUNDRY_MAX_BATCH = 5 cho POST /runs/batch — chỗ duy nhất một cú bấm sinh ra nhiều lượt. Trần để ở vars chứ không ở D1 là cố ý: trần sửa được từ giao diện thì không còn là trần.

Cron kéo việc — có chặn, tắt mặc định. Một đợt 80 bài mà trần lô là 5 thì người vận hành phải bấm 16 lần; ma sát ấy không làm hệ an toàn hơn, chỉ làm người ta bấm bừa. Nên có scheduled chạy mỗi 15 phút, kéo tối đa FOUNDRY_MAX_BATCH việc — nhưng không có đường tắt nào so với bấm tay: mỗi việc vẫn qua đủ năm chốt, chạm 429 là dừng cả lượt kéo, và nó chỉ làm việc trong danh sách nhắm nếu có. Tắt mặc định trong code (FOUNDRY_AUTO_DRAIN khác "1"); bật là một quyết định ghi trong wrangler.jsonc, và kịch bản xấu nhất vẫn bị trần chi phí ngày chặn.

14. Màn hình theo dõi trên Coral ​

coral.nemo12.com → tab Xưởng nội dung (apps/coral/src/Foundry.tsx, SDD-013). Thứ tự khối theo đúng thứ tự câu hỏi người vận hành hỏi: trần đang hiệu lực → ưu tiên môn/bậc → bảng từng khoá (còn bao nhiêu bài thiếu, đã chạy mấy lượt, tốn bao nhiêu) → mở một khoá thấy việc của nó kèm báo cáo đầu vào và nút sinh cả bài / chỉ vá phần X → mở một lượt chạy thấy nó đổi những gì.

Màn này không giữ luật nào: đủ đầu vào chưa, được chạy hay không, ưu tiên ra sao — tất cả do xưởng quyết, Coral chỉ hiển thị và bấm. Chín route /v1/coral/foundry/* chỉ chuyển tiếp (staff/admin gate + audit log cho hai route tiêu tiền).

Trạng thái hiện tại: service binding FOUNDRY trong workers/api/wrangler.jsonc đang tắt vì nemo12-foundry chưa deploy lần nào (token CI thiếu quyền R2 — ghi tại chỗ trong wrangler.jsonc từ SRC-632). Cho tới khi mở lại, mọi route foundry trả 404 có thông điệp rõ và màn Coral hiện đúng lý do. Thứ tự bắt buộc: deploy nemo12-foundry trước, mở binding sau — CI đã xếp bước deploy foundry trước bước deploy api đúng vì lẽ đó.