11 KiB
cicd-verify 2a72695 — S159 ĐỢT-3 "gỡ nhánh S92 khỏi revoker + skeleton menu GĐ2/GĐ4 + ComingSoonPage"
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) · đợt-21a47a61(PARTIAL 8/9, FAIL mục-9 durability) — 2 file cũ KHÔNG chạm. Bắt đầu: 2026-07-29 ~12:35+07 · cicd-monitor (READ-only)🎯 Mục 6(a) là phép PASS/FAIL của cả arc: grant 38-key có SỐNG SÓT restart không. Đúng bài tao tự đính chính đợt-2: phải đo SAU
status=successVÀ SAU thời điểm pool recycle.
M1 — Push + phạm vi · ✅ PASS
git log -1 → 2a7269581c96bd0d3ce939b4dceaeb847df76a89
[CLAUDE] Infra: go nhanh S92 khoi revoker (owner supersede: hien thi het)
+ skeleton menu GD2/GD4 + ComingSoonPage x2 · 2026-07-29 12:34:15 +0700
git log origin/main..HEAD → EMPTY (đã push)
Range 1a47a61..2a72695 = 15 file, 466+/45−. Trong đó 9 file CODE:
| File | vai trò |
|---|---|
src/Backend/…/Persistence/DbInitializer.cs |
🔴 gỡ nhánh [S92] khỏi revoker + grant bền vào reviewKeys |
tests/…/Application/AdminOnlyModulesRevokeTests.cs |
REGRESSION-GUARD mới |
tests/…/Application/ProcurementMasterAccessSeedTests.cs |
cập nhật theo spec-change |
fe-admin/src/pages/ComingSoonPage.tsx · fe-user/…/ComingSoonPage.tsx |
MỚI ×2 |
fe-admin/src/App.tsx · Layout.tsx · fe-user/src/App.tsx · Layout.tsx |
route + menu 2 app |
6 file còn lại = .claude/** (memory + run-trace, ignored bởi 7-glob).
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 1a47a61..2a72695 -- '*Migrations*' → 0
BE-change = seeder + revoker logic (runtime), không sinh schema. Mig giữ 68 / 89 bảng.
📌 BASELINE TRƯỚC RESTART (đo 12:35:47+07, run #424 vừa start — pool CHƯA recycle)
Đây là "vế trước" của phép durability. Ghi lại để đối chứng sau restart:
| Phép đo | Giá trị TRƯỚC restart |
|---|---|
| 38-key set (per-key canread) | min=13 max=13 keys=38 ✅ (lead re-grant 447 đã ăn) |
Khkk_% permission rows |
65 (= 5 key × 13 role) |
Hdc_% permission rows |
91 (= 7 key × 13 role) |
| Tổng skeleton | 156 ✅ khớp con số lead báo |
Control âm Hrm%/Off%/Personal non-Admin canread |
0 |
Bundle trước deploy: admin js D6Pz7nfV css B1ErASuI · user js Bpb8WEuY css 7oAYFlzM
— khớp CHÍNH XÁC baseline hậu-đợt-2 ⇒ baseline hợp lệ, đợt-3 chưa land.
M3 — Poll terminal · ✅ PASS
- id=537 · run_number=424 · head_sha
2a726958· created 12:34:22 →status=success12:40:18 - Duration 5m56s (đợt-1 5m59s · đợt-2 5m44s) — ổn định.
M3b — Test gate · ✅ 562 EXACT
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, 200 ms - SolutionErp.Domain.Tests.dll
Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, 1m36s - SolutionErp.Infrastructure.Tests.dll
Failed! ×0. 45+517 = 562 — giữ nguyên, đúng kỳ vọng: 3 test được SỬA theo spec-change
(không thêm test mới) ⇒ tổng không đổi. Regression-guard nằm TRONG test cũ.
Nội dung guard (đọc diff, không suy đoán) — AdminOnlyModulesRevokeTests.cs tách 2 vế:
StillHiddenKeys=Hrm/Off/Personal→ sau revoke PHẢICanRead=falseReopenedKeys=Contracts/Ct_ThauPhu/Master/Suppliers/Catalogs/CatalogWorkItems→ sau revoke PHẢI GIỮCanRead=true⇒ ai re-add nhánh[S92]vào predicate là test ĐỎ ngay. Guard có RĂNG thật (assert 2 chiều).
Và DbInitializer.cs: nhánh || p.MenuKey == MenuKeys.Contracts || p.MenuKey.StartsWith("Ct_") || Master/Suppliers/Projects/Departments/Catalog* đã GỠ khỏi predicate revoker; reviewKeys
+ContractMenuKeys() +MasterMenuKeys() +KhkkKeys() +HdcKeys().
⚠️ Ghi chú code nêu rõ nhánh grant là skip-existing ⇒ trên prod row false KHÔNG tự nâng;
prod dựa vào re-grant SQL tay (đã chạy) + revoker không lật nữa. Đúng: phép 6(a) dưới đây kiểm chính điều đó.
M4 — Bundle rotate · ✅ PASS 4/4 (đo 12:40:45+07, sau status=success)
| App | asset | TRƯỚC | SAU | KQ |
|---|---|---|---|---|
| admin | js | D6Pz7nfV |
CC1pGE1F |
✅ |
| admin | css | B1ErASuI |
DWDbm5As |
✅ |
| user | js | Bpb8WEuY |
DeAg7MWm |
✅ |
| user | css | BQIZ-rUe ← 7oAYFlzM |
BQIZ-rUe |
✅ |
Cả 2 app đều sửa (App.tsx + Layout.tsx + ComingSoonPage.tsx mới) ⇒ rotate 4/4 nhất quán với diff.
🔴 M6 — RESTART-DURABILITY (mục CHÍNH của cả arc) · ✅ PASS
Bước 0 — CHỨNG MINH pool ĐÃ recycle THẬT (không đo sớm như đợt-2)
| Bằng chứng | Giá trị |
|---|---|
w3wp PID 5508 StartTime |
12:39:56 — nằm trong cửa sổ deploy (12:34:22→12:40:18) |
SolutionErp.Api.dll LastWriteTime |
12:37:38 — binary mới đã copy |
Dòng ĐẦU của logs\solution-erp-20260729.log |
2026-07-29 12:40:03.239 +07:00 [INF] Applying migrations... |
| Log LastWriteTime | 12:40:37 |
🔑 Vì step deploy Remove-Item …\api\* xoá cả logs/, việc dòng đầu log là 12:40:03 chứng minh
file được tạo lại SAU deploy ⇒ đây đúng là lần khởi động mới, và DbInitializer (seeder + revoker)
đã chạy trong lần đó. Mọi số dưới đây đo SAU thời điểm này — sửa đúng lỗi đo-sớm của đợt-2.
(a) 38-key set — PHÉP PASS/FAIL CỦA CẢ ARC · ✅ PASS
A_POST38 min=13 max=13 keys=38 totcr=494
| Mốc | min/max | tổng canread |
|---|---|---|
| Đợt-2 SAU restart (#423) — hỏng | 1 / 2 | 47 / 494 |
| Đợt-3 TRƯỚC restart (12:35:47, sau re-grant tay) | 13 / 13 | 494 / 494 |
| Đợt-3 SAU restart (#424, đo ~12:41) | 13 / 13 | 494 / 494 ✅ |
⇒ Grant 38-key SỐNG SÓT app-pool recycle. Chính phép đo này ở đợt-2 cho ra 0 key còn 13; nay giữ nguyên 38/38 key ở 13/13. Fix của lead ăn thật, không phải trùng hợp — vì: (i) đã chứng minh có restart thật ở Bước 0, (ii) predicate revoker đã gỡ nhánh S92, (iii) control (c) cho thấy hàm revoker vẫn chạy chứ không chết.
(b) Skeleton GĐ2/GĐ4 · ✅ PASS
B_SKEL Khkk=65 Hdc=91 tot=156 (Permissions rows)
B2_SKELCR min=13 max=13 keys=12 (mỗi key đủ 13 role canread)
65 = 5 key × 13 · 91 = 7 key × 13 · tổng 156 ✅ khớp con số lead báo; 12 key × 13 = 156 nhất quán.
(c) Control ÂM — revoker CÒN SỐNG · ✅ PASS
C_CTRL hrmoff_nonadmin_cr = 0
Hrm%/Off%/Personal với role ≠ Admin vẫn canread = 0 ⇒ gỡ nhánh S92 KHÔNG làm chết cả hàm;
vế StillHiddenKeys vẫn thực thi đúng. (Đây là phép chống "Goodhart": nếu ai đó vô hiệu hoá luôn
revoker thì (a) cũng xanh — nhưng (c) sẽ đỏ. (a) xanh và (c) xanh mới là fix ĐÚNG.)
(d) Control Pe_% · ✅ PASS
D_PE rows=197 cr=185 (dùng LIKE 'Pe%' — đúng hình-dạng query gốc)
M7 — Log seeder sau restart · ✅ PASS (khai đầy đủ)
findstr "Revoked" trên log mới ⇒ KHÔNG có dòng nào. Đây là kết quả ĐÚNG chứ không phải thiếu dữ liệu:
RevokeTemporarilyHiddenModulesAsync chỉ ghi log khi rows.Count > 0; nay tập ẩn chỉ còn
Hrm/Off/Personal và chúng đã ở canread=0 từ trước ⇒ 0 row cần lật ⇒ im lặng. Khớp control (c)=0.
So đợt-2: 447 row bị lật → nay 0.
M5 — Byte-marker ComingSoonPage trên 2 bundle · ✅ PASS
Source ComingSoonPage.tsx 2 app đều 2.553 B (mirror): chứa Phân hệ đang được triển khai ×1
Quay lại×1; không chứaSắp triển khai.
| Chuỗi | USER bundle | ADMIN bundle | KQ |
|---|---|---|---|
Phân hệ đang được triển khai |
1 | 1 | ✅ dương cả 2 (chính là ComingSoonPage mới) |
Quay lại |
4 | 4 | ✅ dương cả 2 |
Sắp triển khai |
2 | 1 | ✅ dương cả 2 (nhãn GĐ có sẵn, không phải ComingSoonPage) |
Artifact thật + in-window:
USER index-DeAg7MWm.js ct=application/javascript size=1.587.996 LM=12:39:51+07
ADMIN index-CC1pGE1F.js ct=application/javascript size=1.678.733 LM=12:38:56+07
Control âm: hash CŨ (Bpb8WEuY / D6Pz7nfV) đều rơi về ct=text/html 876 B / 900 B
⇒ bundle đợt-2 đã biến mất khỏi đĩa = chứng ghi-đè thật.
M8 — Smoke · ✅ PASS 4/4
/health/live 200 Healthy · /health/ready 200 Healthy · admin 200 · eoffice 200
M9 — Menu-tree skeleton · ✅ PASS 5/5
MENU Khkk=5 Hdc=7 (MenuItems rows)
ROOT=12 DUPKEYS=0
Hrm=0 Off=0 Personal=0 (IsVisible)
NUM(label '01.'-'07.')=14 → NUMSPLIT ct=7 hdc=7 other=0
Cấu trúc Khkk_* đúng phân cấp thiết kế:
KeHoachKyKet (root)
└── Khkk_G1 ord1 "1. Kế hoạch ký kết HĐ (NCC-TP)"
├── Khkk_List ord1
├── Khkk_Create ord2
├── Khkk_Pending ord3
└── Khkk_Approved ord4
🔎 Giải-trình con số 14 (đừng đọc nhầm thành drift): kỳ vọng ban đầu là 7 nhãn 01.-07.,
đo ra 14. Tách theo prefix key: Ct_=7 + Hdc_=7 + other=0. Đúng thiết kế — diff ghi rõ
GĐ4 "tái dùng đúng nhãn đã đánh số 01.-07." (tree.Add(("Hdc_"+code, label, …)) dùng chung
typeLabels). Vậy 14 = 7 gốc + 7 mirror, không phải nhân đôi nhầm (DUPKEYS=0, root vẫn 12).
VERDICT: PASS — 9/9
| # | Mục | KQ |
|---|---|---|
| 1 | Push + range 15 file (9 code), CI fire đúng | ✅ |
| 2 | Migration VẮNG là ĐÚNG (0 file *Migrations*) |
✅ |
| 3 | Run #424 success 5m56s | ✅ |
| 4 | Test gate 562 (45+517, 0 fail) + regression-guard 2 vế có RĂNG | ✅ |
| 5 | Bundle rotate 4/4 (2 app đều sửa) + control hash-cũ text/html |
✅ |
| 6 | Marker ComingSoonPage dương cả 2 bundle + Quay lại ×4 |
✅ |
| 7 | Smoke health ×2 + FE ×2 | ✅ |
| 8 | Menu-tree: Khkk=5 · Hdc=7 · root=12 · 3 root vis=0 · nhãn 7+7 · DUPKEYS=0 | ✅ |
| 9 | 🔴 RESTART-DURABILITY: 38-key = 13/13/38 (494/494) SAU restart THẬT | ✅ PASS |
| ├ (b) skeleton 156 perm, 12 key × 13 | ✅ | |
| ├ (c) control âm Hrm/Off/Personal non-Admin = 0 (revoker còn sống) | ✅ | |
└ (d) Pe% 197/185 |
✅ |
🎯 Kết luận arc 3 đợt
Regression tao bắt ở đợt-2 (mục 9, 447/494 row rơi) đã đóng. Bằng chứng đóng KHÔNG dựa vào
"CI xanh" mà dựa vào 3 chân kiềng: (i) chứng minh có restart THẬT (w3wp 12:39:56 + log mở lại
12:40:03), (ii) 38-key giữ 13/13/38 sau restart đó, (iii) control âm (c) chứng minh revoker
vẫn chạy — nên (ii) không phải do vô hiệu hoá cả hàm. Kèm regression-guard trong test:
re-add nhánh [S92] ⇒ CI đỏ, không cần trông vào trí nhớ.
Baseline mới: admin js CC1pGE1F / css DWDbm5As · user js DeAg7MWm / css BQIZ-rUe ·
gate 562 · Mig 68 / 89 bảng · menu root 12 · 38-key 13/13.