- STATUS/HANDOFF: thêm S57bis + S58 (2-session gap S57bis đóng vội), counts re-ground (Mig 49 · test 240 · gotcha 60 · menu 57 · bundle CP4CB1ym/BmZ3VHnm curl-verified · RAG 2420). Ops S56 "gán user IT" RESOLVED (nv.cao/nv.truong sống lại nhờ password fix). - gotchas: #59 (PS5.1 git commit -F, residual S57bis) + #60 NEW (Identity seed CreateAsync silent-fail vs prod RequiredLength=12 — population Dev ≠ prod, dump data thật trước lock/seed-by-email) + checklist 31/32. - agent-memory: cicd Run #381 (residual) + #382 · 4 spawn-record on-behalf (database-agent/impl-backend/impl-frontend/reviewer — H2 Coverage 4-MISS đóng) · investigator-codebase recon · 2 monitor RE-REPORT entries. - skills: ef-core +row Mig 49 (49 mig ×5 chỗ) · README + dep-audit count sync. - CLAUDE.md root: Mig 49 + test 240 + gotcha 60 + schema-ref 32-49. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
28 KiB
Implementer-Backend 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..q4.md. Renamed S39: implementer → implementer-backend (.NET half). FE patterns →implementer-frontendMEMORY. Test patterns →test-specialistMEMORY.
🎯 Role baseline
WRITE specialist .NET backend SOLUTION_ERP (Domain+Application+Infrastructure+Api). Case 1+2+3+5 only. Tools: Read, Edit, Write, Bash, Skill, Grep, Glob + 5 RAG MCP. Skills: ef-core-migration + permission-matrix + contract-workflow + form-engine. Output: commits + verify report.
🚫 Split boundary (S39) + auto-refuse
- ✅ MINE:
src/Backend/SolutionErp.{Domain,Application,Infrastructure,Api}/** - ❌ NOT:
fe-*/**→implementer-frontend·tests/**→test-specialist· schema/UX/architecture decision → em main - REFUSE if ANY: 1 schema design (FK/nullable/discriminator) · 2 UX flow · 3 cross-stack >2 layer · 4 bug fix reasoning chain · 5 integration multi-component · 6 <30min trivial · 7 first-time no precedent · 8 spec ambiguity >20%
📋 BE Patterns proven (apply confidently)
Pattern 1: Per-chunk discipline A-E
A Domain+Mig (3-file) · B Application CQRS (Command/Query/Validator) · C Service (workflow logic) · D Api Controller · E commit. Build+test pass mỗi chunk. Commit [CLAUDE] <scope>: Chunk X — ... + Co-Authored-By Claude Opus 4.8 (1M context).
Pattern 2: EF migration 3-file rule (gotcha #17 — BẮT BUỘC commit đủ)
{TS}_{Name}.cs + .Designer.cs + ApplicationDbContextModelSnapshot.cs. Path src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/.
dotnet ef migrations add <Name> --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api
# Apply Dev (runtime): --connection "Server=(localdb)\MSSQLLocalDB;Database=SolutionErp_Dev;Trusted_Connection=True;TrustServerCertificate=true"
# Apply Design (ef default): không cần --connection
Apply BOTH DB per feedback_designtime_runtime_db.
Pattern 3: Audit reuse trước khi clone (feedback_audit_reuse_before_clone)
"Clone X→Y": grep discriminator (ApplicableType/Type/Kind) → check Service/Handler hardcode → check FE route dynamic → check menu key (BE const + FE menuKeys.ts thường thiếu) → default reuse 80%, chỉ thêm menu key + sample seed.
Pattern 4: Service hook vs CRUD endpoint cho derived state (feedback_service_hook_vs_endpoint)
State X derived của action Y → UPSERT trong handler Y, KHÔNG endpoint /X riêng. VD ApproveV2Async UPSERT LevelOpinion qua match ApproverUserId==actorUserId (fallback first khi Admin override). 0 endpoint mới.
Pattern 7: Per-NV admin opt-in flag (Mig 29/30/31)
ApprovalWorkflowLevel +1 bool DEFAULT 0 (opt-in). EF HasDefaultValue(false). DTO extend. FE Designer checkbox inline. Scope role-context → table mapping (Approver→Level table carry ApproverUserId FK, Drafter→User table direct, feedback_per_nv_permission_scope). Reusable F5/F6.
Pattern 8: Tách endpoint riêng cho narrow scope
1 action 2 scope theo role → tách endpoint (guard tự nhiên + audit). VD Drafter UpdatePeDraft (Section 1 rộng, phase Nháp/Trả lại) vs Approver AdjustBudget (Budget rows hẹp, phase Đang duyệt + per-NV flag). KHÔNG default expand Drafter scope cho Approver.
Pattern 9: Defense-in-depth FE+BE guard pair
UI disabled={!canX} + BE helper EnsureCanXAsync(id, userId) throw 403 (NOT inline handler) — tránh forge qua DevTools. Bất kỳ action sensitive (approve/reject/adjust).
Pattern 12-bis: Cross-module entity cookie-cutter mirror (S29 Plan B, proven 3×)
"Mirror entity X từ module A→B": 6 file MAX — (1) new entity Domain/<Mod>/<Entity>.cs rename FK+nav · (2) parent +nav collection · (3) IApplicationDbContext +DbSet · (4) ApplicationDbContext +Set<X>() · (5) new <Entity>Configuration.cs (separate file mirror PE, NOT inline) · (6) dotnet ef migrations add 3-file. AuditableEntity inherit. FK: parent Cascade + 3rd-party Restrict + User skip-nav (denorm <Type>ByFullName). Apply 2 DB. ⚠️ Catalog-mega variant (S35 HrmConfig): HRM entities KHÔNG có global HasQueryFilter(!IsDeleted) (vs Master) → list query MUST .Where(!IsDeleted) thủ công (verify Grep HasQueryFilter Configurations FIRST). Validator MaxLength MATCH EF config (verify source-of-truth, KHÔNG trust spec blind).
Pattern 12-ter: N≤7 satellite CRUD scaffold same parent (S34, feedback_within_module_n_satellite_scaffold)
"N satellite cùng parent" → 1 mega file <Parent>SatelliteFeatures.cs N region cookie-cutter (Create verify parent AnyAsync → Update FirstOrDefault !IsDeleted → Delete soft IsDeleted+DeletedAt+DeletedBy ICurrentUser) + 1 Controller extend (3 verb × N). Endpoint verify parentId==cmd.ParentId BadRequest mismatch. Per-action policy override class-level Read.
Patterns moved (split S39)
- FE patterns (5 mirror 2-app · 6 VND helpers · 13 read-only Designer · 14 Tailwind JIT palette · 15 rowSpan builder · 16-bis 4-place mirror) →
implementer-frontendMEMORY (seeded). - Test patterns (10 reflection authz · 11 test infra helper · 12 InternalsVisibleTo) →
test-specialistMEMORY (seeded).
⚠️ Anti-patterns (DO NOT)
- ❌ Skip MEMORY · 2. ❌
--no-verify· 3. ❌git add -A/git add .(specific files) · 4. ❌ Touch outside spec scope · 5. ❌ Push remote autonomous (em main pushes) · 6. ❌ ModifySolutionErp.slnxautonomous · 7. ❌ Lower bar match em main (Smart Friend) · 8. ❌ Proceed khi ambiguity >20% → REFUSE
🧠 SOLUTION_ERP BE conventions (S40)
- BE .NET 10: PascalCase entities + DTO records + command names. CQRS+MediatR+FluentValidation+AutoMapper. Repository qua
IApplicationDbContext.GlobalExceptionMiddleware→ ProblemDetails (NO try-catch controllers). - State S53: 47 mig (last
FilterMasterCatalogUniqueIndexesByIsDeletedMig 47, index-only) · 93 SQL tables · ~224 endpoints · 203 test (58 Domain + 145 Infra, test-specialist owns). Phase 9 UAT skip per chunk (feedback_uat_skip_verify). - Build:
dotnet build SolutionErp.slnxclean 0 err. Commit[CLAUDE] <scope>: <msg>+ Co-Authored-By Claude Opus 4.8 (1M context). - Pin (KHÔNG
*/latest): MediatR12.4.1(14 fail DI) · Swashbuckle6.9.0· Node CI20.x· LibreOffice25.8.6· @microsoft/signalr8.0.7.
📅 Recent activity (FIFO — older → archive/git)
-
2026-06-11 (S57bis PE WorkItemId BE slice — PARTIAL, on-behalf em main ghi hộ, H2-proposed): Return-truncated #53 TRƯỚC Mig 49 + projection-3 → em main solo hoàn tất (fix CS7036 + CS8019, Mig 49
AddWorkItemToPurchaseEvaluation3-file, projection ListItemDto ×3 LEFT-join WorkItems, UpdateDraft null-safeif (request.WorkItemId is not null)chống null-hóa bug-class S42). LEARNED: FK-guard loose-GuidAnyAsync(w.Id==x && w.IsActive)→Conflict (mirror S43); validatorNotEmptycreate-only, DB nullable backward-compat 4 phiếu cũ;NotEmpty()trênGuid?KHÔNG chặnGuid.Empty→ handler guard bắt (defense-in-depth). SURPRISE: truncate 2 session liên tiếp (S55, S57bis) ở slice lớn cross-layer → cắt stage nhỏ hơn (entity+config / mig / projection tách spawn). Tag[s57bis, truncated-53, on-behalf]. -
2026-06-10 (S57-resume spawn-test H4.8 — Harness-4 two-tier): Mình bị DEMOTE pin
model: claude-opus-4-8(deterministic-scaffold class, double-gate reviewer+test+cicd sau lưng). Spawn-test echo model NGAY sau edit → self-reportclaude-fable-5[1m]= SE env (CCD harness) KHÔNG fresh-read frontmatter → pin ăn SAU restart CLI. Post-restart mình chạy Opus 4.8 (effort Max giữ env-wide); task hệ-trọng giao mình qua hmw có thể overridetier:'fable'. Tag [h4-demote, spawn-test, pending-restart]. -
S56 GOLIVE-HARDEN 3 BE fix (NO mig, 3 file edit, em-main spec deterministic 100% → ACCEPT Case 1): #3 LeaveBalance lost-update
LeaveOtApprovalFeatures.csterminal DaDuyet branch → atomic-executeupdate-tx (spec chosen, KHÔNG RowVersion/Mig). Replaced in-membal.UsedDays += NumDaysvới: set p.Status/Updated* →(DbContext)dbcast +BeginTransactionAsync(ct)(plain, NO IsolationLevel — READ COMMITTED đủ vì increment atomic) → STEP1 ensure-row (FirstOrDefault, auto-create UsedDays=0 via tracker) + SaveChanges (opinion+status+insert trong tx) → STEP2db.LeaveBalances.Where(...).ExecuteUpdateAsync(s=>s.SetProperty(b=>b.UsedDays, b=>b.UsedDays+p.NumDays), ct)server-side row-lock race-free →tx.CommitAsync+return;(skip trailing shared SaveChanges). STALE-TRACKED caveat (load-bearing): ExecuteUpdate bypass tracker → trackedbalgiữ pre-increment value; SAFE vì không đọc lại + handler return ngay; KHÔNG thêmbal.UsedDays +=(double-count).using System.Data+ EF Core đã import. #5 AssignItTicketHandler existence-oracleWorkflowAppsFeatures.cs:493→ moved Admin-OR-dept-IT Forbidden guard (itDeptId+isAdmin+myDeptId resolve) TRƯỚC ticket NotFound lookup → fail-closed (non-IT nhận Forbidden cho MỌI ticketId). assignee-must-be-IT Conflict + reassign giữ nguyên. #6 DocxRenderer.cs:30,40 CS8602 → hoistmainPart = doc.MainDocumentPart ?? throw InvalidOperationException+document = mainPart.Document ?? throw(Document cũng nullable — KEY: 1st hoist chỉ fix part, vẫn còn 1 warn ở.Document.Body); deref qua local non-null. Build SolutionErp.slnx 0 err 0 warn (DocxRenderer warn CLEARED — thực tế 1 warn không phải 2 như MEMORY ghi). Test 58 Domain PASS + 154/158 Infra: 4 FAILLeaveBalanceTests(Approve_LastLevel_DeductsLeave.../AccumulatesExisting.../OverEntitled.../MultiLevel_NoDeductAtIntermediate) = EXPECTED #3 stale-tracked (re-query trả tracked instance pre-increment, DB row đúng) → tests_to_update cho test-specialist (add AsNoTracking/ChangeTracker.Clear). ItTicket authz tests #5 PASS (Case5 đã expect Forbidden, NotFound case dùng Admin caller). KHÔNG touch tests/FE/commit. #4 (Travel/Vehicle smoke test) = test-specialist next stage. Tag[s56, golive-harden, executeupdate-atomic, fail-closed-authz, cs8602, no-mig]. ↳ [em main post-review S56] Tx bumped →IsolationLevel.Serializable(shipped codeLeaveOtApprovalFeatures.cs:369) per database-agent review — convention-align (codegen/Proposal/TravelVehicle) + serialize auto-create-row race. '(plain, NO IsolationLevel — READ COMMITTED đủ)' ở entry = pre-review reasoning, superseded. Test 228 green. -
S55 master-data import (Mig 48
AddProjectMasterFields4 AddColumn no-table +SeedRealMasterDataAsync62 Project+71 WorkItem+3 Supplier) [proxy by em main — agent return truncated gotcha #53 before MEMORY step]: Project entity +4 prop (Year int?,Investor/Location/Package string?, maxlen 250/500/300 ProjectConfiguration).ProjectFeatures.csDTO+CreateCmd+UpdateCmd+validators+handlers+List/Get projections +4 (all nullable, appended end).SeedRealMasterDataAsync= 3 tuple-loop per-code idempotent (mirrorSeedDemoMasterDataAsync:2185existingCodes.Contains→skip) wired UNGATED line 118 AFTERSeedCatalogsAsync→ reaches prod (DemoSeed:Disabled=true KHÔNG gate, by-design như SeedDemoMasterData/Catalogs). Project Name=Code khi Excel blank. WorkItem 4 Category (Vật tư16/Thầu phụ30/MEP9/Thiết bị16, gen Code VT/TP/MEP/TB-NN; divider "THIẾT BỊ" dropped). Supplier NTP→NhaThauPhu/NCC→NhaCungCap, extras→Note. FLOCK01 collision demo↔real → per-code skip (demo thắng, real code+year only, OK). Compile-fixMasterCatalogFilteredUniqueTests.cs+4 null args CreateProjectCommand (necessary build-green). Runtime Dev proof (em main): app-start seeded 62proj/71wi/3sup landed, CAL01.Investor col populates, 0 overflow/dup. Build 0/0, test 216. Data specscripts/master-import-data.generated.md. Tag[s55, master-import, mig48, seed-real-ungated, project-4field]. -
S54 ItTicket reassign cross-stack — IT-staff self-service (NO migration, 2 BE file edit): NEW
GetAssignableItStaffQuery+AssignableStaffResult(CanReassign,Staff)+AssignableStaffDto(Id,FullName)capability endpoint (REGION 5 WorkflowAppsFeatures.cs) + MODIFIEDAssignItTicketHandler: authz Admin-OR-dept-IT →ForbiddenException; assignee-must-be-IT →ConflictException. Controller/assignhạ[Authorize(Roles="Admin")]→[Authorize](handler enforce fine-grained data-driven) + NEWGET /assignable-staff. Predicate IT = reuse round-robin S52Departments.Where(Code=="IT" && !IsDeleted).ICurrentUserKHÔNG có DepartmentId → querydb.Users.Where(Id==cu.UserId).Select(DepartmentId). 2 pattern split (em main reconciled từ stray src/Backend/.claude — cwd-relative Write mishap): pattern-controller-lower-authorize-handler-finegrained + pattern-scoped-capability-endpoint-anti-silent-403. Build 0/0, test 203→216 (test-specialist +13 authz), reviewer PASS (role-string "Admin" chain-verified real: AppRoles→SeedRoles→JWT ClaimTypes.Role→cu.Roles). Tag[s54, it-ticket-reassign, capability-endpoint, authz-handler, no-mig]. -
S54 Task D BE — promote AttendanceReport to sidebar menu leaf (NO migration, 2 file edit): Case 1 mechanical, menu = DbInitializer idempotent seed (not schema). 3 insert: (1) MenuKeys.cs const
OffAttendanceReport = "Off_AttendanceReport"after OffChamCong:124 · (2) MenuKeys.cs All[] Off-group line +OffAttendanceReportafter OffChamCong:158-159 · (3) DbInitializer.cs menu tuple(OffAttendanceReport, "Báo cáo chấm công", Off, 8, "FileBarChart")after OffChamCong:1787 (Order 8, parent Off, mirror Vehicle/Driver S51). adminPermAutoViaAll=TRUE verified 2-point:SeedAdminPermissionsAsyncDbInitializer:1916 iteratesMenuKeys.All→ full-CRUD Permission row per missing key (idempotentexistingMenuKeys.Contains);Program.cs:78iterates All × Actions → policy registration. +All[] = both auto, NO manual grant. Idempotent-add verified: menu upsert loop DbInitializer:1845-1862existingItems.TryGetValue(key)miss →MenuItems.Add(existing prod gets leaf on restart, existing rows only Order-reconciled — same as S51). Build 0 err (2 pre-existing DocxRenderer warn). KHÔNG touch FE (menuKeys.ts/Layout = implementer-frontend) / tests / commit. Tag[s54, task-d, menu-leaf, no-mig, admin-perm-via-all]. -
S53 gotcha #57 EXT BE — filter 3 Master Code unique indexes + Mig 46 local catch-up (Mig 47
FilterMasterCatalogUniqueIndexesByIsDeleted, index-only no-table): Test-before RED→GREEN driven (test-specialistMasterCatalogFilteredUniqueTests, 3 FAIL on unfiltered → must turn GREEN). gotcha #57 4th+5th+6th cumulative (S45 Holiday Mig 43, S51 HRM×3 Mig 45 → now Department/Project/Supplier). Edit 3 config Code unique:b.HasIndex(x=>x.Code).IsUnique()→+.HasFilter("[IsDeleted] = 0"). KEY: copied EXACT filter string byte-for-byte from HolidayConfiguration:18 + LeaveTypeConfiguration:19 (spaces around=, NOT guessed) → snapshot+SQL Server consistent with 13 existing filtered indexes. SupplierConfiguration: ONLY Code index filtered,HasIndex(x=>x.Type):25 LEFT untouched (verified snapshot 3590 Type no-filter). Mig diff CLEAN: Up=3×DropIndex+3×CreateIndex filtered, Down=3×reverse unfiltered (no drift, no extra table/col). Master entities HAVE globalHasQueryFilter(!IsDeleted)(unlike HRM) — app-checkdb.X.AnyAsync(Code==req.Code)filters soft-deleted → passed; then bare DB index counted it → UNIQUE violation 500. Filter aligns DB index with app-check. Mig 46 local catch-up: S52 left local LocalDB stuck at Mig 45 (prod had 46 via CI, local gap).database updateto BOTH DBs applied Mig 46 (AddSlaFieldsToItTicket) THEN Mig 47 — residual closed. Dev override--connection SolutionErp_Dev; Design factory-defaultSolutionErp_Design(both(localdb)\MSSQLLocalDB). Build 0 err (2 pre-existing DocxRenderer warn). Full suite 203 GREEN (58 Domain + 145 Infra, +3 new). KHÔNG touch FE/test/commit (em main commits). Tag[s53, gotcha-57-ext, mig47, mig46-catchup, filter-byte-for-byte]. -
S52 P11-D Wave2 BE — ItTicket round-robin + SLA (Mig 46
AddSlaFieldsToItTicket, 3 AddColumn no-table) [proxy by em main: agent killed session-limit trước MEMORY step]: Entity +SlaDueAt/SlaWarnedSent/SlaBreached.CreateItTicketHandler:SlaWindowstatic map (Urgent4/High8/Medium24/Low72h) shared vớiItTicketSlaJob(single-source) →e.SlaDueAt=CreatedAt+window. Round-robin least-loaded:db.Users.Where(DepartmentId==itDeptId && IsActive).OrderBy(db.ItTickets.Count(assigned && !Closed && !Resolved)).ThenBy(Id).First()(itDept =Departments.Code=="IT"); no IT-staff → unassigned. NEWItTicketSlaJob:BackgroundServicemirror SlaExpiryJob (30s warmup/15min loop/scope) NHƯNG KHÔNG auto-transition — chỉ breach (SlaDueAt<now & !breached & open → SlaBreached=true + notify assignee) + warning (≤20% window → notify + SlaWarnedSent), idempotent qua guard. DIAddHostedService<ItTicketSlaJob>sau SlaExpiryJob.AssignItTicketCommand+ PUT/{id}/assign[Authorize(Roles=Admin)]. DTO +SlaDueAt/SlaBreached + projection. DbInitializerSeedItDepartmentStaffAsyncKEY ordering: PHẢI chạy SAUSeedDemoUsersAsync(method đó reconcile 2 user dept về PRO/CCM mỗi boot → override về IT sau, end-state deterministic) + dept "IT" thứ 10 + gán nv.cao/nv.truong (sample user, idempotent). Build 0-err. Tag[s52, p11-d, mig46, round-robin, sla-job, seed-ordering]. -
S52 P11-E+F Wave1 BE migration-FREE (4 file new + 3 edit, NO mig): Case 1/2 deterministic ~98% em main. P11-F (2 LOC):
CreateItTicketHandlersete.MaTicket = WorkflowAppCodeGen.GenerateMaDonTuAsync(db,"IT",clock.Now.Year,clock,ct)TRƯỚCdb.ItTickets.Add— gen lúc Create (kanban no-workflow, khác Leave/OT gen lúc Submit). Helper =internal staticcùng ns Office, gọi trực tiếp no-using. FormatIT/2026/001. P11-E report chấm công: NEWAttendanceReportFeatures.cs(DTO 3 record EXACT + GetAttendanceReportHandler) + NEWIAttendanceReportExcelExporter(App/Reports/Services) + NEW InfraAttendanceReportExcelExporter(ClosedXML mirror ContractExcelExporter, syncExport(dto)no-DB) + DI scoped + Controller +2 endpoint[Authorize(Roles=Admin)]. KEY gotcha day-type: DayOfWeek+holidaySet KHÔNG EF-translate →.ToListAsync()rồi group/classify IN-MEMORY C#. KEY gotcha type:Holiday.Date=DateOnly KHÔNG DateTime (spec viết HashSet nhầm) →HashSet<DateOnly>+ soDateOnly.FromDateTime(a.AttendanceDate.Date). OtPolicy active fallback 1.5/2.0/3.0. Day-type prio: holiday→weekend(Sat/Sun)→weekday. OtWeighted=Σ(cấp×hệ số). FullName denorm ưu tiênAttendance.UserFullNamerồi User.FullName. DeptName LEFT JOIN. RenderResult=(Content,FileName,ContentType) ns Forms.Services. Controller inject exporter ctor. Build 0 err (2 pre-existing DocxRenderer warn). KHÔNG touch FE/test/mig/ItTicket-khác. Routes: GET/api/attendances/report?year&month&departmentId+/report/excel. Tag[s??, p11-e-f, wave1, no-mig, day-type-in-memory, dateonly-holiday]. -
S51 P11-C HMW W1 — Vehicle+Driver catalogs HrmConfigs (Mig 44
AddVehicleAndDriverCatalogs, 9 add-point ~12 file/edit): Pattern 12-bis catalog-mega 4th cumulative. 2 entity (Vehicle/Driver:AuditableEntity Domain/Hrm) + 2 EF config (mirrorHolidayConfigurationfiltered, NOT buggy bare LeaveType) + 2 DbSet (IAppDbContext+ApplicationDbContext) + Mig 3-file + HrmConfigFeatures Region5/6 (DTO+List/Create/Update/Delete CQRS mirror Region1 EXACT) + Controller +2 route group (8 endpoint, GET public + POST/PUT/DELETE[Authorize(Roles=Admin)]) + MenuKeys +2 const +All array + DbInitializer (menu 2 leaf + SeedHrmConfigsAsync guard&seed 2 veh+2 drv). gotcha #57 KEY: Code UNIQUE.HasFilter("[IsDeleted]=0")— Mig diff verified CLEAN 2 CreateTable + 2 filtered IX no drift. Validator MaxLength = em main schema (Code50/Name200/Plate20/Phone20/LicNum50/LicClass20/Desc500), SeatCount GreaterThanOrEqualTo(0). Admin perm AUTO-grant:SeedAdminPermissionsAsync+Program.cs:78both iterateMenuKeys.All→ +All array = 8 policy + Admin row auto (no manual grant code). HRM no HasQueryFilter →.Where(!IsDeleted)manual. Applied BOTH DB. Build 0 err (2 pre-existing DocxRenderer warn). RAG/Qdrant DOWN → all Read/Grep on-disk. Spec deterministic ~98% em main → ACCEPT Case 1. KHÔNG touch FE/test/commit. Tag[s51, p11-c, mig44, vehicle-driver, catalog-mega]. -
S43 P11-B Wave 1 — LeaveBalance business logic (Mig 42
AddLeaveBalances, 7 file: 1 entity + 1 config + 2 DbSet edit + Mig 3-file + 1 hook edit + 1 Features + 1 Controller): Case 1/3 deterministic ~98% em main spec. Pattern 12-ter-adjacent single-entity: entityLeaveBalance:AuditableEntity(UserId/LeaveTypeId/Year + EntitledDays/UsedDays/AdjustmentDays decimal(5,2), nav LeaveType). Config FK LeaveType WithMany() Restrict (catalog no cascade) + UNIQUE composite (UserId,LeaveTypeId,Year) + IX UserId. Mig diff CLEAN: 1 CreateTable + 3 IX, no drift. Applied BOTH DB (DevSolutionErp_Dev+ Design default). Deduction hook: insert inApproveLeaveRequestHandlerterminal else (DaDuyet branch) ONLY — UPSERT LeaveBalance,bal.UsedDays += p.NumDays, exactly-once guaranteed by early guardStatus != DaGuiDuyet throw. OtRequest/Travel/Vehicle UNTOUCHED (only Leave has balance). CQRSApplication.Hrm: DTO RemainingDays=Entitled+Adjustment−Used COMPUTED (not stored) + GetMy/GetUser lazy-merge (load active LeaveTypes + balances → in-memory merge, synth default when no row — KHÔNG EF LEFT JOIN translate) + AdjustLeaveBalanceCommand admin upsert (HasValue-gated). Policy resolved: HRM admin convention =[Authorize(Roles="Admin")]NOT menu policy (verified HrmConfigsController write endpoints) — used on GET-by-user + PUT /adjust; /my =[Authorize]. Controller injects IDateTime for year defaultclock.Now.Year(thin, no DateTime.Now hardcode). HRM no HasQueryFilter →.Where(!IsDeleted)manual everywhere. KHÔNG touch FE/test/commit. Build 0 err (2 pre-existing DocxRenderer warn). Tag[s43, p11-b-w1, mig42, leave-balance, single-entity]. -
S42 P11-A SEED — 4 sample ApprovalWorkflow V2 for WorkflowApps (DbInitializer.cs only, +4 method ~210 LOC + 4 call): Case 1 mechanical mirror
SeedSampleProposalWorkflowV2AsyncEXACT × 4 (Leave5/Ot6/Travel9/Vehicle7). Each: idempotentAnyAsync(ApplicableType==X)guard → resolve approverbinh.le@solutions.com.vn(SAME user as Proposal/Contract seed, null→LogWarning+return) → 1 ApprovalWorkflow (Version=1, IsActive+IsUserSelectable=true, ActivatedAt=UtcNow) + 1 Step (Order=1, Name="Cấp duyệt", DepartmentId=CCM?.Id) + 1 Level (Order=1, ApproverUserId). Codes QT-NP/OT/CT/XE-V2-001. Wired 4 calls after SeedSampleProposalWorkflowV2Async (NOT gated DemoSeed, gotcha #51 infra seed). Enum verified Grep. Build 0 err 0 warn. Bash tool = bash NOT PowerShell despite env hint (usecd && cmd | grep). Spec deterministic 100% → ACCEPT Case 1. NOT touched App/Controller/FE/test/Mig. Tag[s42, p11-a, seed, mirror-proposal-exact]. -
S42 P11-A Wave 2b APP — wire ApproveV2 CQRS Travel+Vehicle (
TravelVehicleApprovalFeatures.cs~830 LOC + 2 controller edit): Cookie-cutter mirror Wave 2a / ProposalFeatures Region 2. 1 new file nsApplication.Office: 2 module × (DetailDto + LevelOpinionDto + GetById JOIN Step/Level metadata + UpdateDraft + Submit + Approve UPSERT+advance + Reject TuChoi + Return TraLai+RejectedFromStatus) + 1 sharedinternal static TravelVehicleCodeGen.GenerateMaDonTuAsync(Serializable tx,WorkflowAppCodeSequencesPrefix-keyed, prefixDT/CT/{year}Travel &DX/XE/{year}Vehicle, format{prefix}/{seq:D3}— D3 no year segment per spec). KEY gotcha: WorkflowAppStatus enum DIFFERS from ProposalStatus int values (DaGuiDuyet=2 not 1, TraLai=3) → mirror by SEMANTIC enum member not literal. Owner =RequesterUserId(not DrafterUserId). Submit verify wf.ApplicableType==Travel9/Vehicle7 else Conflict. 2 controller +6 route each (GET{id}/PUT/submit/approve/reject/return) nested body records, CreatedAtAction. KHÔNG sửa WorkflowAppsFeatures.cs/Leave/Ot/FE/test/seed. Build 0 err. Spec deterministic ~98% em main → ACCEPT Case 1/2. Tag[s42, p11-a, wave-2b, mirror-proposal-region2]. -
S42 P11-A Wave 2a APP — wire ApproveV2 CQRS Leave+Ot (
LeaveOtApprovalFeatures.cs~770 LOC + 2 controller edit): Pattern 4 (UPSERT in Approve, 0 opinion endpoint) + cookie-cutter mirror ProposalFeatures Region 2. 1 new file nsApplication.Office: 2 module × (DetailDto + LevelOpinionDto + GetById JOIN Step/Level metadata + UpdateDraft + Submit + Approve UPSERT+advance + Reject TuChoi + Return TraLai+RejectedFromStatus) + 1 sharedinternal static WorkflowAppCodeGen.GenerateMaDonTuAsync(Serializable tx +WorkflowAppCodeSequencesPrefix-keyed, prefix DT/LR & DT/OT, format{prefix}/{year}/{seq:D3}). Approve: flatten allLevels OrderBy Step→Level, currentSlot=allLevels[order-1], actor==ApproverUserId OR Admin, comment empty→placeholder, advance OR terminal DaDuyet. Submit verify wf.ApplicableType==Leave5/Ot6 else Conflict + gen MaDonTu nếu null. 2 controller +6 route each (GET{id}/PUT/submit/approve/reject/return) mirror ProposalsController nested body records (WorkflowActionBody). KHÔNG sửa WorkflowAppsFeatures.cs (Region 1 Create/List ở đó). Build 0 err (2 warn DocxRenderer pre-existing). Spec deterministic 100% em main → ACCEPT Case 1. Travel/Vehicle (Wave 2b) + test (Wave 4) deferred. Tag[s42, p11-a, wave-2a, mirror-proposal-region2]. -
S41 P11-A Wave 1 SCHEMA — wire ApproveV2+LevelOpinions 4 WorkflowApps (Mig 41
WireWorkflowAppsApprovalV2): Pattern 12-bis cookie-cutter mirror Proposal Mig 38, 13× cumulative. 11 file: 5 entity (4{Leave,Ot,Travel,Vehicle}RequestLevelOpinion+ sharedWorkflowAppCodeSequencePrefix-PK) + 5 EF config (auto-discover ApplyConfigurationsFromAssembly, no manual register) + edit 4 parent (nav LevelOpinions +WorkflowAppStatus? RejectedFromStatus) + edit enum (TravelRequest=9) + 2 DbSet (IAppDbContext+ApplicationDbContext, 5 each). Mig diff CLEAN: 5 CreateTable + 4 AddColumn (no drift). FK Cascade parent + Restrict Level + UNIQUE composite ({Parent}Id, ApprovalWorkflowLevelId). Applied BOTH DB (Dev + Design). Build 0 err. Wave 2 (App/Controller) + Wave 4 (test) deferred per spec. Spec deterministic 100% (em main chose schema) → ACCEPT Case 1. Tag[s41, p11-a, 12-bis-13x, mig41]. -
S35 G-H2 BE CRUD 4 catalog (HrmConfigFeatures.cs 372 LOC + Controller 134 LOC, 16 endpoint): Pattern 12-bis 3rd application catalog-mega. 4 sub-resource × 4 verb. KEY: HRM no HasQueryFilter →
.Where(!IsDeleted)manual; Validator MaxLength = EF source-of-truth (Code=50 not spec 20). 130 test baseline preserve. ACCEPT clean spec 95%. Tag[s35, be-crud, hrm, 12-bis-3x]. -
S29 Plan B Chunk C Contract V2 mirror (Mig 33 ContractLevelOpinions): Pattern 12-bis 1st — 8 file +4265 LOC (Designer autogen 95%, handcraft ~232 LOC). Em main spec deterministic 100% → ACCEPT. Tag
[s29, plan-b, 12-bis]. -
Archived FE/test + older BE entries →
archive/2026-05-q4.md+ gitd2f52ba(S40 curate): S35 FE inline forms 5 satellite (→ frontend domain) · S34 test bundle +10 [Fact] 130 PASS (→ test-specialist domain) · S33 Task 5 EmployeesListPage · S32 wrap/startup. KEY absorbed in Patterns above + split pointers.
🔄 Curate trigger
-
~30KB → archive recent → L2
archive/<period>.md. Stale >3mo → remove. - Last curate: 2026-05-29 S40 em main proxy (30.9→~18KB): dedup split — removed FE patterns (5/6/13/14/15/16-bis → implementer-frontend) + test patterns (10/11/12 → test-specialist), condensed Pattern 12-bis/12-ter, refreshed stale (104/111→130 test, Opus 4.7→4.8 model). BE patterns 1-4/7-9/12-bis/12-ter foundation preserved. Prev: S34 q3 · S32 q2 · S22 q1.