# sub-test-specialist-w3 — khoá drift menu-key/policy cho +7 key Designer `AwV2_Hd*` (W3)

> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `test-specialist` (WRITE độc quyền `tests/**`) · 2026-08-10.
> Spec-change theo `docs/rules.md §7`: test cũ update + test mới CÙNG wave với code. Lane
> `implementer-backend` land `MenuKeys.cs` + `SeedMenuTreeAsync` SONG SONG ⇒ ĐỎ-chờ-lane là trạng
> thái HỢP LỆ, nhưng phải chứng ĐỎ vì "key chưa có" chứ không phải vì test sai.
> Ghi TỪNG MỤC trong lúc làm (chống #53). Mục chưa chạy chỉ có tiêu đề + "CHƯA ĐO".

## §0 — Ground-truth ĐO TƯƠI trước khi viết

| Thứ đo | Lệnh | Kết quả |
|---|---|---|
| Cây làm việc | `git status --porcelain` | CHỈ `M docs/governance/so-yeu-cau-chu-du-an.md` (không phải của tôi). **`tests/**` SẠCH · `src/**` SẠCH** ⇒ lane impl W3 CHƯA land lúc tôi bắt đầu |
| HEAD | `git log --oneline -1` | `0f4bc184 wal: flush 20260810T1840` |
| Baseline file đích | `dotnet test --filter "~KhkkGroupMenuSeedTests\|~ContractCatalogTests"` | **Passed: 15, Failed: 0** (3 s) — build 0 error |
| `MenuKeys.All` hôm nay | đọc mã `MenuKeys.cs:222-250` + số neo trong test | **113** (= `docs/STATUS.md` row `Menu keys`) · `Policies` = 113×4 = **452** |
| 7 mã quy trình đích | đề bài lead + `UpdateAwDefinitionTests.cs:1574` (lane t0 đã ghim làm DỮ LIỆU) | `HD-TP · HD-GK · HD-NCC · HD-DV · HD-MB · HD-NT-NCC · HD-NT-DV` |

🔴 **KHÔNG đo full-suite** ở lượt này: lane impl có thể sửa `src/**` bất cứ lúc nào ⇒ số full-suite đo
giữa chừng là snapshot dở (#68) và không phải số của tôi. Số tôi chịu trách nhiệm = filter theo file đích.
Phán quyết cuối = gate lead chạy sau khi mọi lane dừng tay.

## §1 — Tra CHỒNG LẤN trước khi viết (bài S166: bước đắt nhất)

`grep -rn "MenuKeys.All" tests/` → 4 file, 3 nhóm mệnh đề đã có:

| Đã có ở đâu | Đo cái gì | Có phủ 7 key mới không |
|---|---|---|
| `ContractCatalogTests.cs:445` (T6 lane K1) | `MenuKeys.All.Except(menuKeysInDb).BeEmpty()` sau `SeedMenuTreeAsync` — **mọi** key ∈ `All` phải có menu-row (thiếu row = Permission FK nổ lúc boot) | ✅ CÓ (tự động, by-construction) ⇒ **KHÔNG nhân bản** invariant này |
| `KhkkGroupMenuSeedTests.cs:131` (T2 lane K4a) | `MenuKeys.All.HaveCount(113)` — số neo duy nhất trong toàn `tests/` cho 2 row canonical STATUS | ⚠️ CÓ nhưng SỐ CŨ ⇒ **phải update 113→120** (đây chính là "khoá drift" của đề bài) |
| `AuthorizePolicyRegressionTests.cs:139/:432` | policy trên controller phải trỏ key ∈ `All` | Không liên quan (chưa có controller nào dùng `AwV2_Hd*`) |

**Lỗ hổng đo được:** `grep -rn "KhkkN\|AwV2" tests/` → **0 hit** ⇒ họ 10 leaf Designer `AwV2_*` (2 PE + 8
KhkkN) hôm nay **KHÔNG có test nào** về hình cây/menu-row/grant. Tức khuôn mà W3 đang mirror chưa từng
được khoá. File mới bên dưới khoá CẢ họ cũ (control dương, xanh hôm nay) LẪN họ mới (đỏ tới khi land).

## §2 — Quyết định thiết kế: vì sao KHÔNG viết tên key ra chữ

🔴 Rủi ro lớn nhất của lượt này: spec (`spec-duyet-hd-10-08-2026.md:10` + `sub-plan-review §5`) chốt
**"CÓ sinh 7 key `AwV2_Hd*`"** nhưng **KHÔNG chốt chuỗi ký tự cụ thể** (`AwV2_HdTp`? `AwV2_HdThauPhu`?
theo `ContractTypeCodes` hay theo token mã `HD-TP`?). Nếu tôi đoán tên rồi viết `MenuKeys.ApprovalWorkflowHdTp`:
- đoán trúng → xanh;
- đoán trượt → **lỗi BIÊN DỊCH** ⇒ sập cả test-project ⇒ ĐỎ vì "test sai", đúng thứ đề bài cấm.

⇒ Luật tự đặt cho file này: **0 tham chiếu tới symbol C# chưa tồn tại**. Họ key mới được PHÁT HIỆN bằng
phép trừ trên `MenuKeys.All` (đã tồn tại): `All.Where(StartsWith "AwV2_")` **trừ** 10 key cũ (tham chiếu
qua const có thật). Nhờ vậy:
- hôm nay: tập rỗng ⇒ đỏ tại vế tiền-đề với thông điệp "chưa có key nào" (= "key chưa có", đúng lý do);
- sau khi land với BẤT KỲ hậu tố nào (`AwV2_HdTp` / `AwV2_HdThauPhu` / …): xanh, không phải sửa test;
- nếu land LỆCH KHUÔN tên (vd `AwV2_HD_TP` hoa/`AwV2_ContractTp`): vẫn phát hiện được và báo đúng chỗ
  bằng vế "mọi key họ mới phải bắt đầu bằng `AwV2_Hd`" — đó là mệnh đề SPEC, không phải đoán mò.

**Cố ý KHÔNG viết (khai để không ai tưởng đã phủ):**
- Ánh xạ 1-1 `AwV2_Hd{X}` ↔ mã quy trình `HD-{X}`: không dựng được phép đo không-tautology khi chưa
  biết luật biến hình token (`NT-NCC` → `NtNcc`? `NguyenTacNcc`?). Ai chốt tên thì bổ vế này sau — ghi
  vào §6 làm nợ.
- Số workflow `HD-*` do seeder W3 tạo: thuộc lane seed (t0 đã ghim `ContractFamilyCodes` ở
  `UpdateAwDefinitionTests.cs:1574` như DỮ LIỆU). File này chỉ đo MENU/POLICY.
- `RevokeTemporarilyHiddenModulesAsync`: đọc mã `:2568-2571` — bộ lọc là `StartsWith("Hrm") ||
  StartsWith("Off") || == Personal` ⇒ `AwV2_*` nằm ngoài; hơn nữa site-2 `:2413` CỐ Ý loại `AwV2_*`
  (admin-only). Không có ca thật ⇒ không viết test trang trí.

## §3 — Đã sửa/thêm gì

CHƯA ĐO

## §4 — Chạy: đỏ có đúng lý-do "key chưa có" không

CHƯA ĐO

## §5 — Hồi quy + phạm vi file

CHƯA ĐO

## §6 — Nợ / bàn giao cho gate W3

CHƯA ĐO
