@educa-corp/sdd-framework 0.9.5 → 0.9.6

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 (55) hide show
  1. package/bin/build.js +11 -1
  2. package/bin/lint-trace.js +397 -28
  3. package/bin/self-check.js +183 -12
  4. package/bin/trace-schema.json +2656 -1981
  5. package/core/FRAMEWORK_VERSION +1 -1
  6. package/core/commands/dev-gen-test.md +62 -0
  7. package/core/commands/generate-code.md +1 -1
  8. package/core/commands/generate-tech-docs.md +3 -3
  9. package/core/commands/map-testids.md +88 -11
  10. package/core/commands/qc-analyze.md +509 -425
  11. package/core/commands/qc-design-test.md +475 -247
  12. package/core/commands/qc-plan.md +134 -93
  13. package/core/commands/qc-review.md +216 -131
  14. package/core/commands/qc-run-test.md +346 -231
  15. package/core/commands/validate-traces.md +17 -2
  16. package/core/rules/workflow.md +40 -0
  17. package/core/skills/qc/qa-analyst/DOC_GAP.template.md +9 -1
  18. package/core/skills/qc/qa-designer/shared/duplicate-check-procedure.md +33 -5
  19. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +24 -0
  20. package/core/skills/qc/qa-planner/test-plan.md +7 -0
  21. package/core/skills/qc/qa-runner/functional/gui-feature.md +1 -1
  22. package/core/skills/qc/qa-runner/functional/gui-screen.md +1 -1
  23. package/core/steps/qc-scope.md +67 -11
  24. package/core/steps/qc-stamp.md +142 -0
  25. package/core/steps/report-footer.md +13 -5
  26. package/core/templates/tech-design.template.md +3 -3
  27. package/docs/01-getting-started/quickstart.md +4 -3
  28. package/docs/02-concepts/architecture.md +14 -0
  29. package/docs/02-concepts/glossary.md +8 -0
  30. package/docs/02-concepts/overview.md +3 -2
  31. package/docs/02-concepts/pipeline-steps/04-bdd.md +1 -1
  32. package/docs/02-concepts/pipeline-steps/05-tech-docs.md +21 -5
  33. package/docs/02-concepts/pipeline-steps/06-code.md +12 -2
  34. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +60 -12
  35. package/docs/02-concepts/pipeline-steps/README.md +4 -3
  36. package/docs/02-concepts/traceability.md +2 -2
  37. package/docs/03-guides/architect.md +2 -2
  38. package/docs/03-guides/developer.md +5 -2
  39. package/docs/03-guides/tester-qa.md +17 -5
  40. package/docs/04-reference/commands.md +6 -3
  41. package/docs/04-reference/trace-schema.md +1 -1
  42. package/docs/explain/07-generate-tech-docs.md +5 -3
  43. package/docs/explain/08-review-tech-docs.md +15 -3
  44. package/docs/explain/09-generate-code.md +30 -4
  45. package/docs/explain/10-review-code.md +1 -1
  46. package/docs/explain/11-map-testids.md +72 -70
  47. package/docs/explain/12-dev-gen-test.md +1 -1
  48. package/docs/explain/15-qc-analyze.md +14 -2
  49. package/docs/explain/16-qc-plan.md +5 -1
  50. package/docs/explain/17-qc-design-test.md +26 -3
  51. package/docs/explain/18-qc-review.md +6 -2
  52. package/docs/explain/19-qc-run-test.md +29 -6
  53. package/docs/explain/20-qc-report.md +5 -2
  54. package/docs/explain/README.md +4 -1
  55. package/package.json +1 -1
@@ -1 +1 @@
1
- 0.9.5
1
+ 0.9.6
@@ -7,6 +7,11 @@
7
7
  > **dev self-test** để QC thấy dev đã tự chạy check của mình.
8
8
 
9
9
  ## Gate
10
+
11
+ *Checkpoint: **chặn CỨNG** — ghi đè file test đã có → XOÁ bản sửa tay của dev (mock, fixture, expectation đã chỉnh) mà chính `/dev-run-test` bảo họ làm. `--yes` KHÔNG bỏ qua được (gate Bước 3a).*
12
+
13
+ *Mức cứng chỉ áp khi file test **đã tồn tại**. Lần sinh đầu đi thẳng. Xem §Chạy lại.*
14
+
10
15
  # Gate — Quy trình vào chuẩn cho mọi lệnh
11
16
 
12
17
  Mọi lệnh PHẢI chạy gate này trước khi thực thi phần logic riêng của nó.
@@ -684,6 +689,63 @@ bên trong từng sub-agent.
684
689
 
685
690
  ---
686
691
 
692
+ ## Output — lệnh này ghi những gì, ở đâu
693
+
694
+ | Ghi gì | Người sửa tay? |
695
+ |---|:---:|
696
+ | File **unit / integration / E2E test** theo layout của `{active_module}` | ✅ **có** — xem §Chạy lại |
697
+ | Sổ trace: `test_count` · `test_classes` · `dev_selftest` | ❌ lệnh ghi |
698
+
699
+ > **Vì sao mục này tồn tại** *(G84)*. Trước đó §Output chỉ khai `Trace: …tsv updated` — **không
700
+ > khai file test nó ghi ra**. Một lệnh không khai mình ghi gì thì không ai nghĩ tới việc **bảo vệ
701
+ > thứ nó ghi**. Đúng lỗi gốc của G77 ở lane QC. Cột *"người sửa tay?"* là câu hỏi mà
702
+ > `bin/trace-schema.json` → `artifact_writers` ép mọi lệnh trả lời.
703
+
704
+ ## Chạy lại — file test đã tồn tại *(GIỮ phần đã có, sinh phần THIẾU)*
705
+
706
+ **Dev SỬA TAY chính các file này.** `/dev-run-test` nói thẳng:
707
+
708
+ > `:260` — *"Test fail → `/fix-bug {TICKET_ID}` (bug thật) hoặc **fix test** (sai expectation)"*
709
+ > `:137` — *"`TimeoutError` | Test gọi LLM endpoint live | **Thêm mock; kiểm tra test fixture**"*
710
+
711
+ Nên chuỗi bình thường là **sinh → chạy → đỏ → dev sửa tay → chạy lại**, và lần sinh sau **không được**
712
+ là lần xoá.
713
+
714
+ | Tình huống | Xử lý |
715
+ |---|---|
716
+ | File test đã có, `.feature` **không đổi** | **Không sinh lại.** Không hỏi gì — ca thường nhất |
717
+ | `.feature` có SC **mới** | Sinh **chỉ** test cho SC mới; giữ nguyên file/class đã có |
718
+ | File test có **thay đổi chưa commit** | **DỪNG** — xem dưới |
719
+
720
+ **Nhận diện "đã sửa tay" bằng `git status` / `git diff`** trên các đường dẫn ở §Output. Không cần ai
721
+ đánh dấu gì, và nó bắt được **mọi** lần sửa — kể cả lần người sửa quên đánh dấu.
722
+
723
+ ```
724
+ ❌ {n} file test có thay đổi chưa commit — có thể là bản sửa tay của bạn
725
+ (mock, fixture, expectation đã chỉnh). Sinh lại sẽ XOÁ chúng.
726
+ Files: {danh sách}
727
+ Cách đúng: commit bản sửa trước, rồi chạy lại.
728
+ Cố ý bỏ hết và sinh từ trắng: thêm --force
729
+ ```
730
+
731
+ **KHÔNG đoán.** Thấy thay đổi chưa commit thì lệnh **không tự phân biệt** *"dev vừa sửa"* với *"lần
732
+ sinh trước chưa commit"* — hai ca đó nhìn giống hệt nhau trên `git status`. **Dừng oan một lần rẻ
733
+ hơn xoá nhầm một lần.**
734
+
735
+ > **Vì sao mặc định là "sinh phần thiếu" chứ không phải "đừng sinh".** `/qc-run-test` (G77) có vòng
736
+ > đời **chạy nhiều / sinh ít** nên mặc định của nó là *"CHẠY, đừng sinh lại"*. Lệnh này **chỉ sinh**,
737
+ > không chạy gì — copy nguyên mặc định đó sang là **copy nhầm ngữ cảnh**. *Cùng khuôn không có nghĩa
738
+ > là cùng thuốc.*
739
+ >
740
+ > **Vì sao ca "không đổi gì" phải HOÀN TOÀN im lặng.** Sinh lại test là việc làm mỗi lần spec đổi.
741
+ > Bản vá nào làm nó hỏi mỗi lần thì người dùng sẽ thêm `--force` vào alias — và lúc đó mất sạch lớp
742
+ > bảo vệ này.
743
+
744
+ Có `--force` → report **bắt buộc** khai:
745
+ ```
746
+ ⚠️ --force: sinh lại từ trắng — đã xoá {n} file test có thay đổi chưa commit: {danh sách}
747
+ ```
748
+
687
749
  ## Output
688
750
 
689
751
  **Đọc `.agent/steps/report-footer.md`** và áp đúng khuôn footer trong đó (Status Badge ·
@@ -657,7 +657,7 @@ DTOs → Entity/Model → Repository → Service interface → Service impl →
657
657
 
658
658
  Mỗi element **có action** (button, input, link, select, toggle, form-submit) PHẢI mang một **test-id ổn định** để QC định vị trực tiếp (không scan runtime):
659
659
 
660
- 1. **Nguồn id — bảng §4.5.6 là HỢP ĐỒNG, không phải gợi ý.** Đọc **§4.5.6 Test Selectors** cho platform này ở tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`, lọc theo cột "Phục vụ SC" khớp SC của UC này, rồi lấy id **nguyên văn**. Bảng do `/map-testids` ghi ở phase Tech Design — **trước** lệnh này.
660
+ 1. **Nguồn id — bảng §4.5.6 là HỢP ĐỒNG, không phải gợi ý.** Đọc **§4.5.6 Test Selectors** cho platform này ở tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md`, lọc theo cột "Serves SC" khớp SC của UC này, rồi lấy id **nguyên văn**. Bảng do `/map-testids` ghi ở phase Tech Design — **trước** lệnh này.
661
661
 
662
662
  **Bảng rỗng hoặc không có row nào cho SC của UC này** → *không tự sinh id rồi đi tiếp*. Cảnh báo rồi **để người quyết**:
663
663
 
@@ -614,7 +614,7 @@ sở hữu (DB) vs lấy live (API ngoài), và thao tác ghi chính.}
614
614
  trong CÙNG nhóm platform (không bao giờ tạo nhóm 4.5 thứ hai cho cùng platform).
615
615
  • §4.5.2–§4.5.5 — tương tự theo màn hình/UC ở chỗ chúng khác nhau.
616
616
  • §4.5.6 Test Selectors — MỘT bảng dùng chung cho cả nhóm platform; cột
617
- "Phục vụ SC" mang (UC · SC) để consumer per-UC lọc row của mình.
617
+ "Serves SC" mang (UC · SC) để consumer per-UC lọc row của mình.
618
618
  Append: platform mới → nhóm "### 4.5 — {platform}" mới; màn hình/UC mới trong
619
619
  platform đã có → thêm sub-block + row vào §4.5.6 (đừng lặp nhóm).
620
620
  Bỏ hẳn §4.5 với PRD backend-only. -->
@@ -670,11 +670,11 @@ sở hữu (DB) vs lấy live (API ngoài), và thao tác ghi chính.}
670
670
  iOS accessibilityIdentifier. Dùng lại CÙNG giá trị id trên web/app cho cùng một
671
671
  element logic.
672
672
  MỘT bảng dùng chung cho cả nhóm platform (phủ mọi màn hình/UC của platform này).
673
- Cột "Phục vụ SC" mang (UC · SC) để consumer per-UC (generate-code / qc) lọc row
673
+ Cột "Serves SC" mang (UC · SC) để consumer per-UC (generate-code / qc) lọc row
674
674
  của mình qua §10. Nhóm §4.5 này vốn đã theo platform, nên platform là ngầm định
675
675
  (khối web → web · SC). -->
676
676
 
677
- | Test-ID | Element | Component (§4.5.1.x) | Action | Phục vụ SC (UC · SC) |
677
+ | Test-ID | Element | Component (§4.5.1.x) | Action | Serves SC (UC · SC) |
678
678
  |---------|---------|----------------------|--------|---------------------|
679
679
  | `{uc}-{screen}-{element}-{type}` | {Nút submit} | {Component} | {submit} | {UC1 · SC1, UC1 · SC3} |
680
680
 
@@ -26,15 +26,25 @@ patch, mọi element đều là `new`. Chỉ chạy Step 1 → 2 → 5.
26
26
  >
27
27
  > ```
28
28
  > /review-tech-docs (APPROVED)
29
- > ├──→ /generate-code FE gắn attribute theo hợp đồng
30
- > └──→ /qc-design-test QC viết test case + script theo CÙNG hợp đồng
29
+ > ├──→ /generate-code FE gắn attribute theo hợp đồng
30
+ > └──→ /qc-analyze → /qc-plan → /qc-design-test QC viết test case theo CÙNG hợp đồng
31
31
  > ```
32
32
  >
33
+ > **Cửa vào làn QC là `/qc-analyze`, KHÔNG phải `/qc-design-test`.** Trạm 3 tiêu thụ output của
34
+ > trạm 1–2 (`REQUIREMENT_ANALYSIS.md` · `DOC_GAP.md` · `TEST_PLAN.md`); trỏ thẳng vào nó là chỉ
35
+ > người ta nhảy vào giữa dây chuyền. Cả ba trạm đầu **không cần code**, nên nhánh QC vẫn song song
36
+ > trọn vẹn với `/generate-code` — chỉ trạm 5 `/qc-run-test` mới cần code chạy được.
37
+ >
33
38
  > Hai nhánh **không chờ nhau** vì cùng đọc một bản đã đóng băng, không đọc output của nhau.
34
39
  > Chạy sau code thì QC phải xếp hàng, và `/generate-code` không có gì để đọc nên sẽ tự sinh id
35
40
  > — hợp đồng thành thứ do code quyết định.
36
41
 
37
42
  ## Gate
43
+
44
+ *Checkpoint: **chặn CỨNG** — ghi đè §4.5.6 đã có → ĐỔI id hợp đồng mà SA viết tay, và kéo theo Step 5b hạ `qc_status` + bump `tech_doc_revision` cho một hợp đồng **chưa hỏng**. `--yes` KHÔNG bỏ qua được (gate Bước 3a).*
45
+
46
+ *Mức cứng chỉ áp khi §4.5.6 **đã có row**. Tech-doc chưa có bảng — brownfield thật, ca lệnh này **sinh ra để phục vụ** — đi thẳng, không CHECKPOINT cứng. Xem Step 2 §Bảng đã có id.*
47
+
38
48
  # Gate — Quy trình vào chuẩn cho mọi lệnh
39
49
 
40
50
  Mọi lệnh PHẢI chạy gate này trước khi thực thi phần logic riêng của nó.
@@ -188,7 +198,7 @@ Mỗi dòng ⚠️/🔴 phải ứng với một trạng thái **context-loader
188
198
  🔴/⚠️ (không chặn ≠ không báo — người đọc log sau này vẫn cần thấy).
189
199
 
190
200
 
191
- *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. Đọc `.feature` FE của UC (web/app), các màn Design Spec của nó, tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` (§4.5.6 của platform, nếu có — bảng này gộp mọi UC của platform, **lọc theo cột "Phục vụ SC" khớp SC của UC này** qua §10), và figma-components catalog cho `active_module`.*
201
+ *Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. Đọc `.feature` FE của UC (web/app), các màn Design Spec của nó, tech-doc gộp `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` (§4.5.6 của platform, nếu có — bảng này gộp mọi UC của platform, **lọc theo cột "Serves SC" khớp SC của UC này** qua §10), và figma-components catalog cho `active_module`.*
192
202
 
193
203
  ## Context
194
204
  **BẮT BUỘC — đọc `.agent/steps/context-loader.md` và thực thi TOÀN BỘ quy trình trong đó**,
@@ -228,6 +238,47 @@ file tối thiểu) thì ghi tên component.
228
238
  - **Reused:** id được áp ở **usage site** (không bake vào component dùng chung) → gán theo cùng quy ước.
229
239
  - **Cross-platform:** nếu §4.5.6 của platform **kia** (block `web`/`app` trong cùng tech-doc gộp) đã có id cho cùng element logic, **dùng lại id value đó** (chỉ attribute khác theo platform) để web và app nhất quán và logic QC tái dùng được.
230
240
 
241
+ ### Bảng §4.5.6 ĐÃ CÓ id cho element này → **DÙNG LẠI AS-IS** *(G83)*
242
+
243
+ Đối xứng với luật *reverse-document* cho code ở gạch đầu tiên. Chỉ gán id mới cho element **chưa có
244
+ row**. Quy ước `{uc-lower}-{screen}-{element}-{type}` là **mặc định cho id MỚI** — **không** phải
245
+ chuẩn để chuẩn-hoá lại id đã có.
246
+
247
+ | Tình huống | Xử lý |
248
+ |---|---|
249
+ | Element **đã có row** trong §4.5.6 | **Giữ nguyên id**. Cập nhật cột `Element`/`Component`/`Action` nếu thật sự đổi; **không đụng cột Test-ID** |
250
+ | Element **chưa có row** | Gán theo quy ước, thêm row mới |
251
+ | Row trong bảng mà element **không còn** trong design-spec/BDD | **Không xoá** — nêu ở report để người quyết *(có thể đang phục vụ UC khác)* |
252
+
253
+ > **Vì sao as-is chứ không chuẩn hoá.** Bảng là **HỢP ĐỒNG**, không phải output của lệnh này. Chuẩn
254
+ > hoá một hợp đồng mà **cả FE lẫn QC đang bám vào**, chỉ vì tên không theo ý lệnh, là đổi hợp đồng
255
+ > **mà không ai yêu cầu** — rồi Step 5b trung thành hạ `qc_status` và bump revision cho một thứ chưa
256
+ > hỏng gì.
257
+ >
258
+ > **Đây là lỗ hổng lâu nhất của lệnh này** *(G83)*. Luật *"đã có test-id → dùng lại as-is"* vốn đã có
259
+ > ở gạch đầu — nhưng **chỉ cho CODE**. Vế cho BẢNG bị thiếu, trong khi bảng mới là hợp đồng còn code
260
+ > là bên phải tuân theo nó. Đo trên dự án thật: **28/28 tech-doc** có §4.5.6 **viết tay**, quy ước
261
+ > riêng (`uc{n}-z2-region` · `uc1-z2-rank-avatar / uc1-z2-rank-name`) mà lệnh này không tái tạo được.
262
+
263
+ ### `--force` — đổi tên hàng loạt *(ca hợp lệ, nhưng phải nói ra)*
264
+
265
+ Đổi convention toàn dự án là việc thật. Không có cờ → **DỪNG**:
266
+
267
+ ```
268
+ ❌ §4.5.6 của {TICKET-ID} ({platform}) đã có {n} id — giữ nguyên theo mặc định.
269
+ Muốn đặt lại tên toàn bộ theo quy ước framework: thêm --force
270
+ ⚠️ --force sẽ đổi {n} id mà FE đang gắn và QC đang bám:
271
+ · code FE trượt locator cho tới khi gắn lại (lint T17/T18 sẽ kêu)
272
+ · .Test.md bám id cũ (lint T19 sẽ kêu)
273
+ · qc_status của mọi SC liên quan → not_run (Step 5b)
274
+ ```
275
+
276
+ Có `--force` → report **bắt buộc** khai:
277
+ ```
278
+ ⚠️ --force: đã đổi {n} id hợp đồng — {danh sách id cũ → mới}
279
+ Chạy lại: /generate-code (FE gắn id mới) · /qc-design-test {UC-ID} (TC bám id mới)
280
+ ```
281
+
231
282
  ## Step 3 — Đảm bảo component tái dùng forward được test-id (catalog) *(chỉ `--from-code`)*
232
283
 
233
284
  Với mỗi component **reused** có action, tra section **`## Test-ID Forwarding`** của catalog (`{paths.domain_knowledge_dir}/figma-components/{active_module}.md`):
@@ -254,14 +305,14 @@ Tạo hoặc cập nhật §4.5.6 (block platform tương ứng) trong tech-doc
254
305
 
255
306
  Mỗi row — **5 cột, đúng thứ tự của template** (`templates/tech-design.template.md` §4.5.6):
256
307
 
257
- `Test-ID | Element | Component | Action | Phục vụ SC (UC · SC)`
308
+ `Test-ID | Element | Component | Action | Serves SC (UC · SC)`
258
309
 
259
310
  - **Component** — trỏ `§4.5.1.x` nếu §4.5.1 đã vẽ; chưa có (ca brownfield ghi file tối thiểu) thì ghi tên component.
260
- - **Phục vụ SC** — danh sách `(UC · SC)` mà id này phục vụ. Một id phục vụ nhiều UC là **bình thường**: tiền tố UC trong tên id chỉ nói UC nào giới thiệu element đó đầu tiên.
311
+ - **Serves SC** — danh sách `(UC · SC)` mà id này phục vụ. Một id phục vụ nhiều UC là **bình thường**: tiền tố UC trong tên id chỉ nói UC nào giới thiệu element đó đầu tiên.
261
312
 
262
313
  > **KHÔNG ghi phân loại `reused`/`existing`/`new` vào bảng.** Nó là phân loại **lúc chạy** của Step 1 (dùng để rẽ nhánh Step 2–4), và nó **đổi theo thời gian** — một element `new` thành `existing` ngay khi dev viết code. Nhét dữ liệu biến thiên vào bảng hợp đồng là làm bảng sai dần mà không ai cập nhật.
263
314
  >
264
- > **Thứ tự cột là load-bearing:** `lint-trace` **T15** đọc cột "Phục vụ SC" theo **vị trí** (ô nội dung cuối cùng) để đối chiếu với `.feature`. Viết sai thứ tự là T15 đọc nhầm ô.
315
+ > **Thứ tự cột là load-bearing:** `lint-trace` **T15** đọc cột "Serves SC" theo **vị trí** (ô nội dung cuối cùng) để đối chiếu với `.feature`. Viết sai thứ tự là T15 đọc nhầm ô.
265
316
 
266
317
  ## Step 5b — Làm mất hiệu lực `qc_status` của SC bị ảnh hưởng *(chỉ khi ĐỔI id đã có)*
267
318
 
@@ -274,13 +325,33 @@ lực ≠ ghi đè"*: *"lệnh nào làm giá trị đó HẾT ĐÚNG thì BẮT
274
325
 
275
326
  ```
276
327
  với mỗi id ĐỔI:
277
- đọc cột "Phục vụ SC" của row đó → danh sách {UC-ID}-SC{N}
328
+ đọc cột "Serves SC" của row đó → danh sách {UC-ID}-SC{N}
278
329
  mở sổ {paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv
279
330
  với mỗi SC trong danh sách:
280
- qc_status → not_run (chỉ hạ từ `pass`/`fail`; đang `not_run`/`skip` thì để yên)
281
- qc_run_at → —
331
+ qc_status → not_run (chỉ hạ từ `pass`/`fail`; đang `not_run`/`skip` thì để yên)
332
+ qc_run_at → —
333
+ tech_doc_revision → @trace.revision MỚI của tech-doc ← bump, xem dưới
282
334
  ```
283
335
 
336
+ ### Bump `tech_doc_revision` — vì sao lệnh này cũng là một bumper *(G64)*
337
+
338
+ Tăng `@trace.revision` ở header tech-doc, rồi ghi số mới vào cột `tech_doc_revision` của **mọi row
339
+ trong các sổ vừa chạm**.
340
+
341
+ §4.5.6 là hợp đồng mà `/qc-design-test` **chép cứng giá trị id vào `.Test.md`** — một artifact **bền,
342
+ nằm lại**. Cơ chế phát hiện lỗi thời của tầng artifact QC (`steps/qc-stamp.md`) hoạt động theo **nhãn**:
343
+ `.Test.md` đóng dấu `tech_doc_revision`, trạm 4–5 so với hiện tại. **Sửa §4.5.6 mà không bump nhãn thì
344
+ cơ chế đó không nổ** — đúng ca quan trọng nhất.
345
+
346
+ > **Vì sao không dựa vào `Next` → `/review-tech-docs`.** Lệnh đó **có** bump (`:396` — set
347
+ > `tech_doc_revision` cho mọi row). Nhưng đường ấy chỉ đúng **khi người dùng đi theo `Next`**, mà
348
+ > Step 5b này tồn tại chính vì ca *"sửa nhanh một id rồi đi tiếp"*. Dựa vào `Next` là dựa vào **kỷ
349
+ > luật**, không phải vào máy — và `rules/workflow.md` gọi chỗ này là **điểm mù tuyệt đối**: *"mọi
350
+ > drift detector so **nhãn version**, không so **nội dung**"*. Không bump là tự tay đi vòng qua nhãn.
351
+ >
352
+ > **Thêm row MỚI thì KHÔNG bump** — cùng điều kiện với việc hạ `qc_status`: chưa `.Test.md` nào bám
353
+ > id mới, không gì hết đúng.
354
+
284
355
  **KHÔNG đụng `qc_owner` và `qc_blocked_by`.** `rules/workflow.md` miễn trừ tường minh hai cột
285
356
  này: chúng là **con trỏ tới bug**, và đổi một test-id không làm con bug biến mất. Xoá đi là mất
286
357
  đường về bug đang mở.
@@ -289,10 +360,16 @@ này: chúng là **con trỏ tới bug**, và đổi một test-id không làm c
289
360
 
290
361
  In ra ở report:
291
362
  ```
292
- ⚠️ {n} scenario có script QC bám id CŨ — qc_status hạ về not_run: {danh sách SC}
293
- Chạy /qc-design-test (hoặc /qc-run-test) lại cho các UC đó.
363
+ ⚠️ {n} scenario có TC/script QC bám id CŨ — qc_status hạ về not_run: {danh sách SC}
364
+ tech_doc_revision bump → {rev mới} (để .Test.md lỗi thời bị phát hiện ở trạm 4-5)
365
+ Chạy /qc-design-test lại cho các UC đó.
294
366
  ```
295
367
 
368
+ > **Chỉ `/qc-design-test`, KHÔNG phải "hoặc `/qc-run-test`"** *(G64 — lời nhắc cũ chỉ sai đường)*.
369
+ > `/qc-run-test` dựng locator từ §4.5.6 **tươi** nên phần locator nó tự cứu — nhưng nó **không sửa**
370
+ > id cũ đã nằm trong `.Test.md`. Ai chọn vế đó sẽ thấy test xanh trở lại và tưởng xong, trong khi tài
371
+ > liệu test vẫn mô tả một element không còn mang id đó.
372
+
296
373
  > **Vì sao hạ chứ không giữ.** `rules/workflow.md`: *"`pass` **không** mang nghĩa 'test đã chạy
297
374
  > xanh' — nó mang nghĩa 'scenario này đã được nghiệm thu theo spec **hiện tại**'."* Một script
298
375
  > định vị bằng id không còn tồn tại thì không nghiệm thu được gì cả. Và QC biết phải chạy lại