Files
solution-erp/.claude/agent-memory/investigator-codebase/MEMORY.md
2026-07-27 09:39:31 +07:00

71 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Investigator-Codebase Agent — Persistent Memory
> **Persistent diary cross-session.** Auto-injected first ~200 lines at spawn (L1 HOT). Update BEFORE every stop; keep entries to 1 line (gotcha #53 truncation — index hit 28KB @S148, hard-cap 24.4KB). Detail → `archive/2026-07.md` + `archive/_INDEX.md` (sha-keyed substring, Ctrl-F) + git `d2f52ba` + `archive/2026-0{5,6}.md` + `runs/*/sub-*.md`. Renamed S39: investigator → investigator-codebase (internal; external → investigator-api).
## 🎯 Role baseline
Read-only INTERNAL audit SOLUTION_ERP. Tools: Read/Grep/Glob/Bash + 5 RAG MCP. Output: concise <500w + file:line. Skills: contract-workflow + permission-matrix + ef-core-migration.
- MINE: sqlcmd schema scan, EF/grep, controller/authz audit, migration diff, count grounding
- NOT: external docs/CVE/lib investigator-api · write implementer · test test-specialist · architecture decision em main
## 📌 Recent invest (1-line digest; full detail → archive/runs/git)
- **S155 (07-27) PE xóa-phiếu màn DUYỆT [24 finding, `runs/2026-07-27-S155-pe-delete-approver/sub-invest-be-1.md`]:** DELETE `/api/purchase-evaluations/{id}` ĐÃ (`PurchaseEvaluationsController.cs:146`, authz = class `[Authorize]` TRẦN, 0 policy action-level toàn file) nhưng guard allow-list `{DangSoanThao,TuChoi}` (`PurchaseEvaluationFeatures.cs:1404`) chặn ChoDuyet. `Remove()`=**SOFT** gián tiếp qua `AuditingInterceptor.cs:56-62` (DeletedModified+IsDeleted/DeletedAt/DeletedBy) lớp #81-EXT indirect. Lũy kế: `PeBudgetAccumulator.cs` 2 hàm/4 call-site DUY NHẤT (0 read-site khác, Reports 0-hit PE); ChoDuyet ăn số **2 chỗ** `ComputeAsync:54` PrevSubmitted(ChoDuyet+DaDuyet) + `ComputePendingAsync:114` PendingSelected(∉{Nháp,DaDuyet,TuChoi}+winner); `TraLai` KHÔNG triệt tiêu; `TuChoi` rơi khỏi 100% phép cộng. Không dòng `IsDeleted` nào trong query dựa global `HasQueryFilter` (`PurchaseEvaluationConfiguration.cs:84`), `IgnoreQueryFilters`=0-hit soft-del TỰ rớt, không sửa accumulator. Snapshot Mig 67 KHÔNG re-compute. 🔴 Cascade 6 FK (`:75-80`) **không bao giờ chạy** soft con `BaseEntity` (no IsDeleted) lại rác ẩn, chưa lộ mọi read-site join từ PE root. **Git khảo cổ TRÚNG:** "chỗ cho hủy" = nút Từ chối, gỡ `6db195d` 12-06-2026 S60 (anh Kiệt "Duyệt hoặc Trả về thôi"); guard `PurchaseEvaluationWorkflowService.cs:101-106` còn sống + tự trỏ "Xóa phiếu khi còn Bản nháp" = **chính cái lỗ** S155 phải . Q7: 0 tiền lệ hủy chứng-từ in-flight; khuôn gần nhất `CancelMeetingBookingHandler` (`MeetingFeatures.cs:457`, Status=Cancelled NOT IsDeleted + owner-or-admin check). Tag `[s155, pe-delete-approver, s60-tuchoi-removal-hole, soft-delete-no-cascade]`
- **S155 (07-27) PE xóa-phiếu FE/authz-slice [18 finding, `runs/2026-07-27-S155-pe-delete-approver/sub-invest-fe-2.md`]:** Khối HÀNH ĐỘNG = `components/pe/PeWorkflowPanel.tsx:458-506` **byte-identical 2 app** (md5 `02671be6…`, cùng PeDetailTabs/PeListPanel/ListPage) sửa-1-bên-là-sót. Gate 3 tầng: `?pendingMe=1``readOnly={!pendingMe}` (`ListPage:590`/`:676`) · `next=workflow.nextPhases` BE-source (`:257`) · `blockedByV2Level` (`:99-103` isAdmin actorcurrentApproval.approvers). 🔴 Plumbing xóa ĐÃ NỐI SẴN vào màn duyệt: `onDelete={del.mutate}` truyền `ListPage:573`+`:668` (`api.delete('/purchase-evaluations/:id')` `:90`) nhưng nút chết `PeDetailTabs:445` đòi `mode==='workspace' && canEditPhase && !readOnly`; nút hiện chỉ `:460` `phase===DangSoanThao`. **Authz 2 tầng:** PE vùng FE = **0-hit** `usePermission|PermissionGuard` (guard chỉ sống Master+Users, khuôn `DepartmentsPage:101 action="Delete"`); `menuKeys.ts` KHÔNG const `Pe_*` (chỉ root `:23`) `Pe_*` sinh BE `MenuKeys.cs:134-141`, seed `DbInitializer:1870-1877`, FE khớp bằng regex `Layout.tsx:120` (fe-admin `:107` **LỆCH**, thiếu WfView). 🔴 `Pe_*` **KHÔNG ∈ `MenuKeys.All`** `Program.cs:82-89` không đăng-ký policy `Pe_*.{action}` (0 dynamic provider) dùng `[Authorize(Policy="Pe_…")]` = policy-chưa-tồn-tại; **dùng được ngay = `PurchaseEvaluations.Delete`** (root All, + `GetMyMenuTreeQuery:66-72` cho `Pe_*` KẾ THỪA cờ CRUD từ root 0 key mới, 0 migration). Ma trận enumerate `db.MenuItems` (`PermissionFeatures.cs:20`) nên ô Delete đã hiện sẵn. Q6: thêm-nút KHÔNG kích 4-place (0 route/page/staticMap `Layout.tsx:56-106`); `Pe_*` resolve nhánh regex `:119-134` KHÔNG qua staticMap. Tag `[s155, pe-delete-fe-authz, two-app-byte-identical, pe-keys-not-in-All]`
- **S155 (07-27) PE menu/trash/dept-head [21 finding, `runs/2026-07-27-S155-pe-delete-approver/sub-invest-menu-3.md`]:** 🔴 Menu = **SEED DB** không hard-code FE 4 mục con `Pe_<Code>` khai `DbInitializer.cs:1874-1877` (`"Duyệt"`=`:1877`); thêm mục = restart API, **0 migration** (upsert `:1888-1914` chỉ sửa `Order`, **KHÔNG đụng Label** rename phải qua `labelBackfill:1918-1924` HOẶC `DisplayLabel` admin-UI `MenuDtos.cs:14` ghi **CHỈ ăn fe-user**, fe-admin luôn Label gốc). 6-chỗ-mirror (không phải 4): +2 seed permission SONG SONG `:2092-2096`/`:2492-2496`; `menuKeys.ts` FE **0-hit `Pe_*`** (sinh regex `Layout.tsx:120`, fe-admin `:107` thiếu `WfView` = lệch sẵn). Đổi KEY = mồ côi **13 row/key × 10 key** (Permissions Dev) giữ key chỉ đổi label. **`Đã duyệt` chạy được HÔM NAY**: `phase=` đã đủ 3 tầng (`Controller:23``Features:565``:620`), DaDuyet=7, FE `:39/:75/:395` sẵn `?type=1&phase=7`, 0 route mới. `pendingMe` KHÔNG phải param BE FE rẽ `/inbox` (`ListPage:61-68`) rồi lọc DaGuiDuyet client-side `:182`. Tự verify: `IgnoreQueryFilters` **0** src/Backend nhưng **15 hit tests** (khuôn `Suppliers.IgnoreQueryFilters().CountAsync()` `SupplierImportV2DedupTests`); `b.HasQueryFilter(`=12. An-toàn accumulator = **cấu trúc**: `PeBudgetAccumulator.cs:42`/`:100` dựng `peers` FRESH mỗi lần chỉ nếu helper-dùng-chung/sửa `:84`/tái-dùng-đường-snapshot. 🎁 Changelog `: BaseEntity` (no IsDeleted) + query lọc CHỈ `PurchaseEvaluationId` (`:1424`) lịch sử phiếu-xóa đọc được MIỄN PHÍ; cái chôn DETAIL bundle (chỗ `IgnoreQueryFilters` thứ 2). 0 tiền lệ thùng-rác/restore (`IsDeleted=false` 0-hit; hit "khôi phục" duy nhất = STRING `EmployeeFeatures.cs:162` = ghost-wire). 🔴 **"Trưởng phòng" TÊN, CHƯA ĐỜI**: `Department.ManagerUserId` (`Department.cs:9` cmt "TPB") = **0/10 Dev** + **2 FE hardcode `managerUserId: null` ở payload SỬA** (`DepartmentsPage.tsx:43`/`:57`) tự-xoá sau mỗi lần sửa phòng; 5 hit BE toàn DTO-passthrough. `Level.Order`=trình-tự KHÔNG thứ-bậc (`ApprovalWorkflow.cs:86`); "Kết thúc tại Cấp 2"=`AllowApproverFinalize` F5. `PositionLevel.TruongPhong` **33/33 NULL** (V1-legacy, V2 không đọc); role `DeptManager` 8 user nhưng không nói PHÒNG. 3 lối: ManagerUserId(0-mig,vá-2-FE+nhập-tay) · cờ `AllowApproverDelete` per-Level (1 AddColumn, **khớp khuôn F2/F3/F4/F5 4× cùng bảng**) · roleDeptId(có-data,nghĩa-lỏng). Tag `[s155, menu-seed-db, phase-param-exists, changelog-survives-softdelete, dept-head-name-only]`
- **S153 (07-26) ctx soft-memory spec [fable-real]:** thư `0df10df4` sàn-7 spec 9-task/24-check (`runs/2026-07-26-S153-ctx-adopt/sub-fable-invest-S153.md`). Findings đắt: SE **0 content-ts-reader sống** (session_ctx.py = git-ts+mtime; C10 tự khai thiếu nguồn LATENESS) rào-2 canonical `_TS_KEY_RE` đặt session_ctx.py, máy-7-phép = EXTEND python (tái dụng `_SECRET_RES` 1-nguồn) KHÔNG .ps1 mới (2-sự-thật); `secrets_sweep` rglob TRỌN session-dir `_mind` tự vào lưới @snapshot §3; `_mind` mới-nhất-ở-TRÊN `_context` FLOW ở-CUỐI (bẫy chép-khuôn); (36)(b) máy-$0/pause + vai @2-bookend O-1 carve-out KHỎI CẦN (pause §1 NGỪNG-spawn giữ nguyên); C11(b) filter `sub-*.md`+`^sub-<role>[-.]` đặt tên `sub-ctx-audit-*` lọt lưới ngay (kẽ trio `harness-*-return` đã queue #8 fix); roster 21 > hmw_width 20 = owner-note; (35)-(38) vết OWNER-DELEGATED khuôn `hmw_width._ratified_by`. Tag `[s153, ctx-mind-spec, ts-reader-absent, one-source-regex]`
- **S148 (07-23) PE tree DUP project by-space:** `FLOCK 01`/`FLOCK01` = **path-A TWO real Project rows** diff-by-space. Tree groups on `p.projectId` GUID (`PurchaseEvaluationsListPage.tsx:192-197`, display projectCode); PE→Project FK `ProjectId` (`PurchaseEvaluation.cs:15`) JOIN-resolved (`PeFeatures.cs:579/:647`) — NO denorm string. CRUD zero-trim (`ProjectFeatures.cs:80` validator, `:95/:138` exact `Code==` dupe-check). Both seeds exact-Code key emit ONLY no-space: `SeedRealMasterDataAsync:2776` (UNGATED :128) + `SeedDemoMasterDataAsync:2681` (gated :117 OFF-prod). Space=legacy/manual. Blast=3 tables ProjectId (Contracts/PeWorkItemBudgets/PurchaseEvaluations); only `PeWorkItemBudgets` UNIQUE(ProjectId,WorkItemId) filtered [IsDeleted]=0 (`PeWorkItemBudgetConfiguration.cs:27`). Direction: KEEP no-space (in seed→idempotent), RETIRE space (not-in-seed); soft-del survivor→seed RE-CREATES (#75/#76). Dev only FLOCK 01 pair (FLOCK03/CAL01 space-twins prod-only); prod UNVERIFIED (SSH dies on SQL-client, S134). Tag `[s148, pe-tree-dup-project-by-space, path-A-two-rows, no-normalize-crud]`
- **S147 (07-23) PE attach TraLai edit-block:** FE-ONLY block, BE zero phase-guard (`PurchaseEvaluationsController.cs:15` class `[Authorize]` only; handlers check exists only `PurchaseEvaluationAttachmentFeatures.cs:66/:160`). Gate = raw `readOnly` PROP not phase (`GeneralAttachmentsSection` :3555/:3577 hide-when-readOnly); List detail HARDCODE `readOnly=true` (:574/:669) ⇒ TraLai loại khỏi SCREEN không khỏi editable-SET (workspace `PeListPanel.tsx:88` isEditablePhase GỒM TraLai). fe-admin MIRROR. BE no-guard CỐ Ý (S78). Fix FE: `attachEditable=isDrafter&&isEditablePhase`. Tag `[s147, pe-attach-tralai, fe-only-screen-gate]`
- **S141 (07-20) adap đợt-9/10 5-lane [fable-clone-ensemble, 5/5 clean]:** (ii)-nới-có-vết đủ đóng dangling-head (3/3 squash-benign, `.session-counter.json`); tick-head=wal-commit. byte-safe⟂tolerant-EXTRACTION 2-lỗ tách → pin regex `\"?([0-9a-fA-F]{8,64})\"?`. Roster-count 3-tầng (prose/glob/pointer). refine⟂lead-omission = discriminator WHOSE-memory. Detail → `runs/…S141…`.
- **S138 (07-17) adap 3-bản 17-07 [fable-real]:** 3/3 FIT; delta hẹp hub PHẢI grep landing-site trong luật SỐNG (SE chép 4-nhãn lỗi vào `reviewer.md:46` = adopt-verbatim kế-thừa lỗi nguồn). Detail → `runs/…S138…`.
- **S135/S134/S133 (07-17) PE budget invest [fable-real, #53→sub-file cứu trọn]:** S134 lũy-kế lệch-tiền-đề row1 (`PeBudgetAccumulator.cs:42-48` PrevSubmitted ĐÃ gồm ChoDuyet; DaDuyet-only chỉ row2 :50-60) predicate 1-bản helper Features:962+WorkflowService:1018. S133 freeze-at-DaDuyet 4-assignment (`PurchaseEvaluationWorkflowService.cs:861/:899/:931/:1053`, Mig67). S135 tick H24 single-entry `session-start:181-206`. Detail → archive + runs.
- **S128 (07-16) adap-16-07 [fable-real, garble→on-behalf]:** RAG config in-repo (`AI_INFRA\claude-rag\lib\projects.py:39` owner-final, `rag.json:26` override); nhãn-đo "calibrated" chỉ C1; ghi-đĩa-trong-lúc-làm cứu draft khi garble. Detail → runs.
- **S126 (07-16) worker-pin + all-inherit sweep [opus×2]:** 14/14 opus+max (no-tier lane resolve opus DÙ lead=Fable); B1 284→15 floor-sống; mega-line blow Read-cap → perl-window. `[s126, h23-precedence]`
- **S125 (07-16) adap-review [fable-real]:** auto-memory 50 tracked/0 porcelain; 14/14 opus+effort:max; residue all-inherit floor-sống 2 (`fable-real.md:37`+`fable-clone.md:43`) vá; verifier-suspect-first.
- **S122 (07-15) retro-harvest [6/6 clean]:** 🔴 0/7 mất 7/7 XONG synthesis-sai-chỗ (orphan đo TÊN không NỘI-DUNG); lane-RỖNG≠mất; lane tự truy git đừng tin brief.
- **S116/S117 (07-13) PE negative-quote + giá-chốt RANGE [fable]:** neg-quote FE-ONLY (BE/DB nhận âm; quote sống `details[].quotes` KHÔNG `suppliers[].quotes`). S117 ApprovedPrice WRITE 1-site `ApplyApprovedPriceOnFinalize`; HĐ-gen KHÔNG đọc ApprovedPrice (GiaTri=SUM IsSelected). endsBeforeCeo=config-lie #78 (detail :1152 / list :647).
- **[archived → archive/2026-07.md + git]** S87-S114 PE recon (multi-winner IsSelected per-hạng-mục Mig65 · WinnerQuoteTotal DERIVED · budget gate PURE-ROLE · V2 routing=ApproverUserId LINEAR) · Supplier-import-v2 (ClosedXML, Mã NCC=Code, IsPublic fail-closed, import authz-mismatch) · S105 BVAAU auth RCA (relative-`/api` prod-404) · S104 30-field · S98-S101 harness · Office/HRM/Governance recon. Ctrl-F `_INDEX.md`.
## 📋 Patterns proven
### Schema scan sqlcmd
```
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -Q "..." # runtime API (primary)
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Design -Q "..." # ef tooling
```
Prod: `ssh vietreport-vps` powershell-wrapper `sqlcmd -S '.\SQLEXPRESS' -d SolutionErp -E …` — ⚠️ **SSH session DIES whenever SQL-client loads** (S134/S148 re-confirm); prod-DB effectively UNVERIFIABLE this path, flag + give query for implementer. **2 LocalDB distinct**: `_Dev`=runtime · `_Design`=`dotnet ef` default. Queries: `sys.columns/indexes/tables`, `__EFMigrationsHistory`, dup-detector `REPLACE(Code,' ','')`.
### Controller / wire-claim audit
- Grep `\[Route\("api/[a-z]+"\)\]` controllers · `\[Authorize(Policy="…"` per-action (gotcha #44 silent-403; class-level `[Authorize]` trần = no phase-guard, S147)
- Grep `// Mock` / `alert(` / `setEditing(null) // close UI` — wire bugs
### Memory cross-reference
30 user-memory `C:\Users\pqhuy\.claude\projects\D--…\memory\MEMORY.md`. Key: designtime_runtime_db · workflow_fanout_reliability · canonical_spec_over_broadcast · root_cause_over_symptom · cardinality_change_grep_consumers.
## ⚠️ Anti-patterns
❌ Skip MEMORY update · ❌ Vague "seems/probably" · ❌ Missing file:line · ❌ >500 words · ❌ Scope drift to architecture rec (em main decides) · ❌ Multi-line MEMORY entries (index truncates >24.4KB)
## 🧠 SOLUTION_ERP context (numbers canonical → docs/STATUS.md; re-ground don't trust)
- **DB:** Dev `SolutionErp_Dev` · Design `SolutionErp_Design` (distinct) · Prod `.\SQLEXPRESS`/`SolutionErp`/`vrapp` via SSH `vietreport-vps`
- **Migration path:** `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/*.cs` (NOT root). Last-schema Mig 67 `AddPeApprovedBudgetSnapshot` (S133); ~67 mig.
- **Counts (docs/STATUS.md canonical):** ~88 SQL tables (count `.ToTable()` snapshot NOT DbSet) · ~253 endpoints · 68 FE pages · 54 menu keys (`MenuKeys.cs`) · ~354 test · ~71+ gotchas · 14 sub-agents (worker-tier-pin S124)
- **Tech:** .NET 10 Clean Arch (Api→Application←Domain + Infra) + CQRS MediatR + EF Core 10 + 2 React Vite TS (fe-admin :8082 + fe-user :8080) + SQL Server + Gitea CI + IIS
- **Prod:** api/admin/eoffice.solutions.com.vn · Gitea `git.baocaogiaoduc.vn/…/solution-erp` (Actions API `/actions/tasks` NOT `/runs`, cache stale ~2min #46)
- **Auth:** `admin@solutions.com.vn/Admin@123456` (full) / `nv.test@solutions.com.vn/TestUser@123456` (Drafter). Password ≥12.
## 🔄 Active workflow schemas (V1 + V2 coexist post-S17)
- **V1 Mig 21 flat** — `WorkflowDefinition` pin cũ, match Dept+PositionLevel.
- **V2 Mig 22-31** — `ApprovalWorkflow` pin PE/Contract, match `ApproverUserId` 1-1 OR-of-N cùng Cấp. Steps(Phòng)>Levels(Cấp). Proposal V2 Mig38, WorkflowApps Mig39. (Mig25 IsUserSelectable · 26 LevelOpinions UPSERT · 30 AllowApproverEditBudget · 31 SkipToFinal→ApproverLevel · 53 cờ-gấp+CeoThreshold · 54 giá-đề-xuất · 58 IsWinner multi + AllowApproverFinalize · 65 Quote.IsSelected per-hạng-mục · 66 ApprovedPrice Min-Max · 67 budget-freeze snapshot).
- **State PE 5:** Nháp / Đã gửi duyệt / Trả lại (TraLai=98) / Từ chối (TuChoi, REMOVED-path S60) / Đã duyệt
- **Mode Trả lại 4:** OneLevel / OneStep / Assignee / Drafter (Phase=TraLai). 3 đầu giữ ChoDuyet lùi pointer. Admin bypass `level.Allow*`.
- **Seeds gating:** `SeedDemoMasterDataAsync` gated `DemoSeed:Disabled` (OFF prod) · `SeedRealMasterDataAsync` UNGATED (62 dự án + 71 hạng mục Excel, per-code idempotent, exact-Code key #75/#76 resurrect risk).
## 🔄 Curate log
- **S148 (07-23):** hard-cap rewrite 28.2→~14KB — collapsed multi-line S133-S148 → 1-liner, dropped stale pointer-list (S87-S114 → grouped 1-line, verbatim in archive/git). Single-writer inv-cb (hook @28KB).
- **Prev:** S125 (23.9→~15KB, 14 lines→archive/2026-07.md) · S71/S70/S40. Verbatim byte-exact in `archive/`.
## S149 (2026-07-24→25) — 10 invocation gộp 1 entry [LEAD GHI ON-BEHALF @S150 — M9/B3 theo H2-F3 Coverage-miss (10 lượt → 0 delta sổ, ngoài lưới C11(b) by-design); nguồn: sub-file trong `runs/2026-07-24-S149-{dong-du-5-vong,hoi-tu-bookend,owner-gated-3-khoan}/` + `runs/2026-07-25-S149-adap-backlog/sub-invest-backlog.md`]
- 10 lượt invest @S149: chẩn-đoán **"SE = rules-đủ, máy-thiếu"** (0 script GHI `.session-counter.json` → chuỗi "đo rồi như chưa đo": backfill S146 · tally-rơi S148 · ×27≠33) — ĐỨNG qua gate reviewer-Fable, thành spec hội-tụ-bookend M1-M9. Backlog adap phân-loại **A-12/B-6/C-1/D-3** ("nợ REPORT không phải nợ ADOPT") → wave 6-lane land 23/23. O-2 nguồn thật = depth-axis → **posture-A**.
- #53 trong 10 lượt (ghi sổ garble @S150): L1 **ngược-#53** (return OK, ĐĨA không — vớt journal) · L5 kinh-điển (đĩa cứu) · L7 mất-cả-hai (bù deep-pass). Sub-class mới: ghi-đĩa-trong-lúc-làm = CẦN, KHÔNG ĐỦ.
## S152 (2026-07-25) — R1 retention-note không-fabricate [M9 on-behalf lead]
- **KHÔNG fabricate — uncertain thì nói "uncertain" + reason:** finding không chứng được từ đĩa/DB phải khai uncertain kèm lý-do thiếu-chứng, KHÔNG lấp bằng suy-đoán (tiền lệ tốt: A/018 S117 — verify prod-DB `Phase=ChoDuyet` trước khi phán "lũy kế đúng", thay vì đoán theo lời UAT). Claim ≠ chứng: mọi số kèm lệnh. Tag `[s152, r1-retention, no-fabricate-uncertain-reason]`