[CLAUDE] Docs: S54 closeout — IT ticket reassign cross-stack (Run #376) + harvest reconcile + monitor GATE PASS
- STATUS/HANDOFF: S54 IT-staff reassign (ca4b602, test 216, bundle rotate cả 2), user-mem re-ground 20, Phase 9 Ops scope cho NEXT
- Session log 2026-06-08-S54 + cicd-monitor MEMORY (Run #376, H2-gap post-deploy lag)
- H2 harvest GATE PASS 5/5 (residual reconcile verified) + H1 tooling 4-mặt stable
- flag monthly 2026-07-01: sys.tables 93-vs-92, STATUS re-tier S50..S38
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,82 @@
|
||||
# Session 54 — IT staff tự reassign ticket (cross-stack authz)
|
||||
|
||||
**Date:** 2026-06-08
|
||||
**Mode:** HMW-mode ON · 6-agent fan-out (Agent-tool spawn, sequential-gated)
|
||||
**Commit:** `ca4b602` → Gitea Run #376 PASS ~4m18s · prod-verified
|
||||
**Test:** 203 → **216 PASS** (58 Domain + 158 Infra · +13)
|
||||
**Bundle:** admin `DfCfHUE9`→`DmjI8Cmn` · user `_3S0BPJ2`→`YxL_MljK` (cả 2 rotate)
|
||||
**Migration:** giữ 47 (NO new — DepartmentId reuse)
|
||||
|
||||
---
|
||||
|
||||
## Bối cảnh
|
||||
|
||||
Anh `/session-start` → chọn (qua AskUserQuestion) **"2 xong sau đó là 1"** = task 2 (mirror ItTicket reassign → fe-user) trước, task 1 (Phase 9 Ops) sau. Task 2 done + prod-verified → anh `/session-end` (task 1 chưa khởi động, anh dừng).
|
||||
|
||||
## Decision chính (em main — KHÔNG mirror mù)
|
||||
|
||||
Recon phát hiện reassign-UI S53 (fe-admin only) gọi `PUT /assign` (`[Authorize(Roles="Admin")]`) + `GET /users` (`[Authorize(Policy="Users.Read")]`) — **cả 2 Admin-only**. Mirror mù sang fe-user = tái tạo **gotcha #44 (silent 403)**: nhân viên thường bấm nút → 403 câm.
|
||||
|
||||
→ AskUserQuestion: "ai được reassign trên fe-user?" → anh chốt **"Tổ IT tự đổi (cross-stack, đứng-đắn)"**. Persona = nhân viên dept-IT (S52 seed nv.cao/nv.truong) tự đổi tay xử lý trong tổ → phải **nới BE authz**, không chỉ sửa FE.
|
||||
|
||||
## Design (em main)
|
||||
|
||||
- **Authz model:** `canReassign` = caller là **Admin** HOẶC `caller.DepartmentId == Department(Code=="IT").Id`. Assignee bắt buộc thuộc tổ IT đang active.
|
||||
- **Predicate IT:** reuse round-robin S52 verbatim `db.Departments.Where(d => d.Code=="IT" && !d.IsDeleted)`.
|
||||
- **`ICurrentUser` không có DepartmentId** → handler query `db.Users.Where(Id==cu.UserId).Select(DepartmentId)`.
|
||||
- **Gate nút FE (chống gotcha #44):** thêm capability endpoint `GET /it-tickets/assignable-staff` trả `{ canReassign: bool, staff: [{id,fullName}] }` — BE tính 1 lần, FE đọc flag. `[Authorize]` any-auth → KHÔNG bao giờ 403 → 0 console noise. Người ngoài → `{false, []}` (0 leak). BE handler `/assign` mới là cửa bảo mật thật (defense-in-depth).
|
||||
- **Bonus:** siết assignee phải thuộc IT (handler cũ cho gán bất kỳ user — looseness âm thầm) → khớp dropdown scoped. Cả 2 app **hội tụ SHA256-identical** lại (reverse divergence S53).
|
||||
|
||||
## Phân rã (6-agent)
|
||||
|
||||
| Agent | Việc | Kết quả |
|
||||
|---|---|---|
|
||||
| 🟨 implementer-backend | `GetAssignableItStaffQuery` + `AssignItTicketHandler` authz + controller 2 endpoint | build 0/0, DTO contract confirmed |
|
||||
| 🟧 implementer-frontend | 2 app `ItTicketsPage.tsx` + types | npm ×2 PASS, **SHA256 `4bcaf2f…`** |
|
||||
| 🟪 test-specialist | `ItTicketReassignAuthzTests.cs` 13 case | 203→216, 0 fail, no prod bug |
|
||||
| 🟥 reviewer | adversarial pre-commit (authz security) | **PASS** 0 blocker · role-string "Admin" chain-verified |
|
||||
| 🟩 cicd-monitor | post-deploy Run #376 | **PASS** bundle rotate + smoke + Mig 47 |
|
||||
| 👤 em main | design + recon + commit + residual reconcile | — |
|
||||
|
||||
BE ∥ FE parallel (file-disjoint, contract chốt) → test (cần BE compiled) → reviewer → cicd.
|
||||
|
||||
## reviewer — điểm chí mạng
|
||||
|
||||
Verify role-string "Admin" THẬT (gotcha #44 bài học decoy "QTV"): chain trace `AppRoles.Admin="Admin"` → `SeedRolesAsync Role.Name="Admin"` → Identity `GetRolesAsync` trả Name → `JwtTokenService Claim(ClaimTypes.Role)` → `CurrentUserService FindAll(ClaimTypes.Role)` → `cu.Roles.Contains("Admin")` **đúng**. "QTV" chỉ là `ShortName` display (`RoleLabels`), không vào JWT. No `RoleClaimType` override. **Guard không thủng, fail-closed** (itDeptId null → non-admin bị chặn, admin vẫn qua).
|
||||
|
||||
## Residual event (em main single-writer containment)
|
||||
|
||||
3 agent (BE/FE/test) ghi MEMORY.md nhầm `src/Backend/.claude/agent-memory/` (cwd-relative Write khi `cd` vào `src/Backend` cho build) → stray dir + canonical thiếu S54 delta. Em main:
|
||||
1. `git status` bắt `?? src/Backend/.claude/` + nghi (BE/FE/test MEMORY không hiện modified ở canonical).
|
||||
2. Inspect stray = 2 pattern file viết tốt (BE) + stub index.
|
||||
3. Reconcile: move 2 pattern file → canonical `.claude/agent-memory/implementer-backend/` + `rm -rf src/Backend/.claude`.
|
||||
4. Harvest delta (B2/B3): APPEND S54 activity entry vào 3 canonical MEMORY (implementer-backend + implementer-frontend + test-specialist) từ return-message của agent (write mis-landed → em main proxy).
|
||||
|
||||
→ memory `feedback_agent_cwd_relative_memory_misland` (mở rộng `feedback_monitor_residual_write_containment` + `feedback_session_end_memory_write_verify`).
|
||||
|
||||
## cicd Run #376 — evidence
|
||||
|
||||
- Run #376 (run_number 262) · `ca4b602` · success · ~4m18s.
|
||||
- Test gate 216 PASS (CI both projects trước build).
|
||||
- Bundle admin `DfCfHUE9`→`DmjI8Cmn` / user `_3S0BPJ2`→`YxL_MljK` (verified AFTER status=success).
|
||||
- Smoke: `/health/{live,ready}` 200/200 · `GET /assignable-staff` 401 · `PUT /assign` 401 (body) · FE roots 200/200.
|
||||
- Migration prod top = Mig 47, giữ nguyên. Control `/it-tickets/zzz` = 404 (chứng minh 401 là auth gate thật).
|
||||
- **Note (documented):** `PUT/POST` bodyless smoke → IIS trả **411 Length Required** TRƯỚC `[Authorize]` (pre-auth Content-Length check) → dùng `-d '{}'` để thấy 401 thật. Lặp lại #364/#367.
|
||||
|
||||
## Files changed
|
||||
|
||||
**Production (6):**
|
||||
- `src/Backend/SolutionErp.Application/Office/WorkflowAppsFeatures.cs` — REGION 5: +`GetAssignableItStaffQuery`/Handler/`AssignableStaffResult`/`AssignableStaffDto`; `AssignItTicketHandler` +authz +assignee-IT-check.
|
||||
- `src/Backend/SolutionErp.Api/Controllers/ItTicketsController.cs` — `/assign` hạ Authorize + NEW `GET /assignable-staff`.
|
||||
- `fe-{admin,user}/src/pages/office/ItTicketsPage.tsx` — SHA256-identical reassign.
|
||||
- `fe-{admin,user}/src/types/workflowApps.ts` — +2 type.
|
||||
|
||||
**Test (1):** `tests/SolutionErp.Infrastructure.Tests/Application/ItTicketReassignAuthzTests.cs` (13 case).
|
||||
|
||||
**Agent-memory (6):** implementer-backend MEMORY + 2 pattern file (reconciled từ stray), implementer-frontend MEMORY, test-specialist MEMORY, reviewer MEMORY.
|
||||
|
||||
## Defer / NEXT
|
||||
|
||||
- **Task 1 Phase 9 Ops** (anh dừng S54): SMTP email outbound (code-able) · SQL backup register (command-ready) · creds + UAT (anh-infra). Detail trong HANDOFF.
|
||||
- **flag monthly audit 2026-07-01:** cicd `sys.tables=93` vs STATUS 92 (1-count drift, pre-existing).
|
||||
- **Optional:** pre-existing `types/workflowApps.ts` 2-app divergence (fe-admin có `AttendanceReportDto`/fe-user thiếu — không từ S54).
|
||||
Reference in New Issue
Block a user