Skip to content

Data Semantics ​

Data Dictionary sinh tự động từ migrations/*.sql: nó nói kiểu dữ liệu và ràng buộc. Nó không nói được cột đó nghĩa là gì với một đứa trẻ và một gia đình thật. Trang này trả lời phần còn lại (AS-04.3.2, AS-04.5.4):

  • một bảng tồn tại để làm gì, ai được ghi, ai đọc;
  • một cột hay bị hiểu nhầm thì nghĩa đúng là gì;
  • trường nào là PII và vì sao nó cần tồn tại — không có lý do thì phải xoá cột, không phải viết thêm chú thích;
  • dữ liệu đó giữ bao lâu và xoá bằng đường nào.

Ba luật đọc trang này:

  1. Data dictionary là cơ khí, trang này là phán đoán. Bảng đổi thì npm run gen:reference tự cập nhật dictionary; còn nghĩa nghiệp vụ thì phải người viết. Thêm bảng mới mà không thêm dòng ở đây là nợ.
  2. Trạng thái vẽ ở nơi khác. Mọi enum status và đường chuyển tiếp nằm trong State Machines; ở đây chỉ nói giá trị đó có ý nghĩa gì.
  3. PII được kê khai, không được suy đoán. §5 là danh sách đóng. Trường không có trong §5 thì không được coi là an toàn mặc định — phải bổ sung vào §5 khi thêm.

1. Bản đồ domain ​

76 bảng D1 chia theo ngôn ngữ sản phẩm, không theo thứ tự migration:

DomainBảngCâu hỏi nghiệp vụ domain này trả lời
Danh tính & gia đìnhusers, auth_identities, sessions, families, family_members, learners, guardians, invitations, role_assignments, audit_logAi là ai, ai là người nhà của ai, ai được xem dữ liệu của ai
Quyền riêng tư & vòng đời dữ liệuconsents, data_deletion_requests, retention_policiesBố mẹ đã đồng ý những gì, xoá dữ liệu bằng đường nào, giữ bao lâu
Tri thức (bản đồ môn)subjects, strands, skill_nodes, skill_edges, items, item_versions, labs, lab_contentMôn học gồm những kỹ năng nào, học liệu nào phục vụ kỹ năng nào
Learner Model & bằng chứnglearner_skill_state, learner_evidence, learner_models, learner_model_versions, learner_retention, learner_goals, learner_goal_entries, learner_context_models, learner_context_eventsCon đang vững/lung lay ở đâu, vì bằng chứng nào, và hệ thống đã nghĩ gì ở thời điểm nào
Học & luyệnassessment_sessions, assessment_responses, learning_experiences, learner_experience_stateCon đã làm gì, làm tới đâu, kết quả ra sao
Ngân hàng đề & lịch thiexams, exam_questions, exam_attempts, exam_attempt_answers, learner_exam_targets, semester_exam_schedule, target_schools, school_enrollmentsĐề nào có thật, con thi gì, khi nào
Nội dung (Coral)blueprints, blueprint_weights, content_blueprints, generation_runs, content_reports, content_review_queueNội dung được soạn theo khuôn nào, ai duyệt, báo sai thì đi đâu
Tương tác & cộng đồngforum_topics, interactions, interaction_votes, interaction_votes_rebuiltAi nói gì với ai, nội dung nào bị ẩn
Student Portraitportraits, portrait_versions, portrait_tracks, portrait_learner_sections, portrait_reactions, milestones, external_activities, parent_beliefsBức tranh gia đình vẽ về con — và tiếng nói của chính con
Mentormentor_assignmentsDolphin nào theo sát Nemo nào
Whale (du học)whale_countries, whale_universities, whale_scholarships, whale_rankings, whale_preferences, opportunities, success_storiesCơ hội học ngoài Việt Nam
Orca (thi đấu)competitions, competition_editions, competition_actionsSân thi đấu và bước con đã đi
Run log & vận hànhengine_runs, workflow_runs, workflow_run_steps, queue_dead_letters, rate_limit_bucketsHệ thống đã thật sự chạy chưa, chạy hỏng ở đâu

Bảng interaction_votes_rebuilt là tàn dư của một lần dựng lại bảng vote; giữ để không mất phiếu cũ. Không code nào ghi mới vào đó.


2. Danh tính & gia đình — bảng lõi ​

users — một con người có tài khoản đăng nhập ​

CộtNghĩa nghiệp vụ (ngoài kiểu dữ liệu)
idKhoá danh tính duy nhất. Mọi quyền, mọi nhật ký đều trỏ về đây.
emailChỉ để liên lạc và đối chiếu với Google, tuyệt đối không để phân quyền (RISK-013 — email đổi được, chuyển chủ được). Cấm mọi câu SQL kiểu WHERE email = 'admin@…'.
display_nameTên hiển thị do Google trả về hoặc người dùng tự đặt. Không phải tên pháp lý, không dùng để định danh.
avatar_media_id / avatar_urlẢnh đại diện. Hai cột cùng mục đích, tồn tại vì avatar_url thêm sau (0022) khi bỏ tầng media id. Code mới ghi avatar_url.
statusactive | suspended | deleted. deleted là xoá mềm — hàng còn để nhật ký và đồng thuận không trỏ vào khoảng không; xoá cứng đi qua data_deletion_requests (§7).

learners — một đứa trẻ đang học, KHÔNG bắt buộc có tài khoản ​

Đây là bảng dễ hiểu sai nhất trong hệ. learners không phải users. Một Nemo lớp 6 có thể chưa có tài khoản Google nào: bố mẹ tạo hồ sơ học trước, con đăng nhập sau.

CộtNghĩa nghiệp vụ
user_idNULL nghĩa là con chưa có tài khoản riêng — không phải dữ liệu lỗi. Khi con nhận lời mời và đăng nhập, cột này mới được nối.
family_idGốc của mọi quyết định quyền: người nhà = thành viên của chính family này (shared/authz.ts).
display_nameTên gọi trong nhà ("Bin", "Nem"), không phải tên khai sinh. Đây là lý do nó không được đưa vào context AI (§5).
birth_year / birth_dateChỉ để suy lớp và chọn độ khó phù hợp lứa tuổi. birth_date thêm ở 0003 khi cần chính xác hơn cho lịch thi chuyển cấp.
gradeLớp đang học — dữ liệu vận hành chính, quyết định nội dung nào hiện ra. Đây là trường tuổi tác duy nhất Context Builder được đọc.
statusinvited (đã tạo, chưa nhận) → active → paused → archived. Xem state machines §8.

family_members vs guardians — vì sao có hai bảng ​

Không trùng nhau, và đây là chỗ hay bị nhầm:

  • family_members(family_id, user_id, role) — quan hệ quyền: ai thuộc gia đình nào với vai gì (owner/guardian/supporter/learner). shared/authz.ts chỉ đọc bảng này.
  • guardians(learner_id, user_id, relationship) — quan hệ con người: người này là bố, mẹ, ông bà hay người giám hộ của đứa trẻ cụ thể nào. Dùng để xưng hô và hiển thị, không dùng để chốt quyền.

Đặt quyền vào bảng quan hệ con người sẽ khiến "ông ngoại" và "người giám hộ hợp pháp" có quyền khác nhau vì lý do tình cảm chứ không phải lý do bảo mật. Tách ra là có chủ đích.

role_assignments — vai nội bộ ​

(user_id, role, scope_type, scope_id). Role ∈ learner/guardian/mentor/staff/admin. scope_type mặc định global với scope_id = '*': hôm nay hệ thống chưa thu hẹp phạm vi theo trường/gia đình, cột này là chỗ trống đã chuẩn bị cho ngày cần. Xem permissions.

sessions — một lần đăng nhập, không phải một thiết bị ​

CộtNghĩa nghiệp vụ
token_hashSHA-256 của token, không bao giờ là token thô. Đọc trộm được bảng này cũng không đăng nhập được.
rotated_fromChuỗi xoay vòng: session mới trỏ về session cũ nó thay thế. Dùng để phát hiện token cũ bị dùng lại.
revoked_atĐăng xuất / thu hồi. Session hợp lệ = revoked_at IS NULL và expires_at > now.
clientweb (cookie) hay mobile (bearer) — quyết định luật CSRF nào áp dụng.

audit_log — ai đã mở hồ sơ của con ​

Bảng này chỉ nối thêm, không có đường sửa/xoá qua API (shared/audit.ts).

CộtNghĩa nghiệp vụ
occurred_atCột thời gian duy nhất của bảng. Bảng này cố ý không có created_at — hai cột thời gian cho cùng một sự kiện là lỗi dữ liệu.
actor_roleVai lúc hành động, không phải vai hiện tại: một người bị gỡ role mentor hôm nay không làm thay đổi nhật ký hôm qua.
ip, user_agentPhần "từ đâu". PII — xem §5.
request_idcf-ray của Cloudflare; nối một dòng nhật ký với một request cụ thể và với log của Cloudflare.
detail_jsonChỉ tham chiếu, không nội dung. sanitiseDetail() thay mọi khoá nhạy cảm bằng [redacted] và chuỗi dài bằng [len:n]. Nhật ký sống lâu hơn dữ liệu gốc, nên nội dung nguyên văn trong log là một lần lộ dữ liệu nữa mỗi lần ai đó mở log ra đọc.

Truy cập bằng quyền mượn (mentor/staff/admin) bắt buộc sinh một dòng; truy cập của chính em ấy và của người nhà thì không — nếu ghi cả thì nhật ký toàn tiếng ồn và chìm mất đúng thứ cần thấy.


3. Quyền riêng tư & vòng đời dữ liệu ​

consents — bằng chứng bố mẹ đã đồng ý, theo từng việc ​

Đồng thuận không phải một cờ boolean. Nó là bản ghi có thời điểm, có phiên bản chính sách, rút lại được.

CộtNghĩa nghiệp vụ
learner_idNULL = đồng thuận ở mức gia đình (áp cho mọi con). Index dùng COALESCE(learner_id,'') vì trong SQLite hai NULL là khác nhau — không có COALESCE thì đẻ ra nhiều dòng trùng ở mức gia đình.
guardian_user_idNgười lớn đã thật sự bấm nút, không phải chủ tài khoản trên danh nghĩa.
scope7 việc tách rời, mỗi việc hỏi riêng: account, learning_data, ai_processing, portrait, community, mentor_access, research. Chữ hiện cho phụ huynh nằm ở shared/privacy.ts (SCOPE_PURPOSE) — mục đích viết bằng tiếng người, không phải chú thích kỹ thuật (AS-07.3.1).
granted1 = đang đồng ý. Rút lại là đặt 0 + revoked_at, không xoá dòng.
policy_versionBản chính sách lúc bấm (POLICY_VERSION, hiện 2026-08-15). Đổi nội dung chính sách phải tăng số này; đồng thuận cũ giữ nguyên phiên bản của nó, nên luôn trả lời được "bố mẹ đã đồng ý với bản nào".
evidence_jsonIP, user-agent, màn hình nào, nút nào. Đây là phần biến "chúng tôi nghĩ là có đồng ý" thành bằng chứng.

Luật thi hành: không có dòng granted=1 còn hiệu lực cho một scope thì tính năng của scope đó không được chạy. account và learning_data là bắt buộc (không có thì không có tài khoản); 5 scope còn lại tuỳ chọn.

data_deletion_requests — đường xoá dữ liệu ​

CộtNghĩa nghiệp vụ
subject_type / subject_idXoá cái gì: một learner, một user, hay cả family.
requested_byPhải là phụ huynh/chủ tài khoản — trẻ không tự yêu cầu xoá hồ sơ của mình.
statuspending → in_progress → completed | rejected.
noteĐã xoá những gì và giữ lại gì, vì sao. Đây là cột quan trọng nhất: có nghĩa vụ giữ lại một số bản ghi (đồng thuận, nhật ký), và phải nói ra được.

retention_policies — chính sách giữ dữ liệu khai báo được ​

Bảng khai báo, không phải bảng thi hành: nó nói nên giữ bao lâu; việc dọn thật là job vận hành. Cột rationale bắt buộc — một chính sách không nói được vì sao thì không phải chính sách. 10 dòng đã seed nằm ở §6.


4. Learner Model — cột hay bị hiểu nhầm ​

learner_skill_state — trạng thái hiện tại của một kỹ năng ​

CộtNghĩa nghiệp vụ
mastery0..1 — mức làm được, suy từ bằng chứng. Không bao giờ tự giảm theo thời gian. Quên là chuyện của learner_retention, không phải của mastery.
confidence0..1 — hệ thống chắc bao nhiêu về con số mastery kia. Hai cột này khác nhau hoàn toàn: mastery=0.2, confidence=0.1 nghĩa là "chưa biết", không phải "hổng".
trajectoryXu hướng gần đây (rising/flat/falling) — dùng để chọn lời nói với phụ huynh, không dùng để chọn bài.

learner_evidence — mỗi dòng là một lần quan sát ​

CộtNghĩa nghiệp vụ
observed_performance0..1. Với trắc nghiệm là đúng/sai; với bài dài là điểm chuẩn hoá.
reliabilityBằng chứng này đáng tin bao nhiêu: học sinh tự khai < làm bài có kiểm < thi có giám sát. Đây là chỗ chặn "tự chấm 10 điểm rồi model tin ngay".
occurred_atThời điểm quan sát, không phải thời điểm ghi. Nhập bù dữ liệu cũ vẫn phải đúng thứ tự lịch sử.

learner_model_versions — hôm đó hệ thống nghĩ gì ​

Version chỉ sinh khi content_hash đổi: engine chạy mà kết quả y hệt thì không đẻ version mới (vẫn có dòng engine_runs). Bản superseded là bất biến vĩnh viễn — đó là thứ làm cho câu "vì sao tháng trước hệ thống bảo con nên ôn Hằng đẳng thức" trả lời được.

learner_context_events — lời khai bối cảnh ​

Không phải log, mà là lời khai có thời hạn của con hoặc bố mẹ ("tuần này con ốm", "mỗi ngày học được 30 phút").

CộtNghĩa nghiệp vụ
statementNguyên văn tiếng người — hiện lại đúng câu này khi giải thích vì sao kế hoạch đổi. Vì thế nó là PII (§5).
effective_from / effective_toKhoảng còn hiệu lực. effective_to IS NULL = còn hiệu lực tới khi bị supersede.
source_rolelearner hay parent — cùng một lời khai, hai người nói thì trọng số khác nhau.
confidenceĐộ tin của lời khai, mặc định 0.8.
status / superseded_byLời khai mới không ghi đè lời cũ, nó supersede. Lịch sử giữ nguyên để giải thích quyết định cũ.

learner_experience_state — đã làm xong từng chặng chưa ​

PRIMARY KEY (learner_id, subject_id, unit_key, exp_key). unit_key = "<strand>|<module>|<unit>", exp_key ∈ skip | mid | final | explore:<node> | practice:<node> | practice<n>:<node>. Mastery theo node không đủ để tô đúng ba màu của Unit kiểu Duolingo — phải biết từng Experience đã làm chưa, nên mới có bảng này.

status chỉ có completed và failed: không có in_progress. Đang làm dở là trạng thái ở client, không phải sự kiện đáng ghi vào D1.


5. Kiểm kê PII — mỗi trường một lý do tồn tại ​

AS-04.5.4 (data minimization). Danh sách đóng: trường PII không có trong bảng này là trường chưa được duyệt.

Bảng.cộtLoạiVì sao trường này phải tồn tạiNếu bỏ thì mất gì
users.emailĐịnh danh liên lạcKhớp tài khoản Google khi đăng nhập; là đường duy nhất liên lạc với phụ huynh khi có sự cố tài khoảnKhông đăng nhập lại được, không báo được sự cố
users.display_nameTênXưng hô trong giao diện người lớnGiao diện gọi phụ huynh bằng id
users.avatar_urlẢnhNhận ra đúng tài khoản khi một máy có nhiều người dùngNhầm tài khoản trong gia đình nhiều con
auth_identities.emailĐịnh danh liên lạcEmail tại thời điểm liên kết với provider; giữ riêng vì users.email đổi đượcKhông phát hiện được khi Google đổi email dưới cùng một subject
learners.display_nameTên trẻCon phải thấy tên mình, bố mẹ phải phân biệt được các conKhông phân biệt được con trong cùng gia đình
learners.birth_year, birth_dateTuổiSuy lớp và độ khó phù hợp lứa tuổi; lịch thi chuyển cấp phụ thuộc năm sinhChọn sai độ khó, sai mốc thi
learners.gradeLớpQuyết định nội dung nào hiện ra — dữ liệu vận hành chínhKhông biết dạy gì
invitations.emailEmail người được mờiGửi lời mời tới đúng ngườiChỉ mời được bằng mã dán tay
guardians.relationshipQuan hệ gia đìnhXưng hô đúng (bố/mẹ/ông bà)Giao diện gọi mọi người lớn là "phụ huynh"
audit_log.ipĐịa chỉ mạngTrả lời "từ đâu" khi điều tra truy cập bất thường vào hồ sơ trẻ (AS-07.4.2)Biết ai xem nhưng không biết từ đâu — không đủ để điều tra
audit_log.user_agentThiết bịPhân biệt truy cập từ trình duyệt người thật với scriptKhông phát hiện được truy cập tự động
consents.evidence_jsonIP + ngữ cảnh bấmBiến đồng thuận thành bằng chứng pháp lý có thể trình raCòn lời khẳng định, hết bằng chứng
learner_context_events.statementLời khai nguyên vănHiện lại đúng câu bố mẹ/con đã nói khi giải thích vì sao kế hoạch đổiGiải thích bằng lời máy, gia đình không nhận ra tiếng nói của mình
portrait_learner_sections.items_jsonLời con tự viếtLà tiếng nói của đứa trẻ trong bức tranh về chính nó (SDD-015)Chỉ còn góc nhìn của bố mẹ
parent_beliefs.*Nhận định của phụ huynhGhi lại "bố mẹ nghĩ con thế nào" để đối chiếu với dữ liệu thậtMất một nửa cuộc đối thoại gia đình
portraits.sections_jsonChân dung do bố mẹ viếtNội dung cốt lõi của Student PortraitKhông có sản phẩm
sessions.token_hashBí mật (đã băm)Xác thực từng requestKhông có phiên đăng nhập

Trường cố tình KHÔNG có trong hệ: số điện thoại, địa chỉ nhà, tên trường của con, số căn cước, ảnh chụp trẻ do trẻ tải lên, dữ liệu định vị. Không thu thập thì không phải bảo vệ.

Ranh giới với AI: không trường nào trong bảng trên đi vào prompt. shared/context-builder.ts chỉ đọc learners.grade cộng dữ liệu học tập đã bỏ định danh, gọi trẻ bằng bí danh nemo_<hash>, và có cổng chặn assertNoIdentifiers() chạy trên context đã dựng xong. Xem AI Registry §5.


6. Retention — giữ bao lâu, vì sao chừng đó ​

Khớp đúng 10 dòng đã seed trong retention_policies (migration 0033):

Nhóm dữ liệuBảngGiữLý do chọn đúng con số đó
Danh tínhuserstới khi yêu cầu xoáCần để đăng nhập và để phụ huynh quản lý con — không có mốc thời gian tự nhiên nào để cắt
Hồ sơ học sinhlearnerstới khi yêu cầu xoáLà gốc của mọi tiến độ; xoá hồ sơ là xoá cả lịch sử học
Bằng chứng học tậplearner_evidence1095 ngày (3 năm)Đủ dài để nhìn tiến bộ qua một cấp học, đủ ngắn để không giữ vô hạn
Phiên bản modellearner_model_versions365 ngàyĐủ để giải thích vì sao kế hoạch học đổi; cũ hơn một năm thì không ai còn hỏi
Lịch sử chạy engineengine_runs90 ngàyPhục vụ điều tra sự cố và đối chiếu hoá đơn AI theo quý
Lịch sử chạy workflowworkflow_runs90 ngàyCùng chu kỳ với engine_runs để hai bảng đối chiếu được với nhau
Lời khai bối cảnhlearner_context_events730 ngày (2 năm)Lời khai cũ vẫn giải thích được quyết định cũ của Planning Engine
Nhật ký truy cậpaudit_log730 ngàySự cố quyền riêng tư thường bị phát hiện muộn; 2 năm là khoảng điều tra thực tế
Đồng thuậnconsentsvĩnh viễnBằng chứng pháp lý cho việc thu thập dữ liệu trẻ em — xoá bằng chứng đồng ý là tự bỏ chỗ đứng
Yêu cầu xoá dữ liệudata_deletion_requestsvĩnh viễnBằng chứng đã thực hiện đúng yêu cầu xoá

Nhóm chưa có dòng chính sách (nói thẳng thay vì im lặng): nội dung học (items, item_versions, learning_experiences) giữ vô thời hạn vì nó là tài sản chung, không gắn với một đứa trẻ; sessions hết hạn tự nhiên theo expires_at; rate_limit_buckets là dữ liệu tạm, dọn theo cron; queue_dead_letters chưa khai hạn. Bốn nhóm này cần bổ sung vào retention_policies khi có job dọn thật.

Model version giữ bao nhiêu bản (AS-04.5.3): không giới hạn số bản, giới hạn theo thời gian (365 ngày). Version chỉ sinh khi nội dung đổi nên số bản tăng theo tốc độ thay đổi thật của learner, không theo số lần engine chạy.


7. Đường xoá dữ liệu ​

Phụ huynh gửi POST /v1/privacy/deletion-requests
        │
        ├── ghi data_deletion_requests (status = pending)
        ├── ghi audit_log (ai yêu cầu, khi nào, từ đâu)
        │
        ▼
Vận hành xử lý (status = in_progress)
        │
        ├── xoá: bằng chứng học, model version, lời khai bối cảnh, portrait, nội dung con viết
        ├── ẩn danh: audit_log giữ dòng nhưng dữ liệu trỏ tới đã biến mất
        ├── GIỮ: consents + chính dòng data_deletion_requests (là bằng chứng đã làm đúng)
        │
        ▼
status = completed, ghi `note` = đã xoá gì / giữ gì / vì sao

Ba thứ không bị xoá và phải nói rõ với gia đình khi họ hỏi: bản ghi đồng thuận, bản ghi chính yêu cầu xoá, và nhật ký truy cập đã xảy ra trước đó. Lý do: cả ba tồn tại để bảo vệ chính đứa trẻ, không phải để phục vụ hệ thống.

Trạng thái: đường ghi nhận yêu cầu và đường tra cứu đã chạy (POST / GET /v1/privacy/deletion-requests/{id}); bước xoá thật hiện là thao tác vận hành có người làm, chưa tự động. Ghi ra đây vì im lặng về chỗ này là nói dối.


8. Cột JSON — bên trong có gì ​

Nguyên tắc chung (AS-04.2.5): dữ liệu cần truy vấn thường xuyên phải có cột riêng và index; JSON chỉ chứa thứ đọc nguyên khối.

CộtBên trongVì sao là JSON chứ không phải bảng
items.quality_jsonHồ sơ chất lượng 8 chiều CC-QAF, mỗi chiều {score, evidence}Đọc nguyên khối khi soát nội dung; điểm tổng hợp đã tách ra quality_score để lọc và index
items.screen_flags_jsonMảng mã lỗi máy sàng lọc, vd ["no_node","dup_prompt"]Tập mã mở, thay đổi theo bộ sàng lọc
item_versions.payload_jsonSnapshot đầy đủ nội dung tại version đóBản chụp bất biến — tách cột sẽ khiến schema đổi làm hỏng bản chụp cũ
audit_log.detail_jsonChỉ tham chiếu đã lọc qua sanitiseDetail()Hình dạng khác nhau theo từng loại hành động
consents.evidence_jsonIP, user-agent, màn hình, nútBằng chứng, đọc nguyên khối khi cần trình ra
portraits.sections_jsonCác mục bố mẹ viếtCấu trúc mục do sản phẩm định nghĩa, đổi mà không cần migration
portrait_learner_sections.items_jsonCác mục con viếtCùng lý do; tách bảng riêng với portraits vì quyền ghi khác nhau (chỉ role learner)
learner_models.*_jsonẢnh chụp modelBất biến theo version
engine_runs.input_json / output_jsonĐầu vào/đầu ra một lượt chạyBằng chứng, không truy vấn theo trường bên trong
blueprints/content_blueprints.spec_jsonKhuôn soạn nội dungLà cấu hình do người soạn viết, không phải dữ liệu quan hệ

9. Bắt buộc khi thêm bảng hoặc cột mới ​

  1. Viết comment -- … ngay trên CREATE TABLE trong migration — gen-reference.mjs bốc nó vào data-dictionary.
  2. Thêm dòng vào §1 (bản đồ domain) của trang này.
  3. Cột có ý nghĩa vượt quá cái tên → thêm vào §2/§3/§4.
  4. Cột chạm tới con người → bắt buộc thêm vào §5 kèm lý do tồn tại. Không viết được lý do thì không thêm cột.
  5. Có status → vẽ vào State Machines.
  6. Nhóm dữ liệu mới → thêm dòng retention_policies trong migration, không để lại sau.

Trace ​