# CI/CD verify — `df52fa0` (W3 KHKK — duyệt 3 trạm, 0 migration) > Ghi TỪNG MỤC ngay khi có bằng chứng (đối-sách #53: file = sản phẩm chính, return chỉ là tóm tắt). > Agent: cicd-monitor · Session S161 W3 · Ngày 2026-07-29 --- ## §0. Grounding — commit + push | Mục | Giá trị | |---|---| | SHA | `df52fa0cd09e13a533724c35f4e6bed44afeb42d` | | Subject | `[CLAUDE] App+Infra+Api+FE-User+FE-Admin+Tests: W3 KHKK — duyet 3 tram OR-of-N + finalize chot gia (test-before 6 RED->GREEN)` | | Author / Date | pqhuy1987 · Wed Jul 29 22:33:11 2026 +0700 | | `git log origin/main..HEAD` | **RỖNG** ⇒ đã push | | Files changed | **11** (+2370 / −6) | | Breakdown | BE 4 (Controller · IContractSigningPlanWorkflowService · DependencyInjection · ContractSigningPlanWorkflowService) · FE 6 (2 app × {KhkkDetailPage, KhkkWorkflowPanel, types/khkk}) · Tests 1 (`ContractSigningPlanApprovalTests.cs`) | | paths-ignore (gotcha #41) | KHÔNG khớp glob nào trong 7-entry ⇒ **CI PHẢI chạy** ✅ | | Migrations trong diff | **0 file** `*Migrations*` ⇒ khai báo 0-mig (verify ở §2) | **File MỚI trong commit này (quan trọng cho §3):** `fe-{admin,user}/src/pages/khkk/KhkkWorkflowPanel.tsx` — **NEW-FILE-IN-THIS-COMMIT** (`git cat-file -e df52fa0~1:…` → `exists on disk, but not in df52fa0~1`), 423 dòng/app, SHA256 mirror KHỚP 2 app: `2b64144ef6e8567d6997c0e19b9686c083080216510a4cd8da6157a80f584817`. ⟹ Mọi chuỗi ĐỘC-QUYỀN của file này **phải VẮNG trong bundle CŨ** (build từ `6cbc6ad9` W2) và **có mặt trong bundle MỚI** → control ÂM theo trục THỜI GIAN, mạnh hơn control âm hash. --- ## §1. CI run — PASS ✅ | Mục | Giá trị | |---|---| | Run | **#429** (task id **542**) | | head_sha | `df52fa0c` ✅ khớp (key theo SHA, KHÔNG theo run_number) | | status | **success** (`conclusion:None` — Gitea `tasks` không populate, tin `status` per baseline) | | created → updated | 22:33:19 → 22:39:13 = **5m54s** (nằm trong dải ~5m40s–6m của arc S159/S161) | | URL | https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/429 | **Test gate (đọc web-UI log `…/runs/429/jobs/0/logs`, 17.900 B) — đối chiếu TÁCH-PHẦN:** ``` :29 Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, Duration: 147 ms - SolutionErp.Domain.Tests.dll :47 Passed! - Failed: 0, Passed: 535, Skipped: 0, Total: 535, Duration: 1 m 36 s - SolutionErp.Infrastructure.Tests.dll ``` | | Kỳ vọng | Đo | KL | |---|---|---|---| | Domain | 45 | **45** | ✅ | | Infrastructure | 535 | **535** | ✅ | | **TỔNG** | **580** | **580** | ✅ | | Failed / Skipped | 0 / 0 | **0 / 0** | ✅ | Delta so run #427 (566 = 45D+521I): **+14 Infra** — commit khai "test-before 6 RED→GREEN"; file test đổi = 1 (`ContractSigningPlanApprovalTests.cs`). 6 là số **kịch bản** lead khai, 14 là số **test-case** thực thi (Theory/InlineData nở) ⇒ không mâu thuẫn, và gate-580 đúng con số đề bài yêu cầu. --- ## §2. 0-migration invariant _(đang cập nhật)_ --- ## §3. Bundle marker ### §3.a Chọn marker theo LUẬT ĐỘC-QUYỀN (#426) Trước khi nhận 1 chuỗi làm chứng-cứ, grep repo-wide xác nhận nó ĐỘC QUYỀN cho thứ cần chứng. Đo bằng `grep -roF fe-admin/src fe-user/src src | wc -l` (occurrence) + `grep -rlF` (file list): | Ứng viên | occurrence | file chứa | Kết luận | |---|---|---|---| | `Không phải lượt bạn` | 5 | +`{2 app}/components/pe/PeWorkflowPanel.tsx` +`PurchaseEvaluationWorkflowService.cs` | ❌ **NHIỄM PE** — loại | | `Đến lượt bạn duyệt` | 4 | +`{2 app}/components/pe/PeWorkflowPanel.tsx` | ❌ **NHIỄM PE** — loại | | `chỉ người trên mới thao tác cấp này` | 2 | CHỈ `{2 app}/pages/khkk/KhkkWorkflowPanel.tsx` | ✅ ĐỘC QUYỀN, **1 lần/app** | | `Cấp hiện tại chỉ người được phân công` | 6 | CHỈ `{2 app}/pages/khkk/KhkkWorkflowPanel.tsx` | ✅ ĐỘC QUYỀN, **3 lần/app** | | `Trả lại người soạn sửa` | 4 | CHỈ `{2 app}/pages/khkk/KhkkWorkflowPanel.tsx` | ✅ ĐỘC QUYỀN, **2 lần/app** | > 🔑 Đúng bài học #426 lặp lại LIVE: 2/5 ứng viên "nghe rất KHKK" thực ra dùng chung với PE panel. > Nếu áp máy móc `Đến lượt bạn duyệt` thì bundle CŨ đã có sẵn ⇒ PASS GIẢ. **Trap #425 (comment bị esbuild strip) — đã loại trừ:** cả 3 marker giữ lại đều nằm ở **mã CHẠY**, không phải comment: `chỉ người trên…` = JSX text (`
⚠ Không phải lượt bạn — …
`) · `Cấp hiện tại chỉ người được phân công…` = 3 × `title={…}` string literal · `Trả lại người soạn sửa` = `dialogTitle` + 1 × `title=` (2 string literal). ### §3.b Hiệu-chuẩn máy đo TRƯỚC khi dự đoán bội-số (chống bẫy minifier-dedupe) Bội-số chỉ dùng được nếu minifier KHÔNG gộp literal trùng. Đo bằng chuỗi **đã ship** `bg-slate-200 text-slate-500`: | App | source (HEAD) | trừ 1 lần trong file MỚI ⇒ kỳ vọng bundle CŨ | đo bundle CŨ | Khớp | |---|---|---|---|---| | fe-admin | 4 (`PeWorkflowPanel`×2 · `EmployeesListPage`×1 · `KhkkWorkflowPanel`×1 MỚI) | **3** | **3** | ✅ | | fe-user | 4 (cùng phân bố) | **3** | **3** | ✅ | ⟹ **KHÔNG dedupe** — literal trùng được giữ nguyên bội-số ⇒ dự đoán SỐ CHÍNH XÁC là hợp lệ (không phải chỉ "≥1"). ### §3.c Control ÂM theo TRỤC THỜI GIAN (đo TRƯỚC deploy, bundle W2 `6cbc6ad9`) Bundle cũ: admin `index-D4uy3yxF.js` (1.709.294 B) · user `index-C1ASe0K1.js` (1.625.473 B), cả 2 `ct=application/javascript`. | Chuỗi | admin CŨ | user CŨ | Ý nghĩa | |---|---|---|---| | `chỉ người trên mới thao tác cấp này` | **0** | **0** | ✅ vắng đúng (file chưa tồn tại ở W2) | | `Cấp hiện tại chỉ người được phân công` | **0** | **0** | ✅ vắng đúng | | `Trả lại người soạn sửa` | **0** | **0** | ✅ vắng đúng | | `Không phải lượt bạn` (PE panel, chứng MÁY ĐO SỐNG) | **1** | **1** | ✅ grep bắt được tiếng Việt trong bundle ⇒ 0 ở trên là "vắng THẬT", không phải "máy đo hỏng" | > 🔑 Đây là control ÂM mạnh hơn hash-bịa: nó chứng minh **trước deploy chuỗi KHÔNG có**, nên nếu sau deploy > chuỗi XUẤT HIỆN đúng bội-số ⇒ chính commit này đã lên đĩa, không thể do bundle cũ hay cache. ### §3.d DỰ ĐOÁN (chốt TRƯỚC khi đo bundle mới) | Chuỗi | admin MỚI | user MỚI | |---|---|---| | `chỉ người trên mới thao tác cấp này` | **1** | **1** | | `Cấp hiện tại chỉ người được phân công` | **3** | **3** | | `Trả lại người soạn sửa` | **2** | **2** | | `bg-slate-200 text-slate-500` (hiệu-chuẩn) | **4** (3→4) | **4** (3→4) | ### §3.e SỐ ĐO bundle MỚI — **4/4 dự đoán KHỚP TỪNG ĐỒNG** ✅ Bundle mới (rotate **4/4**): admin js `D4uy3yxF`→**`BPWQQe4q`** · css `9okZenCM`→**`KS0kL7bG`** · user js `C1ASe0K1`→**`DV3fEpOw`** · css `BfN7IuxJ`→**`Cpp9Qc7C`**. Size: admin 1.709.294 → **1.720.297** (+11.003 B) · user 1.625.473 → **1.636.476** (+11.003 B) — **delta BẰNG NHAU 2 app**, khớp việc file panel là MIRROR SHA256 identical. Cả 2 `ct=application/javascript`. | Chuỗi | Dự đoán | admin đo | user đo | KL | |---|---|---|---|---| | `chỉ người trên mới thao tác cấp này` | 1 / 1 | **1** | **1** | ✅ | | `Cấp hiện tại chỉ người được phân công` | 3 / 3 | **3** | **3** | ✅ | | `Trả lại người soạn sửa` | 2 / 2 | **2** | **2** | ✅ | | `bg-slate-200 text-slate-500` (hiệu-chuẩn) | 4 / 4 | **4** | **4** | ✅ | | `Không phải lượt bạn` (PE 1 + KHKK 1) | 2 / 2 | **2** | **2** | ✅ bonus nhất-quán | > Cả 3 marker đi từ **0 (bundle W2) → đúng bội-số nguồn (bundle W3)**, và chuỗi hiệu-chuẩn đi **3→4** > đúng bằng +1 lần xuất hiện trong file mới ⇒ không chỉ "có ship", mà ship **ĐÚNG BẤY NHIÊU**, không thừa không thiếu. ### §3.f Control ÂM 2 tầng (bundle cũ phải BIẾN MẤT khỏi đĩa) | Phép | code | content-type | size | KL | |---|---|---|---|---| | admin hash **CŨ** `index-D4uy3yxF.js` | 200 | `text/html` | 900 | ✅ đã bị ghi đè | | user hash **CŨ** `index-C1ASe0K1.js` | 200 | `text/html` | 876 | ✅ đã bị ghi đè | | admin hash **BỊA** `index-ZZZZ9999.js` | 200 | `text/html` | **900** | ✅ trùng khít hash cũ | | user hash **BỊA** `index-ZZZZ9999.js` | 200 | `text/html` | **876** | ✅ trùng khít hash cũ | ⟹ hash CŨ trả về **byte-size y hệt hash BỊA** ⇒ 200 đó là SPA-fallback `index.html`, không phải file thật (bẫy #69/SPA-fallback). Bundle W2 đã bị xoá khỏi đĩa ⇒ deploy ghi đè THẬT. **PHÉP 3 = PASS.** --- ## §4. Smoke + transitions THẬT — PASS ✅ [LEAD đo on-behalf] - login admin token-468 · `POST .../00000000-…-01/transitions` body `{"action":"nonsense"}` → **409** ProblemDetails "Hành động không hợp lệ" (validate-action TRƯỚC load-plan — đúng thiết kế skeleton đợt-1). - body `{"action":"approve"}` GUID ma → **404** ProblemDetails "Không tìm thấy ContractSigningPlan" (GlobalExceptionMiddleware map đúng; endpoint transitions SỐNG prod). --- ## §5. Cờ `KeHoachKyKet` 13/13/2 — PASS ✅ [LEAD đo on-behalf] sqlcmd prod: `KHKK_CU=13/13/2` GIỮ · MIGTOP `20260729122015_AddContractSigningPlans` GIỮ · MIGCOUNT 69 · TABLES 96 · MENU **142 GIỮ** (§2 0-mig PASS luôn tại đây). --- END cicd-verify df52fa0 — **VERDICT=PASS 5/5** (§1+§3 cicd-monitor · §2+§4+§5 lead 2-nguồn) ---