Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k2.md
2026-07-31 17:20:31 +07:00

220 lines
11 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-K2: PARTIAL — 5/6 mục (mục 4 = CHÂN-LÝ-RỖNG, không phải sạch)
# CI/CD verify K2 SP-002 — commit `0779f2d`
> Artifact chống #53 — ghi TỪNG MỤC NGAY KHI ĐO. Vai: cicd-monitor (READ-only). Ngày: 2026-07-31.
> Deploy KHỎE, KHÔNG khuyến nghị rollback. Điểm trừ duy nhất = **phép đo mục 4 không có răng**
> (bảng rỗng), không phải lỗi deploy.
---
## Mục 0 — Push + path-filter (tiền đề)
```
$ git log -1 --format='%H|%s|%ci'
0779f2d45a932ec7c279c191d9ae3fe3505442af|[CLAUDE] Contract: K2 SP-002 phieu KHKK mang hang-muc + nhom duyet N1-8 (Mig 71) + FE x2 app + 11 test|2026-07-31 17:05:27 +0700
$ git log origin/main..HEAD --oneline
(RỖNG — đã push trọn)
```
`git diff --name-only 0779f2d^..0779f2d` = **23 file**: BE 9 · Mig 71 ×3 file (3-file rule đủ) ·
FE 8 (admin ×4 + user ×4 đối xứng) · test 3 · paths-ignore 2 (`docs/STATUS.md`,
`.claude/skills/ef-core-migration/SKILL.md` — KHÔNG chặn vì ≥1 file code).
Neo bằng SHA tuyệt đối `0779f2d^..0779f2d` (LUẬT S162: cấm `HEAD~1`, Stop-hook WAL auto-commit).
## Mục 0b — SNAPSHOT PRE-MIGRATE (đo lúc run #435 còn `running`, 17:07)
```
MIGTOP=20260731072441_AddContractCatalogEntries <- Mig 70 (K1), Mig 71 CHƯA áp
PLANS=0 · LINES=0 · SUMPE=NULL|SUMPROP=NULL · SYSTABLES=97
admin /assets/index-CKgq0YlH.js + /assets/index-DD3xQhNt.css
eoffice /assets/index-CHMCU35C.js + /assets/index-CdXRNT7E.css (khớp mốc K1 #434)
```
🔴 **KHAI TRƯỚC — MỤC 4 SẼ RỖNG, KHÔNG PHẢI SẠCH.** Xem mục 4.
---
## Mục 1 — Gitea Actions run ✅ PASS
| | |
|---|---|
| Run | **#435** (id=548), `0779f2d4` |
| Status | **success** |
| Cửa sổ | created `17:05:33` → updated `17:11:45` = **6m12s** |
| URL | https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/435 |
Poll 6 vòng (17:08:31 → 17:12:26), running×5 → success. 6m12s ≈ K1 #434 (6m31s) — hợp lý cho
+Mig +11 test, chưa tới ngưỡng escalate.
## Mục 2 — Test gate 609 ✅ PASS (khớp TÁCH-PHẦN, không chỉ khớp tổng)
Log web-UI `.../actions/runs/435/jobs/0/logs` (18.677 B), grep nới `Passed![^\r\n]*`:
```
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, Du
Passed! - Failed: 0, Passed: 564, Skipped: 0, Total: 564, Du
```
| | Domain | Infra | Tổng |
|---|---|---|---|
| K1 #434 | 45 | 553 | 598 |
| K2 #435 | **45** | **564** | **609** |
| delta | **0** | **+11** | +11 |
Kỳ vọng 609 = 598 + 9 lane + 2 gate-fix ⇒ khớp **từng vế**: Domain đứng yên, +11 rơi TRỌN vào
Infra — đúng nơi 3 file test mới nằm (`ContractSigningPlanApprovalTests` /
`~GroupAndCatalogTests` / `~SchemaTests`). 0 fail, 0 skipped.
Control ÂM: grep chuỗi bịa `ZZZ_KHONG_TON_TAI_9x7` = **0 hit**.
⚠️ Dùng regex nới ngay từ đầu vì bài K1: log dùng **2 khoảng trắng** sau `Passed!`.
## Mục 3 — EF migration prod + chứng-restart ✅ PASS
```
MIGTOP=20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime <- TOP, đúng Mig 71
MIGCOUNT=71
SYSTABLES=97 (GIỮ NGUYÊN — Mig 71 có 0 CreateTable)
```
`sys.tables` 97 == 97 khớp phép đo nguồn `grep -c "CreateTable"` = **0** (control dương:
`grep -c "AddColumn"` = **4** ⇒ grep có tìm được thứ có thật).
**3-CHÂN-KIỀNG (né bẫy #423 đo-trước-recycle):**
1. *Restart thật:* `appcmd list wp``WP "5364" (applicationPool:SolutionErp-Api)`;
`Get-Process w3wp` → PID **5364 StartTime 17:11:25** ∈ cửa sổ deploy (17:05:3317:11:45).
(5 w3wp còn lại là pool VIETREPORT/Default — đã map PID→pool để không nhận nhầm.)
2. *Dòng ĐẦU Serilog:* `2026-07-31 17:11:27.310 +07:00 [INF] Applying migrations...`
3. *Mọi phép đo DB dưới đây chạy từ 17:12+* ⇒ SAU mốc recycle.
## Mục 4 — Data prod sau migrate ⚠️ PARTIAL — **CHÂN-LÝ-RỖNG**
Số đo nguyên văn (POST-migrate, sau recycle):
```
TOTAL=0 · GROUP1=0 · OUTOFRANGE=0 · LINES=0 · SUMPE=NULL|SUMPROP=NULL
CTRL_SUPPLIERS=60 · CTRL_CATALOG=86 <- control dương: bộ đếm CÓ đếm được
```
| Acceptance yêu cầu | Kết quả | Giá trị bằng chứng |
|---|---|---|
| `COUNT(ApprovalGroup=1) == COUNT(*)` | `0 == 0` ✅ | **RỖNG — 0 bit thông tin** |
| `COUNT(ApprovalGroup NOT BETWEEN 1 AND 8) = 0` | `0` ✅ | **RỖNG** |
| Σ `PeReferenceAmount`/`ProposedAmount` | `NULL\|NULL` (PRE cũng `NULL\|NULL`) | **RỖNG hai phía** |
🔴 `ContractSigningPlans` prod có **0 dòng cả TRƯỚC lẫn SAU**. Ba acceptance "xanh" nhưng
**không mang thông tin nào** về việc backfill có chạy đúng — backfill hụt sẽ trông y hệt.
Chính tác giả Mig 71 đã khai bẫy này ("đo local Σ = tập RỖNG hai phía … phép đo prod mới có
răng"); nay prod cũng rỗng ⇒ **phép đo prod cũng KHÔNG có răng**. Không hạ thành FAIL (không có
gì sai), nhưng CẤM đọc thành PASS.
**Bằng chứng THAY THẾ — không phụ thuộc số dòng (CÓ RĂNG):**
*(a) 4 cột, đúng kiểu/nullability/default (`INFORMATION_SCHEMA.COLUMNS`):*
```
ContractSigningPlans |ApprovalGroup |int |null=NO |def=((0))
ContractSigningPlans |EndedByLevelFinalize|bit |null=NO |def=(CONVERT([bit],(0)))
ContractSigningPlanLines |CatalogEntryId |uniqueidentifier|null=YES|def=-
ContractSigningPlanLines |TenHangMuc |nvarchar(500) |null=YES|def=-
```
*(b) HOÁN-ĐỔI INDEX — chứng bằng **liệt kê TRỌN HỌ** (14 index cả 2 bảng), không bằng grep-rỗng:*
```
✅ CÓ : IX_ContractSigningPlanLines_ContractSigningPlanId_SupplierId_CatalogEntryId
unique=1 | filter=([IsDeleted]=(0)) | cols=ContractSigningPlanId,SupplierId,CatalogEntryId
✅ CÓ : IX_ContractSigningPlanLines_CatalogEntryId (cols=CatalogEntryId)
✅ CÓ : IX_ContractSigningPlans_ApprovalGroup_Phase (cols=ApprovalGroup,Phase)
❌ MẤT: IX_ContractSigningPlanLines_ContractSigningPlanId_SupplierId (unique 2-cột cũ)
```
Vắng-mặt của index cũ suy ra từ **danh sách đầy đủ 14 index có thật**, nên đây là vắng-mặt ĐO
ĐƯỢC chứ không phải "truy vấn trả rỗng đọc thành sạch". Đây là phép đo mạnh nhất của mục 4.
*(c) Bất-biến Σ tiền — chứng theo CẤU TRÚC bằng phép ĐO nguồn:*
`grep -nE "PeReferenceAmount|ProposedAmount|ApprovedAmount"` trên Mig 71 → chỉ **2 hit, cả 2 ở
dòng 23-24 và đều là comment `//`** ⇒ **0 câu lệnh thực thi** chạm cột tiền. `migrationBuilder.Sql`
= đúng **1** câu (dòng 61, backfill `ApprovalGroup=1`). Σ bất biến đúng kể cả khi bảng có dữ liệu.
*(d) Materialize qua EF:* xem mục 6 — `?group=N` trả **200** (không 500) ⇒ cột `ApprovalGroup`
tồn tại & query được xuyên Domain→Infra→Api trên DB thật.
## Mục 5 — Bundle ×2 app (#77) ✅ PASS — 4/4 ROTATE
| app | asset | TRƯỚC (K1 #434) | SAU (K2 #435) |
|---|---|---|---|
| admin | js | `CKgq0YlH` | **`DVQEwGwf`** |
| admin | css | `DD3xQhNt` | **`D050EVYG`** |
| eoffice | js | `CHMCU35C` | **`CZAYiWWa`** |
| eoffice | css | `CdXRNT7E` | **`DX1FRk5n`** |
*Hash CŨ chết (control âm tầng-2 — bundle cũ biến mất khỏi đĩa):*
```
admin /assets/index-CKgq0YlH.js -> 200|text/html|900 (SPA fallback, không phải JS)
eoffice/assets/index-CHMCU35C.js -> 200|text/html|876
admin /assets/index-ZZBIADAT.js -> 200|text/html|900 <- hash BỊA, cùng dấu hiệu
```
Hash bịa cho ra ĐÚNG dấu hiệu của hash cũ ⇒ `text/html` thật sự nghĩa là "file không tồn tại"
(né bẫy SPA-fallback-200 S72; dùng `Content-Type` chứ không neo byte).
*Last-Modified in-window:* admin `10:10:20 GMT` = 17:10:20 +07 · eoffice `10:11:20 GMT` =
17:11:20 +07 — cả hai ∈ (17:05:33, 17:11:45). `Cache-Control: no-cache` cả 2 (hardening S88).
## Mục 6 — Smoke ✅ PASS
| Phép đo | Kỳ vọng | Thực tế |
|---|---|---|
| `/health/ready` | 200 | **200** |
| `/health/live` | 200 | **200** |
| `/health` trần | (control) | **404** ⇒ 200 ở trên có nghĩa |
| `GET /api/contract-signing-plans?group=2` KHÔNG token | 401 | **401** |
| route BỊA `/api/contract-signing-plans-KHONG-TON-TAI?group=2` | 404 | **404** |
| route BỊA 2 `/api/zzz-fake-9x7` | 404 | **404** |
| admin.solutions.com.vn | 200 | **200** |
| eoffice.solutions.com.vn | 200 | **200** |
401 (route có thật, có gác) tương phản 404 ×2 (route không có) ⇒ **401 CÓ NGHĨA**.
*Có token admin* (login `/api/auth/login`, `accessToken` len=468):
```
?group=1 -> 200 {"items":[],"total":0,"page":1,"pageSize":20,"totalPages":0,...}
?group=2 -> 200 (thân y hệt)
?group=8 -> 200 (thân y hệt)
inbox -> 200 deleted -> 200
```
"mọi item `approvalGroup==1`" ⇒ **VACUOUS (0 item)** — không tính là bằng chứng.
⚠️ Đã soi **thân RAW** (không tin bộ parse) đúng bài K1 "0-hit là dương-giả tới khi soi format":
thân thật là `{"items":[],"total":0}` ⇒ rỗng THẬT, không phải lỗi đọc format.
**Chứng `group` ĐƯỢC WIRE (không bị nuốt im lặng):**
```
?group=abc -> 400 <- bind int THẬT; nếu param không tồn tại, ASP.NET bỏ qua query lạ và trả 200
```
Khớp nguồn: `ContractSigningPlansController.cs:42``[FromQuery(Name = "group")] int? group = null`
`:49` truyền vào `ListContractSigningPlansQuery(..., group)`.
**Control DƯƠNG cho đường ống list:** `GET /api/catalogs/contract-catalog`**86 item**
(seed K1 còn nguyên) ⇒ token + serializer + đường ống list CHẠY, nên `items:[]` của KHKK là
bảng rỗng thật, không phải ống vỡ.
---
## Lệch / quan sát (không chặn deploy)
1. **`?group=999` → 200** (không 400). Miền nghiệp vụ là 1..8 nhưng biên API không chặn
ngoài-miền; hiện vô hại (trả rỗng) và mục 4 đã xác nhận 0 dòng ngoài 1..8. Ghi cho K4b cân
nhắc cùng lớp với validator `CatalogEntryId` bắt buộc mà Mig 71 đã hẹn.
2. **Ghost-window `EndedByLevelFinalize`** — tác giả khai trước: K2 đẻ cột, K3 tiêu thụ; tới khi
K3 land cột luôn = 0. Đã xác minh cột tồn tại `bit NOT NULL default 0`; **không** coi
"toàn 0" là dữ liệu hỏng.
3. **Mục 4 rỗng** — xem trên. Khi có phiếu KHKK thật trên prod, cần đo LẠI 3 acceptance đó thì
mới có răng; hiện chưa thể kết luận backfill đúng/sai từ dữ liệu.
## Bảng tổng
| # | Mục | Verdict |
|---|---|---|
| 1 | Gitea run `0779f2d` success | ✅ PASS |
| 2 | Test gate 609 = 45D + 564I | ✅ PASS |
| 3 | Mig 71 TOP + chứng-restart | ✅ PASS |
| 4 | Data prod sau migrate | ⚠️ **PARTIAL — vacuous** (schema/index CÓ răng ✅) |
| 5 | Bundle ×2 app rotate + hash cũ chết | ✅ PASS |
| 6 | Smoke 401/404/200 + group wired | ✅ PASS |
**Không ghi gì lên prod ngoài đọc** (toàn bộ là SELECT / curl GET / login POST lấy token).
END — VERDICT=PARTIAL 5/6 (mục 4 vacuous, deploy khỏe, không rollback)