# sub-style-review-fable — STAGE B style-review 2 bản invest "Duyệt Hợp đồng (GĐ3)" — YC-023 lượt 3/4

> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `back-end-reviewer-style` · ENGINE `/fable-real` (H21 lệnh-A, single deep-pass, propose-only) · 2026-08-10.
> Luật soi = LUẬT LÕI owner: **tính năng ĐÃ DEPLOY PRODUCTION = CHUẨN** — đề xuất phải theo khuôn bản đã chạy (PE V2 · KHKK · Contract V2 · bridge K7); lệch = FLAG trừ khi owner đổi trong chính YC-023.
> Mọi đối chiếu đọc THẬT trong phiên này (không trích trí nhớ): `ApprovalWorkflow.cs` · `ApprovalWorkflowV2AdminFeatures.cs:190-389` · `ContractSigningPlanWorkflowGuards.cs` trọn · `CreateContractFromSigningPlanFeatures.cs` trọn · `ContractWorkflowService.cs:40-370` · `ContractFeatures.cs:30-155/:295-339/:520-634` · `ContractSigningPlanFeatures.cs` (grep pin/re-pin) · `ContractsController.cs` · `ContractSigningPlansController.cs:1-40/:285-315` · `DbInitializer.cs:530-629` · `ContractPhase.cs` · `MenuKeys.cs:123-134` · gotchas #82/#85 (`docs/gotchas.md:1375-1399`) · `docs/workflow-contract.md:1-50` · Mig 72 trên đĩa.
> File ghi TỪNG MỤC (chống #53). KHÔNG quyết nghiệp vụ — câu owner giữ nguyên nhãn CHỜ-ANH của 2 con invest.

---

## §A — Per-claim style-check §2d BE: 3 phương án chấm theo tiền-lệ CODE THẬT

### A1. Bảng tiền-lệ đã deploy (đo trong phiên)

| Mảnh khuôn | Bằng chứng code thật | Trạng thái trên đĩa |
|---|---|---|
| `ApplicableType` 1-giá-trị-cho-cả-7-loại-HĐ | `ApprovalWorkflow.cs:57` `Contract = 3, // HĐ general (any ContractType)`; slot riêng `:70` `ContractSigningPlan = 10` | ✅ khớp invest §1b |
| Họ-Code cùng type (khuôn PA-A) | `DbInitializer.cs:540-542` per-Code CỐ Ý né khuôn check-per-type + `:607-610` `$"KHKK-N{n}"` / `AnyAsync(w.Code == code)` | ✅ ĐÃ DEPLOY (S161-S182, qua UAT) |
| Guard họ 1-nhà-duy-nhất | `ContractSigningPlanWorkflowGuards.cs:44-45` regex `^KHKK-N([1-8])$` + `:23-32` tự khai "chép lần nữa = 3 bản chuỗi rời nhau" + `:65-78` 409 khớp nhóm | ✅ ĐÃ DEPLOY |
| Normalize Code đường ghi + deactivate per-Code | `ApprovalWorkflowV2AdminFeatures.cs:348` `ToUpperInvariant()` + `:370-377` ternary per-Code CHỈ type-10 | ✅ ĐÃ DEPLOY (vá S182) |
| Cột phân-nhánh THẬT trong type (khuôn PA-B) | `ApprovalWorkflows` V2: **0 cột nào** (entity `ApprovalWorkflow.cs:21-51` — Code/Version/Type/Name/IsActive/IsUserSelectable/CeoApprovalThreshold, hết) | ❌ CHƯA TỪNG deploy trên bảng V2 |
| Tổ-tiên hình-dạng của PA-B | **V1** `WorkflowDefinition.ContractType` — còn SỐNG, bridge K7 vẫn pin `CreateContractFromSigningPlanFeatures.cs:163-166` | ✅ deploy nhưng là máy V1 |
| Discriminator phiếu | `Contract.Type` có sẵn (bridge ghi `:188`) — KHKK phải đẻ cột Mig 71, HĐ khỏi | ✅ khớp invest |

### A2. Chấm 3 phương án theo LUẬT LÕI

- **PA-A (họ-Code, 0-mig)** — **ĐÚNG KHUÔN ĐÃ DEPLOY NHẤT.** Cả 4 mảnh khuôn KHKK (seeder per-Code · guard 1-nhà · normalize · deactivate per-Code) đều sống trên đĩa và đã qua UAT S179/S182. ĐIỀU KIỆN BẮT BUỘC invest đã tự nêu đúng: bản vá S182 hiện **hard-wire type-10** (`ApprovalWorkflowV2AdminFeatures.cs:370` `typeEnum == ContractSigningPlan`) ⇒ W1 gỡ Bom 1 + Bom 2 KHÔNG phải "nice-to-have" mà là phần khuôn còn thiếu cho type-3. Verdict: **KHỚP**.
- **PA-B (cột thật, 1-mig)** — hình-dạng có tổ-tiên production ở **V1** (`WorkflowDefinition.ContractType`) nhưng trên **bảng V2 = pattern chưa từng deploy**; bảng dùng chung 10 `ApplicableType` ⇒ blast Designer/AdminFeatures/DTO như invest kê. Theo LUẬT LÕI: **LỆCH khuôn V2 hiện hành** ⇒ chỉ đi khi owner ratify — invest treo CHỜ-ANH #2 là đúng kỷ luật, không FLAG.
- **PA-C (giữ chọn-tay)** — chính là hiện trạng deploy (`CreateContractFromSigningPlanFeatures.cs:23-25` comment tự khai "KHÔNG suy được từ ContractType, người bấm phải chọn"; guard 4 vế `:134-148` không có vế so loại). KHỚP style tuyệt đối nhưng không đạt đề bài YC-023 — invest loại: đúng.
- **A-trước-B-sau** — style-safe (khởi đầu bằng khuôn đã chạy, unify là bước sau khi 2 họ cùng sống). Không phản đối về style; chọn A/B/lai = **CHỜ-ANH #2 (giữ nguyên)**.

### A3. Spot-check độ chính xác neo của invest BE (per-claim, đọc lại từng chỗ)

| Claim (sub-invest-be-fable.md) | Đối chiếu đĩa | Verdict |
|---|---|---|
| §1a bảng 7 mảnh V2-wire (`:24-32`) | `Contract... ApprovalWorkflowId` + `ContractWorkflowService.cs:111-116` branch + `:230` ApproveV2Async + UPSERT LevelOpinions `:295-329` + `ContractPhase.cs:14-28` 5-state + submit `:70-102` vai 4 vế `:84-88` | KHỚP |
| §1b guard bridge không so loại (`:38`) | `:134-148` đúng 4 vế tồn-tại/type/IsActive/IsUserSelectable — 0 vế ContractType | KHỚP |
| §1c-1 pin Create OPTIONAL (`:47`) | `ContractFeatures.cs:37` `Guid? ApprovalWorkflowId = null` | KHỚP |
| §1c-2 Create thiếu 2 vế (`:48`) | `ContractFeatures.cs:79-87` chỉ exists+type; bridge có đủ `:141-148` | KHỚP (drift 1-luật-2-site THẬT) |
| §1c-3 UpdateDraft không re-pin (`:49`) | `ContractFeatures.cs:136-145` record 8 field, 0 `ApprovalWorkflowId`; đối chứng KHKK CÓ re-pin `ContractSigningPlanFeatures.cs:606` + `:636-645` | KHỚP |
| §2c Bom 1 + Bom 2 (`:72-73`) | `:370-377` ternary + comment `:363` "bất biến per-TYPE"; `:205-215` `versions.FirstOrDefault(v => v.IsActive)` Active-đơn | KHỚP |
| §5 authz hiện trạng (`:123-124`) | `ContractsController.cs` toàn file đúng 2 hit `[Authorize]` (`:13` trần class + `:32` Contracts.Read/deleted); Reject-trước-guard `ContractWorkflowService.cs:48-66` (0 check vai/phase) | KHỚP |
| §0 mốc Mig 72 (`:15`) | `Persistence/Migrations/20260806070242_AddMaHangMucToContractSigningPlanLines.cs` tồn tại | KHỚP |
| §5 tiền-lệ hội-2-khóa (`:125`) | `ContractSigningPlansController.cs:29` class `KeHoachKyKet.Read` + `:307` action `Contracts.Create` + comment `:293-305` | KHỚP |

⇒ **Invest BE: 100% claim tao spot-check đều neo đúng.** 0 flag về độ chính xác.

---

## §B — Khuôn mirror: các wave đề xuất so nếp scaffold đã ship

- **W0 (chốt trục)** — không code, không soi style. OK.
- **W1 (gỡ 2 bom + guard loại)** — hướng KHỚP bản vá S182 đã deploy. 2 nit (FLAG-5 §D): (i) khuôn deploy là **ternary tường-minh 3-vế + luật R-6 CẤM regex** trong predicate deactivate (`ApprovalWorkflowV2AdminFeatures.cs:365-368`) — chữ "predicate data-driven" của W1 khi thi công KHÔNG được tái-nhập regex; (ii) comment `:363` khắc "bất biến per-TYPE của các loại khác" phải sửa **CÙNG edit** — không sửa là tự đẻ đúng bug-class 1-luật-2-site mà Surprise #6 của chính invest đang tố.
- **W2 (seed họ type-3)** — mirror `SeedKhkkGroupWorkflowsAsync` per-Code idempotent + thiếu-user-bỏ-slot-log-warning (`DbInitializer.cs:540-548/:587-600`) = ĐÚNG khuôn deploy, kể cả rủi ro #51 ungated-prod invest đã kê. 1 nit (FLAG-6 §D): mã họ mới PHẢI ASCII — validator Designer `^[A-Za-z0-9._-]+$` (`ApprovalWorkflowV2AdminFeatures.cs:274-276`); seeder không qua validator nhưng admin tạo version mới thì QUA ⇒ seed mã có "Đ" là kẹt vòng đời. `HD-*` ASCII như invest viết = đạt; chốt luôn uppercase khớp normalize `:348`.
- **W3 (3+1 cửa pin)** — KHỚP khuôn: cửa TRÌNH đặt ở Infrastructure đúng lý do guards là `public static` (`ContractSigningPlanWorkflowGuards.cs:14-22`); cửa re-pin mirror KHKK UpdateDraft (`ContractSigningPlanFeatures.cs:636-645` — nhánh đổi-pin gọi đủ cụm guard). 1 nit (FLAG-7 §D): KHKK gom vế IsActive∧IsUserSelectable thành guard TÊN RIÊNG gọi từ N site (cụm `:501-516` EnsureWorkflowTypeAsync + EnsureWorkflowPinnableAsync + EnsureWorkflowGroupMatchAsync); Contract hiện inline `ContractFeatures.cs:79-87` — W3 "bổ 2 vế" nên bổ bằng cách **gọi nhà chung mới**, đừng inline bản thứ 2.
- **W4 (dry-run K8)** / **W5 (authz điều kiện W0-#5)** — đúng khuôn K8 S168 + khuôn 2-tầng KHKK; W5 để CHỜ-ANH là đúng vì lệch hiện tại (`[Authorize]` trần) là **lệch CÓ bằng-chứng owner**: *"chỗ hợp đồng cứ từ từ"* (S156, tái khẳng định @S159 — trích trong cả 2 invest) ⇒ không FLAG "thiếu authz" ở bản đề xuất, chỉ giữ câu treo.
- **Bộ CQRS + 3-file mig:** mọi wave 0-mig; mig điều-kiện (a)(b)(c) §4 đều khai 3-file rule + Down reversible = khớp `ef-core-migration` khuôn. Naming đề xuất PascalCase EN + policy `{menu}.{action}` — khớp; riêng họ mã workflow xem FLAG-6.

---

## §C — Đồng-nhất 2 bản invest: chỗ 2 lens nói ngược / lệch nhịp (liệt cho plan-review xử, không tự phân xử nghiệp vụ)

### FLAG-1 🔴 — Tiền-đề inbox: FE mang chẩn-đoán S156 STALE, BE đã đính chính — CODE ĐỨNG VỀ PHÍA BE
- claim gốc: `sub-invest-fe-fable.md:47` (*"BE /contracts/inbox + GetEligiblePhases từng đo là 100% V1-LEGACY ⇒ HĐ pin V2 đang ChoDuyet KHÔNG hiện"* — FE tự đánh dấu "cần re-verify") + `:102` wave F3 cột "Chặn bởi BE 🔴 CÓ".
- khuôn deploy đối chiếu: `ContractFeatures.cs:530-538` (inbox xét CẢ 2 nguồn, thoát-sớm phải cả 2 rỗng) + `:543` (`|| v2InboxIds.Contains(c.Id)`) + `:559-593` (`ResolveV2InboxIdsAsync` — match đúng Cấp-đang-chờ, OR-of-N `:588-589`). Khớp `sub-invest-be-fable.md:43-45` (§1c đính chính @W6 S161).
- verdict: **LỆCH (tiền-đề FE stale)** — re-verify mà FE xin đã làm xong tại đây: inbox **ĐÃ V2-aware trên đĩa**.
- sửa đề xuất: plan-review hạ F3 từ "chặn bởi BE" → **"verify-only trên máy sẵn có"** (đúng nhánh rủi ro FE tự viết: *"Nếu BE gộp vào /contracts/inbox sẵn có thì FE 0-đổi"*); diary S156 của investigator đính chính theo Surprise #2 BE.

### FLAG-2 🔴 — Wave mồ côi: FE F1/F2 khai BE-blocker mà wave-list BE KHÔNG có wave DTO nào
- claim gốc: `sub-invest-fe-fable.md:100-101` — F1 cần BE extend `ContractDetailDto` reverse-join `Line WHERE ContractId=id` (card "Từ KHKK"); F2 cần BE thêm `currentApproval` + `approvalFlow` (cookie-cutter DTO PE S17). Đối chiếu wave BE: `sub-invest-be-fable.md:138-145` W0→W5 — **0 wave giao 2 delta DTO này** (§3b BE có nhắc gap đọc-ngược `:100` nhưng chỉ cho `ApprovalGroup`, và không xếp wave).
- khuôn deploy đối chiếu: đo phiên này — `grep CurrentApprovalDto|ApprovalFlowDto` trong `src/Backend/SolutionErp.Application/Contracts/` = **0 hit** (PE có cặp DTO này từ S17 — khuôn để mirror); `Contract.cs` không mang con-trỏ ngược KHKK (link nằm phía `ContractSigningPlanLine.ContractId`, khớp FE §4).
- verdict: **LỆCH nhịp 2 lens** (không phải lệch sự-thật — cả 2 mô tả đúng, nhưng kế hoạch không khớp nhau).
- sửa đề xuất: plan-review **thêm 1 wave BE-DTO** (mirror cặp DTO PE + reverse-join nguồn KHKK vào `ContractDetailDto`) hoặc nhét tường minh vào W3/W4; không thì F1/F2 treo vô hạn.

### FLAG-3 🔴 — Trục 7-type ⟂ 8-nhóm: BE treo câu thượng-nguồn, FE viết CỨNG một nhánh
- claim gốc: `sub-invest-be-fable.md:60/:161` CHỜ-ANH #1 (trục 7 `ContractType` ⟂ 8 nhóm SP-002 ⟂ lai) ⟂ `sub-invest-fe-fable.md:72-81` (§5 chọn khuôn "1 trang + `?type=N`", "TÁI DÙNG TRỌN `Ct_*`, KHÔNG sinh bộ mới") — toàn bộ FE §5 + bảng wave F1-F5 mặc định trục 7-type, **không có nhánh điều-kiện** nếu owner chọn trục nhóm.
- khuôn deploy đối chiếu: cả 2 trục đều có tiền lệ sống — 7-type: 42 leaf `Ct_*` đã seed (S159); 8-nhóm: `ContractSigningPlan.ApprovalGroup` + họ `KHKK-N{n}` + 49 key `Khkk_G*` (S164-S169).
- verdict: **LỆCH đồng-bộ** (FE thiếu cờ điều-kiện, không sai nội-dung).
- sửa đề xuất: plan-review đánh dấu FE §5/F-wave = **điều-kiện theo đáp án BE-CHỜ-ANH #1**; nếu trục nhóm (hoặc lai) thắng thì phần menu/param FE phải vẽ lại theo khuôn `Khkk_G*` — nêu rõ trước khi trình anh để câu #1 được trả lời với đủ hệ quả 2 phía.

### C4 — Trùng câu owner (không phải mâu thuẫn — cần GỘP khi trình, KHÔNG nuốt)
Tổng câu treo giữ nguyên: **BE 7 (#1-#7) + FE 5 (#1-#5) = 12**. Map trùng để plan-review gộp khi hỏi anh (mã câu gốc giữ):
- BE#5 (authz Đ1/Đ2/Đ3) ≈ FE#2 (mở per-action authz bây giờ?) ≈ FE#5 (menu public 13/13 giữ?) — **1 cụm câu authz**, nên trình thành 1 khung có 2 tầng (#82 API ⟂ display).
- BE#1 (trục) — thượng-nguồn của FE#4 (per-LOẠI-HĐ tầng API) và toàn bộ FE §5 (FLAG-3).
- BE#7 (auto-pin có cho override tay?) — cùng dialog với FE-F1/F5 UX, khác câu, giữ riêng.
- FE#1 (bộ nút 2-nút KHKK-trim ⟂ 3-nút PE) + FE#3 (admin thấy GĐ3?) — không có bản BE tương ứng, giữ nguyên.

---

## §D — Clean-code trong địa phận: trùng lặp / pattern-drift từ chính các đề xuất

### FLAG-4 🔴 — PA-A kê thiếu 1 mảnh khuôn KHKK đã deploy: họ sidebar-key `AwV2_KhkkN*` (+32 policy)
- claim gốc: `sub-invest-be-fable.md:79-81` (PA-A "Chi phí Thấp: 0-mig") + `sub-invest-fe-fable.md:77-78` ("Key/policy: TÁI DÙNG TRỌN, KHÔNG sinh bộ mới" — lập luận cho `Ct_*`, không bàn key Designer).
- khuôn deploy đối chiếu: khi KHKK lên họ 8 Code, đội ĐÃ thêm **8 menu-key `AwV2_KhkkN1..8` VÀO `All`** ⇒ sinh 32 policy (`MenuKeys.cs:123-134`, comment `:123` "lệnh lead K3-N1") + fe-admin sidebar trỏ Designer per-code. Mirror trọn khuôn cho họ `HD-*` ×7 ⇒ +7 key +28 policy = **đổi 2 row canonical STATUS (Menu keys/Policies)** — không phải "0 key mới".
- verdict: **LỆCH-thiếu-kê** (bản mới thiếu mảnh bản cũ có, chưa ai yêu-cầu bỏ — đúng phép hỏi bắt buộc của LUẬT LÕI).
- sửa đề xuất: plan-review thêm 1 dòng quyết định: mirror +7 key `AwV2_Hd*` (theo tiền lệ K3-N1, kèm cập nhật 2 row STATUS + test T2 khoá drift) **hay** chốt bỏ có khai lý do (Bom-2 fix overview-family đã đủ đường vào Designer). Phần render sidebar admin = [NGOÀI ĐỊA PHẬN — chuyển front-end-reviewer-style @wave diff-review]; phần `MenuKeys.cs`/policy-count = BE.

### FLAG-5 🟡 — W1 "predicate data-driven": 2 ràng đi kèm khuôn S182 phải giữ
- claim gốc: `sub-invest-be-fable.md:141` (W1 — "predicate data-driven thay hard-wire `== ContractSigningPlan` tại `:370`").
- khuôn deploy đối chiếu: `ApprovalWorkflowV2AdminFeatures.cs:365-368` luật R-6 **CẤM regex** trong predicate deactivate (code ngoài họ phải vẫn per-Code cho type đã mở) + comment `:363` khắc "per-TYPE là bất biến của các loại khác".
- verdict: **KHỚP hướng — 2 nit**: mở type-3 bằng tập-type tường-minh (không regex, giữ R-6); sửa comment `:363` trong CÙNG edit (không sửa = 1-luật-2-site mới, đúng bug-class Surprise #6 của chính invest).
- sửa đề xuất: ghi 2 ràng này vào acceptance W1.

### FLAG-6 🟡 — W2 mã họ mới: ASCII-only theo validator Designer, tránh kẹt vòng đời
- claim gốc: `sub-invest-be-fable.md:79` (họ Code `HD-{...}` ×7).
- khuôn deploy đối chiếu: `ApprovalWorkflowV2AdminFeatures.cs:274-276` validator `^[A-Za-z0-9._-]+$`; normalize uppercase `:348`; KHKK dùng ASCII `KHKK-N{n}` (`DbInitializer.cs:609`). Seeder đi thẳng entity KHÔNG qua validator ⇒ seed mã có ký tự "Đ" vẫn vào DB nhưng admin **không tạo version mới được** qua Designer.
- verdict: **KHỚP (đề xuất đã viết `HD-` ASCII)** — chốt thành luật trong W2: mã họ = ASCII UPPER, token loại là hằng 1-nhà (mirror `GroupCodePattern` có nhà riêng cho họ mới, KHÔNG đụng nhà KHKK `ContractSigningPlanWorkflowGuards.cs:44-45`).
- ghi nhận trùng-lặp CÓ KHAI: PA-A đẻ regex-họ THỨ HAI — đúng cái `:28-32` cảnh báo về lớp "chuỗi họ nhân bản"; invest đã kê ở Rủi ro ("drift 2 họ regex") + PA-B là đường trả nợ ⇒ trùng lặp **có chủ đích, có lối thoát** — không FLAG thêm, chỉ ghi để diff-review soi đúng chỗ.

### FLAG-7 🟡 — W3 "bổ 2 vế" cho Create: bổ bằng NHÀ CHUNG, đừng inline bản thứ 2
- claim gốc: `sub-invest-be-fable.md:143` (W3 — "Create bổ 2 vế `IsActive ∧ IsUserSelectable` (đồng luật vá-3, xóa drift §1c-2)").
- khuôn deploy đối chiếu: KHKK đóng vế này bằng guard TÊN RIÊNG gọi từ N site — cụm `ContractSigningPlanFeatures.cs:501-516` (EnsureWorkflowTypeAsync + EnsureWorkflowPinnableAsync + EnsureWorkflowGroupMatchAsync) + nhánh re-pin `:636-645` gọi lại đúng cụm; lý-do-tầng ghi tại `ContractSigningPlanWorkflowGuards.cs:14-22`. Contract hiện inline (`ContractFeatures.cs:79-87`) và bridge inline bản riêng (`CreateContractFromSigningPlanFeatures.cs:134-148`) — đã là 2 bản chuỗi.
- verdict: **KHỚP hướng — nit kiến trúc**: nếu W3 inline thêm lần 3 là nhân bản luật lần nữa; gom 4 cửa (Create + bridge + re-pin + TRÌNH) về 1 nhà `public static` mirror khuôn KHKK.
- sửa đề xuất: W3 acceptance thêm vế "4 cửa gọi CÙNG method" (fault-inject 1 cửa quên gọi phải đỏ — khuôn test-before W1b S182).

### D-note (không flag): 
- Dual-pin V1+V2 ở bridge/Create (`:163-166/:201-202` + `ContractFeatures.cs:105-109`) là khuôn deploy CÓ CHỦ ĐÍCH (comment "Cả 2 set ok") — đề xuất giữ nguyên là đúng; đừng để diff-review đọc thành trùng lặp phải dọn.
- `ContractWorkflowService.cs:227-229` còn TODO stale ("table chưa tồn tại — Mig 33 sẽ scaffold") trong khi UPSERT đã sống `:295-329` — code ĐÃ DEPLOY = ngoài phạm-vi FLAG đợt này; ghi để wave nào chạm file thì nhân tiện dọn.
- `docs/workflow-contract.md:32` vẫn là bản 9-phase LEGACY (đúng vai tài-liệu gốc); mọi doc/plan GĐ3 mới nên trỏ `ContractPhase.cs:3-13` (5-state) + addendum skill, đừng trỏ nguyên trang 9-phase cho người mới — tránh đọc nhầm khuôn (docs/** ngoài paths deploy, không phải "chuẩn production").

---

## §E — Claim FE chạm BE tao đã verify hộ (còn lại NGOÀI ĐỊA PHẬN)

| Claim FE | Đo phiên này | Verdict |
|---|---|---|
| §5 "hiện 0 endpoint tiêu thụ key `Ct_*`" (`sub-invest-fe-fable.md:81`) | grep `Ct_` toàn `src/Backend/SolutionErp.Api` = 0 file | KHỚP |
| F4 "param `type` có sẵn" (`:103`) | `ContractsController.cs:23-27` `[FromQuery] ContractType? type` (W7 S161) + list filter server-side `ContractFeatures.cs:324-327` | KHỚP — F4 đúng là FE-only |
| F2 "CHẶN bởi BE DTO" (`:96/:101`) | BE-side 0 hit `CurrentApprovalDto/ApprovalFlowDto` trong Application/Contracts (PE có từ S17) | KHỚP — nuôi FLAG-2 |
| §5 gate `Contracts.*` root, không OR `Ct_*` leaf (`:79-80`) | đúng luật #85 (`docs/gotchas.md:1383-1385` — "gate mỗi query bằng đúng key policy của endpoint"; `MenuPermissionHandler` so chính-xác 1 key) + khớp tiền lệ hội-2-khóa `ContractSigningPlansController.cs:293-307` | KHỚP |

NGOÀI ĐỊA PHẬN — chuyển vai đúng tuyến @wave diff-review:
- [→ `front-end-reviewer-style`] toàn bộ §1/§2/§2-bis/§6 FE (Layout regex · ADMIN_HIDDEN_KEYS · TRANSIENT_QUERY_KEYS · khuôn `KhkkWorkflowPanel` vs `PeWorkflowPanel` · SHA-mirror 2 app · bộ nút sau owner-trim S174/S175) + phần sidebar của FLAG-4.
- [→ `database-reviewer-style`] nếu owner chọn nhánh có mig: (a) cột phân-nhánh PA-B trên `ApprovalWorkflows` · (b) `Contract.ApprovalGroup` · (c) cột gợi-ý loại HĐ trên `ContractCatalogEntries` — EF config + tên cột + backfill semantics (gotcha #64 SQLite-mù-migration) là đất con DB; tao chỉ giữ vế "3-file rule + naming PascalCase" đã soi ở §B.

---

## §F — Verdict tổng

**STYLE-REVIEW: PASS_WITH_FLAGS — 7 flag** (3 🔴 cross-lens/thiếu-kê: FLAG-1 tiền-đề-stale · FLAG-2 wave-mồ-côi · FLAG-3 trục-viết-cứng + FLAG-4 🔴 thiếu-kê khuôn key/policy; 3 🟡 nit thi-công W1/W2/W3).

- **Chấm §2d theo tiền-lệ:** PA-A = đúng khuôn V2 đã deploy nhất (kèm điều-kiện W1 gỡ 2 bom — chính là phần khuôn S182 chưa phủ type-3); PA-B = lệch khuôn V2 (tổ-tiên chỉ ở V1), đúng kỷ-luật khi treo CHỜ-ANH #2; PA-C = khớp style nhưng không đạt đề bài. A-trước-B-sau style-safe. **Không quyết thay anh.**
- **Độ tin 2 bản invest:** BE — mọi neo spot-check đều đúng (bảng §A3); FE — đúng ở phần FE + 4 claim chạm BE (bảng §E), sai duy nhất tiền-đề S156 đã stale (FLAG-1, FE có tự khai xin re-verify).
- **12 câu CHỜ-ANH giữ nguyên** (BE #1-#7 + FE #1-#5) — map gộp ở §C4 để trình anh 1 lần, không nuốt câu nào.

== HẾT ==
