Files
solution-erp/.claude/agent-memory/reviewer/MEMORY.md
pqhuy1987 16a199f031 [CLAUDE] Docs: S114 PE multi-NCC per hạng mục closeout (STATUS/HANDOFF/gotcha #81/Mig 65/session-log)
Closeout feature multi-NCC per hạng mục (ship d436af2):
- STATUS/HANDOFF: S114 blurb + CURRENT STATE canonical Mig 64→65 · test 477→486 · gotcha 80→81 · bundle D51OYyGV/BVdssm5S.
- gotcha #81: derived-invariant re-derive sót DELETE writer (DeleteQuote/DeleteDetail) → IsWinner kẹt stale → HĐ giaTri=0 phantom (Bước 4 fable-clone verify bắt; fix helper PeWinnerInvariant test-before).
- ef-core skill: +Mig 65 BackfillPeQuoteIsSelectedAndWinnerInvariant row.
- session log 2026-07-12-S114 + 2 reviewer diary harvest (verify run) + run-trace fold.
- cicd on-behalf record (garble #53 ×5 → em-main-solo deploy verify).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 23:45:08 +07:00

113 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.

# Reviewer 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 cap ~17KB (hook 24.4KB read-limit) · 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..q2.md`; S51→S76 detail → `archive/2026-06.md` (S69/S70/S71/**S80** curate). Archive map: `archive/_INDEX.md` + per-period `.gist.md`.
## 📁 Area memory (L2 on-demand — Read khi review vùng tương ứng)
- [S62 PE budget soft-warning](project_s62_pe_budget_soft_warning.md) — PASS: hard-block→soft-warning; submit-guard intact + validator giữ `BudgetPeriodAmount>0`, row8 negative-safe (additive-only). Validator class `PurchaseEvaluationFeatures.cs:317`.
- [Wire/mirror claim verification anchors](feedback_wire_claim_verification_anchors.md) — sha256 twin-file · `git diff -U0` isolate true-adds · `allowNegative` bleed check · guard-still-intact grep.
- [S89 PE budget-sectionB review](project_s89_pe_sectionb_review.md) — investigator 3/3 upheld (do-not-touch FROZEN) + CAUGHT MISSED #70 race: Block B PRO row3/row8 share adjustMut + cross-echo `ev` stale + NO `||peFetching` (every other cell gated). authz-lens ≠ concurrency-lens.
- [S89 PE ends-before-CEO PLAN review](project_s89_pe_endsbeforeceo_plan_review.md) — CONCERN: BE 3-field design sound (Mig 58 col committed, no mig) but FE coverage gap — 4 PeUrgentChips render-sites, plan covered only 2 (missed InboxPage:290 despite BE Inbox A2 updated + wrong ListPage dir path).
- [S89 finalize-note PLAN review](project_s89_finalize_note_plan_review.md) — CONCERN: plan ground-truth 100% accurate (all file:line verified incl self-flagged); gaps = note doesn't say intermediate approvers still sign + skipToFinal-last-level corner + Dev DB 9 migs behind.
- **S101 H18 WF2 governance re-run** — PWC; detector fault-inject 4/4; lesson: exact-token sweep misses paraphrased labels → widen concept-phrase khi retire named artifact; #53 garble recovered first-hand. → `archive/2026-07.md`.
- [S103 H20+crystallized-backfill review](project_s103_h20_crystallized_backfill_review.md) — WF2 PASS (fable-clone ensemble); 6/6 hash recompute MATCH + 4 already-met verified file:line. **Lesson: multi-axis warning phải nằm trong OUTPUT của script, không chỉ doc** — reader chạy script inline.
- **S101 H19 WF2 review LANE-C (adap-reports+honesty)** — PASS; 4 hash recompute MATCH; hex→SHA = recompute CẢ strip/no-strip chứng content-hash ≠ commit-SHA. → `archive/2026-07.md`.
- [S111 PE sign-off 6-decision design review (schema/history lens)](project_pe_signoff_history_schema_review.md) — GO-WITH-ADJ; Decision-5 opinion-history: RECOMMEND (a) append-only table, REJECT (b) version-col (drops UNIQUE + breaks UPSERT read every site), (c) Changelog zero-mig fallback. Hook @ ApproveV2Async UPSERT :784-790. Decision-3 caller-safe (SlaExpiryJob=Contract-only, not PE). A1 breaks 4 bypass tests.
- [PE multi-NCC per-hạng-mục cardinality#3 review (consumer-completeness lens)](project_pe_multiwinner_perhangmuc_review.md) — PASS_WITH_ADJ; 6 BE read-site inventory COMPLETE (grep 1:1, no sót — unlike S87). 2 MUST: (1) giaTri isSingle/detailsSum branch silent-financial under per-hạng-mục; (2) derive-completeness — seed:1310-1316 sets IsSelected never IsWinner → seeded phiếu empty names + CreateContract throws. Lesson: derived source-of-truth = EVERY write-path of base field must re-derive.
- [S113 Supplier import v2 close-review (owner anh Kiệt)](project_s113_supplier_import_v2_review.md) — GO-WITH-ADJ 2 must-fix: consumers (PeDetailTabs:2222 + ContractCreatePage:321 ×2app) DON'T pass published=true → drafts leak into pickers (R4/R6 "ẩn" undelivered) + CreateSupplier:86 missing IsPublic=true. Dedup/publish/mig/authz SOLID. Lesson: "filter added" ≠ "drafts hidden" — grep-all-consumer + create write-path.
- [S114 PE multi-NCC per-hạng-mục BE-financial close-review](project_s114_pe_multi_ncc_perhangmuc_financial_review.md) — NEEDS-FIX 1 MUST: 6 financial-SUM đều→IsSelected (grep độc-lập, 0 sót) NHƯNG DeleteQuote:396 + DeleteDetail:262(cascade) KHÔNG re-derive IsWinner → stranded winner → phantom HĐ GiaTri=0 @CreateContract:56 + FE row kẹt; reachable "Xóa" btn:2897; 0 test. Lesson: derived-invariant re-establish MỌI mutation kể cả DELETE.
- [PE multi-NCC per-hạng-mục spec review (winner-truth cardinality lần 3)](project_pe_multi_ncc_perhangmuc_specreview.md) — PASS_WITH_ADJ. MUST: CreateContract isSingle→detailsSum (`:76-77,89-90`) over-count khi 1-winner≠cả-gói, phá `Σgiari==winnerQuoteTotal`; spec cite `84-93` LOẠI isSingle = bẫy partial-re-key S87. SHOULD: derive-integrity 3 write-path (winner-cmd + UpsertQuote:308/332 + seed:1257 KHÔNG set IsWinner). 6-site IsWinner grep ĐỦ (0 sót). Lesson: grep write-path field-NGUỒN (IsSelected) không chỉ field-đọc; cite-range bọc branch-decision.
- [S114 PE multi-NCC per-hạng-mục IMPL review (winner-truth flip lần 3)](project_s114_multi_ncc_perhangmuc_impl_review.md) — NEEDS-FIX. CAUGHT MUST: DeleteQuote (`PurchaseEvaluationDetailFeatures.cs:371-397`) KHÔNG re-derive IsWinner → xóa quote IsSelected cuối (nút Xóa FE:2897) VỠ invariant → phantom winner + HĐ giá 0 (CreateContract:56 Where(IsWinner)). Spec A2 enumerate writer sót DeleteQuote. Lesson: cardinality grep-consumer PHẢI gồm Delete-handler field-nguồn; spec-enum writer KHÔNG đáng tin. SOLID: 5 SUM IsSelected + test non-tautology (threshold-cross 1.2tỷ/500tr, giaTri 300/800/2000) + 2-app hash ae3788e9.
---
## 🎯 Role baseline
Adversarial pre-commit reviewer SOLUTION_ERP. Read-only verify + live curl prod UAT (`*.solutions.com.vn`). Tools: Read, Grep, Glob, Bash (curl + git diff + sqlcmd read) + 5 RAG MCP. Skills: `dependency-audit-erp` + `contract-workflow` + `permission-matrix`. Output: PASS/FAIL + concrete issues file:line. NEVER write code.
---
## 🚨 Recurring bug patterns (catch priority)
- **#44 Silent 403 class-level Authorize quá strict** — Drafter dropdown empty silent (TanStack catch silent → UI empty). Grep `\[Authorize\(Policy=.*\)\]` class-level + curl non-admin expect 200. Fix: class-level `[Authorize]` only (any authenticated); POST/PUT/DELETE giữ `[Authorize(Policy="X.Create")]`.
- **#43 Step.Order ≠ index 0-based** — `Where(s=>s.Order==i)` wrong row. Fix: EF query → in-memory `OrderBy(Order).ToList()` → index.
- **#42 Dual schema V1/V2 — Service phải branch** — `if (entity.ApprovalWorkflowId is Guid awId) ApproveV2Async else V1Legacy`.
- **Wire BE claim** — grep diff `// Mock`/`alert(`/no POST-PUT-DELETE call + live curl expect 2XX. Severity CRITICAL block.
- **#70 FE absolute-set stale-echo race** — N fields cùng-cột share 1 mutation + echo sibling từ server-snapshot (`bs`) + `invalidate()` fire-and-forget (không await) → lưu 2 ô liên-tiếp đè mất. Window mở SAU `isPending=false` (btn re-enable), KHÔNG lúc in-flight. Catch: đếm field-cùng-cột share mutation + check invalidate awaited; fix = `useIsFetching` gate nút Lưu tới khi refetch land.
- **#71 enum proxy-predicate pollution** — thêm enum value vào entity dùng-chung → UI/guard phân-loại theo PROXY-predicate (`supplierId===null`) thay vì enum tường minh → value mới lẫn nhầm phân-loại + false-pass guard. Catch: grep mọi field-proxy predicate, loại value mới tường minh; build-verify TỪNG app.
- **Cross-module security mirror (S29 Smart Friend)** — khi mirror entity/Command cross-module (PE→Contract→Budget V2), focus data-shape MISS security guard. Pattern: `aw.ApplicableType == ExpectedType` validate ON Create BEFORE instantiation (mirror `PurchaseEvaluationFeatures.cs:62-77`). Attack: Drafter forge POST với `approvalWorkflowId` của module khác → FK Restrict chỉ check Id-existence NOT ApplicableType → wrong-scope pin. Re-verify `IsActive`+`IsUserSelectable` server-side. Password ≥12 chars. Severity MAJOR.
- **#17 EF migration 3-file** — `git diff --name-only | grep Migrations/` expect 3 (target + Designer + Snapshot).
---
## 📋 6-category checklist (EVERY review)
- **Cat 1 Wire BE/feature claim:** grep mock markers diff + `await api\.(post|put|delete|patch)\(` + live curl POST/PUT/DELETE if deploy claim + status matrix.
- **Cat 2 Schema integrity:** 3-file rule Mig + column types vs entity def. Reference `docs/gotchas.md` (71 active).
- **Cat 3 Security:** `[Authorize]` class-level ALL new controllers + per-action policy admin-scoped (gotcha #44) + FE PermissionGuard + menuKeys.ts mirror BE MenuKeys.cs + FluentValidation + EF parameterized.
- **Cat 4 Code quality:** `dotnet build SolutionErp.slnx` 0 err + `npm run build` × 2 app (TS6 strict) + tests baseline **354 PASS** (Phase 9 UAT exception OK) + no `--no-verify` + anti-fiddle (scope drift >20% LOC = FAIL) + mirror 2 FE app §3.9.
- **Cat 5 Test coverage:** new helper → xUnit · new endpoint → integration · bug → regression test-before-fix. Phase 9 UAT test-after default OK (`feedback_uat_skip_verify`). Baseline 354.
- **Cat 6 Writing-quality (Harness-7, S64):** outward text (verdict gửi anh / report) = tiếng Việt câu hoàn-chỉnh đủ dấu đúng ngữ-pháp; describe issue + acceptance criteria, NOT code edits. Escalate disagreement explicit.
---
## ⚠️ Anti-patterns + 🛡️ Smart Friend guard
1. ❌ Recommend code edits (only describe issue+criteria) · 2. ❌ Skip live curl if deploy claim · 3. ❌ Accept "wire" without grep proof · 4. ❌ Defer to em main authority (escalate explicit) · 5. ❌ Skip MEMORY · 6. ❌ **Lower bar match em main** (Smart Friend Cognition anti-pattern).
**Smart Friend (Cognition):** NEVER lower bar. Em main code fine → PASS. Em main issues → FAIL with specifics regardless social pressure. "Quality ceiling set by primary, not escalation." Value = raise quality through catch.
---
## 🧠 SOLUTION_ERP review essentials (S80 verified — re-ground từ docs/STATUS.md canonical)
- **Tests baseline:** **354 PASS** (45 Domain + 309 Infra · 0 fail/skip). Must increase khi feature added (§7); Phase 9 UAT exception (`feedback_uat_skip_verify`).
- **Gotchas:** **71 active** (`docs/gotchas.md`, format `### N.`). Latest: #69 bundle-hash non-determ (deploy rebuild-FE-uncond rotate) · #70 FE stale-echo race (useIsFetching gate) · #71 enum proxy-predicate pollution · #66 Tailwind v4 unlayered `h1-h4` color thắng utility · #58 EF read-modify-write lost-update→ExecuteUpdate atomic.
- **Migrations:** **57 latest `AddPeSuggestedPriceNotes`** (path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`). PE-budget: Mig 50 `PeWorkItemBudgets` per-gói-thầu (DROP module Budget cũ) · Mig 54 giá-đề-xuất PRO/CCM + giá-chốt · Mig 55 CcmNote · Mig 56 ProInitial/ProAdjust split · Mig 57 ghi-chú-giá PRO/CCM.
- **Endpoints:** ~253 · **88 SQL tables**.
- **Identity password ≥12 chars** (reject 11-char). Test creds: admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM).
- **Prod:** api/admin/eoffice.solutions.com.vn. Bundle live admin `CsJetgZH` / user `BVS0ApIm` (Run #330). **Pin:** MediatR `12.4.1` (flag `Version="14`) · Swashbuckle `6.9.0` · Node CI `20.x`.
- **Conventions:** `docs/rules.md` (§1.1 outward writing, §3.9 mirror 2 FE, §5.2 commit, §6.5 docs narrative, §7 test timing, §2.8 pin).
---
## 📅 Recent activity (compressed — full verbatim → `archive/2026-06.md` + `archive/2026-07.md` via `archive/_INDEX.md`)
- **S101 H19 fable-clone WF2 Lane-A (toggle) — PWC, CONCERN closed @S102:** marker WRITE-ONLY dead artifact — persist-claim cần CẢ reader-side (verify consumer exists, not just producer); reader BƯỚC 0.5b wired S101-cuối. → `archive/2026-07.md`.
- **S100 H18 WF2 synthesis + Lane-B — PWC 0-blocking:** ledger revert-clean + ratio-band + 6/6 🧊; anti-pattern HELD: stale WF2 run-id NOT stamped. → `archive/2026-07.md`.
- **S98 (2026-07-02) 3 verify-lane — PASS (minor):** fidelity-gate 3-lớp (verbatim+pointer-arith+ground-truth-code); Windows byte-verify = .NET ReadAllBytes/`wc -c`/`od -c` (MSYS strip `\r` báo sai). → `archive/2026-07.md`.
- **S97/S97-bis (2026-07-01) PE finalize UAT ×2 + EndsBeforeCeo Mig 60/61 — PASS:** default-flip byte-mirror SHA256 ×2; RUNTIME-flag 1/5 DaDuyet grep-all + 4-projection; backfill-LIKE VN false-pos MINOR. → `archive/2026-07.md`.
- **S93 (2026-06-29) Harness-16 MFE adopt (WF2 3-lane) — PASS 0-blocking:** code-gate re-derived denom-29; READ-ONLY-budget 1 write-op; WF1 caught vocab-fork H6.7≠H16. → adap-report + _INDEX.
- **S92 (2026-06-29) PROD-security hide 5 menu-groups admin-only (uncommitted) — PASS (1 note):** A-E upheld; CatalogManager Danh-mục stripped (by-spec, 0 users). → [project_s92_admin_only_modules_revoke.md](project_s92_admin_only_modules_revoke.md).
- **S91 (2026-06-25) PE create-contract 1→N multi-winner (FROZEN) — PASS:** codegen mid-loop SaveChanges flush ONLY seq-row; GiaTri per-winner Quote-sum; positional DTO 3 sites; FE byte-mirror; 419 PASS. → `archive/2026-06.md`.
- **S90 (2026-06-25) PE stability D1 (FROZEN) — PASS:** Block B re-key IsWinner; CEO-notify SaveChanges (was never-flush); HoSoLink null-safe; #70 ||peFetching; 413 PASS. → `archive/2026-06.md`.
- **S89-bis (2026-06-25) AREA-6 FE-consumers (FROZEN) — 4/4 + 1 NEW:** create-contract joint-winner DEAD-END (FE `some(isWinner)` vs BE `SelectedSupplierId is null`). → `archive/2026-06.md`.
- **S89 (2026-06-25) AREA-4 workflow-edit (FROZEN) — 3 confirm/1 do-not-touch:** HoSoLink #73 clear-on-partial + NEW 2nd call-site PeDetailTabs:817. → `archive/2026-06.md`.
- **S86 (2026-06-24) PE Section B 3-cột (Mig 59) — PASS:** authz byte-mirror UpdatePeSuggestedPriceCcm (NotFound→Forbidden fail-closed); FE canEditCcm BIT-EXACT; 402 PASS. → `archive/2026-06.md`.
- **S82 (2026-06-21) Harness-15-v2 adopt — 3/3 PASS (3 MINOR):** `broadcasts/_index.md` sha = self-declared frontmatter NOT recompute → đọc frontmatter trước khi flag index-vs-file mismatch. → _INDEX.
- **S76 (2026-06-19) PE budget 3-cột Mig 56 — PASS:** #70 race fixed (useIsFetching gate); Badge mirror gate bit-for-bit; SURPRISE spec "KHÔNG migration" FALSE → đọc changed-set thật. → _INDEX S76.
- **S72* (2026-06-18) Mig 54 PE giá-đề-xuất + CCM-finalize OPT-IN — PASS:** fail-closed guard throw BEFORE Phase=DaDuyet; bypass = 3 gate + server-recompute no-trust-client. → _INDEX S72*.
- **S71 (2026-06-18) Harness-10 run-trace — PASS/GAPS:** "TRACKED" = check-ignore(eligible) vs git-ls-files(committed), model post `git add`. → _INDEX S71*.
- **S69 (2026-06-17) Office re-skin + golive authz — PASS:** re-skin = grep api-call+queryKey sorted -u byte-equal; public-grant root NOT inherit-root; accent -800 silent Tailwind v4. → _INDEX S69*.
- **S65 (2026-06-16) public HRM + PE mục E — PASS:** upgrade MUST MUTATE row NOT skip-existing when prior revoke false (S58-class); menu-hide ≠ API-lock. → _INDEX S65*.
- **S88 (2026-06-25) Fidelity-gate L2 gist distill — FAIL (fabrication-by-merge):** gist gán "Mig 45" cho cluster 2-episode (Master=Mig 47) — merge-distill fabricates false-specificity; QUALITY gate after presence gate. → `archive/2026-06.md`.
- **S108 (2026-07-10 first-real-run H19) — FAIL (1C+4M+6m):** email claim "đã sửa comment" khi hmw.js chưa sửa. Lesson: outward-claim "đã sửa X" phải cat X trước gate; grep stale-claim theo CLASS sau fix đại diện. → `archive/2026-07.md`.
- **S109 (2026-07-10 fidelity-gate 2 gist L2) — PASS_WITH_FIXES (3):** gist meta-count 15≠disk 14; delta tự-tính +8.8KB = self-computed false-specificity; N/A-just over-claim → fact-claim trong N/A verify như content. → `archive/2026-07.md`.
- **S111 (2026-07-12 H22 WAL adopt) — PASS_WITH_FIXES (0C/1M):** fault-inject 2-ca PASS; CATCH CAVEAT "STAGED" còn khi N.3 nâng VERIFIED-RUNTIME = nâng-nấc-quên-sync cùng-diff; live-witness>forensics (commit GIỮA review = chứng hook fire). → `archive/2026-07.md`.
- **S110 (2026-07-11 H21+MTv3 adopt) — PASS_WITH_FIXES (0C/5M):** doc "VALID_ROLES 12" nhưng code=10 → claim-về-code grep lại kể cả reword; supersede trỏ adap-report chưa tạo = dangling. → `archive/2026-07.md`.
- **S112 (2026-07-12) close-reviews `[fable-real-single]` — GO-WITH-ADJ ×2:** (import) FE Import btn reuses Suppliers.Create guard but endpoint = Admin/CatalogManager → dead button drafters → **derive guard from ENDPOINT authz not sibling-menu**; (PE sign-off) EDGE-5 terminal-lock sót admin-override SVC:290 → **gác FROM-state ở ĐỈNH method KHÔNG per-branch**; A1 mở CEO-skip mới. → [topic](project_s112_supplier_import_review.md), `archive/2026-07.md`.
- **S112 (2026-07-12) import BE-close + FINAL bundle — GO 0-must-fix:** 6 axes PASS; adjust applied (30 ExpectedHeaderTokens byte-identical NFC + `.Normalize(FormC)` both sides; guard @top SVC:66). UAT by-design: A1 bỏ auto-terminal-on-submit. **Lessons: self-flip token-test = internal-consistency NOT external-file-accept; build/test-green nhận theo claim, review LOGIC bằng đọc; byte-diff token-array TAY.**`archive/2026-07.md`.
---
## 🔄 Curate trigger
- Hook-cap **>17.1KB** (24.4KB read-limit — đổi từ ~30KB cũ, S109) → archive recent → L2 `archive/<period>.md` (append additive) + `_INDEX.md` substring pointer. Stale >3mo → remove.
- **S113 curate (self, 2026-07-12, hook 21.3KB→~16KB):** compressed 4 verbose S112 entries (2 close-review lines→1, BE-close+FINAL→1) + this curate-log in-place; NO file-move (lessons preserved inline). digest kept L1. **+review-lane addendum:** 4 verbatim ALSO moved → `archive/2026-07.md` + 4 `_INDEX` pointer (move-not-cut) + trimmed 2 digest.
- **S109 curate:** moved 4 verbatim S100/S101 → `archive/2026-07.md` +4 pointer.
- **S102 recovery (em-main):** cut-not-moved ×5 @`f229b07` recovered từ git. **Lesson: L1-shrink kèm claim "Detail→archive" = BẮT BUỘC grep moved-not-cut TRƯỚC khi trust digest.**
- **Prev curates:** S80 (moved 13 S65→S76 →archive/2026-06), S71 (moved 10), S70 (built `_INDEX`+`.gist`), S40.