# sub-cicd-monitor-2 — verify deploy `bfc7b79` (S162 cây 4 folder GĐ, FE-ONLY)

> Ghi-đĩa-TRONG-lúc-làm (#53). Mỗi mục ghi NGAY khi đo xong. Kết bằng dòng `END … VERDICT=`.

## CHÂN 1 — Push + path-filter (Stage 1) ✅ ĐO 10:05:52

- HEAD = `bfc7b79f724998cc71a5e3307a1266f31b2e5731` · subject `[CLAUDE] FE-User+FE-Admin: cay 4 folder GD duoi tung goi thau (Duyet NCC / Ke hoach HD / Duyet HD / HD cung) x2 app + 5 fix review F-1/3/4/6/7`
- Commit date `2026-07-30 10:04:32 +0700`
- `git log origin/main..HEAD` = **EMPTY** ⇒ đã push thật.
- Diff `HEAD~1..HEAD` = **17 file, +1839/−12**:
  - **12 file FE** = 6 fe-admin + 6 fe-user (đối xứng ×2 app): `components/pipeline/PipelineStageFolders.tsx` (MỚI), `components/pipeline/PipelineTreePanel.tsx`, `hooks/usePipelineStages.ts`, `pages/contracts/HardCopiesPage.tsx`, `pages/khkk/KhkkListPage.tsx`, `pages/pe/PurchaseEvaluationsListPage.tsx`
  - 5 file governance/run-trace: `.claude/agent-memory/frontend-designer/MEMORY.md`, `.claude/governance/.session-counter.json`, `runs/2026-07-30-S162-*/{run.md,ship-synthesis.md,sub-reviewer-1.md}`
- **0 BE file · 0 migration** (`git diff --name-only HEAD~1 HEAD -- '*Migrations*'` = 0) ⇒ xác nhận FE-ONLY, **KHÔNG có chân EF-mig** (đúng brief).
- Path-filter (`.gitea/workflows/deploy.yml` 7-glob): `fe-*/src/**.tsx|.ts` **KHÔNG khớp** glob nào ⇒ **CI PHẢI TRIGGER**. (`.session-counter.json` cũng không khớp `scripts/**.md`; nhưng FE file đã đủ trigger.)

**CHÂN 1 = PASS.**

## BASELINE PRE-DEPLOY (chụp 10:05:52, tức 80s sau commit — deploy ~5min nên đây là bundle CŨ)

| App | JS cũ | CSS cũ |
|---|---|---|
| admin.solutions.com.vn | `index-BBQKvUMa.js` | `index-BLUPWD4D.css` |
| eoffice.solutions.com.vn | `index-BV5--mPf.js` | `index-HIRTruGh.css` |

**Xác thực "đúng là bundle CŨ"** (`curl -I`, đo 10:07):
- admin `index-BBQKvUMa.js` → `Last-Modified: Wed, 29 Jul 2026 17:22:18 GMT` = **30-07 00:22:18 +0700** · `Content-Length: 1.734.048` · `ct=application/javascript`
- user `index-BV5--mPf.js` → `Last-Modified: Wed, 29 Jul 2026 17:23:14 GMT` = **30-07 00:23:14 +0700** · `Content-Length: 1.650.214` · `ct=application/javascript`
⇒ khớp mốc deploy W7 **#431** đêm qua, **trước** commit 10:04:32 ⇒ đây ĐÚNG là baseline PRE-deploy đợt này.

## ⚠️ ĐÍNH CHÍNH GIỮA CHỪNG — HEAD DI CHUYỂN (bẫy moving-target)

Lúc 10:12 phát hiện `git log -1 HEAD~1` trả về **chính bfc7b79** ⇒ HEAD đã nhảy sang commit mới `a07b942 wal: flush 20260730T1005` (10:05:59). Mọi phép đo dùng `HEAD~1` từ đây bị lệch 1 nấc.
- **Sửa:** neo TUYỆT ĐỐI bằng SHA — parent thật của bfc7b79 = **`8eba137 wal: flush 20260730T0946`**. Diff `8eba137..bfc7b79` = **đúng 17 file / +1839−12** (khớp số đã ghi ở CHÂN 1 ⇒ CHÂN 1 vẫn ĐÚNG, chỉ lý-do-neo phải sửa).
- `a07b942` chạm **3 file `.md`** (`WAL.md`, `agent-memory/reviewer/{MEMORY.md,archive/2026-07.md}`) ⇒ khớp glob `**/*.md` + `.claude/agent-memory/**` ⇒ **KHÔNG trigger CI**, và đang **UNPUSHED** ⇒ `origin/main == bfc7b79`, không có run thứ 2 chen ngang. ✅

## CHÂN 2-prep — CHỌN MARKER (grep repo-wide TRƯỚC, dự-đoán TRƯỚC khi đo)

Brief gợi ý 2 marker. Kiểm chứng cả 2 ở **parent `8eba137`**:
- `git grep -c "pipeline-khkk-index" 8eba137 -- fe-user/src fe-admin/src` → **exit=1, 0 hit**
- `git grep -c "Kế hoạch Hợp đồng" 8eba137 -- …` → **exit=1, 0 hit**
- `git ls-tree 8eba137 -- */components/pipeline/` → **rỗng** ⇒ `PipelineStageFolders.tsx` là file **MỚI TINH** ở bfc7b79.
⇒ **Cả 2 marker ĐỘC QUYỀN cho commit này** (không phải chuỗi dùng-chung có sẵn) ⇒ hợp lệ làm ship-proof DƯƠNG.

**Đếm ở source `bfc7b79` (mỗi app):**
- `pipeline-khkk-index` = **1** (`hooks/usePipelineStages.ts:78` `queryKey`), ASCII ⇒ không lo unicode-escape.
- `Kế hoạch Hợp đồng` = **3**, nhưng **2 nằm trong COMMENT** (`PipelineStageFolders.tsx:3` header-comment · `usePipelineStages.ts:188` comment `── GĐ2 ──`), chỉ **1 là CODE THẬT** (`PipelineStageFolders.tsx:24` `2: 'Kế hoạch Hợp đồng',`).

🔮 **DỰ ĐOÁN TRƯỚC KHI ĐO (khuôn comment-strip S159 #425):** bundle mỗi app phải có
- `pipeline-khkk-index` ≥ 1
- `Kế hoạch Hợp đồng` (hoặc dạng `\uXXXX` escape) = **1** (2 comment bị minifier cắt), **KHÔNG phải 3**.

**Marker thứ 3 (lấy từ `ship-synthesis.md`) — `pipeline-contract-index`:** parent `8eba137` = **0 hit**; ở `bfc7b79` = **2 file/app × 1** (`hooks/usePipelineStages.ts` + `pages/contracts/HardCopiesPage.tsx` — chính là F-7 invalidate).
🔮 Dự đoán: bundle = **2/app** (khuôn "đúng-BỘI-SỐ = số FILE trong diff", S157) — caveat: nếu minifier gộp literal trùng thì còn 1; mốc CHẮC = ≥1.

## CHÂN 3-prep — chuẩn hoá tên endpoint health (đo 10:11, PRE-deploy)

| URL | code |
|---|---|
| `https://api.solutions.com.vn/health/live` | **200** |
| `https://api.solutions.com.vn/health/ready` | **200** |
| `https://api.solutions.com.vn/health` (bare) | **404** |
⇒ đúng như đã biết: bare `/health` UNMAPPED. **404 ở đây là control ÂM tiện dụng — chứng 200 hai route kia CÓ NGHĨA, không phải catch-all.**

## CHÂN 2 — Gitea Actions run (poll)

- Match theo `head_sha` (KHÔNG theo run_number): **`id=545` · `run_number=432` · `head_sha=bfc7b79f7249…` · `name=build-deploy` · `event=push`**
- URL: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/432
- Poll 30s/iter: 10:07:38 → 10:10:20 `status:"running"` (iter 1-6) → **iter 7 @10:10:52 `status:"success"`**, `updated_at 2026-07-30T10:10:32+07:00`. (`tasks` endpoint không populate `conclusion` ⇒ tin `status` — đã biết.)
- **Duration ≈ 5m35s** (commit 10:04:32 → job succeeded 10:10:32). Nằm trong dải bình thường ~3-6min, KHÔNG cần escalate.

### Test gate (log web-UI `…/actions/runs/432/jobs/0/logs`, HTTP 200, 17.916 B)
```
03:05:07Z Passed! - Failed: 0, Passed:  45, Skipped: 0, Total:  45 — SolutionErp.Domain.Tests.dll (net10.0)
03:07:37Z Passed! - Failed: 0, Passed: 545, Skipped: 0, Total: 545 — SolutionErp.Infrastructure.Tests.dll (net10.0)
```
⇒ **45 + 545 = 590 PASS · 0 fail · 0 skip** — khớp TÁCH-PHẦN đúng suite hiện hành brief nêu (FE-only nên số giữ nguyên, không delta). ✅

### Mốc deploy trong log (giờ UTC trong log = +0700 − 7)
- `03:10:04Z ✓ built in 2.02s` (FE build xong)
- `03:10:09Z Deploy done. App pool started.` = **10:10:09 +0700**
- `03:10:23Z API /health/live -> 200` (gate health của chính CI)
- `03:10:32Z 🏁 Job succeeded`

**CHÂN 2 = PASS** (run success + gate 590/0/0).

## CHÂN 3 — Bundle hash delta ×2 app (đo 10:11:5x, SAU status=success)

| App | asset | CŨ (10:05:52) | MỚI (sau deploy) | rotate? |
|---|---|---|---|---|
| admin | js | `index-BBQKvUMa.js` | **`index-DXoYDy7R.js`** | ✅ |
| admin | css | `index-BLUPWD4D.css` | **`index-BZ5F-g1w.css`** | ✅ |
| eoffice | js | `index-BV5--mPf.js` | **`index-o8KDluD1.js`** | ✅ |
| eoffice | css | `index-HIRTruGh.css` | **`index-yNZDmVNz.css`** | ✅ |

**4/4 rotate** (mode-A #69). `index.html` admin: `Last-Modified: Thu, 30 Jul 2026 03:09:07 GMT` = **10:09:07 +0700 → IN deploy-window** · `Cache-Control: no-cache` (đúng hardening S88, shell luôn revalidate).
Hash prod mới `o8KDluD1` ≠ local-build-đời-trước-fix `JKWooGXp` — đúng kỳ vọng brief (prod ship bản SAU 5 fix).

**Bundle MỚI là JS THẬT** (không phải SPA-fallback): `index-o8KDluD1.js` → `ct=application/javascript` **1.667.332 B** · `index-DXoYDy7R.js` → `ct=application/javascript` **1.751.166 B**.

**Control ÂM 3 TẦNG (khuôn S159 — chứng "200 ≠ tồn tại"):**
| probe | HTTP | ct | size | đọc ra |
|---|---|---|---|---|
| eoffice `/assets/index-ZZZZZZZZ.js` (hash BỊA) | 200 | **text/html** | 876 | SPA-fallback ⇒ 200 trần vô nghĩa |
| eoffice `/assets/index-BV5--mPf.js` (hash **CŨ**) | 200 | **text/html** | **876 — trùng khít hash bịa** | bundle cũ **đã biến mất khỏi đĩa** = bị ghi đè |
| admin `/assets/index-BBQKvUMa.js` (hash **CŨ**) | 200 | **text/html** | **900 = đúng size `index.html` admin** | ghi đè |
⇒ Không còn bundle cũ nào cache-được ⇒ **deploy ship THẬT**, không phải "action xanh mà file giữ nguyên".

**CHÂN 3 = PASS (4/4 hash rotate + ghi-đè chứng bằng control âm).**

## CHÂN 4 — Marker nội-dung trong bundle MỚI (đo trên file đã tải, đếm BYTE bằng python)

Đo cả 2 app (`index-o8KDluD1.js` 1.667.332 B · `index-DXoYDy7R.js` 1.751.166 B) — **kết quả GIỐNG HỆT nhau**, khớp tính đối-xứng 6+6 file trong diff:

| probe | dự đoán | eoffice | admin | |
|---|---|---|---|---|
| `pipeline-khkk-index` | ≥1 | **1** | **1** | ✅ |
| `pipeline-contract-index` | **2** (= số FILE chứa nó trong diff) | **2** | **2** | ✅ đúng BỘI-SỐ |
| `Kế hoạch Hợp đồng` (raw UTF-8) | **1** (3 ở source − 2 comment) | **1** | **1** | ✅ đúng y dự đoán |
| dạng escape `ế…` / `ế…` | 0 (vì bundle giữ UTF-8 thô) | 0 | 0 | ✅ nhất quán |
| **ÂM** `pipeline-hardcopy-index` (chuỗi BỊA, `git grep` ở `bfc7b79` = **0 hit**) | **0** | **0** | **0** | ✅ |

**Control ÂM mạnh nhất — ĐẾM TRỌN HỌ (self-validating):** regex `pipeline-[a-z-]{0,24}index` quét toàn bundle ⇒ tập thu được **đúng 3 phần tử** `{contract×2, khkk×1, pe×1}`.
Đối chiếu source `bfc7b79`: `contract`=2 file · `khkk`=1 · `pe`=1 (`PipelineTreePanel.tsx`) ⇒ **khớp TỪNG phần tử, không thừa không thiếu**. Cả 3 đều **0 hit ở parent `8eba137`** ⇒ cả họ là hàng MỚI của commit này.
⇒ vế DƯƠNG >0 bằng chính regex đó **trước** khi tin vế ÂM (đúng luật), và vế ÂM có 2 lớp: chuỗi-bịa cùng-hình-dạng = 0 · họ-đếm-trọn không có phần tử lạ.

⚠️ **1 chuỗi KHÔNG dùng làm control ÂM được — khai thẳng:** `Kế hoạch ký kết HĐ` (nhãn strip cũ) đếm được **8/app**. Đây **không phải lỗi**: `ship-synthesis.md` ghi rõ nhãn folder GĐ2 `Kế hoạch Hợp đồng` **CỐ Ý khác** nhãn strip `Kế hoạch ký kết HĐ`. Hai chuỗi **cùng tồn tại** ⇒ nếu tao lấy nó làm control-ÂM thì đã FAIL OAN (đúng bài học S159 #426: control ÂM phải ĐỘC QUYỀN). Ngược lại, việc `1` (nhãn mới) và `8` (nhãn cũ) cùng có mặt chính là bằng chứng phân-biệt-phiên-bản: bundle chứa **thêm** nhãn mới chứ không thay nhãn cũ.

**CHÂN 4 = PASS.**

## CHÂN 5 — Smoke (đo sau status=success)

| URL | HTTP | ct / size |
|---|---|---|
| `https://api.solutions.com.vn/health/live` | **200** | text/plain 7 B |
| `https://api.solutions.com.vn/health/ready` | **200** | text/plain 7 B |
| `https://admin.solutions.com.vn/` | **200** | text/html 900 B |
| `https://eoffice.solutions.com.vn/` | **200** | text/html 876 B |
| `https://api.solutions.com.vn/health` (bare, **control ÂM**) | **404** | — |
CI tự đo trong log cũng khớp: `03:10:23Z API /health/live -> 200`. 404 của route bare chứng 3 con 200 kia CÓ NGHĨA.

**CHÂN 5 = PASS.**

## Ghi chú cho lần sau (đề xuất bổ sung MEMORY)

1. 🔴 **Bẫy MOVING-TARGET HEAD**: phiên có Stop-hook WAL tự commit ⇒ `HEAD~1` **đổi nghĩa giữa lượt verify**. Lượt này `HEAD~1` từ chỗ "cha của commit đích" biến thành "chính commit đích" trong ~6 phút. **Luật: verify commit X phải neo `X^..X` bằng SHA tuyệt đối, KHÔNG bao giờ dùng `HEAD~1`.** Lỗi này suýt làm tao kết luận ngược 180° (đã kết "2 marker có sẵn ở parent ⇒ vô giá trị", sự thật là **cả 2 đều 0 hit ở parent**).
2. Khuôn **"đếm TRỌN HỌ bằng 1 regex"** (`pipeline-[a-z-]*index`) mạnh hơn đếm từng chuỗi rời: vừa cho vế DƯƠNG, vừa tự chứng regex không quét bừa (không có phần tử lạ), lại đối chiếu được element-wise với source.
3. `paths-ignore` nuốt trọn commit `wal:` (toàn `.md` + `agent-memory/**`) ⇒ WAL-commit chen giữa **không** đẻ run rác. Xác nhận lại lần nữa ở lượt này.

---

**END verify `bfc7b79` (run #432 / id=545) — VERDICT=PASS 5/5** (CHÂN 1 push+filter · CHÂN 2 run success + gate 590 = 45+545 · CHÂN 3 bundle 4/4 rotate + ghi-đè · CHÂN 4 marker khớp dự-đoán + control âm 2 lớp · CHÂN 5 smoke 4×200 + control 404). FE-ONLY ⇒ không có chân EF-migration (0 file `*Migrations*` trong diff). 0 blocker, 0 khuyến nghị rollback.





