12 KiB
cicd-verify 1a47a61 — S159 ĐỢT-2 "tab strip 4 GĐ + menu 4-GĐ + đánh số 01-07 nhóm HĐ"
Ghi-TỪNG-MỤC-trong-lúc-làm (chống #53). File = SẢN PHẨM CHÍNH; return chỉ tóm tắt. Đợt-1
b5799fc(PASS 9/9) ở filecicd-verify-b5799fc.md— KHÔNG chạm. Bắt đầu: 2026-07-29 ~12:07+07 · cicd-monitor (READ-only)
M1 — Push + phạm-vi commit · ✅ PASS
git log -1 → 1a47a61c21b62329c76bdef14a5daa9630c66db3
[CLAUDE] FE-User: tab strip 4 giai doan tren phieu PE + menu 4-GD
+ danh so 01-07 nhom HD (UAT anh Kiet) · 2026-07-29 12:06:19 +0700
git log origin/main..HEAD → EMPTY (đã push)
Range b5799fc..1a47a61 = 2 commit / 10 file; trong đó 2 file là
.claude/workflows/runs/** (ignored) ⇒ 8 file CODE, 190+/18− — khớp CHÍNH XÁC brief.
| File code | ± | loại |
|---|---|---|
fe-admin/src/components/pe/PePipelineStrip.tsx |
+65 | MỚI |
fe-user/src/components/pe/PePipelineStrip.tsx |
+65 | MỚI (mirror) |
fe-admin/src/components/pe/PeDetailTabs.tsx |
+3 | wire strip |
fe-user/src/components/pe/PeDetailTabs.tsx |
+3 | wire strip |
fe-user/src/components/Layout.tsx |
+5 | menu 4-GĐ |
fe-user/src/pages/UserDashboardPage.tsx |
18 ± | đổi nhãn GĐ2/GĐ3 |
src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs |
+7 | key mới |
src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs |
+42/− | seeder-only |
⇒ BE có đụng nhưng 0 controller/service, 0 migration — chỉ hằng-số key + seeder.
fe-admin CÓ sửa ⇒ admin bundle PHẢI rotate (khác đợt-1 nơi admin rotate là do #69 mode-A).
Mirror md5 (kiểm chứng "mirror md5-identical" trong brief):
ef7b782409d02b5a6a2f1bf4c2e97b42 fe-admin/src/components/pe/PePipelineStrip.tsx
ef7b782409d02b5a6a2f1bf4c2e97b42 fe-user/src/components/pe/PePipelineStrip.tsx
✅ BYTE-IDENTICAL — 2 app dùng đúng 1 nội dung, không lệch bản.
M2 — Migration VẮNG là ĐÚNG · ✅ PASS
git diff --name-only b5799fc..1a47a61 -- '*Migrations*' → 0 dòng
BE-change chỉ là MenuKeys.cs (const) + DbInitializer.cs (seeder runtime) ⇒ không sinh schema.
Mig prod kỳ vọng đứng nguyên 68 / 89 bảng. Thay đổi menu đi qua SEEDER lúc app khởi động,
nên rủi ro thật nằm ở M7 (double-insert / đè nhãn), KHÔNG ở __EFMigrationsHistory.
Pre-deploy bundle snapshot (chốt TRƯỚC khi deploy land)
Đo 2026-07-29 12:07:19+07 (run #423 vừa start, chưa tới step deploy):
| App | js | css | HTTP |
|---|---|---|---|
| admin | index-BHodkPNV.js |
index-DiNJjaoo.css |
200 |
| user | index-0GO8mgrG.js |
index-BARVqkP-.css |
200 |
✅ Khớp CHÍNH XÁC baseline hậu-đợt-1 ⇒ baseline hợp lệ, đợt-2 chưa land.
M-src — Marker kỳ vọng, đếm trên SOURCE (chốt trước khi soi bundle)
Đếm BYTES utf-8. (Bài học đợt-1: occurrence trong COMMENT bị minifier strip ⇒ đối chiếu occurrence-CODE.)
| Chuỗi | UserDashboardPage | PePipelineStrip (user) | PePipelineStrip (admin) | vai trò |
|---|---|---|---|---|
Kế hoạch ký kết HĐ |
2 | 2 | 2 | dương |
Hợp đồng cứng |
2 | 3 | 3 | dương |
Toàn trình 4 giai đoạn |
1 | 1 | 1 | dương (giữ từ đợt-1) |
Đề xuất ký kết hợp đồng |
0 | 0 | 0 | ÂM — nhãn cũ đợt-1 đã đổi |
Ký cứng & phát hành |
0 | 0 | 0 | ÂM |
M3 — Poll terminal · ✅ PASS
- id=536 · run_number=423 · head_sha
1a47a61c· created 12:06:25 →status=success12:12:09 - Duration 5m44s (đợt-1 5m59s) — trong dải bình thường.
- kề trên: #422 id=535
b5799fcsuccess (đợt-1) ⇒ hai run tách bạch, không nhầm.
M3b — Test gate · ✅ 562 EXACT
…/actions/runs/423/jobs/0/logs (17.737 ký tự):
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, 167 ms - SolutionErp.Domain.Tests.dll
Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, 1m26s - SolutionErp.Infrastructure.Tests.dll
Failed! ×0. 45+517 = 562, khớp cả TỔNG lẫn TÁCH-PHẦN. Delta 0 — đúng cho commit không kèm test mới.
M4 — Bundle rotate · ✅ PASS 4/4 (đo 12:12:38+07)
| App | asset | TRƯỚC (hậu đợt-1) | SAU | KQ |
|---|---|---|---|---|
| admin | js | BHodkPNV |
D6Pz7nfV |
✅ ROTATE (bắt buộc — fe-admin CÓ sửa) |
| admin | css | DiNJjaoo |
B1ErASuI |
✅ ROTATE (utility mới của strip) |
| user | js | 0GO8mgrG |
Bpb8WEuY |
✅ ROTATE |
| user | css | BARVqkP- |
7oAYFlzM |
✅ ROTATE |
⚠️ Khác đợt-1 ở chỗ: đợt-1 admin-css FROZEN vì 0 admin-src; đợt-2 admin-css rotate vì strip mang class mới ⇒ hình-dạng rotate nhất quán với nội dung diff, không phải nhiễu #69.
Artifact thật:
USER index-Bpb8WEuY.js ct=application/javascript size=1.585.946 LM=12:11:43+07 (in-window)
ADMIN index-D6Pz7nfV.js ct=application/javascript size=1.676.677 LM=12:10:45+07 (in-window)
M5 — Byte-marker 2 bundle · ✅ PASS (6/6 dương EXACT · 4/4 âm VẮNG)
| Chuỗi | USER bundle | ADMIN bundle | Kỳ vọng |
|---|---|---|---|
Kế hoạch ký kết HĐ |
2 | 1 | dương ✅ |
Hợp đồng cứng |
2 | 1 | dương ✅ |
Toàn trình 4 giai đoạn |
2 | 1 | dương ✅ |
Đề xuất ký kết hợp đồng |
0 | 0 | ÂM ✅ (nhãn đợt-1 đã đổi) |
Ký cứng & phát hành |
0 | 0 | ÂM ✅ |
🔑 Quy luật đếm rút ra (nối tiếp bài học comment-strip đợt-1): bundle-count = SỐ MODULE chứa chuỗi (bundler pool hằng-số chuỗi trong 1 module → nhiều occurrence cùng file gộp còn 1):
- USER = 2 module chứa marker (
PePipelineStrip.tsx+UserDashboardPage.tsx) ⇒ 2 ✅ - ADMIN = 1 module (
PePipelineStrip.tsx;Layout.tsxchỉ thêm route'/dashboard', KHÔNG chứa nhãn) ⇒ 1 ✅
Đúng cho cả 3 marker × 2 app = 6/6, tức "đúng-BỘI-SỐ" chứ không chỉ "tìm thấy ≥1".
Kiểm chứng phụ: grep -rn 2 chuỗi ÂM trên toàn fe-user/src + fe-admin/src = 0 hit ⇒ marker âm vắng ở bundle là do mã đã gỡ, không phải do đọc sai.
Control ÂM 2 tầng (cả 2 app): hash CŨ (0GO8mgrG / BHodkPNV) và hash bịa đều trả 200 nhưng
ct=text/html 876 B (user) / 900 B (admin) ⇒ bundle cũ biến mất khỏi đĩa = chứng ghi-đè thật.
M6 — Smoke · ✅ PASS 4/4
/health/live 200 Healthy · /health/ready 200 Healthy · admin 200 · eoffice 200
M7 — Menu-data prod sau app-pool recycle · ✅ 5/5 mục được giao
(a) Root count — chống double-insert: SELECT COUNT(*) FROM MenuItems WHERE ParentKey IS NULL = 12 ✅ (không 13/14).
Bổ sung: DUPKEYS=0 (không key nào lặp) · MENUTOTAL=107.
(b) 2 root mới đúng vị trí:
KeHoachKyKet ord=26 IsVisible=1 ✅
HopDongCung ord=32 IsVisible=1 ✅
Permission 2 root mới: mỗi key 13 rows / 13 canread — đúng 13 role × 1, không nhân đôi ✅
(c) 3 root vẫn ẩn: Hrm ord28 vis=0 · Off ord29 vis=0 · Personal ord30 vis=0 ✅
(d) 2 root đã rename (đối chiếu prefix, KHÔNG so byte VN qua console): PurchaseEvaluations và
Contracts đều bắt đầu Duy… (= "Duyệt NCC" / "Duyệt hợp đồng") ✅
(e) 7 nhãn đánh số CÒN NGUYÊN (backfill silent vì equal):
Ct_ThauPhu 01. · Ct_GiaoKhoan 02. · Ct_NhaCungCap 03. · Ct_DichVu 04.
Ct_MuaBan 05. · Ct_NguyenTacNcc 06. · Ct_NguyenTacDv 07.
(f) Control ÂM Pe%: rows=197 canread=185 ✅ EXACT (seeder không đụng vùng PE — đúng comment code).
🔴 M8 — PHÁT HIỆN NGOÀI CHECKLIST: grant 38-key của ĐỢT-1 ĐÃ BỊ REVOKE · ❌ FAIL
Trong lúc soi Serilog để chứng minh seeder có chạy thật, bắt được dòng:
2026-07-29 12:12:00.028 +07:00 [INF] Revoked 9 permission rows — … HRM/Văn phòng số/Cá nhân/
Hợp đồng/Danh mục khỏi non-Admin (chỉ Admin thấy — S58 + S92)
⇒ đi đo lại grant-set 38 key mà đợt-1 đã áp tay lúc 11:15:
| Thời điểm | Đo được |
|---|---|
| 11:46 (đợt-1, TRƯỚC khi pool restart) | 38 key · min=max=13/13 · tổng 494 canread |
| 12:14 (sau restart đợt-2) | 38 key · min=1 max=2 · tổng 47/494 — 0 key nào còn 13 |
Phân bố còn lại: Admin=38 · Procurement=9 · 11 role còn lại = 0. ⇒ mất 447/494 canread.
Nguyên nhân (đọc code, không suy đoán) — DbInitializer.cs:2203-2236
RevokeTemporarilyHiddenModulesAsync, chạy mỗi lần app khởi động, KHÔNG có cờ gate:
.Where(p => p.RoleId != adminRoleId
&& (p.MenuKey.StartsWith("Hrm") || p.MenuKey.StartsWith("Off")
|| p.MenuKey == MenuKeys.Personal
|| p.MenuKey == MenuKeys.Contracts || p.MenuKey.StartsWith("Ct_")
|| p.MenuKey == MenuKeys.Master || p.MenuKey == MenuKeys.Suppliers
|| p.MenuKey == MenuKeys.Projects|| p.MenuKey == MenuKeys.Departments
|| p.MenuKey.StartsWith("Catalog"))
&& (p.CanRead || p.CanCreate || p.CanUpdate || p.CanDelete))
… row.CanRead = false; row.CanCreate = false; row.CanUpdate = false; row.CanDelete = false;
Tập revoke TRÙNG KHỚP CHÍNH XÁC grant-set 38 key (Contracts + Ct_* + Master/Suppliers/Projects/
Departments/Catalog*). ⇒ Grant tay 11:15 về bản chất KHÔNG BỀN: mỗi lần app restart là bị gỡ lại.
Đây đúng mẫu feedback_wipe_durability_check_reseed — nhưng CHIỀU NGƯỢC: data-change thủ công bị
seeder/revoker ungated ăn mất, thay vì seed ungated tự mọc lại.
⚠️ Tự đính chính đợt-1 (KHÔNG sửa file đợt-1 theo yêu cầu): mục M7 file cicd-verify-b5799fc.md
đo lúc 11:46, mà pool restart của run #422 xảy ra ~11:49 ⇒ phép đo đó nằm TRƯỚC recycle,
nên câu "App pool recycle không hề đụng menu-data" trong file đợt-1 chưa được chứng minh và nay
đã bị bác. 3 root ẩn + Pe% 197/185 vẫn đúng (nằm ngoài / miễn nhiễm), chỉ phần grant 38-key sai.
(Hrm/Off/Personal bị revoke cũng vô hại vì chủ đích chính là ẩn chúng.)
Vì sao log chỉ có 1 dòng Revoked: step deploy Remove-Item …\api\* xoá luôn thư mục logs,
nên log hiện chỉ chứa từ 12:12 trở đi ⇒ không thể dùng log để quy trách nhiệm cho restart 11:49 hay 12:12;
điều chắc chắn là revoker ungated và trạng thái HIỆN TẠI đã mất grant.
Khuyến nghị (KHÔNG tự sửa — READ-only, escalate em main):
- Grant 38-key muốn BỀN thì phải sửa code
RevokeTemporarilyHiddenModulesAsync(thu hẹp tập, hoặc thêm env-flag gate), không áp SQL tay — SQL tay sẽ chết ở lần deploy kế. - Sau khi sửa, nghiệm thu bắt buộc bằng restart THẬT rồi mới đo (không đo trước recycle).
- Không rollback
1a47a61— regression này không do đợt-2 gây ra, đợt-2 ship đúng.
VERDICT: PARTIAL — 8/9
| # | Mục | KQ |
|---|---|---|
| 1 | Push + range 8 file code 190+/18− + mirror md5 identical | ✅ |
| 2 | Migration VẮNG là ĐÚNG (0 file *Migrations*) |
✅ |
| 3 | Run #423 success 5m44s | ✅ |
| 4 | Test gate 562 (45+517, 0 fail) | ✅ |
| 5 | Bundle rotate 4/4 (admin js+css bắt buộc đổi, user js+css) | ✅ |
| 6 | 3 marker dương USER + 3 dương ADMIN, count = số-module EXACT | ✅ |
| 7 | 2 control ÂM vắng ở cả 2 bundle + control hash-cũ/bịa text/html |
✅ |
| 8 | Smoke health ×2 + FE ×2 · menu-data 5/5 mục được giao · 0 double-insert (root=12, dupkeys=0) | ✅ |
| 9 | Grant 38-key đợt-1 sống sót recycle | ❌ FAIL — còn 47/494, mất 447 |
Đợt-2 (1a47a61) tự nó SHIP ĐÚNG HOÀN TOÀN — không khuyến nghị rollback.
Cờ đỏ số 9 là hệ quả cấu trúc của revoker ungated có sẵn, chỉ lộ ra khi app restart; cần fix bằng CODE.
Baseline mới sau đợt-2: admin js D6Pz7nfV / css B1ErASuI · user js Bpb8WEuY / css 7oAYFlzM;
gate 562; Mig 68 / 89 bảng (không đụng); menu root = 12.