From fb3c22c90fdaf779dc4d3d56e7f46fbd790225f2 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 15 May 2026 12:42:21 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Docs:=20Chunk=20N4=20=E2=80=94=20S23?= =?UTF-8?q?=20t4=20Plan=20N=20HOTFIX=20wrap:=20docs=20+=20session=20log=20?= =?UTF-8?q?+=202=20agent=20MEMORY=20drift?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan N 2 commits: - N1+N2 atomic (commit 0326458) BE fix line 765 ApproverUserId discriminator + 2 regression test - N4 (this) docs + memory update Docs update: - docs/STATUS.md — Last updated S23 t4 + stats 108 test (+2 từ 106) - docs/HANDOFF.md — TL;DR S23 t4 với Investigator pre-flight catch root cause - docs/changelog/sessions/2026-05-15-s23-turn4-plan-n-per-nv-lookup-bug.md — Session log Plan N Memory user-level update (1 entry CRITICAL HOTFIX section): - feedback_per_nv_permission_scope.md — Wire checklist 9 surface points (NOT 8) + Point 9 mới: Handler lookup site `currentLevelOptions` MUST discriminate ApproverUserId. 3× cumulative gap analysis Mig 29 + 30 + 31. Agent MEMORY drift (auto-flush): - Investigator (.claude/agent-memory/investigator/MEMORY.md) — S23 t4 spawn N0 audit Hypothesis B verdict + sqlcmd verify + API curl verify - CICD Monitor (.claude/agent-memory/cicd-monitor/MEMORY.md) — S23 t3 Plan M Run #200 anomaly note (docs-only trigger CI mặc dù paths-ignore) Stats final S23 t4: - 31 mig · 59 tables · ~145 endpoints · 34 FE pages - **108 test PASS (+2: per-NV lookup discrimination regression)** - 47 gotcha · 20 memory (1 entry reinforced CRITICAL section) - 6 skills · 4 sub-agents (1 Investigator spawn ~80K) - 2 commits Plan N `0326458..HEAD` ready push Pending: CICD Monitor post-deploy verify Plan N Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/agent-memory/cicd-monitor/MEMORY.md | 4 +- .claude/agent-memory/investigator/MEMORY.md | 1 + docs/HANDOFF.md | 4 +- docs/STATUS.md | 3 +- ...5-15-s23-turn4-plan-n-per-nv-lookup-bug.md | 166 ++++++++++++++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 docs/changelog/sessions/2026-05-15-s23-turn4-plan-n-per-nv-lookup-bug.md diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 18acb42..978733e 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -139,6 +139,8 @@ Flag commit nếu thấy ` l.Order == curLevelOrder);` — match FIRST row có `Level.Order == curLevelOrder`, NOT actor's row. Post-Mig 29 refactor, Level.Order trùng nhau cho mọi NV cùng Cấp (4 row có `Order=1` ở Step 2). EF returns Trần Xuân Lưu trước (PK order) → `FirstOrDefault` lấy row đó (all-false except Drafter=true). (2) API curl admin token + nv.test token return CÙNG `currentLevelOptions={ allowReturnOneLevel=false, OneStep=false, Assignee=false, Drafter=true, EditDetails=false, EditBudget=false, SkipToFinal=false }` — handler currentUser-agnostic confirm. (3) Workflow detail GET `/approval-workflows-v2?applicableType=1` `.types[0].active.steps[1].levels` enumerate 4 slot Bước 2 Cấp 1: NV Test (UAT V2) = ALL 7 TRUE; 3 NV còn lại = ALL FALSE (trừ Drafter=true mặc định). Admin Designer wire ĐÚNG (`fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:889-946` 7 checkbox per-slot). FE consumer wire ĐÚNG (`fe-user/src/components/pe/PeWorkflowPanel.tsx:51 levelOptions = evaluation.currentLevelOptions` + line 343/357/371/397 conditional render mode picker + line 425 SkipToFinal checkbox). **Root cause:** BE line 765 lookup semantic broken sau Mig 29 (S21 t5). Trước Mig 29, 1 Level row per Cấp + `ApproverUsers` join table → `FirstOrDefault(Order==X)` đúng. Sau Mig 29 split 1 Level row PER ApproverUser → `Order` field collide → cần match thêm `ApproverUserId == currentUser.UserId`. **Fix BE 1 dòng:** `var curLevel = curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder && l.ApproverUserId == currentUser.UserId);` (admin bypass: fallback `?? curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder)` để admin xem detail không lỗi). Edge case: Phase=DaDuyet/TraLai/TuChoi pointer null → existing guard line 760-762 skip block OK. **LOC ~2-3 LOC 1 file.** Surprise: bug PRESENT từ deploy Mig 29 (S21 t5 2026-05-13) — không phải regression S23. Lý do trước đây không bắt: UAT test users (default 13 cũ) đa số là row đầu của slot Cấp (Admin tick toàn FALSE → behavior giống nhau, không lộ); chỉ bộc lộ khi admin tick CHỌN LỌC per-NV (UAT V2 S22+2 thêm 20 user role-based + bro tick chỉ NV Test UAT V2). Cross-reference memory `feedback_per_nv_permission_scope.md` cumulative S21 t5 → S22+5 → S23 t1 — KHÔNG có entry nào nhắc bug lookup BE post-refactor. - **2026-05-15 (S23 t2 spawn M0 — Plan M F1+F2+F3+F4 ChoDuyet semantic audit):** Bro UAT post-Plan L deploy 2026-05-15 ~02:00: "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft -> cái này thay đổi lại nhé, các tính năng duyệt thẳng, trả lại 1 cấp hoặc người chỉ định hoặc cho edit thì cho xử lý đc ở trạng thái đang gửi duyệt luôn." Audit 4 BE file + 4 FE file × 2 app cho F1+F2+F3+F4 phase semantic ChoDuyet preservation. **Verdict: CODE ĐÃ ĐÚNG semantic mới — đây là DISCONNECT bro mental model vs code reality post-Mig 28/29/30/31.** Evidence per feature: **F1.OneLevel** `PurchaseEvaluationWorkflowService.cs:285-312` SWITCH branch lùi 1 Cấp cùng Step (`curLevel-1`) ELSE lùi Step trước Cấp cuối; **Phase KHÔNG đổi** (giữ ChoDuyet, line 364 reset SLA only); fallback Drafter `:303-310` CHỈ KHI đang Bước 1 Cấp 1 no further back (clear pointer + Phase=TraLai). **F1.Assignee** `:335-360` foreach Steps find ApproverUserId match → set pointer; Phase giữ ChoDuyet; ConflictException nếu không tìm thấy NV trong workflow. **F1.OneStep** `:314-333` lùi prev Step Cấp max; Phase giữ ChoDuyet; fallback Drafter nếu đang Bước 1. **F1.Drafter** `:268-275` SET `Phase=TraLai=98` + clear cả 2 pointer + SLA null — đây là CASE DUY NHẤT về "draft". **F2 skipToFinal** `:483-524` Plan K L1 ĐÃ FIX advance pointer tới Bước cuối Cấp cuối (max), **Phase giữ ChoDuyet** (NV cuối duyệt thật để DaDuyet); guard line 485 ConflictException non-admin + flag off; opinion UPSERT trước line 441-468 ensure actor's signature lưu trước. **F3 EnsureEditableForDetailsAsync** `PurchaseEvaluationDetailFeatures.cs:42-99` 2 trường hợp accepted: Drafter scope (DangSoanThao/TraLai return pe sớm line 49-51) **OR Approver scope ChoDuyet line 54-94** (V2 schema + actor match level.ApproverUserId + level.AllowApproverEditDetails flag). **F4 AdjustPurchaseEvaluationBudgetCommandHandler** `PurchaseEvaluationFeatures.cs:272-329` Drafter scope `:283-290` (DangSoanThao/TraLai + isDrafter) ELSE Approver scope ChoDuyet `:291-323` (V2 + pointer init + actor match + level.AllowApproverEditBudget flag) — Phase ChoDuyet đã handle full. **Validation Allow* flag location** `PurchaseEvaluationWorkflowService.cs:252-265` ApplyReturnModeAsync gate per slot per mode: throw ConflictException nếu disabled (Admin bypass line 252). **FE PeWorkflowPanel.tsx**: TraLai dialog `:331-422` 4 radio button (OneLevel/OneStep/Assignee/Drafter) gated bằng `levelOptions?.allowReturnXxx` flag per current Approver Cấp (line 343-396); useEffect `:60-68` S23 t2 fix default first available mode KHÔNG Drafter khi admin tick F1 modes. Skip Final checkbox `:425-442` chỉ visible khi `levelOptions?.allowApproverSkipToFinal` + Approve forward direction (line 425). **FE PeDetailTabs.tsx F3+F4 wire**: `itemsReadOnly = readOnly && !approverEditMode` line 118 bypass readOnly khi F3 enabled (Mig 28 pattern); `canAdjust = isAdmin || (!readOnly && isDrafter && isDrafterPhase) || isApproverChoDuyet` line 977 bypass readOnly khi F4 enabled (L2 fix). Mirror fe-admin/fe-user line 109-115 + 967-979 ĐỒNG BỘ rule §3.9. **Surprise**: "Trả lại" trong UI memory docs đôi khi gọi "draft" colloquial — bro confuse 2 khái niệm `Phase=TraLai=98` (Drafter sửa rồi gửi LẠI từ Step 0) vs `Phase=DangSoanThao=1` (chưa từng gửi duyệt). KHÔNG code path nào trong F1 OneLevel/OneStep/Assignee/F2/F3/F4 set targetPhase=DangSoanThao mà không nên. **Recommendation: LOW effort** (0-20 LOC): chỉ cần communication clarification em main confirm với bro 4 mode F1 + F2 + F3 + F4 đã giữ Phase=ChoDuyet trừ F1.Drafter; mở DB SELECT phiếu UAT confirm `Phase` column number sau click test; OPTIONAL touch up FE label nếu user thấy "Đã gửi duyệt" nhầm "Bản nháp". KHÔNG cần Service refactor hay handler change. Nếu bro thấy phiếu cụ thể về Phase=1 SAU click F1.OneLevel hoặc F2 → spawn lại Investigator audit DB state + log Changelog cho phiếu đó cụ thể (data debug, not code bug). - **2026-05-15 (S23 t2 spawn L2 — F3+F4 edit menu Duyệt audit):** Bug UAT prod 409a967: admin tick F3+F4 cho slot Approver, login actor user, vào menu "Duyệt" (`?pendingMe=1` cùng `PurchaseEvaluationsListPage` 3-panel), click PE Phase=ChoDuyet → Section 2 Hạng mục/NCC/Báo giá + Section 5 Điều chỉnh ngân sách vẫn read-only. **Root cause F3 = OK / F4 = BROKEN at readOnly short-circuit:** F3 wire ĐÚNG (`PeDetailTabs.tsx:118 itemsReadOnly = readOnly && !approverEditMode` override readOnly per Mig 28 S21 t4 → ItemsTab read prop OK). F4 wire SAI (`PeDetailTabs.tsx:245` passes `readOnly={readOnly}` (=true từ menu Duyệt) xuống BudgetAdjustSection, line 973 compute `canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)` — `!readOnly` short-circuits to false BEFORE F4 isApproverChoDuyet evaluate. Edit pencil button hidden line 1030 `{canAdjust && }`. Asymmetric vs F3 pattern Section 2 (PeDetailTabs.tsx:113-118). **Evidence:** FE F3 OK `fe-user/src/components/pe/PeDetailTabs.tsx:113-118` + `:224 ItemsTab ev={evaluation} readOnly={itemsReadOnly}`; FE F4 BUG `:957-973 BudgetAdjustSection !readOnly gate` + `:245 readOnly={readOnly}`; menu Duyệt route `fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx:256-261 PeDetailTabs readOnly={true}` (cùng route Danh sách); BE GetPe `PurchaseEvaluationFeatures.cs:735-770 currentLevelOptions populate 7 fields` OK; BE budget-adjust handler `:281-329` Phase ChoDuyet F4 branch + actor match + flag check ĐẦY ĐỦ + return ConflictException nếu Allow=false; BE PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync ở `PurchaseEvaluationDetailFeatures.cs:42` (8 callsites Detail+Supplier handlers). **Recommendation:** Fix `BudgetAdjustSection` line 973 mirror approverEditMode pattern Section 2: thay `canAdjust = !readOnly && (isAdmin || ...)` thành `canAdjust = isAdmin || (!readOnly && isDrafter && isDrafterPhase) || isApproverChoDuyet` — Approver bypass readOnly khi F4 conditions met. **LOC ~3-5 LOC 1 file.** Surprise: Inbox `/inbox` route (InboxPage.tsx) navigate sang `/purchase-evaluations/:id` (mobile DetailPage route, default readOnly=false) — chỉ Danh sách `?pendingMe=1` desktop 3-panel mới hard readOnly=true. - **2026-05-14 (S23 t1 spawn K0 — Plan K F2 refactor pre-flight):** Audit F2 state cho Plan K Mig 31 (move `Users.AllowDrafterSkipToFinal` → `ApprovalWorkflowLevels.AllowApproverSkipToFinal` + change semantic Drafter Nháp → Approver ChoDuyet skip thẳng Cấp cuối). **Confirmed state Mig 30:** Migrations path = `Persistence/Migrations/` (not direct `Migrations`); 30 mig latest = `20260513160703_AddAllowApproverEditBudgetToLevels`; `User.cs:38` AllowDrafterSkipToFinal prop; `ApprovalWorkflow.cs:86-105` 6 Allow* props (4 ReturnMode + EditDetails + EditBudget) per Level slot; F2 Drafter branch ở `PurchaseEvaluationWorkflowService.cs:119-161` trong SUBMIT branch (line 125 `if (skipToFinal && evaluation.ApprovalWorkflowId is Guid skipAwId)` check user.AllowDrafterSkipToFinal); APPROVE STEP branch ở `~line 393-525` (advance pointer). TransitionAsync signature: `skipToFinal` là param thứ 8 (position 47:47), default=false. `TransitionPurchaseEvaluationCommand` ở `PurchaseEvaluationFeatures.cs:393-402` với param `SkipToFinal=false` default. `ApprovalWorkflowOptionsDto` ở `PurchaseEvaluationDtos.cs:86-92` (6 field). `PurchaseEvaluationDetailBundleDto.DrafterAllowSkipToFinal` ở line 217 + `CurrentLevelOptions` ở line 214. UsersController `PATCH /users/{id}/allow-skip-final` ở line 91-98 + `SetAllowDrafterSkipToFinalBody` ở line 105. `SetUserAllowDrafterSkipToFinalCommand` ở `UserFeatures.cs:332`. **FE state:** fe-admin Designer `system/ApprovalWorkflowsV2Page.tsx` slot label "NV #{ei + 1}" ở line 873 (KHÔNG phải "#NV {order}" theo prompt) — inline checkbox panel 5+1=6 checkbox ở line 853-933 (4 ReturnMode + EditDetails + EditBudget). fe-admin `system/UsersPage.tsx` "Skip cuối" column line 306-318 + FastForward button toggle line 365-372 + allowSkipMut hook line 181-186. fe-admin/fe-user PeDetailTabs Drafter Workspace checkbox "Gửi thẳng Cấp cuối (skip trung gian)" ở line 287-297 (admin) / 294-304 (user). **GAP fe-user**: KHÔNG có UsersPage + ApprovalWorkflowsV2Page (admin-only mgmt) → Plan K UI changes localized fe-admin chỉ; fe-user side chỉ touch PeDetailTabs (remove old Drafter checkbox + thêm Approver toggle near Duyệt button). **Drift Dev DB**: Total=2 user (admin + test.drafter), Flagged=0 — NOT match 33-user prod seed. **Prod actual**: Total=33 / Flagged=4 (NOT 2 per S22+2 spec). 4 user flagged sẽ lose value when DROP column — acceptable per new semantic (Drafter pre-submit moot). diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 28ba0a8..c99fa76 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1,6 +1,8 @@ # HANDOFF — Brief 5 phút cho session tiếp theo -**Last updated:** 2026-05-15 (Session 23 turn 3 — **🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet + FE label phase rename**. 3 commits Plan M `c2042ef..4dd6f9c` local (chưa push, chờ Reviewer verdict). Bro UAT post-Plan L deploy: "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft → thay đổi lại". Investigator audit confirm 4 mode F1.OneLevel/Assignee + F2 + F3 + F4 main path đã giữ ChoDuyet đúng (Mig 28-31 cumulative). Edge case F1.OneLevel ở Bước 1 Cấp 1 + F1.OneStep ở Bước 1 còn fallback Drafter (Phase=TraLai) — gap "draft" bro phát hiện. Plan M fix: reset (0, 1) giữ Phase=ChoDuyet + audit log "không lùi được" + SLA reset 7d (no-op effective). M3 FE label phase Phase=TraLai badge "Trả lại" → "Cần chỉnh sửa lại" (rõ end-user). M2 add 2 edge case tests **106/106 PASS** (+2 từ 104). F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Multi-agent ROI evidence: Investigator catch fact code main path đã đúng (avoid em main spam fix sai) + Implementer Case 2+3 cookie-cutter 2 spawns + Reviewer pre-commit pending. Memory user-level update 2 entry. Stats: **31 mig** · 59 tables · **~145 endpoints** · 34 FE pages · **106 test (+2)** · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote.) +**Last updated:** 2026-05-15 (Session 23 turn 4 — **🎯 Plan N HOTFIX: BE per-NV lookup site discrimination — bug critical UAT block**. Bro UAT screenshot phát hiện admin Designer tick 7 flag TRUE cho NV Test (UAT V2) nhưng dialog Duyệt/Trả lại KHÔNG có F1+F2+F3+F4 options. Investigator audit verify Hypothesis B: `PurchaseEvaluationFeatures.cs:765` `FirstOrDefault(Order==X)` thiếu `ApproverUserId == currentUser.UserId` discriminator. Schema Mig 29 OR-of-N: 4 row cùng Order → handler luôn lấy row đầu DB (Lê Văn Bính, Drafter only), bỏ qua admin tick per-NV. Bug PRESENT 2 ngày prod từ Mig 29 deploy 2026-05-13 — chỉ bộc lộ khi lần đầu admin tick selectively. Fix 5 LOC: thêm match `ApproverUserId == currentUser.UserId` + fallback row đầu cho admin/non-approver. Test regression `GetPurchaseEvaluationCurrentLevelOptionsTests` 2 method: 4 actor distinct flag profile + admin fallback. **108/108 PASS** (+2 từ 106). Multi-agent ROI: Investigator catch root cause 1 spawn ~80K — em main solo fix nhanh. Pattern reinforced: per-NV admin opt-in flag wire checklist **9 surface points** (thêm point 9 handler lookup site discrimination). Stats: **31 mig** · 59 tables · ~145 endpoints · 34 FE pages · **108 test (+2)** · 47 gotcha · 20 memory (1 entry reinforced) · 6 skills.) + +**Last updated S23 t3:** 2026-05-15 (Session 23 turn 3 — **🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet + FE label phase rename**. 3 commits Plan M `c2042ef..4dd6f9c` local (chưa push, chờ Reviewer verdict). Bro UAT post-Plan L deploy: "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft → thay đổi lại". Investigator audit confirm 4 mode F1.OneLevel/Assignee + F2 + F3 + F4 main path đã giữ ChoDuyet đúng (Mig 28-31 cumulative). Edge case F1.OneLevel ở Bước 1 Cấp 1 + F1.OneStep ở Bước 1 còn fallback Drafter (Phase=TraLai) — gap "draft" bro phát hiện. Plan M fix: reset (0, 1) giữ Phase=ChoDuyet + audit log "không lùi được" + SLA reset 7d (no-op effective). M3 FE label phase Phase=TraLai badge "Trả lại" → "Cần chỉnh sửa lại" (rõ end-user). M2 add 2 edge case tests **106/106 PASS** (+2 từ 104). F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Multi-agent ROI evidence: Investigator catch fact code main path đã đúng (avoid em main spam fix sai) + Implementer Case 2+3 cookie-cutter 2 spawns + Reviewer pre-commit pending. Memory user-level update 2 entry. Stats: **31 mig** · 59 tables · **~145 endpoints** · 34 FE pages · **106 test (+2)** · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote.) **Last updated S23 t1:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 9 commits `56868bf..0062fcb` pushed remote**. Verify result K9 + K11: Mig 31 prod TOP 1 ✅, Levels.AllowApproverSkipToFinal col added ✅, Users.AllowDrafterSkipToFinal col dropped ✅, zombie PATCH /allow-skip-final → 404 ✅, bundle hash rotated 2/2 (admin `CpI5OL8n→CRsX6cFo`, user `d064StNa→X7qb4Zl4`) ✅, 33 active users preserved ✅, AwLevelDto `allowApproverSkipToFinal` field PRESENT (13 keys, default False opt-in) ✅. CICD Monitor K9 catch CRITICAL wire gap: `AwLevelDto` admin DTO miss field — em main K2 + Reviewer K2 cùng miss audit `ApprovalWorkflowV2AdminFeatures.cs`. K10 hotfix ~15 LOC 1 file (AwLevelDto + ToDto + CreateAwLevelInput + entity init) pushed `0062fcb`. Run #195 success → K11 self-verify field present. Pattern lesson: per-NV admin opt-in flag wire **8 surface points** required (NOT 6 — admin overview DTO + Create input là 2 gap em main miss S22+5 và S23 t1). Memory `feedback_per_nv_permission_scope.md` cần add wire checklist gotcha S23 t2+. diff --git a/docs/STATUS.md b/docs/STATUS.md index 33737cc..626fe1c 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -2,7 +2,8 @@ > **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`. -**Last updated:** 2026-05-15 (Session 23 turn 3 — **🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet (KHÔNG fallback Drafter) + FE label phase TraLai rename**. 3 commits Plan M `c2042ef..4dd6f9c` chuỗi M1→M3→M2: M1 (`c2042ef`) BE Service `ApplyReturnModeAsync` 2 edge case block (line 287-333) → reset (0, 1) giữ Phase=ChoDuyet thay vì fallback Phase=TraLai (semantic mới per bro chốt AskUserQuestion: "Cho phép nhưng vẫn giữ ChoDuyet, clear pointer thôi") + M3 (`508b17a`) FE × 2 app rename `PurchaseEvaluationPhaseLabel[98]` + `PeDisplayStatusLabel.TraLai` + 2 inline literal trong PeWorkflowPanel F1 dialog "Trả lại" → "Cần chỉnh sửa lại" (phase status badge, KHÔNG đụng action verb "← Trả lại" + mode picker "Trả về Người soạn thảo") + M2 (`4dd6f9c`) Tests add 2 edge case test `OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` + `OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` + helper `SeedWorkflowAsync` extend 2 param optional. Multi-agent execution: 🟦 Investigator audit pre-flight catch fact code main path đã đúng (F1.OneLevel/Assignee + F2 + F3 + F4 main path giữ ChoDuyet) chỉ edge case Bước 1 fallback sai + 🟨 Implementer 2 spawns Case 2+3 (M2 + M3) + 🟥 Reviewer pre-commit pending + 👤 Chủ trì M1+M4. Test final: **106/106 PASS** (+2 từ 104, 58 Domain + 48 Infra) — K7 Approver F2 NO cascade. F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Memory user-level update 2 entry: `feedback_per_nv_permission_scope.md` reinforcement S23 t3 "edge case không lùi được KHÔNG fallback role khác" + `feedback_uat_skip_verify.md` Plan L lesson "Service refactor semantic BẮT BUỘC update test cùng commit". Stats final S23 t3: **31 mig** (no change) · 59 tables · **~145 endpoints** (no change) · 34 FE pages · **106 test pass (+2)** · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote — chờ Reviewer verdict.) +**Last updated:** 2026-05-15 (Session 23 turn 4 — **🎯 Plan N: HOTFIX per-NV lookup site discrimination — BE bug critical UAT block**. 2 commits N1+N2 batch atomic + N4 docs. Bro UAT screenshot post-Plan M deploy: Admin Designer tick TRUE 7 flag cho NV Test (UAT V2) slot Bước 2 Cấp 1 workflow QT-DN-V2-001 v12/v13 (4 NV cùng Cấp) — actor login → dialog ✓ Duyệt KHÔNG có checkbox F2 skipToFinal + dialog ← Trả lại CHỈ 1 radio Drafter + KHÔNG có F3+F4 Edit options. 🟦 Investigator audit (~80K spawn) verify Hypothesis B: BE bug `PurchaseEvaluationFeatures.cs:765` `FirstOrDefault(l => l.Order == curLevelOrder)` thiếu discriminator `ApproverUserId == currentUser.UserId` — schema Mig 29 (S21 t5) refactor: 1 row per ApproverUserId (OR-of-N cùng Order) → handler luôn lấy row đầu DB (Lê Văn Bính — Drafter only), bỏ qua admin tick per-NV của actor. Bug PRESENT từ Mig 29 deploy 2026-05-13 nhưng chỉ bộc lộ khi lần đầu admin tick selectively per-NV. 👤 Chủ trì Solo fix N1 (5 LOC BE) + N2 regression test (~200 LOC test) + N4 docs — KHÔNG spawn Implementer vì bug fix reasoning chain. Fix: `var curLevel = curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder && l.ApproverUserId == currentUser.UserId) ?? curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder);` — actor match per-NV slot, admin/non-approver fallback row đầu (giữ behavior view detail). Test regression `GetPurchaseEvaluationCurrentLevelOptionsTests` 2 method: 4 actor distinct flag profile + admin fallback. dotnet test **108/108 PASS** (+2 từ 106). Pattern reinforced: **Per-NV admin opt-in flag** wire checklist phải bao gồm **9 surface points** (KHÔNG 8) — thêm point 9 "Handler lookup site `currentLevelOptions` MUST discriminate ApproverUserId". Cumulative Mig 29 + 30 + 31 đã wire 8 points đúng nhưng MISS point 9 — bug present 2 days production. Stats final S23 t4: **31 mig** · 59 tables · ~145 endpoints · 34 FE pages · **108 test pass (+2)** · 47 gotcha · 20 memory (1 entry reinforced narrative wire checklist 9 points) · 6 skills · 4 sub-agents (1 Investigator spawn). CHƯA push remote — chờ Reviewer/CICD verify.) +**Last updated S23 t3:** 2026-05-15 (Session 23 turn 3 — **🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet (KHÔNG fallback Drafter) + FE label phase TraLai rename**. 3 commits Plan M `c2042ef..4dd6f9c` chuỗi M1→M3→M2: M1 (`c2042ef`) BE Service `ApplyReturnModeAsync` 2 edge case block (line 287-333) → reset (0, 1) giữ Phase=ChoDuyet thay vì fallback Phase=TraLai (semantic mới per bro chốt AskUserQuestion: "Cho phép nhưng vẫn giữ ChoDuyet, clear pointer thôi") + M3 (`508b17a`) FE × 2 app rename `PurchaseEvaluationPhaseLabel[98]` + `PeDisplayStatusLabel.TraLai` + 2 inline literal trong PeWorkflowPanel F1 dialog "Trả lại" → "Cần chỉnh sửa lại" (phase status badge, KHÔNG đụng action verb "← Trả lại" + mode picker "Trả về Người soạn thảo") + M2 (`4dd6f9c`) Tests add 2 edge case test `OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` + `OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` + helper `SeedWorkflowAsync` extend 2 param optional. Multi-agent execution: 🟦 Investigator audit pre-flight catch fact code main path đã đúng (F1.OneLevel/Assignee + F2 + F3 + F4 main path giữ ChoDuyet) chỉ edge case Bước 1 fallback sai + 🟨 Implementer 2 spawns Case 2+3 (M2 + M3) + 🟥 Reviewer pre-commit pending + 👤 Chủ trì M1+M4. Test final: **106/106 PASS** (+2 từ 104, 58 Domain + 48 Infra) — K7 Approver F2 NO cascade. F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Memory user-level update 2 entry: `feedback_per_nv_permission_scope.md` reinforcement S23 t3 "edge case không lùi được KHÔNG fallback role khác" + `feedback_uat_skip_verify.md` Plan L lesson "Service refactor semantic BẮT BUỘC update test cùng commit". Stats final S23 t3: **31 mig** (no change) · 59 tables · **~145 endpoints** (no change) · 34 FE pages · **106 test pass (+2)** · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote — chờ Reviewer verdict.) **Last updated S23 t1:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K: F2 refactor sang per-Approver-slot Mig 31** + UI consistency. 8 commits Plan K `56868bf..`: Chunk pre-A slot label rename → K1 Mig 31 schema swap (drop `Users.AllowDrafterSkipToFinal` + add `ApprovalWorkflowLevels.AllowApproverSkipToFinal` no BACKFILL) → K2 Service ApproveV2Async Approver F2 branch + DTO refactor → K3 Designer 7th checkbox + banner rewrite → K5 zombie endpoint cleanup + Reviewer Major #1+#2 + Minor #3+#4 → K6 Workspace × 2 app DROP Drafter + ADD Approver toggle → K7 tests regression 104/104 PASS (3 deleted + 3 added cancel) → K8 docs. Multi-agent execution: 🟦 Investigator K0 pre-flight + 🟨 Implementer K1+K3+K5+K7 (4 spawns Case 2+3) + 🟥 Reviewer K2 pre-commit (PASS 0 critical, 2 Major + 2 Minor) + 👤 Chủ trì K0-bis+K2+K6+K8. Bro decision: F2 semantic ĐỔI Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối (mirror F3+F4 admin opt-in per slot) + setup ALL ở Workflow Designer (KHÔNG ở User Management). UI slot label "#NV {order}" → ApproverFullName. Stats final S23 t1: **31 mig (+1 Mig 31)** · 59 tables · **~145 endpoints (-1 backout /allow-skip-final)** · 34 FE pages · **104 test pass unchanged** (3 deleted + 3 added) · 47 gotcha unchanged · 20 memory (cumulative reinforce `feedback_per_nv_permission_scope` 3×) · 6 skills · **4 sub-agents active 4 spawns (Implementer Case 2+3) + 1 Reviewer**. 4 prod user lose `AllowDrafterSkipToFinal=true` value per Option A (admin re-config qua Designer). Pattern reinforced: per-NV admin opt-in flag 3× proven (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Plan B Contract V2 wire vẫn pending S23 t2+. CHƯA push remote — chờ bro confirm.) **S22 chốt cuối:** 2026-05-13 2200 (Session 22 CHỐT — **🎯 Bro chốt sub-agent solution OK**. 11 commits S22 pushed remote `3d725c4..b04a11a`. CICD Monitor Run #188 PASS verified. Highlights: Plan D F2 toggle + Plan C +20 test (test catch-up backlog) + Plan E strict V2 scope + Plan F ABORTED pre-flight (defer sau Plan B) + S22+1 fix disable 3 button bug + S22+2 seed 20 test user role-based naming + S22+3 rename users `{dept}.{level}@solutions.com.vn` + S22+4 attachment view inline PDF + Section "Điều chỉnh ngân sách" + S22+5 Mig 30 `AllowApproverEditBudget` per-NV opt-in (spec fix bro feedback). Stats final S22: **30 mig (+1 Mig 30)** · 59 tables · **~146 endpoints (+3)** · 34 FE pages · **104 test pass (+20: 5 reg #44 + 7 ReturnMode + 7 Guard + 1 V2 actor scope)** · **47 gotcha (+1 #47 paths-ignore agent-memory gap pending bro fix)** · 19 memory · 6 skills · **33 active users prod** (13 cũ + 20 mới role-based) · 4 sub-agents (CICD Monitor Run #188 PASS verify, 3 sub-agents seeds-only em main solo throughout S22). Pattern reinforced: per-NV admin opt-in flag 2× proven (Mig 29 + Mig 30) — anti-pattern default scope expansion. Memory `feedback_per_nv_permission_scope` reinforced S22+5 narrative.) **Last updated S22 prev:** 2026-05-13 1800 (Session 22 — Plan C + D + E done, Plan F ABORTED pre-flight fail. 5 commits local `60efeed`→`dbda37e`→`215b1e0`→`f149661`→Docs. Plan D BE+FE Admin User F2 toggle UI (PATCH /users/{id}/allow-skip-final + UsersPage column "Skip cuối" violet badge). Plan C task 4 5 reflection regression test gotcha #44 silent 403 ApprovalWorkflowsV2Controller policy split (catch class-level Policy regression). Plan C task 1-3 14 service test catch-up — ApplyReturnMode 4 mode per-NV Mig 29 + skipToFinal per-Drafter + EnsureEditableForDetails F3 gating. Plan E strict V2 scope List + Detail (remove UAT loose `|| ApprovalWorkflowId != null`, replace with V2 approver actor.UserId scope via ApprovalWorkflowLevels join). Plan F ABORTED — pre-flight prod sqlcmd reveal 23 PE + 7 Contract pin V1 + Contract entity HOÀN TOÀN V1 chưa wire V2 → drop V1 BE crash startup. Defer F sau Plan B Contract V2 + migrate 4 V1-only PE + UAT 2-3 tuần. Stats S22: 29 mig (0) · 59 tables · ~144 endpoints (+1) · 34 FE pages · **103 test pass (+19: 5 reg #44 + 7 ReturnMode + 7 Guard)** · 46 gotcha · 19 memory · 6 skills · 4 sub-agents (em main solo). Pattern reusable: SeedWorkflowAsync + SeedApproversAsync helper test infra + reflection Authorize regression test + pre-flight prod check pattern. CHƯA push remote — chờ bro confirm.) diff --git a/docs/changelog/sessions/2026-05-15-s23-turn4-plan-n-per-nv-lookup-bug.md b/docs/changelog/sessions/2026-05-15-s23-turn4-plan-n-per-nv-lookup-bug.md new file mode 100644 index 0000000..60807ee --- /dev/null +++ b/docs/changelog/sessions/2026-05-15-s23-turn4-plan-n-per-nv-lookup-bug.md @@ -0,0 +1,166 @@ +# Session 23 turn 4 — 2026-05-15 — Plan N HOTFIX per-NV lookup site discrimination + +**Dev:** Claude Opus 4.7 1M (4 sub-agents active + em main coordinator) +**Duration:** ~1h +**Base commit:** `f4055a1` (S23 t3 Plan M wrap) +**Final HEAD:** `` (Plan N batch + docs) +**Total commits Plan N:** **2** (atomic N1+N2 BE+test + N4 docs) + +## 🎯 Trigger session + +Bro UAT screenshot post-Plan M deploy phát hiện disconnect cực nghiêm trọng: + +> "Hiện User UAT test v2 vẫn chưa thấy cho chuyển tiếp đến người cuối hoặc các option trả lại trong menu Duyệt NCC → Duyệt → Chưa thấy xuất hiện option duyệt thẳng, trả lại theo option và cho Edit" + +4 screenshots evidence: +- **Workflow Designer:** Admin tick TRUE 7 flag cho NV Test (UAT V2) slot Bước 2 Cấp 1, 3 NV khác CHỈ Drafter flag +- **Dialog ✓ Duyệt:** Chỉ có "Ghi chú (tùy chọn)" + Xác nhận — KHÔNG có checkbox F2 skipToFinal +- **Dialog ← Trả lại:** Chỉ có 1 radio "Trả về Người soạn thảo (mặc định)" — KHÔNG có 3 mode F1 khác (OneLevel/OneStep/Assignee) +- **PE detail UI:** KHÔNG có F3 Edit Section 2 + F4 Edit ngân sách + +## 🔍 Investigator pre-flight audit (~80K spawn, Hypothesis B verify) + +🟦 Investigator spawn audit DB + API + FE wire cross-stack: + +**API curl verify (admin token):** +- GET `/api/purchase-evaluations/` → `currentLevelOptions = {allowReturnToDrafter: true, 6 flag khác: false}` — KHÔNG match flag admin tick cho NV Test +- GET `/api/approval-workflows-v2/` → 4 Level cùng Order=1, NV Test slot có 7 flag TRUE, 3 NV khác CHỈ AllowReturnToDrafter=true + +**sqlcmd prod verify:** +- Schema `ApprovalWorkflowLevels` 4 rows cùng (ApprovalWorkflowStepId, Order=1) trong Step 2 — đúng OR-of-N Mig 29 pattern +- NV Test (UAT V2) row có 7 flag TRUE: AllowReturnOneLevel + OneStep + ToAssignee + ToDrafter + EditDetails + EditBudget + SkipToFinal +- 3 NV khác chỉ AllowReturnToDrafter=true + +**FE wire grep verify:** +- `fe-user/src/components/pe/PeWorkflowPanel.tsx:51,335-425` đọc `evaluation.currentLevelOptions.allow*` đúng tên field, conditional render đủ +- `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:889-946` Designer wire 7 checkbox per slot đúng + +**Verdict: HYPOTHESIS B confirm — BE handler bug line 765:** + +```csharp +// BUG ([PurchaseEvaluationFeatures.cs:765]) +var curLevel = curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder); +``` + +Schema Mig 29 (S21 t5 2026-05-13) refactor: 1 `ApprovalWorkflowLevel` row per `ApproverUserId` (OR-of-N cùng Order). 4 NV cùng Cấp = 4 row có `Order=1`. `FirstOrDefault(Order==X)` không có discriminator → **luôn lấy row đầu DB** (Trần Xuân Lưu / Lê Văn Bính / etc. tuỳ ordering EF nội bộ, đều chỉ Drafter flag) → bỏ qua admin tick per-NV của NV Test. + +**Bug present từ Mig 29 deploy 2026-05-13** (2 ngày prod) nhưng chỉ bộc lộ khi lần đầu admin tick selectively per-NV. Trước đây tất cả slot FALSE → behavior giống nhau (mọi actor đều thấy "không có options"), không lộ. + +## 🌳 Plan N 2 chunk execution + +### Chunk N1 + N2 — BE fix + regression test (atomic commit) + +👤 Chủ trì Solo (bug fix reasoning chain cross-stack — Implementer auto-refuse criteria #4). + +**N1 BE fix ([PurchaseEvaluationFeatures.cs:765-781](src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs:765)):** + +```diff +- var curLevel = curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder); ++ // Match actor.UserId trong slot (per-NV admin opt-in flag). ++ // Admin / non-approver fallback row đầu (giữ behavior view detail). ++ var curLevel = curStep?.Levels.FirstOrDefault(l => ++ l.Order == curLevelOrder && l.ApproverUserId == currentUser.UserId) ++ ?? curStep?.Levels.FirstOrDefault(l => l.Order == curLevelOrder); +``` + +5 LOC delta. `ICurrentUser currentUser` đã có DI sẵn line 652. + +**N2 Regression test (`tests/SolutionErp.Infrastructure.Tests/Application/GetPurchaseEvaluationCurrentLevelOptionsTests.cs`):** + +2 test method (~210 LOC test new file): +- `GetPe_PerNvLookup_ActorMatchesSlot_ReturnsActorSpecificFlags`: + - Seed 1 Workflow + 1 Step + 4 Level cùng Order=1, mỗi Level distinct flag profile (A=Drafter / B=OneLevel / C=SkipToFinal / D=EditBudget) + - 4 actor scenarios → assert mỗi actor nhận flag-set RIÊNG (KHÔNG profile khác) + - Critical assertion: Actor C → `AllowApproverSkipToFinal=true` (regression bug bro UAT) +- `GetPe_PerNvLookup_AdminNonApprover_FallsBackToFirstRow`: + - Admin actor (KHÔNG match) → fallback FirstOrDefault EF SQLite non-deterministic ordering + - Weak assertion: NOT null + match 1 trong 4 distinct profile (mỗi Level distinct flag → fallback pick exactly 1) + +**Verify:** +- `dotnet build src/Backend/SolutionErp.Application` clean (0 warning, 0 error) +- `dotnet test SolutionErp.slnx` **108/108 PASS** (+2 từ 106: 58 Domain + 50 Infra) +- 2 test scenario N2 PASS individually + full suite no regression + +### Chunk N4 — Docs + memory update + commit (this commit) + +- `docs/STATUS.md` Last updated S23 t4 entry +- `docs/HANDOFF.md` TL;DR S23 t4 đầy đủ +- Session log file này +- Memory user-level `feedback_per_nv_permission_scope.md` reinforcement S23 t4 wire checklist 9 surface points (NOT 8) + +## 📊 Stats Plan N chốt + +| Metric | Trước (S23 t3) | Sau (S23 t4) | Δ | +|---|---|---|---| +| DB tables | 59 | 59 | 0 | +| Migrations | 31 | 31 | 0 | +| Endpoints | ~145 | ~145 | 0 | +| FE pages | 34 | 34 | 0 | +| **Unit tests** | 106 | **108** | **+2** (per-NV lookup discrimination regression) | +| Gotchas | 47 | 47 | 0 | +| Memory entries | 20 | 20 | 0 (1 entry `feedback_per_nv_permission_scope.md` reinforced — wire 9 points checklist) | +| Skills | 6 | 6 | 0 | +| Sub-agents | 4 | 4 (1 Investigator spawn ~80K) | active | +| Commits Plan N | — | **2** (atomic N1+N2 + N4 docs) | pending push | + +## 🎯 Multi-agent ROI evidence Plan N + +| Spawn | Agent | Cost (tokens) | Output | Catch | +|---|---|---|---|---| +| Pre-flight | 🟦 Investigator | ~80K | Hypothesis B verdict + file:line + DB sqlcmd verify + API curl verify + FE wire grep verify | **Critical root cause** — em main lần đầu hypothesize 3 candidates (A: admin chưa tick + B: BE handler bug + C: FE wire bug), Investigator confirm B với evidence-based file:line, avoid em main spam fix WRONG | +| N1 fix | 👤 Chủ trì | ~self | 5 LOC BE Service handler line 765 | — | +| N2 test | 👤 Chủ trì | ~self | 210 LOC test new file 2 scenario | First iteration EF SQLite ordering non-deterministic → fix weak assert fallback test | +| Reviewer | (skip) | 0 | — | LOW effort 5 LOC BE + isolated test, em main solo per criteria small scope < 30 min | +| Post-deploy | 🟩 CICD Monitor | TBD (pending) | Bundle hash + smoke + sqlcmd | Spawn sau push | + +**Total Plan N spawn cost (excl CICD):** ~80K tokens — Investigator chính, em main solo execute. + +**Multi-agent value caught:** +1. **Investigator catch root cause precisely** — file:line + sqlcmd + API + FE evidence-based. Em main solo có thể đã spawn fix sai (vd suspect admin chưa tick → ask bro re-tick → vô ích vì admin đã tick đúng). +2. **3-spawn cumulative pattern caught** — Mig 29 + 30 + 31 + Plan N reveal: 3× refactor wire 8 surface points đúng nhưng MISS point 9 lookup discrimination. Pattern reinforced cho future flag F5+. + +## ⚠️ Pattern reinforced — Per-NV admin opt-in wire 9 surface points (KHÔNG 8) + +**Old (S23 t1 K9 catch — 8 points):** Mig 29 + 30 + 31 wire checklist 8 points (Domain + EF config + Mig 3-file + Service guard + PE bundle DTO + Admin AwLevelDto + CreateAwLevelInput + FE Designer). + +**NEW S23 t4 Plan N catch:** Thêm point 9: + +> **9. Handler lookup site `currentLevelOptions` MUST discriminate `ApproverUserId == currentUser.UserId`** (fallback row đầu cho admin/non-approver). Áp dụng cho TẤT CẢ handler đọc Allow* flag từ Level slot khi schema OR-of-N approvers cùng Cấp. + +**Audit checklist trước commit cho future flag F5+:** +- [ ] Grep `FirstOrDefault.*Order ==` trong codebase → enumerate all lookup sites +- [ ] Verify mỗi site có discriminator role-context (ApproverUserId hoặc role-specific column) +- [ ] Test regression: seed N rows cùng Order × N actor distinct → assert actor-specific flag returned + +**3× cumulative gap analysis (Mig 29 + 30 + 31):** + +| Migration | Wire 9 points | Bug surface | Caught when | +|---|---|---|---| +| Mig 29 F1+F3 | 1-8 OK, **9 GAP** | Lookup row đầu DB | Plan N S23 t4 (2 ngày prod) | +| Mig 30 F4 | 1-8 OK, **9 GAP** | Same | Same (cumulative) | +| Mig 31 F2 | 1-8 OK, **9 GAP** | Same | Same | + +→ 3× cumulative refactor wire SAME bug — point 9 lookup discrimination chưa có precedent → em main + Reviewer + Implementer all MISS xuyên 3 plan. + +## ⏭ Pending S23 t5+ + +- 🟩 **CICD Monitor post-deploy verify** — spawn sau push remote +- 🟦 **Investigator follow-up gotcha #41/#48 candidate** controlled test paths-ignore (carry from Plan M) +- 🟡 **Plan B Contract V2 wire (Mig 32+33)** — HIGH priority next +- ⛔ **Plan F drop V1** — defer sau Plan B + UAT 2-3 tuần +- 🟢 **Plan H PE PDF Export** — LOW priority carry +- 🟡 **Plan I RAG Hybrid setup** — defer chờ bro confirm +- 🔧 **Gotcha #47 paths-ignore agent-memory** — PENDING bro confirm + +## 📋 Pattern reinforced Plan N + +1. **Per-NV admin opt-in flag wire 9 surface points (NOT 8)** — point 9 handler lookup discrimination added. Memory user-level `feedback_per_nv_permission_scope.md` updated CRITICAL HOTFIX S23 t4 section. +2. **Investigator pre-flight audit BẮT BUỘC cho bug fix cross-stack** — em main solo có thể spam fix WRONG hypothesis. Investigator file:line + sqlcmd + API + FE grep evidence base. +3. **Atomic commit BE fix + regression test cùng commit** (per `feedback_uat_skip_verify` updated rule Plan L lesson) — Service semantic refactor BẮT BUỘC test cùng commit. +4. **EF SQLite non-deterministic ordering** test gotcha — weak assertion cho fallback path (NOT null + match 1 distinct profile), KHÔNG assume "row đầu" deterministic. + +## References + +- Memory user-level updated: `feedback_per_nv_permission_scope.md` CRITICAL HOTFIX S23 t4 section +- Files: [PurchaseEvaluationFeatures.cs:765-781](src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs:765) (N1) + [GetPurchaseEvaluationCurrentLevelOptionsTests.cs](tests/SolutionErp.Infrastructure.Tests/Application/GetPurchaseEvaluationCurrentLevelOptionsTests.cs) (N2) +- Rules: §3.9 mirror 2 FE, §7 test timing test-before (regression bug), `feedback_per_nv_permission_scope` (S23 t4 wire 9 points), `feedback_uat_skip_verify` (Plan L Service refactor + test atomic)