SDD-038 · Rà soát và audit sân luyện
Một phần của SDD-038 Cổng IELTS. Phần đầu là đặc tả đang áp dụng, mỗi mục giữ số § cũ; diễn biến và lý do các vòng quyết định nằm ở mục Lịch sử quyết định cuối trang.
51. Một lượt rà cả sân luyện: mã chết, một bài bị đè, và hai cổng nói dối (SRC-931, 21.09.2026)
Mạch tiến độ bài tập (bốn trạng thái, hai cuốn sổ, quy ước
ref, luật mở khoá và các ca hỏng) được mô tả gọn ở một chỗ: Tiến độ bài tập IELTS. Mục này và các mục §42 · §46 · §53 giữ lý do của từng quyết định; trang kia nói cái gì đang chạy.
51a. 104 nhánh tiếng Việt không có cửa nào mở ra được
SRC-834/835 đã bỏ công tắc VI/EN khỏi sân luyện, và từ hôm ấy useLang() trả về đúng một hằng số "en" (apps/learn/src/ielts/lang.tsx). Nhưng 109 chỗ trong mười hai file vẫn viết lang === "vi" ? <bản Việt> : <bản Anh> — mỗi màn hình vẫn cõng theo trọn một bản tiếng Việt mà không thao tác nào của learner mở ra được.
Đã gỡ nhánh chết ở 104 chỗ. Năm chỗ còn lại đọc item.lang của NỘI DUNG (thang bốn chặng §5f), không phải công tắc giao diện, nên chúng ở lại. Phép biến đổi chạy bằng parser TypeScript chứ không bằng regex: ternary ở đây có cái trải bốn dòng và có cái lồng trong cái khác, và một regex sẽ cắt nhầm đúng những chỗ ấy.
Không một pixel nào đổi. useLang() vốn đã trả "en", nên thứ bị xoá đúng bằng thứ chưa bao giờ hiện ra; 345 test của learn và 1.937 test của api đều xanh trước và sau. Kèm theo là 67 cặp ngoặc JSX thừa mà phép gỡ để lại ({"Passages"} → Passages), mười khai báo lang không còn ai đọc, và năm dòng import { useLang } theo sau chúng.
pick() và t() ở lại, và đó không phải sót tay: chúng chọn giữa *_vi và *_en của DỮ LIỆU nội dung, nơi bản tiếng Anh còn đang lấp dần, và chúng rơi về bản còn lại khi bản kia trống. Gỡ chúng là biến mỗi ô chưa dịch xong thành một chỗ trống không ai hiểu vì sao trống.
51b. Sáu ký hiệu không còn ai gọi
useNextOf (Hub.tsx), levelInfo (levels.ts), findTest (data/examTests.ts), useLexicon (data/store.ts), fetchMicroSkills (ielts/api.ts) ở app learn, và parseRef (ieltsSkills/catalog.ts) ở api. Cả sáu là dấu vết của một trang hoặc một lối đi đã bỏ: fetchMicroSkills chẳng hạn là lời gọi của trang danh sách năng lực mà SRC-921 đã gỡ, còn parseRef là nghịch đảo của refOf cho một đường đọc ngược chưa bao giờ dựng.
Phép đo dùng ở đây, ghi lại để lần sau đo cùng cách: một ký hiệu là CHẾT khi không file nào khác nhắc tới nó và trong chính file của nó nó chỉ xuất hiện đúng một lần, tức là chỉ có dòng định nghĩa. Thiếu vế thứ hai thì mọi hàm nội bộ đều bị kết tội oan.
51c. Một bài tập bị đè mất, một bài chưa bao giờ được nạp
Đây là chỗ nặng nhất của lượt rà, và nó không nhìn thấy được từ màn hình.
Hai trục soạn bài (§35e) cùng đổ vào MỘT không gian id: scripts/micro-seed.mjs đặt tên bài là <kỹ năng>.<năng lực>.l<bậc>.<thứ tự>, lấy micro của bài nếu có, không thì lấy code của file. Nên một bài ở trục dạng khai micro: "main-idea", bậc 1, thứ tự 3 sinh ra đúng cái id mà bài thứ ba bậc 1 của reading/main-idea.json sinh ra.
Chuyện ấy đã xảy ra:
| Bài | Id | Kết cục |
|---|---|---|
reading-tasks/matching-headings.json "Give the paragraph a name" | reading.main-idea.l1.3 | Nạp ở migration 0265, đang chạy |
reading/main-idea.json "Five more, same job" | reading.main-idea.l1.3 | Bị đè, chưa từng tới production |
reading/main-idea.json "Same five, two languages" | reading.main-idea.l2.3 | Không migration nào nạp, chưa từng tới production |
INSERT OR REPLACE không báo lỗi khi trùng khoá, nó chỉ đè. Nên Main Idea chạy trên production với mười bài trong khi kho có mười hai, và dấu hiệu duy nhất là một danh sách ngắn hơn kho hai dòng — không ai đọc ra điều đó từ giao diện.
Đã sửa ở ba chỗ:
reading/main-idea.jsonđánh lại bài va chạm thànhseq: 5. Bài ở trục dạng GIỮ số 3, vì nó đã nằm trên production và/ielts/skills/reading/main-idea/levels/1/3đã là một URL learner có thể đã lưu — luật CLAUDE.md là link đã gửi đi thì không được gãy.- Migration
0269_main_idea_two_missing_exercises.sqlnạp đúng hai bài còn thiếu, sinh bằngmicro-seedrồi cắt lấy hai bài (chứ không nạp lại cả file: một diff nhỏ thì đọc được). scripts/check-micro-content.mjsnay dựng id y nhưmicro-seeddựng và bắt trùng, nêu tên cả hai file. Đã thử ngược: đặt lại va chạm thì cổng đỏ đúng một dòng.
Vì sao seq: 5 chứ không phải seq: 4, và bài học thứ hai của cùng một cái bẫy. Bản sửa đầu tiên chọn số 4, vì trong content/ số 4 còn trống. Nhưng không gian id này còn một người dùng nữa mà content/ không nhìn thấy: migration viết tay. Migration 0268 (SRC-925, hôm trước) đã tự tay dựng reading.main-idea.l1.4 để gom ba câu khởi động lẻ vào một bài, và bài ấy chỉ sống trong SQL. Nạp đè lên nó là INSERT OR REPLACE xoá dòng cũ, rồi khoá ngoại ON DELETE CASCADE cuốn theo cả ba câu — mất nội dung, vẫn không một dòng lỗi nào.
Thứ duy nhất phản đối là phép đếm tổng số câu trong microSkills/routes.test.ts: mong 2.429, nhận 2.426. Đó đúng là công dụng mà chú thích của phép đếm ấy tự nhận, và nó đã làm được việc — nhưng nó chỉ nói "lệch ba", không nói lệch ở đâu. Nên cổng nay đối chiếu thêm với id do migration KHÔNG sinh từ content/ dựng (nhận ra bằng tên bộ sinh nằm trong file), và nói thẳng tên file đã lấy số ấy. Thử ngược: đặt lại seq: 4 thì cổng đỏ đúng một dòng, nêu tên 0268.
51d. Hai cổng nói dối theo hai kiểu khác nhau
check-micro-content không chạy ở đâu cả. content/ielts-micro/README.md ghi rõ cổng này gác thành phần từng bậc, nhưng nó không có trong npm run check:code, không có trong CI, không có trong hook. Một cổng như thế tệ hơn không có cổng: người soạn đọc README rồi tin rằng máy đang gác hộ. Chạy tay nó ra 164 dòng lỗi, tất cả rơi vào 11 file của trục DẠNG CÂU HỎI — trục sinh sau nó (SRC-895) và nó chưa biết mặt: nó đòi code trong khi file khai task_type, đòi mười câu ở bậc 3 trong khi một đề Matching Headings là năm tiêu đề, đòi ngữ liệu 110 từ ở bậc 5 trong khi độ dài ở trục này do chính dạng câu hỏi quyết định.
Cổng nay biết cả hai trục, mỗi trục một khuôn, và khuôn của trục dạng đọc từ chính 55 bài đã soạn (năm bài một dạng, năm câu một bài, chỉ bậc 1 · 3 · 5) đúng cách mà dải độ dài cũ đọc từ reading/main-idea. Trần độ dài KHÔNG áp cho trục dạng — dải bậc 3 (25-92 từ) và bậc 5 (48-139 từ) chồng lên nhau thật — nhưng sàn thì giữ, vì một "ngữ liệu" mười từ thì ở dạng nào cũng không còn là bài đọc. Xanh rồi mới nối vào npm run check:code: nối một cổng đang đỏ là chặn deploy của mọi phiên vì một việc không phải của họ.
audit:ielts-content trả 401 kể từ SRC-885. Phép rà nội dung gọi /v1/public/ielts/content/* trần, mà §27 đã khoá cả nhóm ấy sau requireSession. Nó không đọc mã lỗi nên thứ người chạy nhìn thấy là một vệt stack trace của fetch — một câu trả lời sai cho câu hỏi "nội dung có hỏng không". Nay nó nhận token qua NEMO12_SESSION, gửi bằng Authorization: Bearer, và khi thiếu token thì in ra đúng câu lệnh phải gõ thay vì stack trace.
51e. Hai trang chưa từng được nhắc trong docs
/ielts/warmup (bộ khởi động ba câu) và /ielts/domain (chọn lĩnh vực của learner) đều là trang sống, đều có lối vào thật — Shell.tsx trỏ tới domain, trang /ielts/courses trỏ tới warmup — nhưng /ielts/domain không xuất hiện ở bất kỳ dòng nào của docs/, còn /ielts/warmup chỉ được nhắc gián tiếp qua nội dung của nó (§17, §48). Ghi lại ở đây để lần sau đọc docs là thấy:
| Trang | Việc của nó | Nội dung đến từ |
|---|---|---|
/ielts/warmup | Ba câu mở đầu, không chấm điểm, dành cho người vừa mở trang | ielts_content_items kiểu warmup (§17) |
/ielts/domain | Learner chọn lĩnh vực; kho từ và cụm bài theo lựa chọn ấy | ielts_content_clusters + hồ sơ learner |
51f. Còn mở, và đây là chỗ cần chủ dự án quyết
- Ranh giới thật của §8a hẹp hơn câu chữ của nó. Rà xong thì thấy sân luyện đang theo một luật tinh hơn "mọi trang chỉ tiếng Anh": TÊN và NHÃN tiếng Anh, THÂN BÀI DẠY vẫn tiếng Việt.
ExamPart.tsxghi thẳng điều đó trong một comment (name_enở tiêu đề,note_vi/measures_vi/tell_vi/shape_viở thân);levels.tslàm đúng thế (short: "Level 1"tiếng Anh,titlevàwhytiếng Việt);microCheckpoints.tsvàcheckpoints.tschỉ có bản tiếng Việt và không có trường*_ennào. Ba chỗ ấy nhất quán với nhau và nhất quán với thang bốn chặng §5f — thứ lệch là §8a, chứ không phải mã. Đã thêm một đoạn vào §8a nói rõ ranh giới; nếu chủ dự án muốn thân bài cũng sang tiếng Anh thì đó là một đợt SOẠN LẠI nội dung dạy học, không phải một lượt dịch. - Trục dạng câu hỏi mới chỉ có ở Reading.
content/ielts-micro/córeading/(10 năng lực),writing/(16 năng lực) vàreading-tasks/(11 dạng). Nghe và Nói chưa có trục dạng nào, nên hai kỹ năng ấy chỉ lọc được theo năng lực. Đó là việc soạn nội dung, không phải việc của mã. NEMO_TO_VNDkhông ai gọi. Hằng số ấy ghi luật "1 Nemo = 1.000đ" (§34b) nhưng việc chuyển khoản diễn ra ngoài đời, nên nó là chữ chứ không phải mã. Để lại có chủ ý, ghi ra đây để lần rà sau không phải điều tra lại.
71. Rà soát cuối phiên 22.09.2026 (SRC-965)
IeltsLatestTest.tsxđã xoá. Ba lối vào "đã thi / tự đoán / không đoán" (SRC-956) không còn màn nào dẫn tới từ khi trang Goal bỏ đi ở SRC-961. Một file không ai gọi sẽ lệch khỏi phần còn lại mà không ai biết. Lấy lại được:git show 85c9c930:apps/learn/src/IeltsLatestTest.tsx.- Learning Plan có ca e2e cho sợi dây lưu. Sửa một ô, bấm Lưu, mở lại trang. Nếu dây đứt ở bất cứ đâu — nút Save không nối,
cellsgửi lên rỗng, trang không đọc lại bản đã lưu — màn hình vẫn vẽ ra một bảng trông đúng, và learner chỉ phát hiện ở lần mở sau. - Một lỗi nữa lộ ra khi viết ca ấy: nhánh "thiếu mục tiêu hoặc thiếu điểm" của Learning Plan KHÔNG có tiêu đề, vì tiêu đề nằm trong
headercủaEditGateở nhánh kia. Learner mở trang ra và không biết mình đang ở đâu. - Hai bài học đã ghi vào trí nhớ dự án:
xoa-test-bang-script.mdvàuseregistersave-vong-ve-vo-tan.md.
129. Lượt audit đầu tiên và bộ skill audit (SRC-1086, 27.09.2026)
Chủ dự án: test và audit một lượt về UI, logic và nhiều khía cạnh khác; tạo skill chuyên để audit; rồi "fix hết P0 và P1". Bộ skill: /audit (điều phối) và bốn góc /audit-ui, /audit-logic, /audit-language, /audit-security-perf trong .claude/skills/. Bốn agent chạy song song, chỉ đọc và chạy test; phát hiện có bằng chứng, xếp P0 (hỏng/sai dữ liệu), P1 (người dùng gặp), P2 (nâng cấp).
Đã sửa (P0, P1):
- Speak together cộng giờ sau khi đã rời phòng (P1, chứng minh bằng test):
/donenay từ chối khiended_atcó giá trị, và UPDATE đóng buổi thêmAND ended_at IS NULLđể một lượt Rời đua với Xong cũng không lọt. Test mới trongspeakPair/routes.test.ts. - Dò mã phòng 4 số (P1):
/speak-pairs/joincó rate limit riêngspeakPairJoin(20 lần/10 phút), dò hết 9.000 mã mất hơn ba ngày;/donethêm rate limitlearningEvent. - Bundle learn 2,08 MB (P1): sân luyện IELTS (
ielts/Practice) tải lười khi vào/ielts/**; bundle chính còn 1,60 MB, IELTS thành gói riêng 479 KB kèm nội dung Grammar và Speak together. - Câu giới hạn của phép đo bằng tiếng Anh (P0, SRC-953): phiếu điểm đề luyện nay nói tiếng Việt và nói rõ "không phải band IELTS chính thức"; ba dòng dặn dò trước khi làm đề cũng tiếng Việt.
- Lời tour và câu dẫn Learning Plan bằng tiếng Anh (P1, SRC-953): năm lời tour ở trang
/ieltsvà trang kỹ năng, và câu "Đặt mục tiêu, Nemo sẽ lên kế hoạch..." nay tiếng Việt. - Dấu "?" 24px trên điện thoại (P1): vùng bấm nới lên 40px bằng một lớp giả trong suốt, hình tròn giữ nguyên (
CardHint,TaskTypes,TermTitle).
Không làm, có lý do:
- Agent UI đề xuất đổi nút Speak together sang tiếng Việt: ngược chỉ đạo SRC-1084 (trang ấy tiếng Anh đơn giản toàn bộ), bỏ.
- Nhãn nút hành động trong sân luyện đang lẫn Anh và Việt (ExamSit "Back to papers", Grammar "Kiểm tra"): SRC-953 nói nhãn nút tiếng Việt, nhưng chủ dự án cần chốt một chiều trước khi sửa hàng loạt.
- P2 (bậc Grammar chỉ kiểm ở client, cờ
creditedsai khi đã cộng trong ngày, polling khi tab ẩn, NavStats không làm mới, h1 ẩn cho trang kỹ năng, sitemap thiếu bài What's new...) để lượt sau. - Ca vitest
practiceEvidence"xu hướng đổi thì vẫn ghi" đỏ cả khi bỏ mọi thay đổi của lượt này: phụ thuộc thời điểm chạy, không thuộc phạm vi.
132. Audit toàn sân luyện IELTS, lượt 2 (SRC-1097, 28.09.2026)
Năm góc (UI, logic, ngôn ngữ, bảo mật-hiệu năng, nội dung) chạy song song trên toàn /ielts/**. Không có P0 ở mã; một P0 ở NỘI DUNG (đáp án đúng thường là lựa chọn dài nhất: 89% ở Reading, 78% ở Listening) chờ chủ dự án duyệt một lượt viết lại bằng workflow. Chủ dự án: "Fix hết P1".
Đã sửa (P1):
- Cộng giờ không giới hạn:
POST /learning-eventsnay có trần NGÀY 7200 giây cho mỗi learner, cộng mọi nguồn trong sổ (DAILY_CAP_SECONDS); dòng vẫn ghi, chỉ số giây bị cắt về phần còn lại. - "Try again" cộng lại trọn phút: số phút cố định của bài chỉ cộng ở lần làm xong đầu tiên; lượt sau tính theo giây thật client đo, không vượt số phút của bài. Hai test mới trong
ieltsSkills/routes.test.ts. - Bảng xếp hạng quét cả sổ mỗi lần mở: phép tính đệm 60 giây theo isolate, gắn theo binding D1 (
leaderboard/service.ts); chậm tối đa một phút. - Polling khi tab ẩn: Speak together (3 giây) và lớp Live (2,5 giây) thôi hỏi khi
document.hidden, hỏi lại ngay khi quay về tab; đồng hồ 1 giây cũng dừng khi ẩn. - Ngôn ngữ (SRC-953/834): đoạn giới thiệu bốn phần thi (
ExamPart.tsx) sang tiếng Việt; "Các buổi học" → "Sessions", "Bài nói của con" → "Your recordings", nhãn "Bài viết của con" → "Your writing", lỗi micro sang tiếng Anh, tiêu đề gập của Grammar sang tiếng Anh. - Kèm: trang buổi Speak together coi trả lời thiếu
startlà không có buổi (từng hiện NaN).
Còn chờ: P0 nội dung (viết lại lựa chọn sai), các P2 (rate limit hai route nộp bài, lộ mã learner ở bảng xếp hạng, bundle chính 1,6 MB, đáp án Grammar lệch vị trí, audit-content bỏ qua migration lỗi, chủ đề Grammar không kiểm ở máy chủ).
126g. Ai đã đăng ký buổi Speak together (SRC-1098, 28.09.2026)
Trang chi tiết buổi có thẻ "Who is coming (n)": ảnh đại diện hoặc chữ cái đầu và tên hiển thị của từng người đã đăng ký, theo thứ tự đăng ký, đánh dấu "(you)" cho chính mình. API chi tiết trả attendees chỉ gồm tên, ảnh, cờ me (không email, không mã nội bộ), tối đa 60 người. Đăng ký hoặc huỷ xong thì trang tải lại chi tiết để danh sách cập nhật.
137. Làm nốt việc tồn (SRC-1125, 28.09.2026)
Chủ dự án: "Còn nhiều việc vẫn chưa được xử lý. Hãy làm nốt." Các P2 của lượt audit 2 (§132) và các chỗ hụt ghi lại trong những lượt sau:
- Rate limit hai route nộp bài có chấm điểm:
ielts-exam-quiz/submitvàielts-diagnostic/submitnay gọienforceRateLimit(RATE_LIMITS.submit)nhưieltsPractice. - Bảng xếp hạng không lộ mã learner khác: ba route đọc (
/v1/ielts/leaderboard,/weeks,/weeks/{w}) thaylearner_idcủa người khác bằnganon-<n>; chỉ mã của người xem giữ nguyên để màn tô dòng của mình. - Chủ đề Grammar kiểm ở máy chủ:
POST /ielts-grammarchỉ cộng cho mã có trongGRAMMAR_TOPIC_IDS(sinh bởiscripts/gen-grammar-topics.mjstừ file nội dung; test gác khi quên sinh lại). Trước đây mã bịa vẫn ăn 120 giây. - Đáp án Grammar lệch về vị trí đầu:
TaskCardxáo lựa chọn khi hiển thị, cố định theo bài (shuffled); test gác tỉ lệ đáp án đứng đầu dưới 50%. - audit-content không bỏ qua im lặng migration không chạy được trên sqlite3: in tên ra.
- Vân tay video:
src/catalog.ts(danh sách import sinh tự động) ra khỏi vân tay mã dựng hình, để thêm một video không làm mọi video cũ bị coi là đã đổi. Lượt này đổi vân tay của cả kho MỘT lần. - Tài liệu API: tham số
programcủaspeak-schedule(SRC-1120) nay khai trong OpenAPI. - Trang Grammar công khai: câu "đang được soạn dần" thay bằng "cả 103 bài đã có nội dung".
- Bài đo 6 câu cũ trên trang hồ sơ IELTS của cổng learn thay bằng lối vào bài chẩn đoán mới (
/ielts/diagnostic); kết quả cũ vẫn hiện ở cột "Bài đo".
Còn chờ chủ dự án: P0 nội dung (đáp án đúng thường là lựa chọn dài nhất trong kho đọc/nghe; nội dung chỉ nằm trong D1 nên cần một lượt riêng), link Zoom, bốn link nhóm Zalo, video cho 62 bài Grammar, thương hiệu video Grammar, và việc sau tháng 10/2026 của program Speaking.
164. Bốn luật từ audit 03.10.2026 (SRC-1204)
Audit toàn bộ code ngày 03.10.2026 tìm ra bốn chỗ hành vi sản phẩm lệch với ý định. Chủ dự án giao "làm nốt", nên đây là phương án đã chọn - đổi được, nhưng đổi thì sửa ở đây trước.
Đồng hồ dùng thử chỉ chạy khi chính learner mở. Đọc gói IELTS/Grammar hay thưởng học tập từ phiên bố mẹ, mentor, admin trước đó trả
not_started: truevà không ghi dòng nào; trước đó lượt đọc của người khác bắt đầu 14 ngày thử thay learner.Ngày thi gợi ý không phải hạn chót. Chỉ ngày learner đã xác nhận (
confirmed = 1, hoặc lịch học kỳ đã khai) mới tạo mục tiêu, cam kết, chế độ luyện thi và đếm ngược. Kèm theo: Planning trước đó không đọc được ngày thi nào vì câu truy vấn lỗi bị nuốt im lặng; đã sửa.Giới hạn học thử Speaking do server quyết. Mọi learner chưa là thành viên IELTS trả phí còn hạn đều chịu giới hạn buổi thử, không còn phụ thuộc tham số
?programdo client gửi.Trần 2 giờ học mỗi ngày áp cho mọi nguồn ghi giờ (ngữ pháp, lượt nói, bài thang bậc, buổi nói đôi) qua một hàm ghi có kẹp trần, không chỉ đường ghi hoạt động chung.
Mã phòng Speak together giữ 3 chữ số (quyết định 30.09.2026), nhưng mỗi tài khoản chỉ được nhập sai 10 lần một ngày; nhập đúng không tốn lượt.
Đáp án không còn xuống trình duyệt trước khi làm bài. Đề full test ghép từ chính câu luyện tập (
useComposedTests), nên trước đây đáp án nằm sẵn trong tab mạng. Chủ dự án chọn giữ MỘT ngân hàng câu và chấm luyện tập ở server:- Đường nội dung (
/v1/public/ielts/content/*) bỏanswer/accept/why, thêmuid(mã câu đầy đủ) vàpicks(số phương án phải chọn). POST /v1/learners/{id}/ielts-content/checkchấm từng lô câu SAU khi learner trả lời, trảcorrectkèm đáp án và lời giải; gác bằng phiên + quyền với learner, trần 150 lượt/ngày. Logic đúng/sai dùng chung vớiscoreObjective(ieltsContent/grade.ts).- Reading, Listening, Warmup, ExamSit gọi đường chấm, có trạng thái chờ và nút thử lại.
- Script soát nội dung và script sinh fixture e2e lấy khoá bằng
?keys=1với phiên staff/admin; learner gửikeys=1nhận đúng bản không khoá. Bản có khoá trảno-store.
Còn hở có chủ ý: learner gọi tay đường chấm vẫn lấy được khoá từng câu, trong giới hạn 150 lượt một ngày. Đóng hẳn đòi server tự ghép đề (biết câu nào thuộc lượt thi đang mở). Hai phép chuẩn hoá câu điền từ: từ SRC-1225 trang luyện chấm đúng nếu khớp theo MỘT trong hai phép (bản luyện cũ hoặc bản của đề đầy đủ), nên "half-past six" không còn sai khi luyện mà đúng khi thi, và không câu nào đang đúng lại thành sai. Đề đầy đủ giữ nguyên
normaliseStrict, điểm đã chấm không đổi.- Đường nội dung (
Server tự ghép đề và giữ tập câu của lượt (SRC-1229). Đóng chỗ hở ở đoạn trên bằng ba lớp:
- (a)
POST .../ielts-full-tests/{n}/attemptstự ghép đề n (ieltsFullTests/compose.ts, hàm thuần chép phép xếp củaFullTests.tsx/examTests.ts; testfullTestCompose.test.tsở app so hai bên trên cùng dữ liệu) và lưu vàopapers_jsonbốn mã đề kèmquestion_uids(40 câu Listening, 40 câu Reading đúng như trang làm bài rút). Mã đề app gửi lên bị bỏ qua. Khi chấm,scoreObjectivechỉ tính câu trong tập: câu ngoài tập bị bỏ và đếm vàoignored, câu trong tập không gửi tính là bỏ trống. Lượt mở trước SRC-1229 được dựng tập câu lúc bắt đầu phần. - (b) Đường chấm luyện ghi mọi câu đã trả đáp án vào
ielts_content_keys_seen(migration 0331). Lúc ghép, trong các đề của một kỹ năng server chọn đề có ít câu đã lộ nhất; hoà thì theo vị trí bắt đầu từ đề thứ n, và không lặp đề learner đã làm ở ô khác trừ khi hết đề. Không bao giờ chặn đề vì mọi đề đều đã lộ. Learner chưa xem đáp án câu nào thì nhận đúng đề như app ghép. - (c) Trong lúc phần L/R của một lượt
in_progresschưa nộp, đường chấm luyện trả 403TEST_IN_PROGRESScho câu thuộc tập của phần ấy. Nộp phần xong thì hỏi lại được, nên trang làm đề vẫn lấy đáp án ngay sau khi nộp như cũ. Chỉ khoá với lượt mở chưa quá 24 giờ: lượt bỏ dở không có cơ chế hết hạn, và khoá theo trạng thái thì câu của nó bị khoá ở trang luyện mãi mãi. - Cùng đợt:
uuidtrong@cloudflare/realtimekitép lên 11.1.1 bằngoverrides(lỗ hổng GHSA-w5hq-g745-h8pq ở bản 8.3.2; realtimekit chỉ dùngv4vàvalidate).
- (a)
Lịch sử quyết định
Từ mục 51. Một lượt rà cả sân luyện: mã chết, một bài bị đè, và hai cổng nói dối (SRC-931, 21.09.2026)
Chỉ đạo chủ dự án 2026-09-20: "Rà soát và dọn dẹp code của learn.nemo12.com/ielts cho gọn gàng nhé, và đối chiếu với docs.nemo12.com xem sao. Rồi đồng bộ cả hai bên. Docs thiếu thì bổ sung docs, chỉ liên quan tới IELTS thôi, còn tính năng thiếu hoặc data thiếu thì chỉnh data."