# lens2-evi — 8 verdict vs code

## Findings

### V-a (claim: Line per-NCC + UNIQUE(PlanId,SupplierId) filtered + ContractId per-line KHÔNG unique) = ĐÚNG
- `Domain/ContractSigningPlans/ContractSigningPlanLine.cs:14-25` — `ContractSigningPlanId` + `SupplierId` + `ContractId?` (`:25`).
- `Infrastructure/Persistence/Configurations/ContractSigningPlanLineConfiguration.cs:22-24` — `HasIndex(PlanId, SupplierId).IsUnique().HasFilter("[IsDeleted] = 0")`.
- `:26` `HasIndex(x => x.ContractId)` KHÔNG `.IsUnique()` ⇒ N-line→1-HĐ (OG-1 gộp) hợp khuôn schema hiện tại. ĐÚNG 3/3 vế.

### V-b (claim: create validator CHỈ check ApplicableType==10, không check IsActive) = ĐÚNG
- `Application/ContractSigningPlans/ContractSigningPlanFeatures.cs:209-220` — `EnsureWorkflowTypeAsync` chỉ `Select(w => (int?)w.ApplicableType)` (`:214`) + nhánh NotFound (`:216-217`) + so type (`:218-219`). 0 đọc `IsActive`/`IsUserSelectable`.
- Call-site: `:350` (Create) + `:460` (Update pin) — cả 2 nhánh cùng guard.

### V-c (claim: picker FE lọc isUserSelectable) = ĐÚNG
- `fe-user/src/pages/khkk/KhkkCreatePage.tsx:56-66` — GET `/approval-workflows-v2?applicableType=10` → `types.find(...).history` → `.filter(w => w.isUserSelectable)`.
- Chuỗi bổ trợ (lens2 tự đo, sub-invest KHÔNG nêu): `ApprovalWorkflowV2AdminFeatures.cs:117-134` overview KHÔNG lọc `IsActive` ⇒ history trả CẢ bản inactive; POST tạo mới `:341-343` chỉ set `IsActive=false` cho bản cũ, **KHÔNG đụng `IsUserSelectable`** (`:352-353` bản mới = true) ⇒ 8 workflow cùng type-10 vẫn hiện đủ ở picker. Verdict "only-ONE-active VÔ HẠI" ĐỨNG VỮNG cho đường picker.

### V-e (claim: KHKK service KHÔNG port CeoApprovalThreshold ⇒ set = config-lie #78) = ĐÚNG
- Grep `CeoApprovalThreshold|AllowApproverFinalize` trên toàn `*ContractSigningPlan*` (src/Backend): hit DUY NHẤT = comment `ContractSigningPlanWorkflowService.cs:44` + Designer migration (schema `ApprovalWorkflow`). 0 hit đọc-giá-trị trong code KHKK ⇒ 0 consumer THẬT (không phải chỉ comment khai).

### V-f (claim: leaf key KHÔNG infix G1) = ĐÚNG
- `DbInitializer.cs:1780-1787` — `Khkk_G1` (group) + 6 leaf `Khkk_WfView/List/Create/Pending/Approved/Deleted` parent = `"Khkk_G1"`, key leaf KHÔNG mang G1.

### V-d (claim: GĐ3/4 đã có `c.type`; `PipelineStage` leaves PHẲNG, muốn nhóm thì `groups?` hoặc `content`) = ĐÚNG
- `fe-user/src/components/pipeline/PipelineStageFolders.tsx:58-72` — `PipelineStage = {n, leaves[], locked?, failed?, loading?, content?, count?, truncated?}` — KHÔNG có `groups`.
- `:74-75` `stageCount` = `content !== undefined ? (count ?? 0) : leaves.length` ⇒ đi đường `content` thì `count` PHẢI truyền tay (bẫy đếm S162).
- `usePipelineStages.ts:240` `navigate('/hard-copies?type=' + c.type)` ⇒ `c.type` có trên leaf GĐ4; GĐ3 `:220-229` dùng cùng object `c` ⇒ group-by type = thuần FE. ĐÚNG.
- **BONUS ĐO ĐƯỢC (mirror claim K5):** sha256 `usePipelineStages.ts` fe-user == fe-admin = `7144eeb7…ab148`; `PipelineStageFolders.tsx` = `d449cc8d…cdc2d` — 2/2 cặp IDENTICAL, tiền-đề "mirror SHA ×2 app" ĐÚNG tại HEAD.

### F-L2-1 [HIGH] "Phiếu per NHÓM" (quyết-định ②.2) VA vào rào 1-PE-1-phiếu đang sống — K2 không liệt site này
- `ContractSigningPlanFeatures.cs:342-347` — Create chặn: `AnyAsync(p => p.PurchaseEvaluationId == pe.Id && p.Phase != TuChoi)` ⇒ 409 "Phiếu Duyệt NCC này đã có kế hoạch ký kết."
- Cặp sinh đôi ở picker: `:1201-1202` `!db.ContractSigningPlans.Any(pl => pl.PurchaseEvaluationId == e.Id && pl.Phase != TuChoi)` ⇒ PE biến mất khỏi dropdown ngay sau phiếu ĐẦU.
- Hệ quả: 1 gói thầu có hạng mục thuộc >1 nhóm duyệt ⇒ phiếu nhóm thứ 2 KHÔNG tạo được. Spec K2 (§③) chỉ liệt Mig 71 + validator + FE lines editor — 2 vị-ngữ trên KHÔNG có trong phạm vi.
- DoD K2 hiện tại ("tạo phiếu nhóm X + N hạng mục; line khác nhóm → 409") PASS mà lỗ vẫn còn ⇒ acceptance mù đúng chỗ nguy hiểm.
- Falsifier: nếu owner chốt "1 PE chỉ thuộc ĐÚNG 1 nhóm" thì finding rớt xuống LOW — nhưng khi đó "phiếu per NHÓM" == "phiếu per PE", cần khai rõ.
- Đề nghị: thêm dòng K2 "đổi 2 vị-ngữ uniqueness sang (PeId, ApprovalGroup)" + DoD "tạo phiếu nhóm 2 CÙNG PE → 201".

### F-L2-2 [MED] "8 designer entry" (K6 DoD) không khả thi với hình-dạng route hiện tại
- Deep-link designer là PER-TYPE: fe-admin staticMap literal `Khkk_WfView: '/system/approval-workflows-v2/ContractSigningPlan'` (`fe-admin/src/components/Layout.tsx:35`), route `:code` = TYPE-code.
- `resolvePath` nhánh `AwV2_` chỉ nhận 3 code `DuyetNcc|DuyetNccPhuongAn|Contract` (`fe-admin/Layout.tsx:166-172`) ⇒ leaf `AwV2_*` code mới trả `null` = leaf RỚT IM LẶNG (gotcha #50, chính comment `:40` cảnh báo).
- ⇒ muốn 8 entry designer phải (a) mở regex/whitelist + (b) route nhận per-workflow (Code/Id), không có trong K3/K6 scope.

### F-L2-3 [MED] Leaf `WfView` (và `Create`) không có đường mang `group` — K4 DoD chỉ đo leaf list
- `WorkflowMatrixViewPage.tsx:52-71` lọc DUY NHẤT theo `?type=` (`typeInt` whitelist 1|2|3|10) rồi render `summary.history` (mọi workflow `isUserSelectable` của type) ⇒ 8 leaf `Khkk_G{n}_WfView` sẽ trỏ CÙNG `/khkk/workflow-matrix?type=10` và hiện CẢ 8 nhóm.
- K4 DoD (§③) chỉ đòi "bấm từng leaf list lọc đúng ApprovalGroup" ⇒ 8/48 leaf WfView + 8/48 leaf Create nằm NGOÀI phép đo.

### F-L2-4 [LOW-MED] Citation §D K5 gọi tên BE-artifact KHÔNG tồn tại
- Sub-invest §D K5: "BE: `KhkkListItemDto` +`approvalGroup`". Grep `src/Backend`: 0 hit `KhkkListItemDto`; record thật = `ContractSigningPlanListItemDto` (`ContractSigningPlanFeatures.cs:34`). `KhkkListItemDto` là type FE (`fe-user/src/types/khkk.ts:106`).

### F-L2-5 [LOW] Số leaf trong ②.6 mâu thuẫn với §C-6
- Spec ②.6: "nhóm 2-8 = key mới `Khkk_G{n}_*` (48 leaf…)" — đọc như 48 leaf MỚI; §C-6 sub-invest ghi "48 leaf TỔNG". Đúng số: 7 nhóm mới × 6 = 42 leaf mới + 7 group-key mới = 49 row menu mới; tổng leaf = 48.
- Ảnh hưởng thật: 2 danh sách key TRONG DbInitializer phải khớp từng dòng — `KhkkKeys()` (`DbInitializer.cs:2185-2188`, 7 key) và bộ grant (`:2418-2424`) — sai số ở spec = sai 2 chỗ enumerate.

### F-L2-6 [MED] K4 liệt 2 site BE nhưng có 3 — `KhkkKeys()` bị bỏ sót (đúng lớp S155 mà spec tự trích)
- Site 1 menu seed: `DbInitializer.cs:1780-1787` (`SeedMenuTreeAsync` :1737).
- Site 2 grant KHKK: `SeedKeHoachKyKetAccessAsync` (`:2415`, key-list `:2418-2424`).
- **Site 3 KHÔNG được spec nêu:** `KhkkKeys()` `:2185-2189` (7 key hardcode) → `:2216` `.Concat(KhkkKeys())` bên trong `SeedAllRolesReviewReadPermissionsAsync` (`:2144`). Chính comment `:2204-2205` cảnh báo lớp này: "phải có mặt ở CẢ HAI danh sách permission … quên 1 bên = quyền lệch".
- Quên site 3 ⇒ 42 leaf mới không có CanRead trên đường review-scope (fresh-DB / env khác) — lỗi IM LẶNG kiểu menu-vắng.

### Ghi chú kiểm-chứng phụ (không tính là finding)
- "0 workflow type-10 seed" (§B.7) ĐÚNG 2 nguồn độc lập: grep `ContractSigningPlan` trong `DbInitializer.cs` = 0 hit ngoài `Khkk_*` menu; danh sách hàm seeder (`:171,:223,:273,:325,:373,:421,:469`) có Contract/Proposal/Leave/Ot/Travel/Vehicle — KHÔNG có ContractSigningPlan.
- Khuôn được trích tồn tại thật: `SeedRealMasterDataAsync` `:2901` (K1) · `SeedSampleApprovalWorkflowsV2Async` `:171` (K3) · `isAdminHidden` chỉ `Ct_` `fe-admin/Layout.tsx:182-184` (K6).
- FE tree ĐÃ chịu được N-phiếu/1-PE: `usePipelineStages.ts:110-121` `Map<string, KhkkListItemDto[]>` (bucket mảng + sort desc) ⇒ F-L2-1 là rào BE thuần, FE không phải sửa.

## Verdict

**LENS2: 8/11 claim ĐÚNG · 1 SAI · 2 LỆCH-NHẸ.** 6 claim chịu-lực (a)-(f) = 6/6 ĐÚNG ⇒ nền kỹ-thuật của 8 verdict §C VỮNG. Rủi ro không nằm ở evidence sai mà ở **phần spec KHÔNG nói**: F-L2-1 (HIGH, rào 1-PE-1-phiếu chặn thẳng quyết-định "phiếu per NHÓM") + F-L2-6 (MED, thiếu 1 site permission) + F-L2-2/3 (MED, designer-entry & leaf WfView/Create ngoài phép đo K4/K6).

(XONG — 6 verdict verify + 6 finding, không còn mục treo)
