11 KiB
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):
- 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.) - Dòng ĐẦU Serilog:
2026-07-31 17:11:27.310 +07:00 [INF] Applying migrations... - 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)
?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 validatorCatalogEntryIdbắt buộc mà Mig 71 đã hẹn.- 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ạibit NOT NULL default 0; không coi "toàn 0" là dữ liệu hỏng. - 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)