220 lines
11 KiB
Markdown
220 lines
11 KiB
Markdown
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:33–17: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`
|
||
và `: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)
|