# SPEC CỤM-2 FINAL — K4 + K5 + K6 (lead kết tinh sau B2-r cụm-2, @S164)

## ① Đề bài
K4 sidebar 8 nhóm × 6 leaf (49 row menu mới) · K5 cây sub-folder groups · K6 admin thu gọn 12-key. NỀN = `sub-invest-fable-b2-cum2.md` (5/5 section, 0-mig) — **15 VÁ dưới thắng khi xung đột** (từ `sub-reviewer-c2-l{1,2}.md`; ⚠️ lane c2-l3 chết ruột 188B — lens đo-được/thứ-tự phủ bù bằng overlap L1-F2/F6 + L2-H2/H3/M4 + lead-note §④, KHAI RÕ không giấu).

## ② 15 vá

1. 🔴 **[L1-F2 ⨯ L2-H2 hội tụ] Route regex `resolvePath` ship CÙNG K4a** (tách khỏi K4c) — leaf không route bị `if (!path) return null` nuốt IM LẶNG (`fe-user/Layout.tsx:336-338`); không tách thì sau K4a: 7 group RỖNG + 42 leaf VÔ HÌNH by-construction. Chỉ mục WfView-lọc-theo-Code (`Code==='KHKK-N'+n`) mới thật sự chờ K3.
2. 🔴 **[L1-F6 ⨯ L2-H3] Drift khai DELTA** (+49 key / +196 policy), CẤM neo 2 mốc tuyệt đối (64→113 chỉ đúng nếu cụm-1 land trước — thứ tự ship đang cho song song); commit kèm lệnh đo lại `MenuKeys.All` thực tế.
3. 🔴 **[L2-H1] TanStack `queryKey` list PHẢI CHỨA `group`** — thiếu thì 8 leaf dùng chung 1 ô cache = silent-wrong build xanh (đúng họ bài S159 2-khóa-cache).
4. **[L1-F4] SQL nghiệm thu N designer**: JOIN `MenuItems × Permissions × Roles(Admin, CanRead=1)` — row tồn tại ≠ hiện sidebar (`GetMyMenuTreeQuery.cs:96` lọc HasAccess); đo trên env nghiệm thu (prod), không phải Dev.
5. **[L2-M3] `KhkkListPage` derive `phase` từ `searchParams`** (hiện `useState` mount-only `:63-65`, route không keyed ⇒ bấm leaf→leaf KHÔNG re-lọc; cùng file `deletedView:61` đã đọc searchParams đúng — đồng bộ về 1 kiểu). ×2 app.
6. **[L2-M4] Trả "Thao tác/Create preset `?group=`" VỀ K4b** (0 phụ thuộc K3 — chỉ cần picker vá-4 cụm-1).
7. **[L2-M5] 8 nhãn menu ĐÍNH vào file OG-6** (`danh-muc-sp002-transcribe.md`) cho anh soát 1 lượt trước K4a + sửa nhãn N2 thiếu vế **"phá dỡ"** ("Kết cấu, hạ tầng, phá dỡ & DV hỗ trợ thi công"); nhãn đổi-sau-seed = +labelBackfill + restart (không phải sửa chữ tự do — comment `DbInitializer.cs:1959-1961`).
8. **[L2-M6 ⨯ L1-F3] Ràng buộc ngược tại `ADMIN_HIDDEN_KEYS`**: comment "gỡ `KeHoachKyKet` khỏi đây ⇒ PHẢI nới regex `Layout.tsx` fe-admin trước — 48 leaf sẽ drop im lặng #50". KHÔNG nới regex fe-admin đợt này (fe-admin **thiếu `WorkflowMatrixViewPage`** — nới sẽ đẻ leaf WfView chết trên admin; verified L1-BÁC-2).
9. **[L2-M7] Cây K5 HIỆN ĐỦ 8 sub-folder kể cả rỗng** (mirror sidebar đúng nghĩa + tiền lệ prod: folder GĐ "SẮP" vẫn hiện) — bỏ đề xuất ẩn-nhóm-rỗng của lane; fallback "(chưa phân nhóm)" GIỮ cho phiếu NULL-group.
10. **[L2-M1] Sửa LÝ DO giữ site-2** (group-key KHÔNG cần CanRead row — `HasAccess` đệ quy theo con `:96-97`); kết luận giữ-3-site GIỮ NGUYÊN.
11. **[L2-M2] +1 dòng site-4 revoker**: `RevokeTemporarilyHiddenModulesAsync:2286` (gọi `:2097` GIỮA site-2/3) prefix Hrm*/Off*/Personal — đã soi, **0 match Khkk**; A5 restart ×2 là phép thử nó.
12. **[L1-F5] Acceptance label đo bằng SQL cột `Label, DisplayLabel`** (fe-admin không đọc displayLabel — `MenuItem.cs:12-13`; fe-user `effectiveLabel:204-206`).
13. **[L1-F7] Vá comment stale `DbInitializer.cs:1959-1961`** (tự trỏ ":1893-1905" — thật `:1926-1943`) cùng diff K4a.
14. **[cụm-2 surprise] Viết lại comment fe-admin `:179-181`** ("S57 BỎ ẩn Danh mục" — QĐ7 đảo lần 2 cùng chỗ) cùng commit K6 — khai lineage 2 lần đảo.
15. **[precedent-split] Khai 2 khuôn menu-key trong doc K4**: `Ct_*`/`Pe_*` CỐ Ý ngoài-All (comment `:2193-2195`) ⟂ catalog/AwV2/Khkk mới TRONG-All — người build đọc 1 khuôn đừng tưởng khuôn kia sai.

## ③ Checklist final cụm-2
- [ ] K4a (ship 1 khối): 49 row menu VÀO All + labelBackfill G1 + route regex resolvePath ×(fe-user; fe-admin GHI RÀNG BUỘC) + 3-site grant (+dòng site-4 đã-soi) + nhãn N2 sửa + comment :1959/:179 vá. Acceptance: Admin + 1 user thường THẤY 8 group sidebar fe-user; 42 leaf BẤM ĐƯỢC (không null-route); drift DELTA khai + đo lại All; SQL JOIN đếm designer đúng N; restart ×2 số row menu/permission KHÔNG đổi (idempotent + revoker-proof).
- [ ] K4b: 4 leaf list + Thao tác preset — derive phase+group từ searchParams; `queryKey` += group; acceptance bấm leaf→leaf ĐỔI kết quả không cần F5; URL G1 6 leaf mang `group=1`.
- [ ] K4c (sau K3): WfView lọc `Code==='KHKK-N'+n`.
- [ ] K5: `groups` mang leaves thật (không đường `content`) + GĐ2 by `approvalGroup` + GĐ3/4 by `c.type` + HIỆN ĐỦ 8 + fallback chưa-phân-nhóm + Σ sub-folder == tổng phiếu; SHA-identical 4 file pipeline ×2 app (lệnh đo từng file); build ×2 PASS. **+forward-provision (B3-A5, việc cụm-3 vá-4b đẻ cho K5):** builder GĐ3/GĐ4 nối qua **TẬP `lines[].contractId` ∪ `pe.contractId` legacy** — không thì HĐ thứ 2+ (OG-1 UI-1-1, N HĐ/PE) vô hình trên cây.
- [ ] K6: blacklist 12-key tường minh + acceptance liệt kê ĐÚNG N mục lá (tên + đếm, dư/thiếu 1 = FAIL) + `git diff --stat -- fe-user/src` = 0 file + comment ràng-buộc-ngược land.
- [ ] Test (lead-note bù L3): seed-menu idempotent ×2 · labelBackfill đổi-label-không-nhân-row · route-map FE có leaf nào null-path = FAIL (unit nhỏ nếu khuôn có; không thì acceptance tay + chụp). Baseline ≥590+k khai k.

END spec-cum2-final — nền K4 13v·K5 6v·K6 4v + 15 vá · 1 lane review chết ruột ĐÃ KHAI + phủ bù.
