SDD-056 · Trình duyệt và sửa nội dung AI Teen trong Dolphin
Chỉ đạo chủ dự án 07.10.2026 (SRC-1284): dolphin.nemo12.com có một khu liệt kê mọi course AI Teen, xuống unit, lesson, Activity, Bước và cổng đọc. Mọi trang chi tiết chỉ đọc mặc định; nút "Sửa" vào chế độ sửa, "Lưu" và "Huỷ" hiện đúng ô ấy (bảng chế độ của Guided Journey §2c). Mentor đủ quyền sửa được tiêu đề, câu hỏi, outcomes, concept, phần đọc, câu cổng, câu kiểm và chỉ báo.
Các PR dựng phải theo skill content-editor (.claude/skills/content-editor/SKILL.md), skill ấy trỏ về đây. Trạng thái 08.10.2026: M1-M4 xong, M5 dựng phần lõi (§11b, §11c): lịch sử version và khôi phục về nháp, phát hành theo course (staff/admin), rollback (admin), learn đọc bản phát hành lúc chạy với bản chụp trong bundle làm dự phòng. Bản phát hành đầu tiên là chính nội dung TS hôm nay (seed), nên learner chưa thấy gì đổi cho tới lần phát hành đầu từ Dolphin.
1. Vấn đề: nội dung đang là code
Nội dung AI Teen sống trong TypeScript ở apps/learn/src/aiTeen/ (SDD-043 §2):
| File | Chứa | Cỡ (07.10.2026) |
|---|---|---|
program.ts | 3 course, SKILL_LEVELS, focusSkills | 131 dòng |
courses/aiTeen{1,2,3}.ts | unit, lesson (Guiding Question, slot, cycle, outcomes, concepts, output), Performance Task | 633-744 dòng mỗi file |
lessons/<course>-u<n>.ts | BODIES: khối đọc + bài tập (choice, practice), xếp theo VỊ TRÍ lesson | 18 file |
gates/<course>-u<n>.ts | GATES: phần đọc, mỗi phần 3 câu × 3 phương án | 18 file, 864 câu |
skillGoals.ts | LEVEL_INDICATORS (chỉ báo của thang level) | 131 dòng |
Nhiều thứ ngoài trang học đọc nội dung ấy LÚC BUILD: các test giữ luật soạn (program, gate, cycle, slots, skillGoals), scripts/aiteen/gen-api-catalog.mjs sinh taskSkills.ts cho API, apps/web/src/site/aiTeenProgram.ts đối chiếu bản công khai. Sửa nội dung từ Dolphin vì thế cần một nguồn sự thật khác file TS, mà không làm mất những cổng ấy.
2. Ba phương án
| (a) D1 là nguồn, learn đọc qua API | (b) Dolphin mở GitHub PR sửa file TS | (c) Lai: D1 để sửa và phát hành, repo giữ bản chụp sinh ra | |
|---|---|---|---|
| Kiểm hợp lệ | phải chuyển luật từ test sang server | CI chạy đúng test hiện có (mạnh nhất) | MỘT module luật dùng chung, chạy ở API (lưu, phát hành) và ở CI (bản chụp) |
| Slot ổn định (§12.4) | server giữ sổ slot | slots.test.ts như nay | server giữ sổ slot, CI kiểm lại trên bản chụp |
| Độ trễ sửa → learner thấy | ngay khi phát hành | PR + CI + gộp + deploy, 15-30 phút | ngay khi phát hành |
| Rollback | trỏ về bản phát hành cũ | git revert + deploy | trỏ về bản phát hành cũ (tức thì); bản chụp theo sau |
| CI và test | CI không đọc được D1 production: mất cổng nội dung | giữ nguyên | giữ nguyên trên bản chụp |
| Bản dự phòng khi API hỏng | không có (như IELTS, SDD-038) | không cần | bản chụp trong bundle |
| Xung đột với phiên khác | không | cao: các phiên sửa đúng những file này mỗi ngày | thấp: chỉ bot ghi bản chụp |
| Bí mật | không thêm | token GitHub có quyền ghi nằm trong worker | token chỉ ở GitHub Actions |
| Chi phí | D1 + edge cache, gần 0 | mỗi lần Lưu một lượt CI (ngân sách Actions đã từng cạn, skill cost-audit) | D1 + một workflow mỗi lần phát hành |
| Người sửa thấy gì | bản nháp, xem trước, lịch sử | một PR chờ người khác gộp | bản nháp, xem trước, lịch sử |
✍️ Chọn (c), nghiêng về (a): D1 là nguồn sự thật của nội dung AI Teen; learn đọc bản PHÁT HÀNH qua API lúc chạy; repo giữ một bản chụp TS SINH RA từ bản phát hành, dùng làm bản dự phòng trong bundle và làm đầu vào cho mọi test và cổng CI hiện có.
- (b) bị loại vì nó biến mentor thành người gửi PR: mỗi lần Lưu là một lượt CI, một PR chờ gộp, và va chạm với các phiên đang sửa đúng
courses/*.ts; muốn xem trước phải chờ deploy; worker phải giữ token ghi vào repo. - (a) thuần bị loại vì CI không đọc được D1 production: mọi luật soạn đang giữ bằng test sẽ thành luật chỉ server biết, và learn mất bản dự phòng (IELTS đã chấp nhận điều đó ở SDD-038, nhưng IELTS không có hàng chục thứ build-time dựa vào nội dung).
- (c) giữ được cả hai: sửa và phát hành tức thì như (a), mọi cổng và test như hôm nay chạy trên bản chụp. Tiền lệ trong repo: khoá bố mẹ Marlins (Coral ghi D1, script kéo về TS lúc build, SDD-013), chỉ khác là ở đây learn còn đọc thẳng bản phát hành để không phải chờ deploy.
3. Mô hình dữ liệu
✍️ Một tài liệu JSON mỗi thực thể, kiểm bằng zod, không chuẩn hoá thành bảng cột. Khuôn nội dung còn đổi hằng tuần (SRC-1281 thêm Activity và gắn phần đọc vào Activity); chuẩn hoá thì mỗi lần đổi khuôn là một migration. Truy vấn chỉ cần theo khoá, nên JSON không mất gì.
| Bảng | Khoá | Cột chính | Luật |
|---|---|---|---|
ai_teen_content_docs | doc_key | kind (program · course · unit · lesson), course_id, schema_version, draft_json, draft_rev, draft_updated_by, draft_updated_at, live_version | bản nháp hiện tại; draft_rev tăng mỗi lần Lưu |
ai_teen_content_versions | (doc_key, version) | json, fingerprint, created_by, created_at, note | chỉ CHÈN, không sửa, không xoá |
ai_teen_content_releases | release_id | course_id, manifest_json (doc_key -> version), created_by, created_at, note, validator_version | một bản phát hành = cả course |
ai_teen_content_live | course_id | release_id, updated_by, updated_at | con trỏ bản đang chạy; rollback = đổi con trỏ |
ai_teen_slot_registry | (course_id, unit_no, slot) | fingerprint, first_released_at, retired_at | sổ slot của SDD-043 §12.4, nạp từ data/legacy-slots.json + slot hiện có |
doc_key:program:ai-teen,course:ai-teen-1,unit:ai-teen-1:u1,lesson:ai-teen-1:u1:s7. Lesson gắn slot, không gắn vị trí; vị trí của lesson là thứ tự trongunit.lessons(danh sách slot).- Tài liệu lesson gói chung khung lesson + thân bài (khối, bài tập) + cổng đọc + Activity và Bước: đó là MỘT đơn vị soạn, và dấu vân tay slot cần cả thân bài lẫn cổng.
- Bước tick được (SDD-043 §15) mang id khai trong tài liệu; editor sinh id từ tên lúc tạo, và khoá id sau lần phát hành đầu.
- Chỉ báo: tài liệu
program:ai-teengiữSKILL_LEVELSvàLEVEL_INDICATORS; id chỉ báo<skill>:<level>:<n>không đổi khi sửa chữ hay đổi thứ tự (SDD-043 §13.2). - ✍️ Phát hành theo course, không theo từng tài liệu: learner không bao giờ thấy một unit đã đổi danh sách lesson trong khi lesson mới chưa phát hành. Luật chéo (bốn chặng mỗi unit, concept được dạy, slot) chỉ kiểm được trên cả course.
4. Vai và quyền
Vai lấy từ role_assignments (workers/api/src/shared/authz.ts); người qua Cloudflare Access của Dolphin nhận vai mentor (SDD-023 §27). Không thêm vai, không thêm phạm vi: vai mentor là đủ để sửa bản nháp (chủ dự án 07.10.2026, SRC-1291, xem §12).
| Việc | mentor | staff | admin |
|---|---|---|---|
| Duyệt mọi trang (chỉ đọc), xem lịch sử | có | có | có |
| Sửa bản nháp, khôi phục một bản cũ về nháp | có | có | có |
| Phát hành một course | không | có | có |
| Rollback (đổi con trỏ live về bản cũ) | không | không | có |
- Bản 0.2 có thêm phạm vi editor (bảng
ai_teen_content_editors, admin cấp/thu). SRC-1291 bỏ nó: route cấp/thu đã gỡ, API không còn đọc hay ghi bảng ấy; bảng để nguyên (migration 0338 đã chạy, xoá bảng là một migration không đem lại gì). - ✍️ Phát hành tách khỏi sửa: nội dung đi thẳng tới learner, nên người sửa và người bấm phát hành là hai vai (staff trở lên), như Coral tách soạn khỏi duyệt (SDD-013).
- Server kiểm quyền ở MỌI route (QG-008); nút bị ẩn trên giao diện chỉ là tiện, không phải cổng.
5. API
| Route | Quyền | Việc |
|---|---|---|
GET /v1/mentor/ai-teen/content | mentor+ | cây course -> unit -> lesson kèm trạng thái (nháp khác bản live? có lỗi chặn phát hành?) |
GET /v1/mentor/ai-teen/content/docs/{docKey} | mentor+ | bản nháp, bản live, draft_rev, danh sách lỗi và cảnh báo hiện tại |
PUT /v1/mentor/ai-teen/content/docs/{docKey} | editor+ | {draft, rev}; sai rev thì 409 STALE_DRAFT ("Có người vừa sửa, tải lại"); lỗi hình dạng thì 400 và không ghi |
GET .../docs/{docKey}/versions, GET .../versions/{v}, POST .../versions/{v}/restore | mentor+ | lịch sử kèm trường đã đổi; nội dung một version; khôi phục một bản cũ thành bản NHÁP (không phát hành), {rev} như PUT |
GET /v1/mentor/ai-teen/content/courses/{id}/releases | mentor+ | bản đang chạy, các bản phát hành, lỗi chặn và số cảnh báo của bản nháp hiện tại |
POST /v1/mentor/ai-teen/content/courses/{id}/releases | staff+ | {note?}; dựng ứng viên từ mọi bản nháp của course, chạy đủ luật §6; có lỗi chặn thì 409 RELEASE_BLOCKED kèm danh sách (cảnh báo không chặn); giống hệt bản đang chạy thì 409 NO_CHANGES; qua thì tạo version cho tài liệu đã đổi, tạo release, đổi con trỏ, ghi sổ slot, trong MỘT batch D1 |
POST .../courses/{id}/live | admin | {release_id}: rollback |
GET /v1/public/ai-teen/content/{course}/live | công khai | {release_id}, cache ngắn (60 giây) |
GET /v1/public/ai-teen/content/{course}/releases/{id} | công khai | nội dung đã phát hành, bất biến, cache dài |
GET /v1/mentor/ai-teen/content/courses/{id}/preview | editor+ | bản ứng viên từ bản nháp, cho learn mở ở chế độ xem trước (chưa dựng) |
- ✍️ Route đọc nội dung đã phát hành là công khai: nội dung không bí mật (www đã có bản giới thiệu), và route công khai cache được ở edge cho mọi người. Tiến độ của learner vẫn sau đăng nhập.
- Rate limit
enforceRateLimitcho PUT và release (luậtaiTeenContentWrite, 60 / 60 giây / tài khoản); thân PUT tối đa 256 KB (một lesson lớn nhất hiện nay dưới 40 KB). - Mọi route ghi để lại
audit_log(§8). Test hành vi theo skillapi-test.
6. Luật kiểm hợp lệ: một module, ba chỗ chạy
Luật rút từ các test hiện có vào một module thuần dùng chung (dự kiến packages/ai-teen-content/: kiểu, zod schema, validateDoc(doc), validateCourse(course) trả danh sách {code, severity, path, message}). Test của learn gọi module ấy thay cho luật viết lại; API gọi khi Lưu và khi phát hành; CI gọi trên bản chụp. Một luật, ba chỗ, không ai viết lại luật lần hai.
| Luật | Nguồn hôm nay | Mức |
|---|---|---|
| Compelling, Essential, Guiding Question kết thúc bằng "?"; CQ khác EQ | program.test.ts, CD-4 | lỗi |
| 3-5 key concept mỗi unit; mỗi concept đúng 3 ví dụ + 3 hiểu lầm và được ít nhất một lesson dạy; concept của lesson thuộc unit | program.test.ts, cycle.test.ts | lỗi |
Thân bài ≥4 khối, ≥1 example; 3-5 bài tập có ≥1 choice và ≥1 practice; ≥2 choice mỗi lesson | program.test.ts, cycle.test.ts | lỗi |
choice đúng một phương án đúng, mọi phương án có phản hồi; checklist thực hành ≥3 mục | program.test.ts | lỗi |
| Cổng: ≥2 phần, các phần phủ đủ mọi khối đúng một lần; đúng 3 câu mỗi phần, 3 phương án khác nhau, mỗi nhiễu khai hiểu lầm, có lời giải | gate.test.ts, CD-8c | lỗi |
Ký tự cấm trong chữ learner đọc: /[–—“”‘’…]|\.\.\./ (em dash, en dash, ngoặc cong, dấu ba chấm) | gate.test.ts, cycle.test.ts, check-no-emdash | lỗi |
| Đáp án không là phương án dài nhất duy nhất (gợi ý độ dài); câu hỏi cổng tối đa ~14 chữ, phương án ~8 chữ | CD-8c.2, check-option-length-cue (bản IELTS) | cảnh báo mỗi câu; lỗi khi quá 40% câu của một lesson |
Bốn lesson mỗi unit theo đúng thứ tự chặng (AI Teen 2-3); khuôn của course khi course khai template (AI Teen 1, SRC-1281); chặng không lùi | cycle.test.ts, CD-8d | lỗi |
Outcomes: course và unit 2-4 câu "Tôi có thể ..."; lesson ≥1 câu ^Tôi (hiểu|nhận ra|gọi tên được) nhắc đủ concept | cycle.test.ts, CD-6 | lỗi |
| Slot 1-20, không trùng trong unit; slot đã phát hành giữ dấu vân tay (số phương án, vị trí đáp án, số mục checklist, số khối mỗi phần) | slots.test.ts, SDD-043 §12.4 | lỗi |
Id Bước tick được: khai, không trùng trong Activity, không dạng part-\d+ / q\d+; đã phát hành thì không đổi | SDD-043 §15.2 | lỗi |
| Chỉ báo là một mảnh của câu gợi ý level; 1-3 chỉ báo mỗi level | skillGoals.test.tsx, SDD-043 §13.2 | lỗi |
Skill của task có trong CCS; skill bị bỏ (genai-3.3-2) không quay lại | program.test.ts | lỗi |
Phần nghĩa của CD-8 (mỗi hiểu lầm có nhiễu bắt được, phép thử ngược §5e của course-design, không nhiễu nào cũng đúng) | người soát | danh sách tự soát hiện ở màn phát hành, không máy chấm |
- Lỗi hình dạng (sai kiểu, thiếu trường bắt buộc để vẽ được trang) chặn Lưu. Lỗi nội dung không chặn Lưu (bản nháp được phép dở dang) nhưng chặn phát hành và hiện thành danh sách "Chặn phát hành" cạnh nút, mỗi mục bấm được để tới đúng ô.
validator_versionghi vào release: đổi luật thì biết bản phát hành nào được kiểm bằng luật cũ.
7. Giao diện trong Dolphin
- Một mục mới trong
GROUPScủaapps/mentors/src/sections.ts: AI Teen content (/ai-teen-content). Chữ giao diện tiếng Anh gốc, bản tiếng Việt trongapps/mentors/src/lang.tsx(như mọi màn Dolphin); nội dung course giữ nguyên tiếng Việt. - Trang: danh sách course -> course (unit) -> unit (trường unit + danh sách lesson theo thứ tự) -> lesson (khung, Activity và Bước, phần đọc kèm cổng, câu kiểm, thực hành) -> chỉ báo (
program). - Mỗi card là một đơn vị sửa (Guided Journey §2c): chỉ đọc mặc định; "Sửa" ở ô cố định
w-[7.75rem]đổi thành "Lưu" + "Huỷ" không xê dịch; một card sửa mỗi lúc; mở card khác là Huỷ. "Lưu" gửi cả tài liệu kèmrev. Cùng khuôn với màn Albums của Dolphin (SDD-019 §12-§14). - Huy hiệu trạng thái ở đầu trang: "Đang chạy" / "Bản nháp chưa phát hành" / "Có lỗi chặn phát hành". Lịch sử: danh sách version kèm người, ngày DD.MM.YYYY, diff theo trường, nút khôi phục về nháp.
- Xem trước: nút "Xem trước trên learn" mở trang learn của chính lesson ở chế độ xem trước (chỉ vai editor+, nhãn "Bản xem trước" dính trên cùng, không ghi tiến độ).
- Phát hành ở trang course: tóm tắt tài liệu đã đổi, danh sách chặn, danh sách tự soát §6, ô ghi chú.
8. Dấu vết
audit_logcho mọi lượt Lưu, khôi phục, phát hành, rollback, cấp phạm vi:ai_teen_content.save,.restore,.release,.rollback,.grant,detail_jsonchỉ chứa khoá,rev,version,release_id(shared/audit.tstừ chối chữ nội dung trong chi tiết).- Nội dung từng bản nằm ở
ai_teen_content_versions(chỉ chèn), nên diff giữa hai bản luôn dựng lại được. - Bản chụp trong git là dấu vết thứ hai: mỗi release thành một commit có mã release trong message.
9. Learn đọc nội dung thế nào
- Lúc build: bundle có bản chụp, trang vẽ được ngay kể cả khi API hỏng. Bản chụp của một course là file TS (
courses/,lessons/,gates/), hoặcreleased/<course>.jsonkhi bản live đã được kéo về (§10).snapshot.generated.tsghi mã release mà bundle của mỗi course bằng. - Lúc chạy (
apps/learn/src/aiTeen/liveContent.ts, gọi một lần mỗi lượt tải trang ở gốc AI Teen): hỏi.../live(cache 60 giây). Cùng mã với bản chụp,null, hay lỗi bất kỳ: giữ bản chụp, không tải gì thêm. Khác mã: tải.../releases/{id}(bất biến), kiểm bằng CHÍNH module luật dùng chung (hình dạng, mọi luật chặn, sổ slot learn mang theo) và so khung (unit, danh sách slot, chặng, dấu vân tay slot) với bản chụp; qua cả hai mới áp, chữ đổi tại chỗ trên chính các object, không remount. Module luật và zod chỉ tải khi cần (applyRelease.tsnạp động), nên bundle chính không nặng thêm. - ✍️ Mã release là băm nội dung (
releaseIdOf: băm của [khoá, băm JSON] xếp theo khoá). Seed, API và learn đặt tên cùng một nội dung giống hệt nhau, nên bản phát hành đầu (seed từ TS) có đúng mã bản chụp ghi: learn không tải gì và không đổi gì cho tới lần phát hành đầu từ Dolphin. - API (tiến độ, cổng, tick bước, evidence) đọc bản live để biết bước nào có thật và bước nào tick được; khi đó server tự chấm trắc nghiệm được (SDD-043 §8 ✍️ "khi nội dung vào D1 thì server tự chấm"). Chưa dựng (M6).
10. Bản chụp và CI
npm run aiteen:pull(scripts/aiteen/pull-content.mjs) đọc release live của ba course từ API công khai; course nào live bằng chính nội dung TS thì không có file; course nào khác thì ghiapps/learn/src/aiTeen/released/<course>.jsonrồi gọinpm run aiteen:exportsinh lạisnapshot.generated.ts. Một file kéo về mà không qua luật thì được trả lại như cũ và script dừng.npm run aiteen:check(trongnpm run check:code, CI chạy cho mọi commit có code): seed vàsnapshot.generated.tskhớp nội dung; mỗi filereleased/*.jsonqua luật dùng chung (hình dạng, luật chặn, sổ slot), khung bằng khung TS, và mã release đúng là băm nội dung (sửa tay là đỏ).- ✍️ Khác bản 0.3: bản chụp của một release là JSON, KHÔNG sinh lại file TS. Sinh lại
courses/*.tssẽ xoá mọi chú thích soạn bài trong đó và va với các phiên đang sửa đúng những file ấy trong lúc chuyển; JSON qua cùng luật và cùng kiểm khung, learn áp nó lúc tải module. Workflow tự mở PR bản chụp sau mỗi release (§10 bản 0.3) chưa dựng: hôm nay staff chạynpm run aiteen:pullvà commitcontent(ai-teen): release <id>; thiếu bước ấy thì learner vẫn thấy bản live qua API, chỉ bản dự phòng là cũ.
11. Kế hoạch chuyển
| Bước | Việc | Xong khi |
|---|---|---|
| M0 | Tài liệu này, SDD-043 §15, skill content-editor, nâng course-design (PR này) | check:docs xanh |
| M1 | Rút luật từ test vào packages/ai-teen-content (đã dựng, §11b); test learn gọi module | mọi test learn xanh, không đổi hành vi |
| M2 | Migration năm bảng (§3); scripts/aiteen/export-content.mjs (đã dựng, §11b) sinh seed (bản nháp + version 1 + release 1 + sổ slot) từ TS; nạp qua skill seed-data | đếm 3 course, 18 unit, 84 lesson, 1059 câu cổng (sau SRC-1281); export lại từ D1 ra TS không diff |
| M3 | Dolphin chỉ đọc: cây, mọi trang chi tiết, lịch sử | e2e mentors xem đủ bốn cấp |
| M4 | Sửa bản nháp + kiểm hợp lệ + audit; phát hành còn tắt; workflow bản chụp chạy so sánh | sửa một chữ trong nháp, bản live và learn không đổi |
| M5 | Chuyển: bật phát hành và rollback; learn đọc bản live lúc chạy; file TS thành bản chụp sinh ra; skill course-design và make-meaning-lesson đổi sang soạn bằng bản nháp | phát hành một sửa chữ, learner thấy trong 60 giây, PR bản chụp tự gộp |
| M6 | Server tự chấm trắc nghiệm, bản kê bước tick đọc từ bản live | test API chấm sai khi client nói đúng |
- Đóng băng ngắn giữa M2 và M5: trong khoảng ấy mọi sửa TS phải chạy lại export và seed, hoặc dừng sửa nội dung. Giữ khoảng này ngắn; đừng để PR tái cấu trúc AI Teen 1 (SRC-1281) chạy song song với M2.
- Phiên Claude soạn nội dung sau M5 không dùng giao diện: script dự định
scripts/aiteen/push-drafts.mjskiểm luật ở máy rồi sinh SQL ghi BẢN NHÁP, nạp quaseed-data; staff phát hành trong Dolphin. - Chi phí: năm bảng nhỏ (dưới vài MB kể cả lịch sử một năm), đọc lúc chạy đi qua edge cache, một lượt Actions vài phút mỗi release. Không thêm dịch vụ, không thêm thư viện UI.
11b. Lát đầu đã dựng (07.10.2026, SRC-1284)
| Bước | Đã có | Chưa |
|---|---|---|
| M1 | packages/ai-teen-content: zod schema bốn loại tài liệu (.strict()), khoá tài liệu, fingerprint / holdsSlot (chuyển từ slots.ts, learn re-export), validateProgram / validateCourseDoc / validateUnit / validateLesson / validateCourse / issuesOfDoc, docsFromLearn (TS -> tài liệu). apps/learn/src/aiTeen/contentRules.test.ts chạy module trên nội dung thật; các ca luật trùng trong program, cycle, gate, slots.test.ts đã gỡ | luật của rubric product |
| M2 | Migration 0338_ai_teen_content_editor.sql: năm bảng §3 + ai_teen_content_editors (phạm vi editor, xem mục khác thiết kế ngay dưới bảng) + ai_teen_content_import (bảng trung chuyển của seed). scripts/aiteen/export-content.mjs sinh scripts/aiteen/seed-content-{program,ai-teen-1,ai-teen-2,ai-teen-3}.sql: 3 course, 18 unit, 84 lesson, 1059 câu cổng, 99 slot; --check báo seed cũ | nạp lên production (lệnh ở skill content-editor §0); export ngược D1 -> TS (M5) |
| M3 | GET /v1/mentor/ai-teen/content (cây), GET .../docs/{docKey} (nháp, live, rev, lỗi, ngữ cảnh unit). Dolphin /ai-teen-content: course -> unit -> lesson, trang unit và lesson chỉ đọc | lịch sử version, xem trước trên learn, trang program (chỉ báo) |
| M4 | PUT .../docs/{docKey} cho UNIT và LESSON: card Sửa -> Lưu/Huỷ cùng ô w-[7.75rem], luật chạy ngay khi gõ (cùng module), lỗi hiện dưới ô, huy hiệu "Bản nháp chưa phát hành", 409 STALE_DRAFT, 400 sai hình dạng không ghi, audit_log ai_teen_content.save; mọi mentor sửa được (SRC-1291) | sửa course và program; khôi phục version |
| M5 | Xem §11c (08.10.2026) | workflow tự mở PR bản chụp; xem trước trên learn; mở khoá khung (slot mới) |
Khác thiết kế, có lý do:
- Không có phạm vi editor (SRC-1291): lát đầu dựng bảng
ai_teen_content_editorsthay cho dòngrole_assignmentscóscope_type(cột ấy có CHECK từ migration 0001); quyết định 07.10.2026 bỏ luôn phạm vi, nên bảng còn đó nhưng không ai dùng. - Mã lỗi: không phải mentor nhận 401 (luật "401 chứ không 403"); mentor xin phát hành nhận 403
SCOPE_REQUIRED;revcũ nhận 409STALE_DRAFT. - Khung bị khoá ở lát này: Lưu chỉ đổi CHỮ. Đổi danh sách lesson của unit, số key concept, slot, chặng, hay dấu vân tay slot của lesson (số câu, số phương án, vị trí đáp án, số mục checklist, số khối mỗi phần) trả 400
STRUCTURE_LOCKED; đó là việc của bước có phát hành. - Seed không đè bản nháp: chạy lại seed sau khi file TS đổi thì tài liệu có bản nháp bằng bản live nhận chữ mới, tài liệu đang có bản nháp sửa trong Dolphin giữ nguyên bản nháp ấy; version mới chỉ chèn khi chữ khác bản gần nhất; release đặt tên theo băm nội dung nên chạy lại là không đổi gì.
- Hai luật hạ thành cảnh báo (đo trên nội dung 07.10.2026): gợi ý độ dài quá 40% câu của lesson (đa số lesson vượt, luật chưa từng được kiểm cho AI Teen), và ký tự cấm ở thân bài đọc, thực hành, câu hỏi unit (trước nay test của learn không kiểm những trường ấy). Ký tự cấm ở khung lesson, outcomes, câu cổng và câu kiểm AI Teen 1 vẫn là lỗi như test cũ.
- Chữ giao diện Dolphin: gốc tiếng Anh + bản tiếng Việt ở
lang.tsxnhư mọi màn Dolphin; thông điệp của luật viết tiếng Việt vì chúng nói về chữ tiếng Việt của bài.
11c. Lát M5 đã dựng (08.10.2026, SRC-1299)
Chủ dự án giao toàn quyền 08.10.2026 ("Bạn quyết toàn bộ và làm hết đi").
| Phần | Đã có |
|---|---|
| Lịch sử, khôi phục | GET .../versions (mới nhất trước, trường đã đổi so với version trước, cờ live / giống nháp), GET .../versions/{v}, POST .../versions/{v}/restore đi đúng đường Lưu (hình dạng, khoá khung, rev), audit_log ai_teen_content.restore. Dolphin: card "Lịch sử version" ở mọi trang unit và lesson, chỉ đọc mặc định; "Sửa" hiện nút "Khôi phục về nháp" trên từng version |
| Phát hành | POST .../courses/{id}/releases (staff, admin): lỗi chặn thì 409 RELEASE_BLOCKED kèm danh sách, cảnh báo cho qua; version mới chỉ cho tài liệu đã đổi; release đặt tên bằng băm nội dung; con trỏ live; sổ slot nhận slot mới (INSERT OR IGNORE, dấu vân tay đầu là lịch sử); một batch D1; audit_log ai_teen_content.release (mã release, bản trước, số trang đổi, số cảnh báo) |
| Rollback | POST .../courses/{id}/live chỉ admin, đổi con trỏ và live_version theo manifest, không xoá gì, bản nháp giữ nguyên (nên hiện lại là "Bản nháp chưa phát hành"); audit_log ai_teen_content.rollback |
| Dolphin | card "Phát hành" ở trang course: huy hiệu "Sẵn sàng phát hành" / "n lỗi chặn phát hành" + "n lưu ý (không chặn)", danh sách lỗi bấm tới trang có lỗi, danh sách release; "Sửa" mở ô ghi chú và nút "Phát hành course này" (staff+), và "Quay về bản này" trên từng release cũ (admin) |
| Đọc công khai | GET /v1/public/ai-teen/content/{course}/live (cache 60 giây) và .../releases/{id} (bất biến, cache một năm); có tên trong bảng PUBLIC của authCoverage.test.ts |
| learn | §9: liveContent.ts, applyRelease.ts, releaseApply.ts, snapshot.generated.ts, released/; test liveContent.test.ts và hai ca e2e (bản mới hơn thì hiện chữ mới; API hỏng thì giữ bản chụp) |
| Seed | scripts/aiteen/seed-content-*.sql sinh lại: nội dung không đổi, chỉ mã release đổi sang releaseIdOf; nạp lại là bản phát hành đầu bằng đúng nội dung hôm nay |
Quyết định ở lát này:
- ✍️ Cảnh báo không chặn phát hành (chủ dự án 08.10.2026: chưa dọn nội dung). Chỉ
errorvàshapechặn. - ✍️ Khung vẫn khoá (
STRUCTURE_LOCKEDnhư §11b): một bản phát hành chỉ đổi CHỮ. learn từ chối áp bản phát hành nào có khung khác bản chụp, nên câu trả lời đã lưu của learner không bao giờ trỏ sang câu khác. Mở khoá khung (thêm lesson, slot mới) là bước sau, cùng lúc với việc learn hết so khung. - ✍️ Bản nháp giống bản live thì giữ nguyên văn bản live: lưu một bản nháp có nội dung bằng bản live (khôi phục version live, lưu không đổi gì) ghi lại đúng chuỗi JSON của bản live, nên "Bản nháp chưa phát hành" và băm release không báo đổi chỉ vì thứ tự khoá JSON khác (seed ghi theo thứ tự của learn, Lưu ghi theo thứ tự của schema).
- Không có migration mới: năm bảng của 0338 đủ cho mọi việc trên.
- Thứ tự deploy: api, rồi nạp lại seed ba course (để bản live đổi sang mã băm mới bằng bản chụp), rồi learn và mentors. learn deploy trước seed vẫn an toàn về khung (kiểm luật + khung), nhưng sẽ áp bản live cũ nếu nội dung TS đã đổi từ lần seed trước.
12. Còn mở
- ✅ Đã chốt 08.10.2026 (SRC-1299): cảnh báo (gợi ý độ dài 40%, ký tự cấm ở thân bài đọc / thực hành) KHÔNG chặn phát hành; chủ dự án chưa muốn dọn nội dung.
- ✅ Đã chốt 07.10.2026 (SRC-1291): MỌI mentor sửa được bản nháp nội dung AI Teen, không cần cấp quyền riêng. Staff và admin vẫn sửa; phát hành chỉ staff/admin (bật từ 08.10.2026, §11c); mỗi lượt Lưu vẫn để một dòng
audit_logai_teen_content.save. Lý do chấp nhận rủi ro: bản nháp không tới learner, cổng thật là phát hành, và audit cho biết ai đổi gì. - Ai được phát hành ngoài staff/admin.
- Mentor có được tạo lesson mới (cấp slot mới) hay chỉ sửa lesson có sẵn; thiết kế cho phép, quyền mặc định chỉ cho staff.
- Rubric product (
data/products.json) và danh mục CCS chép nguyên có vào editor không; đợt đầu: không.
Trace
| REQ | Mục |
|---|---|
| REQ-MEN-22 | §2 (quyết định), §3 (dữ liệu), §4 (quyền), §5 (API), §6 (kiểm hợp lệ), §7 (giao diện), §8 (dấu vết), §9-§11 (đọc, bản chụp, chuyển), §11b (lát đầu đã dựng), §11c (lát M5) |