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

24 KiB
Raw Blame History

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

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 flatWorkflowDefinition pin PE/Contract cũ. Match Dept+PositionLevel.
  • V2 Mig 22-31ApprovalWorkflow 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 , 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-adjustAdjustPurchaseEvaluationBudgetCommandHandler 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 ==SelectedIN 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).