Skip to content

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):

FileChứaCỡ (07.10.2026)
program.ts3 course, SKILL_LEVELS, focusSkills131 dòng
courses/aiTeen{1,2,3}.tsunit, lesson (Guiding Question, slot, cycle, outcomes, concepts, output), Performance Task633-744 dòng mỗi file
lessons/<course>-u<n>.tsBODIES: khối đọc + bài tập (choice, practice), xếp theo VỊ TRÍ lesson18 file
gates/<course>-u<n>.tsGATES: phần đọc, mỗi phần 3 câu × 3 phương án18 file, 864 câu
skillGoals.tsLEVEL_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 serverCI 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ổ slotslots.test.ts như nayserver giữ sổ slot, CI kiểm lại trên bản chụp
Độ trễ sửa → learner thấyngay khi phát hànhPR + CI + gộp + deploy, 15-30 phútngay khi phát hành
Rollbacktrỏ về bản phát hành cũgit revert + deploytrỏ về bản phát hành cũ (tức thì); bản chụp theo sau
CI và testCI không đọc được D1 production: mất cổng nội dunggiữ nguyêngiữ nguyên trên bản chụp
Bản dự phòng khi API hỏngkhông có (như IELTS, SDD-038)không cầnbản chụp trong bundle
Xung đột với phiên kháckhôngcao: các phiên sửa đúng những file này mỗi ngàythấp: chỉ bot ghi bản chụp
Bí mậtkhông thêmtoken GitHub có quyền ghi nằm trong workertoken chỉ ở GitHub Actions
Chi phíD1 + edge cache, gần 0mỗ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ộpbả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ảngKhoáCột chínhLuật
ai_teen_content_docsdoc_keykind (program · course · unit · lesson), course_id, schema_version, draft_json, draft_rev, draft_updated_by, draft_updated_at, live_versionbả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, notechỉ CHÈN, không sửa, không xoá
ai_teen_content_releasesrelease_idcourse_id, manifest_json (doc_key -> version), created_by, created_at, note, validator_versionmột bản phát hành = cả course
ai_teen_content_livecourse_idrelease_id, updated_by, updated_atcon trỏ bản đang chạy; rollback = đổi con trỏ
ai_teen_slot_registry(course_id, unit_no, slot)fingerprint, first_released_at, retired_atsổ 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ự trong unit.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-teen giữ SKILL_LEVELS và 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ệcmentorstaffadmin
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ápcócócó
Phát hành một coursekhôngcócó
Rollback (đổi con trỏ live về bản cũ)khôngkhôngcó
  • 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 ​

RouteQuyềnViệc
GET /v1/mentor/ai-teen/contentmentor+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}/restorementor+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}/releasesmentor+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}/releasesstaff+{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}/liveadmin{release_id}: rollback
GET /v1/public/ai-teen/content/{course}/livecông khai{release_id}, cache ngắn (60 giây)
GET /v1/public/ai-teen/content/{course}/releases/{id}công khainội dung đã phát hành, bất biến, cache dài
GET /v1/mentor/ai-teen/content/courses/{id}/previeweditor+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 enforceRateLimit cho PUT và release (luật aiTeenContentWrite, 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 skill api-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ậtNguồn hôm nayMức
Compelling, Essential, Guiding Question kết thúc bằng "?"; CQ khác EQprogram.test.ts, CD-4lỗ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 unitprogram.test.ts, cycle.test.tslỗ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 lessonprogram.test.ts, cycle.test.tslỗ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ụcprogram.test.tslỗ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ảigate.test.ts, CD-8clỗ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-emdashlỗ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ùicycle.test.ts, CD-8dlỗ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 đủ conceptcycle.test.ts, CD-6lỗ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.4lỗ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 đổiSDD-043 §15.2lỗi
Chỉ báo là một mảnh của câu gợi ý level; 1-3 chỉ báo mỗi levelskillGoals.test.tsx, SDD-043 §13.2lỗi
Skill của task có trong CCS; skill bị bỏ (genai-3.3-2) không quay lạiprogram.test.tslỗ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átdanh 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_version ghi 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 GROUPS của apps/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 trong apps/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èm rev. 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_log cho 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_json chỉ chứa khoá, rev, version, release_id (shared/audit.ts từ 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 ​

  1. 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ặc released/<course>.json khi bản live đã được kéo về (§10). snapshot.generated.ts ghi mã release mà bundle của mỗi course bằng.
  2. 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.ts nạp động), nên bundle chính không nặng thêm.
  3. ✍️ 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.
  4. 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ì ghi apps/learn/src/aiTeen/released/<course>.json rồi gọi npm run aiteen:export sinh lại snapshot.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 (trong npm run check:code, CI chạy cho mọi commit có code): seed và snapshot.generated.ts khớp nội dung; mỗi file released/*.json qua 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/*.ts sẽ 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ạy npm run aiteen:pull và commit content(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ướcViệcXong khi
M0Tài liệu này, SDD-043 §15, skill content-editor, nâng course-design (PR này)check:docs xanh
M1Rút luật từ test vào packages/ai-teen-content (đã dựng, §11b); test learn gọi modulemọi test learn xanh, không đổi hành vi
M2Migration 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
M3Dolphin chỉ đọc: cây, mọi trang chi tiết, lịch sửe2e mentors xem đủ bốn cấp
M4Sử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ánhsửa một chữ trong nháp, bản live và learn không đổi
M5Chuyể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ápphát hành một sửa chữ, learner thấy trong 60 giây, PR bản chụp tự gộp
M6Server tự chấm trắc nghiệm, bản kê bước tick đọc từ bản livetest 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.mjs kiểm luật ở máy rồi sinh SQL ghi BẢN NHÁP, nạp qua seed-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
M1packages/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
M2Migration 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)
M3GET /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ỉ đọclịch sử version, xem trước trên learn, trang program (chỉ báo)
M4PUT .../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
M5Xem §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_editors thay cho dòng role_assignments có 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; rev cũ nhận 409 STALE_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.tsx như 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ụcGET .../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ànhPOST .../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)
RollbackPOST .../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
Dolphincard "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 khaiGET /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)
Seedscripts/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ỉ error và shape chặn.
  • ✍️ Khung vẫn khoá (STRUCTURE_LOCKED như §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_log ai_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 ​

REQMụ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)