- 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>
6.9 KiB
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:
RevokeTemporarilyHiddenModulesAsync(:2125) — predicate.Where()mở rộng+Contracts +StartsWith("Ct_") +Master +Suppliers +Projects +Departments +StartsWith("Catalog")(cộng dồn S58).- TẮT (comment) 2 re-grant public chạy-sau-revoke:
SeedAllRolesHrmProfileReadPermissionsAsync(S65,:2003) +SeedAllRolesOfficeModulePermissionsAsync(S69,:2012). 2 method giữprivate staticcho reversibility. - 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)
- Full suite: 434 PASS (45 Domain + 389 Infra, 0 fail/skip) —
+3=AdminOnlyModulesRevokeTests(2 seed test cũ chỉ +comment). - Seed-chain order (load-bearing, gotcha #75): mọi permission-write nằm trong
SeedAdminPermissionsAsync(gọi tạiSeedAsync:110, ungated). Trong đó: grants:1975→:1986→ revoke:1993= bước permission CUỐI (2 re-grant sau đã comment).SeedAsyncsau:110không đụng Permissions → revoke thắng toàn cục. ProdDemoSeed:Disabled=truenhưngSeedAdminPermissionsAsyncungated → revoke re-chạy mỗi restart → ẩn BỀN. - Predicate coverage: enumerate
MenuKeys.cs+SeedMenuTreeAsync— 5 nhóm phủ đủ;Forms/Reports/Dashboardlà sibling top-level (parent=null) → giữ hiện đúng (ngoài scope); không over-match;PurchaseEvaluationslà top-level (parent=null) dù label "Duyệt NCC" → PE preserved. - 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/ userBmbQon23→CA8Rla2w(CSS frozen) DÙ 0 fe-src đổi (deploy.ymlrebuild 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.dllmtime 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ậnForms/Reports/PurchaseEvaluationslà 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).