@educa-corp/sdd-framework 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/bin/build.js +113 -19
  2. package/bin/gate-trace.js +464 -0
  3. package/bin/index.js +418 -146
  4. package/bin/lint-trace.js +602 -0
  5. package/bin/self-check.js +376 -1
  6. package/bin/trace-schema.json +252 -2
  7. package/commands/debug.md +123 -511
  8. package/commands/debug.tmpl +3 -0
  9. package/commands/define-product.md +86 -510
  10. package/commands/dev-gen-test.md +86 -510
  11. package/commands/dev-run-test.md +86 -510
  12. package/commands/dev-smoke-test.md +86 -510
  13. package/commands/extend-prd.md +89 -510
  14. package/commands/extend-prd.tmpl +3 -0
  15. package/commands/fix-bug.md +118 -509
  16. package/commands/generate-architecture.md +94 -515
  17. package/commands/generate-architecture.tmpl +3 -0
  18. package/commands/generate-bdd.md +85 -509
  19. package/commands/generate-code.md +86 -510
  20. package/commands/generate-design-spec.md +86 -510
  21. package/commands/generate-prd.md +89 -510
  22. package/commands/generate-prd.tmpl +3 -0
  23. package/commands/generate-spec-manifest.md +86 -510
  24. package/commands/generate-tech-docs.md +86 -510
  25. package/commands/learn.md +172 -496
  26. package/commands/learn.tmpl +70 -3
  27. package/commands/map-testids.md +86 -510
  28. package/commands/propose-scenario.md +86 -510
  29. package/commands/qc-analyze.md +86 -510
  30. package/commands/qc-design-test.md +86 -510
  31. package/commands/qc-plan.md +86 -510
  32. package/commands/qc-report.md +86 -510
  33. package/commands/qc-review.md +86 -510
  34. package/commands/qc-run-test.md +86 -510
  35. package/commands/refine-prd.md +99 -520
  36. package/commands/refine-prd.tmpl +3 -0
  37. package/commands/report-bug.md +86 -510
  38. package/commands/review-code.md +123 -511
  39. package/commands/review-code.tmpl +3 -0
  40. package/commands/review-context.md +93 -514
  41. package/commands/review-context.tmpl +3 -0
  42. package/commands/review-tech-docs.md +90 -511
  43. package/commands/review-tech-docs.tmpl +3 -0
  44. package/commands/setup-ai-first.md +166 -138
  45. package/commands/setup-ai-first.tmpl +72 -0
  46. package/commands/sync.md +50 -106
  47. package/commands/sync.tmpl +48 -3
  48. package/commands/update-framework.md +16 -103
  49. package/commands/update-framework.tmpl +14 -0
  50. package/commands/validate-traces.md +153 -511
  51. package/commands/validate-traces.tmpl +67 -1
  52. package/core/FRAMEWORK_VERSION +1 -1
  53. package/core/README.md +20 -0
  54. package/core/commands/debug.md +123 -511
  55. package/core/commands/define-product.md +86 -510
  56. package/core/commands/dev-gen-test.md +86 -510
  57. package/core/commands/dev-run-test.md +86 -510
  58. package/core/commands/dev-smoke-test.md +86 -510
  59. package/core/commands/extend-prd.md +89 -510
  60. package/core/commands/fix-bug.md +118 -509
  61. package/core/commands/generate-architecture.md +94 -515
  62. package/core/commands/generate-bdd.md +85 -509
  63. package/core/commands/generate-code.md +86 -510
  64. package/core/commands/generate-design-spec.md +86 -510
  65. package/core/commands/generate-prd.md +89 -510
  66. package/core/commands/generate-spec-manifest.md +86 -510
  67. package/core/commands/generate-tech-docs.md +86 -510
  68. package/core/commands/learn.md +172 -496
  69. package/core/commands/map-testids.md +86 -510
  70. package/core/commands/propose-scenario.md +86 -510
  71. package/core/commands/qc-analyze.md +86 -510
  72. package/core/commands/qc-design-test.md +86 -510
  73. package/core/commands/qc-plan.md +86 -510
  74. package/core/commands/qc-report.md +86 -510
  75. package/core/commands/qc-review.md +86 -510
  76. package/core/commands/qc-run-test.md +86 -510
  77. package/core/commands/refine-prd.md +99 -520
  78. package/core/commands/report-bug.md +86 -510
  79. package/core/commands/review-code.md +123 -511
  80. package/core/commands/review-context.md +93 -514
  81. package/core/commands/review-tech-docs.md +90 -511
  82. package/core/commands/setup-ai-first.md +166 -138
  83. package/core/commands/sync.md +50 -106
  84. package/core/commands/update-framework.md +16 -103
  85. package/core/commands/validate-traces.md +153 -511
  86. package/core/hooks/data-guard.js +174 -83
  87. package/core/hooks/settings.json +2 -1
  88. package/core/rules/workflow.md +30 -4
  89. package/core/steps/capture-lesson.md +34 -1
  90. package/core/steps/context-loader.md +24 -3
  91. package/core/steps/gate.md +92 -35
  92. package/core/steps/report-footer.md +23 -0
  93. package/core/templates/README.md +24 -1
  94. package/core/templates/ci/trace-gate.yml +146 -0
  95. package/core/templates/hooks/pre-push +61 -0
  96. package/docs/02-concepts/architecture.md +25 -6
  97. package/docs/02-concepts/traceability.md +57 -0
  98. package/docs/03-guides/architect.md +63 -0
  99. package/docs/04-reference/commands.md +1 -1
  100. package/docs/04-reference/model-selection.md +32 -19
  101. package/docs/explain/21-validate-traces.md +2 -1
  102. package/docs/explain/27-learn.md +5 -3
  103. package/hooks/data-guard.js +174 -83
  104. package/hooks/settings.json +2 -1
  105. package/package.json +5 -2
  106. package/rules/workflow.md +30 -4
  107. package/steps/capture-lesson.md +34 -1
  108. package/steps/context-loader.md +24 -3
  109. package/steps/gate.md +92 -35
  110. package/steps/report-footer.md +23 -0
  111. package/templates/README.md +24 -1
  112. package/templates/ci/trace-gate.yml +146 -0
  113. package/templates/hooks/pre-push +61 -0
  114. package/scripts/init.sh +0 -49
  115. package/scripts/upgrade.sh +0 -94
@@ -1063,6 +1063,19 @@
1063
1063
  ],
1064
1064
  "vocabularies": {
1065
1065
  "$comment": "Mọi giá trị phải xuất hiện ở ít nhất một actor — giá trị chết là dấu hiệu contract lệch.",
1066
+ "$lint_binding_comment": [
1067
+ "BINDING CHO lint-trace (G38). R6 canh 'giá trị có được actor nào DÙNG không' — đó là",
1068
+ "câu hỏi về contract. Nhưng nó không trả lời được 'file .tsv THẬT có tuân enum này không',",
1069
+ "vì không rule nào mở sổ thật ra xem. bin/lint-trace.js làm việc đó, và nó cần biết mỗi",
1070
+ "vocabulary neo vào ĐÂU. Mỗi vocabulary PHẢI khai đúng một trong:",
1071
+ " column → một cột của TSV chính",
1072
+ " columns → nhiều cột của TSV chính",
1073
+ " aux_table + aux_column → một cột của bảng phụ trong aux_tables",
1074
+ " filename_segment → một đoạn trong tên file sổ",
1075
+ " $lint → cố ý ngoài phạm vi lint, PHẢI nêu lý do",
1076
+ "Thiếu tất cả → self-check.js R8a fail build. Đây là van chống lặp lại chính G38:",
1077
+ "thêm vocabulary mới mà quên dạy checker thì build đỏ, không phải 6 tháng sau mới biết."
1078
+ ],
1066
1079
  "trace_status": {
1067
1080
  "values": [
1068
1081
  "OK",
@@ -1078,13 +1091,19 @@
1078
1091
  "web",
1079
1092
  "app",
1080
1093
  "system"
1081
- ]
1094
+ ],
1095
+ "filename_segment": "{UC-ID}-{platform}.tsv",
1096
+ "$filename_note": "Tên sổ là nơi DUY NHẤT mang platform của row — validate-traces Step 2 lấy platform từ đây để tìm .feature đúng thư mục con. Sổ thiếu đoạn này thì cả sổ vô hình (đúng hình dạng G3/G8)."
1082
1097
  },
1083
1098
  "approval_status": {
1084
1099
  "values": [
1085
1100
  "draft",
1086
1101
  "in-review",
1087
1102
  "approved"
1103
+ ],
1104
+ "columns": [
1105
+ "prd_status",
1106
+ "uc_status"
1088
1107
  ]
1089
1108
  },
1090
1109
  "fe_phase": {
@@ -1094,14 +1113,46 @@
1094
1113
  ],
1095
1114
  "column": "fe_phase"
1096
1115
  },
1116
+ "dev_selftest_status": {
1117
+ "$comment": [
1118
+ "G49 — hai cột này mang enum nhưng trước đó KHÔNG có vocabulary, nên lint T3 không canh:",
1119
+ "`dev_selftest = passed`, `qc_status = OK`, `qc_status = pending` đều đi qua sạch.",
1120
+ "Mà đây là hai cột của G28 ('cờ nói dối') và là hai cột có NHIỀU NGƯỜI GHI NHẤT trong",
1121
+ "schema — 5 lệnh và 3 lệnh, mỗi lệnh ở một nhánh điều kiện khác nhau, tất cả bằng prose.",
1122
+ "Cột dễ lệch giá trị nhất trong sổ, và là cột duy nhất mang enum mà không được canh.",
1123
+ "",
1124
+ "VÌ SAO SỐNG ĐƯỢC QUA G30: G30 mở phạm vi schema cho TAG (@trace.*). Hai cột này không",
1125
+ "có tag tương ứng — chúng chỉ sống trong TSV. Nên chúng nằm ngoài lăng kính của G30, và",
1126
+ "trước G38 thì không có lăng kính nào soi cột-không-có-tag."
1127
+ ],
1128
+ "values": [
1129
+ "pass",
1130
+ "fail",
1131
+ "not_run"
1132
+ ],
1133
+ "column": "dev_selftest"
1134
+ },
1135
+ "qc_run_status": {
1136
+ "$comment": "Xem dev_selftest_status. Có thêm `skip` — QC chủ động bỏ qua scenario (khác not_run: chưa chạy).",
1137
+ "values": [
1138
+ "pass",
1139
+ "fail",
1140
+ "skip",
1141
+ "not_run"
1142
+ ],
1143
+ "column": "qc_status"
1144
+ },
1097
1145
  "seam_status": {
1098
1146
  "values": [
1099
1147
  "PENDING",
1100
1148
  "READY",
1101
1149
  "RESOLVED"
1102
- ]
1150
+ ],
1151
+ "aux_table": "_seams.tsv",
1152
+ "aux_column": "status"
1103
1153
  },
1104
1154
  "audit_flags": {
1155
+ "$lint": "n/a — không sống trong TSV. Cờ audit được tính lúc chạy và sống trong trace-report.json (issues + summary counter); R7 canh đủ ba tầng của chúng. lint-trace chỉ kiểm file trên đĩa, nên không có gì để neo.",
1105
1156
  "values": [
1106
1157
  "PRD_DRIFT",
1107
1158
  "PRD_STALE_REF",
@@ -1120,6 +1171,205 @@
1120
1171
  ]
1121
1172
  }
1122
1173
  },
1174
+ "gate": {
1175
+ "$comment": [
1176
+ "CỔNG CHẶN PR (G39). README khai bằng chữ in đậm rằng 4 cờ này 'chặn PR', kèm câu",
1177
+ "'build xanh + test từng-UC xanh KHÔNG phát hiện được' — đó là điểm bán hàng cốt lõi.",
1178
+ "Nhưng trước G39 không có gì chặn: 0 CI, 0 git hook, 0 exit code. /validate-traces in",
1179
+ "một report vào terminal Claude Code, nơi không CI nào đọc được. Kỷ luật là honor-system,",
1180
+ "và cái gì không chặn thì sau sprint thứ ba không ai làm.",
1181
+ "",
1182
+ "bin/gate-trace.js đọc khối này để biết ĐẾM CỜ NÀO — không hard-code. Vì sao quan trọng:",
1183
+ "gate đọc summary[counter]. Nếu counter bị đổi tên mà gate không biết, `undefined > 0`",
1184
+ "là false ⇒ GATE PASS TRÊN CODE HỎNG. Đó là false-green im lặng, đúng hình dạng G1.",
1185
+ "Nên gate PHẢI fail khi counter nó chờ không có mặt trong report, và self-check R9 canh",
1186
+ "cả hai đầu (counter có trong summary của validate-traces · issues_key có trong issues).",
1187
+ "",
1188
+ "Cờ 🟠 (PRD_DRIFT, BDD_DRIFT, TECHDOC_DRIFT, …) CỐ Ý không nằm đây — chúng là 'code chưa",
1189
+ "theo kịp spec', một loại nợ khác, không làm hỏng luồng đang chạy. Chặn PR vì nợ là làm",
1190
+ "cổng bị tắt. Xem README §Traceability."
1191
+ ],
1192
+ "report_path": "{trace_dir}/trace-report.json",
1193
+ "config_preconditions": {
1194
+ "$comment": [
1195
+ "TẦNG G0 — 'sổ có ĐƯỢC BẢO VỆ không?', hỏi TRƯỚC cả ba tầng kia.",
1196
+ "",
1197
+ "Vì sao tách khỏi `blocking`: `blocking` có nghĩa hẹp là CODE ĐANG HỎNG (4 cờ 🔴), và",
1198
+ "R9(e) tồn tại để giữ đúng ranh giới đó — nó fail build nếu ai nhét một cờ NỢ (🟠) vào.",
1199
+ "Hai điều kiện dưới đây thuộc loại THỨ BA: chưa có gì sai, nhưng LƯỚI AN TOÀN ĐANG TẮT.",
1200
+ "Ví như: động cơ hỏng (🔴) · đến hạn thay dầu (🟠) · CHƯA CÀI DÂY AN TOÀN (G0).",
1201
+ "Trộn chúng vào `blocking` là làm mờ chính ranh giới R9(e) bảo vệ.",
1202
+ "",
1203
+ "Vì sao hỏi TRƯỚC: G1 kiểm rất kỹ cấu trúc của một quyển sổ mà có thể KHÔNG NẰM TRONG",
1204
+ "GIT. Kiểm cấu trúc của thứ sắp mất thì vô nghĩa.",
1205
+ "",
1206
+ "Cách hoạt động: cả hai điều kiện đã được `bin/lint-trace.js` phát hiện dưới dạng WARN.",
1207
+ "gate NÂNG chúng thành lỗi chặn. Một implementation, hai mức nghiêm trọng — cố ý:",
1208
+ " pre-push (lint) → NHẮC. Chặn push vì một dòng cấu hình sẽ dạy dev gõ --no-verify,",
1209
+ " và mất luôn phần lint THẬT (sổ hỏng cấu trúc).",
1210
+ " CI (gate) → CHẶN. Đây là chỗ đúng để chặn.",
1211
+ "Cùng cách phân vai G39 đã chọn cho pre-push vs CI."
1212
+ ],
1213
+ "promote_lint_rules": [
1214
+ {
1215
+ "rule": "T11",
1216
+ "why": "sổ trace đang bị gitignore — nó KHÔNG được lưu vào git, mất là mất toàn bộ (G29)"
1217
+ },
1218
+ {
1219
+ "rule": "T10",
1220
+ "why": "sổ thiếu luật merge — hai nhánh cùng ghi sẽ mất row của nhau, im lặng (G40)"
1221
+ }
1222
+ ]
1223
+ },
1224
+ "checkpoint_levels": {
1225
+ "$comment": [
1226
+ "MỨC CHẶN CHECKPOINT CỦA TỪNG LỆNH (nợ 'nhãn chặn cứng' sau G41).",
1227
+ "",
1228
+ "G41 viết ba mức vào steps/gate.md Bước 3a — nhưng KHÔNG lệnh nào tự khai mình thuộc",
1229
+ "mức nào. Nên agent phải TỰ SUY từ bảng đó mỗi lần đọc: '/extend-prd sửa một PRD đã",
1230
+ "duyệt — có tính là ghi đè file đã có không?'. Suy đúng thì không sao; suy sai một lần",
1231
+ "theo hướng nhẹ hơn thì `--yes` bỏ qua được một cổng đáng ra KHÔNG BAO GIỜ được bỏ.",
1232
+ "",
1233
+ "Đây là hình dạng đã gặp bốn lần trong loạt GAP này: luật ĐÚNG, viết RÕ, và KHÔNG AI",
1234
+ "CANH. G1 ('phải bump sc_version') · G28 ('đổi spec thì hạ qc_status') · G41 ('read-only",
1235
+ "được miễn CHECKPOINT' — gate không thi hành) · và cái này. Ba lần trước đều chữa bằng",
1236
+ "cùng một cách: khai tường minh + để máy canh. R11 là phần 'máy canh'.",
1237
+ "",
1238
+ "KHÔNG liệt kê `normal`: nó là mặc định, và liệt kê ra là tạo một danh sách phải bảo",
1239
+ "trì mãi mãi — thêm lệnh mới mà quên thêm vào đó thì lại sai lần nữa.",
1240
+ "",
1241
+ "PHẠM VI: `hard` là 'ghi đè thứ NGƯỜI DÙNG ĐÃ DUYỆT'. /generate-code · /generate-bdd ·",
1242
+ "/generate-tech-docs · /map-testids CỐ Ý không nằm đây — chúng ghi thứ SINH RA ĐƯỢC từ",
1243
+ "spec, không phải thứ người dùng đã duyệt bằng tay. Ba script migrate cũng không: chúng",
1244
+ "là CLI, dry-run mặc định, không đi qua gate."
1245
+ ],
1246
+ "label_marker": "Checkpoint:",
1247
+ "hard": [
1248
+ { "cmd": "generate-prd", "why": "ghi đè PRD đã có → mất changelog, ĐÁNH SỐ LẠI BR, phá @trace.business_rules trong mọi .feature đã sinh" },
1249
+ { "cmd": "extend-prd", "why": "sửa PRD đã DUYỆT" },
1250
+ { "cmd": "refine-prd", "why": "--resume áp findings trực tiếp vào PRD" },
1251
+ { "cmd": "generate-architecture", "why": "ghi đè architecture.md đã verified_by một người thật" }
1252
+ ],
1253
+ "none": [
1254
+ { "cmd": "review-code", "why": "read-only" },
1255
+ { "cmd": "validate-traces", "why": "read-only (ghi trace-report.json + TSV status, không đụng spec/code)" },
1256
+ { "cmd": "debug", "why": "read-only" },
1257
+ { "cmd": "review-context", "why": "read-only (ghi findings vào .agent/review/)" },
1258
+ { "cmd": "review-tech-docs", "why": "read-only (ghi findings vào .agent/review/)" }
1259
+ ]
1260
+ },
1261
+ "audit_invalidating_tags": {
1262
+ "$comment": [
1263
+ "TAG NÀO TRONG CODE, KHI ĐỔI, LÀM AUDIT HẾT TƯƠI (nợ #4 sau Wave 1).",
1264
+ "",
1265
+ "gate chứng minh 'report khớp SỔ', không chứng minh 'sổ khớp CODE' — việc đó cần",
1266
+ "/validate-traces, một lệnh LLM không chạy được trong CI thường. Nên có lỗ: dev sửa",
1267
+ "code mà không audit lại thì sổ+report vẫn khớp nhau, gate vẫn PASS, và mọi cờ 🔴 nói",
1268
+ "về trạng thái TRƯỚC khi sửa. Job `require-fresh-audit` trong templates/ci/ bịt lỗ đó",
1269
+ "bằng một PROXY: không verify được thì ĐÒI BẰNG CHỨNG có người vừa verify.",
1270
+ "",
1271
+ "VÌ SAO LÀ TAG CHỨ KHÔNG PHẢI `^src/`: bản đầu của job hỏi 'PR có sửa src/ không?' và",
1272
+ "vì thế phải để TẮT mặc định — nó ĐO SAI THỨ. Framework có luật boundary-only tagging",
1273
+ "(docs/02-concepts/traceability.md): chỉ Controller/Handler/Middleware/Steps mang tag;",
1274
+ "Entity/Repository/DTO/Interface/Base KHÔNG. Nên `^src/` chặn PR chỉ sửa một DTO — một",
1275
+ "file không mang lời khẳng định trace nào. Cái gì báo oan thì bị tắt, rồi mất luôn phần",
1276
+ "thật sự cần chặn.",
1277
+ "Câu hỏi đúng không phải 'PR có sửa src/?' mà 'PR có sửa thứ mà report đang KHẲNG ĐỊNH?'",
1278
+ "— và report khẳng định qua tag, tag thì nhìn thấy trong diff.",
1279
+ "",
1280
+ "PHẠM VI: chỉ 7 tag là NGUỒN của 4 cờ chặn PR. Tag version (@trace.prd_version,",
1281
+ "tech_doc_revision, design_spec_version) sinh ra cờ 🟠 — cờ đó cố ý không chặn, nên đổi",
1282
+ "chúng không cần đòi audit lại.",
1283
+ "",
1284
+ "GIỚI HẠN ĐÃ BIẾT: đổi tên class (AuthService → AuthenticationService) không chạm dòng",
1285
+ "tag ⇒ tag-diff bỏ lọt, dù cột `implemented_by` giờ trỏ vào tên không còn. Chấp nhận có",
1286
+ "chủ ý: ca đó ít gặp hơn hẳn ca sửa-code-thường, và nó KHÔNG im lặng (lần /validate-traces",
1287
+ "kế tiếp báo ngay). Còn báo oan thì xảy ra mỗi ngày và hậu quả là CỔNG BỊ TẮT — mất tất cả.",
1288
+ "Biến thể chặt hơn ('PR chạm file nào CÓ chứa tag') ghi trong comment của template.",
1289
+ "",
1290
+ "self-check R10 canh templates/ci/trace-gate.yml có nhắc đủ 7 tag này. Tag đổi tên mà",
1291
+ "file CI không biết ⇒ grep không khớp gì ⇒ job LUÔN XANH ⇒ cổng mù im lặng."
1292
+ ],
1293
+ "ci_template": "templates/ci/trace-gate.yml",
1294
+ "tags": [
1295
+ { "tag": "@trace.implements", "flags": ["ORPHANED", "TRACE_ORPHAN"] },
1296
+ { "tag": "@trace.verifies", "flags": ["ORPHANED", "TRACE_ORPHAN"] },
1297
+ { "tag": "@trace.seam_port", "flags": ["SEAM_UNWIRED"] },
1298
+ { "tag": "@trace.seam_pending", "flags": ["SEAM_UNWIRED"] },
1299
+ { "tag": "@trace.stub", "flags": ["STUB_UNRESOLVED"] },
1300
+ { "tag": "@trace.stub_owner", "flags": ["STUB_UNRESOLVED"] },
1301
+ { "tag": "@trace.stub_for", "flags": ["STUB_UNRESOLVED"] }
1302
+ ]
1303
+ },
1304
+ "blocking": [
1305
+ {
1306
+ "counter": "orphaned_count",
1307
+ "issues_key": "orphaned",
1308
+ "label_fields": ["sc_id", "platform"],
1309
+ "why": "code + test còn trỏ vào scenario đã bị xoá khỏi .feature"
1310
+ },
1311
+ {
1312
+ "counter": "trace_orphan_count",
1313
+ "issues_key": "trace_orphan",
1314
+ "label_fields": ["sc_id", "file"],
1315
+ "why": "tag trong code trỏ vào SC-ID không tồn tại — không chỗ nào khác bắt được"
1316
+ },
1317
+ {
1318
+ "counter": "seam_unwired_count",
1319
+ "issues_key": "seam_unwired",
1320
+ "label_fields": ["port", "consumer_uc"],
1321
+ "why": "luồng ghép đang chạy vào stub thay vì hàng thật"
1322
+ },
1323
+ {
1324
+ "counter": "stub_unresolved_count",
1325
+ "issues_key": "stub_unresolved",
1326
+ "label_fields": ["artifact", "consumer_uc"],
1327
+ "why": "hàm rỗng chưa được lấp, nhưng caller đã gọi"
1328
+ }
1329
+ ],
1330
+ "reconcile": {
1331
+ "$comment": [
1332
+ "Gate đọc trace-report.json — một file CÓ THỂ CŨ. Không kiểm tươi thì gate là sân khấu:",
1333
+ "chạy /validate-traces một lần, commit report, rồi sửa spec/code tuỳ ý — CI đọc report cũ",
1334
+ "và cho qua mãi. So sánh theo thời gian commit KHÔNG dùng được (report và code thường nằm",
1335
+ "trong CÙNG một commit, nên mốc thời gian luôn 'cũ hơn'). Cách dùng được: đối chiếu report",
1336
+ "với chính sổ TSV mà nó khai là đang mô tả. Bất biến dựa vào: /validate-traces Step 6 ghi",
1337
+ "`status` trở lại TSV, nên NGAY SAU một lần chạy, TSV và report phải khớp.",
1338
+ "hard = lệch thì FAIL (đếm số row là tuyệt đối, không nhập nhằng).",
1339
+ "soft = lệch thì WARN (phụ thuộc quy ước đếm — vd ORPHANED được report biểu diễn bằng",
1340
+ "boolean `orphaned` chứ không phải giá trị `status`, nên không ép cứng)."
1341
+ ],
1342
+ "hard": { "total_scs": "số row dữ liệu trong mọi TSV thuộc phạm vi report" },
1343
+ "soft": {
1344
+ "drift_count": "DRIFT",
1345
+ "gap_count": "GAP",
1346
+ "untracked_count": "UNTRACKED",
1347
+ "orphaned_count": "ORPHANED"
1348
+ },
1349
+ "per_sc": {
1350
+ "$comment": [
1351
+ "G48 — đối chiếu TỪNG scenario thay vì chỉ tổng số. So tổng bỏ lọt ca thường gặp nhất",
1352
+ "khi spec tiến hoá: một SC bị xoá + một SC được thêm trong cùng một lần → tổng KHÔNG",
1353
+ "đổi → report cũ vẫn được coi là tươi. Khoá phải gồm platform: sc_id một mình không",
1354
+ "định danh được scenario ('sc_id trùng số giữa các platform là 2 scenario khác nhau').",
1355
+ "",
1356
+ "Chuẩn hoá ORPHANED: JSON CỐ Ý chỉ có 4 giá trị status, và row ORPHANED xuất ra là",
1357
+ "`status: DRIFT` + `orphaned: true` (validate-traces §Rules — để VS Code extension",
1358
+ "switch trên status không rơi vào nhánh không khớp). Nên khi so với cột `status` của",
1359
+ "TSV, phải quy đổi `orphaned:true` → ORPHANED TRƯỚC. Quy ước này ĐƯỢC GHI RÕ nên",
1360
+ "status so được ở mức `hard`; nếu nó chỉ là suy đoán thì phải để `soft`.",
1361
+ "",
1362
+ "BACKWARD-COMPAT: report sinh bởi version trước G48 không có `platform`. Gate rơi về",
1363
+ "so tổng số + in ⚠️ 'kiểm độ tươi đang ở mức yếu', KHÔNG fail — người dùng chưa chạy",
1364
+ "lại /validate-traces không phải là lỗi của họ."
1365
+ ],
1366
+ "key_fields": ["sc_id", "platform"],
1367
+ "compare": "status",
1368
+ "orphaned_flag": "orphaned",
1369
+ "orphaned_status": "ORPHANED"
1370
+ }
1371
+ }
1372
+ },
1123
1373
  "path_keys": {
1124
1374
  "$comment": "Key phải có trong templates/project-context.yaml VÀ được dùng bởi ít nhất một actor. Key khai mà không ai dùng = key chết (GAPS.md G17).",
1125
1375
  "config_file": "templates/project-context.yaml",