# CICD verify — dot 1 — commit b1bae77

> Ghi-đĩa TRONG lúc làm (7 sub mất return trong run này). Mỗi mục ghi ngay khi có kết quả.
> Bắt đầu: 2026-07-27, agent cicd-monitor.

## Stage 1 — Push + path-filter (DONE)

- Commit: `b1bae77c9c9863b846be418c7e8c1c695b11656a`
- Subject: `[CLAUDE] Workflow: sua quy trinh duyet TAI CHO + co F6 AllowApproverDelete (dot 1)`
- Commit date: `2026-07-27 12:36:59 +0700`
- `git log origin/main..HEAD` = EMPTY → push đã lên remote OK
- Files changed = **13**, breakdown:
  - FE: 3 (`fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx`, `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx`, `fe-user/src/types/approvalWorkflowV2.ts`)
  - BE: 5 (Api Controller `ApprovalWorkflowsV2Controller.cs`, App `ApprovalWorkflowV2AdminFeatures.cs`, Domain `ApprovalWorkflow.cs`, Infra `ApprovalWorkflowConfiguration.cs`, Infra `PurchaseEvaluationWorkflowService.cs`)
  - **Mig: 3-file rule ĐỦ** — `20260727033522_AddPeAllowApproverDelete.cs` + `.Designer.cs` + `ApplicationDbContextModelSnapshot.cs` ✅
  - Tests: 2 (`AuthorizePolicyRegressionTests.cs`, `UpdateAwDefinitionTests.cs`)
- Path-filter (7-glob `paths-ignore`): KHÔNG file nào khớp → **CI PHẢI trigger** (không skip)

---

## Migration nội dung (đọc từ đĩa, file có trong commit)

`20260727033522_AddPeAllowApproverDelete.cs` Up():
```csharp
migrationBuilder.AddColumn<bool>(
    name: "AllowApproverDelete", table: "ApprovalWorkflowLevels",
    type: "bit", nullable: false, defaultValue: false);
```
→ AddColumn-only, **no new table** (kỳ vọng `sys.tables` giữ 89). Down = DropColumn (reversible).

## Pre-deploy bundle snapshot (chụp LÚC run đang chạy, 12:38)

| App | JS | CSS |
|---|---|---|
| admin | `index-hHISK1Ig.js` | `index-CBzUlgOA.css` |
| eoffice | `index-CJeXZso2.js` | `index-BHsBUA8e.css` |

## Stage 2 — Gitea Actions run (IN PROGRESS)

- Run **id=529 · run_number=#416 · head_sha `b1bae77c`** — MATCH commit ✅
- created_at `2026-07-27T12:37:18+07:00` · status lúc đo đầu = `running`
- Run URL: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/416
- **TERMINAL: `status=success`** · updated `2026-07-27T12:42:49+07:00` · **duration 5m31s** (12:37:18→12:42:49) — trong dải bình thường (~5m30 các run gần đây)
- ⚠️ `conclusion=None` là bình-thường ở Gitea `tasks` API (đã tái-xác-nhận nhiều lần) → tin `status`.

### Test gate — CÓ CHẠY và PASS

Log web-UI `runs/416/jobs/0/logs` (17.856 B tải về), trích nguyên văn:
```
Passed!  - Failed:     0, Passed:    45, Skipped:     0
Passed!  - Failed:     0, Passed:   506, Skipped:     0
```
→ **45 Domain + 506 Infra = 551 PASS / 0 FAIL / 0 SKIP**. **KHỚP CHÍNH XÁC số local lead đo (551 = 45+506)** ✅
Baseline dịch: 532 (run #407) → **551** (+19 Infra).
Test gate chạy TRƯỚC build/deploy ⟹ `status=success` ⟹ gate đã qua.

---

## 🔴 Stage 3 — MIGRATION TRÊN DB PROD (kiểm quan trọng nhất) — **PASS**

`sqlcmd` qua `ssh vietreport-vps`, DB `SolutionErp`, user `vrapp`. Output NGUYÊN VĂN:

```
=== TOP3 HISTORY ===
20260727033522_AddPeAllowApproverDelete
20260717032812_AddPeApprovedBudgetSnapshot
20260714035636_AddPeApprovedPriceRange
=== TARGET MIG ROW ===
20260727033522_AddPeAllowApproverDelete
=== COLUMN ===
ApprovalWorkflowLevels|AllowApproverDelete|bit|nullable=NO
=== SYSTABLES ===
89
=== DEFAULT ===
(CONVERT([bit],(0)))
```

**Đối chiếu (đây là điểm F8):**

| Kiểm | Kỳ vọng | Thực tế | KQ |
|---|---|---|---|
| Row `20260727033522_...` trong `__EFMigrationsHistory` | có + ở TOP | **có, ở TOP** | ✅ |
| Cột `AllowApproverDelete` trên `ApprovalWorkflowLevels` | tồn tại, `bit`, NOT NULL | **tồn tại, `bit`, nullable=NO** | ✅ |
| Default constraint | `0` | `(CONVERT([bit],(0)))` | ✅ |
| `sys.tables` (is_ms_shipped=0) | 89 (AddColumn no-new-table) | **89** | ✅ |
| **Nhất quán history ⟷ cột** | cả 2 cùng có | **cả 2 cùng có** | ✅ **KHÔNG lệch** |

🟢 **Kịch bản F8 (model có cột / DB không có ⟹ `Invalid column name` hạ 7 module) KHÔNG xảy ra.**
Baseline TRƯỚC deploy (đo lúc 12:38, run còn chạy): history top = `20260717032812_AddPeApprovedBudgetSnapshot`, chưa có mig mới
→ chứng minh mig được áp **trong** cửa-sổ deploy này, không phải sẵn có từ trước.

Mig repo mới nhất giờ = **Mig 68 `AddPeAllowApproverDelete`** (trước là Mig 67).

---

## Stage 4 — Smoke 7 module V2 — **PASS 8/8**

Bearer admin (`POST /api/auth/login` → HTTP 200, token 468 ký tự). Output thật:

```
PE          /api/purchase-evaluations?page=1&pageSize=5    HTTP 200  bytes=5103    InvalidCol=False
HopDong     /api/contracts?page=1&pageSize=5               HTTP 200  bytes=91      InvalidCol=False
DeXuat      /api/proposals?page=1&pageSize=5               HTTP 200  bytes=91      InvalidCol=False
NghiPhep    /api/leave-requests?page=1&pageSize=5          HTTP 200  bytes=91      InvalidCol=False
TangCa      /api/ot-requests?page=1&pageSize=5             HTTP 200  bytes=91      InvalidCol=False
CongTac     /api/travel-requests?page=1&pageSize=5         HTTP 200  bytes=91      InvalidCol=False
DatXe       /api/vehicle-bookings?page=1&pageSize=5        HTTP 200  bytes=91      InvalidCol=False
AwV2-list   /api/approval-workflows-v2                     HTTP 200  bytes=41234   InvalidCol=False
```

→ **0/8 có chuỗi `Invalid column name`**, 8/8 HTTP 200. Kịch bản F8 không tái hiện được.
⚠️ Trung thực về độ mạnh: 6 module Văn-phòng-số trả 91 B (envelope rỗng, chưa có dữ-liệu) nên chỉ chứng minh **query chạy không ném**, chưa chứng minh join sâu vào Levels. Hai phép mạnh thật nằm ở PE (5.103 B có dữ-liệu) và AwV2 (41.234 B) dưới đây.

### 🔑 Phép thử TRỰC TIẾP nhất cho F8 — cột round-trip qua EF

`GET /api/approval-workflows-v2` (endpoint đọc THẲNG bảng `ApprovalWorkflowLevels`):
```
bytes 41234
count allowApproverDelete   = 54
count allowApproverFinalize = 54
LEVEL sample: allowApproverDelete= False | order= 1 | keys# 17
```
→ Cột MỚI được EF materialize **54 lần** từ DB thật ra JSON, giá-trị `False` đúng default `0`.
Nếu DB thiếu cột thì chính truy-vấn này đã ném `Invalid column name` — **đây là bằng-chứng khép kín model ⟷ DB đồng bộ.**

## Stage 5 — Endpoint MỚI `PUT /api/approval-workflows-v2/{id}` — **PASS (401, không phải 404)**

```
PUT /api/approval-workflows-v2/1111...1111 (KHÔNG token, có body) -> 401
GET /api/approval-workflows-v2/1111...1111 (KHÔNG token, có body) -> 405
--- control: route chắc chắn KHÔNG tồn tại ---
PUT /api/approval-workflows-v2-nonexistent/{id}                   -> 404
```
→ Route **401** = đã lên + `[Authorize]` chặn đúng. Control route bịa trả **404** ⟹ 404 đúng là tín-hiệu "route chưa lên", nên 401 ở route thật là có nghĩa (discriminator sạch, không phải may).
(405 ở GET chỉ nói `GET {id}` không được map — không liên quan.)

## Stage 6 — Bundle FE — **PASS (deploy thay file thật, không phải cache)**

```
== admin ==
  JS  pre=index-hHISK1Ig.js   post=index-CiUBEEJr.js   ROTATED
  CSS pre=index-CBzUlgOA.css  post=index-DX5ew0wg.css  ROTATED
  real-js  index-CiUBEEJr.js   HTTP 200 size=1670035 ct=application/javascript LM=Mon, 27 Jul 2026 05:41:28 GMT
  FAKE-ctl index-ZZfakehash0.js HTTP 200 size=900    ct=text/html
== eoffice ==
  JS  pre=index-CJeXZso2.js   post=index-DFh7GK0t.js   ROTATED
  CSS pre=index-BHsBUA8e.css  post=index-BHsBUA8e.css  FROZEN
  real-js  index-DFh7GK0t.js   HTTP 200 size=1570257 ct=application/javascript LM=Mon, 27 Jul 2026 05:42:22 GMT
  FAKE-ctl index-ZZfakehash0.js HTTP 200 size=876    ct=text/html
```

- **JS ROTATE cả 2 app** ✅ — khớp commit có sửa src cả `fe-admin` (1 file) lẫn `fe-user` (2 file).
- `Last-Modified` **05:41:28 / 05:42:22 GMT = 12:41:28 / 12:42:22 +07** → **NẰM TRONG cửa-sổ deploy** 12:37:18–12:42:49 ✅ (đây mới là ship-proof chính, không phải hash-delta).
- real-size 1.670.035 / 1.570.257 B; bẫy SPA-fallback: hash bịa cũng trả 200 nhưng **`ct=text/html` + 900/876 B** ⟹ discriminator sạch, file thật là JS thật.
- eoffice **CSS FROZEN** = lành tính (#69-REFINE): 2 file fe-user sửa là logic/type, không thêm utility-class Tailwind mới ⟹ CSS byte-identical ⟹ cùng content-hash. Admin CSS rotate vì thêm ô chọn F6 (class mới).

### Byte-verify #77 — mã tính-năng CÓ THẬT trong bundle prod
```
admin    size=1670035  allowApproverDelete=7  allowApproverFinalize=11
eoffice  size=1570257  allowApproverDelete=1  allowApproverFinalize=5
```
→ chuỗi `allowApproverDelete` hiện diện trong CẢ HAI bundle đã ship ⟹ không chỉ "file mới" mà **đúng mã F6 đã lên**.

## Health
```
/health/live  -> 200 b'Healthy'
/health/ready -> 200 b'Healthy'
```

---

## KẾT LUẬN

**CICD: PASS** — 6/6 hạng mục bắt buộc đạt, rủi ro F8 đã loại trừ bằng bằng-chứng trực tiếp trên DB prod.

| # | Hạng mục | KQ |
|---|---|---|
| 1 | Gitea run `b1bae77` + test gate | ✅ #416 success 5m31s · 551 PASS (45+506) 0 fail |
| 2 | 🔴 Mig áp trên DB prod | ✅ `20260727033522_AddPeAllowApproverDelete` ở TOP history |
| 3 | 🔴 Cột có thật | ✅ `bit` NOT NULL default 0 — **khớp history, không lệch** |
| 4 | Smoke 7 module | ✅ 8/8 HTTP 200, 0 `Invalid column name` |
| 5 | Endpoint mới PUT | ✅ 401 (control route bịa = 404) |
| 6 | Bundle FE 2 app | ✅ JS rotate ×2, LM trong cửa-sổ, marker F6 có trong bundle |

Không có mục nào "không truy được". Không có cảnh-báo cần leo thang.
Baseline cập nhật: test **532 → 551**; mig mới nhất **Mig 67 → Mig 68**; `sys.tables` giữ **89**.

<!-- END cicd-verify-dot1 -->

