SDD-002 · Bằng chứng thi hành, luật tồn tại model và lịch chạy engine
Một phần của SDD-002.
19. Bằng chứng thi hành — Run History & Model Version Store (REQ-PLT-13, REQ-INT-29, SRC-098)
Model và engine chỉ đáng tin khi chứng minh được là đã chạy thật. Ba thứ được ghi lại, tách bạch:
| Ghi cái gì | Bảng | Trả lời câu hỏi |
|---|---|---|
| Workflow run + từng step | workflow_runs, workflow_run_steps | "WF-01/WF-03/WF-04 đã chạy chưa, bước nào hỏng?" |
| Mỗi lần một engine chạy | engine_runs | "Goal Engine có thật sự chạy lúc learner đổi ngày thi không?" |
| Snapshot nội dung model theo version | learner_model_versions | "Learner Model của em này lúc 14/08 ghi gì?" |
- Engine chạy mà output không đổi vẫn có dòng
engine_runs— chỉlearner_model_versionsmới dedupe theocontent_hash. Hash tính trên nội dung đã bỏ trường thời điểm (computed_at— thời điểm đã nằm ở cộtgenerated_at): để nguyên thì hash luôn khác, dedupe thành vô nghĩa và mỗi lần chạy lại đẻ một version. Hệ quả có chủ đích: các model mang trường theo ngày (days_to_deadline,days_since_used) dedupe ở mức một version/ngày/learner dù engine chạy bao nhiêu lần trong ngày. Nhờ vậy "engine đã chạy" và "model đã đổi" là hai câu hỏi riêng, không bị trộn. learner_model_versionsgeneralize §15 cho cả 6 model:{model_kind, version, algorithm_version, generated_at, evidence_cutoff, state, trigger, engine_run_id, workflow_run_id, content_json}. Version cũ chuyểnstate='superseded', không xoá — đọc lại được lịch sử theo thời gian.- Ghi log không được làm hỏng nghiệp vụ (REQ-NFR-01): mọi lệnh ghi run/version bọc try/catch, hỏng thì
console.errorrồi đi tiếp;runModelsSafely()bọc cả chuỗi engine ở hot path (nộp bài, khai goal). - Trigger đã nối dây:
LearnerModelInitialized(WF-01) ·AssessmentCompleted(WF-03 chẩn đoán, nộp đề) ·GoalChanged/EXAM_RESCHEDULED(WF-15 khai mục tiêu, lịch thi) ·admin_recompute(chạy tay từ admin) ·cron:0 21 * * *(WF-17 Retention Refresh hằng ngày — SDD-017 §15). Practice từng câu chưa nối — evidence vẫn được ghi, model refresh ở lần completion/recompute kế tiếp. - Admin console (tiếng Anh toàn bộ, REQ-PLT-14): trang Models đi 4 cấp
Learner list → Learner → Model → Version → Content; trang Runs liệt kê workflow run (kèm step + engine chạy bên trong) và engine run. Mỗi cấp có URL riêng (#/models/:learnerId/:kind/:versionId). Cấp thứ 5 là so sánh — xem §19.1.
19.1 So sánh hai version liền nhau — mười tiêu chí cố định (REQ-INT-38, SRC-530)
Trang Version ở trên trả lời "bản này ghi gì". Câu người vận hành thật sự hỏi khi mở nó ra lại là câu khác: "so với lần chạy trước thì đổi gì, và vì sao". Trả lời bằng cách mở hai tab JSON rồi dò mắt là việc người làm được nhưng làm sai.
GET /v1/admin/model-versions/{versionId}/compare so version đó với version liền trước theo đúng mười tiêu chí, cố định cho mọi loại model:
| # | Tiêu chí | Trả lời |
|---|---|---|
| 1 | Sinh lúc nào | hai bản cách nhau bao lâu |
| 2 | Vì sao chạy | trigger + workflow |
| 3 | Bằng chứng tới đâu | evidence_cutoff — đầu vào thật |
| 4 | Thuật toán | algorithm_version |
| 5 | Sinh bằng gì | luật tất định hay qua AI Gateway |
| 6 | Độ tin của model | confidence + delta |
| 7 | Tóm tắt của model | từng trường của summary |
| 8 | Quy mô | số mục trong danh sách chính |
| 9 | Đổi ở mục nào | thêm / bớt / sửa từng mục |
| 10 | Vân tay nội dung | content_hash, kích thước, số đường dẫn lá khác nhau |
Bốn quyết định thiết kế, mỗi cái vá một cách nói dối khác nhau:
- Mười tiêu chí CỐ ĐỊNH, không đổi theo loại model. Bảng cố định thì lần đọc thứ hai đã quen mắt, và thiếu vắng cũng là thông tin: ô trống ở tiêu chí 6 nghĩa là model này không khai độ tin, chứ không phải màn hình quên hiện.
- Tiêu chí 3+4 gánh phần nặng nhất. Nếu nội dung đổi mà bằng chứng KHÔNG mới và thuật toán KHÔNG đổi, thì hoặc engine đọc một nguồn không ai khai, hoặc nó không tất định — vi phạm §20. Cả hai đều là lỗi, và cả hai đều vô hình nếu chỉ nhìn nội dung. Màn hình cảnh báo thẳng ở đúng dòng đó.
- "Liền nhau" = version LỚN NHẤT còn nhỏ hơn, không phải
version - 1. Engine chỉ ghi version mới khi nội dung đổi (xem §19), nên dãy số có lỗ và trừ một là trỏ vào khoảng trống. - Danh sách chính của mỗi loại model khai tường minh (Learner đọc tới cấp NODE, không dừng ở môn); loại chưa khai thì dò và hiện ra đường dẫn đã dò được. Dò rồi im lặng là cách nhanh nhất để màn hình so sánh nói dối: nó so một mảng phụ rồi báo "không có gì đổi".
Phép so đặt ở API (modules/admin/modelCompare.ts) chứ không ở màn hình, vì nó là một phép đo có thể sai: ở API thì test được bằng dữ liệu thật, viết trong React thì chỉ người nhìn mới biết đúng hay sai.
20. Luật tồn tại model & cập nhật liên tục (SRC-130, SRC-131)
Hai luật cứng, cao hơn mọi tối ưu:
(1) Learner nào cũng có ĐỦ mọi model — nội dung rỗng vẫn phải tồn tại. "Chưa build" và "đã build, chưa có gì" là hai câu trả lời khác nhau: cái đầu nói hệ chưa chạy, cái sau nói hệ đã nhìn và chưa có dữ liệu. Ô trống trong admin không phân biệt được hai điều đó. Vì vậy model rỗng phải nói rõ vì sao rỗng (empty: true, declared: false, kèm note), và:
- Tạo learner →
ensureAllModels()chạy nền ngay, learner có đủ model từ giây đầu tiên. - WF-01 (onboarding) và WF-04 (mỗi lần có bằng chứng) → chạy cả chuỗi 7 engine.
- WF-17 (đêm) → quét MỌI learner
status <> 'archived'(mặc định của learners làinvited, lọcactivesẽ bỏ sót đúng em vừa được mời) và dựng bù model còn thiếu, chỉ chạy engine của model thiếu chứ không tính lại thứ đã có. - Constraint Model có engine riêng (§/
modules/models/constraint.ts) để không còn model nào "chưa có engine".
(2) Learner Model bám sát learner theo từng bằng chứng, kể cả khi learner BỎ DỞ. Chờ tới lúc "nộp bài" là sai: bỏ dở nghĩa là sự kiện hoàn thành không bao giờ tới, trong khi bằng chứng đã có. Nên móc vào từng bước:
| Lúc nào | Chạy gì |
|---|---|
| Trả lời từng câu practice | Không dựng model. Chỉ ghi evidence + mastery + retention (chi phí cố định) — xem §21 |
| Khởi động/self-prediction | như trên |
| Hoàn thành hoặc trượt một Learning/Assessment Experience | như trên (ExperienceCompleted:completed|failed) |
| Nộp chẩn đoán / nộp đề / khai mục tiêu | cả chuỗi WF-04 (7 engine) |
| Hằng đêm | WF-17: retention + dựng bù model thiếu |
Đường theo-từng-câu cố tình nhẹ hơn WF-04 (chỉ 2 model đổi theo mastery) và chạy qua waitUntil — learner không phải chờ engine mới thấy kết quả câu vừa làm. Mỗi lượt vẫn để lại engine_runs làm bằng chứng (§19).
21. Chạy engine lúc nào — và vì sao KHÔNG chạy sau mỗi câu (SRC-138)
Bản đầu móc việc dựng lại Learner Model vào từng câu trả lời. Đúng về ý (model phải theo kịp learner) nhưng sai về chi phí, vì hai việc này khác hẳn nhau:
| Việc | Chi phí | Chạy khi nào |
|---|---|---|
| Ghi evidence + cập nhật mastery/retention của MỘT node | cố định, không phụ thuộc lịch sử | mỗi câu trả lời |
| Dựng lại Learner Model / Readiness | đọc lại toàn bộ lịch sử của learner | xem dưới |
Engine dựng model đọc learner_skill_state (mọi node) và tổng hợp learner_evidence (mọi bằng chứng từng có). Chi phí một lần chạy ≈ số evidence + số node. Chạy sau mỗi câu nghĩa là một phiên 10 câu trả tiền 10 lần cho cùng một kết quả, và giá mỗi câu tăng theo tổng số câu learner đã từng làm — học càng lâu càng đắt, đúng dạng chi phí không được để tồn tại trong hệ chạy nhiều năm.
Thêm một dấu hiệu lãng phí nhìn thấy ngay trong sổ chạy: Readiness của learner chưa có blueprint mục tiêu chạy xong luôn báo no change.
Chính sách hiện tại — dựng lại khi có thứ mới THẬT SỰ, không phải khi có thao tác:
| Thời điểm | Vì sao chọn điểm này |
|---|---|
| Kết thúc một Experience (xong hoặc trượt) | Ranh giới tự nhiên: một lần chạy thay cho 6–10 lần |
| Learner quay lại (mở Phòng Lab, bắt đầu phiên mới) | Bắt đúng ca "bỏ dở lần trước" mà không cần sự kiện thoát |
| Nộp chẩn đoán / nộp đề / khai mục tiêu | Bằng chứng mạnh hoặc deadline đổi → chạy cả chuỗi WF-04 |
| Job đêm WF-17 | Chốt chặn cuối cho learner bỏ dở và không quay lại |
Ba điểm giữa dùng chung một phép kiểm rẻ — learnerModelIsStale(): so MAX(last_evidence_at) trong learner_skill_state (bảng nhỏ, vài trăm dòng/learner) với generated_at của Learner Model mới nhất. Không có gì mới thì không chạy engine nào.
Vì sao không dựa vào sự kiện "learner đã thoát": đóng tab, mất mạng, hết pin đều không phát ra sự kiện nào. Suy ra từ trạng thái (model cũ hơn bằng chứng) thì luôn đúng, còn chờ sự kiện thì không.