Files
solution-erp/.claude/agent-memory/investigator-codebase/MEMORY.md
pqhuy1987 37122f0f64
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m38s
[CLAUDE] PurchaseEvaluation: rang buoc du 4 thong tin muc 3 moi gui duyet + bypass nguoi soan trong chuoi duyet (UAT anh Kiet S60)
- Rename muc 3: "Chon NCC / TP thang thau" -> "Don vi NCC/TP duoc chon" (anh Kiet chot chu) x2 app + wording phu nhat quan
- Guard gui duyet du CA 4 (anh chot): don vi duoc chon + gia chao thau >0 + ngan sach (Budget link HOAC nhap tay) + bang so sanh dinh kem
  + BE ConflictException gop moi muc thieu 1 lan, ap ca Admin (TransitionAsync submit branch)
  + FE pre-check missingForApproval cung predicate -> disable nut + tooltip liet ke du (computeGiaChaoThau extract single-source)
- Bypass drafter-in-chain (luat GENERIC theo cap, anh chot): V2-only, BUOC DAU only - nguoi soan la approver cap k -> auto qua Cap 1..k khi gui
  + Audit 3 tang: Approval row AutoApprove per cap + LevelOpinion CHI slot chinh chu (khong gan chu ky NV bi skip) + Changelog
  + Pointer: k<max -> Cap k+1; het buoc -> Buoc 2 Cap 1; workflow 1 buoc -> terminal DaDuyet
  + TraLai resubmit ap lai idempotent (opinion UPSERT)
- Tests: +14 PeSubmitGuardAndBypassTests (240 -> 254 PASS)
- Reviewer die mid-run (gotcha #53 class) -> em main self-gate evidence-checklist PASS 0 blocker

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

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

27 user-memory tại C:\Users\pqhuy\.claude\projects\D--...\memory\MEMORY.md (index). Key: per_chunk_commit · uat_skip_verify · audit_reuse_before_clone · designtime_runtime_db · per_nv_permission_scope · ef_migration_backfill_reorder · status_handoff_tiering (S40) · 7agent_split_upgrade (S39).

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 (S40 verified — re-grounded)

  • 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/). 40 mig, last 20260528090839_AddAttendances.
  • Counts S40: 40 mig · 84 SQL tables (77 DbSet + 7 Identity, count .ToTable() ModelSnapshot NOT DbSet) · ~211 endpoints · 65 FE pages (36 admin + 29 user *Page.tsx) · ~53 menu keys (BE MenuKeys const) · 130 test (58 Domain + 72 Infra) · 55 gotchas (format ### N. highest #55) · 27 user-memory · 6 skills · 7 sub-agents
  • 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)
  • 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 S37 inline ApproveV2Async). WorkflowApps skeleton (Mig 39 S38 — ApproveV2 advance DEFER Phase 11).
    • Mig 25 IsUserSelectable · Mig 26 PE LevelOpinions UPSERT · Mig 29 Allow* per-NV (F1/F3 per Approver slot + F2 Users.AllowDrafterSkipToFinal) · Mig 30 F4 AllowApproverEditBudget · Mig 31 SkipToFinal→ApproverLevel
  • State machine PE 5 trạng thái: Nháp / Đã gửi duyệt / Trả lại (TraLai=98) / Từ chối / Đã 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 (FIFO — older → archive/git)

  • 2026-06-12 (S60 recon #2 — V2 engine map cho drafter-in-chain bypass): 3 entity V2 CÙNG 1 file Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs — Step.Order :65 1-based, Level.Order :78 1-based PER-STEP (reset mỗi step), Level.ApproverUserId :80 Guid đơn; OR-of-N = N Level rows cùng Order (service GroupBy :475 — entity comment :73 "KHÔNG OR-of-many" STALE Mig22-era). PurchaseEvaluationWorkflowService.cs: submit :131-158 init pointer StepIdx=0 :151 + LevelOrder=1-if-V2 :153 (TraLai resubmit CÙNG path = restart từ đầu); ApproveV2Async :446-634 — guard actor∈HashSet ApproverUserId :488, Approval row :501, LevelOpinion UPSERT :522-546 (SignedByUserId KHÔNG nullable — Guid.Empty system :536; placeholder "(duyệt — không ý kiến)" :526), advance level++ :605 / step++ :628 / terminal DaDuyet :617-624 (pointers null + LogTransition). Notify DRAFTER-only :748 — KHÔNG notify approver Ở ĐÂU CẢ (submit silent vì drafter==actor). V1/V2 fork approve :167; submit chung (chỉ :153 conditional). skipToFinal F2 :561-602 = PRECEDENT advance-pointer-KHÔNG-ghi-opinion cho slot bị skip. FE Section 5 fe-admin/src/components/pe/PeDetailTabs.tsx:510 render approvalFlow×levelOpinions match stepOrder :528; counter :531 đếm TỪ opinions (level bị skip hiện "chưa ký"); badge :592 signedByUserId!==approverUserId → "⚠ Admin … duyệt thay" :602 (text misleading nếu bypass ký hộ slot người khác). Chỗ chèn bypass: submit branch sau :153. Tag [s60-recon2, v2-engine-map, drafter-bypass].

  • 2026-06-12 (S60 PE Section-3 submit-guard recon, on-disk): Submit path: POST /pe/{id}/transitions (Controller:68) → TransitionPurchaseEvaluationCommand (Features:434, validator :446 shape-only) → handler :462 (auth+NotFound, NO data check) → PurchaseEvaluationWorkflowService.TransitionAsync:38; submit branch :131-158 (Nháp/TraLai→ChoDuyet) guard ROLE-only (Drafter/DeptMgr/Admin :138-144), KHÔNG validate supplier/budget/quote/attachment. WorkItem guard S57bis create-only (:43/:66). Section-3 map (ChonNccSection PeDetailTabs:1135): (a) winner = header SelectedSupplierId (entity :27 loose-Guid; set POST /{id}/select-winner → DetailFeatures:390, NO phase guard); (b) budget dual BudgetId || BudgetManualAmount (ManualName always-null :847, manual-detect :825); (c) giá chào thầu = DERIVED sum quotes winner row :1139-48 (0=chưa nhập → guard cần >0); (d) bản so sánh = attachments supplierRowId===null ONLY :1150-53, KHÔNG check Purpose=4 → BE guard mirror predicate null-row tránh FE mismatch. Label: heading DUY NHẤT :228 (CAPS do h3 uppercase :317); SHA256 identical 2 app. Nút gửi: PeDetailTabs :291-304 + canSubmitForApproval:146 + submitDisabledReason:153 = chỗ chèn FE pre-check; S59 hide-self PeWorkflowPanel:271. Test mirror: Services/PurchaseEvaluationWorkflowServiceGuardTests.cs (BuildPeInChoDuyet:46) + Application/PeWorkItemGuardTests.cs (:43 BuildCreateHandler). Tag [s60-recon, pe-submit-guard, section3-map].

  • 2026-06-11 (S59 recon — prod test-data wipe + PE tree Hạng mục, prod+on-disk): Prod: PE=10 active (1 Nháp + 1 DaDuyet(7) + 8 ChoDuyet(10), MaPhieu A/031-040, ALL WorkItemId NULL) + child 20/10/20/28/138/18/18 (Sup/Det/Quote/Appr/Chg/Att/LvlOp); Contracts=7 ALL [DEMO] 05-08 pin V1 (AwId NULL) + Appr15 + details15; Budgets/WorkflowApps/Proposals/Attendances/Meetings ALL 0; Notifications 64. Seq: PE/2026/A=40 B=1; CT=7 demo prefix LastSeq=1. FK: PE child CASCADE trừ Quotes→PE NO_ACTION (multi-path; Plan R S23 proved single DELETE FROM PurchaseEvaluations OK — NO_ACTION check end-of-statement sau cascade Details→Quotes). Contract child ALL CASCADE. PE.ApprovalWorkflowId Restrict → wipe PE trước khi xóa AW QT-DN-V2-001 v1 (inactive, còn 1 PE pin). AW V2=8: 7 ghim KEEP. Uploads orphan: purchase-evaluations/ 19 folder vs 10 PE → ~10 orphan từ S23 (file không xóa); contracts/ 1. Demo gate OK: SeedDemoContracts/PE TRONG DemoSeed:Disabled (DbInitializer:80,131-132) → wipe không resurrect. Surprise: Users 55 total / 21 active — 20 user THẬT batch 2026-06-11 06:01 (S58 seed fix ăn; thanh.lethanh NOW EXISTS — stale S57bis mem; chuong.phan typo-domain VẪN active song song twin). FE tree: pe/PurchaseEvaluationsListPage.tsx:138-179 Project>Year(createdAt :150)>Supplier; SHA256 identical 2 app; PeListItem ĐÃ có workItemId/Name (types :116-118, BE Features :514/570/644) → đổi tree FE-only. Tag [s59-recon, prod-wipe, pe-tree-workitem].

  • 2026-06-11 (S57bis lock no-op — prod user census, on-disk+prod): LockDemoSampleUsersAsync (DbInitializer.cs:1552, chạy CUỐI :98) hardcode 14 named-person email (bod.huynh/pm.nguyen/fin.do/qs.hoang…) = population CHỈ CÓ TRÊN DEV. Prod 34 user ALL-active: 20 UAT-matrix placeholder hand-created batch 2026-05-13 15:04-05, scheme {act,equ,fin,hra,pm,qs}.{nv,pp,tp}@ + bod.{1,2}@ (FullName tự khai "ACT NV - Drafter+Accounting", "[Bypass]"/"[SkipFinal]" = test Mig 29-31 flags) + 9 real staff hand-created 05-04→05-12 + binh.lethanh@ (người thật Lê Thanh Bình — seed dùng thanh.lethanh@ KHÔNG tồn tại prod) + chuong.phan@solution.com.vn TYPO-domain dup (twin đúng tạo 05-12) + admin/catalog.manager/nv.test. ROOT CAUSE seed-user never-on-prod: prod Identity:Password:RequiredLength=12 (appsettings.Production.json) vs DemoUserPassword="User@123456"=11 chars → CreateAsync silent-fail MỌI startup từ prod-init 04-21 (code comment :1675-79 đã biết); Dev fallback 8 (DependencyInjection.cs:67 ?? 8, Development.json no Identity section) → Dev đủ 33 user named-person. bod.1@ NEVER in git pickaxe = tạo tay qua admin UI, không phải seed. Surprise: _Dev hiện CŨNG chưa khóa (Locked=0; LockoutEnd=MaxValue sẽ persist qua reconcile re-activate :1714 nếu từng chạy) → lock chưa từng execute against _Dev runtime. Fix cần 20 email prod-thật; GIỮ binh.lethanh + 9 real + admin/catalog.manager; nv.test@ = creds smoke-verify (khóa = vỡ cicd smoke). Tag [s58, s57bis-lock-noop-recon, prod-user-census, pwd-policy-env-divergence].

  • 2026-06-11 (S57bis PE recon — 4 đầu việc sếp, on-disk): PE entity NO Year, NO WorkItem link (PurchaseEvaluation.cs:15 ProjectId req; Detail free-text PurchaseEvaluationDetail.cs:10-13). Create cmd PurchaseEvaluationFeatures.cs:19-30; MaPhieu gen-AT-CREATE :114-116 format PE/{YYYY}/{A|B}/{Seq:D3} (PurchaseEvaluationCodeGenerator.cs:23). Main create UI = PeWorkspaceCreateView.tsx (:151 workflow-select isUserSelectable ĐẦU TIÊN → tenGoiThau → projectId → DiaDiem → MoTa → PaymentTerms → budget; canSubmit :129 = wf+project+ten). PE controller class-[Authorize] ONLY no policy → mở menu là đủ, no silent-403. Pe_* leaves NOT in MenuKeys.All (chỉ root :156); PE defaults 7 role × 11 key (root + 2type×{group,WfView,List,Create,Pending}) DbInitializer.cs:2098-2160. S57 SeedAllRolesReviewReadPermissionsAsync:1993-2001 InReviewScope EXCLUDES Pe; extend đúng = key == MenuKeys.PurchaseEvaluations EXACT (prefix "Pe" sẽ dính PeWorkflows admin!) — root inherit cascade (GetMyMenuTreeQuery.cs:49-82). Demo gate: prod appsettings.json:35 DemoSeed:Disabled=true → 7 [DEMO] HĐ + 4 [DEMO] PE (MaPhieu [DEMO]-A-001) KHÔNG lên prod; UNGATED trên prod = 31 users + 18 demo NCC + 8 demo project (:2244-2315) + real 62/71/3 (:2329-2522). ⚠️ Clear-demo gotcha: seed re-add per-code idempotent MỖI startup → xóa DB-only sẽ resurrect, phải gỡ khỏi DbInitializer code. WorkItem write Admin-only (CatalogsController:113-130) — CatalogManager có menu-perm nhưng API write bị chặn. Tag [s57bis, pe-recon, demo-inventory].

  • 2026-06-10 (S57 perm-broaden blocks A/B/E/F — on-disk): BE AUTHZ SPLIT (decision-critical for E): Config controllers gate WRITE behind [Authorize(Roles="Admin")], READ open to any-authed: HrmConfigsController.cs:15 class [Authorize]+GET open :19-21, all POST/PUT/DEL Roles="Admin"; CatalogsController.cs:14 same (write :23+ Admin); MeetingRoomsController.cs:15 same (comment :9-10 explicit). → granting FE read on Hrm_Config/Catalogs/MeetingRooms = pure UI-visibility, BE already correct. BUT Master 3 controllers = class [Authorize] ONLY, no per-action role: SuppliersController.cs:17, ProjectsController.cs:11, DepartmentsController.cs:11 — ANY authed user can POST/PUT/DELETE via API (FE menu perm is only gate, not BE-enforced). Flag em-main: making Suppliers/Projects/Departments visible-to-all ⇒ staff can write master incl. S55 prod data unless add [Authorize(Roles="Admin,CatalogManager")] or per-action policy. S55 PROD DATA location: SeedRealMasterDataAsync :2267-2460 writes to Projects(62, :2270), WorkItems(71=CatalogWorkItems key, :2430-2438), Suppliers(3, :2440-2456) — all ungated idempotent per-code. 10 departments now (SeedDepartmentsAsync:2104): 9 orig + IT (:2115). 31 demo users (SeedDemoUsersAsync:1553-1609): dept spread BOD4/PM2/CCM9/PRO6/QS2/FIN2/ACT1/EQU1/HRA2; roles = mostly Drafter/CostControl/Procurement + 1 CatalogManager (catalog.manager@, dept PRO, :1608). NO user has zero-role; every demo user authenticatable. Inherit-root display: GetMyMenuTreeQuery.cs:96 HasAccess = CanRead OR Children.Any(HasAccess) → a non-inherit root (Hrm/Off/Master) auto-shows if ANY child has CanRead, so granting leaves is enough to reveal the root node (root row itself optional for display, but grant it too for cleanliness). Inherit-roots (Contracts/Pe/Wf/PeWf) cascade root→child so root-row-only suffices there. Menu already S57-edited: Personal group + Off_ChamCong re-parent Off→Personal via parentBackfill:1908. Tag [s57-perm, be-authz-split, master-write-open, s55-data, 31user].

  • 2026-06-10 (S57 perm-broaden RECON blocks C/D — RAG down, on-disk): SEED MODEL: SeedAdminPermissionsAsync DbInitializer.cs:1939-1977 Admin loops MenuKeys.All CRUD=true skip-existing dedup (:1950/:1952). Calls 2 sub: (a) SeedPurchaseEvaluationPermissionDefaultsAsync :2036-20987 roles {Drafter,DeptManager,Procurement,CostControl,ProjectManager,Director,AuthorizedSigner} Read+Update on PE keys only; (b) SeedCatalogManagerPermissionsAsync :1984-2029 → role CatalogManager full-CRUD 9 master keys. NO generic per-employee Read seed — plain Drafter user sees ONLY PE keys; DOESN'T see Off_/Hrm_/Master/Contracts. Most non-admin staff today see ~nothing but PE. GetMyMenuTree :96 filters CanRead=true. Permission entity Permission.cs:3-15: RoleId/MenuKey/CanRead/Create/Update/Delete. Dedup=app-level skip-existing per(RoleId,MenuKey), NO DB upsert. 13 AppRoles AppRoles.cs:23: Admin(system)+12 employee. 4 inherit-roots GetMyMenuTreeQuery.cs:56-84: Contracts/Workflows/PurchaseEvaluations/PeWorkflows — root grant auto-cascades to child IF child no own row (:66). Hrm/Off/Master NOT inherit → each leaf needs own row or add to switch. GRANT-ALL pattern (block D): mirror CatalogManager seeder but loop roleManager.Roles (all 13) × chosen key-set, CanRead=true only, insertion AFTER SeedCatalogManagerPermissionsAsync :1976. Tag [s57-perm-recon, seed-model, no-employee-default, inherit-4root].

  • 2026-06-10 (menu-order cross-repo recon SE↔NAMGROUP, RAG down, on-disk): SE menu seed = SeedMenusAsync DbInitializer.cs tuple-list (NOT partial). Văn phòng số root Off (Order=29) :1769-1792; HR root Hrm "Nhân sự" (Order=28) :1754-1767 (+Hrm_Dashboard appended out-of-order :1791). ⚠️ HR SCATTERED 2 roots: Hrm holds only Hồ sơ+Cấu hình HRM(6 leaf)+Dashboard; transactional HR (Nghỉ phép/OT/Công tác/Đặt xe/Chấm công/Báo cáo CC) live under Off as Off_DonTu_*/Off_DatXe/Off_ChamCong/Off_AttendanceReport. SEED = UPSERT that RE-SETS Order (:1845-1871 if(existing.Order!=o){existing.Order=o}) → reorder in code propagates to Dev/prod next deploy, NO migration. BUT Label/ParentKey/Icon NOT touched on existing rows (:1855 comment) — rename needs separate labelBackfill dict :1874. Order = BE-only: GetMyMenuTreeQuery.cs:35 OrderBy(m.Order); both FE Layout.tsx render useAuth().menu as-is, staticMap+menuKeys.ts = key→route ONLY (no sort). FE needs NO edit for pure reorder. NAMGROUP "Puro" = hardcoded FE array (NOT DB seed), index=order: client InternalLayout.tsx:83-118 (Nhân sự 3-item / Văn phòng số 6-item FLAT / Chấm công under separate "Cá nhân" group); admin AdminLayout.tsx:87-119 splits by function not HR/office. NAMGROUP Đơn từ/Phòng họp/Đề xuất = flat single links (no sub-children) vs SE deep-nested. Tag [menu-order, se-namgroup, seed-upsert-order, fe-be-driven, s57].

  • 2026-06-09 (S56 pre-golive verify — 4 logic streams, all PASS): Audited P11-B/D/E/F + ApproveV2 + catalogs + S55 master-wiring. LeaveBalance deduction exactly-once (terminal DaDuyet, guard Status!=DaGuiDuyet :296 blocks re-approve), FK guard Create+UpdateDraft→Conflict; AttendanceReport classify day-type IN-MEMORY (Holiday DateOnly HashSet), OtPolicy multiplier; MaTicket gen-on-Create Serializable IT/2026/NNN. Tests cover (LeaveBalance 9 + AttReport 2 + codegen 3 = 29 green). ApproveV2 4-module flatten Steps→Levels correct; Travel/Vehicle ApproveV2 = 0 test (cookie-cutter of tested Leave/OT — add 2 smoke post-golive). master-data idempotency PROVEN (DbInitializer re-run → counts identical, per-code guard :2310/:2404/:2422). ⚠️ PROD FACT (corrects stale S52 mem): dept "IT"/Phòng CNTT DOES exist (Id 65CC6307…) but has 0 active users on prod → ItTicket auto-assign no-ops, reassign dropdown empty, SLA job no notify-target. Pre-golive ops fix: assign ≥1 real user to dept IT (1 UPDATE, no code). Tag [s56, pre-golive-verify, logic-pass, dept-IT-empty-prod, travel-vehicle-untested].

  • 2026-06-09 (S56 Phase 2 FE-redesign RECON — 25 page audit, on-disk): NOT a rewrite — S55 already redesigned ui-primitives + DataTable + shell → any page importing ui/{Button,Input}+DataTable AUTO-inherits density. Hover-hidden quick-win nearly absent: repo-wide grep opacity-0×group-hover = only 1 real site ContractCreatePage.tsx:196 (EXCLUDED scope) + DataTable.tsx:15 is a comment forbidding it (good). In-scope = 0 hover-hidden fixes. DataTable adoption split: only 5/25 use <DataTable> (Suppliers/Projects/Departments/Users/Forms); 12 pages roll RAW <table> (MeetingRooms/Catalogs/HrmConfigs/EmployeesList/Proposals/WorkflowApps/Attendance×2/MenuVisibility/Roles) → custom density pass needed. Drawer ≥8-field candidates = 3: Suppliers (9 fld, Dialog), Projects (10 fld, Dialog), Users-CREATE (~8 fld w/ roles multiselect, 4 Dialogs total but only create is big). All currently big-Dialog → convert. NO Drawer.tsx exists (ui/ = Button/Dialog/Input/Label/Select/Textarea only) → build first. Bậc-thang reference ALREADY EXISTS: EmployeesListPage.tsx (1200L) = canonical inline add/edit-row for 5 satellites (setEditing{X}Id + addingX mutex, :256-356) → extract InlineEditRow pattern from here, reuse for Catalogs/HrmConfigs/MeetingRooms (these 3 currently edit via Dialog :251/:316/:232, ≤7 cols → bậc-thang candidates). Modal-detail = NONE: ProposalDetail:275 + WorkflowAppDetail:424 Dialogs are tiny action-confirm (just <Textarea> ý kiến), detail body already inline 2-col grid (:181) → NOT convert. fe-user mirrors office/master/hrm (SHA256-identical per comments) but NO system/forms/reports mirror. Custom-layout heavy: InternalDirectory (card-grid :124 not table), MeetingCalendar (693L FullCalendar), EmployeesList (2-panel), HrmConfigs (declarative KIND_CONFIG :45). Effort: Master 3×Drawer=M, Catalogs/HrmConfigs/MeetingRooms bậc-thang=M, Users Drawer=M, rest S (auto-inherit polish). Surprise: hover-hidden NAMGROUP-win essentially pre-solved (team already avoids opacity-0 pattern, DataTable comment enforces) → quick-win section nearly empty. Tag [fe-redesign-p2, recon, drawer-3, basc-thang-ref-exists, s56].

  • 2026-06-09 (S55 master-data Excel-import recon — 3 master + seed mechanism, on-disk): "Hạng mục"/WorkItem master TỒN TẠIDomain/Master/Catalogs/WorkItem.cs:6-14 (Code(50)UNIQUE-filtered/Name(200)/Category(100,idx)/DefaultUnit(50)/Description/IsActive), config CatalogsConfiguration.cs:60-74, full CRUD CatalogsFeatures.cs:260-324 → group(VẬT TƯ/THẦU PHỤ/MEP)→Category, "1 Mat"→Code, item→Name. KHÔNG cần table/migration mới. PE detail = pure free-text (PurchaseEvaluationDetail.cs GroupCode/GroupName/ItemCode/NoiDung strings, NO FK→WorkItem) → load WorkItems non-breaking. Project (Project.cs:5-14, cfg :14-21): Code(50,UNIQUE [IsDeleted]=0 Mig47)+Name(200) REQUIRED, StartDate/EndDate/BudgetTotal(18,2)/Note(1000)/ManagerUserId optional. THIẾU Year/Investor/Location/Package — chỉ Note free-text catch-all. Create cmd ProjectFeatures.cs:67 dup-check :87 AnyAsync(Code==). Supplier (Supplier.cs:5-16, cfg :14-27): Code/Name req + Type enum + TaxCode(20)/Phone/Email/Address/ContactPerson/Note. SupplierType.cs: NhaCungCap=1/NhaThauPhu=2/ToDoi=3/DonViDichVu=4/ChuDauTu=5. THIẾU Status/TinhTrang (KHÔNG có field/enum nào) + bank-acct + legal-rep (≠ContactPerson) + quality-score; "Cả hai" PHÂN LOẠI unmappable (Type single-valued). Create CreateSupplierCommand.cs:10 dup :45. Seed = idempotent existingCodes.Contains→skip (DbInitializer.SeedDemoMasterDataAsync:2149, today 18 supplier :2155 + 8 project :2222; WorkItems 15 rows tuple-loop SeedCatalogsAsync:576-599). NO bulk import — Master chỉ single CRUD; Import/Upload hits = Forms/PE/Employees attachment only; POST one-at-a-time. Seed→prod: DbInitializer.InitializeAsync chạy MỌI startup (Program.cs:197 unless --no-db-init) → MigrateAsync THEN seed; demo gated config.GetValue<bool>("DemoSeed:Disabled") (:80) NHƯNG SeedDemoMasterData+SeedCatalogs chạy BẤT KỂ flag (ngoài if-block :108/:115) → seed method mới auto-reach prod next deploy. Rec: idempotent DbInitializer mirror (NOT API loop). Surprise: real+demo data sẽ trộn chung Suppliers/Projects/WorkItems (18/8/15 demo rows) → cân nhắc gate demo off prod. Tag [master-import, workitem-exists, seed-idempotent, s55].

  • [→ archive/2026-06.md + git] S52 P11-D/E/F 6-gap recon (IT-pool absent → S56 corrected: dept IT exists 0 user · SlaExpiryJob HostedService DI:46 pattern · OtPolicy 3-multiplier · ClosedXML exporter reuse · Attendance API cá nhân-only · FE skeleton state — full text git pre-S60) · S50 P11-C HrmConfigs add-kind 11-chỗ pattern · S50 wave h2-verify B6 gitignore ordering + POSIX-not-pwsh (curated S57bis) · S51 gotcha #57 EXT reachability 3-Master-fix/3-skip global-filter-makes-bug (curated S59).

  • 2026-06-07 (Harness 1/2/3 adap-apply recon — 3 slice, HMW wave): Governance recon AI_INFRA broadcast harness-1/2/3. H1/H2 (Harness 1): roster 8→10 — CREATE 2 sub TÁCH BIỆT tooling-auditor (H1 freshness 4-mặt skill/sub-role/plugin/docs) + harvest-curator (H2 integrity 5-trục). H2 PARTIAL sẵn: session-end.md Phase 1.5 §L.b(d) spawn-record 4-field + (f) double-check moved-not-cut + (c) 0-byte AS-8 = Coverage+Completeness+Corruption (3/5); THIẾU Fidelity-escalate + Placement. RE-REPORT @session-start = 0 (chỉ generic Phase 2.7). 2 sub mirror inv-codebase read-set + store_memory strip + NO Write/Edit; color brown+teal (8 màu cũ hết). H2 wave (Harness 2): SE hmw.js = OLD pre-wave (no subMdPath/writeGuard/wave-block); AI_INFRA hmw.js = canonical template. git check-ignore -v = ground-truth B6: .claude/workflows/wave-test/wave.md HIỆN match .gitignore:83 !.claude/** = TRACKED → wave pattern PHẢI đặt AFTER !.claude/** (last-match-wins, mẫu hmw-mode.on :87). Read-only sub (4)=inv-cb/inv-api/reviewer/cicd; Write sub (4)=impl×2/test/fe-designer. B5 depends H2 harvest-curator. H3 email (Harness 3): broadcasts/ absent; id authoritative = se (NOT solution_erp), 6 others short {ai_infra,vipix,dyd,namgroup,ashico,bvaau} từ AI_INFRA/broadcasts/sister-commands/send-email.md:13-22 (folder name = 2nd source-truth); adap-apply.md:14 base-path STALE flat → outbox/all/*.md (latent bug). broadcasts/ ở root → commit OK (no gitignore rule). Containment post-P2: git-diff bắt 1 file-write (inv-api self-MEMORY), chunk-count 2414=2414 (0 RAG-write) = defense-in-depth proven. Tag [harness-recon, governance, hmw-wave, 2026-06-07].

  • 2026-06-01 (P11-C Vehicle+Driver catalog pre-flight): Mig 44 next (latest=Mig 43 FilterHolidayUniqueIndexByIsDeleted S45). NO Vehicle/Driver master exists — chỉ Office/VehicleBooking.cs (request, Mig 39) dùng FREE-TEXT (VehicleLicense/VehicleName/DriverName? strings, :13-19 comment "defer catalog Phase 11"). RECOMMEND home = extend HrmConfigs (NOT new module): Application/Hrm/HrmConfigFeatures.cs mega 4-region + HrmConfigsController ([Authorize] read / [Authorize(Roles="Admin")] write) — add Region 5 Vehicle + 6 Driver (kind vehicles/drivers), pattern proven 12-bis. ⚠️ HRM entities KHÔNG global HasQueryFilter → manual .Where(!IsDeleted) + UNIQUE soft-delete cần .HasFilter("[IsDeleted]=0") (Holiday Mig 43 lesson, LeaveType/Shift UNIQUE Code chưa có filter → nếu Vehicle BienSo UNIQUE phải add filter). FE cheap: HrmConfigsPage.tsx declarative KIND_CONFIG Record — add 2 entry vào KIND_CONFIG + KINDS[] + renderCells branch + smart-defaults; NO new page. Menu+perm: add 6 const MenuKeys.cs (+Hrm_Config_Vehicles/Drivers), thêm vào All[] (:140) → Admin auto-grant qua SeedAdminPermissionsAsync loop (:1909 idempotent), +2 MenuItem DbInitializer :1757, +2 menuKeys.ts mirror. Hrm_Config KHÔNG inherit-root (4 root=Contracts/Workflows/Pe/PeWf only) → leaf cần row riêng (loop lo). Fields (NamGroup XeCong DROPPED Mig 2026-05-15, ref response shape only): Vehicle{Code/BienSo UNIQUE, Hang, MauXe, SoCho int, TrangThai, GhiChu}; Driver{Code/Hoten, SDT, GPLX, Hang bằng, TrangThai}. FK link defer: P11-C = catalog only, optional FK VehicleBooking.VehicleId?/DriverId? giữ free-text back-compat (Mig sau). Tag [pre-flight, p11-c, vehicle-driver-catalog].

  • 2026-06-01 (MONTHLY DRIFT AUDIT): Ground truth code: migrations=42 (last AddLeaveBalances, path .../Persistence/Migrations/*.cs) · gotchas highest=#56 (file header NO self-count → drift chỉ ở file reference gotchas.md) · tests=154 (58 Domain+96 Infra, em main verified) · tables≈91 (45 config class nhưng Catalogs=4+ContractDetails=7+7 Identity untracked → khớp STATUS 91, KHÔNG cheap-exact). Biggest drift: ef-core-migration SKILL (frontmatter:3 "31 migration"→42, :19 history "31"→42 + thiếu rows Mig 27-42, :50 "59 bảng"→91, :80 "111 test"→154, :258/:267 "59 bảng"). dependency-audit SKILL:153 "49 bẫy"→56. CLAUDE.md:53 "40 mig→84 bảng"→42/91, :66 "130 test"→154, :133 "52 bẫy"→56. docs/CLAUDE.md:65 "52"→56. schema-diagram GAP: migration TABLE dừng Mig 16 (line 487); detail § cuối =§15=Mig 26 (§16=Related KHÔNG phải mig) → thiếu § cho Mig 27-42 (16 mig). database-guide:4 "47 bảng/13 mig"→91/42. STATUS:97 backlog "Curate 4 agent MEMORY 35.7/35.3/30.9/28.4" STALE (đã curate S40 78c9de3, all ≤16KB) → REMOVE. NO-CHANGE: contract-workflow (historical counts OK), form-engine, iis-deploy (no count), HANDOFF (S43 current), PROJECT-MAP (no count). Tag [drift-audit, monthly, 2026-06].

  • 2026-05-30 (P11-A WorkflowApps wire pre-flight): 4 module Leave/OT/Travel/Vehicle. Schema pin ĐÃ CÓ SẴN (Mig 39): Office/{Module}.cs đều có ApprovalWorkflowId?+CurrentApprovalLevelOrder?+WorkflowAppStatus (5-state khớp ProposalStatus). SKELETON tại Application/Office/WorkflowAppsFeatures.cs:11-15 (chỉ Create+List, KHÔNG Approve/Reject/Return/GetById). Proposal = mirror HOÀN HẢO (cùng Office ns, Mig 38): ProposalFeatures.cs:403-486 ApproveHandler = flatten Steps.OrderBy(Order).SelectMany(Levels.OrderBy(Order)) global level index → allLevels[CurrentApprovalLevelOrder-1] → actor match Level.ApproverUserId==uid||Admin → UPSERT LevelOpinion → advance++/DaDuyet. ⚠️ ApprovalWorkflow.cs:72 nói Level KHÔNG OR-of-N (1 ApproverUserId/Level) — KHÁC memory cũ "OR-of-N", verify lại. Gap: 4 bảng {Module}LevelOpinion mới (Mig 41+), 3 route/controller, 4 seed WF, FE Detail+Opinion (chỉ có WorkflowAppsListPage chung). ⚠️ enum ApplicableType THIẾU Travel (có Leave=5/OT=6/Vehicle=7/ItTicket=8); ExtendApplicableTypeForWorkflowApps mig empty Up/Down (enum-only). Tag [pre-flight, p11-a, workflowapps].

  • 2026-05-29 (S40 STATE GROUNDING): 7 metric verify. Migrations=40 (path .../Persistence/Migrations/*.cs, last AddAttendances). Gotchas=55 (### N.). git clean. DbSet=77 nhưng SQL tables=84 (em main verify .ToTable() ModelSnapshot — 77+7 Identity, "84 docs ĐÚNG", DbSet count sai 7). Endpoints=211 (docs ~223). FE pages fe-admin 36+fe-user 29=65 (docs 53 under-count). Menu keys=53 const (docs 85 over-count). 3 số tin cậy nhất = mig/gotcha/git. Lesson: tables phải count ToTable KHÔNG DbSet. Tag [state-grounding, docs-drift, s40].

  • 2026-05-29 (S39 BVAAU 7-agent extract): Đọc 8 file BVAAU .claude/agents/ ~22K. Split 4→7 trục research(2)/implement(2)/quality(3). Boundary: repo interface=domain, EF config=infra, test=test-specialist. Tool: cả 7 agent 5 RAG MCP (+search_code BM25 +store_memory +list_projects). BVAAU Phase 0 codebase RỖNG → aspirational template chưa battle-test; SOLUTION_ERP giữ 6 skill + backend/frontend split (thay domain/infra cho 2-FE fit). VIPIX guide claim KHÔNG verify được (file miss). Tag [cross-project, bvaau, port].

  • Archived S29-S37 → archive/2026-05-q4.md + git d2f52ba (S40 curate): S36 G-O2 Phòng họp clean-room + FullCalendar v6 MIT eval · S36 startup MEMORY-size audit · S35 G-H2 HRM clean-room verdict · S33 G-H1 NamGroup TblNhanVien 10-bảng (105 cols main) · S33 startup RAG verify · S32 Plan G 11-module backlog · S29 Plan CA+B pre-flight (3 patterns: 9-menu terrain, V1+V2 coexist, reference-template-paths cite line-range ROI). KEY absorbed: 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. Stale >3mo → remove.

  • Last curate: 2026-05-29 S40 em main proxy (35.7→~20KB): archived 7 FIFO S29-S36 → q4 + git d2f52ba, refreshed stale essentials S25→S40 numbers, trimmed memory-list. Prev: S34 q3 · S32 q2 · S22 q1.