324 lines
24 KiB
Markdown
324 lines
24 KiB
Markdown
CICD-K4AB: PASS — 7/7 (run #438 success 6m23s · gate 621=45+576 · Mig GIỮ 71 · seed 49 menu+637 perm tam-giác-3-nguồn · A1 8+48 & A5 restart×2 đứng im · bundle JS ROTATE ×2 marker 3/3 · smoke 401/404/200 + bind-400)
|
||
|
||
# cicd-monitor — verify deploy K4a+K4b SP-002 (`88e7ecf`) + phủ luôn F-1 (`7a903cf`)
|
||
|
||
> Artifact chống #53: ghi TỪNG MỤC ngay khi có bằng chứng. Kết bằng `END ... VERDICT=`.
|
||
> Repo `D:\Dropbox\CONG_VIEC\SOLUTION\SOLUTION_ERP` · push `7a903cf..88e7ecf main` · phiên S166 01/08.
|
||
> Khuôn kế thừa k1/k2/k3 cùng folder: tam-giác 3-nguồn ⟂ control ÂM ⟂ recycle-proof ⟂ dự-đoán-TRƯỚC-khi-đo.
|
||
|
||
## §0 — Tiền đề (git, đo LIVE)
|
||
|
||
```
|
||
HEAD = 88e7ecf263f019f4b31d4defb9520704568042a9 2026-08-01 10:10:11 +0700
|
||
[CLAUDE] Contract: K4a+K4b SP-002 sidebar 8 nhom x 6 leaf + noi ?group= end-to-end (0-mig)
|
||
git log origin/main..HEAD -> (rỗng — đã push)
|
||
```
|
||
|
||
`git diff --name-only 7a903cf..88e7ecf` = 33 file. Phân loại vs `paths-ignore` 7-glob:
|
||
- **CODE (13, trigger CI):**
|
||
- BE 4: `ContractSigningPlansController.cs` · `ContractSigningPlanFeatures.cs` · `MenuKeys.cs` · `DbInitializer.cs`
|
||
- FE-admin 4: `components/Layout.tsx` · `pages/khkk/KhkkCreatePage.tsx` · `pages/khkk/KhkkListPage.tsx` · `types/khkk.ts`
|
||
- FE-user 4: y hệt 4 file trên (cặp song sinh)
|
||
- test 1: `tests/…/Application/KhkkGroupMenuSeedTests.cs`
|
||
- **IGNORED (20):** `docs/STATUS.md` (`**/*.md`) + `.claude/WAL.md` + `.claude/agent-memory/**` ×10 + `.claude/workflows/runs/**` ×8
|
||
- **0 file `*Migrations*`** ⇒ K4a+K4b = 0-mig (khẳng định này đo lại độc lập ở §3).
|
||
|
||
**F-1 `7a903cf` (run #437, chưa verify riêng — lượt này phủ luôn):**
|
||
`git diff --name-only 15349e8..7a903cf` = 41 file; CODE 5 (`fe-admin/khkk/KhkkWorkflowPanel.tsx` + `fe-admin/types/khkk.ts` + `fe-user/khkk/KhkkWorkflowPanel.tsx` + `fe-user/types/khkk.ts` + `ContractSigningPlanFeatures.cs`) — khớp mô tả "FE+DTO nhỏ"; phần còn lại ignored-path (`CLAUDE.md`, `docs/**`, `.claude/**`).
|
||
|
||
### §0.1 — Snapshot PRE đo LIVE lúc run #438 CÒN `running` (10:12-10:14 +07)
|
||
|
||
Đây là mốc PRE MẠNH NHẤT có thể có: đo trong lúc CI đang chạy ⇒ chắc chắn chưa bị K4a/K4b ghi đè.
|
||
|
||
**Bundle PRE (curl index.html, 10:12:09):**
|
||
```
|
||
admin /assets/index-1yiNV4VH.js + /assets/index-Cf3iCOOv.css
|
||
eoffice /assets/index-DoULfmdT.js + /assets/index-DX1FRk5n.css
|
||
```
|
||
✔ Khớp ĐÚNG mốc đề bài lead giao (`1yiNV4VH` / `DoULfmdT`) và khớp nguồn gốc của mốc đó
|
||
(`sub-reviewer-gate-k4a.md:131,133` — gate-K4a đo post-#437). **2 nguồn độc lập ⇒ mốc PRE hợp lệ.**
|
||
|
||
**Prod DB PRE (sqlcmd `localhost\SQLEXPRESS`):**
|
||
```
|
||
MENU_TOTAL = 151
|
||
MENU_KHKK_PCT = 7 (đúng 7 key nhóm-1 không-infix: Khkk_G1/WfView/List/Create/Pending/Approved/Deleted)
|
||
MENU_KHKK_G1_LABEL = "1. Kế hoạch ký kết HĐ (NCC-TP)" <- nhãn SKELETON S159, chưa backfill
|
||
PERM_TOTAL = 1584
|
||
PERM_KHKK_PCT = 91 (= 7 × 13, và CanRead=1 cả 91 ⇒ họ Khkk đang mở đủ)
|
||
ROLE_TOTAL = 13 (Accounting/Admin/AuthorizedSigner/CatalogManager/CostControl/DeptManager/
|
||
Director/Drafter/Equipment/Finance/HrAdmin/Procurement/ProjectManager)
|
||
MIGTOP = 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime · MIGCOUNT = 71
|
||
CONTROL ÂM nền #84 : Hrm*/Off* rows=377 nhưng CanRead=1 chỉ **29** (revoker đang SỐNG, giữ 348 row tắt)
|
||
```
|
||
⚠️ Cột PK `MenuItems` = **`[Key]`**; bảng quyền `Permissions(Id,RoleId,**MenuKey**,CanRead,CanCreate,CanUpdate,CanDelete)`
|
||
— 1 ROW / cặp (role,key) với 4 cột cờ, **KHÔNG phải 4 row**. Bảng role = `Roles` (non-prefix).
|
||
|
||
🔴 **ĐÍNH CHÍNH MỐC PRE TRONG ĐỀ BÀI (đo, không đoán):** lead giao `MENU_TOTAL PRE=143 ⇒ kỳ vọng 192`.
|
||
Đo LIVE ra **151**. Truy PHẠM-VI-QUERY trước khi hô drift (bài #422) → giải thích TRỌN, element-wise:
|
||
K3 (#436) đẻ thêm **8 menu-row `AwV2_KhkkN1..N8`** (đo: `MENU_AwV2_ALL=10` = 2 cũ `AwV2_DuyetNcc`/
|
||
`AwV2_DuyetNccPhuongAn` + 8 mới, liệt kê đủ tên). **143 + 8 = 151 khít** ⇒ 143 là mốc PRE-K3 đã lão hoá,
|
||
không phải drift. **Kỳ vọng đúng cho lượt này = 200, KHÔNG phải 192.**
|
||
|
||
**w3wp PRE:** `appcmd list wp` → `WP "4876" (applicationPool:SolutionErp-Api)`, StartTime **01/08 09:11:51**
|
||
= đúng cửa-sổ recycle của #437 (09:05:59→09:12:21). ⇒ mốc-so-sánh recycle-proof cho #438.
|
||
(VPS có 6 w3wp chung VIETREPORT ⇒ map PID→pool bằng `appcmd`, CẤM đoán PID.)
|
||
|
||
### §0.2 — DỰ ĐOÁN TỪ SOURCE, ghi ĐĨA TRƯỚC KHI ĐO POST
|
||
|
||
Đọc `MenuKeys.cs:72-84` + `DbInitializer.cs` (vòng seed menu K4a + `labelBackfill` + grant site-2):
|
||
- `KhkkGroupNumbers=[2..8]` (7) × (`KhkkGroup(n)` 1 + `KhkkLeafActions` 6 = `WfView,List,Create,Pending,Approved,Deleted`)
|
||
⇒ `KhkkGroupMenuKeys` = **7 × 7 = 49 key MỚI** (7 group + 42 leaf).
|
||
- Vòng seed menu-row dùng CHÍNH bộ sinh đó ⇒ **MENU_TOTAL 151 → 200** · **MENU_KHKK_PCT 7 → 56**.
|
||
- `labelBackfill["Khkk_G1"]` ⇒ Label đổi → **"1. Thiết bị, máy móc, vật tư phụ (A1)"** (key GIỮ, 0 row mới).
|
||
- Grant: site-1 loop `MenuKeys.All` cấp **Admin**; site-2 `KhkkKeys().Concat(KhkkGroupMenuKeys)` cấp
|
||
**CanRead mọi role, skip-existing** ⇒ hợp lại = 49 key × **13 role** = **+637 ROW**.
|
||
⇒ **PERM_TOTAL 1584 → 2221** · **PERM_KHKK_PCT 91 → 728** (chéo: 728 = 56 × 13, và 728−91 = 637 — hai lối tính khớp).
|
||
- ⚠️ Số **196** trong đề bài (`49×4`) là **POLICY** (|All|×4 action, tầng authz — `MenuKeys.cs:57-62` khai đúng vậy),
|
||
KHÔNG phải row DB. Hai đại lượng khác nhau, đừng cộng nhầm vào PERM_TOTAL.
|
||
- Mig: 0 file `*Migrations*` trong diff ⇒ **GIỮ 71 / top `20260731085624`**.
|
||
|
||
---
|
||
|
||
## §1 — Run Gitea Actions — ✅ PASS (2 run: #438 K4a+K4b · #437 F-1)
|
||
|
||
API `.../actions/tasks?limit=8` (anon, public repo). Match theo **`head_sha`**, KHÔNG theo run_number:
|
||
```
|
||
id=551 | run# 438 | 88e7ecf263 | status=success | concl=None | 10:10:19 -> 10:16:42 +07 <- K4a+K4b
|
||
id=550 | run# 437 | 7a903cf108 | status=success | concl=None | 09:05:59 -> 09:12:21 +07 <- F-1 (phủ lượt này)
|
||
id=549 | run# 436 | 15349e8ee0 | status=success | concl=None | 31/07 18:21:48 -> 18:27:59 <- K3 (neo thứ tự)
|
||
```
|
||
- **#438 duration 6m23s** · **#437 duration 6m22s** — in-band (#436 6m11s · #435 6m12s · #434 6m31s).
|
||
- **Control neo thứ tự 2 tầng:** run kề dưới #438 = `7a903cf` (F-1), kề dưới nữa = `15349e8` (K3, đã verify ở
|
||
`sub-cicd-verify-k3.md`) ⇒ chuỗi #436→#437→#438 liền mạch, **không có run lạ chen giữa**, không nhầm run.
|
||
- Tao **đo LIVE lúc #438 còn `running`** (poll 10:12→10:16:51, 3 nhịp `running` rồi `success`) ⇒ không dính bẫy
|
||
false-positive "verify khi còn running" (Run #242).
|
||
- `concl=None` là bình thường với endpoint `tasks` ⇒ tin `status` (gate test chạy TRƯỚC build/deploy).
|
||
- URL: `https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/438` (và `/437`).
|
||
|
||
## §2 — Gate CI **621 = 45 + 576** — ✅ PASS (khớp TỪNG VẾ + đo luôn F-1)
|
||
|
||
Log web-UI `.../actions/runs/{438,437}/jobs/0/logs` (HTTP 200; 18.665 B và 18.689 B). Đọc bằng **bytes**
|
||
(text-mode crash cp1252); regex nới khoảng-trắng vì log dùng **2 space** sau `Passed!`:
|
||
```
|
||
#438 Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 — SolutionErp.Domain.Tests.dll
|
||
#438 Passed! - Failed: 0, Passed: 576, Skipped: 0, Total: 576 — SolutionErp.Infrastructure.Tests.dll
|
||
#437 Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 — SolutionErp.Domain.Tests.dll
|
||
#437 Passed! - Failed: 0, Passed: 569, Skipped: 0, Total: 569 — SolutionErp.Infrastructure.Tests.dll
|
||
```
|
||
- **#438 = 45 + 576 = 621** ✔ khớp kỳ vọng TÁCH-PHẦN (45D/576I), 0 fail / 0 skip.
|
||
- **Chuỗi delta khép kín 3 mốc** (đây là chỗ việc phủ-2-run trả giá trị): #436 K3 = 45+569 = 614 →
|
||
**#437 F-1 = 45+569 = 614 (Infra GIỮ 569, +0)** → **#438 = 45+576 = +7**.
|
||
⇒ F-1 đúng là "FE+DTO nhỏ, 0 test mới"; **toàn bộ +7 thuộc về K4a+K4b**, khớp `KhkkGroupMenuSeedTests.cs`
|
||
(file test DUY NHẤT trong diff #438). Domain **GIỮ 45** suốt 3 run.
|
||
- **Control ÂM:** `grep -c 'Failed!'` = **0** trên CẢ HAI log ⇒ 2 dòng `Passed!` là TOÀN BỘ project,
|
||
không có project thứ 3 bị nuốt (nếu fail, dotnet in `Failed! - Failed: N…`).
|
||
|
||
## §3 — Mig **GIỮ 71** — ✅ PASS (2 nguồn ĐỘC LẬP repo ⟂ prod)
|
||
|
||
```
|
||
prod: SELECT MAX(MigrationId) -> 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime ; COUNT(*) = 71
|
||
repo: git diff --name-only 7a903cf..88e7ecf -- '*Migrations*' -> (rỗng)
|
||
```
|
||
- MIGCOUNT 71 (§0.1 PRE) == 71 (POST) ⇒ **không có mig lén chen vào**; top y nguyên mốc K2.
|
||
- ⚠️ Mục "GIỮ NGUYÊN" tự nó ít bit ⇒ răng lấy từ **control DƯƠNG cùng phiên**: §4 chứng minh DbInitializer
|
||
ĐÃ chạy (seed 49 row + backfill 1 label) ⇒ startup-hook SỐNG. "Mig không nhích" là vì **không có mig**,
|
||
chứ không phải vì app không khởi động lại.
|
||
|
||
## §4 — Seed 49 menu-row + 637 permission-row + backfill label — ✅ PASS (TAM-GIÁC 3 NGUỒN, khớp TỪNG ĐỒNG)
|
||
|
||
Mọi số dưới đo **SAU recycle** (recycle-proof ở §4.1). Đối chiếu với dự đoán §0.2 **đã ghi đĩa TRƯỚC khi đo**:
|
||
|
||
| Chỉ số | PRE (§0.1) | DỰ ĐOÁN §0.2 | ĐO POST | |
|
||
|---|---|---|---|---|
|
||
| `MENU_TOTAL` | 151 | **200** | **200** | ✔ |
|
||
| `MenuItems` LIKE `Khkk%` | 7 | **56** | **56** | ✔ |
|
||
| `Khkk_G1`.Label | "1. Kế hoạch ký kết HĐ (NCC-TP)" | "1. Thiết bị, máy móc, vật tư phụ (A1)" | **đúng chuỗi đó** | ✔ |
|
||
| `PERM_TOTAL` | 1584 | **2221** | **2221** | ✔ |
|
||
| `Permissions` LIKE `Khkk%` | 91 | **728** (=56×13) | **728** | ✔ |
|
||
| … trong đó `CanRead=1` | 91 | 728 | **728** (100%) | ✔ |
|
||
| `MIGCOUNT` | 71 | 71 | **71** | ✔ |
|
||
|
||
**3 NGUỒN ĐỘC LẬP cùng ra 49 / 637** (khuôn tam-giác K1):
|
||
- (a) **source-parse**: `KhkkGroupNumbers`[2..8] × (1 group + `KhkkLeafActions` 6) = 7×7 = 49; grant 49 × 13 role = 637.
|
||
- (b) **sqlcmd prod**: menu 151→200 (+49) · perm 1584→2221 (**+637**).
|
||
- (c) **Serilog prod** (nguồn thứ 3, độc lập hoàn toàn với 2 nguồn trên):
|
||
```
|
||
10:16:30.458 [INF] Seeded 49 menu items
|
||
10:16:30.531 [INF] Backfilled 1 menu labels
|
||
10:16:30.711 [INF] Seeded 49 admin permissions
|
||
10:16:31.298 [INF] Seeded all-roles review perms: 588 added + 0 upgraded (139 keys × 13 roles)
|
||
```
|
||
🔑 **Phân rã 637 khớp CHÍNH XÁC cơ chế 2-site**: site-1 (loop `MenuKeys.All`, chỉ Admin) = **49**;
|
||
site-2 (`KhkkKeys().Concat(KhkkGroupMenuKeys)`, mọi role, **skip-existing** nên Admin không đếm lại) =
|
||
**588 = 49 × 12 role còn lại**. **49 + 588 = 637** ✔. Con số 588 này KHÔNG suy ra được từ tổng —
|
||
nó chỉ khớp nếu đúng cơ chế skip-existing ⇒ đây là bit thông tin thật, không phải trùng khớp số học rỗng.
|
||
`0 upgraded` cũng đúng: 49 key MỚI toàn đi nhánh INSERT.
|
||
|
||
**SHAPE element-wise 8 nhóm** (không chỉ đếm — soi từng dòng, khớp bảng nhãn owner `danh-muc-sp002-transcribe.md`):
|
||
```
|
||
Khkk_G1|ord=1|kids=6| 1. Thiết bị, máy móc, vật tư phụ (A1) <- nhãn ĐÃ backfill
|
||
Khkk_G2|ord=2|kids=6| 2. Kết cấu, hạ tầng, phá dỡ & DV hỗ trợ thi công (A2-A3)
|
||
Khkk_G3|ord=3|kids=6| 3. Hoạt động văn phòng (A4)
|
||
Khkk_G4|ord=4|kids=6| 4. Vật tư (B1)
|
||
Khkk_G5|ord=5|kids=6| 5. Thầu phụ xây dựng (B2)
|
||
Khkk_G6|ord=6|kids=6| 6. Thầu phụ MEP (B3)
|
||
Khkk_G7|ord=7|kids=6| 7. BCH công trường (C1-C3)
|
||
Khkk_G8|ord=8|kids=6| 8. HĐ chưa ủy quyền — CEO (5.2)
|
||
```
|
||
8 nhóm × đúng **6 kids** mỗi nhóm · Order 1..8 liền không trùng · 8/8 nhãn khớp bảng owner từng ký tự.
|
||
|
||
**Control ÂM (chứng "đúng NGƯỜI, đúng TẬP", không quét bừa):**
|
||
- `CTRL_OUTOFSET_G0_G9plus = 0` — không đẻ `Khkk_G0`/`Khkk_G9+` ngoài miền 1..8.
|
||
- **Revoker #84 còn SỐNG mà KHÔNG đụng Khkk**: `Hrm*`/`Off*` có 377 row nhưng `CanRead=1` chỉ **29** —
|
||
**y hệt PRE (29)**. Nếu revoker chết thì số này bung lên 377; nếu revoker quét lố sang `Khkk_*` thì 728
|
||
đã tụt. Cả hai đều không xảy ra ⇒ checker còn răng VÀ phạm vi đúng (chân-kiềng iii).
|
||
|
||
## §4.1 — Recycle-proof (2 nguồn) — mọi số §4 đo SAU khởi động lại
|
||
|
||
- `appcmd list wp` → `WP "7020" (applicationPool:SolutionErp-Api)`; `Get-Process w3wp` → PID 7020
|
||
StartTime **01/08 10:16:22**. **KHÁC PRE (PID 4876 @ 09:11:51)** ⇒ pool ĐÃ recycle, đúng cửa-sổ deploy #438.
|
||
(VPS có 6 w3wp chung VIETREPORT ⇒ map PID→pool bằng `appcmd`, CẤM đoán PID.)
|
||
- Nguồn 2: dòng **ĐẦU TIÊN** của `solution-erp-20260801.log` = `10:16:23.961 [INF] Applying migrations...`
|
||
(deploy xoá `logs/` ⇒ dòng đầu = boot). Seed log 10:16:30 nằm sau đó ⇒ đúng lần boot này.
|
||
|
||
## §6 — Bundle ×2 — DỰ ĐOÁN TỪ SOURCE **trước khi curl** (ghi đĩa trước, đo sau)
|
||
|
||
🔴 **LOẠI marker lead gợi ý — nó KHÔNG CÓ RĂNG.** Đề bài đề xuất `Kết cấu, hạ tầng, phá dỡ` (từ
|
||
`KHKK_APPROVAL_GROUP_LABELS`) với kỳ vọng "0-hit PRE / 1-hit POST". Đo nguồn:
|
||
`git show 7a903cf:fe-user/src/types/khkk.ts | grep -c` = **1** (và fe-admin cũng **1**) ⇒ chuỗi này ĐÃ CÓ
|
||
từ trước K4 (nó vào repo ở wave trước, K4 chỉ thêm comment quanh nó) ⇒ **đã nằm sẵn trong bundle PRE**
|
||
⇒ 0 bit. Cùng lớp sai lầm với `earlyFinalizeLocked` (§5 k3). Thay bằng 3 marker sau, đều là **string
|
||
literal** (minifier giữ nguyên) và đều có **transition PRE→POST đo được**:
|
||
|
||
| Marker | Loại | Nguồn PRE `7a903cf` | Nguồn POST `88e7ecf` | Kỳ vọng bundle LIVE |
|
||
|---|---|---|---|---|
|
||
| `/khkk/list?group=1` | POS | admin 0 · user 0 | admin 4 · user 4 | **>0 CẢ HAI** |
|
||
| `/khkk/list?view=deleted` | NEG | admin 1 · user 1 | admin 0 · user 0 | **0 CẢ HAI** |
|
||
| `workflow-matrix?type=10&group=` | DIFFERENTIAL | admin 0 · user 0 | admin 1 (**trong `//` comment**) · user 2 (**code**) | **admin 0 · user 2** |
|
||
|
||
- NEG **ĐỘC QUYỀN** (bài "control ÂM phải độc quyền"): `grep -rn` toàn `fe-admin/src`+`fe-user/src` bản POST
|
||
= **0 hit** ⇒ vắng mặt trong bundle là vắng mặt THẬT, không phải chuỗi còn sống ở chỗ khác.
|
||
- DIFFERENTIAL là marker mạnh nhất lượt này, **2 mũi cùng lúc**: (1) chứng bất-đối-xứng CỐ Ý đã ship
|
||
(fe-admin cố ý KHÔNG nới regex `Khkk_G*` — lead chốt 27/07), (2) **tự chứng comment bị strip** —
|
||
admin có đúng 1 lần xuất hiện và nó nằm trong `//` (dòng 39) nên phải ra 0; nếu admin ra 1 thì hoặc
|
||
comment lọt vào bundle hoặc tao grep nhầm file.
|
||
|
||
### §6.1 — ĐO BUNDLE (sau khi dự đoán trên đã ghi đĩa) — ✅ PASS, khớp 3/3 marker
|
||
|
||
| App | PRE (§0.1) | POST (đo 10:23) | CT | Size | Last-Modified |
|
||
|---|---|---|---|---|---|
|
||
| admin js | `index-1yiNV4VH.js` | **`index-qOcoitOr.js` ROTATE** | application/javascript | 1.756.651 | 01/08 03:15:14 GMT = **10:15:14 +07** |
|
||
| admin css | `index-Cf3iCOOv.css` | `index-Cf3iCOOv.css` FROZEN | text/css | 86.364 | **10:15:14 +07** |
|
||
| eoffice js | `index-DoULfmdT.js` | **`index-D9J3BbcA.js` ROTATE** | application/javascript | 1.673.668 | 01/08 03:16:16 GMT = **10:16:16 +07** |
|
||
| eoffice css | `index-DX1FRk5n.css` | `index-DX1FRk5n.css` FROZEN | text/css | 92.148 | **10:16:16 +07** |
|
||
|
||
- **JS ROTATE ×2 = ĐÚNG YÊU CẦU** (cả 2 app đổi code thật: Layout + ListPage + CreatePage + types).
|
||
- Cả 4 LM **trong cửa-sổ deploy** 10:10:19→10:16:42 ⇒ deploy CÓ chạm cả 4 file (#69: LM-in-window mới là
|
||
ship-proof, hash-delta thì không). **CSS FROZEN lành tính**: K4a/K4b không thêm class Tailwind mới
|
||
(chỉ đổi chuỗi route + logic), LM vẫn in-window ⇒ re-ship byte-identical (#69 mode-B), KHÔNG phải kẹt bundle cũ.
|
||
- **Control ÂM 2 tầng** — hash CŨ **và** hash BỊA cùng rơi `text/html` 900 B / 876 B:
|
||
`admin/index-1yiNV4VH.js`→text/html 900 · `eoffice/index-DoULfmdT.js`→text/html 876 · `admin/index-ZZfakeZZ.js`→text/html 900.
|
||
⇒ `text/html` thật sự nghĩa "không tồn tại" (SPA-fallback), và **bundle PRE đã biến mất khỏi đĩa = chứng ghi-đè**.
|
||
|
||
**Byte-marker đo trên file tải về (`adm.js` 1.738.624 ký tự · `eof.js` 1.658.019 ký tự):**
|
||
|
||
| Marker | Kỳ vọng §6 | admin | eoffice | |
|
||
|---|---|---|---|---|
|
||
| POS `/khkk/list?group=1` | >0 cả hai | **4** | **4** | ✔ (khớp ĐÚNG 4 = số lần trong source mỗi app — element-wise, không chỉ ">0") |
|
||
| NEG `/khkk/list?view=deleted` | 0 cả hai | **0** | **0** | ✔ (chuỗi độc quyền, PRE source =1 ⇒ transition đo được) |
|
||
| DIFF `workflow-matrix?type=10&group=` | admin 0 · user 2 | **0** | **2** | ✔ (bất-đối-xứng cố ý ship + comment bị strip đúng như dự đoán) |
|
||
| _(marker lead gợi ý)_ `Kết cấu, hạ tầng, phá dỡ` | — | **1** | **1** | 🔴 **=1 ở CẢ HAI ⇒ xác nhận bằng ĐO rằng nó 0-bit**: có mặt bất kể K4 ship hay không |
|
||
|
||
## §5 — Acceptance A1 (quyền user-thường) + A5 (idempotent qua restart ×2) — ✅ PASS
|
||
|
||
**A1 — SQL JOIN `Permissions × Roles × MenuItems`, role NON-admin:**
|
||
```
|
||
Procurement : GROUP = 8 LEAF = 48 (CanRead=1)
|
||
Drafter : GROUP = 8 LEAF = 48 (CanRead=1)
|
||
```
|
||
- Khớp ĐÚNG kỳ vọng đề bài "8 group + 48 leaf". Toán khép: group 8 = `Khkk_G1` + `Khkk_G2..G8`;
|
||
leaf 48 = 6 (nhóm-1 không-infix) + 42 (nhóm 2..8) ⇒ tổng 56 = `MENU_KHKK_PCT`.
|
||
- Toàn họ: `PERM_KHKK_CANREAD1 = 728 = 56 × 13` ⇒ **KHÔNG role nào bị hụt** (nếu 1 role sót thì ≤ 715).
|
||
|
||
**A1-bis — chứng ĐI TRỌN STACK (nguồn thứ 3, mạnh nhất): `GET /api/menus/me` materialize qua EF**
|
||
(http 200, 44.656 B; parse cây theo `key`, không grep thô):
|
||
```
|
||
GROUP distinct = 8 LEAF distinct = 42 per-group = {G2..G8 : 6 mỗi nhóm}
|
||
OLD nhóm-1 leaf non-infix = 6 [Khkk_Approved, Khkk_Create, Khkk_Deleted, Khkk_List, Khkk_Pending, Khkk_WfView]
|
||
TOTAL Khkk* distinct = 56
|
||
Khkk_G1 -> 1. Thiết bị, máy móc, vật tư phụ (A1) Khkk_G5 -> 5. Thầu phụ xây dựng (B2)
|
||
Khkk_G2 -> 2. Kết cấu, hạ tầng, phá dỡ & DV hỗ trợ thi công (A2-A3) Khkk_G6 -> 6. Thầu phụ MEP (B3)
|
||
Khkk_G3 -> 3. Hoạt động văn phòng (A4) Khkk_G7 -> 7. BCH công trường (C1-C3)
|
||
Khkk_G4 -> 4. Vật tư (B1) Khkk_G8 -> 8. HĐ chưa ủy quyền — CEO (5.2)
|
||
```
|
||
⇒ 8 nhãn ra ĐÚNG tới dấu tiếng Việt qua đường Domain→Infra→Api (không phải chỉ có row trong bảng);
|
||
`per-group = 6 đều` chứng không nhóm nào thiếu leaf. **Đây là cái sqlcmd không nói được.**
|
||
|
||
**A5 — RESTART APP-POOL ×2 THẬT** (`appcmd recycle apppool /apppool.name:SolutionErp-Api`, KHÔNG gọi seeder lẻ
|
||
qua reflection — chuỗi `SeedAsync` đủ chỉ chạy lúc boot):
|
||
|
||
| Mốc | w3wp PID | StartTime | MENU_TOTAL | Khkk menu | PERM_TOTAL | Khkk perm | CanRead=1 | A1 Proc leaf | CTRL Hrm/Off CanRead=1 |
|
||
|---|---|---|---|---|---|---|---|---|---|
|
||
| sau deploy | 7020 | 10:16:22 | 200 | 56 | 2221 | 728 | 728 | 48 | **29** |
|
||
| **restart #1** | **220** | **10:23:55** | 200 | 56 | 2221 | 728 | 728 | 48 | **29** |
|
||
| **restart #2** | **6636** | **10:24:38** | 200 | 56 | 2221 | 728 | 728 | 48 | **29** |
|
||
|
||
- **Restart là THẬT, không no-op**: PID đổi 3 lần (7020→220→6636) + `appcmd` xác nhận đúng pool
|
||
`SolutionErp-Api` + Serilog có **3 dòng `Applying migrations...`** (10:16:23 · 10:23:57 · 10:24:39).
|
||
- **Idempotent**: sau boot 2 và 3, Serilog **KHÔNG có** `Seeded … menu items` / `Seeded … admin permissions` /
|
||
`Seeded all-roles review perms`. Đây là suy luận CÓ CƠ SỞ, không phải đoán từ im-lặng — đã soi điều kiện
|
||
log trong source: `DbInitializer.cs:2180-2183 if (added > 0)` · `:2247 if (updatedLabels > 0)` ·
|
||
`:2310 if (added > 0)` · `:2524 if (added > 0 || upgraded > 0)`. ⇒ vắng dòng ⟺ **0 added / 0 upgraded**.
|
||
Cộng với 8 con số DB đứng im hoàn toàn ⇒ seeder hội tụ, không nhân bản row.
|
||
- **Revoker-proof (#84)**: `Hrm*`/`Off*` giữ **29/377** CanRead=1 qua cả 3 mốc. Đây là control ÂM có RĂNG
|
||
2 chiều: revoker vẫn CHẠY mỗi boot (nếu chết → bung lên 377) và vẫn KHÔNG chạm `Khkk_*` (nếu quét lố →
|
||
728 tụt). Đúng như comment K4a khai ở `DbInitializer.cs` ("filter chỉ bắt prefix `Hrm*`/`Off*` + `Personal`").
|
||
- Nhãn `Khkk_G1` giữ nguyên bản backfill qua cả 2 restart ⇒ `labelBackfill` không lật ngược về skeleton.
|
||
|
||
## §7 — Smoke prod — ✅ PASS
|
||
|
||
```
|
||
GET /api/contract-signing-plans (no token) -> 401 authz đóng đúng
|
||
GET /api/contract-signing-plans/deleted?group=1 (no token) -> 401 route MỚI cũng đóng
|
||
GET /api/contract-signing-plans-khong-ton-tai-xyz -> 404 \ 2 route BỊA
|
||
GET /api/route-rac-k4ab-999 -> 404 / ⇒ 401 ở trên CÓ NGHĨA
|
||
GET /health/ready -> 200
|
||
GET /health/live -> 200
|
||
GET https://admin.solutions.com.vn/ -> 200
|
||
GET https://eoffice.solutions.com.vn/ -> 200
|
||
```
|
||
🔑 **401 chỉ có nghĩa vì đi kèm 404 của route bịa** — nếu server nuốt mọi path thành 401 thì phép đo rỗng.
|
||
|
||
**Giới hạn phép đo đã gỡ được (đề bài dự phòng đúng):** `?group=abc` **không token → 401**, tức authz chặn
|
||
TRƯỚC model-binding ⇒ không đọc được bind-error. Đã lấy bearer admin re-probe để đóng lỗ này:
|
||
```
|
||
(có token) ?group=abc -> 400 <- BIND-ERROR: param ĐƯỢC WIRE thật (nếu bị nuốt, ASP.NET trả 200)
|
||
(có token) ?group=1 -> 200 ?group=8 -> 200 (không group) -> 200
|
||
(có token) /deleted?group=1 -> 200
|
||
body ?group=1 = {"items":[],"total":0,"page":1,"pageSize":20,"totalPages":0,"hasNext":false,"hasPrev":false}
|
||
```
|
||
⇒ `?group=` **nối end-to-end THẬT** (chứng bằng 400 bind-error, không bằng lời khai).
|
||
|
||
⚠️ **2 quan sát KHÔNG hạ verdict, chuyển tiếp cho wave sau:**
|
||
1. **Chân-lý-rỗng còn nguyên (kế thừa K2):** `ContractSigningPlans` prod vẫn **0 dòng** ⇒ `items:[]` với MỌI
|
||
group. Phép đo `?group=n` chứng được **đường ống + binding**, KHÔNG chứng được **lọc đúng nhóm** —
|
||
backfill/lọc sai sẽ trông y hệt. Muốn có răng phải có ≥1 phiếu thật ở ≥2 nhóm khác nhau.
|
||
2. `?group=999` → **200** (không phải 400/404): biên API vẫn không chặn ngoài-miền 1..8 — **y hệt K2 ghi
|
||
nhận**, chưa hồi quy nhưng cũng chưa đóng. Vô hại hôm nay vì bảng rỗng.
|
||
|
||
---
|
||
|
||
END sub-cicd-verify-k4ab — VERDICT=PASS 7/7
|
||
(1: run #438 success 6m23s match head_sha + #437 F-1 success 6m22s, chuỗi #436→#437→#438 liền mạch ·
|
||
2: gate 621 = 45D + 576I tách-phần, F-1 = 614 (+0) ⇒ trọn +7 thuộc K4, control ÂM Failed!=0 cả 2 log ·
|
||
3: Mig GIỮ 71 / top 20260731085624, 2 nguồn repo ⟂ prod ·
|
||
4: seed 49 menu + 637 perm + backfill 1 label — TAM-GIÁC 3 nguồn (source-parse ⟂ sqlcmd ⟂ Serilog
|
||
49+588=637), shape 8 nhóm × 6 kids element-wise, recycle-proof PID 4876→7020 + Serilog dòng đầu ·
|
||
5: A1 Procurement/Drafter 8-group + 48-leaf + menus/me materialize 8+42+6=56 qua EF; A5 restart ×2 THẬT
|
||
(PID 7020→220→6636) 8 chỉ số ĐỨNG IM + log-condition đã soi source ⇒ 0 added; revoker Hrm/Off giữ 29 ·
|
||
6: bundle JS ROTATE ×2 (1yiNV4VH→qOcoitOr · DoULfmdT→D9J3BbcA), 4 LM in-window, marker 3/3 khớp
|
||
(POS 4/4 · NEG 0/0 · DIFF admin 0 / user 2); marker lead gợi ý ĐO RA 1/1 ⇒ bác vì 0-bit ·
|
||
7: smoke 401/401 + 404 route bịa ×2 + 200 ×4; bind-error 400 chứng ?group= wire thật)
|
||
|
||
|