Files
solution-erp/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-2a72695.md
2026-07-29 12:47:37 +07:00

230 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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-2 `1a47a61` (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=success` VÀ 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=success` 12: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ẢI** `CanRead=false`
- `ReopenedKeys` = `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).
`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ứa `Sắ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.
<!-- END cicd-verify-2a72695 · VERDICT=PASS 9/9 -->