# 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)
