Skip to content

State Machines ​

Mọi giá trị enum trong hệ thống, ai được đổi, và đổi theo đường nào. Trạng thái nằm rải rác trong code là nguồn của lỗi âm thầm — trang này gom về một chỗ.

Luật của trang này (AS-04.3.5): mỗi bảng D1 có cột status phải có một mục ở đây, và tập giá trị phải khớp đúng CHECK constraint trong migrations/. Lệch một giá trị là lệch tài liệu, không phải "gần đúng".

Hai loại trạng thái, đừng lẫn:

  • Trạng thái lưu — có cột status trong D1, đổi bằng một hành động cụ thể của ai đó. §5 trở đi.
  • Trạng thái suy ra — không lưu, tính lại mỗi lần đọc từ số liệu model. §1–§4.

Phần A — Trạng thái suy ra (không lưu) ​

1. Skill state — mức vững một node ​

Suy ra từ (mastery, confidence), không lưu, tính bằng stateFor().

confidence < 0.25  →  unknown    ⚪ "Đang làm quen"
mastery ≥ 0.8      →  chac       🟢 "Chắc rồi"
mastery ≥ 0.5      →  lung_lay   🟡 "Đang lung lay"
còn lại            →  hong       🔴 "Cần xây lại từ gốc"

Confidence luôn thắng: chưa đủ bằng chứng thì không được kết luận "hổng". Không có chuyển tiếp trực tiếp — trạng thái đổi khi mastery/confidence đổi.

Màu: đỏ không dùng cho trạng thái học tập; hong là coral ấm ("cần xây lại"), không phải màu lỗi (REQ-UX-03).

2. Need signal — "bài này có cần học không" ​

Nhãn hiển thị trong Phòng Lab, suy từ skill state:

NeedTừ stateNhãn
urgenthongCần học
reviewlung_layNên ôn
solidchacĐã vững
newunknownChưa học

3. Retention group label ​

Từ current_retention (đã decay) + historical_mastery:

historical_mastery < 0.6  →  null           (chưa từng vững — không thuộc bức tranh trí nhớ)
retention ≥ 0.80          →  dang_chac      Đang chắc
retention ≥ 0.65          →  nhac_lai       Cần nhắc lại sớm
retention ≥ 0.45          →  nguy_co_quen   Có nguy cơ quên
còn lại                   →  nen_on_lai     Nên ôn lại

4. Review urgency ​

NONE → LOW → MEDIUM → HIGH → CRITICAL. Bắt đầu từ thang retention rồi điều chỉnh theo ngữ cảnh:

Điều kiệnDịch chuyển
historical_mastery < 0.6ép về NONE
thuộc goal và thi ≤14 ngày+1 bậc
đang chặn ≥3 bài sau+1 bậc
không thuộc goal nào và không có kỳ thi−1 bậc (sàn LOW)

Chi tiết: retention-model §6.1.


Phần B — Danh tính & phiên ​

5. User ​

users.status — migration 0001.

active  ──┬──▶  suspended   (vận hành khoá tạm, đăng nhập lại được sau khi mở)
          └──▶  deleted     (xoá mềm — hàng còn, dữ liệu trỏ tới đã đi qua đường xoá)

deleted không phải xoá cứng: nhật ký truy cập và bản ghi đồng thuận vẫn phải trỏ về một hàng có thật. Xoá thật đi qua data_deletion_requests (§13). Ai đổi: chỉ vận hành/admin.

6. Session ​

sessions — migration 0001. Bảng không có cột status; trạng thái suy từ ba cột thời gian, và đó là chủ đích: một phiên chỉ có thể chết theo ba cách khác nhau về nguyên nhân.

(đăng nhập)  ──▶  active  ──┬──▶  rotated    (quá 7 ngày → cấp token mới, rotated_from trỏ về bản cũ)
                            ├──▶  expired    (quá expires_at)
                            └──▶  revoked    (revoked_at — đăng xuất/thu hồi)

Session hợp lệ = revoked_at IS NULL và expires_at > now. Token lưu dưới dạng SHA-256 hash, không bao giờ lưu thô. client ∈ web | mobile quyết định luật CSRF nào áp dụng, không phải trạng thái.

7. Invitation ​

invitations.status — migration 0001.

pending  ──┬──▶  accepted   (accepted_by + accepted_at được điền)
           ├──▶  expired    (quá expires_at — không ai bấm)
           └──▶  revoked    (người mời rút lại)

Ba nhánh kết là cuối — không quay lại pending. Mời lại là tạo lời mời mới với code mới, không hồi sinh dòng cũ: mã cũ đã có thể lọt ra ngoài. role ∈ learner | guardian | supporter là vai sẽ được cấp khi chấp nhận, không phải trạng thái.

8. Learner ​

learners.status — migration 0001.

invited  ──▶  active  ──▶  paused  ──▶  active
                     └──▶  archived
  • invited — bố mẹ đã tạo hồ sơ, con chưa có tài khoản (user_id IS NULL). Đây là trạng thái bình thường, không phải dữ liệu dở dang.
  • active — đang học.
  • paused — tạm dừng (nghỉ hè, ốm dài). Dữ liệu giữ nguyên, engine không đẩy nhắc nhở.
  • archived — không còn dùng. Không xoá dữ liệu — xoá đi qua §13.

9. School enrollment ​

school_enrollments.status — migration 0003: active → paused → left. Từ paused quay lại active được; left là cuối. Chỉ enrollment active mới sinh goal trong Goal Model.

10. Mentor assignment ​

mentor_assignments.status — migration 0008: active ⇄ ended. Không phải điều kiện truy cập (SRC-037): mentor xem được mọi learner bất kể có assignment hay không; bảng này chỉ nói ai theo dõi chính. Xem permissions.


Phần C — Quyền riêng tư & dữ liệu ​

consents.granted (0/1) + granted_at / revoked_at — migration 0033. Không có cột status; trạng thái là cặp granted + thời điểm.

(chưa hỏi: không có dòng)
        │  phụ huynh bấm đồng ý
        ▼
   granted = 1, granted_at = now, policy_version = bản đang hiệu lực
        │  phụ huynh rút lại
        ▼
   granted = 0, revoked_at = now          ──▶  đồng ý lại: granted = 1, granted_at mới

Ba luật:

  1. Không có dòng ≠ đã từ chối. Không có dòng nghĩa là chưa hỏi — tính năng của scope đó không được chạy, y như bị từ chối, nhưng giao diện phải hỏi chứ không được báo "bạn đã từ chối".
  2. Rút lại không xoá dòng. granted=0 + revoked_at; bảng này là bằng chứng pháp lý.
  3. Đổi chính sách không tự động huỷ đồng thuận cũ. policy_version giữ nguyên bản lúc bấm; muốn áp bản mới thì phải hỏi lại.

7 scope: account, learning_data (bắt buộc — không có thì không có tài khoản), ai_processing, portrait, community, mentor_access, research.

12. Data deletion request ​

data_deletion_requests.status — migration 0033.

pending  ──▶  in_progress  ──▶  completed
      └────────────────────▶  rejected    (kèm lý do trong `note`)

completed bắt buộc điền note: đã xoá gì, giữ lại gì, vì sao. Một yêu cầu xoá kết thúc mà không nói được đã làm gì thì về mặt bằng chứng là chưa làm.

13. Retention policy ​

retention_policies không có trạng thái — là bảng khai báo. Nêu ở đây để trả lời dứt điểm: không có vòng đời, sửa là sửa thẳng, lịch sử nằm ở git của migration.


Phần D — Nội dung ​

14. Item — vòng đời một câu hỏi ​

items.status — migration 0032. Đây là trạng thái quan trọng nhất của khu nội dung.

candidate  ──┬──▶  published  ──▶  retired
             └──▶  (ở nguyên candidate nếu không đạt ngưỡng chất lượng)
Giá trịNghĩaLearner có thấy không
candidateĐã sinh/đã sửa, chờ đạt ngưỡng đánh giá❌
publishedĐang phục vụ learner✅
retiredGỡ khỏi luồng, giữ lịch sử để bài đã làm vẫn giải thích được❌

Mặc định của cột là published — có chủ đích, để 2.146 item đang chạy không biến mất khỏi Phòng Lab ngay sau migration. Item mới do AI sinh luôn bắt đầu ở candidate (AS-10.4.1).

retired không quay lại published: hồi sinh nội dung là tạo version mới và publish version đó (§15).

15. Item version — vì sao không sửa trực tiếp ​

item_versions.status — migration 0032.

candidate  ──┬──▶  published   ──▶  superseded   (version mới hơn được publish)
             └──▶  rejected                       (người soát bác — giữ lại để biết đã bác gì)
  • Mỗi thay đổi nội dung tạo version mới, không sửa bản đang chạy (AS-06.3.1 🔴).
  • Publish là đổi con trỏ, không phải ghi đè: đúng một version published cho mỗi item_id.
  • Rollback = publish lại một version cũ, không sửa tay DB. Đây là lý do superseded phải bất biến.
  • rejected là nhánh cuối; muốn dùng lại ý tưởng đó thì tạo version mới.

16. Content review queue — báo sai thì đi đâu ​

content_review_queue.status — migration 0032.

open  ──▶  in_review  ──┬──▶  resolved    (đã sửa — kèm `resolution`)
                        └──▶  dismissed   (không phải lỗi — vẫn kèm `resolution`)

Hai nguồn đổ vào: người báo (learner/phụ huynh, qua content_reports) và máy sàng lọc (items.screen_flags_json, reported_by IS NULL). Cả hai nhánh kết đều bắt buộc ghi resolution — đó là điều biến "learner báo sai" thành một vòng khép kín thay vì rơi vào hư vô (AS-10.4.5).

17. Content report ​

content_reports.status — migration 0006: open → reviewed | dismissed | actioned. Là sổ nhận báo cáo của khu tương tác; việc xử lý nội dung học đi tiếp sang content_review_queue (§16).

18. Learning experience ​

learning_experiences.status — migration 0019: draft → review → published → deprecated. Experience do AI sinh vào draft, không tới learner cho tới khi người soát duyệt.

19. Content blueprint ​

content_blueprints.status — migration 0016: draft → active → deprecated. Blueprint deprecated không sinh nội dung mới nhưng nội dung đã sinh từ nó vẫn sống.

20. Generation run ​

generation_runs.status — migration 0016.

queued  ──▶  running  ──┬──▶  review  ──▶  done
                        └──▶  failed

review là trạng thái riêng có chủ đích: lô đã sinh xong về mặt kỹ thuật nhưng chưa ai nhìn. Không có review thì done sẽ nói dối.

21. Lab ​

labs.status — migration 0037: draft → live → retired. Mặc định live.

22. Exam — đề thi ​

exams.status — migration 0004: draft → published → archived.

Đề thi thật đã publish là bất biến (QG-011): sửa nội dung đề thật = tạo đề mới, không sửa tại chỗ. source phân biệt generated (hệ thống soạn) với đề có nguồn thật; archived giữ để bài làm cũ vẫn chấm lại được.

23. Exam attempt & assessment session ​

Hai bảng, cùng hình dạng — exam_attempts.status (0004) và assessment_sessions.status (0002):

in_progress  ──┬──▶  completed
               └──▶  abandoned   (bỏ dở, không tính vào bằng chứng có độ tin cao)

abandoned không bị xoá: bỏ dở giữa chừng cũng là một tín hiệu về độ khó và độ dài bài.

24. Learner experience state ​

learner_experience_state.status — migration 0028: chỉ hai giá trị, completed | failed.

Không có in_progress — có chủ đích: đang làm dở là trạng thái ở client, ghi vào D1 sẽ đẻ ra hàng loạt hàng rác mỗi lần learner đóng tab. failed = Học vượt (skip) sai câu nên trượt lượt đó.

Khoá: (learner_id, subject_id, unit_key, exp_key). exp_key ∈ skip | mid | final | explore:<node> | practice:<node> | practice<n>:<node>.


Phần E — Learner Intelligence ​

25. Model version ​

(mới)  ──▶  active  ──▶  superseded

Chỉ có một active cho mỗi (learner_id, model_kind). Version mới chỉ sinh khi content_hash đổi — engine chạy ra kết quả y hệt thì không đẻ version, nhưng vẫn có dòng engine_runs (§27). Đã superseded thì bất biến vĩnh viễn; đó là điều làm cho việc "xem lại hôm đó hệ thống nghĩ gì" trở nên khả thi.

26. Goal ​

Horizon (theo số ngày còn lại):

≤ 14 ngày  → operational      ≤ 90 → tactical      > 90 → strategic      không deadline → undated

Status:

BảngGiá trịGhi chú
learner_goalsactive | achieved | droppedGoal mức tổng
learner_goal_entriesactive | achieved | droppedTừng mục tiêu do Goal Engine tái sinh từ origin_kind/origin_id
learner_exam_targetsactive | droppedKhông có achieved — trường mục tiêu thì hoặc còn theo đuổi, hoặc bỏ; "đỗ rồi" là sự kiện ngoài hệ

Goal biến mất khỏi Goal Model → dropped (không xoá, giữ lịch sử). Xuất hiện lại → active. Khoá idempotent (learner_id, origin_kind, origin_id) giữ cho engine chạy lại không đẻ trùng.

Conflict flags: deadline_passed · time_competition. Engine chỉ gắn cờ, không tự giải quyết.

27. Learner context event — lời khai bối cảnh ​

learner_context_events.status — migration 0030.

active  ──┬──▶  expired      (quá effective_to)
          └──▶  superseded   (lời khai mới cùng loại — superseded_by trỏ tới bản mới)

Lời khai mới không ghi đè lời cũ. Đây là lý do Planning Engine giải thích được "vì sao tuần trước kế hoạch nhẹ hơn": lời khai "con đang ốm" vẫn còn nguyên ở trạng thái superseded.

kind ∈ time_budget, illness, exam_soon, busy, stress, motivation, focus_subject, other. source_role ∈ learner | parent.


Phần F — Vận hành ​

28. Workflow run ​

workflow_runs.status — migration 0027.

running  ──┬──▶  succeeded
           └──▶  failed

workflow_run_steps.status có thêm skipped: succeeded | failed | skipped. Một run có thể failed mà vẫn có step succeeded — xử lý lỗi từng phần.

Run kẹt ở running quá lâu = worker chết giữa chừng. Đó là tín hiệu vận hành thật, không phải giá trị rác — đừng dọn nó bằng cách ép về failed mà không ghi lý do.

29. Engine run ​

engine_runs.status — migration 0027: running → succeeded | failed.

Cột đi kèm mang nghĩa mà status không nói được:

CộtNghĩa
produced_change0 = engine chạy xong, kết quả không đổi nên không sinh version mới. Đây là kết quả thành công, không phải hỏng.
produced_model_kind / produced_versionVersion đã sinh, nối sang learner_model_versions (§25)
workflow_run_idEngine chạy trong khuôn khổ workflow nào; NULL = chạy lẻ

30. Queue dead letter ​

queue_dead_letters.replay_status — migration 0036. Cột cho phép NULL, và NULL có nghĩa riêng.

NULL (vừa rơi vào DLQ, chưa ai xử lý)
   │
   ├──▶ pending     (đã xếp hàng chờ replay)
   ├──▶ replayed    (đã chạy lại thành công — replayed_at được điền)
   ├──▶ failed      (chạy lại vẫn hỏng)
   └──▶ discarded   (quyết định bỏ — message không còn ý nghĩa, vd learner đã bị xoá)

Message vào đây sau max_retries = 5 lần thất bại. payload_json giữ nguyên văn để replay đúng như cũ; INSERT OR IGNORE theo (queue_name, message_id) nên cùng một message chỉ nằm một dòng. Xem queues.

31. Rate limit bucket ​

rate_limit_buckets không có trạng thái — (key, window_start, count). window_start đổi thì count reset về 1. Nêu ở đây để không ai đi tìm state machine của nó.


Phần G — Tương tác & chân dung ​

32. Interaction & forum topic ​

BảngGiá trịGhi chú
interactionsvisible → hidden → blockedblocked là cuối; nội dung giữ lại để điều tra, không xoá
forum_topicsopen → hidden | lockedlocked = còn đọc được, không trả lời thêm

Nội dung bị report vào hàng chờ kiểm duyệt (§17); media phải duyệt trước khi public (QG-008).

33. Portrait & nhánh liên quan ​

BảngGiá trị
portraitsactive → archived
portrait_tracksplanned → active → paused → done | dropped
milestonesplanned → in_progress → achieved | missed | rescheduled

portrait_reactions không có status — giá trị phản ứng là agree · unsure · disagree. Ba giá trị này không đổi hành vi Recommendation Engine: chúng là tiếng nói của đứa trẻ trong cuộc trò chuyện gia đình, không phải tín hiệu điều khiển hệ thống.

portrait_learner_sections cũng không có status — nội dung con viết chỉ có "đã viết" hoặc chưa; quyền ghi giới hạn ở role learner (AS-07.3.3).

34. Orca — thi đấu ​

BảngGiá trị
competitionsactive (mặc định) — không có CHECK, tập giá trị chưa chốt
competition_editionsupcoming (mặc định) — không có CHECK

Ghi thẳng khoảng trống: hai cột này chưa có ràng buộc DB, nên bất kỳ chuỗi nào cũng ghi vào được. Cần thêm CHECK khi Orca ra khỏi giai đoạn thử.


35. Bắt buộc khi thêm trạng thái mới ​

  1. CHECK (status IN (...)) trong migration — không có CHECK thì cột không phải trạng thái, nó là ô ghi chú.
  2. Thêm mục vào trang này với đủ: giá trị, mũi tên chuyển tiếp, ai được đổi, và nhánh nào là cuối.
  3. Nói rõ giá trị mặc định và vì sao chọn giá trị đó (mặc định sai làm dữ liệu cũ đổi nghĩa im lặng).
  4. Nếu có giá trị mang nghĩa "chưa ai xử lý" (NULL, open, queued) → nói rõ nó khác gì với "đã xử lý và kết luận là không làm gì".
  5. Nghĩa nghiệp vụ của bảng đó → data-semantics.

Trace ​