Closeout S65 (~6 deploy prod-verified, anh + anh Kiệt FDC UAT realtime): - STATUS/HANDOFF S65 (Mig 52 · 88 bảng · 263 test · 65 gotcha · menu 53 · bundle admin BDwV5d0X / user DbVv6rsf Run #295) + session log #289→#295. - gotcha #65 (build csproj con ≠ dotnet build slnx gồm tests → CS7036 Run #291 FAIL-gated; fix +trailing-optional sweep). - CLAUDE.md root Mig 50→52 + PE row +Mig 52. - Harvest: H2 GATE 2-MISS closed — 2 on-behalf record (PE-Workflow FE + reviewer empty-return #53) → impl-frontend + reviewer agent-memory. H1 tooling CLEAN (roster/skill/plugin 11/6/18). - Memory (user-global): +feedback_workflow_fanout_reliability. Carry-P1: cicd-monitor L1 82KB curate-L2 · mirror Employee page→fe-admin · test-after (HoSoLink/ParentId/HRM-perm). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 KiB
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 RAGsearch_memoryjust-in-time. Keep entry ≤ 1.5K chars (gotcha #53). Full verbatim history pre-S40 → gitd2f52ba+archive/2026-05-q1..q2.md.
📁 Area memory (L2 on-demand — Read khi review vùng tương ứng)
- S62 PE budget soft-warning — PASS: hard-block→soft-warning; submit-guard intact + validator giữ
BudgetPeriodAmount>0, row8 negative-safe (additive-only). Validator classPurchaseEvaluationFeatures.cs:317(reconcile S63: stray cwd-misland → canonical, anchor verified). - Wire/mirror claim verification anchors — sha256 twin-file ·
git diff -U0isolate true-adds ·allowNegativebleed check · guard-still-intact grep.
🎯 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-memoryOrderBy(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 == ExpectedTypevalidate ON Create BEFORE instantiation (mirrorPurchaseEvaluationFeatures.cs:62-77). Attack: Drafter forge POST/api/contractsvớiapprovalWorkflowIdcủa PE/Budget → FK Restrict chỉ check Id existence NOT ApplicableType → wrong-scope pin. Also re-verifyIsActive+IsUserSelectableserver-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.slnx0 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
- ❌ 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(pathsrc/Backend/SolutionErp.Infrastructure/Persistence/Migrations/). Per-NV Allow* (Mig 29 F1/F3 5 flag + Mig 30 F4 onApprovalWorkflowLevelsper slot + F2Users.AllowDrafterSkipToFinalper 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) · UATnv.test@solutions.com.vn/TestUser@123456(Drafter CCM). - Prod: api/admin/eoffice.solutions.com.vn. Pin: MediatR
12.4.1(flagVersion="14) · Swashbuckle6.9.0· Node CI20.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-16 (S65 PE mục E HoSoLink review — em-main PROXY, PE-Workflow reviewer-stage died-empty): Review mục-E hyperlink render + HoSoLink BE wiring (
5a0aaa4). Reviewer-stage trong Workflowpe-hoso-link-rename-proreturn RỖNG → em main self-gate evidence: Detail DTOhoSoLinkpresent +nullbackward-compat phiếu thật (Run #293 GET 200); Create/Update +trailing-optionalHoSoLink=nullKHÔNG vỡ call-site (grep 0 manual ctor — KHÁC CreateDepartmentCommand #291 CS7036 vì positional-required vs trailing-optional); mirror fe-user==fe-admin SHA256 IDENTICAL (PeDetailTabs+PeWorkspaceCreateView); hyperlink<a target=_blank rel=noopener noreferrer>no reverse-tabnabbing; rename "Dự trù PRO"→"Ngân sách PRO" CHỈ display (giữ "Ghi chú từ PRO" + field-code). LEARNED: hyperlink free-text = no server-side XSS (render-as-href client-only); absolute-set Update (null=clear) chủ đích. SURPRISE: reviewer-stage chết-rỗng trong fan-out = lý do verify-heavy task vẫn cần em-main self-gate dù có Workflow (verdictfeedback_workflow_fanout_reliability). Tag[s65, pe-section-e-review, em-main-proxy-self-gate, hosolink-backward-compat, workflow-fanout]. -
2026-06-16 (S65 public Hồ sơ NS read for all roles — static pre-commit, PASS, 0 blocker, gotcha #44 family CLEAN): 1-file change DbInitializer.cs (+66, call-site :2046 SAU revoke :2040 + new
SeedAllRolesHrmProfileReadPermissionsAsync:2203). Prod NOT deployed (static review, build PASS đã claim). 7 verify ALL PASS: (1) Ordering — grant gọi SAURevokeTemporarilyHiddenModulesAsynctrong SeedAsync → grant thắng (git diff confirms call sits immediately after revoke). (2) Upgrade path prod-critical — method MUTATES existing rowif(!row.CanRead){row.CanRead=true;upgraded++}(EF change-tracked → SaveChanges persists); NOT skip-existing-noop. Correctly fixes S58-class bug (revoke set CanRead=false on prod rows → upgrade flips true). (3) Scope precise —hrmKeys = new[]{MenuKeys.Hrm, MenuKeys.HrmHoSo}EXACTLY 2; NO Hrm_Dashboard/Hrm_Config*/Off*/Personal.Hrmis NOT one of 4 inherit-roots (Contracts/Workflows/PE/PeWorkflows in GetMyMenuTree:56-59) so granting Hrm root does NOT cascade to Dashboard/Config children → they keep own false flags → filtered out byHasAccess(n)=n.CanRead||Children.Any(HasAccess). Menu shows Hrm root → Hồ sơ NS leaf ONLY (HrmHoSo ParentKey=Hrm:1806, Dashboard sibling ParentKey=Hrm:1850 stays hidden). (4) Read-only — add-path CanCreate/Update/Delete=false; upgrade-path touches ONLY CanRead. (5) No regression — Admin bypass at MenuPermissionHandler:27 untouched; revoke unchanged; Off/Personal/Dashboard/Config stay hidden after full seed. (6) Idempotent — 2nd run: row.CanRead already true →if(!row.CanRead)false → 0 change. (7) No non-Admin write path —MenuPermissionHandlerRead→AnyAsync(CanRead) is what GET checks; all 19 EmployeesController write actions (main+5 satellite) require Hrm_HoSo.Create/Update/Delete which grant leaves false → 403. surprise/monitor-note (NOT a defect, NOT introduced by this change): HrDashboardController/HrmConfigsController/Attendances/LeaveBalances carry ONLY class-level[Authorize](any-auth, NO per-action Hrm_.Read policy) — so their data was already reachable by direct URL pre+post S65 (menu-hide ≠ API-lock; S58 revoke comment DbInit:2153-2155 explicitly acknowledged this). S65 does NOT widen it (only touches perm matrix rows Hrm+Hrm_HoSo + menu filter). cicd-monitor must NOT assume "Dashboard hidden in menu"=="dashboard data unreachable". Spec comment said "6 catalog Hrm_Config" but there are 6 config leaves + Hrm_Config subgroup = 7 keys — cosmetic count, all stay hidden, not a code bug. Learned: for menu-key read-grant, verify the granted root is NOT an inherit-root (else cascade leaks siblings) + trace HasAccess filter + confirm leaf ParentKey chains to the visible root; upgrade-path correctness = grep that method MUTATES row (not skip-existing) when a prior revoke pre-set the flag false on prod. Verdict PASS — safe commit. Tag [s65, public-hrm-hoso, upgrade-path-correct, inherit-root-no-cascade, gotcha44-family-clean, menu-only-not-api-lock-monitor-note]. -
2026-06-12 (S60 đợt1 PE submit-guard + drafter-bypass gate — KHÔNG DELIVER, die mid-run, on-behalf em main ghi hộ, H2-proposed): Task: review
37122f0cross-stack (BE TransitionAsync submit-guard đủ-4-thông-tin mục 3 + bypass người-soạn-trong-chuỗi V2 BƯỚC-ĐẦU-only + FE PeDetailTabs ×2 + 14 PeSubmitGuardAndBypassTests 240→254). Die mid-run #53-class (commit body tự khai "Reviewer die mid-run → em main self-gate evidence-checklist PASS 0 blocker") → ship Run #283 PASS prod-verified, bundle rotate both. LEARNED: self-gate em main đứng vững lần 2 (sau S57bis) — checklist deterministic (test gate + diff scope + prod smoke 401/404-control) đủ cho PE refinement cross-stack. SURPRISE: die lần 3 trong 2 ngày (S57bis die-0-byte ×2 + S60 mid-run) DÙ promote-tier inherit Fable 5 → model-tier KHÔNG phải nguyên nhân die (nghi resume-kill/harness class) — trend data cho Harness-4. Tag[s60, die-mid-run-3rd, self-gate, on-behalf]. -
2026-06-11 (S57bis product gate — KHÔNG DELIVER, die-0-byte ×2, on-behalf em main ghi hộ, H2-proposed): Cả 2 spawn (email-gate đầu + final gate) chết 0-byte output 0 return (resume-kill class #3, ref
feedback_agent_kill_recovery) → em main SELF-GATE evidence-checklist: grep authz key-set + role-string vs AppRoles + Mig 49 Up/Down reversible + 240 test + Run #381 + prod smoke 401/404-control. LEARNED: output-file size=0 + im >5 phút = chết, KHÔNG đợi thêm; KHÔNG re-spawn >2 lần trong session có--resume. SURPRISE: khác S52 killed-with-partial — lần này 0-byte tuyệt đối (không gì recover được từ return). Tag[s57bis, die-0-byte-x2, self-gate, on-behalf]. -
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 pinclaude-opus-4-8+ 4inherit+ 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 buildfe-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+classNamepassthrough unchanged, onlycn()literal; Dialog{open,onClose,title,children,footer,size}destructure + sm/md/lg→max-w map intact (+aria-label="Đóng" = a11y GAIN); DataTableColumn<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, +cnimport 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-400on 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-400→500 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 v4shadow-xsis 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; FE↔BE 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
/assignhạ[Authorize(Roles="Admin")]→[Authorize]any-auth; authz moved INTOAssignItTicketHandler(Admin-OR-IT Forbidden + assignee-must-IT Conflict) + newGetAssignableItStaffQuerycapability endpoint. Independent re-verify GREEN:dotnet test SolutionErp.slnx= 216 PASS (58 Dom + 158 Infra, Failed:0 Skipped:0, +13 matches 203→216 claim exactly) · fe-admin + fe-usertsc -p tsconfig.app.json --noEmitBOTH exit 0 clean. #2 CHÍ MẠNG role-string "Admin" CONFIRMED REAL (full chain traced):AppRoles.Admin="Admin"literal (AppRoles.cs:5) → SeedRolesAsyncName=roleName(DbInit:1485) so DB Role.Name=="Admin" → Identity GetRolesAsync returns NAMES → JwtTokenService:32new Claim(ClaimTypes.Role, r)→ CurrentUserService:30-31FindAll(ClaimTypes.Role)→cu.Roles.Contains("Admin")CORRECT. "QTV" (DbInit:1458 RoleLabels) = ShortName DISPLAY label only = decoy. Program.cs JWT sets NORoleClaimTypeoverride (ClaimTypes.Role symmetric write/read). Same proven pattern as every existing Roles="Admin" endpoint. Bypass airtight: controller any-auth → handler sole gate; guardif(!isAdmin && !(itDeptId is Guid mine && myDeptId==mine)) Forbiddenfail-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 assertAssignedToUserId.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 admin→ANY 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 const→seed Name→GetRolesAsync(names not codes)→Claim(ClaimTypes.Role)→reader AND grep JWT cfg forRoleClaimTypeoverride (none=symmetric) — display-code (QTV) in RoleLabels/ShortName dict = classic decoy. surprise: moving authz controller→handler 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.slnx0-warn/0-err ·npm run buildfe-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.Offorder=8 icon=FileBarChart(verified valid lucide aliasFileChartColumn as FileBarChart— getIcon resolves via Icons[name]); App.tsx route/attendance/report → AttendanceReportPagePRE-EXISTING committed S52 (6a66429, no diff, 170-LOC real page not stub) — Layout maps to REAL route.types/menu.tscorrectly NOT mirrored (key:stringnot typed union). admin auto-perm viaSeedAdminPermissionsAsynciteratingMenuKeys.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}/assignbody{assignedToUserId}MATCHES BE recordAssignItTicketBody(Guid AssignedToUserId)(ItTicketsController:42); endpoint[Authorize(Roles="Admin")](:34) under class[Authorize]— admin-app FE calling = correct; user-list reuses EXISTING/usersGET (PagedResult<UserDto>items{id,fullName,email}, no new BE endpoint, lazyenabled: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: lucideFileBarChartis 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 (xxd5b 49 73 44 65 6c 65 74 65 64 5d 20 3d 20 30— spaces around=, not guessed); index STAYSunique: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-checkAnyAsync(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 hasAnyAsync(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 --noEmitexit 0. Cat3 gotcha #44 attack DISARMED:[Authorize(Roles="Admin")]×2 report endpoints — verifiedAppRoles.Admin = "Admin"literal (AppRoles.cs:5) == attribute string == FEuser?.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/.Monthin 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.FromDateTimecorrect (Holiday.Date=DateOnly); OtPolicy fallback 1.5/2.0/3.0;IsDeletedvia AuditableEntity all 3 entities. Exporter mirrors ContractExcelExporter, ClosedXML 0.105.0,RenderResult(Content,FileName,ContentType)ctor order correct, DI registered. MaTicket codegen:euntracked 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 BEFOREAdd(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→SEreviewer). H2 5-trục in harvest-curator.md + session-end §L.b(f). H2 wave-mode hmw.js mirror AI_INFRA + B6git check-ignoreVERIFIED (wave-*/+agent-teams/ ignored · hmw.js/README tracked). H3 self=secomplete substitution · SHA256 canonical formula byte-identical send==check · 13 .gitkeep exact · adap-apply base-pathoutbox\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 inclall/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.cs439 + 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
AddEmployeeProfiles7 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+ gitd2f52ba(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.