Files
solution-erp/.claude/agent-memory/reviewer/MEMORY.md
pqhuy1987 17b23a418a
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
[CLAUDE] Docs: Harness-4 two-tier runtime-VERIFIED (spawn-test 2 chiều post-restart) + email-back AI_INFRA
- Spawn-test 2 chiều S57bis: H1 tooling-auditor (demote pin) self-report claude-opus-4-8[1m] + H2 harvest-curator (promote inherit) self-report claude-fable-5[1m] → nấc executed-file/PENDING-RESTART → RUNTIME-VERIFIED (adap-report §2/§5 + STATUS row). [1m] 1M-resolve SE tự verify.
- Email update 2026-06-11-se-to-ai_infra-harness-4-runtime-verified (nac sent, sha ecf1d587, honest n=1/chiều, hmw.js executed-file giữ) + _index OUTBOUND.
- Lesson env: CCD harness cache agent frontmatter — restart CLI mới ăn (2 data-point 06-10/06-11).
- Bundle 06-10 carry: 7 agent pin opus-4-8 + 4 inherit + hmw.js tier-map H4.5 + agents/README two-tier + 2 adap-report + email 06-10 + agent-memory delta (KEEP-ALL-5 H2-verified) + investigator L1→L2 archive curate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:12:22 +07:00

90 lines
29 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 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..q2.md`.
---
## 🎯 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.
- **Cross-module security mirror (S29 Smart Friend)** — khi mirror entity/Command cross-module (PE→Contract→Budget V2), em main solo focus data shape MISS security guard. Pattern: `aw.ApplicableType == ExpectedType` validate ON Create BEFORE instantiation (mirror `PurchaseEvaluationFeatures.cs:62-77`). Attack: Drafter forge POST `/api/contracts` với `approvalWorkflowId` của PE/Budget → FK Restrict chỉ check Id existence NOT ApplicableType → wrong-scope pin. Also re-verify `IsActive`+`IsUserSelectable` server-side. Password ≥12 chars (Identity reject 11-char legacy). Severity MAJOR block push.
- **#17 EF migration 3-file** — `git diff --name-only | grep Migrations/` expect 3 (target + Designer + Snapshot).
- **#47 `.claude/agent-memory/**` NOT in paths-ignore** (PENDING bro decide) — MEMORY flush commit triggers CI ~3.5min waste. paths-ignore hiện `['docs/**','**/*.md','.claude/skills/**']` missing agent-memory. Severity minor (CI waste). ⚠️ S40 note: agent-memory commits đang trigger — recommend bro add.
---
## 📋 5-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` (55 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 **130 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 130.
- **Cat 6 Authority boundary:** 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 (S40 verified)
- **Tests baseline:** **130 PASS** (58 Domain + 72 Infra). Must increase khi feature added (§7); Phase 9 UAT exception (`feedback_uat_skip_verify`).
- **Gotchas:** **55 active** (`docs/gotchas.md`, format `### N.` highest #55). Latest #53 truncation · #54 529-fallback · #55 truncation-mid-exploration.
- **Migrations:** **40 latest `AddAttendances`** (path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`). Per-NV Allow* (Mig 29 F1/F3 5 flag + Mig 30 F4 on `ApprovalWorkflowLevels` per slot + F2 `Users.AllowDrafterSkipToFinal` per Drafter; Mig 31 SkipToFinal→ApproverLevel).
- **Endpoints:** ~211 · **84 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. **Pin:** MediatR `12.4.1` (flag `Version="14`) · Swashbuckle `6.9.0` · Node CI `20.x`.
- **Conventions:** `docs/rules.md` (§3.9 mirror 2 FE, §5.2 commit, §6.5 docs narrative, §7 test timing, §2.8 pin).
---
## 📅 Recent activity (FIFO — older → archive/git)
- **2026-06-10 (S57-resume Harness-4 two-tier adopt gate — PASS-with-fixes, 0 blocker):** Gate trước send-email + commit (governance, không product code). Self-report spawn: `claude-fable-5[1m]` (reviewer = promote-list inherit → direct promote-tier evidence, em main cite được). Independent re-verify ALL GREEN: grep frontmatter = đúng 7 pin `claude-opus-4-8` + 4 `inherit` + 0 `[1m]`-in-frontmatter (2 body-text hits hợp lệ: database-agent.md:46 + README.md:9 MỚI — adap-report "match duy nhất" stale-by-own-edit) + 0 project-pin settings. Evidence track-record **8/8 REAL** vs HANDOFF/STATUS/own-memory (S51 MAJOR · S54 QTV-decoy · S53 Mig46 · S56 H2-4.5/5 + dept-IT-0-user · S57 ×3 controller +5/+5/+5 `[Authorize(Roles="Admin,CatalogManager")]` working-tree). Nấc G-011 đúng mọi chỗ load-bearing (demote = executed-file·pending-restart, 0 overclaim runtime). Fixes: hash PLACEHOLDER trước send (`nac: sent` + "SENT ✓" premature = đúng status-verb class broadcast cảnh báo) · STATUS "(runtime resolve 1M)" thiếu attribution AI_INFRA-s20 · hmw.js:91 log "same-model inherit" stale + :9 "8-agent" vs 9 roles · adap-report "(13)" vs "11" count · invalid-role typo → rơi 'opus' (fail-direction xuống vs H4.5 nghiêng-quality). **Learned:** gate adopt-governance = re-run MỌI grep claim + cross-check evidence vs HANDOFF nguyên văn; n=2 demoted spawn-test double-duty làm inherit-chain proof là HỢP LỆ (registry cached = chạy config cũ) nhưng cần phrase rõ kẻo đọc nhầm thành promote-list spawn-test. Tag [s57, harness-4, two-tier-gate, pre-send-gate, g011].
- **2026-06-09 (S56 pre-golive authz live-curl — PASS, 0 blocker):** Live prod curl 8 new endpoints. **8/8 return 401 unauth**; admin-authed: hrm-configs/vehicles(2)+drivers(2), leave-balances/my(5 lazy), attendances/report+excel(200, 6797B xlsx) all 200; non-admin Drafter correctly 403 on the 2 Admin-only attendance endpoints. **gotcha #44 silent-403 sweep CLEAN:** capability GET /it-tickets/assignable-staff returns HTTP 200 `{canReassign:false,staff:[]}` for non-IT Drafter (NOT swallowed 403) + `{true,[]}` admin — handler returns flag, doesn't throw (`WorkflowAppsFeatures.cs:466`). assign-mutation guard fail-closed (:504). E2E: GET /projects payload has all +4 fields (70/70), CAL01 Investor live. Off_AttendanceReport menu key in admin /menus/me. **1 MINOR (non-block, defense-in-depth):** PUT /it-tickets/{id}/assign checks NotFound BEFORE Admin-OR-IT Forbidden (`WorkflowAppsFeatures.cs:496-508`) → existence-oracle leak; mutation itself fail-closed → post-golive hardening only. Tag [s56, pre-golive-verify, authz-clean, gotcha44-clean, notfound-before-forbidden-minor].
- **2026-06-09 (S55 Phase-1 FE visual redesign pre-commit — PASS, 0 blocker, verdict-first survived):** 14 fe-admin files VISUAL/CSS-only (NAMGROUP density + SOLUTION brand). **Independent re-verify GREEN:** `npm run build` fe-admin = ✓ 607ms, 1945 modules, 0 TS err (only PRE-EXISTING warns: CSS @import-order + >500KB chunk + INEFFECTIVE_DYNAMIC_IMPORT realtime.ts — git-confirmed none introduced, @import lines untouched in diff). **Regression Cat1 ALL preserved:** Button cva variant keys (primary/secondary/outline/ghost/danger) + size (sm/md/lg) STABLE — only Tailwind class VALUES swapped, defaultVariants intact (51 call-sites safe); Input/Select/Textarea/Label = `forwardRef`+`...props`+`className` passthrough unchanged, only `cn()` literal; Dialog `{open,onClose,title,children,footer,size}` destructure + sm/md/lg→max-w map intact (+aria-label="Đóng" = a11y GAIN); DataTable `Column<T>` type UNCHANGED (diff starts after type def) — render/sortable/align/width + sort + Pagination props intact, RowActions/RowActionButton purely ADDITIVE; Layout MenuLeaf className-only (brand left-rail via before:), nav/resolver/permission-filter/routing untouched; PhaseBadge phase→ContractPhaseColor/Label map intact; PageHeader/EmptyState/TopBar pure class. DashboardPage data-flow (useQuery/navigate/fmtMoney/BarChart/PhaseBadge) preserved, STAT_TONE+SectionLabel additive, +`cn` import only. **Brand Cat3:** Be Vietnam Pro KEPT (grep: @import:3 + --font-sans:22 + font-family:34 all unchanged — initial blocker RETRACTED after grep); only brand-/slate/semantic colors, 0 off-brand hex/indigo. **a11y:** focus-visible rings present everywhere (brand-500); Label self-documents slate-500 (~4.6:1 AA-pass) chosen over NAMGROUP zinc-400. **Tailwind v4 (^4.2.3)**`ring-current/15`, `shadow-xs`, slash-opacity all valid v4. **noScopeCreep:** exactly 14 fe-admin, 0 fe-user, 0 BE/src (only noise = frontend-designer/MEMORY.md agent file). **2 MINOR (non-block, a11y-floor):** `text-slate-400` on white for small hint/empty text (DashboardPage hints ~line 50/64, DataTable empty-cell, EmptyState was-400-stays-400) ≈3.5-4:1 — borderline-fail WCAG-AA for <18px, but these are de-emphasized hints not primary content + PRE-EXISTING tone (redesign mostly UPGRADED slate-400500 on EmptyState desc + Pagination); accept for hint role, revisit if audit. **Learned:** font-drop scare = grep the 3 load-bearing lines (@import/--font-sans token/font-family) BEFORE flagging diff hunk lower in file font removed; emit PASS/FAIL line-1 FIRST (gotcha #53 truncation survival, mirror S51/S55). **surprise:** Tailwind v4 `shadow-xs` is real (v3's shadow-sm renamed) don't flag as typo; v4 slash-opacity on currentColor (`ring-current/15`) is valid. Verdict PASS safe commit+deploy. Tag [s55-fe, visual-redesign, namgroup-density, verdict-first, regression-clean, slate400-minor].
- **2026-06-09 (S55 master-data import pre-commit PASS [em main proxy reviewer return truncated gotcha #53 before verdict, mirror S51]):** Reviewed Mig 48 `AddProjectMasterFields` (Project +4 nullable col Year/Investor/Location/Package) + `SeedRealMasterDataAsync` (62 Project+71 WorkItem+3 Supplier per-code idempotent ungated) + FE ProjectsPage form +4 ×2 app. Reviewer ran 293s/31-tools nhưng truncated mid-thought (nghi cached-binary 2.76s build muốn forced clean rebuild + Project tests). **Em main COMPLETED đúng việc nó định làm:** `dotnet test SolutionErp.slnx` = clean rebuild + **216 PASS** (58+158, 0 fail/skip) giải tỏa cached-binary concern (test = fresh build). 10 dims GREEN: Mig Up=4 AddColumn/Down=4 DropColumn reversible + 3-file; seed 62/71/3 0-dup; per-code idempotent ungated line 118 (reaches prod); FLOCK01 collision skip-demo-wins; FEBE 4 nullable both sides (tránh S51 mismatch); test-file compile-fix +4 null legit; gotcha #57 index untouched; **runtime Dev proof** (data landed, Investor col populates). 0 rogue write (read-only respected, git clean of code). **Learned:** long adversarial review return truncates (gotcha #53) reviewer nên emit PASS/FAIL verdict SỚM (trước deep re-verify) để sống sót truncation; em main complete được đúng pending-check (clean dotnet test) deterministic. Verdict PASS safe commit. Tag [s55, master-import, em-main-proxy-truncate, runtime-dev-proof].
- **2026-06-08 (S54 ItTicket reassign authz Admin-OR-dept-IT cross-stack pre-commit PASS, 0 blocker, gotcha #44 disarmed correctly):** Controller `/assign` hạ `[Authorize(Roles="Admin")]``[Authorize]` any-auth; authz moved INTO `AssignItTicketHandler` (Admin-OR-IT Forbidden + assignee-must-IT Conflict) + new `GetAssignableItStaffQuery` capability endpoint. **Independent re-verify GREEN:** `dotnet test SolutionErp.slnx` = **216 PASS** (58 Dom + 158 Infra, Failed:0 Skipped:0, +13 matches 203216 claim exactly) · fe-admin + fe-user `tsc -p tsconfig.app.json --noEmit` BOTH exit 0 clean. **#2 CHÍ MẠNG role-string "Admin" CONFIRMED REAL (full chain traced):** `AppRoles.Admin="Admin"` literal (AppRoles.cs:5) SeedRolesAsync `Name=roleName` (DbInit:1485) so DB Role.Name=="Admin" Identity GetRolesAsync returns NAMES JwtTokenService:32 `new Claim(ClaimTypes.Role, r)` CurrentUserService:30-31 `FindAll(ClaimTypes.Role)` `cu.Roles.Contains("Admin")` CORRECT. "QTV" (DbInit:1458 RoleLabels) = ShortName DISPLAY label only = decoy. Program.cs JWT sets NO `RoleClaimType` override (ClaimTypes.Role symmetric write/read). Same proven pattern as every existing Roles="Admin" endpoint. **Bypass airtight:** controller any-auth handler sole gate; guard `if(!isAdmin && !(itDeptId is Guid mine && myDeptId==mine)) Forbidden` fail-CLOSED when itDeptId null (non-admin blocked; admin still passes role-branch). `Department.Code=="IT"` IS seeded (DbInit:2082 "Phòng CNTT") so live non-null. **Capability 0-leak:** non-auth `{canReassign:false, staff:[]}` (no name leak), `[Authorize]` any-auth 0 silent-403. **Defense-in-depth intact:** FE nút `{canReassign&&}` but PUT still hits handler guard 403/409 `onError: toast.error(getErrorMessage(err))` surfaces (NOT swallowed). **FE SHA256 page 4bcaf2f IDENTICAL both apps** (types.ts correctly NOT identical admin AttendanceReportDto vs user HrDashboardDto diverge below; added AssignableStaff block byte-identical); old fe-user page was read-only kanban (NO app-specific logic lost diff purely additive); fe-user has all imports (apiError.getErrorMessage, Dialog size/footer/onClose props match, Select passthrough, sonner). **Test 13-fact NOT happy-path:** Case5 Forbidden side-effect assert `AssignedToUserId.Should().BeNull()` (red-able by contrast vs Case6/7 same-handler success), Case3/3b empty-staff 0-leak, Case8 Conflict msg-exact. prove-by-contrast ĐỦ CHẶT (partition non-IT-throws vs IT/admin-succeed identical handler). **1 MINOR defer:** assignee-must-IT NEW vs old handler (git HEAD: old allowed adminANY active user); itDeptId-null even admin Conflict fail-closed acceptable+spec-requested, cosmetic (prod IT-dept seeded). **Learned:** authz role-string review = trace FULL chain constseed NameGetRolesAsync(names not codes)→Claim(ClaimTypes.Role)→reader AND grep JWT cfg for `RoleClaimType` override (none=symmetric) display-code (QTV) in RoleLabels/ShortName dict = classic decoy. **surprise:** moving authz controllerhandler is the CORRECT gotcha #44 fix (not a smell) when paired with BE-computed capability flag for FE gating + handler as sole gate. Verdict PASS safe commit. Tag [s54, it-ticket-reassign-authz, gotcha44-disarmed, role-string-chain-verified, cross-stack-clean].
- **2026-06-08 (S52-late Task C ItTicket admin reassign + Task D AttendanceReport menu-key pre-commit PASS, 0 blocker):** Migration-FREE (menu = idempotent DbInitializer seed). 5 prod files: MenuKeys.cs (const+All[]), DbInitializer.cs (1 seed tuple), fe-admin {menuKeys.ts, Layout.tsx staticMap, ItTicketsPage.tsx}. **Independent re-verify GREEN:** `dotnet build SolutionErp.slnx` 0-warn/0-err · `npm run build` fe-admin tsc-b+vite OK (1945 modules, only pre-existing CSS @import + >500KB + ineffective-dynamic-import warns). **menuKeyMatchOk:** `"Off_AttendanceReport"` byte-identical 4 places (MenuKeys.cs:125 const == menuKeys.ts:68 == seed parent key == Layout staticMap:87); seed leaf parent=`MenuKeys.Off` order=8 icon=`FileBarChart` (verified valid lucide alias `FileChartColumn as FileBarChart` — getIcon resolves via Icons[name]); App.tsx route `/attendance/report → AttendanceReportPage` PRE-EXISTING committed S52 (6a66429, no diff, 170-LOC real page not stub) — Layout maps to REAL route. `types/menu.ts` correctly NOT mirrored (`key:string` not typed union). admin auto-perm via `SeedAdminPermissionsAsync` iterating `MenuKeys.All` (DbInit:1917, idempotent Contains:1919); new-leaf-on-existing-DB confirmed (upsert loop:1845-1862 TryGetValue-miss→Add). **reassignCorrect:** FE PUT `/it-tickets/{id}/assign` body `{assignedToUserId}` MATCHES BE record `AssignItTicketBody(Guid AssignedToUserId)` (ItTicketsController:42); endpoint `[Authorize(Roles="Admin")]` (:34) under class `[Authorize]` — admin-app FE calling = correct; user-list reuses EXISTING `/users` GET (`PagedResult<UserDto>` items{id,fullName,email}, no new BE endpoint, lazy `enabled:target!==null`); 204 NoContent handled (no body-parse); `invalidateQueries(['it-tickets'])` on success; handler sets BOTH AssignedToUserId+AssignedToFullName (WorkflowAppsFeatures:467-468) + validates assignee IsActive→NotFound, no try-catch (GlobalExceptionMiddleware). **feUserUnchanged:** `git diff -- fe-user/` EMPTY (Task C = fe-admin-only divergence, documented top-comment ItTicketsPage:3-5). **noScopeCreep:** git status prod = EXACTLY the 5 expected files, agent-memory noise ignored, no new migration, no BE beyond MenuKeys+DbInitializer, ItTicketsPage diff 98+/5- all Task-C-scoped (imports+state+mutation+Pencil-btn+Dialog), 0 mock/alert markers. **Learned:** menu-key wiring = verify byte-identity across the FULL mirror set (BE const + BE All[] + seed parent + FE menuKeys + FE staticMap) + confirm the target route actually EXISTS (grep App.tsx) — a staticMap entry pointing to a non-existent route silently drops the leaf (gotcha #50). **surprise:** lucide `FileBarChart` is a deprecated-alias (re-exported from FileChartColumn) but still valid — d.ts grep confirmed before flagging. Verdict PASS — safe to commit. Tag [s52-late, it-ticket-reassign, attendance-report-menukey, menukey-mirror-5way, gotcha44-disarmed, gotcha50-disarmed].
- **2026-06-08 (S53 gotcha #57 EXT Mig 47 — Master catalog filtered-unique pre-commit — PASS, 0 blocker, Smart Friend clean):** 4th/5th/6th cumulative gotcha #57 (after Holiday Mig 43 S45 + HRM ×3 Mig 45 S51). 3 Master configs (Department:18/Project:19/Supplier:24) Code unique index `.IsUnique()``+.HasFilter("[IsDeleted] = 0")` + Mig 47 (3-file) + 3 new tests. **Independent re-verify ALL GREEN:** build SolutionErp.slnx 0-warn/0-err · **full suite 203 PASS** (58 Dom + 145 Infra, Failed:0 Skipped:0, +3) · 3 new tests run isolated 3-passed-0-skipped. **Cat correctness:** filter string byte-identical to HolidayConfiguration:18 (xxd `5b 49 73 44 65 6c 65 74 65 64 5d 20 3d 20 30` — spaces around `=`, not guessed); index STAYS `unique:true` (2 active same-Code still violate — active uniqueness preserved); Supplier Type index (:25) UNTOUCHED non-unique unfiltered (snapshot:3590 bare). Mig Up=3×Drop+3×Create-filtered, Down=3×reverse-unfiltered (reversible). Snapshot+Designer both show filter on all 3 Master Code idx. **Test NOT tautology:** seeds IsDeleted=true row → real Create*CommandHandler (app-check `AnyAsync(Code==req.Code)` thru HasQueryFilter !IsDeleted PASSES) → asserts NotThrow + active-count==1 + IgnoreQueryFilters all==2; RED-before confirmed (3 failed SqliteException UNIQUE on unfiltered). Cmd signatures match test calls (Project 7-arg/Supplier 9-arg/Dept 4-arg). **noScopeCreep:** git status = exactly 3 configs + snapshot + Mig47 (2 untracked) + 1 test + 2 MEMORY; no FE, no extra mig, no stray. Mig 47 latest in seq (after Mig46 ItTicket SLA). **Learned:** cookie-cutter EXT of proven pattern → discriminator = byte-compare filter string (xxd) vs canonical sibling + verify index still unique (filter must NARROW scope not DROP uniqueness). app-level dup-check existence = the test premise; verify handler actually has `AnyAsync(Code)` else test premise false. **surprise:** implementer claimed "2 pre-existing DocxRenderer warnings" but clean incremental rebuild = 0 warn (unrelated, non-issue). Verdict PASS — safe commit. Tag [s53, gotcha57-ext, mig47, master-catalog, smart-friend-clean].
- **2026-06-08 (S52 P11-E AttendanceReport + P11-F MaTicket codegen pre-commit — PASS, 0 blocker):** Migration-free (no schema). Independent re-verify: build 0-err · **191 PASS** (58 Dom + 133 Infra, +5: 3 ItTicketCodeGen + 2 AttendanceReport) · fe-admin `tsc --noEmit` exit 0. **Cat3 gotcha #44 attack DISARMED:** `[Authorize(Roles="Admin")]` ×2 report endpoints — verified `AppRoles.Admin = "Admin"` literal (AppRoles.cs:5) == attribute string == FE `user?.roles.includes('Admin')`; "QTV" (DbInit:1454) = display-code NOT role-name; pattern proven (Catalogs/HrmConfigs identical). **Cat3 camelCase contract MATCH** field-for-field BE record PascalCase→FE interface (year/month/rows/grandTotal*/userId/fullName/ot*) — ASP.NET default camelCase, no Program.cs override. **BE handler correct:** `.Year/.Month` in IQueryable (EF-translatable DateTime), `.DayOfWeek`+holidaySet only AFTER `.ToListAsync()` (in-memory) — IQueryable-translation attack handled; holiday-check BEFORE weekend BEFORE weekday (test 2026-06-01 Mon-but-holiday proves override); `DateOnly.FromDateTime` correct (Holiday.Date=DateOnly); OtPolicy fallback 1.5/2.0/3.0; `IsDeleted` via AuditableEntity all 3 entities. Exporter mirrors ContractExcelExporter, ClosedXML 0.105.0, `RenderResult(Content,FileName,ContentType)` ctor order correct, DI registered. **MaTicket codegen:** `e` untracked at codegen time → inner SaveChanges persists ONLY sequence row, no double-insert; gen-on-Create (kanban no-workflow) vs Leave/OT gen-on-Submit — semantically correct; git-show confirms MaTicket was ALWAYS null pre-P11-F (closes gap). **1 MINOR (informational, defer):** sequence-gap-on-failure — codegen commits seq in own Serializable tx BEFORE `Add(e)+SaveChanges`; ticket-insert fail → burned IT/2026/NNN gap. NOT new defect = identical to existing Leave/OT pattern (project-wide accepted trade-off, cosmetic). MyAttendancePage MIRROR divergence intentional+documented (fe-user untouched, §3.9 OK). 0 mock markers. **Learned:** when spec NAMES an attack vector (gotcha #44 role-string), verify the LITERAL const value not just attribute presence — "QTV" display-code was the decoy; role-name match is the real check. **surprise:** Bash tool = bash not PowerShell (Select-String fails exit 127 → use grep). Verdict PASS — safe to commit. Tag [s52, p11ef, attendance-report, mat-codegen, gotcha44-disarmed].
- **2026-06-08 (S51 P11-C Vehicle+Driver + gotcha #57 pre-commit — PASS, 1 MAJOR caught) [em main proxy — reviewer return truncated gotcha #53]:** Reviewed Mig 44 (Vehicle/Driver catalog) + Mig 45 (filter 3 HRM unique) + FE KIND_CONFIG +2 + 5 tests (186 PASS). Independent build+test re-verify GREEN. **CAUGHT 1 MAJOR (Cat 3 cross-stack contract):** Driver FE↔BE required-field mismatch — FE render phoneNumber/licenseNumber/licenseClass OPTIONAL nhưng BE validator `NotEmpty()` + EF `.IsRequired()` NOT NULL → empty submit = 400/500. Root = inconsistent em-main brief (BE "mirror Vehicle"=required vs FE spec quên required). Fix: FE +`required:true` (align BE all-required như Vehicle). Cats khác clean (Mig diff clean, Authorize Roles=Admin writes, gotcha #57 grep-complete 3 HRM, DbInitializer idempotent + #51 infra-gated, SHA256 mirror, no copy-paste Driver↔Vehicle). **Learned:** parallel fan-out (BE∥FE file-disjoint) → bất kỳ inconsistency trong SHARED em-main contract chỉ lộ lúc integration; green tests ≠ correct contract (no test chạm empty-optional path). reviewer = the net. **surprise:** transient mid-deploy bundle hash (cicd lesson) + reviewer self-truncate trước khi ghi MEMORY → em main proxy. Verdict PASS post-fix. Tag [s51, p11-c, gotcha57, contract-mismatch-catch].
- **2026-06-07 (S49 Harness 1/2/3 adopt pre-commit — PASS all 3, no blocker):** Governance/infra adopt (no product code, no test impact). VERIFIED: H1/H2 = 2 sub scope-DISJOINT + tools `[Read,Grep,Glob,Bash+4RAG]` NO store_memory/Write (INFORM-only); genuinely **TAILORED not copy-paste** (SE 4-RAG vs AI_INFRA 2-RAG · dropped effort:max + agent-ops-monitor/sister · Fidelity→SE `reviewer`). H2 5-trục in harvest-curator.md + session-end §L.b(f). H2 wave-mode hmw.js mirror AI_INFRA + **B6 `git check-ignore` VERIFIED** (wave-*/+agent-teams/ ignored · hmw.js/README tracked). H3 self=`se` complete substitution · **SHA256 canonical formula byte-identical send==check** · 13 .gitkeep exact · adap-apply base-path `outbox\all\`. honest nấc executed-file/verified-runtime-PENDING. G-015 scan = 6 hits ALL negating ("KHÔNG enforced") = correct honesty. **1 MINOR (non-block):** README:11/18 "7-agent" ASCII diagram = **PRE-EXISTING** drift (git diff proved work này chỉ touch load-bearing title/decision-tree/tool-grant/matrix; diagram predates S47 frontend-designer) → tooling-auditor H1 designed-to-catch = self-validating adoption. **learned:** `git diff base..head` = discriminator introduced-defect vs pre-existing-drift (đừng đổ lỗi work mới cho drift cũ); name-collision tailor-verify = diff frontmatter AI_INFRA-canonical vs SE-instance. **surprise:** mojibake scan false-pos trên "ĐÃ" (U+00C3 = valid VN uppercase, KHÔNG double-encode → verify codepoint in-context trước flag); broadcast floor "12 .gitkeep" UNDERCOUNT (correct=13 incl `all/` adap-channel — em main đúng). Verdict PASS, safe commit + restart. Tag [s49, harness-adopt, governance, max-clean].
- **2026-05-30 (S43 P11-B LeaveBalance pre-commit — PASS, Max no-truncate):** 14 file (LeaveBalance entity+config+Mig42 + Features + Controller + deduction hook + Create/Update LeaveType guard + embed balance + FE×4 + tests). 154 PASS (130→154). **Deduction exactly-once VERIFIED** (terminal else only, guard Status!=DaGuiDuyet chặn re-approve; advance/reject/return no-deduct). **FK invariant fully closed** — grep 2 write site LeaveTypeId (Create + UpdateDraft) cả 2 guard AnyAsync→Conflict, bogus type không thể tới terminal FK insert. Embed balance = RequesterUserId (approver thấy đúng người tạo). admin `[Authorize(Roles=Admin)]`. **2 MINOR defer:** concurrency lost-update UsedDays (no RowVersion — human-sequential accept) · stale line-num comment. Verdict PASS. Tag `[s43, p11b-leavebalance, max-clean]`.
- **2026-05-28 (S35 G-H2 BE CRUD 16 endpoint pre-commit — PASS, Smart Friend 8× CLEAN):** 2 NEW file `HrmConfigFeatures.cs` 439 + Controller 137. build clean, 130/130 PASS. Cat1: 0 mock, 8 ConflictException (Holiday Update composite `(Year,Date)` BOTH fields). Cat3: class `[Authorize]` + 12 per-action `[Authorize(Roles="Admin")]`. Cat5: 8 Validator MaxLength MATCH EF source (Code=50 not spec 20). **2 MINOR defer:** ListHolidays no IsActive filter (inconsistent sibling) · OtPolicy "1 active unique" NOT enforced handler (G-P1 ambiguous nếu 2+ active). Verdict PASS. Tag `[s35, smart-friend-8x-clean]`.
- **2026-05-26 (S33 Plan B G-H1 Phase 2 pre-commit — PASS, Smart Friend 6× CLEAN):** 17 file (3 BE + 6 FE new + 6 mod + 2). SHA256 mirror 3 file IDENTICAL admin==user. 5 endpoint real mediator.Send 0 mock. Mig 34 `AddEmployeeProfiles` 7 table UNIQUE indexes + FK Cascade. SeedDemoEmployeeProfiles NOT gated DemoSeed (gotcha #51 ✓). gotcha #50 Layout staticMap mirror ✓. **3 MINOR defer:** EmployeeCode race SERIALIZABLE low-risk · Update 3 bool not nullable (partial reset) · Delete DateTime.UtcNow direct. Verdict PASS. Tag `[s33, hrm-mig34, smart-friend-6x]`.
- **Smart Friend cumulative 8× CLEAN:** (1) S22 #44 silent-403 · (2) S25 #48 SQLite tie-break · (3) S29 password ≥12 · (4) S29 ApplicableType cross-module · (5) S33 BW test · (6) S33 Plan B Phase 2 · (7) S35 FE forms · (8) S35 G-H2. Plus 9× G-O2 (S36, em không track ở đây). 2 MAJOR catches total (S29 password + S29 ApplicableType); rest clean với MINOR defer.
- **Archived S29-S33 detail + S32 startup → `archive/2026-05-q2.md` + git d2f52ba (S40 curate):** S33 Plan C B-Wrap 9/9 [Fact] verify · S33 startup drift audit (CLAUDE.md SEVERE → patched S40) · S32 wrap/startup standby · S29 wrap 2 MAJOR catch detail. KEY absorbed in bug patterns + Smart Friend cumulative above.
---
## 🔄 Curate trigger
- >~30KB → archive recent → L2 `archive/<period>.md`. Stale >3mo → remove.
- **Last curate: 2026-05-29 S40 em main proxy** (28.4→~18KB): archived S33 Plan C + S33 startup + S32×2 + S29 wrap detail → q2 + git d2f52ba; refreshed stale (81/111→130 test, 47→55 gotcha, 31→40 mig, ~146→211 endpoints). Foundation (bug patterns + 5-category + Smart Friend guard + cross-module security) preserved. Prev: S34 q2 · S22 q1.