CICD-GD3D-LE: PASS 6/6 — run #444 success 6m28s, gate 644 = 45D + 599I (đúng +5 Fact), bundle rotate ×2 app với marker element-wise 1/2 khớp số trang mỗi app, route PUT lines/{lineId} 404→401 (bearer→400 gọi tên đủ 3 field của command MỚI), Mig prod GIỮ 71 set-diff 2 chiều rỗng.

# CI/CD verify — S168 GĐ3-D + Lines-editor KHKK (`a2b37dc..0bfadc2`)

Agent: cicd-monitor (READ-only) · 2026-08-01 · đo 17:10 → 17:22 (+07)
Đề bài: 2 commit code (`081557b` GĐ3-D FE-only · `0bfadc2` Lines-editor BE+FE+test), 0 migration mới, gate kỳ vọng 644 = 45D + 599I.

---

## M0 — Push + range + path-filter + ancestry (#86 guard) — **PASS**

| Mục | Đo | Kết quả |
|---|---|---|
| TIP repo | `git log -1 origin/main` | `0bfadc2011defe60cb1b7a9b17dbf18fbdd35abd` |
| Unpushed | `git log origin/main..HEAD` | **rỗng** |
| Ancestry | `git merge-base --is-ancestor 081557b 0bfadc2` | **TRUE** ⇒ 1 run trên TIP phủ CẢ 2 commit |
| Range files | `git diff --name-only a2b37dc..0bfadc2` | 18 file: 7 FE (`fe-admin/src` ×3 · `fe-user/src` ×4) + 2 BE + 1 test + `scripts/backup-sql.ps1` + 7 file `.claude/**` (paths-ignore) |
| Path-filter (#41) | vs 7-glob | **CI PHẢI CHẠY** — `fe-*/src/**`, `src/Backend/**`, `tests/**` đều non-ignored |
| Mig mới | `git diff … -- '*Migrations*'` | **RỖNG**; repo có **71** file mig (đếm neo đuôi `Designer\.cs$|ModelSnapshot\.cs$`) |

Commit kẹp giữa `2305c39`/`5c94612`/`7d08670` = `wal:` flush (chỉ `.claude/**`) ⇒ noise, không đẻ run.
⚠️ **#86 KHÔNG tái diễn**: bảng run KHÔNG có run nào head_sha=`081557b` (bị nuốt hay không thì cũng không quan trọng) — **bằng chứng quyết định là marker M-B của `081557b` NẰM TRONG bundle prod** (mục M2), tức code GĐ3-D thật sự ship qua run của TIP.

---

## M1 — Gitea Actions run — **PASS**

| Mục | Giá trị |
|---|---|
| Run | **#444** (id 557), `head_sha=0bfadc20…` = TIP ✅ |
| Status | **success** |
| Cửa sổ | created `17:08:56` → updated `17:15:24` = **6m28s** (baseline 5m3x–6m3x, không vượt 8min) |
| URL | https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/444 |
| Run trước | #443 `a2b37dc` success 16:12→16:18 (mốc PRE) · #441 `aaed699` **cancelled** = di chứng #86 hôm nay, KHÔNG dính lần này |

**Test-gate** (web-UI log `runs/444/jobs/0/logs`, 18.699 B):
```
Passed!  - Failed:     0, Passed:    45, Skipped:     0      <- Domain
Passed!  - Failed:     0, Passed:   599, Skipped:     0      <- Infrastructure
```
⇒ **644 = 45 + 599**, khớp kỳ vọng **tách-phần** (không chỉ khớp tổng). Delta vs #443/K7 (639 = 45 + 594) = **+5 Infra**, đúng bằng "+5 Fact" khai trong commit-msg `0bfadc2`. 0 fail / 0 skip.

---

## M2 — Bundle ×2 app: rotate + marker + LM + control-âm — **PASS**

### Rotate (4 nhân chứng độc lập: index.html live ⟂ file thật ⟂ log CI ⟂ Last-Modified)

| App | PRE (sổ) | POST | size prod | size in LOG CI | LM (UTC) → +07 |
|---|---|---|---|---|---|
| admin JS | `RgvHZkPH` | **`DLRf9TeA`** | 1.767.413 B | `1,767.41 kB` ✅ | 10:13:56 → **17:13:56** in-window |
| admin CSS | `DqXm5Hpn` | **`IuRRquXK`** | 86.602 B | `86.60 kB` ✅ | — |
| eoffice JS | `D65FJPGd` | **`C7TGA4Kn`** | 1.685.160 B | `1,685.16 kB` ✅ | 10:14:57 → **17:14:57** in-window |
| eoffice CSS | `DlN3yZ5L` | **`DesmJ6-_`** | 92.340 B | `92.34 kB` ✅ | — |

`Content-Type: application/javascript` (KHÔNG phải SPA-fallback text/html) ⇒ file thật.

### Control-âm tầng-2 (hash CŨ phải biến mất khỏi đĩa)
`GET /assets/index-RgvHZkPH.js` → **200 `text/html` 900 B** · `index-D65FJPGd.js` → **200 `text/html` 876 B** ⇒ SPA-fallback = bundle cũ ĐÃ bị ghi đè, không phải "copy thêm file mới".

### Marker-set — `grep -o -F` fixed-string (chống bẫy backtick-minify K6), PRE đo lúc 17:10 trước khi deploy land

| Marker | PRE admin | PRE eoff | POST admin | POST eoff | Kỳ vọng từ source |
|---|---|---|---|---|---|
| **M-A** `— Chọn hạng mục —` (select line-editor, `0bfadc2`) | 0 | 0 | **1** | **1** | 1 chỗ/app ✅ |
| **M-B** `lg:max-h-none` (GĐ3-D, `081557b`) | 0 | 0 | **1** | **2** | admin 1 trang (ContractsListPage) · eoffice 2 trang (MyContractsPage + InboxPage) ✅ **element-wise, KHÔNG đối xứng** |
| **M-C** `bodyMaxHClass` (prop) | 0 | 0 | **2** | **3** | = 1 destructure + N call-site ✅ |
| CTRL+ `Dòng này đã được đưa vào hợp đồng` (K7, cùng FILE) | 1 | 1 | 1 | 1 | grep CÓ RĂNG + file có trong bundle |
| CTRL+ `max-h-[28rem]` (default cũ) | 1 | 1 | **2** | **3** | count-delta +1/+2 khớp đúng số call-site mới |
| CTRL+ `Chọn hạng mục công việc` (PE, KHÔNG phải marker) | 2 | 2 | 2 | 2 | chứng M-A độc-quyền, đứng im |
| **CSS** `max-h-none` | 0 | 0 | **1** | **1** | Tailwind gen class mới — **nhân chứng ở ARTIFACT KHÁC (CSS ≠ JS)** |

**Byte thật (trích, chống 0-hit-giả và kết-luận-suông):**
```
option`,{value:``,children:`— Chọn hạng mục —`}                     (post-admin.js)
entStage:3,bodyMaxHClass:`max-h-[28rem] lg:max-h-none`}             (×1 admin, ×2 eoffice)
.lg\:max-h-none{max-height:none}                                     (post-admin.css)
```
⇒ M-B phân bố **1 : 2** đúng bằng số trang Panel-3 mỗi app (tổng 3 trang như đề bài). Comment `lg:max-h-none` trong `PipelineTreePanel.tsx:94` bị Vite strip ⇒ 0 đóng góp — nhất quán.

---

## M3 — BE endpoint mới `PUT /api/contract-signing-plans/{id}/lines/{lineId}` — **PASS**

| Probe | PRE-deploy (17:11) | POST-deploy (17:16) | Đọc |
|---|---|---|---|
| PUT route mới, **KHÔNG token** | **404** | **401** | route CHƯA có → ĐÃ có + authz sống ✅ |
| PUT route **BỊA** (`/zzz-khong-ton-tai/`), không token | 404 | **404** | control-âm: 401 KHÔNG phải phản-xạ chung, đúng route mới ✅ |
| PUT route mới, **bearer admin**, GUID toàn-0 | — | **400** | vượt policy `KeHoachKyKet.Update`, tới FluentValidation |

**Wire-proof mạnh nhất** — body 400 gọi tên **đủ 3 field của command MỚI**:
```json
"errors":{"PlanId":["'Plan Id' must not be empty."],
          "LineId":["'Line Id' must not be empty."],
          "CatalogEntryId":["'Catalog Entry Id' must not be empty."]}
```
⇒ `UpdateContractSigningPlanLineCommand(PlanId, LineId, CatalogEntryId)` + validator của nó đã land và chạy end-to-end Api→Application (không chỉ "route tồn tại"). Đây cũng là **chứng app-pool ĐÃ recycle bằng code mới** (không cần đo `w3wp` StartTime riêng).

---

## M4 — Smoke prod — **PASS**

| Check | Kết quả |
|---|---|
| `GET https://api.solutions.com.vn/health/ready` · `/health/live` | **200** · **200** |
| `GET https://admin.solutions.com.vn/` · `https://eoffice.solutions.com.vn/` | **200** · **200** |
| `http://` (redirect rule) admin · eoffice | **301** · **301** |
| `POST /api/auth/login` admin@solutions.com.vn | **200** (accessToken 468 ký tự) |
| Bearer: `/api/contract-signing-plans` · `/api/menus` · `/api/contracts` · `/api/purchase-evaluations` | **200 / 200 / 200 / 200** |

*(nv.test không probe lượt này — commit 0 đụng grant/seeder; tầng-2 #82 không phải phạm vi K8-blocker.)*

---

## M5 — Migration prod — **PASS (GIỮ 71)**

`ssh vietreport-vps` + `sqlcmd -E` (⚠️ `$env:PROD_DB_PASSWORD` không tồn tại trên VPS — dùng Windows-auth, đúng như đề bài dặn):

| Mục | Giá trị |
|---|---|
| `COUNT(*) __EFMigrationsHistory` | **71** |
| Top 3 prod | `20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime` · `20260731072441_AddContractCatalogEntries` · `20260729122015_AddContractSigningPlans` |
| Repo (neo đuôi) | **71**, top y hệt |
| **Set-diff 2 chiều** | REPO∖PROD = **rỗng** · PROD∖REPO = **rỗng** ⇒ bằng nhau theo TẬP, không chỉ theo tổng |
| `sys.tables (is_ms_shipped=0)` | **97** (giữ nguyên — 0 CreateTable trong range) ✅ |

---

## M6 — Restart-side-effect

0 seeder đổi trong range ⇒ không có row-delta cần đo. Chứng "pool đã chạy code mới" lấy **miễn phí** từ M3 (404→401 + 400 gọi tên 3 field command mới) — mạnh hơn đo `w3wp` StartTime vì nó chứng **đúng binary mới đang phục vụ**, không chỉ "process có restart".

---

## Tổng kết — **PASS 6/6**

| Mục | Verdict | Số chốt |
|---|---|---|
| M0 push/filter/ancestry | ✅ | 18 file, 0 mig, ancestor=TRUE |
| M1 run + gate | ✅ | #444 success 6m28s · **644 = 45 + 599** (Δ+5 = đúng 5 Fact mới) |
| M2 bundle ×2 | ✅ | 4/4 hash rotate · marker **1:2 element-wise** · CSS +1 · LM in-window · hash cũ → text/html |
| M3 endpoint mới | ✅ | 404→**401**, control-âm 404, bearer→**400 tên đủ 3 field** |
| M4 smoke | ✅ | 6/6 site+health, login 200, 4/4 bearer 200 |
| M5 mig | ✅ | prod **71** ≡ repo 71 (set-diff 2 chiều rỗng), tables 97 |

**0 critical issue. Không cần rollback. Không đề xuất gotcha mới.**

Ghi chú cho lượt sau: mốc PRE mới = admin `DLRf9TeA`/`IuRRquXK` · eoffice `C7TGA4Kn`/`DesmJ6-_` · gate **644 = 45 + 599** · Mig 71 · tables 97.

END sub-cicd-verify-gd3d-lineeditor VERDICT=PASS 6/6
