@educa-corp/sdd-framework 0.9.7 → 0.9.8

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 (104) hide show
  1. package/bin/qc-base-map.json +13 -11
  2. package/bin/self-check.js +49 -4
  3. package/bin/trace-schema.json +3226 -3187
  4. package/core/FRAMEWORK_VERSION +1 -1
  5. package/core/commands/qc-analyze.md +2 -2
  6. package/core/commands/qc-automation-assess.md +3 -3
  7. package/core/commands/qc-design-script.md +60 -30
  8. package/core/commands/qc-design-test.md +79 -7
  9. package/core/commands/qc-plan.md +1 -1
  10. package/core/commands/qc-report.md +85 -76
  11. package/core/commands/qc-review-script.md +25 -16
  12. package/core/commands/qc-review-testcase.md +8 -7
  13. package/core/commands/qc-run-manualtest.md +1 -1
  14. package/core/commands/qc-run-script.md +15 -8
  15. package/core/modules/qc-playwright-ts/module.yaml +13 -0
  16. package/core/modules/qc-playwright-ts/stack-profile.yaml +99 -0
  17. package/core/modules/qc-wdio-appium/module.yaml +20 -0
  18. package/core/modules/qc-wdio-appium/stack-profile.yaml +107 -0
  19. package/core/skills/qc/qa-analyst/data-flow.md +1 -1
  20. package/core/skills/qc/qa-automation-assess/matrix.md +6 -3
  21. package/core/skills/qc/{qa-runner → qa-designer}/exploratory/session.md +8 -2
  22. package/core/skills/qc/qa-designer/functional/api.md +1 -1
  23. package/core/skills/qc/qa-designer/functional/job.md +128 -0
  24. package/core/skills/qc/qa-designer/integration/api.md +1 -1
  25. package/core/skills/qc/qa-designer/integration/db.md +1 -1
  26. package/core/skills/qc/qa-designer/integration/{kafka.md → queue.md} +20 -4
  27. package/core/skills/qc/qa-designer/shared/skill-decision-tree.md +17 -0
  28. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +17 -0
  29. package/core/skills/qc/qa-reviewer/script/_shared/review-rules.md +121 -0
  30. package/core/skills/qc/qa-reviewer/script/api/auth.md +49 -0
  31. package/core/skills/qc/qa-reviewer/script/api/endpoint.md +89 -0
  32. package/core/skills/qc/qa-reviewer/script/api/security.md +46 -0
  33. package/core/skills/qc/qa-reviewer/script/exploratory.md +2 -2
  34. package/core/skills/qc/qa-reviewer/script/mobile/e2e.md +41 -0
  35. package/core/skills/qc/qa-reviewer/script/mobile/functional.md +90 -0
  36. package/core/skills/qc/qa-reviewer/script/mobile/integration.md +41 -0
  37. package/core/skills/qc/qa-reviewer/script/mobile/non-functional.md +43 -0
  38. package/core/skills/qc/qa-reviewer/script/web/e2e.md +46 -0
  39. package/core/skills/qc/qa-reviewer/script/web/functional.md +111 -0
  40. package/core/skills/qc/qa-reviewer/script/web/integration.md +46 -0
  41. package/core/skills/qc/qa-reviewer/script/web/non-functional.md +49 -0
  42. package/core/skills/qc/qa-reviewer/shared/read-doc-gap-inputs.md +1 -1
  43. package/core/skills/qc/qa-reviewer/shared/review-file-template.md +26 -7
  44. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  45. package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
  46. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  47. package/core/skills/qc/qa-reviewer/test-case/integration.md +1 -1
  48. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  49. package/core/skills/qc/qa-script-designer/_shared/api-conventions.md +94 -0
  50. package/core/skills/qc/qa-script-designer/_shared/file-naming-and-folders.md +109 -0
  51. package/core/skills/qc/qa-script-designer/_shared/mobile-conventions.md +196 -0
  52. package/core/skills/qc/qa-script-designer/_shared/web-conventions.md +257 -0
  53. package/core/skills/qc/qa-script-designer/api/auth.md +43 -0
  54. package/core/skills/qc/qa-script-designer/api/endpoint.md +61 -0
  55. package/core/skills/qc/qa-script-designer/api/security.md +41 -0
  56. package/core/skills/qc/qa-script-designer/mobile/e2e.md +35 -0
  57. package/core/skills/qc/qa-script-designer/mobile/functional/feature.md +32 -0
  58. package/core/skills/qc/qa-script-designer/mobile/functional/screen.md +42 -0
  59. package/core/skills/qc/qa-script-designer/mobile/integration.md +39 -0
  60. package/core/skills/qc/qa-script-designer/mobile/non-functional.md +39 -0
  61. package/core/skills/qc/qa-script-designer/web/e2e.md +36 -0
  62. package/core/skills/qc/qa-script-designer/web/functional/api.md +39 -0
  63. package/core/skills/qc/qa-script-designer/web/functional/gui-feature.md +34 -0
  64. package/core/skills/qc/qa-script-designer/web/functional/gui-screen.md +42 -0
  65. package/core/skills/qc/qa-script-designer/web/integration.md +43 -0
  66. package/core/skills/qc/qa-script-designer/web/non-functional.md +42 -0
  67. package/core/skills/qc/qa-script-runner/mobile/run.md +38 -0
  68. package/core/skills/qc/qa-script-runner/report.md +41 -0
  69. package/core/skills/qc/qa-script-runner/web/run.md +48 -0
  70. package/core/steps/qc-scope.md +43 -0
  71. package/core/steps/report-footer.md +2 -2
  72. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
  73. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +10 -10
  74. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +1 -1
  75. package/docs/02-concepts/traceability.md +1 -1
  76. package/docs/03-guides/developer.md +1 -1
  77. package/docs/03-guides/tester-qa.md +40 -12
  78. package/docs/04-reference/commands.md +1 -1
  79. package/docs/04-reference/modules.md +2 -1
  80. package/docs/explain/17-qc-design-test.md +2 -2
  81. package/docs/explain/19-qc-run-test.md +4 -4
  82. package/docs/explain/20-qc-report.md +1 -1
  83. package/docs/explain/23-fix-bug.md +2 -2
  84. package/docs/plans/qc-surgery/01-checklist.md +18 -6
  85. package/docs/plans/qc-surgery/PLAN_v2.md +295 -0
  86. package/docs/plans/qc-surgery/exec-S-ap-stack-typescript.md +420 -0
  87. package/docs/plans/qc-surgery/exec-S0-guard-cam-stack-cu.md +400 -0
  88. package/docs/plans/qc-surgery/exec-S1-hai-module-thay-qc-playwright.md +267 -0
  89. package/docs/plans/qc-surgery/exec-S2-qa-runner-thanh-script-designer-runner.md +340 -0
  90. package/docs/plans/qc-surgery/exec-S3-viet-lai-tieu-chi-review-script.md +322 -0
  91. package/docs/plans/qc-surgery/exec-S5-an-theo-don-dau-vet-stack-cu.md +292 -0
  92. package/package.json +1 -1
  93. package/core/modules/qc-playwright/stack-profile.yaml +0 -66
  94. package/core/skills/qc/qa-reviewer/script/e2e.md +0 -95
  95. package/core/skills/qc/qa-reviewer/script/functional.md +0 -109
  96. package/core/skills/qc/qa-reviewer/script/integration.md +0 -99
  97. package/core/skills/qc/qa-reviewer/script/non-functional.md +0 -134
  98. package/core/skills/qc/qa-runner/e2e.md +0 -49
  99. package/core/skills/qc/qa-runner/functional/api.md +0 -35
  100. package/core/skills/qc/qa-runner/functional/gui-feature.md +0 -57
  101. package/core/skills/qc/qa-runner/functional/gui-screen.md +0 -61
  102. package/core/skills/qc/qa-runner/integration.md +0 -47
  103. package/core/skills/qc/qa-runner/non-functional.md +0 -49
  104. package/core/skills/qc/qa-runner/report/report.md +0 -37
@@ -0,0 +1,111 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/Automation-Standards.md §4 §6 §7 §8 §9 §10 §11 (Approved)
5
+ ---
6
+
7
+ # Soát script — Web Functional *(Playwright Test + TypeScript)*
8
+
9
+ **Nạp `../_shared/review-rules.md` trước.** File này chỉ có phần riêng của nền web, tầng
10
+ functional.
11
+
12
+ ## Khi nào trigger
13
+ - Soát script cho TC functional (1 màn hoặc đa màn) của một PRD, nền `web`, sau `/qc-design-script`.
14
+
15
+ ## Khi KHÔNG trigger
16
+ - Nền `app` → `../mobile/functional.md` · nền `system` → `../api/endpoint.md`
17
+ - Soát **test case** *(không phải code)* → `/qc-review-testcase`
18
+ - Soát biên bản exploratory → `../exploratory.md`
19
+
20
+ ---
21
+
22
+ ## Phase 1 — Đọc đủ trước khi soát *(R01)*
23
+
24
+ 1. `automation/tests/{TICKET-ID}/<feature>-*.spec.ts` — **mọi** spec của feature
25
+ 2. `automation/pages/<feature>.page.ts` + `base.page.ts`
26
+ 3. `automation/data/<feature>.data.ts` · `fixtures/*.fixture.ts` nếu spec có dùng
27
+ 4. `.Test.md` gốc + `AUTOMATION_ASSESSMENT.md` — để đếm TC `Automatable: Y`
28
+
29
+ **R07 chặn cửa:** spec thiếu header artifact ID / `@trace.verifies` ⇒ **BLOCKER, dừng soát.**
30
+
31
+ ---
32
+
33
+ ## Phase 2 — Bảy nhóm soát của nền web
34
+
35
+ ### A · Page Object *(§4)*
36
+
37
+ - Có `extends BasePage`? Không kế thừa là chép lại method dùng chung → **MAJOR**
38
+ - **Không `expect()` trong Page Object** — assertion thuộc spec. Có ⇒ **MAJOR**
39
+ - Method 1 việc, đặt tên **camelCase động từ trước**: `login()` · `fillEmail()` ⇒ sai tên là MINOR
40
+ - Class `PascalCase` + hậu tố `Page`: `LoginPage` · file `kebab-case`: `login.page.ts`
41
+ - Locator khai **trong** Page Object, **không** hard-code trong spec ⇒ trong spec là **BLOCKER**
42
+
43
+ ### B · Locator *(§6.1 · §6.2)*
44
+
45
+ Thứ tự ưu tiên bắt buộc:
46
+
47
+ ```
48
+ getByRole → getByLabel → getByPlaceholder → getByTestId → getByText → locator('[css]')
49
+ ```
50
+
51
+ - `locator('[css]')` mà **không có comment nêu lý do** ⇒ **MAJOR**
52
+ - Locator bám **class CSS tự sinh** (`.css-1x2y3z`, `.MuiBox-root-42`) ⇒ **MAJOR**
53
+ - XPath ⇒ **MAJOR** *(web có đủ 5 cách tốt hơn)*
54
+ - `.first()` / `.nth(n)` không kèm lý do ⇒ **MINOR** — vị trí đổi là test đổi
55
+ - **test-id lấy từ bảng §4.5.6 của tech-doc**, không dò DOM lúc chạy. Dò DOM ⇒ **MAJOR**
56
+ - Thuộc tính test-id ≠ `data-testid` mà **thiếu** `use: { testIdAttribute: '…' }` trong
57
+ `playwright.config.ts` ⇒ **BLOCKER** — `getByTestId()` sẽ trượt **100 %**, và nó trượt trông
58
+ **y hệt một bug sản phẩm**
59
+
60
+ ### C · Assertion *(§7)*
61
+
62
+ - Dùng **web-first assertion** `await expect(locator).toBeVisible()` — tự retry
63
+ - `expect(await locator.isVisible()).toBe(true)` ⇒ **MAJOR**: chụp giá trị một lần, mất auto-retry
64
+ - Chỉ assert URL mà không assert nội dung ⇒ **MINOR** — điều hướng đúng không có nghĩa hiển thị đúng
65
+ - `expect(true).toBe(true)` hoặc assert không thể sai ⇒ **BLOCKER** *(test xanh giả)*
66
+ - Thiếu assertion âm cho nhánh lỗi mà `.Test.md` có ⇒ **MAJOR**
67
+
68
+ ### D · Wait *(§8)*
69
+
70
+ - `waitForTimeout(…)` ⇒ **MAJOR** — chờ cứng, hỏng theo tốc độ máy
71
+ - Chờ theo **điều kiện**: `expect(...).toBeVisible()` · `waitForResponse` · `waitForURL`
72
+ - Timeout tự đặt < 3 s hoặc > 30 s mà không nêu lý do ⇒ **MINOR**
73
+
74
+ ### E · Test structure *(§5)*
75
+
76
+ - Mỗi test **độc lập**: chạy riêng lẻ vẫn pass. Phụ thuộc thứ tự ⇒ **BLOCKER**
77
+ - `test.describe` gom theo màn/nhóm chức năng; tên test mang **TC-ID**
78
+ - Auth dùng `storageState` / fixture, **không** đăng nhập lại trong từng test ⇒ lặp login là **MINOR**
79
+ - Data từ `data/<feature>.data.ts`, **không** hard-code trong spec ⇒ hard-code là **MAJOR**
80
+ - Tạo data thì phải dọn *(teardown)*; trừ khi chỉ mock/intercept ⇒ thiếu dọn là **MAJOR**
81
+
82
+ ### F · Code quality *(§9)*
83
+
84
+ - TypeScript **strict**: không `any`, không `as any` ⇒ có là **MINOR**
85
+ - Kiểu trả về khai tường minh cho method public
86
+ - Thứ tự import: Playwright → Page Object → data → helper
87
+ - Logic lặp ≥3 lần chưa tách ⇒ **MINOR**
88
+
89
+ ### G · Anti-pattern *(§10)* và flaky *(§11)*
90
+
91
+ - Test flaky **không mang tag `@flaky`** mà vẫn nằm trong suite chính ⇒ **MAJOR**
92
+ - Tag `@flaky` có, nhưng **không có ghi chú điều tra** ⇒ **MINOR**
93
+ - `test.skip` không nêu lý do ⇒ **MINOR**; `test.only` sót lại ⇒ **BLOCKER** *(CI chạy 1 test rồi báo xanh)*
94
+
95
+ ---
96
+
97
+ ## Phase 3 — Kiểm cơ học *(bắt buộc — `_shared` §5)*
98
+
99
+ ```
100
+ npx tsc --noEmit
101
+ npx playwright test automation/tests/{TICKET-ID}/<feature>-*.spec.ts --list
102
+ ```
103
+
104
+ Số test collect được **phải bằng** số TC `Automatable: Y` của feature này. Lệch ⇒ **BLOCKER**.
105
+
106
+ ---
107
+
108
+ ## Output
109
+
110
+ Theo `_shared/review-rules.md` §3 — verdict suy từ số đếm, kèm **≥2–3 điểm tốt** *(R05)*.
111
+ Ghi vào `{qc_artifact_dir}test-cases/REVIEW_SCRIPT_<FEATURE>.md`.
@@ -0,0 +1,46 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/Automation-Standards.md §5 §7 §8 (Approved)
5
+ ---
6
+
7
+ # Soát script — Web Integration *(GUI ↔ Backend · API · DB)*
8
+
9
+ **Nạp `../_shared/review-rules.md` trước**, rồi `functional.md` cùng nền — file này chỉ thêm
10
+ phần riêng của tầng tích hợp.
11
+
12
+ ## Khi nào trigger
13
+ - Soát script kiểm **giao tiếp giữa hai thành phần**: UI gọi API, API ghi DB, sự kiện qua queue.
14
+
15
+ ## Khi KHÔNG trigger
16
+ - Chỉ kiểm UI một màn → `functional.md` · chỉ kiểm endpoint → `../api/endpoint.md`
17
+
18
+ ---
19
+
20
+ ## Thêm gì so với functional
21
+
22
+ ### A · Ranh giới thật, không mock
23
+
24
+ - Tầng này tồn tại để kiểm **đường dây thật**. Mock hết hai đầu ⇒ **BLOCKER** — test không kiểm gì
25
+ - Mock **một** đầu để cô lập lỗi thì được, nhưng phải ghi rõ đầu nào thật
26
+
27
+ ### B · Đối chiếu hai phía
28
+
29
+ - Assert trên UI **và** đối chiếu dữ liệu phía server *(qua `request` của Playwright)*
30
+ - Chỉ assert UI ⇒ **MAJOR**: màn hình hiển thị đúng **dữ liệu cũ** vẫn là bug, UI assertion không bắt được
31
+
32
+ ### C · Trạng thái và thứ tự
33
+
34
+ - Data tạo ở bước trước phải được **dọn** kể cả khi test fail giữa chừng *(dùng `finally`/fixture)*
35
+ - Chờ theo **điều kiện của hệ**: `waitForResponse(...)` thay vì chờ cứng ⇒ chờ cứng là **MAJOR**
36
+ - Test phụ thuộc bản ghi có sẵn trên môi trường ⇒ **MAJOR** — môi trường sạch là fail oan
37
+
38
+ ### D · Mã lỗi
39
+
40
+ - Nhánh lỗi tích hợp *(timeout, 5xx, kết nối đứt)* có được kiểm không? Thiếu hẳn ⇒ **MAJOR**
41
+
42
+ ---
43
+
44
+ ## Kiểm cơ học + Output
45
+
46
+ Như `functional.md` Phase 3 và §Output.
@@ -0,0 +1,49 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/Automation-Standards.md §7 §8 §10 (Approved)
5
+ ---
6
+
7
+ # Soát script — Web Non-Functional *(hiệu năng · bảo mật · a11y · tương thích)*
8
+
9
+ **Nạp `../_shared/review-rules.md` trước**, rồi `functional.md` cùng nền.
10
+
11
+ ## Khi nào trigger
12
+ - Soát script kiểm thuộc tính **phi chức năng**: thời gian đáp ứng, kiểm soát truy cập, khả năng
13
+ tiếp cận, chạy đa trình duyệt.
14
+
15
+ ---
16
+
17
+ ## Thêm gì so với functional
18
+
19
+ ### A · Ngưỡng phải là con số, không phải cảm tưởng
20
+
21
+ - Assert hiệu năng **không có ngưỡng cụ thể** ⇒ **BLOCKER** — không có ngưỡng thì không có kết quả
22
+ - Ngưỡng hard-code trong spec thay vì đọc từ config/data ⇒ **MINOR**
23
+ - Đo bằng `Date.now()` quanh một `await` ⇒ **MINOR**; ưu tiên `page.evaluate(performance…)` hoặc
24
+ `waitForResponse` + timing của response
25
+
26
+ ### B · Bảo mật
27
+
28
+ - Payload thử injection **hard-code trong spec** ⇒ **MAJOR** — để ở `data/`
29
+ - Kiểm quyền: phải assert **bị chặn** *(403/redirect/không thấy phần tử)*, không chỉ assert
30
+ "không lỗi" ⇒ assert yếu ⇒ **MAJOR**
31
+ - Credential thật trong code ⇒ **BLOCKER**
32
+
33
+ ### C · Khả năng tiếp cận
34
+
35
+ - Dùng thư viện a11y qua Playwright *(vd `@axe-core/playwright`)*, assert trên **số vi phạm theo
36
+ mức**, không assert "có chạy"
37
+ - Không nêu tiêu chuẩn áp dụng (WCAG mức nào) ⇒ **MINOR**
38
+
39
+ ### D · Tương thích
40
+
41
+ - Chạy đa trình duyệt bằng **`projects` trong `playwright.config.ts`**, không tự lặp trong spec
42
+ ⇒ tự lặp ⇒ **MAJOR** *(mất song song, mất report tách theo project)*
43
+ - Chỉ khai một browser rồi gọi là "kiểm tương thích" ⇒ **MAJOR**
44
+
45
+ ---
46
+
47
+ ## Kiểm cơ học + Output
48
+
49
+ Như `functional.md` Phase 3 và §Output.
@@ -16,7 +16,7 @@ upstream_sha: 3caa1562399ef1167ac2dc620add2c6c729e55df
16
16
  2. **`Read` toàn bộ DOC_GAP.** Định vị mục **`## Tài liệu đầu vào đã đọc để phân tích`** — bảng liệt kê **đầy đủ** file nguồn (cột "Đường dẫn", tính từ `{paths.specs_dir}/`), kèm vai trò & phiên bản.
17
17
  3. **`Read` TỪNG file trong bảng đó** — ghép prefix `{paths.specs_dir}/` vào đường dẫn ở cột. Đọc HẾT, không bỏ sót dòng nào (spec chính + ref bắt buộc + transitive 1-hop). Nếu bảng liệt kê phiên bản, kiểm tra file hiện tại khớp; lệch phiên bản → ghi chú vào REVIEW.
18
18
  4. **Đối chiếu chéo:** file nào có mặt ở header DOC_GAP ("Tài liệu nguồn (spec chính)"/"Coverage Attestation") mà thiếu trong bảng → vẫn đọc.
19
- 5. Chỉ sau khi đã nạp xong toàn bộ input + file domain (`business-dictionary.md`, `product-definition/`) mới bắt đầu review — dùng chúng làm chuẩn đối chiếu để phán quyết `APPROVED` / `NEEDS_FIX`.
19
+ 5. Chỉ sau khi đã nạp xong toàn bộ input + file domain (`business-dictionary.md`, `product-definition/`) mới bắt đầu review — dùng chúng làm chuẩn đối chiếu để phán quyết `APPROVED` / `REVISION_REQUIRED` / `REJECTED`.
20
20
 
21
21
  ## Nguyên tắc
22
22
 
@@ -12,7 +12,7 @@ upstream_sha: bd596393ecc016cbea106835681a4979c0a9ca0e
12
12
  >
13
13
  > | | Upstream | Ở đây | Vì sao |
14
14
  > |---|---|---|---|
15
- > | Verdict | `APPROVE` / `REJECT` | **`APPROVED` / `NEEDS_FIX`** | Đâytừ vựng chung của framework — `/review-code` dùng đúng cặp này. Đổi theo upstreamlàm ba lệnh review nói ba kiểu |
15
+ > | Verdict | `APPROVE` / `REJECT` | **4 mức của `AGT-006`** *(xem §Verdict)* | **Đổi 2026-09-17, Bước S · S3.** Trước đó `APPROVED`/`NEEDS_FIX` cho khớp `/review-code`. Nay lane QC có `AGT-006` §3.2 định nghĩa **severity thành bốn ô đếm được**, nên bốn verdict chỉ là **tên của bốn ô đó** không thêm phán quyết nào, và ngưỡng qua cửa đã khai sẵn ở `AGT-005:132`. Lane DEV *(`/review-code` · `/review-context` · `/review-tech-docs`)* **giữ 2 mức** vì chưa có bảng severity tương ứng; ở đó 4 tên sẽ bốn nhãn phải tự đoán. **Bất đối xứng có lý do, không phải chỗ quên đồng bộ** |
16
16
  > | Bảng Tổng quan | mỗi vòng **ghi đè** hàng của tầng đó | mỗi vòng **thêm một hàng** | Ghi đè thì mất điểm vòng trước, và **không ai thấy được điểm có tăng không**. Các bảng chi tiết vẫn ghi đè (chúng là trạng thái hiện tại, tích luỹ chỉ thành nhiễu) |
17
17
 
18
18
  # Khuôn file Review (dùng chung)
@@ -41,7 +41,7 @@ upstream_sha: bd596393ecc016cbea106835681a4979c0a9ca0e
41
41
  - **1 bullet Expected / TC (ATOMIC — đội QC chốt 2026-07-09):** mỗi TC có đúng **1**
42
42
  `#### Expected Result` với **ĐÚNG 1 bullet**; Test Steps chỉ `[Action]`/`[Verify]`, **không**
43
43
  còn `→ [Expected]` ở bước. Đếm nhanh: số header `### TC_` = số `#### Expected Result` = số
44
- bullet Expected. **TC có ≥2 bullet → `NEEDS_FIX` `[MULTI_BULLET_EXPECTED]`**, yêu cầu tách
44
+ bullet Expected. **TC có ≥2 bullet → `MAJOR` `[MULTI_BULLET_EXPECTED]`**, yêu cầu tách
45
45
  thành nhiều TC độc lập (mỗi TC 1 bullet, giữ full steps, đánh số lại tuần tự).
46
46
  - **Soi bullet 2-kết-cục ẩn (ATOMIC = 1 KẾT CỤC, không chỉ đếm bullet):** bullet nối bằng `;`
47
47
  kiểu "khẳng-định + phủ-định" (grep bullet chứa `;` **và** `không/KHÔNG`, vd
@@ -87,26 +87,45 @@ upstream_sha: bd596393ecc016cbea106835681a4979c0a9ca0e
87
87
  - **Oracle tự chứa và đúng nguồn:** chuỗi sao-nguyên-văn (tiêu đề · nhãn · chữ trên nút · thông
88
88
  báo lỗi) mà TC assert phải ghi nguyên văn trong Expected Result **và** khớp design-spec/PRD của
89
89
  **chính feature này**. Soi kỹ chuỗi **mượn nhầm từ feature anh em** (vd *"tạo tài khoản"* vs
90
- *"tạo câu hỏi"*). Chuỗi chưa có nguồn chốt mà bị bịa oracle → `NEEDS_FIX`; đúng ra phải để
90
+ *"tạo câu hỏi"*). Chuỗi chưa có nguồn chốt mà bị bịa oracle → `MAJOR`; đúng ra phải để
91
91
  `Status: PENDING` hoặc gắn `🚫 Block: [GAP-UC{N}-{nnn}]`.
92
92
 
93
93
  ## Thang điểm
94
94
 
95
95
  **Điểm chất lượng `XX/100`:** trừ **5đ** mỗi lỗi `FAIL`, trừ **2đ** mỗi `WARN`.
96
96
 
97
+ > **Hai bộ nhãn, một phép ánh xạ.** Nhãn cũ `FAIL`/`WARN` dùng để **trừ điểm**; nhãn
98
+ > `BLOCKER`/`MAJOR`/`MINOR`/`SUGGESTION` *(`AGT-006` §3.2)* dùng để **suy verdict**. Ánh xạ:
99
+ > `FAIL` ≡ `BLOCKER` ∪ `MAJOR` · `WARN` ≡ `MINOR` ∪ `SUGGESTION`. Ghi finding thì dùng nhãn
100
+ > **bốn mức** — nó phân biệt được *"không chạy được"* với *"chạy được nhưng khó bảo trì"*,
101
+ > thứ mà `FAIL` gộp làm một.
102
+
97
103
  | Điểm | Nghĩa |
98
104
  |---|---|
99
105
  | **≥ 80** | đạt |
100
106
  | 60–79 | cần cải thiện |
101
107
  | < 60 | không đạt |
102
108
 
103
- **Verdict suy ra được, không chấm cảm tính:**
109
+ **Verdict suy ra được, không chấm cảm tính — và suy từ SỐ ĐẾM LỖI, không từ điểm:**
104
110
 
105
111
  ```
106
- điểm 80 VÀ không còn lỗi FAIL chặn APPROVED
107
- ngược lại NEEDS_FIX
112
+ 1 BLOCKER REJECTED
113
+ 0 BLOCKER, ≥1 MAJOR REVISION_REQUIRED
114
+ 0 BLOCKER, 0 MAJOR, ≥1 MINOR/SUGGESTION → APPROVED_WITH_SUGGESTIONS
115
+ 0 BLOCKER, 0 MAJOR, 0 MINOR/SUGGESTION → APPROVED
116
+
117
+ đi tiếp được: APPROVED · APPROVED_WITH_SUGGESTIONS (ngưỡng ≥ APPROVED_WITH_SUGGESTIONS)
118
+ phải sửa lại: REVISION_REQUIRED · REJECTED
108
119
  ```
109
120
 
121
+ > **Điểm `XX/100` vẫn giữ, nhưng nó KHÔNG quyết verdict.** Điểm là con số để **so hai vòng soát
122
+ > với nhau**; verdict là kết luận **đi tiếp hay không**. Khi hai thứ mâu thuẫn — điểm 75 mà
123
+ > 0 BLOCKER + 0 MAJOR — thì **số đếm thắng** *(`AGT-006` R06: verdict phải phản ánh đúng finding
124
+ > counts, không được override)*, và chỗ phải sửa là **trọng số trừ điểm**, không phải verdict.
125
+ >
126
+ > **Vì sao không để điểm quyết:** một lỗi BLOCKER duy nhất vẫn có thể ra 85 điểm nếu mọi mục khác
127
+ > tốt — và 85 điểm mà script không dịch được là một con số nói dối.
128
+
110
129
  > **Vì sao là số, không phải chữ cái.** Framework trước đây ghi `Score: A / B / C / D` mà **không
111
130
  > có luật chấm** — nên hai người soát ra hai kết quả, và vòng 2 không so được với vòng 1. Một con
112
131
  > số có luật trừ điểm làm được cả ba: **so được các vòng** · **verdict suy ra được** · **hai
@@ -177,7 +196,7 @@ review sẽ làm `/qc-design-script` nhặt nó lên như một file test case r
177
196
  | Tầng | Kịch bản còn thiếu |
178
197
  |---|---|
179
198
 
180
- ## Điều kiện để APPROVED (khi đang NEEDS_FIX)
199
+ ## Điều kiện để lên `APPROVED` (khi đang `REVISION_REQUIRED` / `REJECTED`)
181
200
 
182
201
  | Tầng | Điều kiện cần sửa |
183
202
  |---|---|
@@ -123,7 +123,7 @@ hàng của tầng mình, KHÔNG ghi đè tầng khác).
123
123
  Mỗi tiêu chí: ✅ PASS | ⚠️ WARN | ❌ FAIL + evidence cụ thể (TC ID / journey)
124
124
 
125
125
  **Điểm `XX/100`** — trừ 5đ mỗi `FAIL`, 2đ mỗi `WARN`. ≥80 đạt · 60–79 cần cải thiện · <60 không đạt.
126
- **Verdict:** `điểm 80` không còn `FAIL` chặn**`APPROVED`**; ngược lại **`NEEDS_FIX`**.
126
+ **Verdict:** suy từ **số đếm lỗi** theo `shared/review-file-template.md` §Verdict `≥1 BLOCKER``REJECTED` · `≥1 MAJOR` `REVISION_REQUIRED` · chỉ MINOR/SUGGESTION → `APPROVED_WITH_SUGGESTIONS` · sạch → `APPROVED`. Điểm `XX/100` vẫn ghi, nhưng **không quyết verdict**.
127
127
 
128
128
  Danh sách journey thiếu TC; TC cần sửa Expected; TC vi phạm isolation.
129
129
  Điền thêm bảng **Coverage journey/path (E2E)** ở cuối file review.
@@ -84,7 +84,7 @@ hàng của tầng mình, KHÔNG ghi đè tầng khác).
84
84
  Mỗi tiêu chí: ✅ PASS | ⚠️ WARN | ❌ FAIL + evidence cụ thể (charter ID)
85
85
 
86
86
  **Điểm `XX/100`** — trừ 5đ mỗi `FAIL`, 2đ mỗi `WARN`. ≥80 đạt · 60–79 cần cải thiện · <60 không đạt.
87
- **Verdict:** `điểm 80` không còn `FAIL` chặn**`APPROVED`**; ngược lại **`NEEDS_FIX`**.
87
+ **Verdict:** suy từ **số đếm lỗi** theo `shared/review-file-template.md` §Verdict `≥1 BLOCKER``REJECTED` · `≥1 MAJOR` `REVISION_REQUIRED` · chỉ MINOR/SUGGESTION → `APPROVED_WITH_SUGGESTIONS` · sạch → `APPROVED`. Điểm `XX/100` vẫn ghi, nhưng **không quyết verdict**.
88
88
 
89
89
  Mỗi charter: ✅ PASS | 🔧 REWORK | ✂️ SPLIT | 🔗 MERGE + feedback cụ thể.
90
90
  Charter bổ sung nếu còn gap.
@@ -117,7 +117,7 @@ hàng của tầng mình, KHÔNG ghi đè tầng khác).
117
117
  Mỗi tiêu chí: ✅ PASS | ⚠️ WARN | ❌ FAIL + evidence cụ thể
118
118
 
119
119
  **Điểm `XX/100`** — trừ 5đ mỗi `FAIL`, 2đ mỗi `WARN`. ≥80 đạt · 60–79 cần cải thiện · <60 không đạt.
120
- **Verdict:** `điểm 80` không còn `FAIL` chặn**`APPROVED`**; ngược lại **`NEEDS_FIX`**.
120
+ **Verdict:** suy từ **số đếm lỗi** theo `shared/review-file-template.md` §Verdict `≥1 BLOCKER``REJECTED` · `≥1 MAJOR` `REVISION_REQUIRED` · chỉ MINOR/SUGGESTION → `APPROVED_WITH_SUGGESTIONS` · sạch → `APPROVED`. Điểm `XX/100` vẫn ghi, nhưng **không quyết verdict**.
121
121
 
122
122
  Đề xuất TC cần thêm/sửa/xoá, sắp theo priority. Liệt kê TC thiếu Trace BR (⚠️) cần bổ sung.
123
123
 
@@ -113,7 +113,7 @@ hàng của tầng mình, KHÔNG ghi đè tầng khác).
113
113
  Mỗi tiêu chí: ✅ PASS | ⚠️ WARN | ❌ FAIL + evidence cụ thể (TC ID / điểm tích hợp)
114
114
 
115
115
  **Điểm `XX/100`** — trừ 5đ mỗi `FAIL`, 2đ mỗi `WARN`. ≥80 đạt · 60–79 cần cải thiện · <60 không đạt.
116
- **Verdict:** `điểm 80` không còn `FAIL` chặn**`APPROVED`**; ngược lại **`NEEDS_FIX`**.
116
+ **Verdict:** suy từ **số đếm lỗi** theo `shared/review-file-template.md` §Verdict `≥1 BLOCKER``REJECTED` · `≥1 MAJOR` `REVISION_REQUIRED` · chỉ MINOR/SUGGESTION → `APPROVED_WITH_SUGGESTIONS` · sạch → `APPROVED`. Điểm `XX/100` vẫn ghi, nhưng **không quyết verdict**.
117
117
 
118
118
  Điểm tích hợp thiếu TC; TC Expected mờ nhạt; TC DB thiếu cleanup.
119
119
  Điền thêm bảng **Coverage handshake (Integration)** ở cuối file review.
@@ -124,7 +124,7 @@ hàng của tầng mình, KHÔNG ghi đè tầng khác).
124
124
  Mỗi tiêu chí: ✅ PASS | ⚠️ WARN | ❌ FAIL + evidence cụ thể (TC ID)
125
125
 
126
126
  **Điểm `XX/100`** — trừ 5đ mỗi `FAIL`, 2đ mỗi `WARN`. ≥80 đạt · 60–79 cần cải thiện · <60 không đạt.
127
- **Verdict:** `điểm 80` không còn `FAIL` chặn**`APPROVED`**; ngược lại **`NEEDS_FIX`**.
127
+ **Verdict:** suy từ **số đếm lỗi** theo `shared/review-file-template.md` §Verdict `≥1 BLOCKER``REJECTED` · `≥1 MAJOR` `REVISION_REQUIRED` · chỉ MINOR/SUGGESTION → `APPROVED_WITH_SUGGESTIONS` · sạch → `APPROVED`. Điểm `XX/100` vẫn ghi, nhưng **không quyết verdict**.
128
128
 
129
129
  Danh sách TC Expected mờ nhạt (thiếu ngưỡng); loại non-functional thiếu coverage.
130
130
  Ghi rõ TC nào cần môi trường đặc biệt. Điền thêm bảng **Chi tiết NFR** ở cuối file review.
@@ -0,0 +1,94 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/API-Testing-Standards.md §4 §5 §6 §10 §11 · AGT-010 §2 §3 (Approved)
5
+ ---
6
+
7
+ # Quy ước viết script API — TypeScript + Playwright (API mode)
8
+
9
+ **Nạp file này trước mọi file lane `api/`.** Nó là bản song song của `web-conventions.md` cho
10
+ nền `system`; hai file **không** kế thừa nhau vì đối tượng khác hẳn.
11
+
12
+ > ⚠️ **API Object ≠ Page Object.** Không màn hình, không locator, không chờ. Mang thói quen web
13
+ > sang đây là viết ra thứ chạy được nhưng sai kiến trúc — và `/qc-review-script` lane `api` sẽ
14
+ > chặn nó bằng `R01`.
15
+
16
+ ## 1 · API Object *(§4 · AGT-010 §2.2 · R01)*
17
+
18
+ ```typescript
19
+ // api/booking.api.ts
20
+ export class BookingAPI extends BaseAPI {
21
+ async create(payload: BookingPayload): Promise<APIResponse> {
22
+ return this.ctx.post('/bookings', { data: payload });
23
+ }
24
+ }
25
+ ```
26
+
27
+ | Luật | Vi phạm là |
28
+ |---|---|
29
+ | **Mọi HTTP request đi qua API Object** — không `request.post()` thô trong spec | `BLOCKER` |
30
+ | `extends BaseAPI`; class `<Resource>API`; file `<resource>.api.ts` | `MINOR` |
31
+ | **1 method = 1 endpoint action** | `MAJOR` |
32
+ | Trả `Promise<APIResponse>` — **không parse response bên trong** | `MAJOR` |
33
+ | **Không `expect()` trong API Object** — assertion thuộc spec | `MAJOR` |
34
+ | Mỗi resource một API Object, không "God API Object" | `MAJOR` |
35
+
36
+ **Vì sao cấm `expect()` bên trong:** API Object là *nơi biết gọi đâu*, spec là *nơi biết thế nào
37
+ là đúng*. Trộn hai vai thì một thay đổi hợp đồng phải sửa ở hai chỗ, và cái sót lại nói dối.
38
+
39
+ ## 2 · Cấu trúc spec — AAA *(§5)*
40
+
41
+ ```typescript
42
+ test('TC-API-001 — tạo booking hợp lệ trả 201', async ({ bookingAPI }) => {
43
+ const payload = validBooking; // Arrange
44
+ const res = await bookingAPI.create(payload); // Act
45
+ expect(res.status()).toBe(201); // Assert
46
+ expect(await res.json()).toMatchObject({ id: expect.any(String) });
47
+ });
48
+ ```
49
+
50
+ - Header block mang **TC-ID** + `@trace.verifies` — thiếu là `BLOCKER` *(`R07`)*
51
+ - Ba khối tách rõ; trộn assert vào giữa chuỗi gọi ⇒ `MINOR`
52
+ - Data-driven bằng `for...of` / `test.each`, không chép test năm lần ⇒ `MINOR`
53
+
54
+ ## 3 · Assertion *(§6)*
55
+
56
+ | Bắt buộc | Mức nếu thiếu |
57
+ |---|---|
58
+ | Assert **mã trạng thái** | `BLOCKER` |
59
+ | Assert **cấu trúc + giá trị** của body, không chỉ status | `MAJOR` |
60
+ | Kiểm schema cho response có cấu trúc *(`helpers/schema.helper.ts`)* | `MINOR` |
61
+ | Assert header khi hợp đồng nêu | `MINOR` |
62
+
63
+ `expect(res.ok()).toBeTruthy()` làm assertion **duy nhất** ⇒ `MAJOR` — nó đúng cho mọi 2xx/3xx,
64
+ nên nó không phân biệt được thành công với chuyển hướng.
65
+
66
+ ## 4 · Async *(AGT-010 R02)*
67
+
68
+ - **`waitForTimeout` tuyệt đối không dùng** ⇒ `BLOCKER`. API đồng bộ về bản chất: `await` thẳng.
69
+ - Thiếu `await` trước lời gọi ⇒ `BLOCKER` — test kết thúc trước khi có response và **xanh giả**.
70
+
71
+ ## 5 · Dữ liệu *(AGT-010 R03)*
72
+
73
+ - Credential · payload · giá trị mong đợi để ở `data/<resource>.data.ts` ⇒ hard-code là `MAJOR`
74
+ - Token qua `fixtures/api.fixture.ts`, phân biệt **theo vai** ⇒ một token cho mọi test là `MAJOR`
75
+ - Bản ghi tạo ra phải xoá ở `afterEach`/`afterAll`, kể cả khi test fail ⇒ thiếu là `MAJOR`
76
+
77
+ ## 6 · Đặt tên và đường dẫn
78
+
79
+ ```
80
+ api-automation/tests/{TICKET-ID}/<feature>-<scenario>.spec.ts
81
+ api-automation/api/<resource>.api.ts ← PHẲNG, dùng lại xuyên PRD
82
+ api-automation/data/<resource>.data.ts
83
+ api-automation/fixtures/api.fixture.ts
84
+ api-automation/helpers/{schema,auth}.helper.ts
85
+ ```
86
+
87
+ Dãy ID của lane này **độc lập** với web/mobile *(§7.3)*: `TS-API` · `SCN-API` · `TC-API` ·
88
+ `AUT-API` · `TDS-API`.
89
+
90
+ ## 7 · Chất lượng *(§10 · §11)*
91
+
92
+ - TypeScript strict, không `any` ⇒ `MINOR`
93
+ - `test.only` sót lại ⇒ `BLOCKER` *(CI chạy một test rồi báo xanh)*
94
+ - Test flaky phải mang tag `@flaky` và bị tách khỏi suite chính ⇒ không tag là `MAJOR`
@@ -0,0 +1,109 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-09
4
+ new_in: post-review fix — trước đây `<project>` là placeholder không được định nghĩa, và
5
+ Page Object có 2 quy ước đặt tên mâu thuẫn nhau giữa các file
6
+ ---
7
+
8
+ # Quy ước đặt tên & Folder — Automation Code (nguồn chuẩn duy nhất)
9
+
10
+ Mọi file khác (`_shared/web-conventions.md`, `_shared/mobile-conventions.md`, `qc-design-script.md`,
11
+ `qc-run-script.md`, mọi layer file trong `qa-script-designer/`, `qa-script-runner/`,
12
+ `qa-reviewer/script/`) **tham chiếu file này** thay vì tự lặp lại quy ước path — sửa path chỉ
13
+ sửa ở một chỗ.
14
+
15
+ ## 0. Vị trí repo (đã chốt)
16
+
17
+ Automation code nằm trong **repo/module riêng** cho QC — tách biệt spec repo (`spec_source`)
18
+ và repo product code. Root **KHÔNG khai bằng key cấu hình riêng** — nó lấy từ `§layout` của module
19
+ đã phân giải ở `steps/qc-scope.md` §2b: `web`·`webview` → `automation/` · `system` → `api-automation/`
20
+ · `app`·`app-ios`·`app-android` → `mobile-automation/`.
21
+
22
+ > **Vì sao không có key riêng** *(quyết định `S-PATH`, 2026-09-17)*: `§layout` của module đã nói
23
+ > gốc ở đâu. Thêm một key nữa là **hai nguồn cho một sự thật**; khi chúng lệch, artifact ghi vào
24
+ > thư mục nền này còn script sinh theo layout nền kia — hỏng **im lặng**. Cùng lập luận đã dùng
25
+ > khi bỏ `tech_stack.qc_module`.
26
+
27
+ ```
28
+ automation/
29
+ ├── web/ ← Playwright Test + TypeScript
30
+ │ ├── tests/{TICKET-ID}/{feature-slug}.spec.ts
31
+ │ ├── pages/{TICKET-ID}/{feature-slug}.page.ts
32
+ │ ├── fixtures/ ← client API/DB/Kafka dùng chung nhiều feature
33
+ │ ├── playwright/.auth/{role}.json ← storageState per-role (xem web-conventions.md §6.1)
34
+ │ └── playwright.config.ts
35
+ └── mobile/ ← WebdriverIO + Appium + TypeScript
36
+ ├── test/specs/{TICKET-ID}/{feature-slug}.spec.ts
37
+ ├── pageobjects/{TICKET-ID}/{feature-slug}.page.ts
38
+ ├── fixtures/
39
+ └── wdio.conf.ts
40
+ ```
41
+
42
+ `{TICKET-ID}` — **dùng lại đúng 2 giá trị đã resolve ở Gate Bước 1** khi target là
43
+ UC-ID (cùng 2 segment đang dùng cho `{paths.specs_dir}/{TICKET-ID}/...`). Automation
44
+ code tổ chức theo **cấu trúc sản phẩm** (domain/feature-package), không theo UC-ID — vì 1 file
45
+ script thường phục vụ nhiều UC-ID cùng lúc (1 màn hình bị nhiều UC verify các khía cạnh khác
46
+ nhau), nên tổ chức theo UC-ID sẽ ép phải chọn 1 UC "sở hữu" file trong khi thực tế không đúng.
47
+ Đây là lý do automation code **KHÔNG** mirror cấu trúc `{paths.qc_dir}/{UC-ID}/...` (khác trục
48
+ tổ chức, không phải thiếu sót).
49
+
50
+ ## 1. Chuyển đổi slug (bắt buộc, một chiều duy nhất)
51
+
52
+ Nguồn gốc của mọi slug là `<FEATURE>` trong tên file thiết kế manual `TC_<FEATURE>.md`
53
+ (qa-designer, UPPER_SNAKE_CASE, vd `TC_LOGIN.md`, `TC_STUDENT_ENROLLMENT.md`):
54
+
55
+ | Ngữ cảnh | Transform | Ví dụ (từ `TC_LOGIN.md`) |
56
+ |---|---|---|
57
+ | Tên file script/spec | kebab-case, giữ nguyên | `login.spec.ts` |
58
+ | Tên file Page Object | kebab-case + suffix `.page.ts` | `login.page.ts` |
59
+ | Tên class Page Object | PascalCase + suffix `Page` | `LoginPage` |
60
+ | Tag/tiêu đề test | giữ nguyên `TC_LOGIN_001` (từ TC ID gốc trong `.md`) | `test('TC_LOGIN_001 — ...', ...)` |
61
+
62
+ **Một chiều duy nhất** — không suy ngược từ code ra `<FEATURE>`, luôn suy từ `TC_<FEATURE>.md`
63
+ đã có sang code. Nếu chưa có TC file (chưa qua phase 3) thì chưa có gì để suy — không tự đặt
64
+ tên trước.
65
+
66
+ ## 2. Nhiều UC cùng chạm 1 màn hình/feature (bắt buộc kiểm tra trước khi tạo file mới)
67
+
68
+ Feature/màn hình có thể được nhiều UC-ID khác nhau verify các khía cạnh khác nhau (vd UC1
69
+ "login thành công", UC2 "login khoá tài khoản" — cùng chạm màn Login). Trước khi tạo file mới
70
+ ở phase 6 (`qc-design-script`):
71
+
72
+ 1. **Tìm Page Object đã tồn tại chưa** — glob
73
+ `automation/pages/{TICKET-ID}/*.page.ts` (hoặc mobile tương ứng)
74
+ theo cùng slug màn hình. Có rồi → tái sử dụng, **không tạo file PO thứ 2** cho cùng 1 màn.
75
+ 2. **Tìm file spec đã tồn tại chưa** cùng slug — nếu UC khác đã tạo `login.spec.ts` cho cùng
76
+ màn hình, **thêm `test.describe()` mới vào file đó** (nhóm theo UC nguồn, vd
77
+ `test.describe('UC2 — Login lockout', () => {...})`), không tạo file trùng lặp
78
+ (`login-2.spec.ts`, `login-uc2.spec.ts`…).
79
+ 3. Nếu `<FEATURE>` slug của 2 UC khác nhau nhưng cùng route/màn hình (do qa-designer đặt tên
80
+ khác nhau ở 2 lần chạy) → dùng slug của file **tạo trước** (kiểm tra tồn tại theo route,
81
+ không chỉ theo tên) để tránh phân mảnh; ghi chú lại trong report của `qc-design-script` nếu
82
+ phát hiện trường hợp này.
83
+
84
+ ## 3. Ví dụ đầy đủ — UC1 (domain `academic`, prd-slug `student-enrollment`, screen Login)
85
+
86
+ ```
87
+ automation/
88
+ ├── tests/academic/student-enrollment/login.spec.ts # @trace.verifies=UC1-SC1, UC2-SC1...
89
+ ├── pages/academic/student-enrollment/login.page.ts # class LoginPage
90
+ └── playwright/.auth/teacher.json
91
+ ```
92
+
93
+ ## 4. Vị trí file "hồ sơ" khác (không phải code) — không đổi so với trước
94
+
95
+ `AUTOMATION_ASSESSMENT.md`, `TEST_DATA_PLAN.md`, `TESTABILITY_IMPROVEMENTS.md` vẫn ở
96
+ `{paths.qc_dir}/{UC-ID}/{active_platform}/` như các phase 4/5/6 đã định — đây là hồ sơ **theo
97
+ UC-ID** (đúng trục của chúng: đánh giá/dữ liệu/rào cản là quyết định gắn với 1 UC cụ thể tại 1
98
+ thời điểm), khác với code (gắn với màn hình, dùng lại xuyên UC). Hai trục tổ chức khác nhau
99
+ cho hai loại artifact khác nhau — không phải thiếu nhất quán.
100
+
101
+ > **Trạng thái tích hợp sdd-framework — ĐÃ THI HÀNH** *(Bước S · S1, 2026-09-17)*. Module của stack cũ
102
+ > — `qc-playwright` — đã **gỡ hẳn** *(quyết định `H2`)*, thay bằng hai module:
103
+ > `qc-playwright-ts` *(nền `web` và `system` — hai `layout`, cùng một dãy phiên bản Playwright/TS)*
104
+ > và `qc-wdio-appium` *(nền `app`)*. Không giữ bản deprecated nào: một hồ sơ stack cũ còn
105
+ > sống trong `core/` là một cửa để nó quay lại.
106
+ >
107
+ > Ghi chú gốc của bộ đề xuất từng dự kiến khai tên module qua `tech_stack.qc_module`. **Không
108
+ > làm vậy** — key đó chưa bao giờ tồn tại trong framework, và `active_platform` đã trả lời xong
109
+ > câu "dùng module nào" ngay ở trạm 1.