Skip to content

SDD-013 — Coral: Content Plane (coral.nemo12.com) ​

Coral là giao diện vận hành của Learning Knowledge Factory: nơi staff quản lý toàn bộ học liệu — Knowledge Package, Learning Experience (LX), Assessment Experience (AX), LX Blueprint, Assessment Blueprint, các ngân hàng đề thi (generated + đề thật), Item Bank, cùng Rubrics/Quality để đánh giá hầu hết artifact. Coral thay thế data.nemo12.com (Q-091 ✅ 2026-08-14 — ngừng sử dụng data.nemo12.com).

Tham khảo mori.chuyenchon.com (CC-MORI-01..05, FEAT-039..043) về phạm vi — nhưng không bắt chước cách làm: Coral sửa tận gốc các lỗi legacy (content-as-migrations RISK-014, cron-poll thay Queues RISK-018, 4 hệ rubric song song RISK-005).

1. Vị trí & nguyên tắc ​

  1. Factory, không phải CMS (SDD-003): vòng lặp Model → Build → Review → Score → Detect Problems → Improve → Publish → Observe Learners ↺. Coral là bề mặt điều khiển + quan sát của vòng lặp đó.
  2. Registry là nguồn sự thật (SDD-004 §3): Coral thao tác trên các registry D1 + content R2 hiện có — không tạo kho dữ liệu riêng, không copy nội dung vào app.
  3. Content là data qua API/pipeline — cấm ship content bằng migration SQL (RISK-014); cấm sửa production trực tiếp — mọi thay đổi qua candidate version + review (SDD-003 §10).
  4. Mọi pipeline dài chạy bằng Cloudflare Workflows + Queues (§8) — cấm cron-poll bảng D1 (RISK-018).
  5. Hai nghĩa "blueprint": registry §3 quản lý content blueprint (LX Blueprint, Assessment Blueprint — nghĩa SDD-004 §5–8). Goal blueprint (SDD-002 §12, specialized-chuyen) thuộc Learner Intelligence, không nằm trong Coral.
  6. Truy cập: Cloudflare Access chỉ cho dac2205@gmail.com (Q-092 ✅ 2026-08-14) + API vẫn kiểm tra session role staff/admin (REQ-ACC-05) — defense-in-depth; email nằm trong Access policy (config hạ tầng), không hardcode trong code (RISK-013).

2. Catalog artifact Coral quản lý (REQ-CNT-01..03, 05) ​

ArtifactRegistryThiết kế gốcThao tác trong Coral
Knowledge Node / Skillknowledge_nodes, skillsSDD-003 §2-3browse graph, sửa metadata, gaps
Knowledge Packagelearning_packagesSDD-004 §3CRUD, gắn node/LX, manifest
Learning Experiencelearning_experiences + R2SDD-004 §5-8CRUD, version, preview, evidence_contract
Assessment Experiencelearning_experiences (subtype)SDD-004 §5-8CRUD, gắn blueprint, xem instance stats
LX Blueprint / Assessment Blueprintcontent_blueprints (§3, migration 0016)SDD-013CRUD, version, chạy generation run
Item BankitemsSDD-004 §9browse, sửa, misconception (REQ-CNT-03)
Exams (generated + authentic)exams, exam_questions, exam_problems, problem_typesSDD-011 §3, SDD-012§5 — ingestion, tách bài, lời giải, publish
Labslabs registrySDD-010 §3browse, config version, quality
Rubrics / Qualityrubrics, quality_evaluationsSDD-003 §6-9§6 — registry, review queue

Mọi artifact có: lifecycle Draft → Review → Published → Deprecated (SDD-003 §10), version + provenance (SDD-003 §13), quality profile (SDD-003 §6).

3. Blueprint registry & generation runs (REQ-CNT-05, 09, 11) ​

✍️ 2026-08-14: tên bảng chốt là content_blueprints (bản đầu SDD này viết blueprints, nhưng tên đó đã thuộc goal blueprint của SDD-002 §12 từ migration 0002 — đúng cảnh báo "hai nghĩa blueprint" ở §1). Registry mở ở migration 0016_content_blueprints.sql; seed 14 blueprint đầu qua scripts/seed-blueprints.mjs + data workers/api/src/modules/coral/blueprints-seed.json: 3 AX nền (exam-problem-v1, diagnostic-mcq-v1, writing-rubric-band-v1), 8 exam blueprint Toán giữa/cuối HK1 lớp 6-9 (từ nghiên cứu đề thật 2023-2025, khung TT22: TN 12 câu 3đ + TL 7đ, 90 phút; spec có topics + thang điểm từng bài + nguồn), 3 LX (micro-v1, practice-set-v1, lab-config-v1). API GET/PATCH /v1/coral/blueprints* (staff/admin, sửa tay từng phần trong tab Blueprint của Coral; sửa spec/target thì version +1).

text
content_blueprints (
  id, kind[lx|assessment],
  name_vi, description,
  target_selector_json,   -- phạm vi áp dụng: subject/area/node list/dạng bài
  spec_json,              -- mẫu: pedagogy, cấu trúc, item mix, measurement layers
                          --   (vd assessment: answer+confidence+justification — SDD-012 §4)
  version, status[draft|active|deprecated],
  created_by, created_at
)
generation_runs (
  id, blueprint_id → blueprints, blueprint_version,
  input_json,             -- target set đã resolve
  status[queued|running|review|done|failed],
  progress_done, progress_total,
  stats_json,             -- sinh bao nhiêu, pass gate bao nhiêu, cost
  started_at, finished_at
)
  • LX Blueprint sinh Learning Experience (micro/practice/lab-config…); Assessment Blueprint định nghĩa cách đo của AX (exam-problem-v1 của SDD-012 §4 là một bản ghi trong registry này).
  • Flow: blueprint + targets → generation run (WF-14, §8) → candidates (Draft) → Quality Engine (SDD-003 §7-8) → Review queue (§6) → Published.
  • Mọi artifact sinh ra lưu provenance {blueprint_id, blueprint_version, model, prompt_version, input_ref} (QG-010, SDD-003 §13) — kế thừa ý tưởng mori source-provenance (FEAT-043), làm trong registry thống nhất.

4. Dashboard & tổng quan (REQ-CNT-02, 07) ​

Ba màn, trả lời đúng câu "đang có bao nhiêu, tốt đến đâu, đang tiến bộ thế nào":

  1. Tổng quan: đếm artifact theo loại × lifecycle status; quality score trung bình theo môn/area; coverage gaps (node thiếu item/LX/AX/lab — REQ-CNT-02); exam bank: số đề/bài theo hệ thi × năm × vòng, % bài đã có lời giải reviewed.
  2. Tiến độ (status việc tạo và nâng cấp dần về số lượng và chất lượng): time-series theo tuần — artifact tạo mới / published / nâng version; số generation run + % pass gate; điểm quality trung bình theo thời gian (kỳ vọng đi lên).
  3. Hoạt động: runs đang chạy (WF-07/13/14) + progress; queue depth + DLQ (QG-009); review backlog (số candidate chờ human review, tuổi già nhất).

5. Quản lý ngân hàng đề thi (REQ-CNT-06) ​

  • Danh sách đề theo exam_system × năm × vòng × môn (generated lẫn authentic); trạng thái từng đề (draft/review/published, % bài có lời giải).
  • Ingestion đề thật = UI của WF-13 (SDD-012 §7): upload/paste + nguồn → tách bài → biên tập statement (markdown + KaTeX + hình R2) → gắn problem_types + node links → soạn lời giải 3 tầng (AI draft → AI multi-evaluator — AI-first SRC-035) → deterministic checks QG-011 → publish.
  • Checklist publish QG-011 hiển thị ngay trong editor (provenance đủ, ≥1 node link, ≥1 dạng bài, lời giải reviewed) — không đạt thì nút publish disabled kèm lý do.

6. Quản lý Quality Gates & Rubrics (REQ-CNT-08) ​

  • Rubric registry — một nguồn duy nhất (RISK-005), gốc CC-QAF-1.0 (SDD-003 §7); CRUD rubric + version; mapping rubric ↔ artifact type (đánh giá "hầu hết artifact": node, package, LX, AX, item, exam problem, lab, solution).
  • Quality profile per artifact: 8 chiều (SDD-003 §6) + findings của Multi-Evaluator (SDD-003 §8); variance cao → cờ Review.
  • Review queue (AI-first — SRC-035): AI multi-evaluator là gate mặc định; hàng đợi chỉ chứa item variance cao/bị learner report — owner spot-check approve/needs-work/reject, có audit. Human bắt buộc duy nhất: moderation an toàn trẻ em (QG-008).
  • QG catalog view: đọc QG-001..011 từ docs làm reference; các gate nội dung (QG-006, QG-011) hiển thị trạng thái checks chạy trong plane.

7. App, worker & auth (REQ-CNT-04) ​

  • App: apps/coral — Vite + React SPA, design system chung (DS-001). Worker: nemo12-coral, route coral.nemo12.com (zone nemo12.com).
  • Access: Cloudflare Access app "Nemo12 Coral" — policy allow duy nhất email dac2205@gmail.com (Q-092 ✅). API routes /v1/content/*, /v1/coral/* vẫn gate session staff/admin (pattern admin.nemo12.com).
  • data.nemo12.com ngừng sử dụng (Q-091 ✅): mã nguồn apps/data đã gỡ khỏi repo sau khi làm seed cho apps/coral; worker nemo12-data không phát triển tiếp; gỡ route data.nemo12.com khi Coral đạt parity browse (Q-093 ✍️).
  • API: workers/api — giữ module content (browse/coverage hiện có) + module mới coral: blueprints CRUD, generation runs, dashboard aggregates, review queue, rubric registry. OpenAPI từ router (QG-003).

8. Cloudflare Workflows & Queues (REQ-CNT-10) ​

PipelineLoạiMô tả
WF-07 Content Quality loopWorkflow content-qualityevaluate → findings → priority → improvement candidate → re-evaluate (SDD-003 §7-11)
WF-13 Real exam ingestionWorkflow exam-ingestcác bước SDD-012 §7, có human-review step (chờ approve trong Coral)
WF-14 Generation runWorkflow content-generationresolve targets → fan-out generate qua Queue → collect → evaluate → review queue
  • Queues: nemo12-events (hiện có) cho domain events; queue mới nemo12-content (+ DLQ) cho task generation/evaluation fan-out. Consumer idempotent, DLQ bắt buộc (QG-009, SDD-006 §5).
  • Trạng thái mọi run ghi ở generation_runs/bảng run tương ứng — Coral đọc để hiển thị (§4.3); không cron-poll (RISK-018); Workflow tự cập nhật status qua step.
  • AI calls trong generation/evaluation đi qua AI Gateway nemo12 (QG-010, RISK-022).

9. Data Quality — rà soát unit/lesson (REQ-CNT-16, SRC-446) ​

Người phụ trách data vào Coral → chọn môn → tab Data Quality. Tổng quan là hub chỉ việc (SRC-447): card "Việc đang chờ" tính từ dữ liệu thật, bấm là nhảy thẳng vào việc — người mới không phải lò mò các tab. Ba mảnh:

  1. Hàng đợi có lý do (GET /v1/coral/dq/queue): mỗi lesson một điểm ưu tiên deterministic, mỗi cộng điểm kèm một câu lý do — đang ẩn +50 · chưa rà +40 · lần trước major +25 · có câu nguồn llama +20 · học sinh đúng <40% (≥5 lượt) +20 · <10 câu +15 · lần rà cuối >60 ngày +10. Ưu tiên phải giải thích được thì người rà mới tin hàng đợi thay vì tự dò 300 dòng.
  2. Học thử không để vết (GET /v1/coral/dq/lesson): endpoint trả toàn bộ câu KÈM đáp án + misconception; người rà bấm chọn ngay trong Coral, đúng/sai hiện client-side. Không có POST evidence nào — không chạm Learner Model của ai by construction, không cần cờ "shadow mode" dễ quên.
  3. Đánh dấu 1-2 cú bấm (POST /v1/coral/dq/mark): verdict ok|minor|major|blocker ngay trên dòng (unit, lesson, hoặc từng câu); verdict xấu mở thêm chip tag (các lỗi đúc từ đợt review 1.317 câu: sai đáp án, ngoài chương trình, khó hơn lớp…) + note tuỳ chọn. Mỗi confirm là một dòng experience_reviews append-only (migration 0049, dùng CHUNG với khu rà của mentor trong Dolphin — SRC-206): một người rà nhiều lần, nhiều người rà một bài, lịch sử giữ đủ.

Ẩn khi nghiêm trọng: lesson blocker → skill_nodes.hidden_at + lý do; practice-start của learn chặn với thông điệp "đang bảo trì nội dung"; rà lại ok thì mở. Câu blocker → items.status='candidate' — rút khỏi lưu thông bằng chính cơ chế status sẵn có (AS-10.4.1), không thêm cờ mới.

Nâng cấp tiếp (vòng khép kín): verdict ≠ ok + tags + note = fixlist máy đọc được. Vòng sửa: fixlist → sinh lại bằng pipeline candidate (§3, kèm Claude review bắt buộc REQ-KNW-17) với tags làm chỉ dẫn ("sai đáp án" → giải lại từ đầu; "khó hơn lớp" → hạ cấp độ) → publish → review_status quay về unreviewed để được rà lại — người rà chỉ xác nhận, không phải tự sửa từng câu.

Trace ​

REQSection
REQ-CNT-01 (browse registry + Item Bank + Exams)§2, §5
REQ-CNT-02 (coverage/quality dashboard)§4
REQ-CNT-03 (chi tiết item + LX gắn node)§2
REQ-CNT-04 (Coral portal + Access, thay data.nemo12.com)§1, §7
REQ-CNT-05 (quản lý Package/LX/AX/Blueprints)§2, §3
REQ-CNT-06 (quản lý ngân hàng đề)§5
REQ-CNT-07 (dashboard tổng quan + tiến độ)§4
REQ-CNT-08 (Quality Gates & Rubrics)§6
REQ-CNT-09 (blueprint → generation runs)§3
REQ-CNT-10 (Workflows + Queues)§8
REQ-CNT-11 (provenance + content intelligence)§3

Liên quan: SDD-003 (factory, quality, lifecycle, provenance) · SDD-004 (registry, LX/AX, Item Bank) · SDD-010 (labs) · SDD-011 §3 (exams generated) · SDD-012 (đề thật, WF-13, QG-011) · Q-091..Q-094 · CC-MORI-01..05.

Tab Prerequisite graph (SRC-396) ​

Coral quản dữ liệu chương trình, nên unit_prereqs thuộc về đây chứ không phải Admin (Admin quản người: role, giao mentor, thống kê tài khoản).

Tab bày từng Unit và 1-2 Unit đứng trên nó kèm câu lý do, đánh dấu riêng cạnh xuyên Package. Ba hành động: Giữ (reviewed), Sửa lý do (edited), Bỏ cạnh này (rejected, bỏ được thì dùng lại được). Không có nút "Duyệt" — cạnh đã chạy từ lúc sinh ra; đặt một nút duyệt ở đây sẽ nói dối về việc hệ đang chờ ai đó.

API: GET /v1/coral/unit-prereqs, PATCH /v1/coral/unit-prereqs (staff/admin).

Hai tab, hai câu hỏi khác nhau — "Độ phủ" và "Khoá học" (SRC-693) ​

Chúng trùng nhau đúng một chữ "Unit", còn lại là hai cây khác gốc. Ghi ra đây vì đã có một lần đề xuất gộp chúng chỉ vì cái tên:

Tab Độ phủTab Khoá học
Bảngskill_nodes · labs · itemscurriculum_courses/units/lessons
Cây gìbản đồ tri thức (strand → module → unit)chương trình học (course → unit → lesson)
Trả lời"unit nào chưa có lab, chỗ nào còn mỏng""Big Idea của unit này viết đã đúng chưa"
Việcsoát độ phủsoát và sửa chữ nghĩa

Tab Độ phủ trước 2026-09-08 mang nhãn "Unit" — chính cái nhãn ấy sinh ra đề xuất gộp. Nhãn đổi, slug URL vẫn là unit: link đã gửi đi thì không thu hồi được.

Tab Khoá học — một khu vực cho cả Nemo lẫn Marlins (SRC-680) ​

Cấu trúc khoá của học sinh và khoá của bố mẹ y hệt nhau: Course → Unit (Big Idea + Essential Question + key concept) → Lesson (Guiding Question). Nên trong Coral chúng dùng một tab, và audience (nemo | marlin) chỉ là một công tắc ở đầu trang, không phải một nhánh code. Hai màn hình cho một cấu trúc nghĩa là mọi luật kiểm ("unit nào chưa có Big Idea") phải viết hai lần, và lần thứ hai sẽ lệch.

Trước SRC-680 khoá của bố mẹ nằm cứng trong apps/marlins/src/parentCourses.ts: sửa một dấu phẩy phải build lại và deploy, và Coral không có đường nào chạm tới. Rà soát nội dung khoá học vì thế chỉ làm được cho một nửa số khoá.

Lưu ở đâu ​

Một cây bảng, một cột phân loại — không phải hai bộ bảng song song (migration 0203):

Cột / bảng
Phân loạicurriculum_courses.audience, course_group (5 chặng hành trình của bố mẹ), display_seq
Nỗi đau bố mẹcurriculum_course_pains
Việc cần làm (JTBD)curriculum_course_specs.purpose_vi/_en + jtbd_json
Key concept có ví dụcurriculum_unit_concepts.example_vi/_en

(level, seq) với audience='marlin' chỉ còn là ô lưu cho chỉ mục UNIQUE cũ; thứ tự đọc thật nằm ở display_seq. Không nới CHECK vì SQLite phải dựng lại cả bảng cha, và D1 chặn — đã thử ở SRC-642/643.

API (staff/admin) ​

RouteViệc
GET /v1/coral/courses?audience=danh sách khoá kèm gaps — số ô người soạn còn để trống
GET /v1/coral/courses/{id}cả cây Course → Unit → Lesson
PATCH /v1/coral/course-units/{unitId}tên Unit, Big Idea, Essential Question (song ngữ)
PATCH /v1/coral/course-lessons/{lessonId}tên Lesson, Guiding Question (song ngữ)
PATCH /v1/coral/unit-conceptskey concept theo (unit_id, seq)
PATCH /v1/coral/courses/{id}tên khoá, câu "việc cần làm" (song ngữ)
PUT /v1/coral/courses/{id}/pains · /outcomesthay TRỌN danh sách

Nỗi đau và outcome gửi lên trọn danh sách chứ không sửa từng dòng: hai bảng khoá theo (course_id, seq), mà việc thật của người soạn là "bỏ câu thứ hai đi" — tức đánh số lại cả danh sách. Sửa từng dòng thì client phải tự dựng lại chuỗi seq, và đó đúng chỗ để lệch.

Bộ lọc môn khớp cả tiền tố (subject_id = 'ielts' hoặc LIKE 'ielts\_%' ESCAPE): Coral chọn môn theo school ("ielts") còn tầng Course tách IELTS thành năm subject_id. Khớp đúng chuỗi thì chọn Squid → IELTS ra danh sách rỗng trong khi D1 có 24 khoá.

Đây là lần đầu Coral ghi vào nội dung khoá học — trước đó Coral chỉ soi chất lượng. Nên mọi lần sửa đều ghi audit_log, và ô để trống ghi thành NULL để câu đếm chỗ hổng không lệch.

Thân bài lesson (đợt 3) ​

Bốn phần của một bài: câu chuyện mở bài (SCQA) · ví dụ · chỗ dễ hiểu lầm · 20 câu trắc nghiệm. Ba phần đầu dùng lại bảng có sẵn của tầng Course; phần thứ tư cần bảng mới (migration 0204):

PhầnBảng
SCQAcurriculum_lesson_stories (nhịp thứ tư answer → resolution)
Ví dụcurriculum_lesson_examples kind example (title → text, body → why)
Chỗ dễ hiểu lầmcurriculum_lesson_examples kind near_miss (looksLike → text, whyWrong → why)
Trắc nghiệmcurriculum_lesson_quiz + curriculum_lesson_quiz_choices
Performance Taskcurriculum_unit_tasks + curriculum_task_criteria

Trắc nghiệm không nhét vào items: items là ngân hàng cho học sinh, gắn với knowledge node, do máy sinh và người duyệt. Câu ở đây do chủ dự án viết, gắn với lesson, đọc cùng bài.

GET /v1/coral/lesson-body/{lessonId} tải riêng, không đi kèm cây khoá: 162 lesson × 20 câu × 4 phương án × 2 thứ tiếng là vài megabyte cho một màn hình chỉ để liệt kê. Ba route sửa: .../story, .../examples/{kind}/{seq}, .../quiz/{seq}.

Đề, đáp án và phương án của một câu đi cùng một lượt: answer là chỉ số vào danh sách phương án, tách hai lượt thì có khoảnh khắc chỉ số trỏ vào phương án cũ — và nếu lượt thứ hai không tới thì khoảnh khắc ấy thành vĩnh viễn, im lặng.

Không ô nào được chứa dấu |. Thân bài kéo ngược ra parentLessons/*.ts dưới dạng chuỗi "tiếng Việt | English", nên một dấu | làm bi() ném lỗi lúc nạp module: trang khoá học của bố mẹ trắng màn hình. Chặn hai lớp — zod ở API, và pull-parent-bodies.mjs dừng trước khi ghi file.

Vòng đời một sửa đổi ​

D1 là nguồn thật; file trong apps/marlins là bản sinh ra:

  1. Sửa trong Coral → ghi thẳng D1.
  2. node scripts/curriculum/pull-parent-courses.mjs → chép ngược ra parentCoursesData.ts.
  3. node scripts/curriculum/gen-parent-courses.mjs > scripts/seed-parent-courses.sql → commit.

Thân bài đi đường song song: pull-parent-bodies.mjs rồi gen-parent-bodies.mjs (ghi thẳng ra bốn lô seed-parent-bodies-*.sql).

Vì sao Marlins không gọi API lúc chạy: trang khoá học của bố mẹ hiện ra tức thì, và parentCompetency.ts cùng parentLessons/ đang khớp với khung này theo chỉ số unit/lesson. Đánh đổi đã chọn (2026-09-05): một sửa đổi phải chờ một vòng deploy. Cổng scripts/check-parent-courses.mjs (trong npm run check:code) chắn việc hai file lệch nhau.