Files
solution-erp/docs/changelog/sessions/2026-06-29-S92-admin-only-modules.md
pqhuy1987 396266e5fd [CLAUDE] Docs: S92 closeout — ẩn 5 nhóm menu chỉ Admin (Run #358 PASS prod-verified) + harvest
- 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>
2026-06-29 15:10:43 +07:00

6.9 KiB
Raw Blame History

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 InReviewScopechỉ 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:1986revoke :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 DgQyuG7fxz8FdmLN / user BmbQon23CA8Rla2w (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:0008: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).