[CLAUDE] Docs: S118 closeout - STATUS/HANDOFF/session-log + memory reconcile (509 test / gotcha 82)

Session S118: phan quyen PRO tra.bui (Procurement exception to S92 revoke) + va lo hong
ReportsController authz. 2 prod deploy #388/#389 verified; 3 code/docs commit da push
(e88e75d docs-reconcile + c2135ba PRO-perm-seeder + c808b9a Reports-authz).

Closeout (docs/memory-only -> CI-skip per paths-ignore):
- STATUS: S118 mega-line blurb + canonical rows Tests 501->509 + Gotchas 81->82
  (governance-detector [OK] canonical matches disk 509/82).
- HANDOFF: S118 brief + NEXT anh (tra.bui re-login · binh.le decision · account-trung Le-Thanh-Binh)
  + lessons (wipe-durability / menu-flag!=API / verify-lane-garble-recover / Serilog-durability-proof).
- Session log 2026-07-14-S118-perm-pro-tra-bui-reports-authz.md.
- User memory: +feedback_permission_grant_two_layers (menu-flag grant != API-power, 2-layer verify);
  garble-recover x18->x20 (cicd VERIFY-lane x2 -> em-main-solo curl+sqlcmd).
- Agent-memory: cicd fidelity-fix (harvest-curator F1: false "CLEAN-return" label -> garble+recover,
  substance disk-verified TRUE) + tooling-auditor/harvest-curator @end diaries (H1/H2 gate PASS 5-truc).
- H22 §5.0: fold 2 trailing wal: flush commits (670c888 + e5f655f) into this closeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-14 16:29:55 +07:00
parent c808b9ad81
commit 3fde8afb93
9 changed files with 83 additions and 11 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
# S118 — 2026-07-14 — Phân quyền PRO tra.bui + vá lỗ hổng ReportsController
> **Nguồn:** UAT anh Kiệt FDC (chat Zalo) — "phân quyền tra.bui đi nhé" (ảnh chat: bạn PRO login không thấy menu).
> **Mode:** HMW-ON · lead Opus 4.8 (owner-choice per-session) · em-main-led + 7 spawn + 3 script.
> **Commit:** `e88e75d` (docs-reconcile bootstrap) + `c2135ba` (PRO-perm seeder + test) + `c808b9a` (Reports-authz + test + gotcha) — 3 pushed, 2 deploy #388/#389 verified.
## Bối cảnh
Anh Kiệt báo `tra.bui` (Bùi Lê Thủy Trà, TP Cung ứng) đăng nhập không thấy menu quản lý NCC/Suppliers, dù "em phân quyền hình như đủ". Chat xoay quanh công bố NCC (S113: phải có Mã NCC mới cho công bố).
## Task 1 — Phân quyền tra.bui (Procurement)
### Điều tra (read-only, không đoán)
- Skill `permission-matrix``GetMyMenuTreeQuery` gate menu thuần theo Permission `CanRead` (KHÔNG Admin-bypass ở menu-tree).
- `DbInitializer.cs`: `tra.bui` seed sẵn roles `[Procurement, DeptManager]`.
- **Prod read-only** (SSH `vietreport-vps` + sqlcmd đọc password từ `appsettings.Production.json`, cơ chế plan-ca): tra.bui **active OK, roles đúng**, nhưng chỉ đọc **11 menu — toàn `Pe_*`** (Duyệt NCC), KHÔNG Master/Suppliers.
- **Root-cause:** seeder `RevokeTemporarilyHiddenModulesAsync` (S92 owner-decision, dòng ~2140-2181) chạy **mỗi startup** set CanRead=0 cho `Master`/`Suppliers`/`Projects`/`Departments`/`Catalog*`/`Contracts`/`Hrm*`/`Off*`/`Personal` với MỌI role non-admin ("chỉ Admin thấy"). `Pe_*` được chừa → PE giữ cho user.
### Owner 3-decision (AskUser)
1. **Scope:** Master đầy đủ (Suppliers + Projects + Departments + Catalogs + 4 leaf) + Dashboard + Reports.
2. **Durability:** làm bền qua code (không để grant tay bị revoke reset next-deploy).
3. **Edit-NCC:** chỉ công-bố/import/thêm-mới (KHÔNG sửa/xóa).
### Fix 2 tầng
1. **Grant tạm prod** — MERGE idempotent vào `Permissions` cho RoleId Procurement (`5B21CDC1-...`): tra.bui 11→**22** (verify LIVE). Row Master/Suppliers đã tồn tại all-0 (placeholder) → UPDATE; Dashboard/Reports chưa có → INSERT.
2. **Code-hóa bền** — method mới `SeedProcurementMasterAccessAsync` chạy **NGAY SAU** `RevokeTemporarilyHiddenModulesAsync` (mirror pattern re-grant-thắng-revoke S65 Hrm / S69 Office). **CHỈ role Procurement**, upgrade-only (nâng cờ desired lên true, không hạ, không đụng role khác).
### Reviewer catch (PASS-có-điều-kiện) → owner refine
- **M1:** menu `Reports` gọi `GET /reports/dashboard` lộ `TotalValueActive` + top NCC/dự án theo giá trị + xu hướng 12 tháng → **ngược S92** ("HĐ chỉ Admin thấy"). Owner chốt **bỏ cả Reports + Dashboard** → seeder còn **9 key**, tra.bui còn **20 menu**. Revert Dashboard/Reports trên prod DB.
- **M2:** `Suppliers.Update` chỉ mở Publish/Import; `PUT/DELETE /suppliers` gate `[Authorize(Roles="Admin,CatalogManager")]` → PRO vẫn 403 khi Sửa/Xóa NCC. Owner chốt chỉ cần công-bố/import/thêm → grant hiện đủ.
### Verify durability (bằng chứng vàng)
Serilog prod sau deploy restart:
```
15:35:47.115 [INF] Revoked 9 permission rows ... (chỉ Admin thấy - S58 + S92)
15:35:47.159 [INF] Seeded Procurement master access: 0 added + 9 upgraded ... (PRO exception to S92, S118)
```
→ revoke set 9 row false, seeder lật lại true 44ms sau ⇒ **seeder THẮNG revoke trên startup thật**. Verify ×2 deploy (tra.bui giữ 20). Scope-isolation prod: `binh.le` (CostControl, no-Procurement)=11 · DeptManager-standalone Master/Suppliers=0 (S92 nguyên vẹn role khác).
### Test
`+4 ProcurementMasterAccessSeedTests` (reflection gọi private seeder, không đụng prod code): upgrade-path + **scope-isolation** (DeptManager riêng vẫn revoke) + insert-path + idempotent. 501→505.
## Task 2 — Vá lỗ hổng ReportsController (task anh mở từ chip reviewer flag)
- **Lỗ hổng:** `ReportsController` chỉ `[Authorize]` trần (không policy) → MỌI user đăng nhập gọi được `GET /reports/dashboard` (tài chính HĐ toàn công ty) + `GET /reports/contracts/export` (dump toàn bộ HĐ). Menu bị S92 ẩn nhưng API mở.
- **Phân loại DTO:** `dashboard` + `contracts/export` = company-wide nhạy cảm; `my-dashboard` = per-user (scope `currentUser`, an toàn).
- **Owner chốt:** chỉ Admin (gate `Reports.Read` policy — linh hoạt mở role khác qua ma trận).
- **Fix:** `[Authorize(Policy="Reports.Read")]` per-method cho `Dashboard` + `ExportContracts`; `MyDashboard` giữ `[Authorize]` trần.
- **Test:** +4 reflection authz (`AuthorizePolicyRegressionTests`, mirror gotcha #44 S45). 505→509.
- **Prod verify:** Reports.Read=chỉ Admin (DB), dashboard no-token=401, health 200×3. Non-admin→403 by-construction.
- **gotcha #82** ghi: menu-flag grant ≠ quyền API thật (display-layer ⟂ API-authz-layer).
## Lessons
- **wipe-durability-check-reseed (#75/#76):** grant tay prod sẽ bị seeder revoke reset next-deploy → phải code-hóa; bắt kịp TRƯỚC khi báo "done".
- **menu-flag ≠ API-power (#82):** grep authz-attribute controller đích trước khi tin hiệu-lực grant.
- **#53 cicd-garble ×2 (verify-lane):** cả 2 cicd-monitor trả memory-curation thay verdict → em-main-solo recover (curl 401/health + sqlcmd DB, 2 nguồn khớp). verify-lane garble ≠ mất-việc.
- **prod-config-inspect-before-change:** điều tra prod-DB THẬT (SSH sqlcmd) trước khi kết luận — lật ngược giả thuyết ban đầu ("thiếu quyền" → thực ra "scope gap + cache").
- **Serilog-as-durability-proof:** log-line startup = bằng chứng seeder thắng revoke (mạnh hơn end-state DB đơn thuần).
## Carry (chờ anh)
- `binh.le` (CostControl+Drafter, 11 menu, khác phòng CCM) — có cần thấy NCC?
- 2 account trùng "Lê Thanh Bình": `binh.lethanh` vs `thanh.lethanh` (cùng roles, cùng 20 menu) — dùng cái nào, dọn không?
- Audit authz-vs-menu toàn bộ (nhiều controller master `[Authorize]` trần — gotcha #82) — non-blocking, có thể task riêng.
## State
**Mig 66 · 89 bảng · 509 test (45 Domain + 464 Infra) · gotcha 82 · menu 54 · bundle admin `cFI7ih4a`/user `MxgooVZw` (FE FROZEN — session BE/permission-only) · prod 37 NCC/17 user.**