Files
solution-erp/.claude/agent-memory/investigator-codebase/MEMORY.md
pqhuy1987 ea075bac00 [CLAUDE] Docs: S93 closeout — PE project-code (Run #473) + Harness-16 MFE adopt + harvest
Closeout 2 task: (1) PE tree node Du an hien MA du an (8ee8f3e Run #473
prod-verified) (2) adopt Harness-16 MFE memory-fidelity-EVAL (9580098, 2-workflow PASS).

- STATUS + HANDOFF: prepend S93 (mega-line via PowerShell, S92->Prev).
- session log 2026-06-29-S93-pe-projectcode-harness16-mfe.md.
- error-ledger E-006: AS-10 2nd-fire recurrence (WF1 3-agent residual caught+reverted;
  WF2 explicit-return-only -> 0 residual) + containment guard verified S93.
- harvest (B3 single-writer APPEND): investigator-codebase (MFE ground-truth extraction-spec)
  + reviewer (MFE adoption review WF2 PASS) diaries.
- user-MEMORY index compacted 23.7->7.8KB (hook), all 38 entries kept, vocab-collision
  lesson folded into 2-workflow entry.

State unchanged (Mig 59 · 88 tables · 434 test · gotcha 76 · bundle CDn40vpV/CoPmPBg7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:01:19 +07:00

100 lines
24 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. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 `archive/` on-demand · L3 RAG `search_memory` just-in-time. Keep entry ≤ 1.5K chars (gotcha #53).
> Full verbatim history pre-S40 → git `d2f52ba` + `archive/2026-05-q1..q4.md` + `archive/2026-06.md`. 🗺️ **Lookup map: `archive/_INDEX.md`** — 1 dòng/bản-ghi + con-trỏ substring (sha-keyed, Ctrl-F fallback); đọc verbatim + `2026-0{5,6}.gist.md` theo nhu cầu. **S80 curate: moved 15 recon entries S65→S76 + 2× 06-20 → archive.**
> **Renamed S39:** investigator → investigator-codebase (internal half; external → investigator-api).
---
## 🎯 Role baseline
Read-only INTERNAL audit SOLUTION_ERP codebase. Tools: Read, Grep, Glob, Bash + 5 RAG MCP. Output: concise findings <500 words + file:line refs. Skills: `contract-workflow` + `permission-matrix` + `ef-core-migration`.
## 🚫 Split boundary (S39)
- MINE: internal SQL/EF/grep/reference mirror, sqlcmd schema scan, controller audit, migration diff, count grounding
- NOT: external docs/CVE/lib `investigator-api` · write implementer · test test-specialist · architecture decision em main
---
## 📋 Patterns proven (apply confidently)
### Schema scan via sqlcmd
```bash
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -Q "..." # runtime API (primary)
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Design -Q "..." # ef tooling
ssh vietreport-vps "sqlcmd -S .\SQLEXPRESS -d SolutionErp -U vrapp -P '...' -Q '...'" # prod
```
Queries: `sys.columns`, `sys.triggers`, `__EFMigrationsHistory`, `COUNT(*)`, `sys.indexes`.
**Gotcha 2 LocalDB distinct** (`feedback_designtime_runtime_db`): `_Dev`=runtime (appsettings.Development), `_Design`=`dotnet ef` default. Prod password fallback `C:\inetpub\solution-erp\api\appsettings.Production.json` khi `$env:PROD_DB_PASSWORD` empty.
### Controller / wire-claim audit
- Grep `\[Route\("api/[a-z]+"\)\]` enumerate controllers · `\[Authorize(Policy = "..."` per-action policy (gotcha #44 silent 403 class-level quá strict)
- Grep `// Mock` / `alert(` / `setEditing(null) // close UI` wire claim bugs · `IActionResult` vs `ActionResult<T>`
### Smoke verify catalog
Bearer từ `POST api.solutions.com.vn/api/auth/login` status matrix expected vs actual + file:line evidence.
### Memory cross-reference
30 user-memory tại `C:\Users\pqhuy\.claude\projects\D--...\memory\MEMORY.md` (index). Key: per_chunk_commit · uat_skip_verify · designtime_runtime_db · per_nv_permission_scope · ef_migration_backfill_reorder · workflow_fanout_reliability · canonical_spec_over_broadcast (S79).
### External research → DEFER `investigator-api` (split S39)
---
## ⚠️ Anti-patterns
Skip MEMORY update · Vague "seems like/probably" · Missing file:line · >500 words · ❌ Scope drift to architecture recommendation (em main decides)
---
## 🧠 SOLUTION_ERP context essentials (S80 verified — re-ground từ docs/STATUS.md canonical)
- **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 `/Migrations/`). **57 mig**, last `AddPeSuggestedPriceNotes`.
- **Counts S80 (canonical docs/STATUS.md):** 57 mig · **88 SQL tables** (count `.ToTable()` ModelSnapshot NOT DbSet) · ~253 endpoints · 68 FE pages · **54 menu keys** (BE `MenuKeys` const) · **354 test** (45 Domain + 309 Infra) · **71 gotchas** (format `### N.`) · 30 user-memory · 6 skills · **11 sub-agents** (all-inherit H8)
- **Tech:** .NET 10 Clean Arch (Api→Application←Domain + Infra) + CQRS MediatR + EF Core 10 + 2 React 19 Vite 8 TS 6 (fe-admin :8082 + fe-user :8080) + SQL Server + Gitea CI + IIS
- **Prod:** api/admin/eoffice.solutions.com.vn · Gitea `git.baocaogiaoduc.vn/vietreport-admin/solution-erp` (Actions API `/api/v1/repos/.../actions/tasks` NOT `/runs` 404, cache stale ~2min gotcha #46 — cross-check VPS mtime). Bundle live admin `CsJetgZH`/user `BVS0ApIm` (Run #330).
- **Auth:** `admin@solutions.com.vn/Admin@123456` (full) / `nv.test@solutions.com.vn/TestUser@123456` (Drafter). Response `accessToken`+`refreshToken`+`user`. Password ≥12 chars.
---
## 🔄 Active workflow schemas (V1 + V2 coexist post-S17)
- **V1 Mig 21 flat** — `WorkflowDefinition` pin PE/Contract cũ. Match Dept+PositionLevel.
- **V2 Mig 22-31** — `ApprovalWorkflow` pin PE/Contract mới, match `ApproverUserId` 1-1 OR-of-N cùng Cấp. Steps (Phòng) > Levels (Cấp). PE + Contract đã wire V2 (Mig 32+33 S29). Proposal V2 (Mig 38). WorkflowApps skeleton (Mig 39).
- Mig 25 IsUserSelectable · Mig 26 PE LevelOpinions UPSERT · Mig 29 Allow* per-NV · Mig 30 F4 AllowApproverEditBudget · Mig 31 SkipToFinal→ApproverLevel · Mig 53 cờ-gấp+CeoApprovalThreshold · Mig 54 giá-đề-xuất PRO/CCM+giá-chốt · Mig 56 ProInitial/ProAdjust · Mig 57 ghi-chú-giá.
- **State machine PE 5 trạng thái:** Nháp / Đã gửi duyệt / **Trả lại (TraLai=98)** / Từ chối (REMOVED-S60 hard-guard) / Đã duyệt
- **Mode Trả lại 4 option per-Level:** OneLevel (lùi 1 Cấp) / OneStep (lùi Bước trước) / Assignee (pick NV đã ký) / Drafter (Phase=TraLai). 3 mode đầu giữ ChoDuyet lùi pointer. Admin bypass `level.Allow*`.
---
## 📅 Recent activity (compressed — full verbatim → `archive/2026-06.md` via `archive/_INDEX.md`)
- **2026-06-29 S93 Harness-16 MFE ground-truth (WF1 `wf_4c63e1bd`):** ⭐ MFE denominator extraction (deterministic, status-field KHÔNG grep): marks Active(-High) (`ACTIVE-MARKS.md`) + AS-class + Active-Guards (`error-ledger.md` Verified✅) + recurring-gotcha (`gotchas.md`); **dedup-by-AS-cross-reference** (gotcha/guard mà AS-row trỏ `#N`/`AS-N` → gộp) = **29 item**. Per-role sub-coverage: denom = numbered anti-pattern/NEVER trong `agents/<role>.md`; numer = diary `agent-memory/<role>/MEMORY.md` (KHÔNG project/user — bẫy trùng-tên H15-v3) match exact-id OR ≥2 content-word sau stop-list (đánh bại leading-verb-9/9-giả). Codified `scripts/mfe-eval.ps1` + engine §H. (WF1 self-write reverted AS-10; em-main harvest single-writer.) → _INDEX.
- **2026-06-25 PE S89 AllowApproverFinalize NOTE feasibility (anh Kiệt FDC — "phiếu kết thúc cấp X không CEO" 3 surface):** ⭐ **GAP CONFIRMED — BE field MỚI cần.** `AllowApproverFinalize` (Mig 58 per-Level bool `ApprovalWorkflow.cs:142`) HIỆN CHỈ expose qua `currentLevelOptions` (CURRENT level only) `PurchaseEvaluationFeatures.cs:981` + DTO `PurchaseEvaluationDtos.cs:123` + FE type `purchaseEvaluation.ts:412`. **`approvalFlow` DTO KHÔNG carry flag**: flow-level record `PurchaseEvaluationApprovalFlowLevelDto` (`Dtos.cs:154-158`) chỉ {Order,Name,Approvers,Status}; build loop `PurchaseEvaluationFeatures.cs:1050-1065` KHÔNG project `l.AllowApproverFinalize`. **LIST DTO KHÔNG có** (`PurchaseEvaluationListItemDto:6-45`, 0 finalize field). **DASHBOARD = reuse list** → cũng thiếu. ⇒ FE detail CHỈ biết finalize khi actor LÀ approver cấp finalize (current). Để render "phiếu này kết thúc ở Bước Y/Cấp X — không CEO" cho MỌI người (kể cả non-approver, list, dashboard) → BE PHẢI expose field mới: bundle `bool EndsBeforeCeo`+`string? FinalizeStepName/FinalizeLevelName` (compute = first Step/Level có AllowApproverFinalize=true trong aw.Steps loop đã load `:985`) + mirror lên list-item + dashboard. BE finalize logic `PurchaseEvaluationWorkflowService.cs:861` (AUTO khi matchingLevel.AllowApproverFinalize → DaDuyet bỏ CEO, NO threshold/role-gate). **FE detail placement (cả 2 app SHA-IDENTICAL** sha `e477c939` PeWorkflowPanel + `49b39404` PeDetailTabs): note nên đặt đầu `PeWorkflowPanel.tsx` flow `<ol>` (sau h3 "Quy trình duyệt" :245, trước :256) — banner emerald mirror style :599 existing approverFinalize callout (chỉ trong dialog hiện). Detail đã có precedent: TraLai banner `PeDetailTabs.tsx:370-378`, V2 current-level banner `PeWorkflowPanel.tsx:346-365`. Edge: phiếu **DaDuyet** → đổi copy "đã kết thúc tại <X>, không qua CEO" (past-tense); copy đề xuất "⚑ Quy trình này duyệt tới <StepName/Cấp X> là KẾT THÚC — không trình CEO". MIRROR 2-app bắt buộc. → _INDEX.
- **2026-06-25 PE FROZEN patchwork audit AREA-6 (FE consumers BOTH apps — multi-winner + mirror-drift + dead-code + #70):** ⭐ Post-Mig58 multi-winner (`isWinner` bool) NOW implemented (supersedes 06-24 "SINGLE" recon). **All 6 PE components + 3 PE pages SHA-IDENTICAL fe-admin↔fe-user** (PeDetailTabs/PeHeaderForm/PeUrgentChips/PeListPanel/PeWorkspaceCreateView/PeWorkflowPanel). type files DIFFER but ONLY badge-tint (-700→-800 ink) + comments (intentional per-app UX, struct fields identical incl ccmBudgetPeriodAmount/isWinner/8 Allow flags). **RESIDUAL single-winner BUG (low):** `PeListPanel.tsx:168` renders `p.selectedSupplierName` (list-card winner label) → BLANK when ≥2 joint winners (BE list-DTO `PurchaseEvaluationFeatures.cs:615,706` projects from `SelectedSupplierId` which BE sets null when count≠1 `PurchaseEvaluationDetailFeatures.cs:431`). List DTO has NO isWinner/winner-list field; detail-view multi-winner correct everywhere (8 detail consumers all converted: computeGiaChaoThau:78, missingForApproval:195, submitChecklist:229, ThongTinChonThau:1949/1970, Section-B row4:1578, CreateContractDialog:2171 fallback `||selectedSupplierName`, HangMucCard toggle:2560). **#70 stale-echo ASYMMETRY:** budget Section-B CCM-period editor gates `ccmPeriodMut.isPending||peFetching` (`PeDetailTabs.tsx:1563`) but PRO sibling row3/row8 `adjustMut` gates `.isPending` ONLY (`:1548,:1649`, NO peFetching) — rapid PRO row3→row8 absolute-set pair can echo stale ev.* pre-refetch (same #70 class CCM guards). 3 peFetching sites exist (945/1268/1736). **Stale comment** PeDetailTabs:2507 references `AddItemDialog` "giữ(dead)" but function DELETED (grep 0 def) — misleading comment only, not dead code. PeWorkflowsPage admin-only = by-design (workflow config not mirrored). → _INDEX.
- **2026-06-25 PE FROZEN patchwork audit AREA-2 (budget matrix + Section B 3-col, Mig 56/59):** ⭐ **BAND-AID CONFIRMED** `SetPeCcmBudgetPeriodCommand+Validator+Handler` (`PeSuggestedPriceFeatures.cs:153-199`) is a BUDGET setter (writes `pe.CcmBudgetPeriodAmount`) MISPLACED in a price-feature file; SHOULD live in `PeWorkItemBudgetFeatures.cs` (where UpdatePeBudgetPro/Ccm sit). Test+comments already self-flag (PeCcmBudgetPeriodSetterAuthzTests.cs:14). ⚠️ **Role-gate ASYMMETRY (intentional, NOT a bug — but inconsistent surface):** CCM budget setters = flat role-gate (CostControl|Admin fail-closed): UpdatePeBudgetCcm `PeWorkItemBudgetFeatures.cs:160-165` + SetPeCcmBudgetPeriod `PeSuggestedPriceFeatures.cs:176-181`. BUT the PRO sibling `/budget-adjust``AdjustPurchaseEvaluationBudgetCommandHandler` `PurchaseEvaluationFeatures.cs:349-394` = **phase/drafter/workflow-slot gate** (Drafter when Nháp/TraLai; Approver+AllowApproverEditBudget F4 when ChoDuyet) NOT role-PRO. So "PRO col" & "CCM col" of same Section-B row 3 have DIFFERENT authz models (PRO=drafter-flow on `pe.BudgetPeriodAmount`; CCM=role-flow on `pe.CcmBudgetPeriodAmount`). Both per-PE fields (NOT PeWorkItemBudget per-pair — that's the matrix rows Initial/Adjustment). Section-B **"Dự án" col = intentional FE-only "—"** hardcoded (`PeDetailTabs.tsx:1520,1542,1596,1606`; Domain `PeWorkItemBudget.cs:19` "chưa wire BE — sau mới có người dự án nhập") — documented, NOT a stub-bug. Controller class-level `[Authorize]` only (`PurchaseEvaluationsController.cs:15`) → handler gates ARE the boundary. Tests solid (PeWorkItemBudgetTests 30+ cases, PeCcmBudgetPeriodSetterAuthzTests 7). → _INDEX.
- **2026-06-24 PE 3-change recon (Lưu Trần/anh Kiệt CCM — multi-winner + budget=0-valid + per-step terminal):** ⭐ (A1 winner) cardinality=**SINGLE** scalar `PE.SelectedSupplierId Guid?` (`PurchaseEvaluation.cs:28`); cmd `SelectPurchaseEvaluationWinnerCommand(PEId,Guid?)` `PurchaseEvaluationDetailFeatures.cs:392` handler `:398-423` (null=clear S85-C5); ctrl `/select-winner` `PurchaseEvaluationsController.cs:167-172`. winnerQuoteTotal=**DERIVED** SUM `Quote.ThanhTien WHERE SupplierId==Selected` 3 sites: read `PurchaseEvaluationFeatures.cs:1089` + submit-guard `PurchaseEvaluationWorkflowService.cs:191` + ccm-delegate `:868`. ⚠️ Quote ALREADY has `IsSelected bool` (`PurchaseEvaluationQuote.cs:15` per-item winner, UNUSED by total). Multi-winner = NEW junction OR reuse IsSelected + change predicate `==Selected``IN selectedSet`. FE single `<Select>` `PeDetailTabs.tsx:961-991` (NccSelectorRow). (A2 budget=0) BLOCKED 2 layers: validator `.GreaterThan(0).When(HasValue)` create `:55` + UpdateDraft `:233` + budget-adjust `:339`; submit-guard `effectiveBudget<=0`→missing `PurchaseEvaluationWorkflowService.cs:213-216` (effective=`BudgetPeriodAmount??ProInitialAmount` S85-D3). FE `effBudget<=0` `PeDetailTabs.tsx:211,237` canSubmit `:242`. To allow 0: relax `>0``>=0` validator + submit `<0` + FE. winnerQuoteTotal-gate SEPARATE `:194`. (A3 terminal) `ApprovalWorkflowLevel` already has 6 per-slot bool flags (`ApprovalWorkflow.cs:104-131`: AllowReturnOneLevel/OneStep/ToAssignee/ToDrafter/EditDetails/EditBudget/**SkipToFinal**); `CeoApprovalThreshold decimal?` on WORKFLOW `:43`. Finalize branches `ApproveV2Async`: terminal `nextIdx>=steps.Count` `:905-916` + ccm-delegate opt-in `:854-891` + skipToFinal `:803-844`. "Approve-ends-here" = NEW per-Level bool mirror SkipToFinal but set Phase=DaDuyet (like ccm-delegate but no threshold). Designer FE = `fe-admin/ApprovalWorkflowsV2Page.tsx`. **schema-decision forks→em-main:** (1) multi-winner storage: reuse Quote.IsSelected vs new PE↔Supplier winner-junction (2) budget=0 vs budget-null distinction post-relax (3) terminal flag scope per-Level vs per-Step + interaction w/ CeoApprovalThreshold (both finalize → ordering). → _INDEX.
- **2026-06-25 PE AREA-4 UpdatePeDraft edit-preserve audit (gotcha #73-class hunt, FROZEN Mig 59):** ⭐ **LATENT #73-CLASS BUG FOUND:** `UpdatePeDraftHandler` absolute-sets `entity.HoSoLink = request.HoSoLink` (`PurchaseEvaluationFeatures.cs:280`) BUT `PeHeaderForm.tsx` PUT payload (admin+user `:112-120`) OMITS hoSoLink → editing header CLEARS HoSoLink (Mig 52) to null. HoSoLink set separately in `PeDetailTabs.tsx:2012-2020` HoSoLinkRow (re-sends all absolute fields but only IT edits link). Two PUT call-sites to SAME /purchase-evaluations/{id} endpoint are mutually destructive. PeHeaderForm reachable as EDIT (`PurchaseEvaluationCreatePage.tsx:13,26` editId from URL `?id=`, deep-link "Sửa header"). Field table: TenGoiThau/DiaDiem/MoTa/PaymentTerms absolute-set BUT PeHeaderForm re-sends all (safe); HoSoLink absolute-set + OMITTED (BUG); ApprovalWorkflowId/Budget/ExpectedRemaining/WorkItemId all null-safe `if(x is not null)` `:285-297` (S85 fix). ExpectedRemaining absolute-set in budget-adjust `:423-424` (FE sends both, OK). Require-workflow NOT duplicated: CREATE-validator `:48` + submit-guard `PurchaseEvaluationWorkflowService.cs:237` = different lifecycle (coherent defense-in-depth); UPDATE-validator correctly omits. Tests SOLID: PeUpdateDraftWorkflowPreserveTests (3 null-safe cases incl ApprovalWorkflowId+Budget) + PeSubmitGuardAndBypassTests (14) + PurchaseEvaluationDraftGuardTests (8). NO HoSoLink-preserve test (test-gap for the bug). → _INDEX.
- **2026-06-25 PE AREA-3 finalize/price audit (FROZEN Mig 59 Run#346 — patchwork hunt):** ⭐ TWO finalize mechanisms COEXIST + BOTH LIVE (NOT dead): (1) `AllowApproverFinalize` per-Level AUTO config (Mig 58) `PurchaseEvaluationWorkflowService.cs:861-877` + (2) `finalizeByCcmDelegation` OPT-IN param threshold-gated (Mig 54) `:879-915`. AUTO runs FIRST + returns → if both eligible, OPT-IN flag silently ignored (coherent, no double). `finalizeByCcmDelegation` FULLY wired E2E (ctrl `:141`+`:351` · Feature `:474,:526` · IPe…Service:32 · FE both `PeWorkflowPanel.tsx:184,584` ô-tích) — NOT orphan. ALL 3 DaDuyet paths bind giá chốt via `ApplyApprovedPriceOnFinalize` (private static `:956-971`, allow-list `{Ncc,ProMin,ProMax,Ccm}` `:955`): terminal `:933` + AllowApproverFinalize `:863` + ccm-delegate `:901`. isSystem-exempt branch (`:961-964`) = DEAD via public ApproveV2Async (needs decision=AutoApprove but APPROVE-STEP gates Approve; PE has NO SLA-job — only Contract) — tested-only-via-reflection (PeApprovedPriceFinalizeTests:238-295, header DISCLOSES "defensive/dead"). **MISPLACED-CODE CONFIRMED (known band-aid):** `SetPeCcmBudgetPeriodCommand` (Mig 59 Section-B BUDGET setter) lives in `PeSuggestedPriceFeatures.cs:153-199` (a SUGGESTED-PRICE file) — wrong file, own header admits Section-B. Has test `PeCcmBudgetPeriodSetterAuthzTests.cs` (NOT test-gap). DTO `currentLevelOptions` per-NV-discriminated `PurchaseEvaluationFeatures.cs:950-963` = same source as service `matchingLevel` (coherent). 4 finalize test files all present + green-design. → _INDEX.
- **2026-06-25 PE AREA-5 urgent/notify/TZ audit (FROZEN Mig 59 — gotcha #72):** ⭐ (a-TZ) `UtcDateTimeJsonConverter` registered ONLY on `AddControllers().AddJsonOptions` `Program.cs:33-34` — covers REST. ⚠️ **SignalR push BYPASSES it** (`AddSignalR()` no `.AddJsonProtocol`, separate STJ): `NotificationPushInterceptor.cs:77` pushes `createdAt=n.CreatedAt` over `SendAsync`. BENIGN at runtime: `n.CreatedAt=clock.UtcNow`=Kind.Utc (DateTimeService.cs:7) → default STJ writes 'Z' for Utc-kind, FE `new Date(iso)` NotificationBell.tsx:23 OK. Latent: any future SignalR push of an EF-READ (Unspecified) DateTime would skew. NOT a bug now, inconsistency-low. Converter handles DateTime? auto (non-null) — no double-convert. (b-authz) Urgent SET/UNSET branches `PurchaseEvaluationUrgentFeatures.cs:54-73` SYMMETRIC + fail-CLOSED both (`if(!allowed)throw` BEFORE mutate); 14 tests `PeUrgentToggleAuthzTests` cover plain/TP/Admin/drafter×set/unset. No fail-open. Controller class-`[Authorize]` only `:91` → handler IS boundary. (c-notify) Drafter excluded 2 sites: advance `:1138` (`!=DrafterUserId`) + terminal-result KEPT `:1085`; tests `PeApproverNotifyExcludesDrafterTests` RED-on-remove. ⚠️ **POSSIBLE double-notify-then-skip:** submit `:252` LogTransition notifies Cấp-1 approvers (excl drafter+actor) THEN `:260` ApplyDrafterBypass advances pointer + `:650/660` re-notifies new level — if drafter=Cấp1 w/ co-approver, co-approver pinged for a level immediately bypassed. Low-sev (extra ping, not wrong recipient); no de-dup across the 2 LogTransition calls. → _INDEX.
- **2026-06-23 PE UAT recon D2/D4/D5 (anh Kiệt FDC go-live):** ⭐ (D2-urgent authz) `PurchaseEvaluationUrgentFeatures.cs:49-50` BẤT-ĐỐI-XỨNG `isPro=IsUrgent?hasPro:(hasPro&&isDeptManager)` — GẮN=role-only / GỠ=DeptManager-only. NO DrafterUserId match (handler chỉ load entity, never reads DrafterUserId). Để drafter gỡ cờ mình: sửa :49-50 OR `actor.Id==entity.DrafterUserId`. Endpoint `PUT /purchase-evaluations/{id}/urgent` (controller:91). ⚠️ cờ KHÔNG split-by-drafter (PRO/CCM = role, drafter ≠ tất nhiên PRO/CCM). (D5) FE PE-create dropdown=`PeWorkspaceCreateView.tsx:94-104` GET `/approval-workflows-v2?applicableType={1|2}` filter `isUserSelectable`. **Dev DB: PE type1/type2 selectable=0** (6 selectable đều type3-9 Contract/Proposal/Leave/OT/Xe/Ticket NOT PE) → prod UNVERIFIED (SSH-paren-quote-trap). Validator REQUIRE workflow CẢ 2 tầng: `PurchaseEvaluationFeatures.cs:48` create NotEmpty (S83) + submit-guard `PurchaseEvaluationWorkflowService.cs:225` ConflictException. (D4 required) create-validator `:35-57`: Type/TenGoiThau≤500/ProjectId/WorkItemId/ApprovalWorkflowId NotEmpty + BudgetPeriodAmount>0-when-present. Submit-guard `:174-228`: SelectedSupplierId + winnerQuoteTotal>0 + BudgetPeriodAmount>0 + Bảng-so-sánh-attachment + ApprovalWorkflowId. → _INDEX.
- **2026-06-20 governance-landing map (RC-sig + User-Mark H12/13 + objective-criteria):** ⭐ WHERE-to-land 3 AI_INFRA gov broadcasts. `harness-11-engine.md` = CANONICAL engine (D5/D6/D7 safety-tier + D8 one-direction-lock + D9 single-writer + D10 Bash-residual + CAVEAT no-OS-hook). `error-ledger.md` = §L.a AS-1..13 + §L.b 7-step. adap-report FORMAT richest = `2026-06-18-Governance-harness-11.md`. rules.md §6.6 = objective-criteria. **report-before-stamp ⊂ D7 (extend, đừng duplicate → C3 vocab-fork).** → _INDEX.
- **2026-06-20 Harness-14 Eval/Budget/Outcome audit:** ⭐ H-14 = time/age/recency-decay KHÔNG được làm căn-cứ cắt feature. (1) BUDGET aligned: `memory-budget.json` 0 decay/TTL/age hit; `keep_floor=5`=newest-protection (drains OLDEST), cap seed-by-MEASURE. (2) detectors = canonical-anchor-vs-STATUS + disk-cross-check, ZERO age-window. (3) EVAL genuine: `eval/golden-set-solution_erp.jsonl` (14q) + `evaluator.md` recall@5≥0.7 — weekly-manual no-automation. (4) anti-downgrade = Harness-8 "all-inherit chất-lượng>chi-phí" PARTIAL (no generic rule in rules.md, grep 0). → _INDEX.
- **PE recon cluster (S69→S76, → _INDEX):** "Giá chào thầu" mục-c = `WinnerQuoteTotal`=SUM(Quote.ThanhTien WHERE supplier==Selected) DERIVED (3 nơi đồng-predicate) — PRO-Min/Max+CCM-proposed = Mig 54 NEW field. **Budget gate = PURE ROLE no-phase** (`canEditPro=Admin||Procurement`/`canEditCcm=Admin||CostControl` `PurchaseEvaluationFeatures.cs:800-801`; comment "bảng NS=tài-liệu-sống như Excel"); **submission-count-lock NEXISTS** (grep 0) → "khóa Initial sau lần-trình-đầu" = feature-MỚI. **Value-threshold HOOK B** = `ApproveV2Async` advance `:816-845` (reuse skipToFinal mechanic); **urgent flag** = AddColumn mirror `ItTicketPriority`. Badge ✎NS = +2 bool DTO via `GetUsersInRoleAsync` batch (no-N+1) both sites. PRO/CCM/CEO = `Procurement/CostControl/Director` (domain-shorthand NOT constants); V2 routing IGNORES roles (= specific ApproverUserId), 100% LINEAR no value/threshold config.
- **Office/HRM recon cluster (S65→S69, → _INDEX):** 21 `Off_*` keys, 10 office pages `{fe-admin,fe-user}/src/pages/office/` SHA256-mirror (except MyAttendance + AttendanceReport admin-only); golive-flip = remove `RevokeTemporarilyHiddenModulesAsync` call `DbInitializer.cs:2040`. Employee **master-detail EXISTS** (not list-only), Dept FLAT no-tree (pre-Mig51), 5 satellite 15-endpoint full. NamGroup **PURO = UI-skin** (ref demo.purocorp.vn) hardcoded-navTree, KpiCard-not-tab, shared PageHeader/WidgetCard. **gotcha #66:** `index.css h1-4{color:#0b1220}` OUTSIDE @layer → TW-v4 unlayered wins → heading-in-gradient MUST `text-white!`.
- **Governance/Harness recon cluster (S71, → _INDEX):** H8 all-inherit FULLY adopted both layers (12/12 frontmatter + hmw.js resolveModel); **run-trace "TRACKED" = 2-level** (`git check-ignore` eligible vs `git ls-files` committed — model works only post `git add`); G-015 containment shift (Harness-2 "mọi tracked=vi-phạm" → H10 "tracked NGOÀI run-folder+code-disjoint=vi-phạm"). **path-trap:** bare `runs/` from repo-root = 0 → actual `.claude/workflows/runs/` (22 tracked). detector refine(b) = `.claude/hooks`/`.claude/scripts` not-exist (now S75 has `scripts/governance-detectors.ps1`).
- **[→ git pre-S60]** S60 recon#2 V2-engine-map (ApproveV2Async:446-634 guard+UPSERT+advance; skipToFinal F2:561-602 precedent) · S60 PE Section-3 submit-guard ROLE-only. Full text git.
- **[→ archive/2026-06.md + git]** S52 P11-D/E/F 6-gap recon · S50 HrmConfigs add-kind 11-spot · S51 gotcha #57 EXT reachability (curated S59).
- **[→ archive/2026-05-q4.md + git d2f52ba]** S29-S37: clean-room > NamGroup-port verified 4× · Pattern 12-bis cross-module mirror · FK+freetext dual-write.
---
## 🔄 Curate trigger
- >~30KB → archive recent → L2 `archive/<period>.md` (byte-exact append) + `_INDEX.md` substring pointer. Stale >3mo → remove.
- **Last curate: 2026-06-20 S80 (em-main, archive-gate keep-floor-hit → manual)** (39.8→~14KB): moved 15 recon entries (lines 73-105 byte-exact via `sed`) → `archive/2026-06.md` (33→67KB) + `_INDEX.md` +15 table-row pointers (all verified count=1). KEPT foundation + 2 newest 06-20 + compressed recon-clusters + 3 git-stubs; UPDATED stale counts (40→57 mig, 84→88 tables, 130→354 test, 55→71 gotcha, 7→11 sub). gist NOT updated (em-main distill later).
- **Prev curate: 2026-06-18 S71** (29.8→23.2KB FIFO-trim 3 + gist gen:2). **Prev S70** (47.0→24.1KB dark-matter recovery + built _INDEX/gist). **Prev S40** (35.7→~20KB).