Skip to content

Rà soát content-ops 28.08.2026 ​

Rà soát đối kháng toàn phần hệ tự sinh và tự nâng chất lượng nội dung (ba Cloudflare Workflow + tầng authoring + bộ kiểm + chuẩn @conan/course-standard + docs), đúng ngày hệ ra đời, trước khi nó chạy đủ lâu để các lỗi nền kịp làm bẩn dữ liệu.

Phương pháp: 6 reviewer độc lập theo 6 lát cắt (ngữ nghĩa Workflows · SQL/D1 · prompt & sư phạm · tầng lọc/assert · bảo mật & chi phí · drift docs-mã), mỗi lát một reviewer đối kháng đọc lại từng finding để gỡ; 21 nghi vấn của reviewer chính được kiểm chứng riêng; một critic soát độ phủ (đường sinh→hiển thị, rollback, observability, vòng phản hồi người học). Chỉ giữ finding có bằng chứng file:line.

Kết quả: 57 finding qua kiểm chứng (5 blocker · 31 major · 21 minor/upgrade). Toàn bộ blocker và phần lớn major đã sửa cùng ngày; phần còn lại nằm ở [Roadmap](#roadmap-, -ghi-nhan-chua-lam-đot-nay).

Bản vá sau đó được đưa qua đợt review đối kháng thứ hai (3 reviewer + 3 verifier trên chính diff), bắt thêm 15 lỗi trong bản vá (đều đã sửa), đáng kể nhất: luật "câu đầu có tên riêng" vừa hồi sinh lại bác oan chính câu mở đúng bằng tên nhân vật; [Oo]ng bắt oan "làm xong A"; audit chạy no-retell thiếu ignore-set cast nên phạt SCQA đã qua cửa sinh đúng luật; id con cắt 8 ký tự làm hai target trùng tiền tố sập về một; comparator ưu tiên bị đảo (visible đè level); và mọi đường đọc/ghi cột owner_* nay tự rơi về lược đồ cũ khi migration 20260828j chưa kịp áp, vòng cron không chết vì thứ tự deploy.

Ba phát hiện đắt nhất ​

1. So sánh model: mục đích tồn tại của hệ, toàn dữ liệu bịa ​

Ba lỗi độc lập cùng đánh vào một chỗ:

LỗiHệ quả
Orchestrator xoay vòng model, ghi vào course_content_runs.model, nhưng không hàm authoring nào nhận model, callAI luôn chạy llama-70bDB nói "deepseek-r1 sinh bài này", sự thật là llama-70b. /ops/models so sánh những thứ chưa từng chạy
Bước score lọc finding theo f.target_id === targetId, nhưng finding trỏ vào dòng con (câu hỏi, ví dụ) còn target của job là concept/unitmine luôn rỗng → mọi run được 1.0 bất kể chất lượng
runId = crypto.randomUUID() sinh ngoài step, engine replay run() từ đầu khi resume, UUID đổi, UPDATE đóng run trượt hếtRun dài/retry (đúng những run đáng xem nhất) treo running vĩnh viễn

Đã sửa: model xuyên suốt mọi hàm authoring xuống callAI; finding mang owner_kind/owner_id (migration 20260828j) và score chấm theo owner với mẫu số là số item của chính artefact; runId = event.instanceId; score hỏng → NULL chứ không đánh fail một run đã sinh xong.

2. Cron chéo: mọi job 6 tiếng lặng lẽ chạy dày gấp đôi ​

Một hàm scheduled() chạy cho CẢ HAI cron (17 */6 và 41 */3) mà không rẽ theo controller.cron, thêm cron 3 tiếng cho course-ops đồng nghĩa reflection, product-reward, book-heartbeat, mọi concept-evolution và mentor-model chạy mỗi 3 tiếng thay vì 6. Kèm theo: một lỗi tạo instance ở đầu danh sách làm rơi toàn bộ phần còn lại của lượt cron.

Đã sửa: rẽ theo controller.cron; mỗi block tự lo lỗi của mình (log rồi đi tiếp).

3. Sinh lại ngân hàng giữa lúc người học đang làm quiz → 500, mất trọn lượt làm ​

Lượt quiz chốt chỉ lưu id câu hỏi; máy sinh nền sinh lại ngân hàng bằng DELETE+INSERT (id mới). Người học start lúc 8:00, orchestrator regen lúc 8:05, submit lúc 8:10 → byId.get(itemId)! nổ TypeError → 500, không nộp lại được. Cùng lúc, phát hiện đáp án không hề được xáo dù assessment.md tuyên bố có.

Đã sửa: items_json giờ là snapshot trọn câu hỏi (đề + đáp án ĐÃ xáo + ô đúng sau xáo), vừa miễn nhiễm với regen, vừa làm claim "xáo đáp án mỗi lượt" thành sự thật; run cũ dạng mảng id được chấm theo đường cũ nhưng bỏ qua câu đã mất thay vì nổ.

Blocker còn lại đã sửa ​

#FindingSửa
B4/ops/* chỉ sau auth member thường: tài khoản học thử cũng bật được mẻ sinh với budget không trần, model không validate (kể cả __proto__ lọt qua lookup)Cả cụm sau Cloudflare Access; budget kẹp 1–24; model kiểm bằng hasOwnProperty; slugs chỉ nhận chuỗi
B5engine.ts lọc leaksAnswer TRƯỚC khi bilingualizeDiagnosticPrompt chèn tên tiếng Việt của khái niệm (thường chính là đáp án) vào đề, tự tạo lại đúng cái leak vừa lọcRewrite trước, lọc sau (cả diagnostic lẫn checkpoint)

Major đã sửa (chọn lọc) ​

Orchestrator & Workflows

  • Id con chứa Date.now() → "already exists" không bao giờ khớp: trùng target trong một lượt là sinh đôi, retry của step là sinh lại cả mẻ. → Id dựng từ event.instanceId, thêm dedup theo kind:target.
  • sleep 20 phút < trần retry của con (~47–63 phút) → audit chấm nội dung đang ghi dở, finding trỏ vào dòng sắp bị xoá. → 65 phút.
  • Blocker không lọc theo course_slugs; finding mồ côi (artefact đã đổi id) chiếm đầu hàng đợi LIMIT vĩnh viễn. → Lọc theo scope; mồ côi tự đánh resolved.
  • unit-spec được giao cho unit chưa có khái niệm → fail lặp mỗi 3 tiếng, đốt budget. → Chỉ pick unit có concept.
  • Không bao giờ sinh unit-big-idea/unit-bilingual (mức 3 theo chuẩn v2) và không thấy bài mất summative. → Ba counter mới trong missingCounts + ba pick mới; mức 3 gác big-idea quiz, mức 4 gác cả hai ngân hàng.
  • Ưu tiên "khoá hiển thị trước khoá ẩn" có trong docs nhưng không có trong mã. → Cột visible (legacy_courses.is_active) + sort.
  • open-run không idempotent khi step retry; persist-findings một batch không chặn trên; course_quality_reports ghi trùng khi replay. → ON CONFLICT, chia lô 80, id theo instanceId.

Tầng lọc & prompt

  • Regex dựng từ tên khái niệm trong DB không escape + \b ASCII: tên chứa ký tự đặc biệt làm crash cả lượt audit/assert, tên có dấu làm luật tắt im lặng. → wordBound() (escape + lookaround Unicode) dùng chung.
  • countSentences đếm dấu chấm ngăn nghìn ("1.000.000 đồng" = +3 câu ảo); nhãn [A-E] với /i bắt oan "chị e rằng"; luật "câu đầu có tên riêng" chết im vì [A-ZÀ-Ỹ] phủ cả chữ thường có dấu. → Sửa cả ba trong content-rules.ts.
  • Người kiểm đáp án là CHÍNH model ra đề, temperature 0.5; chỉ số trả về không chặn trên (đánh số 1-based là loại oan hàng loạt); lượt kiểm hỏng thì giữ lô không log. → Kiểm chéo VERIFY_PARTNER + temp 0; schema kẹp 0–9; trả lời dưới nửa số câu coi như hỏng; log mọi lô bỏ kiểm.
  • authorQuizBanks đo pool TRƯỚC kiểm (loại ~40%) → ngân hàng 8–11 câu được tính "đủ mức 4" vĩnh viễn. → Vòng sinh–kiểm–bù tối đa 5 lượt đo bằng số câu ĐÃ QUA kiểm, sàn 12/ngân hàng (minAccept v2.1).
  • course_metadata (anti-scope, thuật ngữ cấm mượn, scenes, giọng văn) sinh ra để làm hàng rào nhưng không máy sinh nào đọc; tham số banned của blueprint không call site nào truyền. → courseAuthoringContext() bơm fence vào mọi prompt; scenes của khoá đè danh sách công ty chung; blueprint nhận danh sách cấm + retry + trần token 10000.
  • evaluateUnitTask bỏ qua trọng số tiêu chí không khớp tên → bài thiếu hẳn một phần vẫn qua cổng 80%. → Mọi tiêu chí vào mẫu số, không khớp = mức 0; temp 0.2.
  • authorLessonPack (đã bị basics+quiz-banks thay) vẫn sống: chạy là xoá CẢ HAI ngân hàng rồi ghi 20 câu không cột bank → tất cả rơi về summative, formative trống, tụt mức 4. → Gỡ hẳn hàm + endpoint.
  • Ngân hàng unit 25 câu (đường legacy nhiều người học gặp nhất) chỉ lọc 1/4 lớp. → Đủ bốn lớp.
  • assertJourneyInvariants quét JSON.stringify nên khái niệm trùng tên key schema (Option, Question, Select…) bị bắt oan vĩnh viễn. → Chỉ gom giá trị chuỗi.
  • collectLog: true cho cả lượt chấm reflection/bài nộp/inquiry → log gateway thành kho lưu thứ cấp dữ liệu người học. → Tắt log ba engine đó.

Bộ kiểm (auditCourse)

  • Chỉ cài 6/9 luật chuẩn v2, bỏ trắng SCQA đã lưu, ngân hàng unit, thư viện Big Idea, outcome cấp lesson; hai luật có trong mã lại vắng trong chuẩn; ba helper chép tay lệch chuẩn hoá với cửa sinh; JSON.parse không guard làm một dòng hỏng giết cả lượt audit. → Viết lại: dùng chung content-rules.ts/diagnostic-language.ts, phủ mọi bảng, guard mọi parse, thêm no-foreign-chars, foreign-term-borrowed, bank-missing-summative, và suspect-answer-key, vòng phản hồi từ dữ liệu trả lời thật (≥5 lượt, >60% dồn một ô sai).

Docs & chuẩn: 16 chỗ drift đã đồng bộ ​

Đáng kể nhất: index.md còn dạy cơ chế 3-vòng-95% và thư viện ~20 câu (đã bị hai-cổng/hai-ngân-hàng thay); course-levels.md coi TEST_MODE là đường duy nhất; assessment.md nói lô kiểm 5 (mã: 10), "bỏ mọi câu bất đồng" (mã: hai lần bất đồng cùng phương án), "6 lượt mỗi ngân hàng" (mã: pool chung), "xáo đáp án" (mã: chưa, nay đã); content-ops.md khoe 5 model xoay vòng (mã: 3, nay 4 + llama-8b đứng ngoài có chủ đích); README nói ngưỡng PT thuộc profile nhưng chuẩn hardcode 0.8.

Chuẩn ra bản v2.1 (file mới, v2.json không đụng, đúng chính sách "đã công bố thì không sửa tại chỗ"): +6 luật chất lượng từ lỗi thật, bannedPhrases máy đọc được cho outcome-observable (một nguồn cho cả sinh lẫn kiểm), đặc tả lớp kiểm đáp án (lô 10 · kiểm chéo model · hai lần bất đồng), minAccept: 12, ba counter xếp mức mới chỉ gác khi có đếm, repo cũ không tụt mức oan.

Roadmap: ghi nhận, chưa làm đợt này ​

ViệcVì sao đáng làmCỡ
Versioning nội dung + chấm-trước-hiển-thịMáy sinh vẫn ghi đè thẳng bảng đang phục vụ người học; lượt sinh tệ thay mất bản tốt, không đường rollbackKiến trúc, cần bảng version cho họ course_*
Entitlement + school-scope + unit-lock cho nhóm endpoint học liệu mớilearn-overview/lesson-quiz/task/inquiry/reflection bỏ qua các cổng mà nhánh 5E cũ enforce, gọi thẳng API là vượtNgoài phạm vi content-ops, đã tách task riêng
Rate-limit các endpoint AI của người họcVòng inquiry→view tạo được lượt gọi AI không giới hạnĐã tách task riêng
Lớp thẩm định chuyên môn bằng model mạnhBộ kiểm bắt lỗi hình thức + đáp án, chưa bắt được giảng sai tinh viMột workflow mới, dùng deepseek-r1/model ngoài
Dọn finding resolved + progress trỏ item đã xoáBảng chỉ lớn dần; course_material_progress đếm cả dòng mồ côi sau regenNhỏ, làm cùng đợt versioning
Đo thật retries.limit của Workflows (3 hay 4 lượt)Tài liệu Cloudflare tự mâu thuẫn; ảnh hưởng ngân sách thời gianLog attempt một đợt rồi chốt số

Bài học nạp lại vào triết lý ​

  1. Đường ống đo chất lượng phải tự chứng minh được nó đang đo, ba lỗi độc lập cùng làm cột score thành 1.0 mà không ai hay; một truy vấn HAVING score < 1 ngày đầu đã lộ ngay.
  2. Cùng một luật, một cài đặt, luật chép tay ở hai cửa (sinh/kiểm) là hai luật khác nhau chờ ngày cãi nhau; nay văn phong SCQA, cụm outcome rỗng, ngưỡng bài viết đều một nguồn.
  3. Metadata sinh ra để dùng, không phải để có, hàng rào anti-drift nằm trong DB tám ngày mà không prompt nào đọc.
  4. Dữ liệu người học là lớp kiểm cuối và rẻ nhất, mọi lớp kiểm lúc sinh đều thua được; đa số người thật cùng chọn một ô sai là bằng chứng mạnh hơn mọi model.
  5. Mã ngoài step.do là mã chạy lại, mọi giá trị định danh của một run phải bám vào event.instanceId.

Tài liệu nội bộ nền tảng Conan School.