Error Catalog
Hợp đồng phản hồi
// thành công
{ "data": … }
// lỗi
{ "error": { "code": "VALIDATION_ERROR", "message": "…" } }Client luôn đọc error.code để rẽ nhánh, và hiển thị error.message cho người dùng. message viết bằng tiếng Việt, cho người đọc — không bao giờ nhét stack trace hay tên bảng vào đó.
15 mã lỗi
Định nghĩa duy nhất tại workers/api/src/shared/errors.ts. Không thêm mã mới mà không sửa file này — enum là hợp đồng.
| Code | HTTP | Nghĩa | Client nên làm |
|---|---|---|---|
VALIDATION_ERROR | 400 | Đầu vào sai (zod) hoặc thiếu ngữ cảnh bắt buộc | Sửa dữ liệu, đừng retry |
AUTHORIZATION_ERROR | 401 | Chưa đăng nhập, phiên hết hạn, Origin sai, thiếu role | Đăng nhập lại hoặc báo không có quyền |
NOT_FOUND | 404 | Không có tài nguyên | Không retry |
LESSON_LOCKED | 403 | Bài nằm ngoài phần học thử, learner chưa có quyền học khoá | Mời ghi danh, đừng bắt đăng nhập lại |
HOST_ONLY | 403 | Người trong phòng nói nhưng không phải host bấm Record/Pause/Stop hoặc chuyển host (SRC-1166) | Ẩn nút điều khiển, đừng bắt đăng nhập lại |
SCOPE_REQUIRED | 403 | Mentor đã xác nhận vai nhưng thiếu phạm vi cho việc này: phát hành nội dung AI Teen (SRC-1284; sửa bản nháp không cần phạm vi từ SRC-1291) | Hiện chế độ chỉ đọc, đừng bắt đăng nhập lại |
NOT_LEARNER | 403 | Phụ huynh, mentor hay staff đọc được learner nhưng ghi thứ chỉ chính learner được ghi: tick bước AI Teen (SRC-1283) | Ẩn ô tick, đừng bắt đăng nhập lại |
UNKNOWN_STEP | 400 | Id bước không có trong bản kê nội dung (gõ sai, bước đã nghỉ) (SRC-1283) | Tải lại nội dung, đừng retry |
STEP_NOT_TICKABLE | 422 | Bước có thật nhưng máy chấm: phần đọc có cổng, câu kiểm (SRC-1283) | Không cho tick tay; bước tự xong khi qua cổng / đúng câu |
CONFLICT | 409 | Xung đột trạng thái (trùng, đã xử lý) | Đọc lại rồi thử lại |
STALE_DRAFT | 409 | Lưu bản nháp với rev cũ: người khác vừa lưu trước (SDD-056 §5) | Tải lại bản mới rồi sửa tiếp |
RATE_LIMITED | 429 | Chạm hạn mức | Đợi theo Retry-After rồi thử lại |
TEMPORARY_FAILURE | 503 | Lỗi tạm của chính ta | Retry có backoff |
DEPENDENCY_FAILURE | 503 | Phụ thuộc ngoài hỏng (AI Gateway, dịch vụ ngoài) | Retry hoặc suy giảm dịch vụ |
INTERNAL_ERROR | 500 | Lỗi không lường trước | Báo lỗi, không retry mù |
Quy ước
401 chứ không 403. Cả "chưa đăng nhập" lẫn "đăng nhập rồi nhưng không có quyền" đều trả 401 AUTHORIZATION_ERROR. Có chủ ý: 403 tiết lộ rằng tài nguyên tồn tại và người này chỉ thiếu quyền — với dữ liệu trẻ em, đó đã là rò rỉ thông tin.
Ngoại lệ thứ hai: HOST_ONLY trả 403 (30.09.2026, SRC-1166): chỉ trả SAU khi đã xác nhận người gọi ở trong phòng, nên không lộ gì họ chưa thấy trên màn hình.
Ngoại lệ thứ ba: SCOPE_REQUIRED trả 403 (07.10.2026, SRC-1284): chỉ trả SAU khi đã xác nhận người gọi là mentor, về nội dung AI Teen vốn công khai, nên không lộ gì; trả 401 thì Dolphin hiểu là phiên hết hạn và đẩy một mentor được quyền xem đi đăng nhập lại.
Ngoại lệ thứ tư: NOT_LEARNER trả 403 (07.10.2026, SRC-1283): chỉ trả SAU khi canAccessLearner đã cho người gọi ĐỌC learner ấy, nên không lộ gì họ chưa thấy; trả 401 thì phụ huynh bị bảo đăng nhập lại mà vẫn không tick được.
Ngoại lệ đầu tiên: LESSON_LOCKED trả 403 (2026-09-05). Lý do của luật trên không áp được ở đây — sự tồn tại của mọi bài học đã công khai tại /v1/public/ielts/catalog cho ielts.nemo12.com, nên 403 không nói thêm điều gì. Còn trả 401 thì gây hại thật: client hiểu là phiên hết hạn và bảo learner đăng nhập lại, làm lại, rồi vẫn không mở được bài.
503 tách làm hai. TEMPORARY_FAILURE là lỗi của ta, DEPENDENCY_FAILURE là lỗi bên ngoài. Phân biệt được thì dashboard mới trả lời được câu "ai đang hỏng".
Suy giảm ≠ lỗi. Có những thứ hỏng mà không trả lỗi, vì nghiệp vụ chính vẫn đúng:
| Sự cố | Hành vi | Log |
|---|---|---|
| Model update hỏng sau khi nộp bài | Học sinh vẫn nộp được | model_update_degraded |
| Ghi run log hỏng | Nghiệp vụ chạy tiếp | RUNLOG_DEGRADED |
| Publish event hỏng | Nghiệp vụ chạy tiếp | EVENT_PUBLISH_FAILED |
Ba dòng log này là tín hiệu quan sát chính của hệ thống — thấy chúng tăng nghĩa là có thứ đang hỏng dù người dùng chưa kêu.
Tương thích ngược
Trong /v1: không bỏ mã lỗi, không đổi HTTP status của mã đã có, không đổi ý nghĩa. Thêm mã mới là thay đổi tương thích; đổi mã cũ thì phải lên /v2 (QG-003).
Trace
- REQ-NFR-09 (error taxonomy), REQ-PLT-04 (OpenAPI sinh từ router).
- Thiết kế: SDD-006 §11.
- Kiểm chứng: QG-003, QG-009.