# 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 <s> 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 (`<div className="mt-0.5">⚠ Không phải lượt bạn — …</div>`) ·
`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)

---
