Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k6.md
2026-08-01 12:11:32 +07:00

146 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

CICD-K6: PASS — 5/5
# cicd-monitor — verify deploy K6 (S167, 2026-08-01)
Push range `e202393..1c6d77a` (2 commit: `d82c5a8` wal-flush + `1c6d77a` K6). ĐÚNG 1 file code `fe-admin/src/components/Layout.tsx`. 0-mig · fe-user 0-diff · BE 0-diff (đo: `git diff --name-only e202393..1c6d77a -- fe-user/ src/Backend/ tests/` = **0 file**).
Run **#440** (task id 553) — **success**, 11:54:38 → 12:01:07 (**6m29s**, trong dải quen 5m3x6m3x).
Artifact ghi TỪNG MỤC ngay khi có bằng chứng (luật #53: ghi-đĩa-TRONG-lúc-làm; return chỉ là tóm tắt).
---
## M0 — Mốc PRE (tự đo LIVE lúc run CÒN `running`, KHÔNG tin số chép)
Cửa sổ vàng: bắt được #440`running` lúc **11:55:43** ⇒ bundle LIVE lúc đó = PRE thật.
| App | index-*.js | index-*.css | size js (B) | Last-Modified js | Content-Type |
|---|---|---|---|---|---|
| admin | `index-C2FhHCNT.js` | `index-DqXm5Hpn.css` (86.495) | **1.758.685** | 01 Aug 04:13:05 GMT = **11:13:05 +07** | application/javascript |
| eoffice | `index-DFOCQ2ks.js` | `index-DlN3yZ5L.css` (92.233) | **1.676.752** | 01 Aug 04:14:05 GMT = **11:14:05 +07** | application/javascript |
🔑 **PRE tự chứng là bundle #439 bằng LM, không chỉ bằng hash**: LM 11:13:05 / 11:14:05 nằm TRỌN trong cửa-sổ deploy #439 (11:08:17 → 11:14:33). Trùng baseline đề bài — nhưng trùng đó là KẾT QUẢ ĐO, không phải giả-định đầu vào (bài "mốc PRE lão hoá sau MỖI deploy").
Content-Type `application/javascript` + ~1,7MB ⇒ không dính bẫy SPA-fallback (`text/html` ~900B). File tải về khớp byte-for-byte `Content-Length`.
## M0-bis — Chọn byte-marker CÓ RĂNG (verify 0-hit PRE TRƯỚC khi phong)
Đề bài cảnh báo đúng: K6 = 12 key literal **đã có từ K4a** + comment sẽ bị Vite strip ⇒ marker ngây thơ = 0 bit. Đo PRE xác nhận từng cái:
**LOẠI "key đơn lẻ"** — PRE admin đã có sẵn: `PurchaseEvaluations` **4** · `KeHoachKyKet` **1** · `HopDongCung` **1** · `PeWorkflows` **3** ⇒ POST có hit cũng **0 bit**.
**LOẠI tên biến `ADMIN_HIDDEN_KEYS`** — minifier đổi tên; đo POST: source-name **0 hit**, thực tế thành `var IW=new Set([...])`. (Bài K3 `earlyFinalizeLocked` tái xác nhận.)
**LOẠI comment lineage**`NƠI-CONFIG` / `thắng mốc trước`: PRE **0** POST **0** ⇒ Vite strip đúng như dự đoán.
**CHỌN marker HỢP-CHẤT: TRẬT TỰ KỀ NHAU của 12 literal trong `new Set([...])`.** Từng literal thì cũ, nhưng *thứ tự kề nhau* chỉ sinh ra khi Set mới được minify. Lineage source: `git grep ADMIN_HIDDEN_KEYS` @`e202393` = **0 hit** (exit 1) · @`1c6d77a` = **2 hit** ⇒ marker chưa tồn tại ở mốc PRE.
**Marker ÂM (K6 là DISPLAY-ONLY, không gỡ route):** `/purchase-evaluations`, `/khkk/list`, `/suppliers` PHẢI CÒN trong bundle admin POST.
**Differential:** eoffice kỳ vọng FROZEN (fe-user 0-diff) ⇒ marker phải **0** ở eoffice cả PRE lẫn POST.
---
## Mục 1 — Run Gitea cho `1c6d77a` ✅ PASS
| | |
|---|---|
| head_sha | `1c6d77a6ccd95b2e287a3602f23780558c9d1ecc` (khớp HEAD; `git log origin/main..HEAD` **rỗng**) |
| run | **#440** / task id **553** / `status=success` |
| thời gian | created 11:54:38 → updated 12:01:07 = **6m29s** |
| tail log | `Deploy done. App pool started.``API /health/live -> 200``🏁 Job succeeded` |
`paths-ignore` (7-glob): 8/10 file trong range là `.claude/**` (bị bỏ qua), **nhưng** `fe-admin/**` KHÔNG ⇒ CI đúng ra phải chạy, và nó đã chạy.
## Mục 2 — Gate CI **622 = 45 D + 577 I** ✅ PASS (tách-phần, khớp từng vế)
Đo ở web-UI log `…/actions/runs/440/jobs/0/logs` (18.697 B):
| Project | dòng log | Failed | Passed | Skipped |
|---|---|---|---|---|
| `SolutionErp.Domain.Tests.dll` | L29 | **0** | **45** | 0 |
| `SolutionErp.Infrastructure.Tests.dll` | L47 | **0** | **577** | 0 |
| **Tổng** | | **0** | **622** | 0 |
- Khớp **dự đoán TRƯỚC khi đo**: giữ nguyên 622, delta **0** — đúng: K6 chỉ thêm `Set` + comment, 0 file test trong range.
- **control ÂM:** `grep -c 'Failed!'` = **0**; pattern lỗi = **1 hit duy nhất** là dòng runner vô hại `Failed to setup Pty Unsupported` (L12, có ở mọi run).
- **Flaky đã khai** `DeleteContractSigningPlan_SoftDeletes`: run này **KHÔNG đỏ** (Failed 0). Mốc #439 xanh, #440 xanh ⇒ 2 run liên tiếp sạch, chưa cần re-run.
## Mục 3 — Mig GIỮ **71** + `sys.tables` **97** ✅ PASS (2 nguồn)
- **Nguồn 1 — git:** `git diff e202393..1c6d77a -- '*Migrations*'` = **rỗng** ⇒ 0-mig phía code. Đếm file repo (filter neo-đuôi `Designer\.cs$|ModelSnapshot\.cs$` — bản vá từ K5) = **71**.
- **Nguồn 2 — sqlcmd prod (đo SAU `status=success`, tức sau recycle):**
`MIGTOP=20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime` (= Mig 71, GIỮ) · `MIGCOUNT=71` · `SYSTABLES=97` (is_ms_shipped=0).
- Anchor DB không nhích (đúng cho FE-only): `MENUITEMS=200` · `PERMS=2221` — y hệt mốc #438.
## Mục 4 — Bundle: admin ROTATE / eoffice FROZEN-hợp-lệ ✅ PASS
| App | asset | PRE | POST | size | LM POST (+07) | trong cửa-sổ deploy? |
|---|---|---|---|---|---|---|
| admin | js | `C2FhHCNT` | **`Ig1R5tAh`** ROTATE | 1.758.685 → **1.758.853** (**+168**) | 11:59:40 | ✅ (11:54:3812:01:07) |
| admin | css | `DqXm5Hpn` | `DqXm5Hpn` FROZEN | 86.495 (giữ) | 11:59:40 | ✅ |
| eoffice | js | `DFOCQ2ks` | `DFOCQ2ks` FROZEN | 1.676.752 (giữ) | **12:00:41** (PRE là 11:14:05) | ✅ |
| eoffice | css | `DlN3yZ5L` | `DlN3yZ5L` FROZEN | 92.233 (giữ) | 12:00:41 | ✅ |
**Admin rotate = ĐẠT điều kiện FAIL-nếu-không** của đề bài. Δ **+168 B** — dương, nhỏ, đúng cỡ 1 `new Set` 12 key backtick (~170 ký tự); comment bị strip nên không cộng thêm.
🔑 **eoffice FROZEN là LÀNH TÍNH — chứng bằng 3 nhân chứng độc lập, KHÔNG phải "đoán vì fe-user 0-diff":**
1. **LM ĐÃ ĐỔI** 11:14:05 → **12:00:41**, nằm trong cửa-sổ #440 ⇒ file **có bị ghi lại** (deploy.yml copy `dist\*` vô điều kiện = #69 mode-A), không phải bundle cũ kẹt lại. *Đây là discriminator quyết định: bundle-cũ-kẹt sẽ giữ LM 11:14:05.*
2. **Log CI in thẳng asset phát ra** — L128 `dist/assets/index-DFOCQ2ks.js 1,676.75 kB` lúc `05:00:41.56` GMT ⇒ runner **ĐÃ BUILD LẠI** fe-user trong CHÍNH run này và Vite phát ra **cùng hash**. LM trên prod = mốc build này **khớp tới từng giây**. Frozen = build-determinism, không phải skip.
3. `cmp` PRE vs POST eoffice = **byte-identical** ⇒ nhất quán với source 0-diff.
(Đối chiếu admin: L93 `dist/assets/index-Ig1R5tAh.js 1,758.85 kB` @ `04:59:40.39` — khớp cả hash lẫn size lẫn LM prod. Log-CI là nhân chứng **thứ 4**, độc lập với curl prod.)
🔑 **Control-ÂM tầng-2 (chứng ghi-đè thật):** GET hash CŨ admin `index-C2FhHCNT.js`**200 `text/html` 900B** ⇒ rơi về SPA-fallback ⇒ bundle cũ đã biến mất khỏi đĩa. (200 ở đây KHÔNG phải "còn sống" — discriminator là Content-Type.)
### 4-bis — Byte-marker: 🔴 SUÝT FAIL-OAN 2 LẦN vì CƠ-CHẾ REGEX, không phải vì code
Marker hợp-chất đo lần đầu ra **PRE 0 / POST 0** — đọc vội thì thành "K6 không ship" ⇒ FAIL. Không phong vội, đi **soi byte thật** (`grep -oE '.{90}HopDongCung.{90}'`) thì thấy dạng minify THẬT:
```
var IW=new Set([`PurchaseEvaluations`,`KeHoachKyKet`,`Contracts`,`HopDongCung`,`Forms`,
`Reports`,`Master`,`Hrm`,`Off`,`Personal`,`Workflows`,`PeWorkflows`]);
```
- **Sai lần 1:** regex của tao chỉ chấp `["']` — rolldown/Vite 8 phát **backtick** (template literal). 0-hit là do regex không thể khớp, KHÔNG phải do vắng mặt.
- **Sai lần 2:** vá xong, truyền pattern qua biến trong `"..."` → backtick bị shell nuốt, `grep -cE` gãy (`grep: Unmatched (`) và trả **0** gần như im lặng trong vòng lặp. Lại một 0-hit giả.
- **Chốt bằng `grep -F` fixed-string** (không còn regex để sai):
| Marker | PRE admin | POST admin | POST eoffice | kỳ vọng | |
|---|---|---|---|---|---|
| Trọn chuỗi 12-key `new Set([...])` | **0** | **1** | **0** | 0→1, eoffice 0 | ✅ |
| Adjacency 3-key `` `PurchaseEvaluations`,`KeHoachKyKet`,`Contracts` `` | **0** | **1** | 0 | 0→1 | ✅ |
| control-ÂM đảo trật tự `` `KeHoachKyKet`,`PurchaseEvaluations` `` | 0 | **0** | 0 | 0 | ✅ |
| control-ÂM chuỗi bịa `ZZZ_NOT_EXIST_MARKER` | 0 | **0** | 0 | 0 | ✅ |
🔑 **COUNT-DELTA — nhân chứng thứ 2, độc lập với adjacency.** Vì literal đã tồn tại từ K4a nên *sự có mặt* = 0 bit, nhưng *số lần xuất hiện tăng* thì đo được. PRE→POST admin, cả **12/12** key đều **+1**:
`PurchaseEvaluations` 4→5 · `KeHoachKyKet` 1→2 · `Contracts` 7→8 · `HopDongCung` 1→2 · `Forms` 3→4 · `Reports` 3→4 · `Master` 4→5 · `Hrm` 28→29 · `Off` 400→401 · `Personal` 2→3 · `PeWorkflows` 3→4 · **`Workflows` 9→11 (+2)**.
Con **+2** duy nhất KHÔNG phải bất thường mà là **kiểm-tra-nhất-quán tự thân**: `Workflows` là substring của `PeWorkflows`, nên Set thêm 1 `Workflows` + 1 `PeWorkflows` ⇒ đếm substring ra +2. Nếu K6 chỉ ship một phần thì hình dạng 12×(+1) với đúng 1 ngoại lệ giải-thích-được này sẽ không dựng lại được.
🔑 **Marker ÂM (display-only, KHÔNG phải gỡ route) — ĐẠT:** `/purchase-evaluations` **68→68** · `/khkk/list` **13→13** · `/suppliers` **16→16**. Route còn nguyên trong bundle admin ⇒ K6 cắt đúng lớp *hiển thị*, không lỡ tay cắt lớp routing.
## Mục 5 — Smoke + phép K6-đặc-thù ✅ PASS
Bearer admin: `POST /api/auth/login` → `accessToken` len **468** ✅
| Endpoint | code |
|---|---|
| `GET /api/contracts` | **200** |
| `GET /api/purchase-evaluations` | **200** |
| `GET /api/menus` | **200** |
| `GET /api/contract-signing-plans` | **200** |
| control-ÂM `GET /api/khong-ton-tai-zzz` (có bearer) | **404** ✅ |
| control-ÂM `GET /api/contracts` KHÔNG token | **401** ✅ |
🔑 **Phép K6-đặc-thù — 2 tầng độc lập (gotcha #82) đo được bằng số:** `GET /api/menus` bearer admin trả **200 item** (khớp `MENUITEMS=200` sqlcmd ⇒ không mất row nào). Trong đó **10/10 root bị K6 ẩn VẪN được BE trả về**: `Contracts`, `Forms`, `HopDongCung`, `Hrm`, `KeHoachKyKet`, `Master`, `Off`, `Personal`, `PurchaseEvaluations`, `Reports` — cộng cả 2 con của `System` là `Workflows` + `PeWorkflows` ⇒ **12/12**.
⇒ Đúng như comment K6 tự khai: **ẩn ở client, KHÔNG thu quyền**. Deep-link + API vẫn 200. Muốn CHẶN thật phải làm ở BE authz.
---
## Verdict
**PASS 5/5.** Run #440 `1c6d77a` success 6m29s · gate **622 = 45 D + 577 I** (Failed 0, delta 0 đúng dự đoán) · Mig GIỮ **71** + `sys.tables` **97**, MenuItems/Perms đứng im 200/2221 · admin ROTATE `Ig1R5tAh` (+168 B) & eoffice FROZEN-**lành-tính** (LM đổi + log CI in cùng hash + byte-identical) · marker 12-key `Set` PRE 0→POST 1 + count-delta 12/12 + marker-ÂM route 68/13/16 nguyên vẹn · smoke 4/4 + 2 control-âm + menu-tree 12/12 key vẫn được BE phục vụ.
**Treo / khuyến nghị (không chặn):**
1. 🔴 **Bài học to nhất run này: 0-hit marker bị SAI 2 lần liên tiếp do cơ-chế regex, cả 2 lần đều trông y hệt "code không ship".** (a) rolldown/Vite 8 minify string → **backtick**, regex neo `["']` không bao giờ khớp; (b) truyền pattern chứa backtick qua biến trong `"..."` làm grep gãy và trả 0 gần như im lặng. ⇒ Từ nay: marker chuỗi trên bundle **dùng `grep -F` fixed-string**, và **luôn trích byte thật quanh literal (`.{90}X.{90}`) trước khi kết luận vắng mặt**.
2. **Kỹ thuật mới dùng được lại — COUNT-DELTA:** khi literal đã tồn tại từ đợt trước (marker "có/không" = 0 bit), đo **số lần xuất hiện PRE→POST**. Ngoại lệ substring (`Workflows` ⊂ `PeWorkflows` ⇒ +2) lại thành kiểm-tra-nhất-quán miễn phí.
3. **Log CI in thẳng `dist/assets/index-<hash>.js <size>`** — nhân chứng thứ 4 cho ship-proof, độc lập hoàn toàn với curl prod, và là cách rẻ nhất phân biệt FROZEN-lành-tính với bundle-kẹt (so LM prod với mốc build in trong log).
**Bundle baseline MỚI (post-#440):** admin `Ig1R5tAh`/`DqXm5Hpn` · eoffice `DFOCQ2ks`/`DlN3yZ5L`. *(Snapshot-per-run — lão hoá ngay ở deploy kế.)*
END cicd-k6 — VERDICT=PASS 5/5