230 lines
11 KiB
Markdown
230 lines
11 KiB
Markdown
# 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).
|
||
|
||
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ứ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 -->
|
||
|