# SPEC CỤM-1 FINAL — K1 + K2 + K3 (lead kết tinh sau B2-r, @S164)

## ① Đề bài
K1 master-data danh mục (86 row) · K2 phiếu mang hạng-mục + nhóm + freeze-răng · K3 8 workflow nhóm + port-finalize + designer. Nguồn NỀN = `sub-invest-fable-b2-cum1.md` (33KB, 17 test) — **12 VÁ dưới đây THẮNG khi xung đột** (từ B2-r 3 lane: `sub-reviewer-c1-l{1,2,3}.md` — 41 finding, hội tụ cross-lane).

## ② 12 vá (build ĐỌC CẢ nền LẪN vá)

1. 🔴 **[3-lane hội tụ] Menu key PHẢI VÀO `MenuKeys.All`** — nền khai `ContractCatalog` + 8 `AwV2_KhkkN*` "ngoài All" ⇒ **0 permission row ⇒ sidebar chết** (loop grant `DbInitializer.cs:2058` nguồn = All; khuôn THẬT: 5 leaf catalog hiện hành `MenuKeys.cs:168` + 3 leaf AwV2 `:182` đều TRONG All). Fix: cả 9 key **VÀO All** + KHAI drift dạng **DELTA per-wave** (B3-A1 — cấm neo tuyệt-đối vì thứ tự ship: K1 = +1 key/+4 policy · K3 = +8 key/+32 policy; mỗi commit kèm lệnh đếm lại `MenuKeys.All` thực tế + cập nhật 2 row STATUS, đừng Goodhart giữ-số-đứng-yên). Acceptance += "login Admin THẤY leaf trong sidebar ×2 app" + 1 test seed-grant.
2. 🔴 **[L1-F5] GỠ phương án (i) tại site `:342`** — nền đọc NGƯỢC comment (`WorkflowService.cs:36-43` mô tả NGUỒN COPY và khẳng định KHKK CỐ Ý không có site gán-qua-BIẾN; `:342` nằm trong `ReturnOrRejectAsync` chỉ nhận TraLai/TuChoi literal — DaDuyet **unreachable** qua đó). GIỮ phương án (ii): test khóa khẳng định điều đó. Cấm "sửa" :342.
3. 🔴 **[L2-H2] K3 thêm việc (d): sweep cụm ép-false** — `grep -rn "AllowApproverFinalize|AllowApproverSkipToFinal"` toàn src + fe-*: gỡ đủ **CREATE ép-false server-side** `ApprovalWorkflowV2AdminFeatures.cs:380-381` + **PUT Conflict** `:591-594`/`:605-610` + **FE `levelFlagsPayload`** `ApprovalWorkflowsV2Page.tsx:247-258` (`earlyFinalizeLocked :638`) — không chỉ note `:151-152`. Test mới: "tạo version qua CreateCommand type-10 GIỮ được cờ".
   > 🔄 **ĐÍNH-CHÍNH @S168 (thi-hành phán gate-K3 — `sub-reviewer-gate-k3.md`; spec ĐÓNG-BĂNG nên đính-chính bằng khối này, KHÔNG sửa chữ gốc):** site **PUT Conflict `:591-594`/`:605-610` KHÔNG GỠ** — đó là luật versioning CHUNG mọi type (409 khi sửa version đang tham-chiếu), lead GIỮ là **ĐÚNG**; vá-3 liệt nhầm nó vào cụm ép-false. Phần còn lại của vá-3 (CREATE ép-false `:380-381` server-side + FE `levelFlagsPayload` — riêng type-10 GIỮ ép-false + FE disable ô theo phán F-2 gate-K3) đã xử @K3 `15349e8`.
4. 🔴 **[L2-H3] `KhkkCreatePage` ×2 app PHẢI SỬA** (mâu thuẫn nền: command +`ApprovalGroup` required nhưng page ghi "KHÔNG-ĐỔI") — body POST thêm `approvalGroup` + picker nhóm. Acceptance += "tạo phiếu nhóm 2 **TỪ UI** ×2 app" (curl-only không đủ).
5. 🔴 **[L2-M6/L1-F6] Chốt nhất quán `CatalogEntryId`**: auto-gen line lúc tạo phiếu được mồi null NHƯNG **submit-guard CHẶN null** (mọi line phải gán hạng mục trước trình) + validator line-mới-qua-API bắt buộc có. DoD "line khác nhóm → 409" phải chạy trên phiếu CÓ line thật (chống PASS-bằng-tập-rỗng).
6. **[L2-M4] Kéo `phase-guard line-edit sau DaDuyet` từ K4b VỀ K2** — Σ `Features.cs:709-711` tính LIVE, soft-delete line làm số phiếu-đã-duyệt trôi; freeze chưa kín nếu guard nằm cụm-2.
7. **[L2-M5/L1-F13] Mig 71 BỔ SUNG item 6 = cột `EndedByLevelFinalize`** (K2.a hiện chỉ 1-5) + tên mig phản ánh (vd `AddKhkkApprovalGroupCatalogAndFinalizeRuntime`) + khai ghost-window K2→K3 (cột nằm im tới khi K3 land).
8. **[L1-F8] Bảng consumer += `Features.cs:712-713` Σ ProposedAmount** (nền sót — list có 2 Σ) + quyết: list thêm Σ Approved sau freeze để list/detail không lệch số.
9. **[L1-F7] Chứng NULL-unique vá lại**: unique cũ bị DROP trong chính Mig 71 — mắt xích thật chặn 2-line-NULL-cùng-(Plan,Supplier) phải nêu ĐÚNG (submit-guard vá-5 + backfill line cũ gán từ header-group; đừng dựa index đã chết).
10. **[L1-F9/L2-M2] Sửa nhãn IgnoreQueryFilters**: số đúng = "0 hit trong `DbInitializer.cs`" (toàn src/Backend = 29 hit/5 file — claim "duy nhất"/"0-hit" là sai lây từ comment code); đơn thuốc seed `:2970` thiếu IgnoreQueryFilters vẫn ĐÚNG.
11. **[L1-e] Roster: acceptance đo DANH TÍNH** — K3.e += mục "mở workflow nhóm N, danh sách người duyệt KHỚP bảng đội hình từng người" + xử 2 cặp gần-trùng (`truong.le` vs `tring.le` vs `truong.nguyen` · `thanh.lethanh` "Lê Thanh Bình" vs `binh.le` "Lê Văn Bình") bằng đối chiếu **dept + chức danh trên bản LIVE**, không match tên trần; dept-lookup `:185` thêm null-guard.
12. **[L1-F3/F4] Trích dẫn `:44-46` ĐỦ + khai supersede**: nguyên văn có "chủ dự án phán 'PHÁ VỠ' @S155" — spec PHẢI khai K3 = **ĐẢO phán quyết cũ theo lời owner @S164**, kèm việc (e): sửa comment `:36-43`/`:44-46` + 2 lệnh tự-kiểm `:292-293` dán PR + cập nhật `KhkkWorkflowPanel.tsx:8` ×2 app + HANDOFF:77. ✅ **[OG-9 CHỐT @S164]** anh xác nhận TƯỜNG MINH: *"đúng, chỗ budget chốt chặn là a Chương… vậy bài toán trần budget xem như đóng"* ⇒ đảo "PHÁ VỠ" @S155 hợp lệ — **K3 (a)-(e) HẾT CHẶN**. Kèm: NĐUQ = human-gate VĨNH VIỄN (không còn wave enforce-máy nào; xóa mọi ý "opt-in sau").

## ③ Checklist final cụm-1
- [ ] K1: 7 mục nền + **"Admin thấy leaf sidebar ×2 app"** + test seed-grant + drift DELTA khai cùng commit.
- [ ] K2 (+B3-A6): nút **"Tạo thêm cho nhóm khác"** (OG-7 UI-hướng-1) CHỦ = K2, site picker/PE-flow `ContractSigningPlanFeatures.cs:1201-1202` + PE-detail — acceptance TỪ UI: PE có phiếu → thấy phiếu + nút phụ, bấm → form preset nhóm khác → 201.
- [ ] K3 (+B3-B3 câu-ranh): fe-admin `resolvePath` nới **CHỈ họ `AwV2_KhkkN*`** — **CẤM nới họ `Khkk_*`** (fe-admin thiếu WorkflowMatrixViewPage; phép-âm: grep route Khkk_ fe-admin = 0).
- [ ] K2: 8 mục nền + **submit-guard null-CatalogEntryId (test ÂM trên phiếu có line)** + phase-guard line-edit + Σ×3 nhất quán + Mig-71 6 item + 3-file + backfill=1 + **UI tạo-phiếu-nhóm-2 ×2 app** + bảng 18 consumer (17 nền + :712-713).
- [ ] K3 (SAU OG-9): 6 mục nền + sweep ép-false ≥4 site + test giữ-cờ-qua-CreateCommand + đo danh tính roster + comment/panel/HANDOFF cập nhật + verify LIVE trước seed.
- [ ] Mọi wave: test ≥590+k khai k · cicd PASS · BE-only smoke API (#77 chỉ khi FE-ship).

END spec-cum1-final — nền 17 test + vá: ~5 test mới · 1 OG mới (OG-9) · 2 blocker menu/ép-false đã đóng đường.
