GATE-K7: PASS-WITH-FLAGS 10 # Gate K7 — adversarial review cầu KHKK → HĐ (S167, 2026-08-01) Vai: reviewer (READ-only). Base đo: HEAD = `f5ee146` (`wal: flush 20260801T1242`), diff UNCOMMITTED. Nguyên tắc: mọi mục dưới đây là phép đo TỰ CHẠY của reviewer, không chép claim của lane. --- ## §0 — Mốc cây + residual scope Đo bằng `git status --porcelain` + `git diff --stat` (2026-08-01, đầu lượt review). 13 file thuộc phạm vi K7 — CÓ ĐỦ, đúng như spec giao: | Nhóm | File | Trạng thái | |---|---|---| | BE | `src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs` | M (+33) | | BE | `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs` | M (+23/-2) | | BE | `src/Backend/SolutionErp.Application/ContractSigningPlans/CreateContractFromSigningPlanFeatures.cs` | **?? MỚI** (252 dòng) | | BE | `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` | M (+86) | | FE | `fe-{admin,user}/src/hooks/usePipelineStages.ts` | M (+61/-?) ×2 | | FE | `fe-{admin,user}/src/pages/khkk/KhkkDetailPage.tsx` | M (+308) ×2 | | FE | `fe-{admin,user}/src/types/khkk.ts` | M (+49) ×2 | | Test | `tests/SolutionErp.Infrastructure.Tests/Api/AuthorizePolicyRegressionTests.cs` | M (+48/-?) | | Test | `tests/SolutionErp.Infrastructure.Tests/Api/KhkkBridgeAuthzTests.cs` | **?? MỚI** | | Test | `tests/SolutionErp.Infrastructure.Tests/Application/CreateContractFromSigningPlanTests.cs` | **?? MỚI** | **FLAG RESIDUAL (F-R1, MINOR)** — 3 file dirty NGOÀI cả 13-file-set LẪN lead-set đã khai (`.claude/*` + `docs/STATUS.md`): - `scripts/distill-shard-probe.ps1` (+1/-1) - `scripts/measure-agent-memory.ps1` (+1/-1) - `scripts/memory-archive-gate.ps1` (+11/-2) Đọc diff: cả 3 là việc **governance memory-index rotation `_INDEX-.md` @S167**, KHÔNG liên quan K7. Không phải lỗi nội dung; rủi ro là **`git add -A` sẽ nuốt chúng vào commit K7**. Đề nghị: hoặc khai chúng vào lead-set, hoặc commit tách. Untracked trong `src`/`tests` = đúng 3 file MỚI đã khai, **0 file lạ**. --- ## §1 — TRỌNG TÂM 1: hội-2-khoá 2 tầng (BE ∧ FE) ### 1a. Tầng server — ĐO ĐƯỢC, ĐẠT - Class-level: `ContractSigningPlansController.cs:29` → `[Authorize(Policy = "KeHoachKyKet.Read")]`. - Action mới: `ContractSigningPlansController.cs:227-228` → `[HttpPost("{id:guid}/create-contract")]` + `[Authorize(Policy = "Contracts.Create")]`. - ASP.NET Core cộng dồn class + action theo AND ⇒ hội 2 khoá là THẬT, không phải claim. - Policy `Contracts.Create` có tồn tại: `MenuKeys.cs:12` khai const `Contracts`, và `MenuKeys.cs:228` đưa `Contracts` VÀO mảng `All` ⇒ `Program.cs` sinh đủ 4 policy. (Nếu key ngoài `All` thì runtime 500 chứ không 403 — đã kiểm, không rơi vào ca đó.) ### 1b. Tầng FE — ĐỌC DIFF TỪNG DÒNG (lead ghi §3-§4 on-behalf; đây là lưới THẬT đầu tiên) `fe-user/src/pages/khkk/KhkkDetailPage.tsx` (bản admin byte-identical, xem §3c): - `:138-140` — `const { can } = usePermission()`; `canReadPlan = can('KeHoachKyKet','Read')`; `canCreateContract = can('Contracts','Create')`. Đúng 2 key của server, KHÔNG OR `Khkk_*` (gotcha #85 giữ được). - `:299` — `showBridge = phase === KhkkPhase.DaDuyet && canReadPlan`. - `:303-312` — `bridgeBlockReason`: nhánh ĐẦU TIÊN là `!canCreateContract` ⇒ nút render nhưng `disabled`. - `:352-363` — `