# CI/CD verify — S157 (2026-07-28) — PE cảnh báo "Địa điểm" khi dự án chưa có Location

> Agent: cicd-monitor · READ-only · file ghi TỪNG MỤC trong lúc làm (chống #53 return-garble)
> Range: `f4494cf..a52a91e` · run-folder tái dụng S155

---

## MỤC 1 — Push + path-filter (gotcha #41)  ✅ ĐÃ ĐO

```
a52a91e7110b6d04a7ad26b70aa7db2c4888e160 | 2026-07-28 09:32:59 +0700 | [CLAUDE] Docs: tick counter S157 + bump header CURRENT STATE moc S153->S156
6250f39c52905579f873034c0dc04dfc3ada0287 | 2026-07-28 09:32:33 +0700 | [CLAUDE] PurchaseEvaluation: hien ly do o Dia diem trong khi du an chua co Location
```

- `git log origin/main..HEAD` → **RỖNG** ⇒ push đã lên `main` thật.
- Files `6250f39` (4 file, FE 2-app mirror):
  - `fe-admin/src/components/pe/PeHeaderForm.tsx`
  - `fe-admin/src/components/pe/PeWorkspaceCreateView.tsx`
  - `fe-user/src/components/pe/PeHeaderForm.tsx`
  - `fe-user/src/components/pe/PeWorkspaceCreateView.tsx`
- Files `a52a91e` (2 file): `.claude/governance/.session-counter.json` + `docs/STATUS.md`

**Path-filter check vs 7-glob `paths-ignore`** (`docs/**`, `**/*.md`, `.claude/skills/**`, `.claude/agent-memory/**`, `.claude/workflows/runs/**`, `.gitignore`, `scripts/**.md`):
- `fe-{admin,user}/src/**` **KHÔNG khớp** glob nào ⇒ CI **PHẢI chạy**.
- `.session-counter.json` cũng thoát glob (không phải `.md`, không nằm skills/agent-memory) — nhưng Gitea đánh giá cả **range push**, có ≥1 file non-ignored ⇒ toàn range build.

**Kết luận M1: KHÔNG phải docs-only skip. Pipeline bắt buộc fire.** ✅

---

## MỤC 2 — Gitea Actions run (đang đo)

Poll #1 @ ~09:41+07 — `.../actions/tasks?limit=6`:

| id | run# | head_sha | status | created | updated |
|---|---|---|---|---|---|
| **532** | **419** | **a52a91e7** | **running** | 2026-07-28T09:40:18+07 | 09:41:23+07 |
| 531 | 418 | f4494cfe | success | 07-27T14:36:54 | 14:42:35 |
| 529 | 416 | b1bae77c | success | 07-27T12:37:18 | 12:42:49 |

⇒ Run **ĐÃ FIRE đúng head_sha `a52a91e`** (xác nhận M1: không skip). Trạng thái lúc đo đầu = **running**, CHƯA kết luận. Đang chờ terminal.

**Marker gốc (từ `git show 6250f39`)** — chuỗi sẽ grep trong bundle prod:
`⚠ Dự án này chưa có Địa điểm trong Danh mục Dự án — nhập tay ở đây, hoặc bổ sung vào Danh mục (Dự án → Địa điểm) để các phiếu sau tự điền.`
Substring dùng để grep (an toàn minifier, nằm trong string literal): **`chưa có Địa điểm trong Danh mục`**

---

## MỤC 3 — PRE-deploy bundle snapshot (chụp TRƯỚC khi run xong) ✅ ĐÃ ĐO

Đo lúc **09:41:56 +07** (run 532 còn `running` ⇒ đây là trạng thái CŨ, chưa ship):

| app | js hash | css hash | `Last-Modified` index.html |
|---|---|---|---|
| admin.solutions.com.vn | **`BK8w3QxU`** | `DiNJjaoo` | Mon, 27 Jul 2026 07:41:15 GMT (=14:41 +07) |
| eoffice.solutions.com.vn | **`DYnxSI0T`** | `BRCvEHvC` | Mon, 27 Jul 2026 07:42:10 GMT (=14:42 +07) |

⇒ **KHỚP CHÍNH XÁC baseline STATUS S156** (`BK8w3QxU` / `DYnxSI0T`), LM trỏ đúng run #418 (`f4494cf`, xong 14:42:35 07-27). Pre-state hợp lệ để so hash sau deploy.

---

## MỤC 4 — Run terminal + TEST GATE ✅ PASS

Poll 7 vòng (09:41→09:46, ~30s/vòng), chuyển trạng thái:
`running` (iter 2–6) → **`success`** (iter 7 @ 09:46:00, `updated_at` 2026-07-28T09:45:58+07).

- **Run id 532 · run_number #419 · head_sha `a52a91e`**
- Start 09:40:18 → End 09:45:58 ⇒ **duration 5m40s** (nằm trong dải bình thường 4m40–5m40, không escalate)
- URL: `https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/419`

**Test gate — đếm CỤ THỂ từ web-UI log (17.850 bytes):**

```
Passed!  - Failed: 0, Passed:  45, Skipped: 0, Total:  45 — SolutionErp.Domain.Tests.dll        (164 ms)
Passed!  - Failed: 0, Passed: 517, Skipped: 0, Total: 517 — SolutionErp.Infrastructure.Tests.dll (1 m 25 s)
```

⇒ **562 PASS (45 Domain + 517 Infra), 0 fail / 0 skip** — **KHỚP baseline canonical 562** (`docs/STATUS.md`). Grep `error |FAILED|Build FAILED` trên toàn log ⇒ **0 hit**. Gate xanh, chạy TRƯỚC build+deploy ⇒ deploy hợp lệ.

---

## MỤC 5 — Bundle rotate ×2 app ✅ ROTATE CẢ HAI

Đo lúc **09:46:12 +07** (sau khi status=success — tránh false-positive stale kiểu Run #242):

| app | js TRƯỚC | js SAU | Δ | css | `Last-Modified` (UTC → +07) |
|---|---|---|---|---|---|
| admin | `BK8w3QxU` | **`8-QYcMOg`** | ✅ ĐỔI | `DiNJjaoo` (frozen) | 28 Jul 02:44:33 GMT → **09:44:33 +07** |
| eoffice | `DYnxSI0T` | **`DptYR4wL`** | ✅ ĐỔI | `BRCvEHvC` (frozen) | 28 Jul 02:45:30 GMT → **09:45:30 +07** |

- **Cả 2 LM nằm TRONG cửa-sổ deploy** (09:40:18 – 09:45:58) ⇒ file thật sự được ghi đè trong run này, không phải tàn dư.
- **css FROZEN cả 2** = dấu vân tay "FE-logic-no-new-style": khối thêm chỉ dùng utility Tailwind đã có sẵn trong bundle (`text-amber-700`, `mt-1`) ⇒ content-hash CSS không đổi. Lành tính theo #69-REFINE, KHÔNG phải cờ đỏ.
- index.html size 900B/876B + `Last-Modified` mới ⇒ shell `no-cache` đã revalidate đúng.

⇒ **Deploy ĐÃ ăn tới đĩa. Hash đổi 2/2.**

---

## MỤC 6 — 🔴 BYTE-MARKER trong bundle prod (phép QUYẾT ĐỊNH, gotcha #77) ✅ 2/2

Tải THẲNG file JS prod rồi `bytes.count(marker.encode('utf-8'))` (bytes, không decode — tránh bẫy cp1252 S134):

| app | URL asset | HTTP | size THẬT | Content-Type | `Last-Modified` |
|---|---|---|---|---|---|
| admin | `/assets/index-8-QYcMOg.js` | 200 | **1.675.282 B** | `application/javascript` | 28 Jul 02:44:33 GMT |
| eoffice | `/assets/index-DptYR4wL.js` | 200 | **1.575.513 B** | `application/javascript` | 28 Jul 02:45:30 GMT |

**Đếm marker (4 chuỗi độc lập, cùng 1 câu cảnh báo):**

| chuỗi grep | admin | eoffice |
|---|---|---|
| `chưa có Địa điểm trong Danh mục` | **2** | **2** |
| `Danh mục Dự án` | **2** | **2** |
| `Dự án → Địa điểm` | **2** | **2** |
| `để các phiếu sau tự điền` | **2** | **2** |

⇒ **CÓ MẶT Ở CẢ 2 APP. count=2 khớp CHÍNH XÁC số file/app trong diff** (`PeHeaderForm.tsx` + `PeWorkspaceCreateView.tsx` = 2 chỗ nhúng chuỗi/app). Không dư, không thiếu.

**Control âm (chống đọc-nhầm SPA-fallback 200):**

| control | HTTP | size | ct | marker |
|---|---|---|---|---|
| fake hash `index-ZZfake0000.js` (admin) | 200 | 900 | **text/html** | **0** |
| fake hash `index-ZZfake0000.js` (eoffice) | 200 | 876 | **text/html** | **0** |
| hash CŨ `index-BK8w3QxU.js` (admin) | 200 | 900 | **text/html** | — |
| hash CŨ `index-DYnxSI0T.js` (eoffice) | 200 | 876 | **text/html** | — |

⇒ Control chứng minh 2 điều: (i) HTTP 200 một mình VÔ NGHĨA — discriminator thật là `ct=application/javascript` + size ~1,6MB; (ii) **bundle CŨ đã BIẾN MẤT khỏi đĩa** (rơi về SPA shell) ⇒ deploy ghi đè thật, không phải thêm file mới cạnh file cũ.

**Đây là bằng chứng mạnh nhất: mã S157 đang CHẠY THẬT trên prod cả 2 app.**

---

## MỤC 7 — Smoke 3 site sau deploy ✅ ALL GREEN

| check | kỳ vọng | thực đo | KQ |
|---|---|---|---|
| `GET /health/live` | 200 | **200 `Healthy`** | ✅ |
| `GET /health/ready` | 200 | **200 `Healthy`** | ✅ |
| `POST /api/auth/login` (admin) | 200 + token | **200**, `accessToken` 468 ký tự | ✅ |
| `GET /api/projects?pageSize=1` | 200 | **200** (374 B) | ✅ |
| `GET /api/purchase-evaluations?pageSize=1` | 200 | **200** (969 B) | ✅ |
| `GET /api/menus` | 200 | **200** (14.802 B) | ✅ |
| `GET /api/suppliers?pageSize=1` | 200 | **200** (751 B) | ✅ |
| `GET https://admin.solutions.com.vn/` | 200 | **200** (900 B shell) | ✅ |
| `GET https://eoffice.solutions.com.vn/` | 200 | **200** (876 B shell) | ✅ |

`/api/projects` 200 đặc biệt đáng ghi: đây chính là nguồn `location` mà khối cảnh báo mới đọc (`projects.data?.find(...)?.location`) ⇒ đường dữ liệu của feature còn sống.

---

## MỤC 8 — Migration: VẮNG là ĐÚNG ✅

> ⚠️ **ĐÍNH CHÍNH số của chính tôi (self-correct):** thoạt đầu tôi viết "range = 6 file" — **SAI**. Đó là số của 2 commit `6250f39`+`a52a91e` cộng lại, không phải của range. Range `f4494cf..a52a91e` thật ra gồm **13 commit** (2 commit code/docs + **11 commit `wal: flush` / `wal: pause`**) chạm **26 file**. Đo lại bằng `git diff --name-only ... | wc -l`. Kết luận BE=0 KHÔNG đổi, nhưng con số phải đúng.

`git diff --name-only f4494cf..a52a91e` = **26 file / 13 commit**; lọc `-- 'src/Backend/*' '*Migrations*'` ⇒ **0 file** (đếm bằng `wc -l`, không phải mắt thường).
⇒ Không có Mig mới; **Mig vẫn 68 `AddPeAllowApproverDelete`**, `sys.tables` vẫn 89. Không query prod DB vì không có gì để đối chiếu — vắng migration ở đây là KỲ VỌNG, không phải cờ đỏ (đúng brief của lead).
BE 0 đổi ⇒ deploy này thuần FE; API vẫn `Healthy` (M7) xác nhận app pool recycle không làm gãy gì.

---

## MỤC 9 — Cross-check baseline 562 với canonical ✅ KHỚP

`docs/STATUS.md:452` (canonical): `| Tests | **562 PASS** | 45 Domain + 517 Infra · 0 fail / 0 skip · S155 +30 ...`
Log CI run #419 đo được: **45 Domain + 517 Infra = 562**, 0 fail / 0 skip.
⇒ **Trùng khít cả TỔNG lẫn TỪNG THÀNH PHẦN** (không chỉ khớp số tổng — khớp cả tách 45/517). Không có test nào bị drop âm thầm, không có skip che lỗi.

---

## VERDICT — **PASS** (9/9 mục xanh, 0 caveat)

| # | Mục | KQ |
|---|---|---|
| 1 | Push + path-filter không skip | ✅ |
| 2 | Run fire đúng `a52a91e` | ✅ |
| 3 | Pre-deploy snapshot = baseline S156 | ✅ |
| 4 | Run #419 success 5m40s + gate 562 | ✅ |
| 5 | Bundle rotate 2/2 + LM in-window | ✅ |
| 6 | 🔴 Byte-marker **2/2 app** (count=2 mỗi app) + control âm sạch | ✅ |
| 7 | Smoke 3 site + 4 endpoint + login | ✅ |
| 8 | Migration vắng = đúng (BE 0 đổi) | ✅ |
| 9 | 562 khớp canonical cả 45/517 | ✅ |

**Không có việc phải làm tiếp. Không khuyến nghị rollback.**

Ghi chú cho session sau: `text-[11px]` + `text-amber-700` KHÔNG làm rotate CSS hash ⇒ 2 utility này đã có sẵn trong bundle từ trước; css-frozen ở đây là dấu hiệu ĐÚNG của "FE logic-only", không cần điều tra.

<!-- END · TOTAL=9 MUC · VERDICT=PASS -->
