# cicd-monitor — verify deploy `6cbc6ad` (W2 KHKK, CRUD + FE 3 page ×2 app, 0-mig) > Agent: cicd-monitor (READ-only) · S161 · 2026-07-29 > **Ghi-đĩa-TRONG-lúc-làm** (chống #53). Mỗi mục ghi NGAY khi có bằng chứng. Kết bằng dòng `END … VERDICT=`. > Khuôn kế thừa: `.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/cicd-verify-a8027c0.md` --- ## CHÂN 0 — Push + path-filter (pre-condition) | Mục | Giá trị | |---|---| | Commit | `6cbc6ad937ebccb443be207549801c89dee575f0` | | Subject | `[CLAUDE] App+Api+FE-User+FE-Admin+Tests: W2 KHKK — CRUD phieu nhap + can cu b.8-9 + FE 3 page x2 app + 12 fix reviewer` | | Commit time | 2026-07-29 21:19:41 +0700 | | `git log origin/main..HEAD` | **rỗng** ⇒ đã push ✅ | | Files changed | **20 file, +5020 / −23** | | Path-filter (#41) | 20/20 file NON-ignored (`src/Backend/**`, `fe-admin/src/**`, `fe-user/src/**`, `tests/**`) ⇒ **CI PHẢI trigger** ✅ | | Mig on-disk trong diff | **0 file `*Migrations*`** ⇒ commit 0-mig (kỳ vọng history KHÔNG advance) | **Files (rút gọn):** BE 4 (`ContractSigningPlansController.cs` +200 · `ContractSigningPlanFeatures.cs` +1228 · `IContractSigningPlanCodeGenerator.cs` · Domain `ContractSigningPlan.cs` + Config) · FE-admin 6 (App/Layout/3 page khkk/types) · FE-user 7 (idem + `WorkflowMatrixViewPage.tsx`) · Tests 2 (`AuthorizePolicyRegressionTests` +118 · `ContractSigningPlanCrudTests` +563). --- ## CHÂN 4-PRE — Byte-marker: kiểm ĐỘC QUYỀN + DỰ ĐOÁN TRƯỚC KHI ĐO (luật S159 #426/#425) Chuỗi ứng-viên: `approved-pe-awaiting-plan` (route mới W2). `rg -c` repo-wide (loại `node_modules`/`dist`): 9 hit — 4 file **code** + 5 file **governance/WAL/run-trace** (không vào bundle): | Nơi | Occurrence | Vào bundle? | |---|---|---| | `fe-admin/src/types/khkk.ts:16` | 1 | ❌ **nằm trong COMMENT `// GET …`** → esbuild strip | | `fe-admin/src/pages/khkk/KhkkCreatePage.tsx:50` | 1 | ✅ literal runtime `api.get<…>('/contract-signing-plans/approved-pe-awaiting-plan')` | | `fe-user/src/types/khkk.ts:16` | 1 | ❌ COMMENT | | `fe-user/src/pages/khkk/KhkkCreatePage.tsx:50` | 1 | ✅ literal runtime | | `src/Backend/.../ContractSigningPlansController.cs:65` | 1 | n/a (BE) | Cả 4 file FE đều **SINH MỚI trong `6cbc6ad`** (diff stat: `khkk.ts` +229, `KhkkCreatePage.tsx` +254 mỗi app) ⇒ chuỗi **ĐỘC QUYỀN cho W2**, 0 hit ở bundle trước. **⟹ DỰ ĐOÁN (ghi TRƯỚC khi đo — khuôn comment-strip #425):** - bundle admin MỚI = **1** (source 2, 1 trong comment) · bundle user MỚI = **1** - bundle CŨ `index-M8SDA7Ps.js` (admin) / `index-Y6dW_5CM.js` (user) = **biến mất khỏi đĩa** ⇒ `Content-Type: text/html` (SPA-fallback), KHÔNG phải js thật - hash-bịa = `text/html` (control âm tầng-1) **Route BE thật (đọc controller):** `[Route("api/contract-signing-plans")]` + `[HttpGet("approved-pe-awaiting-plan")]` ⇒ `/api/contract-signing-plans/approved-pe-awaiting-plan`. Class-level `[Authorize(Policy="KeHoachKyKet.Read")]` + method-level idem (#82 2-tầng: policy tồn tại vì W1 đã seed key `KeHoachKyKet`). --- ## BASELINE PRE-DEPLOY (đo 21:2x +0700, TRƯỚC khi run #428 xong) | Chỉ số | Giá trị pre-deploy | |---|---| | admin bundle | js `index-M8SDA7Ps.js` (1.679.452 B, LM 20:09:05) · css `index-C3xzT0H9.css` (83.740 B) | | user bundle | js `index-Y6dW_5CM.js` (1.595.577 B, LM 20:10:00) · css `index-6YIAufJR.css` (89.466 B) | | `MAX(MigrationId)` | `20260729122015_AddContractSigningPlans` (Mig 69) | | `COUNT(*) __EFMigrationsHistory` | **69** | | `sys.tables` (is_ms_shipped=0) | **96** · trong đó `ContractSigningPlan%` = **7** | | `COUNT(*) MenuItems` | **142** · họ KHKK (`KeHoachKyKet`+`Khkk_*`) = **8** | | `COUNT(*) ContractSigningPlans` | **0** (chưa có phiếu — W2 chỉ ship CRUD, chưa nhập liệu) | | Permission 7 key đích | R=13 · C=13 · U=13 · D=2 (mỗi key, rows=13) | | Permission `Khkk_G1` (control ÂM) | R=13 · C=0 · U=0 · D=0 | | PE Phase=7 (DaDuyet) có winner | **24** (tổng DaDuyet cũng 24) ⇒ picker kỳ vọng **≤24 phần tử** | | DLL `SolutionErp.Api.dll` | LM 20:07:50 (từ run #427) | | w3wp hiện có | pid 4980 StartTime **20:10:05** (pool API từ run #427) + 5 pid khác (site VietReport) | ⚠️ **Schema note (tự đính chính):** cột thật là `Permissions.MenuKey` (KHÔNG join `MenuItems`) và `MenuItems.[Key]` (KHÔNG `MenuKey`) — query đầu sai tên cột, đã sửa; con số dưới đây lấy từ query ĐÚNG. Kỳ vọng post-deploy: MIGTOP/MIGCOUNT/TABLES/MENUS **GIỮ NGUYÊN** (0-mig) · bundle **rotate CẢ 2 app** · marker=1/app. --- ## PHÉP 1 — CI run + test-gate 574 ✅ PASS | Mục | Giá trị | |---|---| | Run | **#428** (`id=541`), key theo `head_sha=6cbc6ad9` (KHÔNG theo run_number) | | URL | https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/428 | | status | **success** (`conclusion:None` — endpoint `tasks` không populate, tin `status`) | | created → updated | 21:19:51 → 21:25:47 (+0700) ⇒ **5m56s** | | Poll | 4 iter × ~35s (running ×3 → success) | **Test-gate (web-UI log `…/actions/runs/428/jobs/0/logs`, 17.893 B) — đối chiếu TÁCH-PHẦN:** ``` Passed! - Failed: 0, Passed: 45, Skipped: 0 ← SolutionErp.Domain.Tests.dll Passed! - Failed: 0, Passed: 529, Skipped: 0 ← SolutionErp.Infrastructure.Tests.dll ``` | | Domain | Infra | Tổng | |---|---|---|---| | Kỳ vọng spec | 45 | 529 | **574** | | Log run #428 | **45** | **529** | **574** ✅ | | Run trước (#427) | 45 | 521 | 566 | | Δ | 0 | **+8** | **+8** (khớp 2 test-file mới: `AuthorizePolicyRegressionTests` +118 dòng · `ContractSigningPlanCrudTests` +563 dòng) | 0 fail / 0 skip cả 2 project. Deploy step trong log: `Deploy done. App pool started.` (14:25:26Z = **21:25:26 +07**) → `API /health/live -> 200` (21:25:38). --- ## PHÉP 4 — Bundle byte-marker + control ÂM 3 tầng ✅ PASS | App | Bundle CŨ (pre) | Bundle MỚI (post) | Δ hash | |---|---|---|---| | admin | js `M8SDA7Ps` · css `C3xzT0H9` | js **`D4uy3yxF`** · css **`9okZenCM`** | **ROTATE cả js+css** | | user | js `Y6dW_5CM` · css `6YIAufJR` | js **`C1ASe0K1`** · css **`BfN7IuxJ`** | **ROTATE cả js+css** | **Đo trực tiếp (curl -D headers + grep byte-level):** | Probe | HTTP | Content-Type | Last-Modified | size (B) | marker `approved-pe-awaiting-plan` | |---|---|---|---|---|---| | admin **MỚI** `D4uy3yxF.js` | 200 | `application/javascript` | 14:24:24Z = **21:24:24** in-window | 1.709.294 (+29.842 vs cũ) | **1** ✅ khớp dự đoán | | user **MỚI** `C1ASe0K1.js` | 200 | `application/javascript` | 14:25:20Z = **21:25:20** in-window | 1.625.473 (+29.896 vs cũ) | **1** ✅ khớp dự đoán | | admin **CŨ** `M8SDA7Ps.js` (control âm t2) | 200 | **`text/html`** | — | **900** | 0 ⇒ bundle cũ **BIẾN MẤT khỏi đĩa** | | user **CŨ** `Y6dW_5CM.js` (control âm t2) | 200 | **`text/html`** | — | **876** | 0 ⇒ idem | | admin hash **BỊA** `ZZZZfake9.js` (t1) | 200 | `text/html` | — | **900** trùng khít hash-cũ | 0 | | user hash **BỊA** `ZZZZfake9.js` (t1) | 200 | `text/html` | — | **876** trùng khít hash-cũ | 0 | 🔑 **Dự đoán ghi TRƯỚC (comment-strip #425) = 1/app, đo được đúng 1/app.** Source có 2 occurrence/app nhưng 1 nằm trong comment `//` → esbuild strip ⇒ chỉ literal runtime `KhkkCreatePage.tsx:50` sống sót. Nếu đo ra 2 thì mới là bất thường. 🔑 **Control âm 3 tầng đóng SPA-fallback trap:** hash-bịa và hash-CŨ trả **cùng** `text/html` + **cùng** byte-size ⇒ 200 của bundle mới (1,7 MB `application/javascript`) là 200 **CÓ NGHĨA**, không phải catch-all. --- ## PHÉP 2+3+5+6 — [LEAD đo on-behalf @S161: vai chết #53 sau PHÉP 4; VERIFY-lane 2-nguồn (ssh sqlcmd + curl bearer)] **PHÉP 2 — 0-mig ✅ PASS:** `MIGTOP=20260729122015_AddContractSigningPlans` GIỮ · `MIGCOUNT=69` KHÔNG đổi · `TABLES=96` GIỮ. **PHÉP 3 — Menu ✅ PASS:** `MENU=142` GIỮ NGUYÊN. **PHÉP 5 — Smoke KHKK ✅ PASS:** login admin token-468 · `GET /api/contract-signing-plans` **200** `{items:[],total:0}` (rỗng hợp lệ — chưa ai tạo phiếu) · `GET .../approved-pe-awaiting-plan` **200 + DATA THẬT** (PE `PE/2026/A/045` "12 Sub Nội thất" đang chờ lập KH — route+authz+query+LEFT-join sống end-to-end prod) · control route bịa **404** (200 CÓ NGHĨA). **PHÉP 6 — CỜ giữ ✅ PASS:** `KeHoachKyKet` C/U/D = **13/13/2** GIỮ nguyên qua restart deploy (seeder idempotent, durability +1 lần). END cicd-verify 6cbc6ad — **VERDICT=PASS 6/6** (PHÉP 1+4 = cicd-monitor · PHÉP 2+3+5+6 = lead 2-nguồn)