- Session log 2026-06-29-S92-admin-only-modules.md + STATUS/HANDOFF prepend S92 (demote S91→Prev). - cicd Run #358 PASS: deploy shipped (dll-mtime in-window 08:00–08:01Z + w3wp recycle 08:04Z), smoke 4×200, prod sqlcmd verify (non-Admin CanRead 0 trên 5 nhóm ẩn / 0 leak Contracts·Master·Catalogs / Admin giữ 5 key / Pe_* non-Admin 120 untouched). Permission change LIVE. - Bundle JS rotate admin xz8FdmLN / user CA8Rla2w (CSS frozen) — sửa nhận-định #69-REFINE (deploy.yml rebuild FE vô-điều-kiện + rolldown chunk-id non-deterministic → bundle-hash KHÔNG đáng-tin làm ship-signal cho BE-only; tín-hiệu thật = dll-mtime + w3wp-recycle). - Harvest agent-memory: test-specialist (434) + reviewer (S92 topic file + L1 curate 12.9KB) + cicd-monitor (Run #358 + prod schema note: bảng Roles/UserRoles KHÔNG prefix AspNetRoles). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
6.9 KiB
Markdown
62 lines
6.9 KiB
Markdown
# S92 (2026-06-29) — Ẩn 5 nhóm menu → CHỈ Admin thấy trên eoffice
|
||
|
||
> **Loại:** Security / permission-seed · BE-only (no migration) · em-main-led + reviewer + cicd-monitor · HMW-mode ON · 1 commit prod-verified `1408d4e`.
|
||
|
||
## Bối cảnh
|
||
|
||
Anh gõ `/session-start`. Bootstrap phát hiện **WIP "S92" chưa commit** trên đĩa (dated hôm nay 2026-06-29): `DbInitializer.cs` (+32/−10) + 2 seed test sửa comment + `AdminOnlyModulesRevokeTests.cs` (mới) + `test-specialist/MEMORY.md` delta. Đây là việc làm dở của một phiên trước hôm nay, chưa build-verify / commit / deploy / closeout. Anh chốt qua AskUserQuestion: **"Verify + hoàn thiện + deploy"**.
|
||
|
||
## Nội dung S92 (anh chốt: 5 nhóm menu CHỈ Admin thấy trên user-portal eoffice)
|
||
|
||
| Nhóm | Menu key | Trạng thái |
|
||
|---|---|---|
|
||
| Nhân sự | `Hrm*` | S58 đã ẩn — giữ |
|
||
| Văn phòng số | `Off*` | S58 đã ẩn — giữ |
|
||
| Cá nhân | `Personal` | S58 đã ẩn — giữ |
|
||
| **Hợp đồng** | `Contracts` + `Ct_*` | **MỚI S92** |
|
||
| **Danh mục** | `Master` · `Suppliers` · `Projects` · `Departments` · `Catalog*` | **MỚI S92** |
|
||
|
||
**Cơ chế:** menu hiện ⟺ `Permission.CanRead=true` (`GetMyMenuTreeQuery` lọc CanRead, KHÔNG admin-bypass). Revoke set 4 cờ CRUD=false cho MỌI role non-Admin trên key match predicate → menu tự ẩn. Admin loại trừ khỏi revoke → vẫn thấy đủ.
|
||
|
||
**3 thay đổi `DbInitializer.cs`:**
|
||
1. `RevokeTemporarilyHiddenModulesAsync` (`:2125`) — predicate `.Where()` mở rộng `+Contracts +StartsWith("Ct_") +Master +Suppliers +Projects +Departments +StartsWith("Catalog")` (cộng dồn S58).
|
||
2. TẮT (comment) 2 re-grant public chạy-sau-revoke: `SeedAllRolesHrmProfileReadPermissionsAsync` (S65, `:2003`) + `SeedAllRolesOfficeModulePermissionsAsync` (S69, `:2012`). 2 method giữ `private static` cho reversibility.
|
||
3. Thu hẹp `InReviewScope` → **chỉ `Pe_*`** (bỏ Master/Catalog/Suppliers/Projects/Departments khỏi grant-all-roles; row cũ trên prod được revoke gỡ).
|
||
|
||
**Invariant bảo vệ tính năng đang dùng:** `Pe_*` (Duyệt NCC) KHÔNG match predicate → module PE giữ nguyên cho user thường. Master-data GET = `any-auth` (controller class-`[Authorize]`) → form PE vẫn chọn Dự án/NCC/Hạng mục dù menu Danh mục ẩn.
|
||
|
||
## Verify chain (security change → test-before-merge + independent review)
|
||
|
||
1. **Full suite: 434 PASS** (45 Domain + 389 Infra, 0 fail/skip) — `+3` = `AdminOnlyModulesRevokeTests` (2 seed test cũ chỉ +comment).
|
||
2. **Seed-chain order (load-bearing, gotcha #75):** mọi permission-write nằm trong `SeedAdminPermissionsAsync` (gọi tại `SeedAsync:110`, **ungated**). Trong đó: grants `:1975`→`:1986` → **revoke `:1993` = bước permission CUỐI** (2 re-grant sau đã comment). `SeedAsync` sau `:110` không đụng Permissions → revoke thắng toàn cục. Prod `DemoSeed:Disabled=true` nhưng `SeedAdminPermissionsAsync` ungated → revoke re-chạy mỗi restart → ẩn BỀN.
|
||
3. **Predicate coverage:** enumerate `MenuKeys.cs` + `SeedMenuTreeAsync` — 5 nhóm phủ đủ; `Forms`/`Reports`/`Dashboard` là sibling top-level (parent=null) → giữ hiện đúng (ngoài scope); không over-match; `PurchaseEvaluations` là top-level (parent=null) dù label "Duyệt NCC" → PE preserved.
|
||
4. **Reviewer PASS** (adversarial, 5 attack vector A-E độc-lập re-derive; 15/15 trên 3 class; category 1-5 PASS). 0 blocking.
|
||
|
||
## FYI — CatalogManager (intended-by-spec, 0 live impact)
|
||
|
||
`SeedCatalogManagerPermissionsAsync` (`:1980`) cấp Danh-mục CRUD cho role `CatalogManager`, chạy **TRƯỚC** revoke (`:1993`) → revoke (mới) cũng thu Danh-mục của CatalogManager. Role này hiện **0 user** (S89 xóa hết test user) → **không vỡ gì hôm nay**. Theo lệnh "Danh mục CHỈ Admin" = đúng ý. Nếu sau này anh muốn 1 user non-admin quản lý danh mục → cần carve-out predicate (gỡ prefix + uncomment, code comment đã ghi). Re-grant qua UI Permission-matrix chỉ giữ tới restart kế (revoke thắng).
|
||
|
||
## Deploy
|
||
|
||
- Commit `1408d4e` `[CLAUDE] Infra: ẩn 5 nhóm menu ... chỉ Admin thấy (S92)` — 4 file (DbInitializer + 3 test).
|
||
- Push `f98a743..1408d4e` → Gitea Actions (test gate 434 → build → deploy).
|
||
- **cicd Run #358 PASS** (~5m24s) — test gate (434) → build → deploy. Smoke 4×200 (api ready/live + admin + eoffice).
|
||
- **BE-only ship-proof (gotcha #69):** bundle hash KHÔNG phải tín-hiệu ship đáng-tin — JS **rotate** admin `DgQyuG7f`→`xz8FdmLN` / user `BmbQon23`→`CA8Rla2w` (CSS frozen) DÙ 0 fe-src đổi (`deploy.yml` rebuild FE vô-điều-kiện + rolldown chunk-id non-deterministic → **sửa nhận-định #69-REFINE "same-hash deterministic"**). Tín-hiệu ship THẬT = `Infrastructure.dll`/`Api.dll`/`Application.dll` mtime in-window (08:00–08:01Z) + w3wp PID recycle 08:04:14Z.
|
||
- **Hiệu lực permission PROD-VERIFIED (sqlcmd):** revoke đã flip row prod tại pool-recycle 08:04:14Z (KHÔNG cần restart tay) — non-Admin CanRead=1 trên 5 nhóm ẩn = **0** · leak S92-new (Contracts/Master/Catalogs) = **0** · Admin giữ 5 key · Pe_* non-Admin untouched = **120** (12 role). Permission change **LIVE** trên prod.
|
||
|
||
## State (sau S92)
|
||
|
||
**Mig 59 · 88 bảng · 434 test (45D+389I) · gotcha 76 · menu 54 · bundle admin `xz8FdmLN`/user `CA8Rla2w` (JS rotate #69, CSS frozen; Run #358) · prod 17 user/7 NCC.** KHÔNG migration, KHÔNG bảng/menu mới.
|
||
|
||
## Lessons
|
||
|
||
- **Seed-chain order = invariant load-bearing** (gotcha #75): thay đổi permission-visibility phải đảm bảo revoke là bước CUỐI + ungated (chạy mỗi restart prod) thì mới BỀN. S92 tắt 2 re-grant sau revoke + giữ revoke ungated → đúng pattern S91 seed-gate.
|
||
- **Cardinality/scope change → grep MỌI nhánh** (`feedback_cardinality_change_grep_consumers`): mở rộng predicate revoke = phải enumerate toàn bộ menu key của nhóm (MenuKeys + SeedMenuTree) để không under-revoke leak; reviewer xác nhận `Forms`/`Reports`/`PurchaseEvaluations` là sibling top-level (không lọt/không vỡ).
|
||
- **Reviewer lượng-hoá blast-radius**: thay vì chặn vì "CatalogManager có thể mất quyền", reviewer xác định **0 user** giữ role đó → biến lo-ngại thành FYI, deploy có cơ sở.
|
||
|
||
## NEXT
|
||
|
||
- **Anh:** UAT eoffice bằng 1 user non-admin (Ctrl+F5) → xác nhận KHÔNG còn thấy Hợp đồng / Danh mục / Nhân sự / Văn phòng số / Cá nhân (chỉ còn Duyệt NCC + Dashboard). ⚠️ restart CLI (carry: budget v3 + `/user-mark-*` + cadence chưa hot-reload).
|
||
- **Em:** monthly audit **2026-07-01** (2 ngày) — re-tier STATUS/HANDOFF mega-line (bloat: Read-truncate + detector mis-parse canonical) · flush ef-core SKILL/README Mig→59 · docs/CLAUDE Budget-removed · 3 vocab-fork alias-map.
|
||
- **Pending product (carry):** Section B cột Dự-án wire · Ngưỡng giá CEO (Mig 54) Designer UAT · "C" chuyển phiếu→dự án (chờ spec).
|