# lens1-cov — coverage + owner-gate

Đối tượng chấm: `spec-4gd-khkk-tong-quat-31-07-2026.md` (viết tắt **spec:N**)
Yêu cầu gốc: `draft-yeu-cau-owner.md` (viết tắt **draft:N**)
Lăng kính: (Q1) phủ đủ 5 mảnh scope + 2 yêu cầu bổ sung? · (Q6) OG list đủ chưa — có chốt lậu không?

## Q1 — bảng mảnh → wave phủ → lỗ

| # | Mảnh (draft §3) | Wave spec | Trạng thái |
|---|---|---|---|
| (i) | Master data danh mục (draft:46) | K1 (spec:22, :36) | ⚠️ nguồn extract KHÔNG TỒN TẠI theo tên (F-1); acceptance chỉ đo SỐ DÒNG, 0 đo cột (F-14) |
| (ii) | eOffice sidebar 8 nhóm × 6 leaf (draft:48) | K4 (spec:23, :39) | ⚠️ thiếu deliverable FE route-map ×2 app (F-9); số 48 vs 42 + 2 dạng key (F-8) |
| (iii) | Cây toàn trình mirror sidebar (draft:50) | K5 (spec:23, :40) | ✅ nền có thật cho cả GĐ3 (`Ct_*`) lẫn GĐ4 (`Hdc_*` 7 leaf, `DbInitializer.cs:1868,:1981-1987`) |
| (iv) | Admin thu gọn (draft:52) | K6 (spec:23, :41) + K3 | ❌ **vỡ 2 nửa**: whitelist giữ đúng 2 mục owner bảo ẩn (F-2); nửa "8 designer" 0 wave 0 acceptance (F-3) |
| (v) | Dry-run E2E (draft:54) | K8 (spec:24, :43) | ⚠️ chạy prod, tác dụng phụ không xoá được + ping user thật, 0 gate (F-13) |
| bổ sung 1 | "Tạo luôn master data danh mục" (lời 10, draft:16) | K1 | = (i), xem F-1/F-14 |
| bổ sung 2 | "yêu cầu là dry-run luôn" (lời 10) | K8 | = (v), xem F-13 |

### Đối chiếu 10 lời chốt verbatim §0 draft → wave/quyết định nào ăn

| Lời | Nội dung | Ăn ở đâu | Verdict |
|---|---|---|---|
| 1 (draft:7) | PE bự → bổ hạng mục nhỏ | QĐ2 + K2 (spec:13) | ✅ |
| 2 (draft:8) | hạng mục ≠ loại HĐ ≠ 5 form; "đặt tên field tách bạch" (draft:27) | — | ⚠️ F-15 (kỷ luật đặt tên rơi; spec vẫn xài "loại HĐ" 2 nghĩa trong cùng file) |
| 3 (draft:9) | duyệt xong → **"đưa vào loại hợp đồng gì"** | QĐ1 (spec:12) chỉ chốt CARDINALITY | ⚠️ F-15: cơ chế CHỌN loại HĐ/khuôn lúc bắc cầu K7 không giao cho ai |
| 4 (draft:10) | GĐ3 chỉ điều khoản | spec:7 | ✅ (cố ý ngoài scope) |
| 5 (draft:11) | ký nháy = mực trên bản cứng; "in khối ký đúng pattern" (draft:25) | chỉ nằm trong ngoặc lý do QĐ4 (spec:15) | ❌ F-11: 0 wave, 0 checklist |
| 6 (draft:12) | fan-out A1/A2 + chú thích ngắn | K4 | ✅ (chú thích ngắn không có phép đo — LOW, gộp F-8) |
| 7 (draft:13) | "Ẩn hết… chỉ còn Quy trình duyệt (Mới)" + chừa User + "theo nhóm duyệt" | QĐ7/K6 + QĐ2 | ❌ F-2 (whitelist mâu thuẫn) + ❌ F-3 (8 designer) |
| 8 (draft:14) | thêm sub-folder + sliderbar giống nhau | K5 | ✅ |
| 9 (draft:15) | giống nhau, chỉ khác form, đừng chế | spec:7 + acceptance SHA K5 (spec:40) | ✅ |
| 10 (draft:16) | dry-run + master data | K8 + K1 | ✅ có wave (chất lượng: F-1, F-13) |

## Findings

### F-L1-1 [HIGH] — Nguồn dữ liệu của K1 không tồn tại theo cái tên spec ghi
- **Claim:** "việc ĐẦU K1" = extract file gốc `SOL-CCM-SP-002` (spec:19) trỏ vào một tên file 0 hit trên toàn cây `D:\Dropbox\CONG_VIEC\SOLUTION`.
- **Evidence:** `find . -iname "*SP-002*"` → **0 hit**; control-dương cùng lệnh cùng cây `-iname "*FO-002*"` → **7 hit** (`FORM/SOL-CCM-FO-002.01…07`) ⇒ không phải thước hỏng. `QUY_TRINH/` chỉ có **1** file: `QT TRINH KY HOP DONG TP-NCC.docx` (141.036 B). Draft:42 cũng ghi path `…\QUY_TRINH\SOL-CCM-SP-002…`.
- **Vì sao HIGH:** K1 là wave nền của cụm 1; K2 (CatalogEntryId), K4 (ApprovalGroup) đều phụ thuộc. OG-5 (spec:30) chỉ phủ ca "extract vẫn mơ hồ", KHÔNG phủ ca "không tìm thấy file".
- **Đề nghị:** pin TÊN FILE THẬT + kích thước vào spec trước khi K1 chạy; nếu `QT TRINH KY HOP DONG TP-NCC.docx` chính là SP-002 thì khai đính chính; acceptance K1 thêm "trích được bảng 3.5 với N dòng, N khai trước khi seed".

### F-L1-2 [HIGH] — Whitelist "System subtree" GIỮ LẠI đúng 2 mục owner ra lệnh ẩn
- **Claim:** QĐ7 "chừa `System` subtree + AwV2" (spec:18) mâu thuẫn trực tiếp draft:52 ("+ 2 mục quy trình cũ (Quy trình Duyệt NCC · Quy trình HĐ)" nằm trong danh sách ẨN) và lời 7 (draft:13 *"chỉ còn cái Quy trình duyệt (Mới) thôi"*).
- **Evidence (đĩa):** `DbInitializer.cs:1795` `(MenuKeys.Workflows, "Quy trình HĐ", MenuKeys.System, 95, …)` · `:1798` `(MenuKeys.PeWorkflows, "Quy trình Duyệt NCC", MenuKeys.System, 95, …)` · `:1801` `(MenuKeys.ApprovalWorkflowsV2, "Quy trình duyệt (Mới)", MenuKeys.System, 96, …)`. ⇒ cả 3 đều là CON của `System`; whitelist cả subtree = giữ nguyên 2 mục cũ. Vế "+ AwV2" trong QĐ7 còn **thừa** (AwV2 vốn ở trong System).
- **Acceptance không bắt được:** K6 (spec:41) "sidebar admin chỉ còn HỆ THỐNG + Quy trình duyệt (Mới)" — 2 mục cũ nằm BÊN TRONG "HỆ THỐNG" nên câu này vẫn đọc là PASS. Đây là dòng acceptance không phân biệt được đúng/sai.
- **Đề nghị:** whitelist ở mức KEY (`System, Users, Roles, Permissions, MenuVisibility, ApprovalWorkflowsV2` + con của AwV2), KHÔNG "subtree"; acceptance đổi thành liệt kê ĐỦ tên mục còn lại và ghi rõ `Workflows`/`PeWorkflows` phải VẮNG.

### F-L1-3 [HIGH] — Nửa "+= 8 designer nhóm-duyệt" của mảnh (iv) không có wave, không có acceptance
- **Claim:** draft:52 đòi *"Quy trình duyệt (Mới) **+= 8 designer nhóm-duyệt** (khuôn y Duyệt NCC (Mới): Bước—Phòng—Cấp, versioned + archived history)"*. Spec chỉ có 2 chữ "designer entry" trong mô tả K3 (spec:22) và "+ 8 designer" trong §① (spec:7) — **0 dòng checklist** đo nó; sub-file invest còn chốt "0 designer mới" (`sub-invest-fable-b1a.md:53`).
- **Evidence nền:** tiền lệ là 2 leaf con của AwV2 (`DbInitializer.cs:1802-1803` `AwV2_DuyetNcc` / `AwV2_DuyetNccPhuongAn`); route FE là allowlist HARDCODE 3 code (`fe-admin/src/components/Layout.tsx:169`: `code === 'DuyetNcc' || 'DuyetNccPhuongAn' || 'Contract'`) ⇒ 8 designer nhóm cần +8 MenuItem +8 nhánh route, không tự có.
- **Acceptance hiện tại đo trật:** K3 (spec:38) chỉ đo `GET /approval-workflows-v2?applicableType=10` trả 8 — API trả 8 mà admin UI vẫn 0 mục designer nhóm thì vẫn PASS.
- **Đề nghị:** hoặc thêm dòng checklist "8 leaf `AwV2_KHKK_N1..N8` hiện dưới Quy trình duyệt (Mới), click mở đúng designer của Code tương ứng", hoặc khai TỪ CHỐI phạm vi (1 designer type-10 dùng chung) và cho owner biết vì đó là thứ anh nhìn thấy.

### F-L1-4 [HIGH] — 4 OWNER-GATE được khai nhưng KHÔNG nối vào wave nào ⇒ không chặn được gì
- **Claim:** §② liệt OG-1/OG-2/OG-3/OG-5 (spec:26-30) nhưng §③ checklist (spec:36-44) **0 dòng** nhắc tới gate; bảng wave (spec:22-24) cũng không ghi tiền-điều-kiện.
- **Cụ thể OG-2 (roster map vai→user):** phải gật TRƯỚC **K3** (wave seed 8 workflow, spec:22) — spec nói "cần anh gật trước khi seed" (spec:28) nhưng DoD K3 (spec:38) không có dòng đó. Fan-out B4 dặn worker "bám sát spec-chi-tiết + checklist" (draft:86) ⇒ worker đọc §③, nơi gate vô hình.
- **Rơi hẳn:** cảnh báo ⚠️ của draft:52 *"verify bản LIVE không phải archived trước khi wire"* không xuất hiện ở bất kỳ đâu trong spec ⇒ nguy cơ seed roster từ bản workflow đã archive.
- **Đề nghị:** mỗi wave có gate thì DoD mở đầu bằng "[BLOCKED-BY OG-n] — chưa có gật thì không chạy"; thêm 1 dòng checklist "OG-1/2/3/5 đã có phán quyết bằng văn bản (link) trước khi cụm tương ứng khởi động"; OG-2 kèm bước verify workflow `QT-DN-V2-001` bản LIVE.

### F-L1-5 [HIGH] — Repurpose `Lines` per-hạng-mục phá nghĩa `PeReferenceAmount`, checklist 0 bất-biến tài chính
- **Claim:** QĐ2 (spec:13) đổi `Lines` từ 1-row-per-NCC sang N-row-per-(NCC × hạng mục). Entity hiện tại nói rõ **"1 row = 1 NCC (winner của PE nguồn)"** và `PeReferenceAmount` = **SNAPSHOT tổng báo giá của NCC này trên phiếu PE (SUM Quote.IsSelected)** — một con số PER-NCC-TOÀN-PHIẾU.
- **Evidence:** `src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs:6` (comment "1 row = 1 NCC"), `:16-20` (`PeReferenceAmount` snapshot), `:9-10` (UNIQUE (PlanId, SupplierId) filtered `WHERE IsDeleted=0`).
- **Hệ quả:** sau khi nhân dòng, mọi nơi SUM `PeReferenceAmount` theo phiếu sẽ nhân N lần (cùng NCC lặp lại trên N hạng mục). Spec tự gọi đúng tên lớp lỗi (S87/S88, spec:32) nhưng guard duy nhất là **quy trình** ("grep MỌI consumer"), còn acceptance K2 (spec:37) chỉ đo tạo phiếu / 409 / mở phiếu cũ / test validator âm — **không có một phép so tiền nào**.
- **Đề nghị:** thêm dòng acceptance đo được: "trên ≥1 phiếu KHKK có sẵn, tổng tiền hiển thị/API TRƯỚC và SAU Mig 71 bằng nhau (chụp số)"; và chốt luôn ngữ nghĩa mới của `PeReferenceAmount` ở cấp dòng (chia? lặp? NULL?).

### F-L1-6 [MED] — Acceptance K7 `giaTri == ApprovedAmount` mâu thuẫn chính default OG-1
- **Claim:** OG-1 default = **GỘP** N line → 1 HĐ (spec:12) nhưng acceptance K7 viết số ít `giaTri == ApprovedAmount` (spec:42); gộp thì phải là `SUM(ApprovedAmount)` của các line cùng (NCC × dòng-danh-mục).
- **Evidence:** `ContractSigningPlanLine.cs:24` `public decimal? ApprovedAmount` — **nullable**, "CHỈ ghi tại choke-point finalize" ⇒ gộp khi có line NULL chưa được định nghĩa (bỏ qua? coi 0? chặn?).
- **Thiếu thêm:** **phạm vi gộp** không khai — trong 1 phiếu, trong 1 PE cha, hay toàn cục theo (SupplierId, CatalogEntryId)? Đọc thẳng mặt chữ "gộp theo cặp (NCC × dòng-danh-mục)" có thể gộp xuyên phiếu/xuyên dự án.
- **Đề nghị:** viết lại acceptance thành `giaTri == SUM(ApprovedAmount) các line thuộc nhóm gộp` + khai rõ khoá gộp gồm cả `ContractSigningPlanId` (hoặc PE cha) + xử lý NULL.

### F-L1-7 [MED] — Treo #6 (phiếu KHKK cũ) chỉ được trả lời nửa MENU, rơi hẳn nửa DỮ LIỆU
- **Claim:** draft:76 hỏi *"Mục '1. Kế hoạch ký kết HĐ (NCC-TP)' cũ (đã có phiếu?) → migrate/giữ/ẩn thế nào"*. QĐ6 (spec:17) trả lời phần MENU (giữ key, đổi label). Phần DỮ LIỆU: QĐ3 thêm cột `ApprovalGroup **int**` (spec:14 — mặt chữ = non-null) mà **không có backfill/default** cho phiếu đã tồn tại.
- **Hệ quả đo được:** K4 (spec:39) "leaf lọc đúng `ApprovalGroup`" ⇒ phiếu cũ mang group mặc định 0 sẽ **không thuộc leaf nào trong 8 nhóm** = biến mất khỏi điều hướng mới. K2 (spec:37) "phiếu cũ mở nguyên vẹn" chỉ nói `CatalogEntryId=NULL`, không đả động ApprovalGroup.
- **Đề nghị:** chốt `ApprovalGroup int?` (nullable) + quy tắc hiển thị phiếu chưa gán nhóm (leaf "Chưa phân nhóm" hoặc backfill về nhóm 1 kèm bằng chứng đếm row trước/sau).

### F-L1-8 [MED] — Số leaf 48 vs 42 + hai dạng key song song (hệ quả của QĐ6, không ai đo)
- **Claim:** QĐ6 (spec:17) viết "nhóm 2-8 = key mới `Khkk_G{n}_*` **(48 leaf)**" — nhóm 2-8 là **7 nhóm × 6 = 42 leaf mới**; 48 là TỔNG (khớp checklist spec:39 "8 mục + 48 leaf").
- **Evidence hình dạng thật:** `DbInitializer.cs:1780` `Khkk_G1` là **GROUP** dưới root `KeHoachKyKet`; 6 leaf con là `Khkk_WfView/List/Create/Pending/Approved/Deleted` (`:1782-1787`) — **không có infix `G1_`**. Giữ key cũ ⇒ tồn tại song song 2 dạng (`Khkk_List` và `Khkk_G2_List`), mọi parser/route-map/seeder-grant phải xử 2 nhánh.
- **Đề nghị:** khai rõ tổng 8 group + 48 leaf, trong đó **42 leaf mới + 7 group mới = 49 MenuItem mới**; thêm acceptance "0 chỗ nào parse key bằng cách cắt cứng prefix" hoặc chấp nhận rename có backfill label (khuôn S155 đã nhắc).

### F-L1-9 [MED] — K4 thiếu deliverable "route-map FE ×2 app"; acceptance K4 không ghi ×2 app
- **Claim:** leaf không có route ⇒ **biến mất im lặng**, không lỗi. Spec K4 (spec:23, :39) không nhắc gì tới static route map.
- **Evidence:** `fe-user/src/components/Layout.tsx:59-62,:82-83` và `fe-admin/src/components/Layout.tsx:35-40` đều **hardcode đúng 6 key `Khkk_*`**; `fe-admin/src/components/Layout.tsx:270` `if (!path) return null` (leaf tự rụng); comment ngay tại `fe-admin/…/Layout.tsx:40`: *"[S159-đợt5 FLAG-1] thiếu là leaf drop SILENT (gotcha #50)"*; run S159 đã dính thật ("fe-admin thiếu 2 route Khkk → FIX +2 staticMap").
- **Thêm:** K2/K5 có chữ "×2 app" trong acceptance, K4 **không** ⇒ rớt 1 app vẫn PASS. (Lưu ý tương tác: nếu K6 whitelist land trước, nhánh admin của 42 leaf thành vô ích — spec nên nói rõ K4 chỉ làm fe-user hay cả 2.)

### F-L1-10 [MED] — "only-ONE-active-per-type VÔ HẠI" chưa chứng đủ, và acceptance K3 không thể phát hiện nếu sai
- **Claim:** spec:14 khẳng định vô hại vì "create chỉ check type, picker lọc `isUserSelectable`" — biện luận này phủ 2 consumer, không phủ hết.
- **Evidence:** `SolutionErp.Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs:341-343` — `.Where(w => w.ApplicableType == typeEnum && w.IsActive)` rồi `foreach (var old in actives) old.IsActive = false;` ⇒ mỗi lần tạo bản mới **tắt toàn bộ workflow cùng type**; sau khi seed 8 nhóm sẽ còn **1 active / 7 inactive**, và mỗi lần admin tạo version cho nhóm X thì 7 nhóm kia lại bị tắt (hazard LẶP, không chỉ lúc seed). Consumer khác vẫn đọc cờ này: `:210` `versions.FirstOrDefault(v => v.IsActive)`.
- **Acceptance mù:** K3 (spec:38) chỉ đếm `GET …?applicableType=10` trả 8 — trả 8 bản kể cả 7 bản `IsActive=false` thì vẫn PASS.
- **Đề nghị:** acceptance K3 thêm "8 bản đều `isActive=true` sau seed" **và** "tạo version mới cho nhóm 5 xong, 7 nhóm còn lại vẫn active" (đây mới là phép làm TRƯỢT nếu 'vô hại' sai).

### F-L1-11 [MED] — "App in khối ký nháy" không có chủ sở hữu
- **Claim:** lời 5 (draft:11) + draft:25 *"app in khối ký đúng pattern"*; spec chỉ để nó trong ngoặc lý do của QĐ4 ("hiển thị/in khối ký", spec:15). Không wave nào nhận, không dòng checklist nào đo. GĐ4 đã chốt 1-mốc xong từ S161 ⇒ không có wave GĐ4 để rơi vào.
- **Đề nghị:** hoặc gắn vào K1 (render khối ký từ chuỗi vai của dòng danh mục) với acceptance "in thử 1 phiếu, khối ký khớp cột chuỗi-nháy", hoặc khai defer công khai.

### F-L1-12 [MED] — QĐ4 (vai TEXT 2 tầng) là ứng viên OWNER-GATE bị tự chốt (chốt lậu ứng viên #1)
- **Claim:** QĐ4 (spec:15) quyết định rằng chuỗi vai (TBP.CCM, QS…) chỉ là **TEXT** trên danh mục, còn người duyệt thật nằm ở V2 Levels — **không có ràng buộc nào giữa 2 tầng**. Hệ quả nghiệp vụ: giấy in ra ghi "TBP.CCM ký" trong khi người bấm duyệt có thể là ai khác; hệ thống không phát hiện, không có phép đo.
- **Vì sao là quyền owner:** đây là thứ owner nhìn thấy trên bản cứng và là lý do tồn tại của SP-002 (ai được ký). 4 quyết định khác được dán nhãn OG (1/2/3/5); QĐ4 thì không, dù mức độ nhìn-thấy-được cao hơn OG-3 (NĐUQ tham khảo).
- **Đề nghị:** gán [OWNER-GATE] hoặc khai LIMITATION tường minh + 1 dòng acceptance đối chiếu (ví dụ: cảnh báo mềm khi người duyệt ở Level không khớp vai ghi trên dòng danh mục).

### F-L1-13 [MED] — K8 dry-run chạy PROD với 2 tác dụng phụ tự khai mà 0 gate
- **Claim:** spec:32 tự liệt rủi ro K8: "HỐ-1 S160 (HĐ DaPhatHanh không xóa được qua API)" + "notification bắn user thật"; acceptance K8 (spec:43) đòi "mã phiếu thật" ⇒ chạy trên dữ liệu thật. Draft:54 để ngỏ "prod/UAT". Không có OG nào cho việc tạo bản ghi **không xoá được** và ping nhân viên thật.
- **Đề nghị:** thêm [OWNER-GATE] "dry-run chạy prod hay UAT; nếu prod thì chấp nhận N bản ghi ZZTEST tồn tại vĩnh viễn + ai bị notify" — hỏi TRƯỚC K8, không hỏi sau.

### F-L1-14 [LOW] — Acceptance K1 chỉ đo số dòng ⇒ bảng thiếu cột vẫn PASS
- **Claim:** draft:46 liệt cột bắt buộc: "mã nhóm A1…C3 + tên **VN/EN** + nhóm-duyệt 1-8 + người-ký + chuỗi nháy" (+ cột NĐUQ tham khảo theo QĐ5). Acceptance K1 (spec:36) chỉ đo N dòng + CRUD 200 + seed idempotent + `dotnet test`.
- **Cụ thể rơi:** "tên EN" không xuất hiện một lần nào trong spec.
- **Đề nghị:** liệt kê tập cột tối thiểu ngay trong dòng acceptance.

### F-L1-15 [MED] — Lời 2+3 (3 trục độc lập / "đưa vào loại hợp đồng gì") chỉ được ăn một nửa
- **Claim (a):** draft:27 cảnh báo *"Chữ 'loại HĐ' trong hội thoại mang 2 nghĩa — **đặt tên field tách bạch**"*. Spec không mang cảnh báo này sang, và chính spec dùng cả "đưa vào loại HĐ" (spec:12, nghĩa = dòng danh mục SP-002) lẫn "7 loại HĐ" (spec:7, nghĩa = ContractType) trong cùng một file ⇒ đúng cái bẫy owner cảnh báo.
- **Claim (b) nặng hơn:** **cơ chế CHỌN** loại HĐ/khuôn form lúc bắc cầu KHKK→HĐ không giao cho ai. K7 (spec:24, :42) chỉ nói bridge đọc `ApprovedAmount` + gộp theo OG-1; ai/ở đâu chọn `ContractType` (7 loại, quyết cả mã HĐ RG-001 lẫn workflow pin) thì trống. Lời 3 (draft:9) đòi đúng bước này.
- **Đề nghị:** 1 dòng acceptance K7: "màn tạo HĐ từ KHKK bắt chọn ContractType (mặc định suy từ …), mã HĐ sinh đúng RG-001 theo loại đã chọn".

## Điểm CHỊU ĐƯỢC soi (khai 2 chiều, chống Smart-Friend)

- **P1 — khai thiếu OG-4 là THẬT:** spec:30 viết "Không có OG-4 trong sub-file — số nhảy là của lane, giữ nguyên không bịa". Đo lại: `grep -n "OG-" sub-invest-fable-b1a.md` → hit ở `:36 (OG-1)`, `:39 (OG-2)`, `:40 (OG-3)`, `:43 (OG-5)`, `:53 (OG-2)`, `:57 (OG-1)` — **0 hit OG-4**. Khai đúng, không bịa lấp chỗ trống.
- **P2 — tôi ĐÃ ĐỊNH flag sai và tự bác:** dự định flag "GĐ4 không có sidebar 7 loại để mirror" ⇒ đo đĩa thì `Hdc_*` 7 leaf có thật (`DbInitializer.cs:1868`, nhãn `:1981-1987`) ⇒ **BỎ finding**, mảnh (iii) đứng vững.
- **P3 — QĐ7 gọi đúng tên hàm có thật:** `isAdminHidden` (`fe-admin/src/components/Layout.tsx:182-184`, hiện chỉ chặn `Ct_`) + `filterForAdmin` (`:186-190`) + sidebar admin render 100% từ menu-tree động (`:315`) ⇒ cơ chế "1 file FE, 0 BE" khả thi. Lỗi của QĐ7 nằm ở **tập whitelist** (F-2), không ở cơ chế.
- **P4 — header spec khai rõ lane E/F chết + lead compile từ nhãn inline** (spec:3) ⇒ truy được nguồn từng quyết định; đây là lý do lens này soi được §② nhanh.

## Verdict

**FAIL (spec chưa đủ chuẩn để mở fan-out B4 nguyên trạng) — 15 finding: 5 HIGH · 9 MED · 1 LOW.**

Không phải bác toàn bộ: khung 8 wave / 3 cụm và 5/7 mảnh scope có wave phủ, luật khuôn được ràng bằng acceptance SHA. Nhưng 3 thứ phải sửa TRƯỚC khi worker cầm spec:
1. **F-2 + F-3** — mảnh (iv) đang thực thi ngược lời owner (giữ 2 designer cũ) và thiếu hẳn nửa "8 designer".
2. **F-4** — 4 owner-gate không nối vào wave nào ⇒ B4 sẽ chạy xuyên gate mà không ai chặn (đặc biệt OG-2 phải gật trước K3).
3. **F-1 + F-5** — K1 không có nguồn theo tên đã ghi; K2 đổi cardinality mà 0 bất-biến tiền tệ trong checklist.

Ba dòng acceptance đang **không phân biệt được đúng/sai** (mù): K6 (spec:41 — F-2), K3 (spec:38 — F-3 và F-10), K1 (spec:36 — F-14).

END — TOTAL: 15 finding (F-L1-1 → F-L1-15), 5 HIGH / 9 MED / 1 LOW.
