[CLAUDE] Docs: S53 closeout — gotcha #57 EXT Master (Mig 47) + P11-D/E + database-agent verified-runtime + doc-drift
Session 53 closeout (HMW-mode ON, 'làm hết' full close). Code already shipped in44b9e54(Mig 47, Run #260) +dbf6648(C+D, Run #261), both prod-verified. - STATUS/HANDOFF: S53 entry (mig 46->47, test 200->203, menu +Off_AttendanceReport, bundle admin DfCfHUE9, database-agent verified-runtime). - Doc-drift E (H1 top-5): ef-core skill 43->47, agents/README roster 10->11 + plugin nac, CLAUDE.md root 45->47 mig + 186->203 test, docs/CLAUDE.md 56->57 gotcha + 91->92 ERD. - adap-report: database-agent executed-file -> verified-runtime (spawn-test caught Mig 46-unapplied-local). - session log 2026-06-08-S53 + 4 agent diaries (S53 work). - Memory: +project_database_agent_verified_local_drift (user-memory, outside repo). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -66,7 +66,7 @@ UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT i
|
||||
|
||||
## 🧠 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 S51:** 44 mig (last `AddVehicleAndDriverCatalogs` Mig 44) · 93 SQL tables · ~222 endpoints · 181 test baseline (test-specialist owns). Phase 9 UAT skip per chunk (`feedback_uat_skip_verify`).
|
||||
- **State S53:** 47 mig (last `FilterMasterCatalogUniqueIndexesByIsDeleted` Mig 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.slnx` clean 0 err. Commit `[CLAUDE] <scope>: <msg>` + Co-Authored-By Claude Opus 4.8 (1M context).
|
||||
- **Pin (KHÔNG `*`/latest):** MediatR `12.4.1` (14 fail DI) · Swashbuckle `6.9.0` · Node CI `20.x` · LibreOffice `25.8.6` · @microsoft/signalr `8.0.7`.
|
||||
|
||||
@ -74,6 +74,10 @@ UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT i
|
||||
|
||||
## 📅 Recent activity (FIFO — older → archive/git)
|
||||
|
||||
- **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 +`OffAttendanceReport` after 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:** `SeedAdminPermissionsAsync` DbInitializer:1916 iterates `MenuKeys.All` → full-CRUD Permission row per missing key (idempotent `existingMenuKeys.Contains`); `Program.cs:78` iterates All × Actions → policy registration. +All[] = both auto, NO manual grant. **Idempotent-add verified:** menu upsert loop DbInitializer:1845-1862 `existingItems.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-specialist `MasterCatalogFilteredUniqueTests`, 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 global `HasQueryFilter(!IsDeleted)` (unlike HRM) — app-check `db.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 update` to BOTH DBs applied Mig 46 (`AddSlaFieldsToItTicket`) THEN Mig 47 — residual closed. Dev override `--connection SolutionErp_Dev`; Design factory-default `SolutionErp_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`: `SlaWindow` static map (Urgent4/High8/Medium24/Low72h) **shared với `ItTicketSlaJob`** (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. NEW `ItTicketSlaJob:BackgroundService` mirror 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. DI `AddHostedService<ItTicketSlaJob>` sau SlaExpiryJob. `AssignItTicketCommand` + PUT `/{id}/assign` `[Authorize(Roles=Admin)]`. DTO +SlaDueAt/SlaBreached + projection. **DbInitializer `SeedItDepartmentStaffAsync` KEY ordering: PHẢI chạy SAU `SeedDemoUsersAsync`** (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): `CreateItTicketHandler` set `e.MaTicket = WorkflowAppCodeGen.GenerateMaDonTuAsync(db,"IT",clock.Now.Year,clock,ct)` TRƯỚC `db.ItTickets.Add` — gen lúc Create (kanban no-workflow, khác Leave/OT gen lúc Submit). Helper = `internal static` cùng ns Office, gọi trực tiếp no-using. Format `IT/2026/001`. **P11-E** report chấm công: NEW `AttendanceReportFeatures.cs` (DTO 3 record EXACT + GetAttendanceReportHandler) + NEW `IAttendanceReportExcelExporter`(App/Reports/Services) + NEW Infra `AttendanceReportExcelExporter`(ClosedXML mirror ContractExcelExporter, sync `Export(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<DateTime> nhầm) → `HashSet<DateOnly>` + so `DateOnly.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ên `Attendance.UserFullName` rồ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]`.
|
||||
|
||||
@ -42,11 +42,10 @@ Dynamic class purged. PALETTE array full literal `as const` cycle `index % lengt
|
||||
|
||||
## 📅 Recent activity (last 10 FIFO)
|
||||
|
||||
- **2026-06-08 (S52 Task C+D-FE — ItTicket admin reassign + AttendanceReport menu, fe-admin ONLY):** Both intentional mirror-break (admin-only, NO fe-user touch, NO SHA256). **Task D-FE menu wiring:** Page+App.tsx route `/attendance/report` ALREADY exist (S52 prior). Only 2 of 4-place needed: (1) menuKeys.ts +OffAttendanceReport='Off_AttendanceReport' (mirror BE string exact, after OffChamCong) · (2) Layout.tsx staticMap +Off_AttendanceReport:'/attendance/report' (4th place gotcha #50). `types/menu.ts` = MenuNode tree type, key:string NOT typed-union → NO mirror there (resolvePath(key:string)). Leaf perm-gated via BE All[]→admin auto. **Task C reassign:** ItTicketsPage.tsx top-comment updated DIVERGES fe-user. Per-card Pencil button (cạnh 👤 assignee) → Dialog (size sm) + Select user. Users source = **GET /users {params:{page:1,pageSize:200}}→{items:UserOption{id,fullName,email}}** (reuse, proven PeWorkflows/Workflows/MeetingCalendar — `enabled:target!==null` lazy fetch). useMutation api.put(`/it-tickets/${id}/assign`,{assignedToUserId})→204 (NO json read)→invalidate['it-tickets']+toast.success+close. preselect t.assignedToUserId. UI deps: Dialog(open/onClose/title/children/footer?/size) + Select(native passthrough) + Button(variant=outline) + toast(sonner) + getErrorMessage(@/lib/apiError). Build PASS (0 err, 1945 mod). git: only 3 fe-admin file, fe-user untouched.
|
||||
- **2026-06-08 (P11-E Wave 1 — AttendanceReportPage fe-admin ONLY):** Report endpoint `[Authorize(Roles=Admin)]` → KHÔNG fe-user page → NO SHA256 mirror (intentional). 4 FE file: (1) types/workflowApps.ts +AttendanceReportRowDto{userId,fullName,departmentName?,daysPresent,totalWorkHours,otRaw,otWeekday,otWeekend,otHoliday,otWeighted}+AttendanceReportDto{year,month,rows,grandTotalWorkHours,grandTotalOtWeighted} (decimal→number) · (2) pages/office/AttendanceReportPage.tsx NEW: PageHeader+filter(Year Input number / Month Select 1-12 / Phòng ban Select fetch /departments) + TanStack key ['attendance-report',year,month,deptId] GET /attendances/report + Table 9 col STT/Họ tên/Phòng ban/Ngày công/Tổng giờ/OT thường/OT cuối tuần/OT lễ/OT quy đổi + tfoot Tổng(colSpan trick) + fmtNum vi-VN · (3) App.tsx import+route /attendance/report · (4) MyAttendancePage.tsx +button "Báo cáo" admin-only (user?.roles.includes('Admin')) navigate → DIVERGED fe-user (header comment cảnh báo). **Download Excel: `api.get(url,{params,responseType:'blob'})` (api instance inject JWT interceptor + refresh-retry — CHUẨN HƠN raw fetch spec gợi ý; proven ReportsPage/FormsPage/PeDetailTabs) → blob → createObjectURL → anchor.download.click → revoke. Filename content-disposition regex, fallback BaoCao-ChamCong-{Y}-{MM}.xlsx.** Build PASS (0 err, 1945 mod). KHÔNG menu key (button-reachable MVP).
|
||||
- **2026-06-08 (S51 P11-C — vehicles+drivers kind → HrmConfigsPage):** Declarative KIND_CONFIG +2 entry (10th proof). 4-place adapt (`:kind`-driven page → NO App.tsx route): types/hrm-config.ts (union +'vehicles'+'drivers' + VehicleDto/DriverDto + Create/Update inputs) · HrmConfigsPage.tsx (KIND_CONFIG +2, KINDS array +2, renderCells +2 branch before ot-policies fallback, import Car+IdCard) · menuKeys.ts (+Hrm_Config_Vehicles/Drivers — BE string exact) · Layout.tsx staticMap +2 BOTH app. Field keys: vehicles{code,name,licensePlate,seatCount,description} drivers{code,name,phoneNumber,licenseNumber,licenseClass,description}. cp admin→user 3 file SHA256 identical (page a3afd724, type 2c0775b3, menuKeys d650c086). Layout mirror tay (structural diff OK, 2 entry verified both). Build PASS ×2 (admin 1944mod, user 1934mod, 0 TS err). lucide IdCard EXISTS (no UserRound fallback). AMBIGUITY: BE catalog vehicles/drivers chưa tồn tại on-disk (Wave 1 parallel — implementer-backend đang/sẽ làm) → FE scaffold theo contract spec cấp; runtime cần BE `/hrm-configs/vehicles`+`/drivers` endpoint + Hrm_Config_Vehicles/Drivers const trong BE MenuKeys.cs + seed.
|
||||
- **2026-05-30 (S42 P11-B Wave 2 — leave balance display):** WorkflowAppDetailPage.tsx + workflowApps.ts (2 app SHA256 identical). +3 optional `leaveBalance{Entitled,Used,Remaining}?: number|null` trong `// leave` block (BE `decimal?` → camelCase). Block "Số dư phép" sau Section 1 IIFE `kind==='leave' && d.leaveBalanceRemaining != null`: year từ StartDate, banner amber/red khi `remaining<0 || (status!==DaDuyet && remaining<numDays)`. Case 1, KHÔNG 4-place (enrich existing page). cp fe-admin→fe-user. Build PASS ×2 (page 8ef83e4b, type 1c4f167a). Lesson reuse: IIFE inline `(() => {...})()` cho conditional block có derived vars — sạch hơn tách helper.
|
||||
- **2026-05-29 (S39 agent split setup):** NEW agent từ split implementer. Seeded FE patterns (16-bis 9× + SHA256 mirror + KIND_CONFIG + Tailwind palette + PageHeader S37). Prior FE work absorbed: S33 EmployeesListPage + S34 Directory + S35 HrmConfigs declarative + S36 MeetingCalendar + S37 Proposal + S38 WorkflowApps generic.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Anti-patterns (DO NOT)
|
||||
|
||||
@ -57,6 +57,10 @@ Adversarial pre-commit reviewer SOLUTION_ERP. Read-only verify + live curl prod
|
||||
|
||||
## 📅 Recent activity (FIFO — older → archive/git)
|
||||
|
||||
- **2026-06-08 (S52-late Task C ItTicket admin reassign + Task D AttendanceReport menu-key pre-commit — PASS, 0 blocker):** Migration-FREE (menu = idempotent DbInitializer seed). 5 prod files: MenuKeys.cs (const+All[]), DbInitializer.cs (1 seed tuple), fe-admin {menuKeys.ts, Layout.tsx staticMap, ItTicketsPage.tsx}. **Independent re-verify GREEN:** `dotnet build SolutionErp.slnx` 0-warn/0-err · `npm run build` fe-admin tsc-b+vite OK (1945 modules, only pre-existing CSS @import + >500KB + ineffective-dynamic-import warns). **menuKeyMatchOk:** `"Off_AttendanceReport"` byte-identical 4 places (MenuKeys.cs:125 const == menuKeys.ts:68 == seed parent key == Layout staticMap:87); seed leaf parent=`MenuKeys.Off` order=8 icon=`FileBarChart` (verified valid lucide alias `FileChartColumn as FileBarChart` — getIcon resolves via Icons[name]); App.tsx route `/attendance/report → AttendanceReportPage` PRE-EXISTING committed S52 (6a66429, no diff, 170-LOC real page not stub) — Layout maps to REAL route. `types/menu.ts` correctly NOT mirrored (`key:string` not typed union). admin auto-perm via `SeedAdminPermissionsAsync` iterating `MenuKeys.All` (DbInit:1917, idempotent Contains:1919); new-leaf-on-existing-DB confirmed (upsert loop:1845-1862 TryGetValue-miss→Add). **reassignCorrect:** FE PUT `/it-tickets/{id}/assign` body `{assignedToUserId}` MATCHES BE record `AssignItTicketBody(Guid AssignedToUserId)` (ItTicketsController:42); endpoint `[Authorize(Roles="Admin")]` (:34) under class `[Authorize]` — admin-app FE calling = correct; user-list reuses EXISTING `/users` GET (`PagedResult<UserDto>` items{id,fullName,email}, no new BE endpoint, lazy `enabled:target!==null`); 204 NoContent handled (no body-parse); `invalidateQueries(['it-tickets'])` on success; handler sets BOTH AssignedToUserId+AssignedToFullName (WorkflowAppsFeatures:467-468) + validates assignee IsActive→NotFound, no try-catch (GlobalExceptionMiddleware). **feUserUnchanged:** `git diff -- fe-user/` EMPTY (Task C = fe-admin-only divergence, documented top-comment ItTicketsPage:3-5). **noScopeCreep:** git status prod = EXACTLY the 5 expected files, agent-memory noise ignored, no new migration, no BE beyond MenuKeys+DbInitializer, ItTicketsPage diff 98+/5- all Task-C-scoped (imports+state+mutation+Pencil-btn+Dialog), 0 mock/alert markers. **Learned:** menu-key wiring = verify byte-identity across the FULL mirror set (BE const + BE All[] + seed parent + FE menuKeys + FE staticMap) + confirm the target route actually EXISTS (grep App.tsx) — a staticMap entry pointing to a non-existent route silently drops the leaf (gotcha #50). **surprise:** lucide `FileBarChart` is a deprecated-alias (re-exported from FileChartColumn) but still valid — d.ts grep confirmed before flagging. Verdict PASS — safe to commit. Tag [s52-late, it-ticket-reassign, attendance-report-menukey, menukey-mirror-5way, gotcha44-disarmed, gotcha50-disarmed].
|
||||
|
||||
- **2026-06-08 (S53 gotcha #57 EXT Mig 47 — Master catalog filtered-unique pre-commit — PASS, 0 blocker, Smart Friend clean):** 4th/5th/6th cumulative gotcha #57 (after Holiday Mig 43 S45 + HRM ×3 Mig 45 S51). 3 Master configs (Department:18/Project:19/Supplier:24) Code unique index `.IsUnique()` → `+.HasFilter("[IsDeleted] = 0")` + Mig 47 (3-file) + 3 new tests. **Independent re-verify ALL GREEN:** build SolutionErp.slnx 0-warn/0-err · **full suite 203 PASS** (58 Dom + 145 Infra, Failed:0 Skipped:0, +3) · 3 new tests run isolated 3-passed-0-skipped. **Cat correctness:** filter string byte-identical to HolidayConfiguration:18 (xxd `5b 49 73 44 65 6c 65 74 65 64 5d 20 3d 20 30` — spaces around `=`, not guessed); index STAYS `unique:true` (2 active same-Code still violate — active uniqueness preserved); Supplier Type index (:25) UNTOUCHED non-unique unfiltered (snapshot:3590 bare). Mig Up=3×Drop+3×Create-filtered, Down=3×reverse-unfiltered (reversible). Snapshot+Designer both show filter on all 3 Master Code idx. **Test NOT tautology:** seeds IsDeleted=true row → real Create*CommandHandler (app-check `AnyAsync(Code==req.Code)` thru HasQueryFilter !IsDeleted PASSES) → asserts NotThrow + active-count==1 + IgnoreQueryFilters all==2; RED-before confirmed (3 failed SqliteException UNIQUE on unfiltered). Cmd signatures match test calls (Project 7-arg/Supplier 9-arg/Dept 4-arg). **noScopeCreep:** git status = exactly 3 configs + snapshot + Mig47 (2 untracked) + 1 test + 2 MEMORY; no FE, no extra mig, no stray. Mig 47 latest in seq (after Mig46 ItTicket SLA). **Learned:** cookie-cutter EXT of proven pattern → discriminator = byte-compare filter string (xxd) vs canonical sibling + verify index still unique (filter must NARROW scope not DROP uniqueness). app-level dup-check existence = the test premise; verify handler actually has `AnyAsync(Code)` else test premise false. **surprise:** implementer claimed "2 pre-existing DocxRenderer warnings" but clean incremental rebuild = 0 warn (unrelated, non-issue). Verdict PASS — safe commit. Tag [s53, gotcha57-ext, mig47, master-catalog, smart-friend-clean].
|
||||
|
||||
- **2026-06-08 (S52 P11-E AttendanceReport + P11-F MaTicket codegen pre-commit — PASS, 0 blocker):** Migration-free (no schema). Independent re-verify: build 0-err · **191 PASS** (58 Dom + 133 Infra, +5: 3 ItTicketCodeGen + 2 AttendanceReport) · fe-admin `tsc --noEmit` exit 0. **Cat3 gotcha #44 attack DISARMED:** `[Authorize(Roles="Admin")]` ×2 report endpoints — verified `AppRoles.Admin = "Admin"` literal (AppRoles.cs:5) == attribute string == FE `user?.roles.includes('Admin')`; "QTV" (DbInit:1454) = display-code NOT role-name; pattern proven (Catalogs/HrmConfigs identical). **Cat3 camelCase contract MATCH** field-for-field BE record PascalCase→FE interface (year/month/rows/grandTotal*/userId/fullName/ot*) — ASP.NET default camelCase, no Program.cs override. **BE handler correct:** `.Year/.Month` in IQueryable (EF-translatable DateTime), `.DayOfWeek`+holidaySet only AFTER `.ToListAsync()` (in-memory) — IQueryable-translation attack handled; holiday-check BEFORE weekend BEFORE weekday (test 2026-06-01 Mon-but-holiday proves override); `DateOnly.FromDateTime` correct (Holiday.Date=DateOnly); OtPolicy fallback 1.5/2.0/3.0; `IsDeleted` via AuditableEntity all 3 entities. Exporter mirrors ContractExcelExporter, ClosedXML 0.105.0, `RenderResult(Content,FileName,ContentType)` ctor order correct, DI registered. **MaTicket codegen:** `e` untracked at codegen time → inner SaveChanges persists ONLY sequence row, no double-insert; gen-on-Create (kanban no-workflow) vs Leave/OT gen-on-Submit — semantically correct; git-show confirms MaTicket was ALWAYS null pre-P11-F (closes gap). **1 MINOR (informational, defer):** sequence-gap-on-failure — codegen commits seq in own Serializable tx BEFORE `Add(e)+SaveChanges`; ticket-insert fail → burned IT/2026/NNN gap. NOT new defect = identical to existing Leave/OT pattern (project-wide accepted trade-off, cosmetic). MyAttendancePage MIRROR divergence intentional+documented (fe-user untouched, §3.9 OK). 0 mock markers. **Learned:** when spec NAMES an attack vector (gotcha #44 role-string), verify the LITERAL const value not just attribute presence — "QTV" display-code was the decoy; role-name match is the real check. **surprise:** Bash tool = bash not PowerShell (Select-String fails exit 127 → use grep). Verdict PASS — safe to commit. Tag [s52, p11ef, attendance-report, mat-codegen, gotcha44-disarmed].
|
||||
|
||||
- **2026-06-08 (S51 P11-C Vehicle+Driver + gotcha #57 pre-commit — PASS, 1 MAJOR caught) [em main proxy — reviewer return truncated gotcha #53]:** Reviewed Mig 44 (Vehicle/Driver catalog) + Mig 45 (filter 3 HRM unique) + FE KIND_CONFIG +2 + 5 tests (186 PASS). Independent build+test re-verify GREEN. **CAUGHT 1 MAJOR (Cat 3 cross-stack contract):** Driver FE↔BE required-field mismatch — FE render phoneNumber/licenseNumber/licenseClass OPTIONAL nhưng BE validator `NotEmpty()` + EF `.IsRequired()` NOT NULL → empty submit = 400/500. Root = inconsistent em-main brief (BE "mirror Vehicle"=required vs FE spec quên required). Fix: FE +`required:true` (align BE all-required như Vehicle). Cats khác clean (Mig diff clean, Authorize Roles=Admin writes, gotcha #57 grep-complete 3 HRM, DbInitializer idempotent + #51 infra-gated, SHA256 mirror, no copy-paste Driver↔Vehicle). **Learned:** parallel fan-out (BE∥FE file-disjoint) → bất kỳ inconsistency trong SHARED em-main contract chỉ lộ lúc integration; green tests ≠ correct contract (no test chạm empty-optional path). reviewer = the net. **surprise:** transient mid-deploy bundle hash (cicd lesson) + reviewer self-truncate trước khi ghi MEMORY → em main proxy. Verdict PASS post-fix. Tag [s51, p11-c, gotcha57, contract-mismatch-catch].
|
||||
|
||||
@ -49,15 +49,15 @@ Test theo CODE (single source truth), document mismatch header comment + report.
|
||||
2. ✅ **DONE S45** — EmployeeSatellite FK invariant + soft-delete + cascade: 10 test (`EmployeeSatelliteTests.cs`)
|
||||
3. ✅ **DONE S45** — gotcha #44 authz regression EmployeesController + HrmConfigsController: 10 test (extend `AuthorizePolicyRegressionTests.cs`)
|
||||
4. Phase 10.3 Proposal ApproveV2 (S37) + Workflow Apps skeleton (S38) — test-after khi UAT confirm
|
||||
5. **gotcha #57 (S51 REPRODUCED — RED live):** LeaveType.Code + ShiftPattern.Code bare `.IsUnique()` chưa filter `[IsDeleted]=0` (cùng class Holiday Mig 43). Test `HrmConfigFilteredUniqueTests.cs` 2 RED (`SQLite Error 19 UNIQUE constraint failed`). Em main fix Mig 45 `.HasFilter` → 2 GREEN. Vehicle+Driver (Mig 44) ĐÃ filtered → 2 GREEN baseline.
|
||||
5. **gotcha #57 (S51+S52 REPRODUCED):** HRM `HrmConfigFilteredUniqueTests` 2 RED (LeaveType+Shift+OtPolicy) → em main Mig 45 `.HasFilter` GREEN. **S52 EXT Master** `MasterCatalogFilteredUniqueTests` 3 RED (Department cfg:18 / Project:19 / Supplier:24 bare `.IsUnique()`) → pending em main fix migration. Vehicle+Driver (Mig 44) ĐÃ filtered. Pattern: seed `IsDeleted=true` slot + Create cùng Code → assert active==1 + `IgnoreQueryFilters` all==2.
|
||||
|
||||
## 📅 Recent activity (last 10 FIFO)
|
||||
|
||||
- **2026-06-08 (S52 P11-D Master gotcha #57 EXT) [test-before · 3 RED LIVE]:** +3 test `tests/.../Application/MasterCatalogFilteredUniqueTests.cs` (run `--filter MasterCatalogFilteredUnique` → Failed 3/Passed 0). Department+Project+Supplier `.IsUnique()` BARE (Dept cfg:18 / Proj:19 / Supp:24) chưa `[IsDeleted]=0` — cùng class gotcha #57. Mirror EXACT GROUP B HrmConfigFilteredUniqueTests: seed row `IsDeleted=true` slot Code="DUP1" → `Create{Dept|Project|Supplier}CommandHandler(db)` cùng Code → assert `NotThrowAsync` + active==1 + `IgnoreQueryFilters` all==2. **3 RED** = `DbUpdateException → SQLite Error 19 UNIQUE constraint failed: {Departments|Projects|Suppliers}.Code` (app-check `AnyAsync(Code==X)` chạy QUA HasQueryFilter → loại soft-deleted → PASS → Add+SaveChanges → DB UNIQUE bare đếm cả row xoá → throw). NOT test lỗi — REPORTED em main fix migration `.HasFilter` 3 config → flip GREEN. **⚠️ all-count PHẢI `IgnoreQueryFilters()`** (khác HRM ref dùng raw `Count(Code==X)` trên DbSet đã có HasQueryFilter → trả 1 not 2 = sai; tôi sửa = active-count plain DbSet, all-count IgnoreQueryFilters). 3 handler clean `(IApplicationDbContext db)` 1-dep. KHÔNG đụng Configuration/Domain/migration. Tag [s52, p11-d, gotcha-57, master-catalog, filtered-unique, test-before, RED].
|
||||
- **2026-06-08 (S52 P11-D Wave2 round-robin + SLA-due) [proxy by em main: agent killed session-limit trước MEMORY step]:** +9 test `ItTicketAssignSlaTests.cs` → **200 PASS** (Infra 133→142). **Round-robin:** seed Department Code="IT" + 2 user A/B `IsActive` trong IT + A có 1 ticket Open → Create → assign **B** (load 0<1); tie A=B → `ThenBy(Id)`; edge no-dept-IT / no-user-IT → unassigned; user ngoài IT hoặc `IsActive=false` KHÔNG assign. **SLA-due:** Priority Urgent→+4h / High→+8h / Medium→+24h / Low→+72h (assert `e.SlaDueAt==CreatedAt+SlaWindow[priority]`). **Regression P11-F:** create vẫn gen `^IT/\d{4}/\d{3}$`. `ItTicketSlaJob` BackgroundService SKIP unit-test (breach-query inline, khó test trực tiếp — REPORTED). Baseline 191→**200** (58 Domain + 142 Infra). Tag [s52, p11-d, round-robin, sla-due, regression].
|
||||
|
||||
- **2026-06-08 (S52 P11-E + P11-F WorkflowApps/Attendance test-after):** +5 test → **191 PASS** (Infra 128→133). 2 file `tests/.../Application/`: **ItTicketCodeGenTests** (3 — MaTicket regex `^IT/\d{4}/\d{3}$` + sequential 001→002 cùng prefix `IT/{year}` LastSeq++ + per-year-prefix 2027 reset 001) + **AttendanceReportTests** (2 — full aggregate day-type/weighted + DepartmentId filter). **⭐ Serializable-on-SQLite GOTCHA = NON-ISSUE (confirmed):** `WorkflowAppCodeGen.GenerateMaDonTuAsync` dùng `BeginTransactionAsync(IsolationLevel.Serializable)` chạy SẠCH trên SQLite — provider map isolation level gracefully (no throw), format+seq+per-year đều hold KHÔNG cần try/skip. Đã proven sẵn bởi WorkflowAppApproveV2Tests (DT/LR path). Handler `CreateItTicketHandler(db, cu, clock)` = 3 dep MediatR. **Day-type test pattern (P11-E core):** holiday check chạy TRƯỚC weekend/weekday → seed 2026-06-01 (thứ Hai) vào holidaySet → assert phân **Holiday** dù là weekday (override day-of-week). Holiday.Date=DateOnly → `BuildHoliday` dùng `DateOnly.FromDateTime`. OtWeighted = 2×1.5+3×2.0+1×3.0=12.0m. DepartmentId filter: seed 2 Department row + 2 user khác dept → query deptA chỉ trả 1 row (handler join Users `u.DepartmentId==deptId`, userMeta dùng `DefaultIfEmpty` nên dept row optional nhưng seed cho DepartmentName assert). No prod bug. **⚠️ MSBuild OOM** chạy full parallel → dùng `-maxcpucount:1 -p:BuildInParallel=false` (env resource, KHÔNG test fail). Tag [s52, p11-e, p11-f, codegen, day-type, serializable-sqlite-ok, test-after].
|
||||
- **2026-06-08 (S51 P11-C HMW Wave2 filtered-unique gotcha #57):** +4 test `tests/.../Application/HrmConfigFilteredUniqueTests.cs` → **185 total = 183 PASS + 2 RED** (Infra 123→127). Mirror HolidayTests Case 7 (seed soft-deleted Code-slot → Create same Code → assert success + active==1 + all==2). **2 GREEN** Vehicle+Driver (Mig 44 config ĐÃ filtered → 2 catalog mới đúng). **2 RED INTENTIONAL = gotcha #57 REPRODUCED** (test-before): `CreateLeaveType_OnSoftDeletedCodeSlot...` → `SQLite Error 19 UNIQUE constraint failed: LeaveTypes.Code` + `CreateShift_OnSoftDeletedCodeSlot...` → `ShiftPatterns.Code` (bare `.IsUnique()` đếm cả row soft-deleted; handler app-check `!IsDeleted` PASS → Add+SaveChanges → DbUpdateException). NOT test lỗi — REPORTED em main fix Mig 45 `.HasFilter("[IsDeleted]=0")` cho 2 config → flip GREEN. **⚠️ Soft-delete trong test (giống Holiday):** AuditingInterceptor (prod soft-delete Deleted→Modified+IsDeleted=true) KHÔNG wire trong SqliteDbFixture → `Remove+SaveChanges` = HARD delete (không test được). PHẢI seed row `IsDeleted=true` thủ công để mô phỏng slot bị chiếm. Handlers chỉ cần IApplicationDbContext → `new CreateXxxHandler(db)`. Tag [s51, p11-c, gotcha-57, filtered-unique, test-before].
|
||||
- **2026-05-30 (S42 P11-A Wave4):** +11 test `tests/.../Application/WorkflowAppApproveV2Tests.cs` → **141 PASS** (Infra 72→83). LeaveRequest 8 case full (Submit happy/guard×2, Approve advance/terminal/UPSERT-invariant/forbidden/empty-comment-placeholder, Reject→TuChoi, Return→TraLai+RejectedFromStatus) + OtRequest smoke (submit→approve single-level→DaDuyet). **No prod bug** — LeaveOt ApproveV2 wire correct, all PASS first run. **NEW Pattern:** WorkflowApps handlers = CQRS MediatR (KHÔNG service) → instantiate handler trực tiếp `new ApproveLeaveRequestHandler(db, AsUser(u), clock).Handle(cmd,ct)`, chỉ 3 dep (IApplicationDbContext + TestCurrentUser + FixedDateTime) — nhẹ hơn 6-dep Contract service. MaDonTu format "DT/LR/2026/001". Gap #4 (Workflow Apps) PARTIAL done — Travel/Vehicle mirror pending. ⚠️ Lesson: CWD drift (fe-user) → ghi MEMORY nhầm path, em main relocate. Verify CWD root trước Write memory.
|
||||
- **2026-05-30 (S43 P11-B Wave3 LeaveBalance):** +8 test `tests/.../Application/LeaveBalanceTests.cs` → **152 PASS** (Infra 86→94). Deduction hook (ApproveLeaveRequestHandler terminal) full: deduct single-level (create row from DaysPerYear), only-at-terminal multi-level (advance no-deduct + 1× terminal), accumulate UPSERT (5+2=7 no new row), negative allowed (Used20>Entitled12 → Remaining−8 no throw), Reject+Return no-deduct (split 5a/5b), GetMyLeaveBalances lazy synth (2 active type filter inactive), AdjustLeaveBalance upsert. **⚠️ FOUND + FIXED 2 pre-existing RED** in S42 template (`Approve_LastLevel_TransitionsToDaDuyet` + `Approve_EmptyComment_StoresPlaceholder`): Wave 1 deduction hook (uncommitted, prod) làm terminal insert LeaveBalance FK→LeaveTypes Restrict FAIL vì BuildLeave dùng `LeaveTypeId=Guid.NewGuid()`. **NOT prod bug** (prod đơn luôn pin LeaveType thật) — fix tại test: BuildLeave +optional leaveTypeId, seed LeaveType ở 2 test đó. Baseline thật trước S43 = 142-pass/2-RED (KHÔNG phải 144-green). REPORTED em main.
|
||||
- **2026-06-01 (S45 HRM coverage gaps + Holiday drift) [em main proxy]:** +27 test → **181 PASS** (Infra 96→123). 3 file: HrmConfigHolidayTests (7 — composite UNIQUE Create/Update, ⭐self-update giữ key đổi Name no-false-positive, soft-delete exclusion) + EmployeeSatelliteTests (10 — 5× FK-invariant parent `AnyAsync(!IsDeleted)` guard + soft-delete + cascade-non-behavior Case5 + EF model `DeleteBehavior.Cascade` config assertion) + AuthorizePolicyRegressionTests extend (10 — HrmConfigs bare-`[Authorize]`+writes `Roles=Admin`; Employees class-`Policy=Hrm_HoSo.Read`+per-action). **FOUND drift** (test theo CODE = single source): Holiday DB UNIQUE (Year,Date) unfiltered vs handler `!IsDeleted` → recreate-on-soft-deleted-slot `DbUpdateException(500)`. REPORTED → em main fixed Mig 43 `.HasFilter("[IsDeleted]=0")` (Case 7 flipped assert SUCCESS). New pattern: EF model-metadata assertion `db.Model.FindEntityType(typeof(X)).GetForeignKeys()...DeleteBehavior` lock schema intent. ⚠️ gotcha #57 backlog: LeaveType.Code + ShiftPattern.Code vẫn unfiltered.
|
||||
|
||||
|
||||
@ -9,16 +9,16 @@
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Architecture (10 agent)
|
||||
## 🎯 Architecture (11 agent)
|
||||
|
||||
> ⓘ **Diagram dưới = 7 core lane (pre-S47 snapshot).** Roster THẬT = **10 sub**: 7 core (diagram) + 🩷 frontend-designer (S47) + 🟫 tooling-auditor H1 + ⬜ harvest-curator H2 (2026-06-07 Harness 1) — xem skill matrix + decision tree + tool grant dưới. (Cosmetic ASCII chưa vẽ lại — tooling-auditor H1 sẽ flag drift này @session-start.)
|
||||
> ⓘ **Diagram dưới = 7 core lane (pre-S47 snapshot).** Roster THẬT = **11 sub**: 7 core (diagram) + 🩷 frontend-designer (S47) + 🔵 database-agent (S52) + 🟫 tooling-auditor H1 + ⬜ harvest-curator H2 (2026-06-07 Harness 1) — xem skill matrix + decision tree + tool grant dưới. (Cosmetic ASCII chưa vẽ lại — tooling-auditor H1 sẽ flag drift này @session-start.)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ EM (Main) — Opus 4.8 1M Max │
|
||||
│ • Reasoning + write code (single-threaded principle) │
|
||||
│ • Schema/UX/architecture decision + cross-stack tight coupling│
|
||||
│ • Coordinate 10 sub-agents via spawn + SendMessage │
|
||||
│ • Coordinate 11 sub-agents via spawn + SendMessage │
|
||||
│ • Synthesize cross-agent findings + commit/push (em main only)│
|
||||
│ • Fallback solo nếu spawn fail (gotcha #53 truncate / 529) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
@ -162,7 +162,7 @@ All 10 agent có **4 RAG-READ MCP**: `search_memory` + `search_code` (BM25, pref
|
||||
|
||||
## 🔌 External skill/plugin mapping (H3 — Harness 1 adopt 2026-06-07)
|
||||
|
||||
> Floor H3: nạp đúng skill/plugin hợp stack, **gộp-vai KHÔNG phình roster**. Audit (investigator-api 2026-06-07; tooling-auditor H1 re-count S50): **18** plugin enabled user-global (`~/.claude/settings.json` — +csharp-lsp +typescript-lsp +session-report vs S49's 15) + ~23 standalone skill (`~/.claude/skills/`). **0 agent mới** — mọi cái = skill→gộp-vai-hiện-có. Nấc: enabled/available → **assigned** (bảng dưới = doc) → used (per-session auto-trigger). tooling-auditor (H1) rà new-alloc mỗi session-end.
|
||||
> Floor H3: nạp đúng skill/plugin hợp stack, **gộp-vai KHÔNG phình roster**. Audit (investigator-api 2026-06-07; tooling-auditor H1 re-count S53): **18** plugin registered (15 enabled / 3 disabled) user-global (`~/.claude/settings.json` — +csharp-lsp +typescript-lsp +session-report vs S49's 15) + ~23 standalone skill (`~/.claude/skills/`). **0 agent mới** — mọi cái = skill→gộp-vai-hiện-có. Nấc: enabled/available → **assigned** (bảng dưới = doc) → used (per-session auto-trigger). tooling-auditor (H1) rà new-alloc mỗi session-end.
|
||||
|
||||
| Skill/plugin (nguồn) | Value-locus | Map → vai | Ghi chú |
|
||||
|---|---|---|---|
|
||||
|
||||
@ -17,7 +17,7 @@ Skill này là tài liệu chuyên biệt để Claude (và developer khác) dù
|
||||
| Skill | Mục đích | Trigger ví dụ | Trạng thái |
|
||||
|---|---|---|---|
|
||||
| `dependency-audit-erp` | Scan CVE NuGet + npm 2 FE, respect pin constraint (MediatR 12.4.1, Swashbuckle 6.9.0) | "npm audit", "dotnet vulnerable", "deps scan", "nâng cấp package" | ✅ New Tier 3 |
|
||||
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, **43 migration history** (Init → FilterHolidayUniqueIndexByIsDeleted Mig 43) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated S45 (Mig 43 Holiday filtered-unique) |
|
||||
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, **47 migration history** (Init → FilterMasterCatalogUniqueIndexesByIsDeleted Mig 47) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated S53 (Mig 47 Master filtered-unique) |
|
||||
| `iis-deploy-runbook` | 3 IIS site + win-acme cert + gitea-runner + LibreOffice + debug 500/502/SignalR prod + **G-084 IPv4/IPv6 hardening** | "prod 500", "IIS fail", "cert hết hạn", "restart app pool", "deploy IIS", "port hijack" | ✅ Updated (G-084) |
|
||||
|
||||
## Format chuẩn 1 skill
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: ef-core-migration
|
||||
description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có 43 migration sẵn (Init → FilterHolidayUniqueIndexByIsDeleted Mig 43, S45). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ.
|
||||
description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có 47 migration sẵn (Init → FilterMasterCatalogUniqueIndexesByIsDeleted Mig 47, S53). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ.
|
||||
when-to-use:
|
||||
- "thêm migration"
|
||||
- "EF Core migration"
|
||||
@ -16,7 +16,7 @@ when-to-use:
|
||||
|
||||
> **Context:** .NET 10 + EF Core 10 + SQL Server. DbContext: `ApplicationDbContext` ở `Infrastructure/Persistence/`. Startup: `SolutionErp.Api`.
|
||||
|
||||
## Migration history (43 migration hiện có)
|
||||
## Migration history (47 migration hiện có)
|
||||
|
||||
| # | Name | Tables added / changed |
|
||||
|---|---|---|
|
||||
|
||||
@ -50,7 +50,7 @@ Kiến trúc: **.NET 10 Clean Architecture + 2 React FE (admin + user) + SQL Ser
|
||||
- Audit fields: `CreatedAt`, `UpdatedAt`, `CreatedBy`, `UpdatedBy` (`BaseEntity`)
|
||||
- Soft delete: `IsDeleted`, `DeletedAt`, `DeletedBy` (`AuditableEntity`)
|
||||
- Migrations: `dotnet ef migrations add <Name> --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api`
|
||||
- **Hiện có 45 migration → 92 bảng** (Phase 10 COMPLETE + Phase 11 P11-A/B/C — Mig 34-42 HRM/Office/WorkflowApps/Attendance + Contract V2 (32-33) + WireWorkflowApps V2 (41) + LeaveBalance (42) + Holiday filtered-unique (43, S45) + Vehicle/Driver catalog (44, S51) + HRM-catalog filtered-unique 3× (45, S51 gotcha #57). V2 schema history S29-era bên dưới giữ nguyên — Mig 32+33 Plan B Contract V2 cookie-cutter mirror PE Mig 22-26 (S29). Mig 26 `AddPeLevelOpinionsForV2`: bảng mới `PurchaseEvaluationLevelOpinions` UNIQUE composite (PEId, LevelId), FK Cascade Pe + Restrict Level. Section 5 "Ý kiến cấp duyệt" V2 dynamic theo workflow đã pin: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox. Service `ApproveV2Async` UPSERT auto khi NV duyệt — Q1=1B (sync gắn với Duyệt, KHÔNG form input rời). SignedByUserId track signer thật, FE banner "Admin duyệt thay" khi !== ApproverUserId. Comment empty → "(duyệt — không ý kiến)" placeholder. Phiếu V1 legacy fallback Mig 15 4 box readOnly (data history). Mig 25 `AddIsUserSelectableToApprovalWorkflows`: ALTER `ApprovalWorkflows` +`IsUserSelectable bit` (admin pin/unpin workflow nào cho user pick lúc create phiếu, multi-select độc lập IsActive). Backfill `WHERE IsActive=1 SET 1` giữ behavior cũ. Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim. Workspace filter dropdown chỉ workflows `IsUserSelectable=true`. Mig 22-24 V2 schema (Session 17): `ApprovalWorkflows`/Steps/Levels — Quy trình > Bước (Phòng) > Cấp (N NV cụ thể qua ApproverUserId, OR-of-N cùng cấp). PE.ApprovalWorkflowId pin V2. PE.CurrentApprovalLevelOrder track. State machine 5 trạng thái: Nháp / Đã gửi duyệt / Trả lại (Phase riêng TraLai=98) / Từ chối / Đã duyệt. PE Service V2 wire match `actor.Id == ApproverUserId`. Contract V2 ĐÃ WIRE (Mig 32+33 Plan B S29 — cookie-cutter mirror PE V2: `ApproveV2Async` + `ContractLevelOpinions` UPSERT + Workspace V2 Select dropdown). Mig 21 V1 flat workflow vẫn live cho phiếu cũ.)
|
||||
- **Hiện có 47 migration → 92 bảng** (Phase 10 COMPLETE + Phase 11 P11-A→F done — Mig 34-42 HRM/Office/WorkflowApps/Attendance + Contract V2 (32-33) + WireWorkflowApps V2 (41) + LeaveBalance (42) + Holiday filtered-unique (43, S45) + Vehicle/Driver catalog (44, S51) + HRM-catalog filtered-unique 3× (45, S51) + ItTicket SLA (46, S52) + Master filtered-unique 3× (47, S53 gotcha #57 EXT). V2 schema history S29-era bên dưới giữ nguyên — Mig 32+33 Plan B Contract V2 cookie-cutter mirror PE Mig 22-26 (S29). Mig 26 `AddPeLevelOpinionsForV2`: bảng mới `PurchaseEvaluationLevelOpinions` UNIQUE composite (PEId, LevelId), FK Cascade Pe + Restrict Level. Section 5 "Ý kiến cấp duyệt" V2 dynamic theo workflow đã pin: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox. Service `ApproveV2Async` UPSERT auto khi NV duyệt — Q1=1B (sync gắn với Duyệt, KHÔNG form input rời). SignedByUserId track signer thật, FE banner "Admin duyệt thay" khi !== ApproverUserId. Comment empty → "(duyệt — không ý kiến)" placeholder. Phiếu V1 legacy fallback Mig 15 4 box readOnly (data history). Mig 25 `AddIsUserSelectableToApprovalWorkflows`: ALTER `ApprovalWorkflows` +`IsUserSelectable bit` (admin pin/unpin workflow nào cho user pick lúc create phiếu, multi-select độc lập IsActive). Backfill `WHERE IsActive=1 SET 1` giữ behavior cũ. Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim. Workspace filter dropdown chỉ workflows `IsUserSelectable=true`. Mig 22-24 V2 schema (Session 17): `ApprovalWorkflows`/Steps/Levels — Quy trình > Bước (Phòng) > Cấp (N NV cụ thể qua ApproverUserId, OR-of-N cùng cấp). PE.ApprovalWorkflowId pin V2. PE.CurrentApprovalLevelOrder track. State machine 5 trạng thái: Nháp / Đã gửi duyệt / Trả lại (Phase riêng TraLai=98) / Từ chối / Đã duyệt. PE Service V2 wire match `actor.Id == ApproverUserId`. Contract V2 ĐÃ WIRE (Mig 32+33 Plan B S29 — cookie-cutter mirror PE V2: `ApproveV2Async` + `ContractLevelOpinions` UPSERT + Workspace V2 Select dropdown). Mig 21 V1 flat workflow vẫn live cho phiếu cũ.)
|
||||
|
||||
### Modules
|
||||
|
||||
@ -63,7 +63,7 @@ Kiến trúc: **.NET 10 Clean Architecture + 2 React FE (admin + user) + SQL Ser
|
||||
| Identity (User/Role/Permission/MenuItem) | `Domain/Identity/` | 1, 3, 11 | Feature-complete (30 demo user — 16 sample + 14 Solutions thật) |
|
||||
| Forms (Template + Clause) | `Domain/Forms/` | 4 | Feature-complete |
|
||||
| Notifications | `Domain/Notifications/` | 6 | In-app + SignalR OK, email SMTP TODO |
|
||||
| **Tests** | `tests/SolutionErp.{Domain,Infrastructure}.Tests/` | — | **186 test pass** (58 Domain + 128 Infra) — CI gate + path filter docs-only skip |
|
||||
| **Tests** | `tests/SolutionErp.{Domain,Infrastructure}.Tests/` | — | **203 test pass** (58 Domain + 145 Infra) — CI gate + path filter docs-only skip |
|
||||
|
||||
### Commit convention
|
||||
|
||||
@ -84,7 +84,7 @@ tests/
|
||||
└── Application/ (6 test - PeWorkflowDefinition versioning)
|
||||
```
|
||||
|
||||
**186 unit test pass** (58 Domain + 128 Infra). CI gate + path filter live. (S51: +5 `HrmConfigFilteredUniqueTests` — gotcha #57 extended to 3 HRM catalog (LeaveType/Shift/OtPolicy Mig 45) + Vehicle/Driver catalog Mig 44. EXT Department/Supplier/Project → worktree Mig 46.)
|
||||
**203 unit test pass** (58 Domain + 145 Infra). CI gate + path filter live. (S52 +14→200; S53 +3 `MasterCatalogFilteredUniqueTests` — gotcha #57 EXT Master Department/Supplier/Project DONE Mig 47, S53.)
|
||||
|
||||
```bash
|
||||
dotnet test SolutionErp.slnx # chạy cả 2 test project
|
||||
@ -128,7 +128,7 @@ Quy tắc:
|
||||
| [`docs/workflow-contract.md`](docs/workflow-contract.md) | State machine 9 phase HĐ + role matrix |
|
||||
| [`docs/forms-spec.md`](docs/forms-spec.md) | Catalog 8 form + quy định mã HĐ RG-001 |
|
||||
| [`docs/database/database-guide.md`](docs/database/database-guide.md) | DB conventions + migration workflow + cheatsheet |
|
||||
| [`docs/database/schema-diagram.md`](docs/database/schema-diagram.md) | ⭐ ERD + luồng DB + data flow 92 table (+ §11 PE + §12 Budget + §13 PEDeptOpinions + §14 Contract V2 LevelOpinions; §16+ Mig 32-45 pending) |
|
||||
| [`docs/database/schema-diagram.md`](docs/database/schema-diagram.md) | ⭐ ERD + luồng DB + data flow 92 table (+ §11 PE + §12 Budget + §13 PEDeptOpinions + §14 Contract V2 LevelOpinions; §16+ Mig 32-47 pending) |
|
||||
| [`docs/flows/README.md`](docs/flows/README.md) | Index 6 flow (auth, permission, contract, form, SLA) |
|
||||
| [`docs/gotchas.md`](docs/gotchas.md) | ⭐ 57 bẫy đã gặp — đọc trước khi debug tương tự |
|
||||
| [`.claude/skills/`](.claude/skills/README.md) | 6 skill: contract-workflow, form-engine, permission-matrix, dependency-audit-erp, ef-core-migration, iis-deploy-runbook |
|
||||
|
||||
@ -62,12 +62,12 @@ SOLUTION_ERP/
|
||||
│ ├── PROJECT-MAP.md bản đồ tổng quan
|
||||
│ ├── rules.md coding conventions
|
||||
│ ├── architecture.md layered + PE §9 + Budget §10 + Testing §11
|
||||
│ ├── gotchas.md 56 pitfall đã gặp
|
||||
│ ├── gotchas.md 57 pitfall đã gặp
|
||||
│ ├── forms-spec.md 8 form catalog + RG-001
|
||||
│ ├── workflow-contract.md 9 phase HĐ + role matrix
|
||||
│ ├── database/
|
||||
│ │ ├── database-guide.md conventions + migration workflow
|
||||
│ │ └── schema-diagram.md ERD 91 bảng (+§11 PE +§12 Budget +§13 PEDeptOpinions +§14 Contract V2 LevelOpinions; §16+ Mig 27-42 pending)
|
||||
│ │ └── schema-diagram.md ERD 92 bảng (+§11 PE +§12 Budget +§13 PEDeptOpinions +§14 Contract V2 LevelOpinions; §16+ Mig 27-47 pending)
|
||||
│ ├── flows/ 6 sequence diagram (auth/permission/contract/form/sla + PE ref architecture)
|
||||
│ ├── guides/ setup, cicd, deploy, runbook, security
|
||||
│ ├── changelog/
|
||||
|
||||
@ -2,7 +2,27 @@
|
||||
|
||||
> **Tiering rule (S40):** giữ **2-3 session gần nhất**. Cũ hơn → `docs/changelog/sessions/`. Full brief history pre-S40 → `docs/_archive/HANDOFF-preS40-fullhistory.md`.
|
||||
|
||||
**Last updated:** 2026-06-08 (Session 52 — **Phase 11 product backlog ĐÓNG TRỌN (P11-D+E+F deployed prod)** + database-agent adopt. HMW-mode ON. 3 commit (`e9ee97f`+`6a66429`+`dcf76f8`). Test 186→**200**. Mig 46. Bundle admin `DYfjnpY0`/user `_3S0BPJ2` (deploy verified curl độc lập). ⚠️ **Session-limit hit giữa Wave 2** → recovery on-disk + em main solo FE + curl-self-verify. 🔴 database-agent CHỜ CLI restart. Prev S51: P11-C Vehicle+Driver.)
|
||||
**Last updated:** 2026-06-08 (Session 53 — **gotcha #57 EXT Master (Mig 47) + P11-D reassign-UI + P11-E menu-key + database-agent verified-runtime — all prod-verified**. HMW-mode ON, "làm hết" full closeout. 2 code commit (`44b9e54` Mig 47 Run #260 + `dbf6648` C+D Run #261). Test 200→**203**. Bundle admin `DYfjnpY0`→`DfCfHUE9`/user `_3S0BPJ2` unchanged. Bonus Mig 46 local catch-up. ⚠️ cicd-monitor truncated 2× → curl-self-recovered. Prev S52: P11-D+E+F deployed + database-agent adopt.)
|
||||
|
||||
---
|
||||
|
||||
## S53 (2026-06-08) — gotcha #57 EXT Master (Mig 47) + P11-D reassign-UI + P11-E menu-key + database-agent verified-runtime (HMW-mode ON · "làm hết" full closeout · all prod-verified)
|
||||
|
||||
**User: `/session-start` → "Workflow làm nhanh" (Task B) → "làm hết luôn đi" (C+D+E+session-end). 2 code commit + docs closeout, all Gitea-verified prod.**
|
||||
|
||||
**Done:**
|
||||
- **Bootstrap (3 governance agents):** database-agent **verified-runtime** spawn-test (first real spawn since S52 adopt — CLI restart confirmed; caught **Mig 46 committed-but-unapplied-local** drift) · H1 tooling-auditor + H2 harvest-curator re-report (S52 closeout debt cleared; H2 0-orphan, S52 proxy-append verified present).
|
||||
- **Task B — Mig 47 `FilterMasterCatalogUniqueIndexesByIsDeleted`** (`44b9e54`, Run #260): gotcha #57 EXT to 3 Master (Department/Supplier/Project) filtered-unique Code. Workflow test-specialist→implementer-backend→reviewer (PASS). Test 200→203. Prod: 3 indexes `filter_definition` NULL→`([IsDeleted]=(0))` live. **gotcha #57 EXT backlog CLOSED (6× cumulative: Holiday 43 + 3 HRM 45 + 3 Master 47).** Bonus: Mig 46 local catch-up (Dev+Design).
|
||||
- **Task C+D** (`dbf6648`, Run #261): C = ItTicket admin reassign-UI (fe-admin only divergence; per-card dialog reuse `PUT /assign` + `GET /users`). D = `Off_AttendanceReport` menu-key leaf (no migration, DbInitializer idempotent seed, admin-perm auto via `All[]`, 5 mirror points byte-identical). Workflow implementer-backend→implementer-frontend→reviewer (PASS). Prod: menu row seeded + admin bundle `DfCfHUE9` rotated / user unchanged.
|
||||
- **Doc-drift E + closeout:** H1 top-5 patched (ef-core 43→47, roster 10→11, CLAUDE.md root 45→47/186→203, docs/CLAUDE.md 56→57/91→92) · database-agent adap-report→verified-runtime · STATUS/HANDOFF/session log.
|
||||
|
||||
**⚠️ Lessons:** cicd-monitor truncated 2× (gotcha #53/#55) → curl-self-verify recovery (Gitea run + bundle hashes public, no re-spawn). database-agent first spawn earned ROI immediately (caught local-DB drift that SQLite-tests + CI-prod both miss). Menu-key = 5 mirror points (gotcha #50) — reviewer byte-verified all 5.
|
||||
|
||||
**🔴 NEXT SESSION (anh pick):**
|
||||
- **Phase 9 Ops** (go-live blockers — anh main coordinate): SMTP outbound · rotate creds · SQL auto-backup register · UAT 2-3 real user 1 tuần.
|
||||
- **Monthly drift audit 2026-07-01** (cron) — investigator-codebase ground-truth + H1 chốt.
|
||||
- **Optional minor:** mirror ItTicket reassign to fe-user (nếu employee cần) · RAG re-index S42-S53 (AI_INFRA op, stale 05-29).
|
||||
- **Cert** `api.solutions.com.vn` expire ~2026-07-23 (auto-renew ~06-23).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
> **Update rule:** trước khi bắt đầu 1 task → ghi row `🔥 In Progress`. Xong → `✅ Recently Done`.
|
||||
> **Tiering rule (S40):** chỉ giữ **state hiện tại + 3 session gần nhất** ở file này. Session cũ hơn → `docs/changelog/sessions/`. Full history pre-S40 → `docs/_archive/STATUS-preS40-fullhistory.md`. (Tránh over-context — xóa double, không cắt nội dung.)
|
||||
|
||||
**Last updated:** 2026-06-08 (Session 52 — **Phase 11 product backlog ĐÓNG TRỌN: P11-D+E+F deployed prod** + database-agent adopt, HMW-mode ON): 3 commit — `e9ee97f` (database-agent DB1–DB11 read-advisory, roster 10→11, executed-file CHỜ restart) + `6a66429` Wave 1 (P11-E AttendanceReport+Excel+OtPolicy multiplier + P11-F MaTicket codegen, migration-free) + `dcf76f8` Wave 2 (P11-D ItTicket round-robin assign dept-IT + SLA timer, Mig 46). Test 186→**200**. Bundle admin `DYfjnpY0`/user `_3S0BPJ2` (cả 2 deploy verified curl độc lập — Wave 1 BE 401 wired + Wave 2 /assign 401 + Mig 46 applied health-200). ⚠️ **Session-limit hit giữa Wave 2** → recovery: BE/test verify-on-disk + em main solo FE redo + curl-self-verify thay cicd-spawn (multi-agent resilience, git/disk/prod = source-of-truth). RAG recovered (chunk 2416 rerank live) nhưng stale 05-29. Prev S51: P11-C Vehicle+Driver.)
|
||||
**Last updated:** 2026-06-08 (Session 53 — **gotcha #57 EXT Master (Mig 47) + P11-D reassign-UI + P11-E menu-key — all prod-verified · database-agent verified-runtime; HMW-mode ON, "làm hết" full closeout**: 2 code commit `44b9e54` (Mig 47, Run #260) + `dbf6648` (C+D, Run #261) → test 200→**203**, bundle admin `DYfjnpY0`→`DfCfHUE9` / user `_3S0BPJ2` unchanged. Bonus: Mig 46 local catch-up. cicd-monitor truncated 2× → curl-self-recovered.) Prev S52 (Phase 11 P11-D+E+F deployed + database-agent adopt, HMW-mode ON): 3 commit — `e9ee97f` (database-agent DB1–DB11 read-advisory, roster 10→11, executed-file CHỜ restart) + `6a66429` Wave 1 (P11-E AttendanceReport+Excel+OtPolicy multiplier + P11-F MaTicket codegen, migration-free) + `dcf76f8` Wave 2 (P11-D ItTicket round-robin assign dept-IT + SLA timer, Mig 46). Test 186→**200**. Bundle admin `DYfjnpY0`/user `_3S0BPJ2` (cả 2 deploy verified curl độc lập — Wave 1 BE 401 wired + Wave 2 /assign 401 + Mig 46 applied health-200). ⚠️ **Session-limit hit giữa Wave 2** → recovery: BE/test verify-on-disk + em main solo FE redo + curl-self-verify thay cicd-spawn (multi-agent resilience, git/disk/prod = source-of-truth). RAG recovered (chunk 2416 rerank live) nhưng stale 05-29. Prev S51: P11-C Vehicle+Driver.)
|
||||
|
||||
---
|
||||
|
||||
@ -11,30 +11,30 @@
|
||||
|
||||
| Metric | Value | Note |
|
||||
|---|---|---|
|
||||
| Migrations | **46** | +S52 Mig 46 `AddSlaFieldsToItTicket` (P11-D: 3 column SlaDueAt/SlaWarnedSent/SlaBreached vào ItTicket — KHÔNG bảng mới) |
|
||||
| Migrations | **47** | +S53 Mig 47 `FilterMasterCatalogUniqueIndexesByIsDeleted` (gotcha #57 EXT: Department/Supplier/Project filtered-unique Code — index-only, no new table) |
|
||||
| SQL tables | **92** | unchanged S52 (Mig 46 = AddColumn, no new table; cicd `sys.tables` ground-truth) |
|
||||
| API endpoints | **~252** | +3 S52 (attendances/report + report/excel + it-tickets/{id}/assign) |
|
||||
| FE pages | **68** | +1 S52 AttendanceReportPage (fe-admin); ItTicketsPage upgrade in-place (P11-D badge) |
|
||||
| Menu keys | **~55** | unchanged S52 (report via button, ticket dùng menu sẵn — no new key) |
|
||||
| Tests | **200 PASS** | 58 Domain + 142 Infra · 0 fail / 0 skip · +5 Wave1 (codegen+aggregate) +9 Wave2 (`ItTicketAssignSlaTests` round-robin/SLA-due) |
|
||||
| Menu keys | **~56** | +1 S53 `Off_AttendanceReport` (P11-E promote → sidebar leaf under Văn phòng số, order 8) |
|
||||
| Tests | **203 PASS** | 58 Domain + 145 Infra · 0 fail / 0 skip · +3 S53 `MasterCatalogFilteredUniqueTests` (gotcha #57 EXT Master RED→GREEN) |
|
||||
| Gotchas | **57** | unchanged S52 (#57 soft-delete UNIQUE filter; ext backlog 3 Master worktree still open) |
|
||||
| User memory | **17** | (unchanged S52 — em main solo session, no new feedback entry) |
|
||||
| User memory | **18** | +1 S53 `project_database_agent_verified_local_drift` (read-advisory DB lens catches local-DB drift) |
|
||||
| Skills | 6 | 3 domain + 3 ops |
|
||||
| Sub-agents | **11** | Opus 4.8 1M · 9 product/quality (7 core + frontend-designer + **database-agent S52** read-advisory) + 2 monitor INFORM-only (tooling-auditor H1 + harvest-curator H2). ⚠️ database-agent executed-file CHỜ CLI restart → verified-runtime |
|
||||
| Sub-agents | **11** | Opus 4.8 1M · 9 product/quality (7 core + frontend-designer + database-agent) + 2 monitor INFORM-only (tooling-auditor H1 + harvest-curator H2). ✅ database-agent **verified-runtime S53** (spawn-test PASSED — caught Mig 46-unapplied-local drift) |
|
||||
| RAG chunks | **2416** | Recovered S52 (S51 Qdrant DOWN → nay alive, rerank live 0.9375). Stale `last_indexed 05-29` (S42-S52 via store_memory stopgap; full re-index = AI_INFRA op cần VOYAGE_API_KEY). |
|
||||
|
||||
**Bundle hash live (prod):** admin `DYfjnpY0` · user `_3S0BPJ2` (S52 — rotated by P11-D Wave 2, cả 2 app). Deploy verified curl độc lập (no cicd-spawn do session-limit): api health 200 (→ Mig 46 applied) · GET /it-tickets 401 + PUT /assign 401 (P11-D wired) · /attendances/report 401 (P11-E Wave 1) · cả 2 bundle rotate.
|
||||
**Bundle hash live (prod):** admin `DfCfHUE9` · user `_3S0BPJ2` (S53 — admin rotated by Task C+D FE; user unchanged BE+menu-only). Deploy verified (Run #260 Mig 47 + Run #261 C+D): 3 Master Code indexes `filter_definition`=`([IsDeleted]=(0))` live · `Off_AttendanceReport` MenuItems row seeded prod · health 200 · /users + /it-tickets 401 wired.
|
||||
**Phase:** ✅ Phase 10 COMPLETE · ✅ **Phase 11 product backlog ĐÓNG TRỌN** — P11-A/B/C/**D/E/F** ALL DONE (deployed prod) · 🚫 Phase 9 Ops blocked (anh main coordinate).
|
||||
|
||||
> ⚠️ **Count drift fixed S40:** endpoints ~223→**211**, FE pages 53→**65**, menu keys 85→**~53**. Tables **84 confirmed correct** (DbSet 77 + Identity 7). 3 số "khó fake" (mig/gotcha/git) luôn đúng. Cause: số "incremented mỗi session" over/under-count optimistic — re-ground định kỳ.
|
||||
|
||||
---
|
||||
|
||||
## 🔥 In Progress (S52)
|
||||
## 🔥 In Progress (S53)
|
||||
|
||||
| Task | Owner | Status |
|
||||
|---|---|---|
|
||||
| _(none — S52 ĐÓNG TRỌN Phase 11 product backlog (P11-D+E+F deployed prod) + database-agent adopt. **🔴 NEXT-FIRST: anh restart CLI** → activate database-agent (registry load) → spawn-test verified-runtime. **NEXT (anh pick):** Phase 9 Ops (SMTP/creds/backup/UAT real-user) · follow-up minor: ItTicket reassign-UI (endpoint `/assign` sẵn, UI defer) + P11-E menu-key promote (hiện via button) + gotcha #57 EXT Master = **Mig 47** (Mig 46 đã dùng cho P11-D).)_ | 👤 | ✅ |
|
||||
| _(none — S53 "làm hết" closeout DONE: gotcha #57 EXT Master (Mig 47) + P11-D reassign-UI + P11-E menu-key all **prod-verified** · database-agent **verified-runtime** · Mig 46 local catch-up · doc-drift E patched. **NEXT (anh pick):** Phase 9 Ops (SMTP/creds/backup/UAT real-user — anh main coordinate) · monthly drift audit **2026-07-01** · optional: mirror ItTicket reassign to fe-user nếu cần.)_ | 👤 | ✅ |
|
||||
|
||||
**S40 done:** ✅ Consolidation (`d2f52ba`) · ✅ Curate 4 agent MEMORY >25KB→<8.4KB (`78c9de3`) · ✅ RAG catch-up chunk S37-S40 (rerank 0.867) · ✅ **AI_INFRA bulletin 2026-05-29 adopt 4/4** (MỤC2 Tiered Memory Policy v1 `6f08d1f` + MỤC3 /session-start+/session-end slash commands `c8ff5e1`). ⏳ Full RAG re-index = AI_INFRA op (cần VOYAGE_API_KEY).
|
||||
|
||||
@ -44,6 +44,15 @@
|
||||
|
||||
## ✅ Recently Done (newest on top — 3 session; cũ hơn → session logs)
|
||||
|
||||
### S53 (2026-06-08) — ✅ gotcha #57 EXT Master (Mig 47) + P11-D reassign-UI + P11-E menu-key — all prod-verified · database-agent verified-runtime (HMW-mode ON, "làm hết" full closeout)
|
||||
- **2 code commit deployed prod:** `44b9e54` Task B (Mig 47 `FilterMasterCatalogUniqueIndexesByIsDeleted` — Department/Supplier/Project filtered-unique Code, gotcha #57 EXT) Run #260 · `dbf6648` Task C+D Run #261. Test 200→**203** (+3 `MasterCatalogFilteredUniqueTests` RED→GREEN). Bundle admin `DYfjnpY0`→**`DfCfHUE9`** (rotated C+D FE) · user `_3S0BPJ2` unchanged.
|
||||
- **Bootstrap:** database-agent **verified-runtime** (first real spawn since S52 adopt → caught **Mig 46 committed-but-unapplied-local** drift that 203 SQLite-tests + CI-applied-prod both MISS → bonus closed via Mig 47 deploy applying 46+47 to LocalDB Dev+Design). H1+H2 monitors re-reported (S52 closeout debt cleared; H2 confirmed S52 proxy-append present, 0 orphan).
|
||||
- **Task B (Mig 47, Run #260):** workflow 🟪 test-specialist → 🟨 implementer-backend → 🟥 reviewer (PASS 0 issues). 3 prod indexes `filter_definition` NULL→`([IsDeleted]=(0))` live. gotcha #57 EXT cumulative now **6×** (Holiday 43 + 3 HRM 45 + 3 Master 47) — **backlog CLOSED**. Root cause: app dup-check `AnyAsync(Code==req)` đi qua `HasQueryFilter(!IsDeleted)` (bỏ qua soft-deleted) nhưng bare unique index đếm cả → delete+re-add = UNIQUE-500 reachable.
|
||||
- **Task C+D (`dbf6648`, Run #261):** workflow 🟨 implementer-backend → 🟧 implementer-frontend → 🟥 reviewer (PASS 0 issues). C = ItTicket admin reassign dialog (fe-admin only, intentional mirror-break; reuse `PUT /assign` + `GET /users`). D = `Off_AttendanceReport` menu leaf (order 8, no migration — DbInitializer idempotent seed, admin-perm auto via `All[]`, menu-key byte-identical 5 mirror points). Prod: menu row seeded + admin bundle rotated + smoke 200/401.
|
||||
- **Doc-drift E:** H1 top-5 patched (ef-core skill 43→47, agents/README roster 10→11, CLAUDE.md root 45→47 mig + 186→203 test, docs/CLAUDE.md 56→57 + 91→92 ERD). database-agent adap-report → verified-runtime.
|
||||
- ⚠️ **cicd-monitor truncated 2×** (gotcha #53/#55; C+D verify cut mid-Q3) → recovered via curl-self-verify (Gitea run + bundle hashes public; menu-row from agent's pre-truncation sqlcmd). `feedback_agent_kill_recovery` reinforced.
|
||||
- → session log `2026-06-08-S53-gotcha57-ext-p11-cd-closeout.md`.
|
||||
|
||||
### S52 (2026-06-08) — 🎉 Phase 11 product backlog ĐÓNG TRỌN (P11-D+E+F deployed) + database-agent adopt — HMW-mode ON, session-limit recovery
|
||||
- **3 commit deployed prod:** `e9ee97f` (database-agent governance .md, CI-skip) + `6a66429` Wave 1 (P11-E+F code) + `dcf76f8` Wave 2 (P11-D, Mig 46). Test 186→191→**200**. Bundle admin `DYfjnpY0`/user `_3S0BPJ2` (cả 2 rotate; deploy verified curl độc lập — không cicd-spawn do limit).
|
||||
- **🔌 database-agent adopt** (AI_INFRA broadcast `2026-06-08-Agent-database-codebase-agents`): floor DB1–DB11 **READ-advisory tier** (implementer-backend vẫn author file) · skill `sql-database-assistant`+`ef-core-migration` · color OMIT (8 standard hết → mirror monitor precedent) · `store_memory` strip · DB11 RowVersion tie-in vá S43 lost-update gap. **codebase-agent SKIP n-a** (investigator cover + csharp-lsp Windows no-op). Roster 10→**11**. Nấc executed-file → **🔴 CHỜ CLI restart** verified-runtime. adap-report 5-trường LOCK written.
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
# Session 53 — gotcha #57 EXT Master (Mig 47) + P11-D reassign-UI + P11-E menu-key + database-agent verified-runtime
|
||||
|
||||
**Date:** 2026-06-08 · **Mode:** HMW-mode ON · **Theme:** "làm hết luôn đi" — full close of remaining Phase 11 follow-ups + governance + doc-drift, all prod-verified.
|
||||
|
||||
**User flow:** `/session-start` → "Workflow làm nhanh" (Task B gotcha #57 EXT) → "làm hết luôn đi" (Task C + D + doc-drift E + `/session-end`).
|
||||
|
||||
---
|
||||
|
||||
## Outcome
|
||||
|
||||
| Metric | Before (S52) | After (S53) |
|
||||
|---|---|---|
|
||||
| Migrations | 46 | **47** (Mig 47 `FilterMasterCatalogUniqueIndexesByIsDeleted`) |
|
||||
| Tests | 200 | **203** (+3 `MasterCatalogFilteredUniqueTests`) |
|
||||
| Menu keys | ~55 | **~56** (+`Off_AttendanceReport`) |
|
||||
| Sub-agents | 11 (database-agent executed-file) | 11 (database-agent **verified-runtime**) |
|
||||
| Bundle admin | `DYfjnpY0` | **`DfCfHUE9`** (rotated by C+D FE) |
|
||||
| Bundle user | `_3S0BPJ2` | `_3S0BPJ2` (unchanged — fe-user untouched) |
|
||||
| Tables | 92 | 92 (Mig 47 index-only) |
|
||||
|
||||
**2 code commits, both Gitea-verified prod:**
|
||||
- `44b9e54` — Task B (Mig 47, gotcha #57 EXT Master) → Run #260 success
|
||||
- `dbf6648` — Task C + D (ItTicket reassign-UI + AttendanceReport menu-key) → Run #261 success
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — Bootstrap (3 governance agents)
|
||||
|
||||
- **🔵 database-agent → VERIFIED-RUNTIME** (first real spawn since S52 adopt). CLI restart confirmed (registry loaded it). Spawn-test: connected LocalDB `(localdb)\MSSQLLocalDB` Dev+Design, read `__EFMigrationsHistory`, introspected `ItTickets`, queried `sys.tables`. **Caught 2 real drifts** the rest of the toolchain missed:
|
||||
1. **Mig 46 committed-but-unapplied-local** — the migration file + snapshot were on disk (3-file committed S52) and prod had it (CI-applied), but local `SolutionErp_Dev`/`_Design` were stuck at Mig 45 (S52 session-limit killed the local `database update`). 203 SQLite tests + green prod both **miss** this — local DB is the one surface nothing else verifies. → Closed as a bonus when Mig 47's `database update` applied 46+47 to both local instances.
|
||||
2. table-count: raw `sys.tables` = 93 = 92 domain + `__EFMigrationsHistory` (docs' "92 domain" correct).
|
||||
- **🟫 tooling-auditor (H1)** re-report: top-5 doc-drift (ef-core skill 43, roster 10→11, CLAUDE.md root 45, docs/CLAUDE.md 56→57/91→92). Patched at session-end (Part 4).
|
||||
- **⬜ harvest-curator (H2)** re-report: 🟢 clean. S52 proxy-append (Wave-2 killed-mid implementer-backend + test-specialist diaries) **verified present, 0 orphan**. 11 diaries byte>0. 1 cosmetic flagged (`s??`→`s52` placeholder).
|
||||
|
||||
## Part 2 — Task B: gotcha #57 EXT Master (Mig 47)
|
||||
|
||||
**Workflow:** 🟪 test-specialist (RED) → 🟨 implementer-backend (fix+Mig47+GREEN) → 🟥 reviewer (PASS, 0 issues).
|
||||
|
||||
- 3 Master configs (Department:18 / Project:19 / Supplier:24) → unique Code index `.HasFilter("[IsDeleted] = 0")` (byte-for-byte from the existing 13 filtered indexes). Mig 47 = 3× DropIndex+CreateIndex filtered (Up) / reverse (Down).
|
||||
- **Root cause** (test-specialist pinned it): app-level dup-check `db.X.AnyAsync(x => x.Code == req.Code)` runs *through* `HasQueryFilter(!IsDeleted)` → ignores the soft-deleted row → says "Code free"; the **bare** DB unique index counts the soft-deleted row → UNIQUE-500. Admin delete+re-add same Code = reachable 500. `.HasFilter` aligns the index with the query filter.
|
||||
- test-before RED confirmed (3× `SqliteException UNIQUE`) → GREEN after fix. Test 200→203.
|
||||
- Prod (Run #260): 3 indexes `filter_definition` NULL → `([IsDeleted]=(0))` live (cicd sqlcmd).
|
||||
- **gotcha #57 EXT backlog CLOSED** — cumulative 6× (Holiday Mig 43 S45 + 3 HRM Mig 45 S51 + 3 Master Mig 47 S53).
|
||||
|
||||
## Part 3 — Task C+D (one commit `dbf6648`)
|
||||
|
||||
**Workflow:** 🟨 implementer-backend (D-BE) → 🟧 implementer-frontend (C + D-FE) → 🟥 reviewer (PASS, 0 issues).
|
||||
|
||||
- **Task C — ItTicket admin reassign-UI** (fe-admin ONLY, intentional mirror-break): per-card pencil → Dialog with user Select (reuse existing `GET /users` pagedSize 200) → `PUT /it-tickets/{id}/assign` (Admin-only, 204 handled) → invalidate `['it-tickets']`. fe-user `ItTicketsPage` untouched (employees don't reassign). Top comment updated to flag divergence.
|
||||
- **Task D — AttendanceReport menu-key** (no migration): `MenuKeys.OffAttendanceReport = "Off_AttendanceReport"` + `All[]` + DbInitializer seed `(…, "Báo cáo chấm công", MenuKeys.Off, 8, "FileBarChart")`. Admin-perm auto via `SeedAdminPermissionsAsync` iterating `All`. Idempotent seed → prod gets leaf on restart. FE: `menuKeys.ts` const + `Layout.tsx` staticMap → existing `/attendance/report` route (only 2 of 4-place needed; page+route from S52).
|
||||
- **Menu-key = 5 mirror points** (gotcha #50) — reviewer byte-verified `"Off_AttendanceReport"` identical at: BE const, BE `All[]`, DbInitializer seed, FE `menuKeys.ts`, FE Layout staticMap.
|
||||
- Prod (Run #261): `Off_AttendanceReport` MenuItems row seeded (ParentKey=Off, Order=8) · admin bundle `DfCfHUE9` rotated / user `_3S0BPJ2` unchanged · smoke health 200, /users + /it-tickets 401.
|
||||
|
||||
## Part 4 — Closeout (doc-drift E + session-end)
|
||||
|
||||
- **Doc-drift E (H1 top-5 patched):** ef-core SKILL.md + skills/README (43→47, Mig 47, S45→S53) · agents/README roster 10→11 (×3) + plugin "18 enabled"→"18 registered/15 enabled" · CLAUDE.md root 45→47 mig + 186→203 test + Mig list +46+47 · docs/CLAUDE.md 56→57 gotcha + 91→92 ERD + Mig 27-47.
|
||||
- database-agent adap-report: executed-file → **verified-runtime** (§2 nấc + §5 caveat #1 + commit-sha `e9ee97f`).
|
||||
|
||||
---
|
||||
|
||||
## §L.b(d) Spawn-records (4-field {agent · task · nấc · evidence})
|
||||
|
||||
| Agent | Task | Nấc | Evidence |
|
||||
|---|---|---|---|
|
||||
| 🔵 database-agent | verified-runtime spawn-test (introspect ItTickets/migrations) | **verified** | LocalDB connect + `__EFMigrationsHistory` read + caught Mig 46-unapplied-local + table-count 93 |
|
||||
| 🟫 tooling-auditor (H1) | session-start re-report (4-mặt freshness diff vs S50) | verified | top-5 doc-drift coords → all patched Part 4 |
|
||||
| ⬜ harvest-curator (H2) | session-start re-report + session-end gate (5-trục) | verified | 11 diaries byte>0, S52 proxy-append present, 0 orphan |
|
||||
| 🟪 test-specialist | Mig 47 test-before (3 `MasterCatalogFilteredUniqueTests`) | verified | RED 3× SqliteException → GREEN; 200→203 |
|
||||
| 🟨 implementer-backend | Mig 47 (configs+migration) + D-BE (MenuKeys+DbInitializer) | verified | Run #260 + #261; dotnet build 0-err; em-main re-verified |
|
||||
| 🟧 implementer-frontend | C reassign-UI + D-FE menu wiring (fe-admin) | verified | npm build 0-err; admin bundle `DfCfHUE9` rotated prod |
|
||||
| 🟥 reviewer | Mig 47 pre-commit + C+D pre-commit | verified | 2× PASS 0-issues; em-main confirmed builds + git scope |
|
||||
| 🟩 cicd-monitor | Mig 47 prod verify + C+D prod verify | partial (truncated 2×) | Run #260/#261 success + filter_definition + menu-row; **gaps curl-self-recovered** |
|
||||
|
||||
## §L.a — Deterministic detect (AS scan)
|
||||
|
||||
- **AS-truncation (gotcha #53/#55):** cicd-monitor truncated output 2× (C+D verify cut mid-Q3 on its own `AspNetRoles` query typo). **Guard held** (not a new RCA — existing `feedback_agent_kill_recovery` guard): em-main curl-self-verified the gaps (Gitea run #261 + bundle hashes via public curl; menu-row from agent's pre-truncation sqlcmd). No prod impact, no re-spawn needed. Active-Guard "curl-self-verify recovery" → marked verified (2nd consecutive session held: S52 session-limit + S53 truncation).
|
||||
- No bug-production (all 203 green, both deploys verified). No new RCA entry.
|
||||
|
||||
## Lessons
|
||||
|
||||
1. **Read-advisory DB lens earns its seat on first spawn.** database-agent caught a committed-but-unapplied-local migration that *every other green signal missed* (SQLite tests build from snapshot, prod uses CI-applied DB). Local DB is the unverified surface.
|
||||
2. **cicd-monitor truncation is now a 2-session pattern** → curl-self-verify (public Gitea API + bundle HTTP) is the reliable recovery; don't re-trust a 2nd agent round-trip. Bundle-hash diff is content-addressed ground truth for "right FE shipped".
|
||||
3. **Hybrid scout→pipeline** (em-main recon inline → Workflow with exact coords) kept both workflows tight + 0-issue reviews.
|
||||
|
||||
## Next session (anh pick)
|
||||
- **Phase 9 Ops** (anh main coordinate): SMTP · rotate creds · SQL auto-backup register · UAT real user.
|
||||
- **Monthly drift audit 2026-07-01** (cron).
|
||||
- Optional: mirror ItTicket reassign to fe-user · RAG re-index S42-S53 (AI_INFRA op).
|
||||
@ -6,7 +6,7 @@
|
||||
`2026-06-08-Agent-database-codebase-agents` (from: ai_infra · category: **Agent** · reviewer_gate: **PASS_WITH_FIXES-applied** · nac: published · targets: **all-fit** · content_sha256 `76de8f24…`). 2 agent KHUNG: **A `database-agent`** (floor DB1–DB11, EF-Core/SQL-Server-centric) + **B `codebase-agent`** (floor CB1–CB8, .NET semantic + LSP). Recon-grounded: AI_INFRA quét 6/6 sister = .NET + EF Core + SQL Server → 2 floor universal.
|
||||
|
||||
## 2. nac G-011
|
||||
**executed-file** (database-agent.md tailored + agent-memory seed + agents/README roster 10→11 5-điểm sync) → **VERIFIED-pending CLI restart** (agent `.md` no hot-reload → cần (a) anh restart Claude Code để registry load `database-agent`, (b) 1 spawn-test task DB nhỏ — đọc schema 1 bảng `sys.tables` / introspect ItTicket — confirm load OK + chạy DB1–DB11 thật). **codebase-agent = SKIP n-a** (KHÔNG executed — lý do §3).
|
||||
**VERIFIED-RUNTIME (S53 2026-06-08)** — CLI restarted (registry loaded `database-agent`), spawn-test ran live: connected LocalDB `(localdb)\MSSQLLocalDB` (Dev+Design), read `__EFMigrationsHistory`, introspected `ItTickets` schema, queried `sys.tables`/`sys.columns` — **caught 2 real drifts** (Mig 46 committed-but-unapplied-local + table-count 93 raw = 92 domain + `__EFMigrationsHistory`). DB1/DB10 floor exercised. (S52 executed-file → S53 upgraded.) **codebase-agent = SKIP n-a** (KHÔNG executed — lý do §3).
|
||||
|
||||
## 3. evidence
|
||||
**PROJECT-FIT:**
|
||||
@ -26,7 +26,7 @@
|
||||
- `store_memory` KHÔNG trong `tools:` (adap #1) · 4 RAG-read giữ. ✓ (`grep store_memory .claude/agents/database-agent.md` = 0 post-write)
|
||||
- Restart + spawn-test → verified-runtime. ⏳ (defer session-end/next-session)
|
||||
|
||||
commit-sha: **pending S52** (governance .md → CI path-ignore skip; fill sau commit).
|
||||
commit-sha: **`e9ee97f`** (S52 adopt, governance .md → CI path-ignore skip) · **verified-runtime S53** (this session, spawn-test).
|
||||
|
||||
## 4. tailored-gì + skip-gì-vì-sao
|
||||
- **FUNCTION-floor adopt FULLY:** DB1–DB11 giữ đủ canonical (0 hạ). DB2 destructive-guard + DB11 RowVersion concurrency + DB3 EF-Core discipline + DB6 3-file rule — nguyên vẹn.
|
||||
@ -40,7 +40,7 @@ commit-sha: **pending S52** (governance .md → CI path-ignore skip; fill sau co
|
||||
- **SKIP codebase-agent = n-a** (KHÔNG behind — investigator cover + csharp-lsp Windows no-op; §3). Đúng floor broadcast "skip nếu investigator đủ, KHÔNG thêm trùng".
|
||||
|
||||
## 5. honest-caveat
|
||||
- **Nấc = executed-file, KHÔNG verified-runtime.** database-agent CHƯA spawn lần nào (agent `.md` no hot-reload) → DB1–DB11 mới là floor-trong-file, chưa chạy thật. Anh restart CLI → spawn-test mới upgrade verified-runtime. KHÔNG claim "database-agent đang hoạt động".
|
||||
- **Nấc = VERIFIED-RUNTIME (S53 2026-06-08).** database-agent spawn-test PASSED — load OK + DB1/DB10 introspection chạy thật (LocalDB connect + `__EFMigrationsHistory` read + schema introspect). **Value-add proven on first spawn:** read-advisory DB lens caught local-DB drift (Mig 46 unapplied) mà 200/203 tests (SQLite) + prod (CI-applied) đều MISS — đúng gap roster cũ không cover. (S52 executed-file → S53 upgraded; KHÔNG còn pre-restart caveat.)
|
||||
- **G-015 KHÔNG overclaim:** DB7 scope-DB-only + READ-advisory tier = **PHÂN-VAI**, KHÔNG "read-only enforced" — agent giữ `Bash` (write-channel shell mở) + Skill. Containment thật = em main single-writer + git-diff post-session (defense-in-depth), KHÔNG allowlist đơn-độc.
|
||||
- **Value-add chưa proven-runtime:** lý-lẽ "lấp DB-layer gap + DB11 vá S43 lost-update" = thiết-kế-hợp-lý, nhưng ROI thực phụ thuộc tần-suất task DB-design/concurrency phát sinh. Nếu sau vài session database-agent idle (investigator+reviewer đã đủ) → re-assess prune (tránh roster bloat 11-agent). Theo dõi @tooling-auditor H1 (idle/scope-drift check).
|
||||
- **skill active-verify pending:** `sql-database-assistant` present trong skill-list nhưng chưa smoke-test trong vai database-agent; `ef-core-migration` project skill OK. tooling-auditor H1 @session-end confirm "đã-map-vai chưa".
|
||||
|
||||
Reference in New Issue
Block a user