14 KiB
cicd-verify ea962b1 — S159 ĐỢT-5 "menu 6-leaf y-chang-Duyệt-NCC + GET /contracts/deleted + bỏ hero"
Ghi-TRỌN-RA-ĐĨA (chống #53). Lead /pause — file này là sản phẩm chính, đọc ở phiên sau qua
/tiep. Đợt-1b5799fcPASS 9/9 · đợt-21a47a61PARTIAL 8/9 · đợt-32a72695PASS 9/9 · đợt-4a2bbcb9PASS 9/9 — 4 file cũ KHÔNG chạm. Bắt đầu: 2026-07-29 ~14:42+07 · cicd-monitor (READ-only)
M1 — Push + phạm vi · ✅ PASS
git log -1 → ea962b1e59432bd6b003836cf0c93bed73732abf
[CLAUDE] Contract+FE-User: dot-5 menu 6-leaf y-chang-Duyet-NCC
+ GET /contracts/deleted + bo hero + "Tong quan quy trinh"
2026-07-29 14:40:10 +0700
git log origin/main..HEAD → EMPTY (đã push)
Range ceeebf3..ea962b1 = 10 file, 173+/371− (net âm vì gỡ hero + gỡ code cũ). Khớp brief.
| File | vai trò |
|---|---|
src/Backend/…/Api/Controllers/ContractsController.cs |
+GET deleted |
src/Backend/…/Application/Contracts/ContractFeatures.cs |
ListDeletedContractsQuery |
src/Backend/…/Application/PurchaseEvaluations/PeSoftDeleteFeatures.cs |
comment |
src/Backend/…/Persistence/DbInitializer.cs |
🔴 seeder: Ct +3 leaf ×7 · Khkk 6 · labelBackfill · reviewKeys |
fe-user/src/pages/UserDashboardPage.tsx |
BỎ HERO + đổi title |
fe-user/src/pages/contracts/MyContractsPage.tsx |
phase/deleted + guard |
fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx |
matrix + type=3 |
fe-user/src/App.tsx · fe-user/src/components/Layout.tsx |
route alias · Ct-regex + staticMap + MenuLeaf-caps |
fe-admin/src/components/Layout.tsx |
staticMap + 2 |
Cả 2 app FE đều sửa ⇒ bundle 2 app đều phải rotate.
M2 — Migration VẮNG là ĐÚNG · ✅ PASS
git diff --name-only ceeebf3..ea962b1 -- '*Migrations*' → 0
BE thêm query + endpoint + seeder, không schema. Mig giữ 68 / 89 bảng. ⇒ Toàn bộ thay đổi menu đi qua SEEDER lúc app khởi động → rủi ro thật nằm ở M6 (seeder-run).
📌 BASELINE TRƯỚC DEPLOY (đo 14:41–14:42+07, run chưa land — pool CHƯA recycle)
Đây là "vế TRƯỚC" để chứng minh seeder thật sự chạy (delta 0→7 rất sắc):
| Phép đo | TRƯỚC |
|---|---|
Ct_%_WfView (MenuItems) |
0 |
Ct_%_Approved |
0 |
Ct_%_Deleted |
0 |
Khkk_% |
5 |
Ct_%_Pending có Label LIKE N'Đang%' |
0 (trên tổng 7 key Pending) |
Dashboard có Label LIKE N'%quy tr%' |
0 |
| MenuItems TỔNG | 119 |
| 38-key set | min=13 max=13 keys=38 |
Bundle trước deploy: admin js CC1pGE1F css DWDbm5As · user js CLfAftby css B6bUSFBt
— khớp baseline hậu-đợt-4 ⇒ hợp lệ.
🔎 M3-pre — Marker kỳ vọng (dự đoán TRƯỚC khi đo, khuôn đợt-4)
UserDashboardPage.tsx (50.142 B — đợt-4 là 63.124 B, giảm đúng với "bỏ hero"):
| Chuỗi | trong UserDashboardPage | vai trò |
|---|---|---|
Tổng quan quy trình |
1 | dương |
Toàn trình 4 giai đoạn |
0 | (hero h2 đã gỡ) |
Quy trình mua sắm |
0 | (eyebrow hero đã gỡ) |
⚠️⚠️ CẢNH BÁO: 1 trong 2 control ÂM của brief KHÔNG HỢP LỆ — đã soi TRƯỚC khi đo
grep -rn toàn fe-user/src + fe-admin/src:
fe-user/src/components/pe/PePipelineStrip.tsx:24: aria-label="Toàn trình 4 giai đoạn"
fe-admin/src/components/pe/PePipelineStrip.tsx:24: aria-label="Toàn trình 4 giai đoạn"
⇒ Chuỗi Toàn trình 4 giai đoạn VẪN SỐNG trong PePipelineStrip (aria-label, thêm từ đợt-2).
Nó KHÔNG độc quyền cho hero ⇒ dự đoán bundle USER = 1, KHÔNG phải 0.
Nếu máy móc áp "phải VẮNG" sẽ báo FAIL OAN — trong khi hero thật sự đã gỡ.
→ Xử lý: coi đây là control KHÔNG dùng được, thay bằng phép đúng ở dưới.
Quy trình mua sắm: grep -rc toàn 2 app = 0 hit ở mọi file ⇒ control ÂM HỢP LỆ,
dự đoán bundle = 0. Đây là phép chứng "hero đã gỡ" đáng tin của lượt này.
(Đúng bài đợt-4: marker phổ-thông/dùng-chung chỉ kết luận "≥1", muốn phép ÂM phải chọn chuỗi ĐỘC QUYỀN.)
🔎 M7-pre — Endpoint mới (đọc code, chốt kỳ vọng)
ContractsController.cs:
[ApiController]
[Route("api/contracts")] // L12
[Authorize] // L13 (class-level)
…
[HttpGet("deleted")] // L28
[Authorize(Policy = "Contracts.Read")] // L29 (chặt hơn [Authorize] trần)
public async Task<ActionResult<PagedResult<ContractListItemDto>>> ListDeleted(…)
⇒ URL thật = GET /api/contracts/deleted (brief ghi /contracts/deleted, thiếu prefix api).
Kỳ vọng 401 khi không token (route tồn tại) · 404 = FAIL (route không tồn tại).
👍 Ghi nhận: có policy method-level chứ không dựa [Authorize] trần — đúng bài 2-tầng authz (#82).
M4 — Poll terminal · ✅ PASS
- id=539 · run_number=426 · head_sha
ea962b1e· created 14:40:15 →status=success14:45:53 - Duration 5m38s (đợt 1-4: 5m59s / 5m44s / 5m56s / 5m40s) — ổn định.
M4b — Test gate · ✅ 562 EXACT
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, 151 ms - SolutionErp.Domain.Tests.dll
Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, 1m23s - SolutionErp.Infrastructure.Tests.dll
Failed! ×0 ⇒ 562. Guard đợt-3 (StillHiddenKeys/ReopenedKeys) nằm trong 517 này, vẫn xanh.
M5 — Bundle · ✅ PASS (đo 14:46:14, sau status=success)
| App | asset | TRƯỚC | SAU | KQ |
|---|---|---|---|---|
| admin | js | CC1pGE1F |
D0sXA0fe |
✅ ROTATE |
| admin | css | DWDbm5As |
DWDbm5As |
FROZEN — lành tính (chứng dưới) |
| user | js | CLfAftby |
Y6dW_5CM |
✅ ROTATE |
| user | css | B6bUSFBt |
6YIAufJR |
✅ ROTATE |
"Bundle ×2 rotate (cả 2 app đổi)" — đạt qua JS cả 2 app ✅.
USER index-Y6dW_5CM.js ct=application/javascript size=1.595.577 LM=14:45:25+07 (in-window)
ADMIN index-D0sXA0fe.js ct=application/javascript size=1.678.823 LM=14:44:29+07 (in-window)
ADMIN index-DWDbm5As.css ct=text/css size= 83.705 LM=14:44:29+07 (in-window) ← FROZEN
🔑 admin-css FROZEN nhưng Last-Modified 14:44:29 NẰM TRONG cửa sổ deploy (14:40:15→14:45:53)
⇒ file đã được ghi lại, chỉ là thay đổi fe-admin lần này là staticMap trong Layout.tsx (JS thuần,
không thêm class Tailwind mới) nên CSS ra byte y hệt. Đúng khuôn #69 mode-B đã dùng ở đợt-4:
frozen + LM-in-window = re-ship giống hệt (khác "bundle cũ kẹt lại" — cái đó LM sẽ cũ).
Control âm 2 tầng: hash CŨ CLfAftby / CC1pGE1F đều → ct=text/html 876/900 B ⇒ bundle đợt-4 đã
biến mất khỏi đĩa = chứng ghi-đè thật.
M3 — Byte-marker · ✅ PASS (dự đoán TRƯỚC khớp ĐO 100%)
| Chuỗi | USER | ADMIN | Kỳ vọng đã chốt trước | KQ |
|---|---|---|---|---|
Tổng quan quy trình |
1 | 0 | dương ở user (chỉ fe-user có) | ✅ |
Quy trình mua sắm |
0 | 0 | ÂM (control HỢP LỆ) | ✅ |
Toàn trình 4 giai đoạn |
1 | 1 | dự đoán 1, KHÔNG phải 0 | ✅ đúng dự đoán |
🔑 CỨU MỘT FALSE-FAIL — điểm quan trọng nhất lượt này:
Brief yêu cầu control âm Toàn trình 4 giai đoạn phải VẮNG. Nếu áp máy móc thì lượt này FAIL OAN,
vì đo ra 1. Nhưng tao đã soi TRƯỚC KHI ĐO và biết lý do:
fe-user/src/components/pe/PePipelineStrip.tsx:24: aria-label="Toàn trình 4 giai đoạn"
fe-admin/src/components/pe/PePipelineStrip.tsx:24: aria-label="Toàn trình 4 giai đoạn"
Chuỗi này không độc quyền cho hero — nó còn sống trong PePipelineStrip (thêm từ đợt-2) dưới dạng
aria-label. Hero thật sự đã gỡ (UserDashboardPage chứa 0 lần; file giảm 63.124 → 50.142 B).
⟹ Kết luận: control này KHÔNG DÙNG ĐƯỢC, thay bằng Quy trình mua sắm (eyebrow hero) —
grep -rc toàn 2 app = 0 hit ở mọi file ⇒ độc quyền ⇒ bundle = 0 ⇒ chứng hero đã gỡ.
Luật rút ra (nối tiếp đợt-4): trước khi nhận một chuỗi làm control ÂM, phải grep repo-wide xác nhận nó ĐỘC QUYỀN cho thứ mình muốn chứng vắng. Chuỗi dùng-chung chỉ làm marker DƯƠNG.
M6 — 🔴 SEEDER-RUN sau restart · ✅ PASS (delta khớp từng đơn vị)
Bước 0 — chứng minh restart THẬT (khuôn 3 chân kiềng, đợt-3):
| Bằng chứng | Giá trị |
|---|---|
Dòng ĐẦU log solution-erp-20260729.log |
14:45:37.283 +07:00 [INF] Applying migrations... |
w3wp StartTime mới nhất |
14:45:32 (và 14:45:31) |
Deploy xoá logs/ ⇒ log mở lại 14:45:37 = boot mới; DbInitializer đã chạy. Đo SAU mốc này.
| Phép đo | TRƯỚC (14:41) | SAU (14:46) | Δ | KQ |
|---|---|---|---|---|
Ct_%_WfView |
0 | 7 | +7 | ✅ |
Ct_%_Approved |
0 | 7 | +7 | ✅ |
Ct_%_Deleted |
0 | 7 | +7 | ✅ |
Khkk_% |
5 | 7 | +2 | ✅ (6 leaf + G1) |
Ct_%_Pending nhãn N'Đang%' |
0/7 | 7/7 | +7 | ✅ labelBackfill ăn |
Dashboard nhãn N'%quy tr%' |
0 | 1 | +1 | ✅ |
| MenuItems TỔNG | 119 | 142 | +23 | ✅ |
| root | — | 12 | 0 | ✅ không double-insert |
DUPKEYS |
— | 0 | — | ✅ |
🔑 Toán khớp từng đồng: +23 = 7 (WfView) + 7 (Approved) + 7 (Deleted) + 2 (Khkk) = 23 ✅.
Không có row thừa nào ⇒ seeder insert ĐÚNG tập, không nhân đôi.
Permission cho key MỚI (nhánh insert của reviewKeys — key chưa có row nên insert ăn):
F_NEWPERM min=13 max=13 keys=21 (21 = 7 WfView + 7 Approved + 7 Deleted)
✅ Mỗi key mới đủ 13/13 role canread. Đây đúng như ghi chú code đợt-3: nhánh grant là
skip-existing nên chỉ ăn với key MỚI — lần này key hoàn toàn mới ⇒ grant thành công (khác
tình huống đợt-2/3 nơi row false sẵn có không được nâng).
M7 — Endpoint mới GET /api/contracts/deleted · ✅ PASS (401, KHÔNG 404)
| Probe | HTTP | Đọc |
|---|---|---|
GET /api/contracts/deleted (no token) |
401 | ✅ route TỒN TẠI, auth chặn đúng |
GET /api/contracts/zzznope (route bịa) |
404 | ✅ control — 404 vẫn hoạt động ⇒ 401 ở trên CÓ NGHĨA |
GET /api/contracts (route cũ đã biết) |
401 | ✅ nhất quán |
🔑 Có control 404 nên 401 không thể là "server trả 401 cho mọi thứ" — discriminator sạch. (Nếu chỉ đo mỗi 401 thì không loại trừ được khả năng catch-all.)
M8 — Restart-durability lần 3 · ✅ PASS
| Phép đo | Kết quả SAU restart | KQ |
|---|---|---|
| 38-key set | min=13 max=13 keys=38 · totcr=494/494 | ✅ GIỮ (lần 3) |
Control ÂM Hrm%/Off%/Personal non-Admin canread |
0 | ✅ revoker CÒN SỐNG |
Pe% |
197 / 185 | ✅ |
Hdc_* nhãn - Bảng cứng (%-%) |
7 | ✅ nguyên |
3 root ẩn — SUM(IsVisible) |
0 | ✅ cả 3 vẫn ẩn |
⚠️ Lưu ý hình-dạng-truy-vấn (quan trọng cho lần sau): đợt này Ct_* đẻ thêm 21 key mới
(_WfView/_Approved/_Deleted). Nếu vẫn dùng LEFT(MenuKey,3)='Ct_' như các đợt trước thì
"38-key set" sẽ thành 59 key và không so sánh được với 4 đợt trước. Nên tao cố ý loại
3 hậu tố mới khỏi mệnh đề để giữ đúng tập 38 gốc:
WHERE MenuKey IN (10 key Master-set + 'Contracts')
OR (LEFT(MenuKey,3)='Ct_'
AND MenuKey NOT LIKE 'Ct[_]%[_]WfView'
AND MenuKey NOT LIKE 'Ct[_]%[_]Approved'
AND MenuKey NOT LIKE 'Ct[_]%[_]Deleted')
⇒ ra đúng 38 key, so được apples-to-apples với đợt 2/3/4. (Bài học query-shape từ đợt-1: khi tập dữ liệu nở ra, phải neo lại phạm vi truy vấn, nếu không con số "tăng" sẽ bị đọc nhầm thành drift.)
M9 — Smoke · ✅ PASS 4/4
/health/live 200 Healthy · /health/ready 200 Healthy · admin 200 · eoffice 200
VERDICT: PASS — 9/9
| # | Mục | KQ |
|---|---|---|
| 1 | Push + range 10 file (173+/371−, net âm do bỏ hero) | ✅ |
| 2 | Migration VẮNG là ĐÚNG | ✅ |
| 3 | Run #426 success 5m38s | ✅ |
| 4 | Test gate 562 (45+517, 0 fail) | ✅ |
| 5 | Bundle: JS rotate cả 2 app; admin-css frozen chứng minh lành tính bằng LM-in-window | ✅ |
| 6 | Marker Tổng quan quy trình=1 · control ÂM HỢP LỆ Quy trình mua sắm=0 |
✅ |
| 7 | SEEDER-RUN: 0→7 ×3 · Khkk 5→7 · Pending-label 7 · Dashboard-label 1 · tổng 119→142 (+23 khớp) · root 12 · DUPKEYS 0 · 21 key mới 13/13 | ✅ |
| 8 | Endpoint /api/contracts/deleted = 401 (route sống) + control bịa 404 |
✅ |
| 9 | Durability lần 3: 38-key 494/494 · control âm 0 · Pe 197/185 · Hdc nhãn 7 · 3 root ẩn | ✅ |
Không có cờ đỏ. Không khuyến nghị rollback.
⚠️ 1 điểm cần owner biết (KHÔNG phải lỗi deploy)
Control âm Toàn trình 4 giai đoạn trong brief không dùng được — chuỗi vẫn sống hợp lệ ở
PePipelineStrip.tsx (aria-label, cả 2 app). Hero đã gỡ đúng (chứng bằng Quy trình mua sắm=0 +
UserDashboardPage 63.124→50.142 B). Lần sau nếu muốn phép ÂM cho hero, dùng Quy trình mua sắm.
Baseline mới: admin js D0sXA0fe / css DWDbm5As(frozen hợp lệ) · user js Y6dW_5CM / css 6YIAufJR ·
gate 562 · Mig 68 / 89 bảng · MenuItems 142 · root 12 · 38-key 13/13 · Ct_ leaf mới 21 key × 13.
Arc S159 — 5 đợt
đợt-1 PASS 9/9 → đợt-2 PARTIAL 8/9 (bắt regression revoker ăn 447/494 row) → đợt-3 PASS 9/9 (đóng regression, 3 chân kiềng) → đợt-4 PASS 9/9 (durability lần 2) → đợt-5 PASS 9/9 (durability lần 3 + seeder-insert delta khớp toán + endpoint mới sống).