15 KiB
sub-cicd-monitor-4 — verify deploy 3119cb9 (W7 KHKK GĐ4 "Hợp đồng cứng")
Ghi-từng-mục TRONG LÚC LÀM (chống #53). File này là SẢN PHẨM CHÍNH; return chỉ là tóm tắt. Bắt đầu: 2026-07-30 00:18 (+07)
CHÂN-0 — Push + path-filter (ground truth từ đĩa)
- Commit:
3119cb91041bb08192d354da86fc68b305dde48b(3119cb9) - Subject:
[CLAUDE] Contract: W7 KHKK GD4 "Hop dong cung" 1-moc — HardCopiesPage x2 + ?type filter + hasSealedCopy badge + 3 test PIN (suite 590/0) - Author date:
2026-07-30 00:17:52 +0700 git log origin/main..HEAD→ EMPTY ⇒ đã push, không còn commit treo local. ✅git diff --shortstat HEAD~1 HEAD→ 12 files changed, 1358 insertions(+), 18 deletions(-)- 12 file (KHÔNG file nào khớp
paths-ignore7-glob ⇒ CI PHẢI chạy — đúng, run #431 đã trigger):fe-admin/src/App.tsx·fe-admin/src/components/Layout.tsx·fe-admin/src/pages/contracts/HardCopiesPage.tsx·fe-admin/src/types/contracts.tsfe-user/src/App.tsx·fe-user/src/components/Layout.tsx·fe-user/src/pages/contracts/HardCopiesPage.tsx·fe-user/src/types/contracts.tssrc/Backend/SolutionErp.Api/Controllers/ContractsController.cssrc/Backend/SolutionErp.Application/Contracts/ContractFeatures.cssrc/Backend/SolutionErp.Application/Contracts/Dtos/ContractDtos.cstests/SolutionErp.Infrastructure.Tests/Application/ContractHardCopyTests.cs
- NO-Mig proof (vế 1/2):
git diff --name-only HEAD~1 HEAD -- '*Migrations*'→ 0 file. ⇒ kỳ vọng__EFMigrationsHistory= 69 GIỮ (vế 2 = đo prod, CHÂN-2). - FE đổi thật ở CẢ 2 app (4 file/app) ⇒ kỳ vọng bundle rotate ×2 (mode-A #69). Nếu FROZEN → phải chứng LM-in-window (mode-B).
CHÂN-1 — CI (Gitea Actions)
- Run #431 (id=544), head_sha
3119cb91, created2026-07-30T00:17:57+07:00 - Poll status: (đang cập nhật bên dưới)
Pre-deploy bundle baseline (chụp LÚC 00:18:48, TRƯỚC khi deploy job chạy — của run #430 761d818)
| App | js | css |
|---|---|---|
| admin.solutions.com.vn | index-BPWQQe4q.js |
index-KS0kL7bG.css |
| eoffice.solutions.com.vn | index-DV3fEpOw.js |
index-Cpp9Qc7C.css |
DỰ ĐOÁN marker — CHỐT TRƯỚC KHI ĐO (chống Goodhart / false-fail)
Đọc source TRƯỚC, dự đoán con số, rồi mới curl. Lệch ⇒ phải truy nguyên nhân, không được hợp-lý-hoá sau.
Tiền đề then chốt: HardCopiesPage import TĨNH/EAGER ở cả 2 App.tsx (import { HardCopiesPage } from '@/pages/contracts/HardCopiesPage', KHÔNG React.lazy) ⇒ code nằm trong index-*.js CHÍNH, không rơi vào lazy-chunk ⇒ curl index bundle là đủ, không phải săn chunk.
| Marker | Source occurrences/app | Trừ đi (comment-strip + type-erase) | DỰ ĐOÁN bundle/app |
|---|---|---|---|
hasSealedCopy |
3 | −1 comment (HardCopiesPage.tsx:194) −1 TS-interface types/contracts.ts:96 (erase lúc compile) |
đúng 1 (c.hasSealedCopy L195; property-name của object JSON KHÔNG bị mangle) |
hard-copies |
12 | −2 comment (HardCopiesPage.tsx:12, Layout.tsx:42/64) |
10 = 1 route App.tsx + 2 queryKey (L74, L278) + 7 staticMap |
⚠️ Control ÂM phải ĐỘC-QUYỀN (bài học #426 — suýt FAIL OAN): brief bảo kiểm coming-soon 0 hit. Grep repo-wide TRƯỚC:
coming-soon VẪN SỐNG HỢP LỆ ở App.tsx:64/57 (route /coming-soon), pages/ComingSoonPage.tsx, và fe-user UserDashboardPage.tsx:1116 (/coming-soon?stage=) — đó là trang đích cho menu skeleton GĐ2 khác, KHÔNG phải Hdc_.
⇒ Đo coming-soon trần = 0 là SAI kỳ vọng, áp máy móc sẽ FAIL OAN.
⇒ Control ÂM ĐÚNG = cặp co-occurrence (độc quyền cho đúng thứ cần chứng vắng):
- ÂM:
Hdc_<X>:"/coming-soon…"→ DỰ ĐOÁN 0 (7 leaf đã re-point, không còn trỏ skeleton) - DƯƠNG:
Hdc_<X>:"/hard-copies?type=→ DỰ ĐOÁN đúng 7 (7 leaf × 1)
CHÂN-1 — CI (Gitea Actions) — ✅ PASS
- Run #431 (id=544) · head_sha
3119cb91·status=success - created
00:17:57→ updated00:23:47⇒ ~5m50s (khớp dải bình thường 5m38–5m59 của arc S159/S161) conclusion=None— bình thường cho endpointtasks(không populate); tinstatus(đã ghi memory).- Chờ tới
successmới đo CHÂN-2/3 (deploy job là step CUỐI; đo lúcrunning= trạng-thái CŨ — bẫy S159 đợt-2).
Test gate — ✅ 590/590, đúng con số brief
Nguồn: web-UI job log …/actions/runs/431/jobs/0/logs (17.913 B).
| Project | Failed | Passed | Skipped | Total |
|---|---|---|---|---|
SolutionErp.Domain.Tests.dll |
0 | 45 | 0 | 45 |
SolutionErp.Infrastructure.Tests.dll |
0 | 545 | 0 | 545 |
| GATE | 0 | 590 | 0 | 590 |
Đối chiếu TÁCH-PHẦN (không chỉ khớp tổng): 45 + 545 = 590 ✅
Delta chứng bằng run liền trước (không tin số tổng trần): run #430 (761d818, W6) = 45 + 542 = 587
⇒ 545 − 542 = +3 Infra, +0 Domain — khớp đúng "3 test PIN" trong subject commit. ✅
CHÂN-3a — Bundle ×2 — ✅ PASS (rotate 4/4)
| App | Asset | PRE (#430) | POST (#431) | KQ |
|---|---|---|---|---|
| admin | js | BPWQQe4q |
BBQKvUMa |
✅ rotate |
| admin | css | KS0kL7bG |
BLUPWD4D |
✅ rotate |
| eoffice | js | DV3fEpOw |
BV5--mPf |
✅ rotate |
| eoffice | css | Cpp9Qc7C |
HIRTruGh |
✅ rotate |
Rotate 4/4 (mode-A #69) — nhất quán với diff: FE đổi thật 4 file/app × 2 app.
Content-Type: application/javascript · admin 1.734.048 B · eoffice 1.650.214 B (real-size, không phải SPA-shell).
Ruột-marker — dự đoán khớp TỪNG ĐỒNG
| Marker | Dự đoán/app | admin | eoffice | KQ |
|---|---|---|---|---|
hasSealedCopy |
đúng 1 | 1 | 1 | ✅ |
hard-copies |
10 | 10 | 10 | ✅ |
DƯƠNG Hdc_<X>:`/hard-copies?type=N |
7 | 7 | 7 | ✅ |
ÂM Hdc_<X>:`/coming-soon |
0 | 0 | 0 | ✅ |
hasSealedCopy=1 (KHÔNG phải 3) chứng dự đoán comment-strip + type-erase đúng: types/contracts.ts:96 là TS-interface ⇒ biến mất lúc compile; L194 là comment ⇒ bị strip; chỉ c.hasSealedCopy (L195) sống.
hard-copies=10 phân rã ĐÚNG như dự đoán: 7 staticMap + 2 queryKey (L74 [hard-copies,{type…}] + L278 invalidateQueries) + 1 route path:/hard-copies.
7 leaf DƯƠNG liệt-kê-toàn-bộ, distinct type 1..7 không trùng không thiếu, giống hệt ở CẢ 2 app:
Hdc_ThauPhu:1 · Hdc_GiaoKhoan:2 · Hdc_NhaCungCap:3 · Hdc_DichVu:4 · Hdc_MuaBan:5 · Hdc_NguyenTacNcc:6 · Hdc_NguyenTacDv:7
🔑 SUÝT FALSE-CLEAN — control ÂM rỗng đọc nhầm thành sạch (tự bắt, đã vá)
Lần đo ĐẦU tao dùng regex Hdc_[A-Za-z]+:"/… (dấu nháy kép) → ÂM ra 0… nhưng DƯƠNG cũng ra 0.
Cùng một hình-dạng regex mà vế DƯƠNG (phải có 7) cũng 0 ⇒ regex chết, không phải map sạch. Nếu chỉ nhìn vế ÂM sẽ báo "✅ 0 hit" = FALSE-CLEAN.
Nguyên nhân: minifier (rolldown) xuất backtick ` chứ không phải ". Vá regex → DƯƠNG 7 / ÂM 0 ⇒ control ÂM giờ MỚI có nghĩa (đã chứng checker còn răng trên chính bundle đó).
⇒ LUẬT: control ÂM chỉ đáng tin khi vế DƯƠNG dùng ĐÚNG hình-dạng regex đó trả về số > 0 mong đợi.
⚠️ coming-soon TRẦN KHÔNG dùng làm control (sẽ FAIL OAN)
Brief ghi "Hdc_ không còn đi kèm coming-soon (0 hit)". Grep repo-wide TRƯỚC khi đo:
coming-soon vẫn sống HỢP LỆ: App.tsx route /coming-soon, pages/ComingSoonPage.tsx, và fe-user UserDashboardPage.tsx:1116 (/coming-soon?stage=) — đích của menu skeleton GĐ2 khác, không liên quan Hdc_.
Đo thật: bare coming-soon = 1 (admin) / 2 (eoffice) — ĐÚNG như kỳ vọng, không phải lỗi.
⇒ Chuỗi dùng-chung chỉ làm marker DƯƠNG; muốn chứng VẮNG phải dùng cặp co-occurrence độc quyền Hdc_<X>:<route> như trên.
Control ÂM 2 tầng (chứng bundle CŨ đã bị ghi đè)
| Probe | http | Content-Type | size |
|---|---|---|---|
admin hash CŨ index-BPWQQe4q.js |
200 | text/html |
900 |
admin hash BỊA index-ZZfakeZZ.js |
200 | text/html |
900 |
user hash CŨ index-DV3fEpOw.js |
200 | text/html |
876 |
user hash BỊA index-ZZfakeZZ.js |
200 | text/html |
876 |
Hash CŨ trả về trùng khít hash BỊA ⇒ file cũ không còn trên đĩa = đã bị ghi đè thật.
Đồng thời chứng 200 trần VÔ NGHĨA (SPA-fallback rewrite /*→index.html) — discriminator sạch là Content-Type, không phải mã 200.
Đối chứng từ phía đĩa (ssh): index-BPWQQe4q.js → OLD-adminjs-GONE. Hai nguồn độc lập khớp.
CHÂN-2 — sqlcmd prod — ✅ PASS
ssh vietreport-vps + sqlcmd -E -S .\\SQLEXPRESS (4-backslash qua ssh), đo SAU khi run #431 success (không đo lúc running — bẫy S159 đợt-2).
| Chỉ số | Kỳ vọng | Đo được | KQ |
|---|---|---|---|
COUNT(*) MenuItems |
142 GIỮ | 142 | ✅ |
COUNT(*) __EFMigrationsHistory |
69 GIỮ | 69 | ✅ |
MAX(MigrationId) |
không đổi | 20260729122015_AddContractSigningPlans (Mig 69, từ W1 #427) |
✅ |
sys.tables (is_ms_shipped=0) |
96 GIỮ | 96 | ✅ |
NO-Mig proof đủ 2 vế: vế 1 = git diff … -- '*Migrations*' 0 file (CHÂN-0) · vế 2 = history 69 KHÔNG nhích + MIGTOP y nguyên. ✅
MenuItems 142 GIỮ đúng bản chất W7: chỉ re-point staticMap phía FE, không đụng seeder menu (7 leaf Hdc_* đã tồn tại sẵn từ đợt trước, W7 chỉ đổi ĐÍCH của chúng).
Chứng minh app pool ĐÃ recycle (điều kiện để 3 số trên có nghĩa)
appcmd list wp → map pid→pool (không đoán "một w3wp nào đó"):
WP "5576" (applicationPool:SolutionErp-Api) ← đúng pool cần
| Mốc | Giờ | Trong cửa-sổ deploy 00:17:57→00:23:47? |
|---|---|---|
API DLL SolutionErp.Api.dll mtime |
00:21:02 | ✅ |
| w3wp pid=5576 (SolutionErp-Api) StartTime | 00:23:20 | ✅ (và SAU mtime DLL 00:21:02 ⇒ nạp DLL MỚI) |
admin index-BBQKvUMa.js LM |
00:22:18 size 1.734.048 | ✅ (size khớp CHÍNH XÁC bản curl về) |
user index-BV5--mPf.js LM |
00:23:14 size 1.650.214 | ✅ (khớp chính xác) |
CHÂN-3b — Smoke API prod
Login admin@solutions.com.vn → accessToken (len 468) ✅ 200.
| # | Call | Kỳ vọng | Đo | KQ |
|---|---|---|---|---|
| a | GET /api/contracts?phase=9&pageSize=5 |
200 | 200 (total=0, items=0) |
✅ mã · ⚠️ field xem dưới |
| b | GET /api/contracts?phase=9&type=1 |
200 | 200 | ✅ |
| c | GET /api/contracts?type=999 |
400 KHÔNG 500 | 400 | ✅ |
| d | control route BỊA /api/contracts-khong-ton-tai |
404 | 404 | ✅ |
(c) body 400 là ValidationProblemDetails THẬT, không phải lỗi chung chung:
{"title":"One or more validation errors occurred.","status":400,"errors":{"type":["The value '999' is invalid."]}}
→ tên field type đích danh ⇒ chặn đúng ở tầng model-binding enum, không phải 500 rơi từ handler. ✅
(d) route bịa trả 404 ⇒ 200 ở (a)/(b) CÓ NGHĨA, không phải catch-all nuốt mọi thứ.
⚠️ hasSealedCopy per-item = KHÔNG KIỂM CHỨNG ĐƯỢC LIVE (không phải PASS, không phải FAIL)
Phép all('hasSealedCopy' in i for i in items) trả True — nhưng N=0 ⇒ all() trên tập RỖNG luôn True. Đây là phép trích RỖNG đọc nhầm thành sạch, y hệt bẫy đã ghi trong memory. Không được tính là ✅.
Truy nguyên nhân bằng ĐO, không suy đoán — sqlcmd prod:
CONTRACTS_TOTAL=0 · CONTRACTS_NOTDELETED=0 · PHASE9=0
⇒ Prod chưa có hợp đồng nào. total=0 là API trả ĐÚNG trên bảng rỗng — không phải IDOR-scope lọc mất, không phải regression của W7. Đã loại trừ khả năng "bug nuốt data" (đó mới là điều đáng sợ nếu bỏ qua).
⇒ Field hasSealedCopy hiện được bảo chứng bằng 3 nguồn KHÁC (không phải bằng payload live):
- Bundle prod có
hasSealedCopy(đo được, CHÂN-3a) — FE đã ship code đọc field. - Gate CI 590/590 gồm 3 test PIN mới
ContractHardCopyTests— có ca LẬT-1-BIẾN (thêm đúng 1 đính kèmpurpose=SealedCopy⇒ cờ false→true) nên không phải test "xanh vì cờ chết cứng". - Đây là giới hạn của môi trường, sẽ tự đóng khi prod có HĐ đầu tiên ở phase 9.
Khuyến nghị (không tự làm — READ-only): khi UAT tạo HĐ thật đầu tiên, re-curl ?phase=9 1 lần để đóng nốt mục này.
VERDICT
| Chân | Nội dung | Kết quả |
|---|---|---|
| CHÂN-0 | push + path-filter + NO-Mig vế 1 | ✅ PASS |
| CHÂN-1 | CI run #431 success · gate 590 (45+545, 0 fail/skip) · delta +3 vs #430 | ✅ PASS |
| CHÂN-2 | MenuItems 142 · MigHistory 69 · MIGTOP giữ · tables 96 · pool recycle chứng bằng pid đúng | ✅ PASS |
| CHÂN-3a | bundle rotate 4/4 · marker khớp dự đoán từng đồng (1 / 10 / 7 / 0) · control ÂM 2 tầng | ✅ PASS |
| CHÂN-3b | smoke 200/200/400/404 | ✅ PASS |
| — | hasSealedCopy per-item live |
⚠️ UNVERIFIED-LIVE (prod 0 HĐ — đã đo, không phải lỗi deploy) |
🟢 VERDICT = PASS (5/5 chân; 1 mục treo do môi trường thiếu dữ liệu, KHÔNG phải lỗi)
Ghi nhận 2 lần tao SUÝT SAI (giữ lại để lần sau không lặp)
- Control ÂM rỗng — regex nháy-kép cho ra ÂM=0 và DƯƠNG=0; nếu chỉ nhìn vế ÂM đã báo "sạch". Vá: minifier dùng backtick. ⇒ Luật: control ÂM chỉ có giá trị khi vế DƯƠNG dùng ĐÚNG hình-dạng regex đó trả về số > 0 mong đợi.
- Dự đoán
type=999của tao SAI — tao suy "không có validator choListContractsQuery(IsInEnumở dòng 43 thuộcCreateContractCommandValidator, khác lệnh) ⇒ enum bind nhận số bừa ⇒ 200". Thực đo 400: ASP.NET Core cóEnumTypeModelBindertự chặn giá trị enum không-định-nghĩa +[ApiController]→ 400 tự động. Brief đúng, tao sai. ⇒ Nhờ ĐO thay vì khẳng định theo suy luận nên không báo sai. Giữ nguyên bài học: suy-luận-từ-code là GIẢ THUYẾT, prod mới là phán quyết.
END sub-cicd-monitor-4 — VERDICT=PASS (CHÂN-0 ✅ · CHÂN-1 ✅ 590 · CHÂN-2 ✅ 142/69 · CHÂN-3a ✅ 4/4 rotate · CHÂN-3b ✅ 200/200/400/404 · 1 mục UNVERIFIED-LIVE do prod 0 HĐ)