CICD-K1: PASS — 6/6 mục # cicd-monitor — verify deploy K1 SP-002 (`50e6d8c`) > Artifact chống #53: ghi TỪNG mục NGAY khi đo xong. Kết bằng dòng `END … VERDICT=`. > Spawn 2026-07-31 · commit `50e6d8c38283a9dd45da2001bd2fc6596595558f` · READ-only (0 ghi prod) ## Stage 1 — Push + path-filter (PRE-điều-kiện) - `git log -1` → `50e6d8c38283a9dd45da2001bd2fc6596595558f|[CLAUDE] Contract: K1 SP-002 danh muc loai HD - ContractCatalogEntries (Mig 70) + seed 86 + CQRS + FE kind-5 x2 app + 8 test|2026-07-31 15:45:50 +0700` - `git log origin/main..HEAD` → **RỖNG** (đã push hết) - `git diff --name-only 50e6d8c^ 50e6d8c` → **20 file** (neo bằng `X^..X` theo LUẬT S162 CẤM-`HEAD~1`, vì phiên có Stop-hook WAL auto-commit): BE 11 · FE 6 · Test 1 · Docs 2 - vs `paths-ignore` 7-glob: **18/20 file KHÔNG bị ignore** ⇒ CI **PHẢI chạy**. ✅ ## Stage 1b — DỰ ĐOÁN TRƯỚC KHI ĐO (chống xác-nhận-ngược) Đọc `DbInitializer.SeedContractCatalogAsync` (dòng 3163-3325) **trước** khi chạm prod: - Parse lần 1 (regex line-anchored) → **78** ⇒ SAI (tuple xuống dòng). Vứt, KHÔNG báo cáo số này. - Parse lần 2 (multiline) → **86**; cross-check độc-lập `raw code-literal` = **86**, distinct = **86**, dup = **[]**. - Dự đoán `GROUP BY ApprovalGroup`: **1:5 · 2:10 · 3:5 · 4:23 · 5:14 · 6:15 · 7:13 · 8:1** - Dự đoán `GROUP BY GroupCode`: 11 nhóm — A1:5 A2:1 A3:9 A4:5 B1:23 B2:14 B3:15 C1:6 C2:3 C3:4 CEO:1 - Seeder idempotent (`existingCodes.Contains(code) continue` + `IgnoreQueryFilters` né #75/#76) ⇒ deploy đầu `added=86`. - Serilog dự kiến: `Seeded 86 contract catalog entries (S164 SP-002)` (chỉ ghi khi `added>0`). ## Bundle PRE-deploy (đo LIVE lúc run đang chạy, 15:47) | App | js | css | |---|---|---| | admin | `index-B43R6Y17.js` | `index-BZ5F-g1w.css` | | eoffice | `index-NLI5umBg.js` | `index-yNZDmVNz.css` | Khớp nguyên văn hash đề-bài (`B43R6Y17` / `NLI5umBg`) ⇒ mốc "TRƯỚC" hợp lệ. --- ## ✅ Mục 1 — Gitea Actions run - Run **id=547 · run_number=#434** · head_sha `50e6d8c3` · job `build-deploy` · runner `vps-vietreport-runner v0.2.13` - created `2026-07-31T15:46:06+07:00` (push 15:45:50 → pick sau **16s**) - Poll 7 iter × ~40s → iter7 `status=success`, updated `2026-07-31T15:52:37+07:00` - **Duration ≈ 6m31s** · `conclusion=None` (tasks API không populate — tin `status`, đúng khuôn đã biết) - URL: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/434 - **PASS** ## ✅ Mục 2 — Test gate 598 Nguyên văn từ web-UI log (`/actions/runs/434/jobs/0/logs`, 18.698 B): ``` 2026-07-31T08:46:34.2961110Z Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, Duration: 196 ms - SolutionErp.Domain.Tes… 2026-07-31T08:49:26.8226799Z Passed! - Failed: 0, Passed: 553, Skipped: 0, Total: 553, Duration: 1 m 44 s - SolutionErp.Infrastr… ``` - Domain **45** + Infra **553** = **598** — khớp baseline MỚI đề bài (590+8). Đối chiếu TÁCH-PHẦN: Domain **45 GIỮ NGUYÊN**, Infra **545→553 = +8** ⇒ đúng "7 lane K1 + T5b gate-fix" (delta rơi trọn vào Infra, đúng nơi file test mới nằm). - 0 fail / 0 skip cả 2 project. `Failed!`/`error CSxxxx` = **0 hit**. - ⚠️ Ghi chú phương-pháp: grep đầu tiên **0 hit** do format nhiều-khoảng-trắng (`Passed! - Failed: 0`); đã soi lại format thật rồi mới trích — KHÔNG báo "không thấy dòng test". - **PASS** ## ✅ Mục 3 — EF migration prod ``` 20260731072441_AddContractCatalogEntries ← TOP 20260729122015_AddContractSigningPlans 20260727033522_AddPeAllowApproverDelete 20260717032812_AddPeApprovedBudgetSnapshot ``` - `__EFMigrationsHistory` TOP == repo HEAD mig ⇒ **Mig 70 applied prod**. - Chân-kiềng (i) chứng-minh-restart-thật: `w3wp` PID **2644 StartTime 7/31/2026 3:52:13 PM** (trong cửa-sổ deploy 15:46:06→15:52:37) + dòng ĐẦU Serilog `2026-07-31 15:52:19.770 +07:00 [INF] Applying migrations...` ⇒ mọi số DB dưới đây đo **SAU** recycle (né đúng lỗi #423). - **PASS** ## ✅ Mục 4 — Seed prod 86 + phân bố 8 nhóm `sqlcmd` (SQL-auth `vrapp`, sau recycle): | Đo | Kết quả | |---|---| | `COUNT(*) FROM ContractCatalogEntries` | **86** | | `GROUP BY ApprovalGroup` | **1:5 · 2:10 · 3:5 · 4:23 · 5:14 · 6:15 · 7:13 · 8:1** | | `COUNT(DISTINCT Code)` | **86** (0 trùng) | | `IsActive=1` | **86** · `IsDeleted=1` = **0** | | `COUNT(DISTINCT GroupCode)` | **11** | Serilog (`solution-erp-20260731.log`) nguyên văn: ``` 2026-07-31 15:52:27.074 +07:00 [INF] Seeded 86 contract catalog entries (S164 SP-002) 2026-07-31 15:52:25.867 +07:00 [INF] Seeded 1 menu items 2026-07-31 15:52:26.030 +07:00 [INF] Seeded 1 admin permissions ``` - Control ÂM: `findstr "contract catalog KHONGTONTAI"` → **0 hit** ⇒ findstr có răng, không phải khớp bừa. - Đối chứng: 2 dòng `Seeded 1 menu items` / `Seeded 1 admin permissions` = menu-row `ContractCatalog` + permission cũng vào (leaf `Catalogs` thứ 5). - 🔑 **TAM-GIÁC 3 NGUỒN ĐỘC-LẬP khớp từng đồng**: source-parse (86 / 8 nhóm) == sqlcmd prod (86 / 8 nhóm) == **API materialize qua EF** (86 / 8 nhóm, xem Mục 6). Không nguồn nào suy ra nguồn kia. - **PASS** ## ✅ Mục 5 — Bundle đổi ×2 app (#77 byte-level) **Vế DƯƠNG — hash MỚI là asset THẬT:** | URL | code | content-type | size | |---|---|---|---| | admin `/assets/index-CKgq0YlH.js` | 200 | `application/javascript` | **1.751.576** | | admin `/assets/index-DD3xQhNt.css` | 200 | `text/css` | 86.298 | | eoffice `/assets/index-CHMCU35C.js` | 200 | `application/javascript` | **1.667.782** | | eoffice `/assets/index-CdXRNT7E.css` | 200 | `text/css` | 92.092 | **Vế ÂM — hash CŨ phải BIẾN MẤT khỏi đĩa** (né SPA-fallback-200-trap S72, discriminator = `Content-Type` không phải status): | URL cũ | code | content-type | size | |---|---|---|---| | admin `index-B43R6Y17.js` | 200 | **`text/html`** | 900 | | admin `index-BZ5F-g1w.css` | 200 | **`text/html`** | 900 | | eoffice `index-NLI5umBg.js` | 200 | **`text/html`** | 876 | | eoffice `index-yNZDmVNz.css` | 200 | **`text/html`** | 876 | ⇒ cả 4 rơi về SPA-shell 876/900 B (trùng khít size index.html) = file cũ đã bị ghi đè thật, KHÔNG phải cache. **Ship-proof `Last-Modified` in-window** (#69 TWO-MODE — hash-delta một mình không đủ): - admin `/` → `Last-Modified: Fri, 31 Jul 2026 08:51:06 GMT` = **15:51:06 +07** ✔ in-window · `Cache-Control: no-cache` - eoffice `/` → `Last-Modified: Fri, 31 Jul 2026 08:52:07 GMT` = **15:52:07 +07** ✔ in-window · `Cache-Control: no-cache` **4/4 hash ROTATE + 4/4 hash cũ chết + LM in-window. PASS** ## ✅ Mục 6 — Smoke | Check | Kỳ vọng | Đo được | KL | |---|---|---|---| | `GET /health` | (bare) | **404** | control — route bare không tồn tại | | `GET /health/ready` | 200 | **200** | ✅ | | `GET /health/live` | 200 | **200** | ✅ | | `GET /api/catalogs/contract-catalog` **KHÔNG token** | 401 | **401** | ✅ authz sống | | CONTROL `…/contract-catalog-KHONGTONTAI` | 404 | **404** | ✅ 401 CÓ NGHĨA | | CONTROL `…/bia-dat-hoan-toan` | 404 | **404** | ✅ (route bịa ⇒ 401 ở trên không phải blanket) | | `https://admin.solutions.com.vn/` | 200 | **200** | ✅ | | `https://eoffice.solutions.com.vn/` | 200 | **200** | ✅ | | **CÓ token admin** `GET …/contract-catalog` | 200 + 86 row | **200, 25.374 B, 86 row** | ✅ | Cross-check EF-materialize (mạnh hơn sqlcmd vì đi qua Domain→Infra→Api thật): ``` rows materialized via EF = 86 by approvalGroup: 1:5 . 2:10 . 3:5 . 4:23 . 5:14 . 6:15 . 7:13 . 8:1 distinct codes = 86 sample row: code=A1-01 group=1 signer=TP.CCM ``` Class-level `[Authorize]` + ghi `[Authorize(Roles="Admin")]` (đọc `ContractCatalogController.cs:21-22,39-56`) ⇒ 401-không-token đúng thiết-kế, không phải #44 silent-403. - **PASS** --- ## Ghi chú lệch / quan sát (KHÔNG phải FAIL) 1. **Hash CI ≠ hash local build.** Đề bài dự-báo local ra admin `BE7zE81C`/user `DSXJJbJq`; CI ra `CKgq0YlH`/`CHMCU35C`. Tiêu chí đề bài (ĐỔI so với trước + curl 200) **thoả**. Nghĩa: artifact CI khác byte với artifact local (thường do Node minor / cây npm resolve khác) — bình thường, nhưng ⇒ **đừng bao giờ dùng hash local làm mốc kỳ vọng cho prod**, luôn re-snapshot LIVE. 2. **Duration 6m31s** — dài nhất 4 run gần đây (#432 5m50s · #433 5m55s · #431 5m50s). Chưa tới ngưỡng escalate; hợp lý vì run này có thêm Mig 70 + seed 86 row + 8 test mới. Theo dõi tiếp, chưa cần hành động. 3. **`Passed!` grep 2-khoảng-trắng** — lần grep đầu 0-hit là DƯƠNG-GIẢ do format; đã soi format rồi mới kết luận (bài học: 0-hit phải kèm control-dương trước khi báo). 4. `[WRN]` global-query-filter (ContractSigningPlan/Contract ↔ Approval/Changelog/LevelOpinion) là warning EF **có sẵn từ trước** (Mig 69 S161), không do commit này sinh ra. ## Kết luận Không có critical issue. Không đề xuất rollback. 0 thao tác ghi lên prod. END sub-cicd-verify-k1 — VERDICT=PASS 6/6 (run #434 id=547 success 6m31s · gate 598=45D+553I · Mig 70 top prod · seed 86/8-nhóm khớp 3 nguồn · bundle 4/4 rotate + 4/4 hash cũ chết · smoke 8/8 + 401 có-nghĩa)