[CLAUDE] Docs: S56 closeout — pre-golive verify + golive-harden + doc-drift + gotcha #58
STATUS/HANDOFF S56 + session log: WF1 pre-golive-verify (7-stream → GO) + WF2 golive-harden (4 fix, code a20cde8 Run #379 PASS). Test 216→228. Code golive-ready; 2 ops VPS pending (IT user + tzutil); FE Phase 2 deferred.
§L closeout (H1/H2): database-agent executed-file→verified-runtime (agents/README:4, D1 closed); ef-core skill 47→48; sys.tables 92→93 reconciled (cicd ground-truth); root CLAUDE test 203→228 + 92→93 bảng; gotcha #58 NEW (EF read-modify-write lost-update→ExecuteUpdate atomic). agent-memory harvest: cicd Run#379 + Fidelity Serializable-correction (impl/test MEMORY, H2 GATE 4.5/5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -47,10 +47,10 @@ Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, ve
|
||||
- **Gitea:** `git.baocaogiaoduc.vn/vietreport-admin/solution-erp` · workflow `.gitea/workflows/deploy.yml` · paths-ignore `['docs/**','**/*.md','.claude/skills/**']`
|
||||
- **Prod:** api/admin/eoffice `.solutions.com.vn` · SSH `ssh vietreport-vps` (Administrator, id_ed25519) · IIS site phys paths (S42 verified): API `C:\inetpub\solution-erp\api` · admin `\fe-admin` · user `\fe-user` (3 sites Started). DB `.\SQLEXPRESS`/`SolutionErp`/`vrapp` SQL-auth. **Conn string key = `ConnectionStrings.Default` (NOT `DefaultConnection`!)** — read pw from prod appsettings.Production.json when `$env:PROD_DB_PASSWORD` empty.
|
||||
- **SSH→PS quoting (S42 lesson):** nested bash→ssh→powershell mangles `$var`/`\"`. Use `iconv UTF-16LE | base64` → `powershell -EncodedCommand $B64`. Single-quote literal paths.
|
||||
- **Tests baseline:** **186 PASS** (S50 Run #371 sha 30a99aa; Domain 58 + Infra 128 = +5 `HrmConfigFilteredUniqueTests` soft-deleted-slot-reuse for Vehicle/Driver/LeaveType/Shift/OtPolicy vs prev 181). CI gate runs both test projects BEFORE build/deploy → status=success ⟹ test gate passed (`tasks` endpoint reports terminal as `status:success`, `conclusion` field NOT populated). Local grep undercounts (Theory/InlineData) — trust CI conclusion. Phase 9 UAT mode skip per chunk OK.
|
||||
- **Mig latest repo:** **Mig 45 `20260608031611_FilterHrmCatalogUniqueIndexesByIsDeleted`** (S50; index-only — drop+recreate IX_{ShiftPatterns,OtPolicies,LeaveTypes}_Code filtered `[IsDeleted]=0`) + **Mig 44 `20260608030149_AddVehicleAndDriverCatalogs`** (+Vehicles +Drivers tables, both IX_*_Code filtered-from-birth). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`. Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. ⚠️ Table-count drift: `sys.tables` count = 92 post-Mig44 (was 90 S42/S45 + 2 new), CLAUDE.md narrative = 93 — same counting-convention diff, NOT missing table. Don't FAIL on 92.
|
||||
- **Tests baseline:** **228 PASS** (S56 Run #379 sha a20cde8; Domain 58 + Infra 170 = +12 golive-harden `ItTicketReassignAuthzTests`/`LeaveBalanceTests`/`WorkflowAppApproveV2Tests`/`DocxRendererTests` vs prev 216). CI gate runs both test projects BEFORE build/deploy → status=success ⟹ test gate passed (`tasks` endpoint reports terminal as `status:success`, `conclusion` field NOT populated). Local grep undercounts (Theory/InlineData) — trust CI conclusion. Phase 9 UAT mode skip per chunk OK.
|
||||
- **Mig latest repo:** **Mig 48 `20260609020759_AddProjectMasterFields`** (S55; AddColumn-only, Project +Year/Investor/Location/Package nullable, NO new table; kèm `SeedRealMasterDataAsync` ungated). Prev Mig 47 FilterMasterCatalog... + 46 AddSlaFieldsToItTicket. Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`. Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. ⚠️ Table-count: `sys.tables` (is_ms_shipped=0) count = **93** (S56 Run #379 verified, Mig 48 col-only no delta); narrative also 93 now — reconciled. Don't FAIL on 92↔93 convention diff.
|
||||
- **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM, gotcha #44 check)
|
||||
- **Bundle hash live S50:** admin `Cg9mvltU` · user `YgqDvsqr` (Run #371 sha 30a99aa, P11-C HrmConfigsPage). Prev admin `DPPTx2Kw` · user `CjoUEsoV` (#369/350b2bf). Bundle size ~800KB/750KB gz. ⚠️ S50 mid-deploy transient: pre-success snapshot showed `CVbyotwa`/`BBlyMlJH` (intermediate FE copy in-flight), final post-success = `Cg9mvltU`/`YgqDvsqr` — re-confirm hash AFTER status=success ALWAYS (anti-pattern #3).
|
||||
- **Bundle hash live S56:** admin `4SUwDLD8` · user `XdKzt9LL` (Run #378/#379, FROZEN since S55 #378 FE redesign — BE-only #379 kept both unchanged ✓). Prev admin `B-d6893W` · user `XdKzt9LL` (#377). Bundle size ~800KB/750KB gz. ⚠️ S50 mid-deploy transient lesson: pre-success snapshot can show intermediate FE copy in-flight — re-confirm hash AFTER status=success ALWAYS (anti-pattern #3).
|
||||
- **DB pw (S42, when `$PROD_DB_PASSWORD` empty):** `vrapp/buKL3TGBkD0wDDbYVw65QeX9` read from `C:\inetpub\solution-erp\api\appsettings.Production.json`→`ConnectionStrings.Default`. ⚠️ Skill-doc path `C:\inetpub\apps\SolutionErp\Api` is STALE → real path `C:\inetpub\solution-erp\api`. sqlcmd over SSH works direct (no UTF-16 encode needed). ⚠️ sys-catalog string-concat queries hit collation conflict (`Latin1_General_CI_AS_KS_WS` vs `SQL_Latin1_General_CP1_CI_AS`) → add `COLLATE DATABASE_DEFAULT` per concatenated column.
|
||||
|
||||
## 🔑 Critical config (flag commit nếu tái xuất)
|
||||
@ -68,6 +68,7 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code /
|
||||
|
||||
## 📅 Recent runs (FIFO — older → archive/git)
|
||||
|
||||
- **2026-06-09 Run #379 (run_number 265) sha=`a20cde8` PASS ~4m20s (S56 GOLIVE-HARDEN BE fixes — LeaveBalance concurrency + ItTicket authz-order + DocxRenderer null-guard + tests, ZERO FE/Mig):** Push `bef5825..a20cde8` 1 commit 13 files: 3 BE `LeaveOtApprovalFeatures.cs` (atomic ExecuteUpdate + Serializable tx vs lost-update) + `WorkflowAppsFeatures.cs` (authz reorder Forbidden-before-NotFound) + `DocxRenderer.cs` (null-guard) + 4 test files (+12 → 216→**228**) + 6 agent-memory `.md`. `.cs`+test present → NOT docs-skip, full pipeline RAN. **Run IN-PROGRESS at first check (status=running 17:51) — correctly did NOT FAIL, polled to terminal** (started 17:51:45 → updated 17:56:05 ≈4m20s status=success iter5). **Bundle FROZEN admin `4SUwDLD8` + user `XdKzt9LL`** (= #378, UNCHANGED ✓ CORRECT for BE-only — NOT ship-fail, mirror Run #243/#368; verified pre-deploy + post-success + +5s re-confirm, NO transient, NO unexpected rotation). **NO migration** — prod `__EFMigrationsHistory` top = `20260609020759_AddProjectMasterFields` (Mig 48) == repo latest, GIỮ NGUYÊN ✓ (BE-logic-only, schema untouched). sys.tables=**93** unchanged. Health live+ready **200/200** + admin/eoffice root 200. **Smoke changed-area endpoints (all gated, none crash):** `GET /it-tickets/assignable-staff` unauth=**401** · `PUT /it-tickets/{guid}/assign` unauth+body=**401** (authz-reorder fix live, route wired) · `GET /leave-balances/my` unauth=**401** (concurrency fix dll deployed) · control fake `/it-tickets/zzz-not-a-route`=**404** (proves 401s are real auth gates not catch-all). 0 regression. **Ship-proof for BE-only no-contract-change = run success + test 228 + Mig 48 unchanged + bundle frozen + health 200** (no observable API delta — fixes are internal handler logic: atomic tx / exception order / null-guard; cannot curl-assert lost-update fix, rely on +12 tests passing in CI gate). Tag `[s56, run379, pass, golive-harden, be-only-bundle-frozen, no-mig]`.
|
||||
- **2026-06-09 (S56 pre-golive verify — NO deploy, read-only audit):** Re-verified prod truth at golive gate (HEAD `bef5825` docs-only → prod correctly = Run #378). build SolutionErp.slnx 0-err + fe-admin/fe-user npm build 0-TS each + test **216** (58D+158I) exact. Prod health live+ready 200; admin root serves `4SUwDLD8` / eoffice `XdKzt9LL` (== baseline, NO drift). `__EFMigrationsHistory` top = Mig 48 == repo; 92 tables. Master-data prod spot: Projects=70 (62 real+8 demo), CAL01.Investor=N'Công ty TNHH Calofic' exact, WorkItems real=71 (VT16/TP30/MEP9/TB16) of 86, Suppliers 3/3. **LESSON — local-vs-prod FE hash divergence is EXPECTED, not ship-fail:** fresh local `npm build` produces a DIFFERENT content-hash than CI-built prod artifact (node_modules/timestamp inputs not byte-reproducible) → load-bearing check is `prod-hash == documented-baseline`, NOT `== my-local-rebuild`. Don't false-alarm on local≠prod when HEAD unchanged. Tag [s56, pre-golive-verify, prod-truth-pass, local-vs-prod-hash-lesson].
|
||||
|
||||
- **2026-06-09 Run #378 (run_number 264) sha=`7feb53e` PASS ~4m24s (S55 Phase-1 FE-Admin VISUAL redesign density-first design-system NAMGROUP-ref keep brand #1F7DC1 — FE-ADMIN-ONLY, ZERO BE/Mig/fe-user):** Push `84fa638..7feb53e` 1 commit 15 files: 13 fe-admin (`index.css` design tokens + 6 ui primitives Button/Dialog/Input/Label/Select/Textarea + 6 shell DataTable/EmptyState/Layout/PageHeader/PhaseBadge/TopBar + DashboardPage) + 2 agent-memory `.md` (frontend-designer/reviewer). NO fe-user, NO `.cs`, NO Mig. `.tsx`/`.css` present → NOT docs-skip, pipeline RAN. **Run IN-PROGRESS at first check (status=running 11:51) — correctly did NOT FAIL, polled to terminal** (started 11:51:06 → updated 11:55:30 ≈4m24s status=success; updated_at froze 11:55:30 across 3 poll iters = terminal signal before status field parsed). **THE KEY PROOF — admin bundle ROTATE `B-d6893W→4SUwDLD8`** (✓ redesign shipped, verified AFTER status=success; pre-success snapshot 11:51 still showed OLD `B-d6893W` = anti-pattern #3 timing confirmed AGAIN; re-confirm +3s post-success = stable `4SUwDLD8`, NO transient this run). **fe-user bundle UNCHANGED `XdKzt9LL`** (= #377; untouched ✓ NOT ship-fail — correct, no fe-user file in commit). Admin root **200 text/html** + serves `<title>Solutions ERP · Admin</title>` + `<div id="root">` (app loads ✓). **NO migration** — prod `__EFMigrationsHistory` top = `20260609020759_AddProjectMasterFields` (Mig 48) == repo latest, GIỮ NGUYÊN ✓ (FE-only, BE/Domain untouched). Health live+ready **200/200** (both pre- and post-deploy). Test gate **216** (CI both proj pre-deploy ⟹ success=passed; `tasks` endpoint reports terminal as `status:success`, `conclusion` NOT populated — trust CI conclusion). 0 regression. **LESSON (single-app FE redesign — asymmetric bundle verify):** when ONLY fe-admin changes, the PASS criteria is asymmetric — admin hash MUST rotate (proof shipped) AND user hash MUST stay frozen (proof scope-correct, no accidental fe-user redeploy). User-unchanged is a POSITIVE signal here (mirror of BE-only Run #243/#368 where admin+user both stay frozen). Visual-only redesign (CSS tokens + className) rotates bundle exactly like logic change — Vite content-hash byte-sensitive. Status-grep gotcha: greedy `.*?` regex failed to isolate `"status"` field mid-poll → use `grep -oE '\\{"id":378,[^}]*\\}'` to capture full object then sub-grep status. Tag `[s55, run378, pass, fe-admin-only-redesign, asymmetric-bundle-verify, no-mig]`.
|
||||
@ -77,7 +78,7 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code /
|
||||
- **2026-06-03 Run #369 (run_number 255) sha=`350b2bf` PASS ~4m13s (S48 FE-only login subtitle a11y `text-slate-500→600`, ZERO BE/Mig):** Push range `7bbfa5a..350b2bf` 2 commits: `009dd94` DOCS/GOVERNANCE-only (9 files: STATUS/HANDOFF + 3 adap-reports + error-ledger + session-log + frontend-designer MEMORY + session-end.md cmd — ALL `.md`/`.claude/**`) + `350b2bf` CODE 2 files `fe-{admin,user}/src/pages/LoginPage.tsx` (1-line each, slate-500→600 subtitle contrast). Mixed push: `.tsx` present → **NOT path-filter skipped, full pipeline RAN** (gotcha #41 Discovery #3 — ≥1 non-ignored file in range ⟹ whole range builds; docs commit alone would skip but `.tsx` overrides). Poll iter5 status=success (started 00:06:33 → 00:10:46). **Bundle ROTATE admin `Krjvg_3j→DPPTx2Kw` + user `6sNStgxa→CjoUEsoV`** (BOTH changed ✓ FE shipped — verified AFTER status=success; pre-deploy snapshot iter0 still showed OLD `Krjvg_3j`/`6sNStgxa`, correct timing per anti-pattern #3). **NO migration** — repo 43 == prod `__EFMigrationsHistory` 43, latest both `...FilterHolidayUniqueIndexByIsDeleted` (Mig 43 unchanged, BE/Domain untouched ✓). Health live+ready 200 + admin/eoffice index 200. Test gate 181 (CI both proj pre-deploy ⟹ success=passed). 0 regression. NEW LESSON: smallest possible FE change (1-line className) still rotates bundle hash — Vite content-hash sensitive to any source byte; mixed docs+tsx push is the canonical case where docs-only-skip does NOT apply. Tag `[s48, run369, pass, fe-only-a11y, mixed-push-not-skipped]`.
|
||||
- **2026-06-01 Run #368 (run_number 254) sha=`0c5a014` PASS ~4m20s (S45 Mig 43 filter Holiday UNIQUE by IsDeleted + 3 HRM test gaps — BE+tests ONLY, ZERO FE):** Push range `dbbed15..0c5a014` 2 commits: `051b62b` Tests +27 (HrmConfigHolidayTests + EmployeeSatelliteTests + AuthorizePolicyRegressionTests-ext → baseline 154→**181**) + `0c5a014` Mig 43 `20260601064128_FilterHolidayUniqueIndexByIsDeleted` (drops+recreates `IX_Holidays_Year_Date` as filtered UNIQUE `WHERE [IsDeleted]=0`, was unfiltered) + HolidayConfiguration.cs edit + Case-7 test flip. 7 files, all BE+tests, none in paths-ignore → CI ran. Poll iter4 status=success (started 13:43:47 → 13:48:07). **Bundle hashes UNCHANGED admin `Krjvg_3j` + user `6sNStgxa`** (= #367) — CORRECT for BE-only push, NOT ship-fail (Run #243 precedent; ship-proof = Mig 43 applied, not bundle rotate). **Mig 43 auto-applied prod** (history top = `...FilterHolidayUniqueIndexByIsDeleted` ✓). **THE FIX VERIFIED prod:** `IX_Holidays_Year_Date | unique=1 | filter=([IsDeleted]=(0))` — filter_definition non-NULL = filtered UNIQUE live (soft-deleted holidays no longer collide on UNIQUE). Health live+ready 200 Healthy. `Holidays` table exists, 10 rows, 2 named idx (PK + filtered UNIQUE). Prod tables=90-by-sys.tables (index-only change, NO new table — consistent #364 delta). NEW LESSON: filtered-index migration verify = check `sys.indexes.filter_definition` non-NULL (NOT just mig-history row); index-only mig = bundle unchanged + table-count unchanged both EXPECTED. Tag `[s45, run368, pass, mig43-filtered-index, be-only-bundle-unchanged]`.
|
||||
- **2026-05-30 Run #367 (run_number 253) sha=`82d7fcf` PASS ~4m08s (S42 P11-B LeaveBalance business logic, Mig 42):** Code commit 22 files (4 BE: Domain `LeaveBalance.cs` + App `LeaveBalanceFeatures.cs`/`LeaveOtApprovalFeatures` deduction hook + `LeaveBalancesController` + IApplicationDbContext + DbContext + Config + Mig42 3-file + 2 FE `WorkflowAppDetailPage`×2 +`workflowApps.ts`×2 + 2 tests + 4 agent-memory .md). Started 11:11:40 → success iter4 11:15:48. **Bundle rotate admin `BU8FTBRi→Krjvg_3j` + user `tepE4jvR→6sNStgxa`** (both changed ✓ FE shipped, verified AFTER status=success — pre-deploy snapshot still showed old hash, correct timing). **Mig 42 `20260530034336_AddLeaveBalances` auto-applied prod** (tables 90→**91**, `LeaveBalances` EXISTS). Schema ✓: UserId/LeaveTypeId/Year/EntitledDays/UsedDays/AdjustmentDays decimal + AuditableEntity soft-delete. **UNIQUE `IX_LeaveBalances_UserId_LeaveTypeId_Year`** + **FK→LeaveTypes del=NO_ACTION** (=Restrict) ✓. New endpoint smoke: `GET /api/leave-balances/my` unauth=**401** (route live not 404) + admin auth=**200** lazy-default 5 LeaveTypes (ANNUAL12/COMPASSIONATE3/MATERNITY180/SICK30/UNPAID0, all Used=0, `remainingDays`=entitled ✓ DTO shape has remainingDays/entitledDays) + `?year=2026` admin route 401 unauth + `PUT /adjust`=411 (route reg). health live/ready 200 Healthy. **NO seed gate concern** (plain table, lazy DTO — Stage 4.6 N/A). 0 regression. Note: prev run #366 (ffb2062 docs STATUS update) was a CODE-path push w/ status=success — NOT docs-only-skipped (commit touched only .md but Gitea still ran since prior range?); actually #366 display_title is Docs but ran full → confirms agent-memory .md NOT in paths-ignore (`.claude/skills/**` ignored, `.claude/agent-memory/**` NOT). Tag `[s42, run367, pass, p11b-leavebalance, mig42]`.
|
||||
- **2026-05-30 Run #365 sha=`75df04e` PASS ~4m05s (S42 P11-A fix workflow picker 2-bug + SetWorkflow endpoint, NO migration):** Code commit 11 files (4 BE controllers + 2 App features `LeaveOtApprovalFeatures`/`TravelVehicleApprovalFeatures` +125 lines + 2 FE `WorkflowAppDetailPage` ×2 + 1 test +79 lines). Status=success iter5 (started 10:15:45). **Bundle rotate admin `BLA09-qv→6D4k-aRi` + user `CXvejOE-→DkME-974`** (both changed ✓ FE fix shipped, verified AFTER status=success). +4 endpoint `PUT /api/{leave,ot,travel,vehicle-bookings}/{id}/workflow` (`Set{Module}WorkflowCommand`, route `[HttpPut("{id:guid}/workflow")]` body record `SetWorkflowBody(Guid ApprovalWorkflowId)`). Unauth smoke leave+ot/workflow → **401** (route exists, NOT 404 ✓). health live+ready 200 Healthy. Test gate **144** (CI both proj pre-deploy; grep undercounts InlineData=14 Fact at WorkflowAppApproveV2Tests). **NO migration** → skipped Stage 4.6 seed (verified #250). **NAMING RECONCILE:** Gitea task IDs are real #364 (e7b66cd, mem-labeled "#250") + #365 (this). Going forward use actual Gitea task id. **HEADS-UP em main:** follow-up commit `e47ef1d` (FE-User ProposalCreatePage workflow dropdown shape, latent S37 bug) pushed 10:19:17 DURING poll — NOT yet triggered CI run, will redeploy FE shortly (bundle may re-rotate). Out of scope this verdict. Tag `[s42, run365, pass, p11a-setworkflow]`.
|
||||
- **2026-05-30 Run #365 sha=`75df04e` PASS ~4m05s (S42 P11-A fix workflow picker 2-bug + SetWorkflow endpoint, NO mig):** 11 files BE+FE×2+test. Bundle rotate admin `BLA09-qv→6D4k-aRi` + user `CXvejOE-→DkME-974`. +4 `PUT /api/{leave,ot,travel,vehicle-bookings}/{id}/workflow` unauth=401. Test 144. NAMING RECONCILE: use real Gitea task id (#364=e7b66cd mem-labeled "#250"). Tag `[s42, run365, pass, p11a-setworkflow]`.
|
||||
- **2026-05-30 Run #364 (mem #250) sha=`e7b66cd` PASS ~4m07s (S42 P11-A wire ApproveV2+LevelOpinions 4 WorkflowApps):** 1 commit BE+FE×2+Mig41+Tests. Status=success iter3. Bundle rotate admin `cWAXid0q→BLA09-qv` + user `CX79e2kZ→CXvejOE-`. **Mig 41 auto-applied prod** (latest=`20260530021936_WireWorkflowAppsApprovalV2`). Tables 84→**90** (+5: Leave/Ot/Travel/VehicleRequest LevelOpinions + WorkflowAppCodeSequences — ALL EXIST). 4 new endpoint smoke 200 auth (leave/ot/travel/vehicle-requests) + unauth 401 (route exists) + POST .../approve=411 (route reg). health live/ready 200. **Stage 4.6 seed gate PASS** (gotcha #51): 4 WF seeded prod despite DemoSeed:Disabled — QT-NP/OT/CT/XE-V2-001 AppType=5/6/7/9, verified call-site L142-145 OUTSIDE `if(!demoSeedDisabled)` gate. Test gate 141 (CI runs both proj pre-deploy). Note: table count 90 vs spec-expected 89 = baseline-count diff, NOT missing table (all 5 present). Stale doc drift deploy.yml comments "54/17 test" (cosmetic, flag em main). Tag `[s42, run250, pass, p11a-approvev2-workflowapps]`.
|
||||
- **2026-05-28 Run #247 sha=`e54a22d` PASS 3m25s (S38 SKELETON 5-plan combo Mig 39+40 dual):** Push 1 commit mega `Domain+App+Infra+Api+FE×2`. ALL PASS. Bundle rotate admin `CGueDk22→cWAXid0q` + user `CEt0QRgX→CX79e2kZ`. Mig 39+40 dual auto-applied startup (90830→90839). 6 endpoint smoke 200 (leave/ot/travel/vehicle/it-tickets/hr-dashboard `totalEmployees=33 male=17 female=16`). 6 new tables + 8 menu seeded. 0 regression. Fastest S38 deploy. Tag `[s38, run247, pass, skeleton-combo]`.
|
||||
- **Archived Run #246 (S37 Proposal Mig 37+38 — `/api/proposals` 200 + QT-DX-V2-001 AppType=4 seed + Stage 4.6 INFRASTRUCTURE-gated correct gotcha #51) + #359/#243/#242/#241/#240 + S35/S36 startup → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** Run #359 G-O2 Meeting Mig 36 · #243 HrmConfig BE 16 endpoint (BE-only bundle unchanged anti-pattern verify) · #242 FE inline forms 5 satellite · #241 Mig 35 HRM foundation · #240 satellite CRUD. Discovery #7 path-filter eval/** + #8 collection `proj_*`. KEY absorbed in essentials/Stage sections above.
|
||||
|
||||
@ -75,6 +75,7 @@ UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT i
|
||||
## 📅 Recent activity (FIFO — older → archive/git)
|
||||
|
||||
- **S56 GOLIVE-HARDEN 3 BE fix (NO mig, 3 file edit, em-main spec deterministic 100% → ACCEPT Case 1):** **#3 LeaveBalance lost-update** `LeaveOtApprovalFeatures.cs` terminal DaDuyet branch → atomic-executeupdate-tx (spec chosen, KHÔNG RowVersion/Mig). Replaced in-mem `bal.UsedDays += NumDays` với: set p.Status/Updated* → `(DbContext)db` cast + `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) → STEP2 `db.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 → tracked `bal` giữ pre-increment value; SAFE vì không đọc lại + handler return ngay; KHÔNG thêm `bal.UsedDays +=` (double-count). `using System.Data` + EF Core đã import. **#5 AssignItTicketHandler existence-oracle** `WorkflowAppsFeatures.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** → hoist `mainPart = 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 FAIL `LeaveBalanceTests` (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 code `LeaveOtApprovalFeatures.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 `AddProjectMasterFields` 4 AddColumn no-table + `SeedRealMasterDataAsync` 62 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.cs` DTO+CreateCmd+UpdateCmd+validators+handlers+List/Get projections +4 (all nullable, appended end). **`SeedRealMasterDataAsync`** = 3 tuple-loop per-code idempotent (mirror `SeedDemoMasterDataAsync:2185` `existingCodes.Contains→skip`) wired UNGATED line 118 AFTER `SeedCatalogsAsync` → 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-fix `MasterCatalogFilteredUniqueTests.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 spec `scripts/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) + MODIFIED `AssignItTicketHandler`: authz Admin-OR-dept-IT → `ForbiddenException`; assignee-must-be-IT → `ConflictException`. Controller `/assign` hạ `[Authorize(Roles="Admin")]`→`[Authorize]` (handler enforce fine-grained data-driven) + NEW `GET /assignable-staff`. Predicate IT = reuse round-robin S52 `Departments.Where(Code=="IT" && !IsDeleted)`. `ICurrentUser` KHÔNG có DepartmentId → query `db.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 +`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]`.
|
||||
|
||||
@ -54,6 +54,7 @@ Test theo CODE (single source truth), document mismatch header comment + report.
|
||||
## 📅 Recent activity (last 10 FIFO)
|
||||
|
||||
- **2026-06-09 (S56 GOLIVE-HARDEN TEST stage — 4 pre-golive fixes, test-after build):** +12 test → **216→228 PASS** (58 Domain + Infra 158→170, 0 fail). Build stage đã land prod fixes (CONTRACT từ build, signatures UNCHANGED). **#3 LeaveBalance lost-update fix:** handler terminal nay increment `db.LeaveBalances.Where(...).ExecuteUpdateAsync(s=>s.SetProperty(b=>b.UsedDays, b=>b.UsedDays+p.NumDays))` server-side + 1 explicit tx (READ COMMITTED, NO IsolationLevel). **⭐ GOTCHA: ExecuteUpdateAsync BYPASS change tracker** → instance bal tracked (Add STEP1 hoặc pre-seed cùng context) GIỮ UsedDays PRE-increment. **4 test cũ LeaveBalanceTests (case 1/2/3/4 line 163/201/240/269) FAIL ở baseline = stale-tracked-read, KHÔNG regression** (spec TEST GUIDANCE đã tiên đoán). Fix = `.AsNoTracking()` re-read (hoặc `ChangeTracker.Clear()`). +2 new: `TwoSeparateRequests_BothTerminal_UsedDaysAccumulates_NotOverwrites` (3+5=8 chứng minh increment accumulate KHÔNG overwrite = race-free invariant) + `Approve_AlreadyDaDuyet_ReApprove_ThrowsConflict_NoDoubleDeduct` (early guard Status!=DaGuiDuyet:296 → exactly-once, balance vẫn 3 not 6). **#4 Travel/Vehicle ApproveV2 smoke (WorkflowAppApproveV2Tests.cs +4):** mỗi module Submit→Approve→DaDuyet happy + outsider→Forbidden. ApplicableType Travel=9 prefix `DT/CT`, Vehicle=7 prefix `DX/XE`. Travel/Vehicle KHÔNG trừ balance → không seed LeaveType. Helper mới `SeedWorkflowForTypeAsync(type,code,...approverIds)`. **#5 ItTicket existence-oracle (ItTicketReassignAuthzTests.cs +2):** authz reorder (Forbidden TRƯỚC NotFound) — non-IT non-admin nhận Forbidden cho ticketId tồn tại VÀ không tồn tại (cặp 5b/5c phản hồi giống nhau = no oracle leak). Reorder KHÔNG vỡ test cũ (Case5 đã expect Forbidden; TicketNotFound dùng Admin caller pass authz hợp lệ). **#6 DocxRenderer (Forms/DocxRendererTests.cs NEW +4):** 0 test trước đó. MainDocumentPart null→`InvalidOperationException("*MainDocumentPart*")` (OpenXml 3.5.1 `WordprocessingDocument.Create(path,type)` tạo package RỖNG no main part) + placeholder replace happy + unknown-key giữ literal + null-value→empty. **⚠️ test helper ExtractBodyText: tránh `MainPart!.Document.Body!` (CS8602 warning) → dùng `?.Document?.Body` + `.Should().NotBeNull()`.** No prod bug found — tất cả fixes là build-stage, tôi WRITE test theo CONTRACT. Tag [s56, golive-harden, executeupdate-tracker-bypass, asnotracking-reread, travel-vehicle-smoke, existence-oracle, docxrenderer].
|
||||
↳ **[em main post-review S56]** Shipped tx = `IsolationLevel.Serializable` (code `LeaveOtApprovalFeatures.cs:369`), KHÔNG READ COMMITTED — entry's '(READ COMMITTED, NO IsolationLevel)' = build-stage snapshot, **superseded** post-review (SQLite test path unaffected — codegen Serializable already green).
|
||||
- **2026-06-08 (S54 ItTicket reassign authz — test-before-merge SECURITY) [harvested by em main — agent MEMORY write mis-landed, B2/B3]:** +13 test `tests/.../Application/ItTicketReassignAuthzTests.cs` → **203→216 PASS** (58 Domain + Infra 145→158, 0 fail). **GetAssignableItStaff (6):** Admin→CanReassign=true + 2 IT-active ordered FullName (Cao<Truong) no KT/inactive leak · IT-staff→true · non-IT non-admin (KT)→false + **empty staff (0-leak assert)** · dept-null→false+empty · inactive-IT-excluded · UserId null→Unauthorized. **AssignItTicket (7):** non-IT non-admin→**ForbiddenException** + side-effect `AssignedToUserId.Should().BeNull()` (no-mutation) · Admin+assignee∈IT→success · IT-staff+assignee∈IT→success · assignee∉IT(KT)→**ConflictException** "Người được giao phải thuộc tổ IT." · assignee inactive→NotFound · ticket not found→NotFound · null→Unauthorized. **Pattern mới: authz-capability test = seed 2-dept (IT+KT) + fake `ICurrentUser` role/dept matrix; assert canReassign flag + Forbidden/Conflict guard; empty-staff = 0-leak.** Forbidden red-able **by-contrast** (case5 non-IT vs case7 IT-staff identical-setup → chỉ khác caller-identity; rule cấm sửa prod để chứng minh RED). **No prod bug** — handler-level data-dependent authz (caller-dept vs IT-dept) = CORRECT pattern, KHÔNG phải gotcha #44 silent-403 gap (Pattern 10 reflection-regression chỉ cho static `[Authorize(Policy)]`; data-driven authz PHẢI ở handler = enforcement point, test cover tại đó). Tag [s54, it-ticket-reassign, authz-capability, forbidden-conflict-guard, test-before-merge, 0-leak].
|
||||
- **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].
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Multi-agent SOLUTION_ERP — Master Coordination Guide (11-agent)
|
||||
|
||||
> **Architecture:** 11 sub-agents Opus 4.8 1M Max + em main coordinator — **9 product/quality** (7 core + frontend-designer pink S47 + database-agent read-advisory S52) + **2 monitor INFORM-only** (`tooling-auditor` H1 + `harvest-curator` H2, 2026-06-07 Harness 1).
|
||||
> **Upgrade S52 (2026-06-08 — AI_INFRA broadcast `2026-06-08-Agent-database-codebase-agents`):** **+database-agent (read-advisory DB specialist, floor DB1–DB11)** — schema/query/migration-design-review/perf/concurrency (DB11 RowVersion vá lost-update S43). Tailor READ-tier (implementer-backend vẫn author) · color OMIT (8 standard hết) · `store_memory` strip. `codebase-agent` = **SKIP n-a** (investigator-codebase đã cover grep/audit + `csharp-lsp` Windows no-op). 🔴 Cần CLI restart → verified-runtime (nấc hiện = executed-file).
|
||||
> **Upgrade S52 (2026-06-08 — AI_INFRA broadcast `2026-06-08-Agent-database-codebase-agents`):** **+database-agent (read-advisory DB specialist, floor DB1–DB11)** — schema/query/migration-design-review/perf/concurrency (DB11 RowVersion vá lost-update S43). Tailor READ-tier (implementer-backend vẫn author) · color OMIT (8 standard hết) · `store_memory` strip. `codebase-agent` = **SKIP n-a** (investigator-codebase đã cover grep/audit + `csharp-lsp` Windows no-op). ✅ **verified-runtime** — spawned OK S53 (first real spawn, caught Mig 46 committed-but-unapplied-local drift) + S56 2× (pre-golive-verify schema-stream + golive-harden design+review). DB11 lost-update fix landed S56 (atomic ExecuteUpdate + Serializable tx, gotcha #58).
|
||||
> Pattern: Anthropic Building Effective Agents orchestrator-workers + Cognition "writes single-threaded" hybrid + post-deploy automated watchdog.
|
||||
> **Upgrade S39 (2026-05-29):** 4→7 agent (split investigator + implementer, +test-specialist) + budget +50% + 5 RAG MCP per agent. Reference BVAAU 7-agent config (adapted, NOT copied — SOLUTION_ERP 2-FE-app fit + 6 skill proven battle-test 38 session). Prior: S20t12 initial 3 + S21t1 +cicd-monitor.
|
||||
> **Upgrade S47 (2026-06-02):** **+frontend-designer (8th sub, pink)** — FD1–FD10 visual-verification design floor (forked AI_INFRA canonical, broadcast `Agent-frontend-designer-floor`). + **`store_memory` STRIPPED khỏi MỌI sub → lead = sole RAG-writer** (broadcast `Memory-store-memory-strip-global`); sub ghi finding → MEMORY.md (file). adap-reports: `docs/governance/adap-reports/`.
|
||||
|
||||
@ -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, **47 migration history** (Init → FilterMasterCatalogUniqueIndexesByIsDeleted Mig 47) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated S53 (Mig 47 Master filtered-unique) |
|
||||
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, **48 migration history** (Init → AddProjectMasterFields Mig 48) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated S55 (Mig 48 Project master fields) |
|
||||
| `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
|
||||
|
||||
10
CLAUDE.md
10
CLAUDE.md
@ -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ó 48 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) + Project master fields Year/Investor/Location/Package (48, S55 — AddColumn no new table, kèm nạp 62 dự án + 71 hạng mục + 3 NCC real data từ Excel qua `SeedRealMasterDataAsync` ungated idempotent). 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ó 48 migration → 93 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) + Project master fields Year/Investor/Location/Package (48, S55 — AddColumn no new table, kèm nạp 62 dự án + 71 hạng mục + 3 NCC real data từ Excel qua `SeedRealMasterDataAsync` ungated idempotent). 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/` | — | **203 test pass** (58 Domain + 145 Infra) — CI gate + path filter docs-only skip |
|
||||
| **Tests** | `tests/SolutionErp.{Domain,Infrastructure}.Tests/` | — | **228 test pass** (58 Domain + 170 Infra) — CI gate + path filter docs-only skip |
|
||||
|
||||
### Commit convention
|
||||
|
||||
@ -84,7 +84,7 @@ tests/
|
||||
└── Application/ (6 test - PeWorkflowDefinition versioning)
|
||||
```
|
||||
|
||||
**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.)
|
||||
**228 unit test pass** (58 Domain + 170 Infra). CI gate + path filter live. (S56 +12 golive-harden; 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,9 +128,9 @@ 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-47 pending) |
|
||||
| [`docs/database/schema-diagram.md`](docs/database/schema-diagram.md) | ⭐ ERD + luồng DB + data flow 93 table (+ §11 PE + §12 Budget + §13 PEDeptOpinions + §14 Contract V2 LevelOpinions; §16+ Mig 32-48 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ự |
|
||||
| [`docs/gotchas.md`](docs/gotchas.md) | ⭐ 58 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 |
|
||||
| [`docs/guides/vps-setup.md`](docs/guides/vps-setup.md) | ⭐ Master runbook deploy VPS shared với VIETREPORT |
|
||||
|
||||
|
||||
@ -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 57 pitfall đã gặp
|
||||
│ ├── gotchas.md 58 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 92 bảng (+§11 PE +§12 Budget +§13 PEDeptOpinions +§14 Contract V2 LevelOpinions; §16+ Mig 27-47 pending)
|
||||
│ │ └── schema-diagram.md ERD 93 bảng (+§11 PE +§12 Budget +§13 PEDeptOpinions +§14 Contract V2 LevelOpinions; §16+ Mig 27-48 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-09 (Session 55 — **Nạp master data thật từ Excel (62 dự án + 71 hạng mục + 3 NCC) + Project +4 cột (Mig 48) — prod-verified**. HMW-mode ON. Commit `69cb393` → Run #377 PASS ~4m33s. Test 216 (compile-fix only). Bundle admin `B-d6893W`/user `XdKzt9LL`. `SeedRealMasterDataAsync` ungated idempotent → coexist demo. 2 agent return truncated (BE+reviewer) → em main disk/runtime-recover. Prev S54 — IT staff tự reassign ticket (cross-stack authz) — prod-verified. 1 code commit `ca4b602` → Run #376 PASS ~4m18s. Test 203→**216**. Bundle admin `DfCfHUE9`→`DmjI8Cmn`/user `_3S0BPJ2`→`YxL_MljK` (cả 2 rotate). NO migration. Task 1 Phase 9 Ops anh dừng. ⚠️ residual: 3 agent ghi MEMORY nhầm `src/Backend/.claude` → em main reconcile. Prev S53: gotcha #57 EXT Master Mig 47 + P11-D/E + database-agent verified-runtime.)
|
||||
**Last updated:** 2026-06-09 (Session 56 — **Pre-golive verify sweep + golive-harden 4 fix — Run #379 PASS, code golive-ready**. WF1 `pre-golive-verify` 7-stream + adversarial → 6 PASS/1 CONCERN/0 blocker = GO (key finds = ops not code). WF2 `golive-harden` fix 4: #3 LeaveBalance lost-update→atomic ExecuteUpdate+Serializable tx (NO mig) · #5 ItTicket authz Forbidden-trước-NotFound · #6 DocxRenderer null-guard · #4 Travel/Vehicle ApproveV2 tests. Test 216→**228**. Bundle FROZEN `4SUwDLD8`/`XdKzt9LL`. `sys.tables` re-ground 92→**93**. gotcha **#58** NEW. reviewer StructuredOutput-fail→em main đỡ. **2 ops VPS pending** (gán user IT + tzutil UTC+7). FE Phase 2 redesign **deferred** (recon ready). Commit `a20cde8`. Prev S55 — **Nạp master data thật từ Excel (62 dự án + 71 hạng mục + 3 NCC) + Project +4 cột (Mig 48) — prod-verified**. HMW-mode ON. Commit `69cb393` → Run #377 PASS ~4m33s. Test 216 (compile-fix only). Bundle admin `B-d6893W`/user `XdKzt9LL`. `SeedRealMasterDataAsync` ungated idempotent → coexist demo. 2 agent return truncated (BE+reviewer) → em main disk/runtime-recover. Prev S54 — IT staff tự reassign ticket (cross-stack authz) — prod-verified. 1 code commit `ca4b602` → Run #376 PASS ~4m18s. Test 203→**216**. Bundle admin `DfCfHUE9`→`DmjI8Cmn`/user `_3S0BPJ2`→`YxL_MljK` (cả 2 rotate). NO migration. Task 1 Phase 9 Ops anh dừng. ⚠️ residual: 3 agent ghi MEMORY nhầm `src/Backend/.claude` → em main reconcile. Prev S53: gotcha #57 EXT Master Mig 47 + P11-D/E + database-agent verified-runtime.)
|
||||
|
||||
---
|
||||
|
||||
## S56 (2026-06-09) — Pre-golive verify sweep + golive-harden 4 fix (HMW 2-workflow · 1 code commit prod-verified)
|
||||
|
||||
**Anh: `/session-start` → hỏi NAMGROUP UI density-first → "Tiếp Phase 2 redesign" (dismiss scope → defer) → "kiểm tra lại tính năng + master data, sắp golive" + `/ultra-on` → "fix hết workflow luôn" → `/session-end`.**
|
||||
|
||||
**Done (commit `a20cde8` → Run #379 PASS ~4m20s, prod-verified):**
|
||||
- **WF1 `pre-golive-verify`** (7 stream song song → adversarial-per-issue): prod-truth/schema/4×logic/authz-curl. **6/7 PASS · 1 CONCERN · 0 blocker · 8 issue (confirm 6 real)** = **GO**. Insight: phát hiện đáng giá nhất = **ops/data không phải bug code** — prod phòng IT (CNTT) 0 active user → helpdesk inert (chỉ live-curl+sqlcmd thấy) + S43 LeaveBalance lost-update còn nguyên + master-data idempotency PROVEN.
|
||||
- **WF2 `golive-harden`** (Design→Build→Test→Review∥): **#3** LeaveBalance lost-update → database-agent design **atomic `ExecuteUpdateAsync`+Serializable tx** (NO migration, exactly-once nguyên) · **#5** ItTicket authz Forbidden-trước-NotFound (fail-closed) · **#6** DocxRenderer null-guard (CS8602 2→0) · **#4** Travel/Vehicle ApproveV2 +4 smoke. Test **216→228**.
|
||||
- **🟥 reviewer stage StructuredOutput-FAIL** → em main đỡ cross-stack review (3 diff clean). **🔵 database-agent PASS** + 1 MAJOR (convention) → em main bump `IsolationLevel.Serializable`. **🟩 cicd Run #379 PASS** (228 test · bundle frozen BE-only · Mig 48 · health 200).
|
||||
- **Closeout:** gotcha **#58** NEW (EF read-modify-write lost-update) · doc-drift patched (agents/README database-agent verified-runtime · ef-core skill 47→48 · sys.tables 92→93 reconciled · docs/CLAUDE 93/Mig48) · **H2 GATE 4.5/5** (Fidelity: Serializable-correction appended impl/test MEMORY).
|
||||
|
||||
**🔴 NEXT SESSION (anh pick):**
|
||||
- **2 ops VPS — của anh (CHƯA làm):** (1) gán ≥1 user thật vào phòng IT (`UPDATE Users SET DepartmentId='65CC6307-BF3A-4F42-9B83-18FE187F46BB' WHERE Email='<email>@solutions.com.vn'`) — helpdesk inert vì 0 active user · (2) `ssh vietreport-vps "tzutil /g"` → confirm `SE Asia Standard Time` (codegen mã đơn dùng năm giờ-server).
|
||||
- **FE redesign Phase 2 (DEFERRED — recon READY):** build `ui/Drawer.tsx` (chưa có) + tách `InlineEditRow` (mẫu `EmployeesListPage:256`) → áp Drawer (Suppliers/Projects/Users ≥8 field) + bậc-thang (Catalogs/MeetingRooms/HrmConfigs Dialog→inline) cho Master/Office/System/HRM. **Scope chờ anh chốt:** Budget (2 trang) + 3 WF-Designer = IN hay OUT (default em đề xuất **OUT**). fe-user mirror = Phase 3. NAMGROUP density-first, giữ brand #1F7DC1 + Be Vietnam Pro. Authed-page visual qua deploy-prod (dev-rig chặn authed screenshot, designer gotcha #3).
|
||||
- **Phase 9 Ops** (go-live blockers): SMTP outbound (em code-able greenfield `IEmailSender`) · SQL backup register · rotate creds · UAT.
|
||||
- **L1 soft-cap chore (H2):** cicd-monitor 29.2KB + investigator-codebase 28.4KB + reviewer 28KB ≈ 30KB cap → archive L2 next curate.
|
||||
- **Monthly audit 2026-07-01:** root CLAUDE.md count-sweep còn (test "203"→228, "92 bảng"→93 ×several, gotcha "57"→58, ASCII tree "128") — defer deliberate (monthly mechanism).
|
||||
- **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-09 (Session 55 — **Nạp master data thật từ Excel + Project +4 cột (Mig 48), HMW-mode ON**: commit `69cb393` → Run #377 PASS ~4m33s, prod-verified. Anh giao file Excel "HẠNG MỤC CÔNG VIỆC DỰ ÁN" → `/ultra-on "workflow làm xong hết"`. Nạp **62 dự án + 71 hạng mục + 3 NCC** vào Project/WorkItem/Supplier qua `SeedRealMasterDataAsync` (per-code idempotent, **UNGATED** → coexist demo, tự lên prod). **Mig 48 `AddProjectMasterFields`**: Project +4 cột nullable (Year/Investor/Location/Package, NO new table). FE ProjectsPage form +4 input ×2 app SHA256 mirror. Test 216 (compile-fix MasterCatalogFilteredUniqueTests +4 null args, no new test). Bundle admin `DmjI8Cmn`→`B-d6893W`/user `YxL_MljK`→`XdKzt9LL` (cả 2 rotate). Prod verify: Mig 48 applied · Projects spot-6/6 · WorkItems VT/TP/MEP/TB=71 · Suppliers 3 · CAL01.Investor="Công ty TNHH Calofic". **2 agent return truncated** (implementer-backend + reviewer, gotcha #53) → em main disk/runtime-recover (build/test/sqlcmd/git truth); cicd verdict-FIRST → PASS clean no-truncate. Data-quality catch: MEP col gộp 2 nhóm + divider "THIẾT BỊ" → split đúng 71/4-category. Provenance `scripts/master-import-data.generated.md`. Prev S54 — **IT staff tự reassign ticket (cross-stack authz, HMW-mode ON)**: 1 code commit `ca4b602` → Run #376 PASS ~4m18s, prod-verified. Cho tổ IT (dept Code=="IT") + Admin reassign ItTicket trên CẢ 2 app. BE: NEW `GetAssignableItStaffQuery` capability endpoint `{canReassign,staff}` + `AssignItTicketHandler` authz Admin-OR-dept-IT (Forbidden) + assignee-must-IT (Conflict) + controller `/assign` hạ `[Authorize(Roles=Admin)]`→`[Authorize]` (handler fine-grained). FE: fe-admin+fe-user ItTicketsPage **SHA256-identical** (REVERSE S53 divergence) gate nút by `canReassign`, dropdown từ `/assignable-staff` (không `/users`). Test 203→**216** (+13 authz guard test-before-merge). NO migration (DepartmentId reuse). Bundle admin `DfCfHUE9`→`DmjI8Cmn` / user `_3S0BPJ2`→`YxL_MljK` (cả 2 rotate). 6-agent fan-out (BE∥FE→test→reviewer→cicd) + em main reconcile stray-memory residual (3 agent ghi MEMORY nhầm `src/Backend/.claude` → harvest về canonical). reviewer PASS 0 blocker (role-string "Admin" chain-verified). Task 1 Phase 9 Ops KHÔNG làm (anh dừng). flag: cicd `sys.tables=93` vs STATUS 92 → monthly audit re-ground.) Prev S53 (gotcha #57 EXT Master Mig 47 + P11-D reassign-UI fe-admin + P11-E menu + database-agent verified-runtime: `44b9e54` Run #260 + `dbf6648` Run #261, test→203, bundle→`DfCfHUE9`). 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.)
|
||||
**Last updated:** 2026-06-09 (Session 56 — **Pre-golive verify sweep + golive-harden 4 fix — HMW 2-workflow, prod-verified**: commit `a20cde8` → Run #379 PASS ~4m20s. WF1 `pre-golive-verify` 7-stream song song + adversarial → 6 PASS/1 CONCERN/0 blocker = **GO**; key finds = **ops not code** (prod IT-dept 0 active user → helpdesk inert + S43 LeaveBalance lost-update còn nguyên). WF2 `golive-harden` fix 4: **#3** LeaveBalance lost-update→atomic `ExecuteUpdateAsync`+Serializable tx (NO mig, exactly-once nguyên) · **#5** ItTicket authz Forbidden-trước-NotFound (fail-closed) · **#6** DocxRenderer null-guard (2 warn→0) · **#4** Travel/Vehicle ApproveV2 +4 smoke. Test **216→228**. Bundle FROZEN `4SUwDLD8`/`XdKzt9LL` (BE-only). `sys.tables` re-ground **92→93** (cicd ground-truth, Mig 48 col-only). reviewer stage StructuredOutput-fail→em main đỡ cross-stack review (3 diff clean) + bump Serializable đóng MAJOR. gotcha **#58** NEW (EF read-modify-write lost-update→ExecuteUpdate atomic). **2 ops VPS pending** (gán user phòng IT + `tzutil` UTC+7). FE Phase 2 redesign **deferred** (recon ready). Prev S55 — **Nạp master data thật từ Excel + Project +4 cột (Mig 48), HMW-mode ON**: commit `69cb393` → Run #377 PASS ~4m33s, prod-verified. Anh giao file Excel "HẠNG MỤC CÔNG VIỆC DỰ ÁN" → `/ultra-on "workflow làm xong hết"`. Nạp **62 dự án + 71 hạng mục + 3 NCC** vào Project/WorkItem/Supplier qua `SeedRealMasterDataAsync` (per-code idempotent, **UNGATED** → coexist demo, tự lên prod). **Mig 48 `AddProjectMasterFields`**: Project +4 cột nullable (Year/Investor/Location/Package, NO new table). FE ProjectsPage form +4 input ×2 app SHA256 mirror. Test 216 (compile-fix MasterCatalogFilteredUniqueTests +4 null args, no new test). Bundle admin `DmjI8Cmn`→`B-d6893W`/user `YxL_MljK`→`XdKzt9LL` (cả 2 rotate). Prod verify: Mig 48 applied · Projects spot-6/6 · WorkItems VT/TP/MEP/TB=71 · Suppliers 3 · CAL01.Investor="Công ty TNHH Calofic". **2 agent return truncated** (implementer-backend + reviewer, gotcha #53) → em main disk/runtime-recover (build/test/sqlcmd/git truth); cicd verdict-FIRST → PASS clean no-truncate. Data-quality catch: MEP col gộp 2 nhóm + divider "THIẾT BỊ" → split đúng 71/4-category. Provenance `scripts/master-import-data.generated.md`. Prev S54 — **IT staff tự reassign ticket (cross-stack authz, HMW-mode ON)**: 1 code commit `ca4b602` → Run #376 PASS ~4m18s, prod-verified. Cho tổ IT (dept Code=="IT") + Admin reassign ItTicket trên CẢ 2 app. BE: NEW `GetAssignableItStaffQuery` capability endpoint `{canReassign,staff}` + `AssignItTicketHandler` authz Admin-OR-dept-IT (Forbidden) + assignee-must-IT (Conflict) + controller `/assign` hạ `[Authorize(Roles=Admin)]`→`[Authorize]` (handler fine-grained). FE: fe-admin+fe-user ItTicketsPage **SHA256-identical** (REVERSE S53 divergence) gate nút by `canReassign`, dropdown từ `/assignable-staff` (không `/users`). Test 203→**216** (+13 authz guard test-before-merge). NO migration (DepartmentId reuse). Bundle admin `DfCfHUE9`→`DmjI8Cmn` / user `_3S0BPJ2`→`YxL_MljK` (cả 2 rotate). 6-agent fan-out (BE∥FE→test→reviewer→cicd) + em main reconcile stray-memory residual (3 agent ghi MEMORY nhầm `src/Backend/.claude` → harvest về canonical). reviewer PASS 0 blocker (role-string "Admin" chain-verified). Task 1 Phase 9 Ops KHÔNG làm (anh dừng). flag: cicd `sys.tables=93` vs STATUS 92 → monthly audit re-ground.) Prev S53 (gotcha #57 EXT Master Mig 47 + P11-D reassign-UI fe-admin + P11-E menu + database-agent verified-runtime: `44b9e54` Run #260 + `dbf6648` Run #261, test→203, bundle→`DfCfHUE9`). 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.)
|
||||
|
||||
---
|
||||
|
||||
@ -12,13 +12,13 @@
|
||||
| Metric | Value | Note |
|
||||
|---|---|---|
|
||||
| Migrations | **48** | +S55 Mig 48 `AddProjectMasterFields` (Project +4 cột Year/Investor/Location/Package — AddColumn, no new table) |
|
||||
| SQL tables | **92** | unchanged S55 (Mig 48 = AddColumn, no new table; cicd `sys.tables` ground-truth) |
|
||||
| SQL tables | **93** | re-ground S56 (cicd `sys.tables` ground-truth Run #379 — prior "92" was narrative under-count; Mig 48 col-only added no table) |
|
||||
| Master data (prod) | **real loaded S55** | 62 Projects + 71 WorkItems (Vật tư16/Thầu phụ30/MEP9/Thiết bị16) + 3 Suppliers thật, coexist demo (ungated idempotent seed). Provenance `scripts/master-import-data.generated.md` |
|
||||
| API endpoints | **~253** | +1 S54 `GET /it-tickets/assignable-staff` (capability endpoint); +3 S52 (attendances/report + report/excel + it-tickets/{id}/assign) |
|
||||
| FE pages | **68** | unchanged S54 (ItTicketsPage reassign = in-place 2 app); +1 S52 AttendanceReportPage |
|
||||
| Menu keys | **~56** | +1 S53 `Off_AttendanceReport` (P11-E promote → sidebar leaf under Văn phòng số, order 8) |
|
||||
| Tests | **216 PASS** | 58 Domain + 158 Infra · 0 fail / 0 skip · +13 S54 `ItTicketReassignAuthzTests` (authz capability + Forbidden/Conflict guard, test-before-merge) · +3 S53 Master filtered-unique |
|
||||
| Gotchas | **57** | unchanged S54 (#57 backlog CLOSED S53; #44 silent-403 pattern reinforced — data-driven authz ở handler đúng, capability-flag chống) |
|
||||
| Tests | **228 PASS** | 58 Domain + 170 Infra · 0 fail / 0 skip · +12 S56 golive-harden (LeaveBalance accumulate/exactly-once + Travel/Vehicle ApproveV2 smoke + ItTicket existence-oracle + DocxRenderer) · +13 S54 authz |
|
||||
| Gotchas | **58** | +1 S56 **#58** EF read-modify-write lost-update (`entity.X += n; SaveChanges` đua) → `ExecuteUpdateAsync` atomic increment + Serializable tx. (#57 backlog CLOSED S53) |
|
||||
| User memory | **20** | re-grounded S54 (H1 disk-count — S53 base thật 19 không phải 18); +1 S54 `feedback_agent_cwd_relative_memory_misland` (sub cd subdir → MEMORY Write stray) |
|
||||
| Skills | 6 | 3 domain + 3 ops |
|
||||
| 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) |
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
| Task | Owner | Status |
|
||||
|---|---|---|
|
||||
| _(none — S55 master-data import DONE + prod-verified Run #377 (`69cb393`). Real 62 dự án + 71 hạng mục + 3 NCC live prod. **NEXT (anh pick):** Phase 9 Ops (SMTP/backup/creds/UAT) · doc-drift D1 H1-flagged (`agents/README.md:4` database-agent "🔴 executed-file"→"✅ verified-runtime S53", anh dừng S55) · monthly audit 2026-07-01. Prev S54 task 2 (ItTicket reassign) DONE Run #376. Task 1 (Phase 9 Ops) anh dừng — chưa khởi động. **NEXT (anh pick):** Phase 9 Ops — em đã scope: SMTP email outbound (greenfield code-able, NEW `IEmailSender`+`SmtpEmailSender` config-driven + wire NotificationService) · SQL backup register (`scripts/backup-sql.ps1` READY → em đưa lệnh schtasks, anh chạy VPS) · rotate creds + UAT (anh-infra) · monthly drift audit **2026-07-01**.)_ | 👤 | ✅ |
|
||||
| _(none — S56 pre-golive verify + golive-harden DONE, prod-verified Run #379 (`a20cde8`). Code golive-ready: 4 fix shipped, 228 test, 0 blocker. **🔴 2 ops VPS — của anh:** (1) gán ≥1 user thật vào phòng IT (`UPDATE Users SET DepartmentId='65CC6307-BF3A-4F42-9B83-18FE187F46BB' WHERE Email='<email>@solutions.com.vn'` — helpdesk đang inert vì 0 active user) · (2) `ssh vietreport-vps "tzutil /g"` → confirm `SE Asia Standard Time` (codegen mã đơn dùng năm giờ-server). **NEXT (anh pick):** FE redesign Phase 2 (recon ready: Drawer+bậc-thang Master/Office/System; scope Budget+designers chờ chốt) · Phase 9 Ops (SMTP/backup/creds/UAT) · monthly audit 2026-07-01.)_ | 👤 | ✅ |
|
||||
|
||||
**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).
|
||||
|
||||
@ -45,6 +45,15 @@
|
||||
|
||||
## ✅ Recently Done (newest on top — 3 session; cũ hơn → session logs)
|
||||
|
||||
### S56 (2026-06-09) — ✅ Pre-golive verify sweep + golive-harden 4 fix — HMW 2-workflow, prod-verified
|
||||
- **2 Workflow fan-out + 1 code commit `a20cde8` → Gitea Run #379 PASS ~4m20s, prod-verified.** Anh: `/session-start` → hỏi NAMGROUP UI density-first → "Tiếp Phase 2 redesign" (dismiss scope → defer) → "kiểm tra lại tính năng + master data, sắp golive" + `/ultra-on` → "fix hết workflow luôn" → `/session-end`.
|
||||
- **WF1 `pre-golive-verify`** (7 stream song song → adversarial-per-issue): prod-truth(🟩cicd) · schema(🔵database-agent) · 4× logic(🟦investigator) · authz-curl(🟥reviewer). **6/7 PASS · 1 CONCERN(non-blocker) · 0 blocker · 8 issue (adversarial confirm 6 real, refute 2 false-pos).** Verdict **GO**. Insight: phát hiện đáng giá nhất = **ops/data, không phải bug code** — prod phòng IT (CNTT) tồn tại nhưng **0 active user** → ItTicket auto-assign/reassign/SLA-notify đều inert (chỉ live-curl+sqlcmd thấy, test xanh không bắt). + S43 LeaveBalance lost-update còn nguyên + master-data idempotency PROVEN.
|
||||
- **WF2 `golive-harden`** (Design→Build→Test→Review∥): **#3** LeaveBalance lost-update → database-agent design **atomic `ExecuteUpdateAsync`+Serializable tx** (NO migration, exactly-once `Status!=DaGuiDuyet:296` nguyên, server-side `UPDATE SET UsedDays=UsedDays+n` race-free) · **#5** ItTicket `AssignItTicketHandler` authz Forbidden-trước-NotFound (fail-closed, hết existence-oracle) · **#6** DocxRenderer null-guard MainDocumentPart+Document (CS8602 2→0) · **#4** Travel/Vehicle ApproveV2 +4 smoke test (trước đó 0 coverage).
|
||||
- **Test 216→228** (+12). Build 0 warn. Bundle FROZEN admin `4SUwDLD8`/user `XdKzt9LL` (BE-only đúng). Mig 48 unchanged. `sys.tables` re-ground **92→93** (cicd ground-truth — Mig 48 col-only, prior under-count).
|
||||
- **🟥 reviewer stage StructuredOutput-FAIL** → em main đỡ cross-stack review (đọc 3 production diff = clean). **🔵 database-agent review PASS** nêu 1 MAJOR (tx READ COMMITTED vs convention Serializable + rare auto-create-race) → **em main bump `IsolationLevel.Serializable`** đóng nốt + align convention. **🟩 cicd Run #379 PASS:** test 228 · health 200 ×3 · bundle frozen verified 3× · endpoint 401 (control 404 chứng minh auth thật) · Mig 48 top.
|
||||
- ⚠️ **Lessons:** (1) **workflow-agent StructuredOutput-fail = class mới** của agent-return-unreliable → em main đỡ qua git-diff/disk truth (extends `feedback_agent_kill_recovery`). (2) workflow-agent self-write MEMORY (G-015 residual — sub giữ Write dù MODE-A) → em main verify sane + bundle harvest commit. (3) **gotcha #58** NEW. (4) HMW full-cycle: verify→fix→review→em-main-đỡ→re-verify→ship→cicd-PASS — adversarial tách-vai bắt lỗ phụ build+test-xanh không thấy.
|
||||
- **🔴 NEXT:** 2 ops VPS (gán user IT + tzutil) · FE Phase 2 (recon ready) · Phase 9 Ops · monthly audit 2026-07-01. → session log `2026-06-09-S56-pre-golive-verify-harden.md`.
|
||||
|
||||
### S55 cont. (2026-06-09) — ✅ Phase 1 FE redesign fe-admin (density-first, giữ brand) — HMW-mode ON, prod-verified
|
||||
- **Commit `7feb53e` → Gitea Run #378 PASS ~4m24s, prod-verified.** Anh: `/check-email NAMGROUP` (nhận bộ quy ước UI density-first) → "thiết kế lại giao diện cho đẹp hơn, tham khảo NAMGROUP, cho designer làm, workflow plan + làm luôn".
|
||||
- **🩷 frontend-designer** redesign **14 file fe-admin** (Phase 1 foundation): `index.css` density heading-ladder + `.label-eyebrow` util (drop font-bold) · 6 UI primitive (Button/Input/Label/Select/Textarea/Dialog → `text-xs font-semibold`, `py-1.5` ≤36px, `rounded-lg`, brand focus-ring) · 6 shell (DataTable sticky-thead+RowActions / Layout brand-rail / TopBar / PageHeader / PhaseBadge / EmptyState) · **DashboardPage** flagship (KPI cards + brand-50 icon chips + uppercase labels + accent bars).
|
||||
@ -70,7 +79,7 @@
|
||||
- **🟥 reviewer PASS** 0 blocker/0 major/1 minor: điểm chí mạng **role-string "Admin" chain-verified real** (`AppRoles.Admin`→`SeedRoles Role.Name`→`JwtTokenService Claim(ClaimTypes.Role)`→`cu.Roles` — decoy "QTV" chỉ ShortName, không vào JWT). Fail-closed verified, defense-in-depth nguyên.
|
||||
- **🟩 cicd Run #376:** test 216 · bundle admin `DfCfHUE9`→`DmjI8Cmn`/user `_3S0BPJ2`→`YxL_MljK` (cả 2 rotate) · smoke health 200 + /assignable-staff 401 + /assign 401(body) · Mig giữ 47. Note: 411-bodyless-PUT = IIS Content-Length pre-`[Authorize]` (không phải routing miss).
|
||||
- ⚠️ **Residual caught + fixed (em main single-writer):** 3 agent (BE/FE/test) ghi MEMORY nhầm `src/Backend/.claude/` (cwd-relative Write khi cd subdir) → em main git-status scan bắt stray + reconcile 2 pattern file về canonical + APPEND S54 delta vào 3 canonical MEMORY (harvest B2/B3). → memory `feedback_agent_cwd_relative_memory_misland`.
|
||||
- 📌 **flag monthly audit 2026-07-01:** cicd đo `sys.tables=93` vs STATUS 92 (1-count drift, pre-existing — session này no new table).
|
||||
- 📌 **flag monthly audit 2026-07-01:** cicd đo `sys.tables=93` vs STATUS 92 → ✅ **RECONCILED S56** (93 authoritative, cicd ground-truth Run #379; Mig 48 col-only added no table — STATUS Current State updated 92→93).
|
||||
- → session log `2026-06-08-S54-it-ticket-reassign-cross-stack.md`.
|
||||
|
||||
### 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)
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
# Session 56 (2026-06-09) — Pre-golive verify sweep + golive-harden 4 fix
|
||||
|
||||
> HMW-mode ON · 2 Workflow fan-out + 1 code commit `a20cde8` → Gitea Run #379 PASS ~4m20s, prod-verified. Code golive-ready.
|
||||
|
||||
## Arc
|
||||
`/session-start` (HMW-ON marker) → anh hỏi "đọc cách design UIUX NAMGROUP chưa?" (đọc bộ quy ước density-first qua `broadcasts/inbox/namgroup/`) → "Tiếp Phase 2 redesign → fan-out" + "trừ PE/Contract, office các thứ làm giống" → recon (investigator-codebase) → AskUserQuestion scope **DISMISSED** ("wait for next instruction") → pivot: **"kiểm tra lại tính năng + master data, sắp golive"** + `/ultra-on` → **WF1 pre-golive-verify** → "fix hết workflow luôn" → **WF2 golive-harden** → em main review + Serializable bump → ship → cicd PASS → `--resume` → `/session-end`.
|
||||
|
||||
## WF1 — `pre-golive-verify` (custom Workflow tool, 7 stream + adversarial)
|
||||
7 verify stream song song → mỗi issue qua reviewer-skeptic refute-test → em main synthesize. **15 agent · 1.24M token · ~7m.**
|
||||
- **prod-truth** (cicd-monitor) PASS · **schema-integrity** (database-agent) CONCERN (S43 lost-update) · **leave-att-codegen / it-ticket / approvev2-catalogs / master-data-wiring** (investigator-codebase ×4) PASS · **authz-curl** (reviewer) PASS.
|
||||
- **6/7 PASS · 1 CONCERN(non-blocker) · 0 blocker · 8 issue → adversarial confirm 6 real, refute 2 false-pos.** Verdict **GO**.
|
||||
- **Key insight:** phát hiện đáng giá nhất = **ops/data, KHÔNG phải bug code** — prod phòng IT (CNTT) tồn tại nhưng **0 active user** → ItTicket auto-assign/reassign/SLA-notify inert (test xanh + deploy xanh KHÔNG bắt được "prod chưa provision"; chỉ live-curl + ssh sqlcmd thấy). + master-data idempotency PROVEN (DbInitializer re-run → counts identical).
|
||||
- **Containment:** 0 file-write từ 15 agent (MODE-A return-delta), chunk-count 2420 unchanged. P3 harvest: em main APPEND 4 agent-memory.
|
||||
|
||||
## WF2 — `golive-harden` (Design→Build→Test→Review∥, 5 agent)
|
||||
- **Design (database-agent):** chốt **atomic `ExecuteUpdateAsync` + explicit Serializable tx** cho #3 (ưu tiên NO-migration; reject RowVersion+retry vì re-run MediatR handler awkward + reject plain-Serializable-only).
|
||||
- **Build (implementer-backend):** #3 (LeaveOtApprovalFeatures terminal branch) + #5 (ItTicket authz reorder) + #6 (DocxRenderer null-guard). NO migration.
|
||||
- **Test (test-specialist):** +12 → 228. 4 LeaveBalanceTests update `.AsNoTracking()` (ExecuteUpdate bypass tracker = stale-tracked-read, không regression).
|
||||
- **Review (reviewer ∥ database-agent):** reviewer stage **StructuredOutput-FAIL** (kết thúc không gọi StructuredOutput) → **em main đỡ cross-stack review** (đọc 3 production diff = clean). database-agent **PASS** + 1 MAJOR: tx READ COMMITTED vs convention Serializable + rare auto-create-race → **em main bump `IsolationLevel.Serializable`** (1 dòng, đóng nốt + align). Re-verify build 0-warn + test 228.
|
||||
|
||||
## 4 fix
|
||||
| # | Issue | Fix | File |
|
||||
|---|---|---|---|
|
||||
| #3 | LeaveBalance lost-update (S43/DB11) | atomic `ExecuteUpdateAsync(UsedDays=UsedDays+n)` + explicit Serializable tx (NO mig, exactly-once `Status!=DaGuiDuyet:296` nguyên) | `LeaveOtApprovalFeatures.cs:354-405` |
|
||||
| #5 | ItTicket existence-oracle | authz Forbidden-trước-NotFound (fail-closed, non-IT nhận 403 cho mọi ticketId) | `WorkflowAppsFeatures.cs:493-510` |
|
||||
| #6 | DocxRenderer CS8602 (2 warn) | null-guard MainDocumentPart + Document, deref qua local non-null | `DocxRenderer.cs:27-44` |
|
||||
| #4 | Travel/Vehicle ApproveV2 0 test | +4 smoke (Submit→Approve terminal + outsider-Forbidden) | `WorkflowAppApproveV2Tests.cs` + NEW `Forms/DocxRendererTests.cs` |
|
||||
|
||||
## Ship + verify
|
||||
- Commit `a20cde8` (13 file: 3 BE + 4 test + 6 agent-memory) → push main `bef5825..a20cde8`.
|
||||
- 🟩 cicd Run #379 (run_number 265) PASS ~4m20s: test **228** · bundle FROZEN admin `4SUwDLD8`/user `XdKzt9LL` (BE-only verified 3×) · Mig 48 unchanged · `sys.tables`=**93** · health 200 ×3 · endpoint 401 (control route 404 chứng minh auth thật).
|
||||
|
||||
## Lessons
|
||||
- **Workflow-agent StructuredOutput-fail = class mới** của agent-return-unreliable → em main đỡ qua git-diff/disk truth (extends `feedback_agent_kill_recovery`: external-kill / truncate / **StructuredOutput-non-emission** đều recover qua git/disk/prod, KHÔNG agent return-message).
|
||||
- **Workflow-agent self-write MEMORY (G-015 residual):** sub giữ Write dù brief MODE-A "return-delta-only" → implementer-backend/test-specialist/database-agent tự ghi agent-memory mình (placement-correct, không stray) → em main VERIFY sane + bundle harvest commit. H2 GATE bắt **Fidelity gap**: em main định bump-correction nhưng Edit fail (file-not-Read) → skip → 2 entry stale "READ COMMITTED" contradicting shipped Serializable → session-end append correction.
|
||||
- **Adversarial tách-vai có ROI:** database-agent review bắt lỗ phụ (auto-create race + lệch convention isolation) mà build+test-xanh KHÔNG thấy.
|
||||
- **gotcha #58** NEW: EF read-modify-write lost-update → ExecuteUpdate atomic + Serializable.
|
||||
- **sys.tables 92↔93 reconciled:** 93 authoritative (cicd ground-truth Run #379); narrative "92" = under-count cũ.
|
||||
|
||||
## Deferred
|
||||
- FE redesign Phase 2 (recon ready, scope Budget+designers chờ chốt).
|
||||
- 2 ops VPS (gán user IT + tzutil UTC+7) — của anh.
|
||||
- root CLAUDE.md count-sweep → monthly audit 2026-07-01.
|
||||
- L1 soft-cap trim (cicd/investigator/reviewer ≈30KB) → next curate.
|
||||
@ -1060,6 +1060,18 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
|
||||
|
||||
---
|
||||
|
||||
### 58. EF read-modify-write lost-update — dùng `ExecuteUpdateAsync` atomic + Serializable tx (Session 56)
|
||||
|
||||
**Triệu chứng:** Handler trừ/cộng counter kiểu đọc-sửa-ghi in-memory: `entity.X += n; await SaveChangesAsync()`. 2 request đồng thời (vd 2 lượt duyệt cuối 1 đơn nghỉ, hoặc admin + approver bấm cùng lúc) cùng đọc `X` cũ → cùng `+= n` → lần ghi sau đè lần trước → **mất 1 update** (quota lệch). Im lặng, không exception, không corruption — chỉ sai số. Reachable: `LeaveBalance.UsedDays` trừ phép (S43 gap, fixed S56).
|
||||
|
||||
**Root cause:** read-modify-write KHÔNG atomic dưới READ COMMITTED (default). EF tải value vào RAM, tính ở app, ghi lại — cửa sổ race giữa SELECT và UPDATE.
|
||||
|
||||
**Fix (proven S56, NO migration):** atomic server-side increment — `db.Set.Where(pred).ExecuteUpdateAsync(s => s.SetProperty(b => b.X, b => b.X + n), ct)`. EF Core 7+ phát `UPDATE SET X = X + @n` 1 lệnh atomic dưới row-lock → 2 increment đồng thời serialize, zero lost-update, BẤT KỂ isolation. ⚠️ `ExecuteUpdate` **bypass change tracker** → tracked instance giữ value CŨ; KHÔNG đọc lại entity đó (dùng `.AsNoTracking()` re-query / `ChangeTracker.Clear()`), KHÔNG thêm `entity.X += n` (double-count). Bọc trong explicit `BeginTransactionAsync(IsolationLevel.Serializable, ct)` để (a) atomic với các write khác cùng handler, (b) serialize nhánh auto-create row mới (2 insert cùng key). Convention codebase = Serializable (codegen `WorkflowAppCodeGen:34`, ProposalFeatures, TravelVehicle).
|
||||
|
||||
**References:** `LeaveOtApprovalFeatures.cs:354-405` (ApproveLeaveRequestHandler terminal DaDuyet) · `LeaveBalanceTests.cs` (TwoSeparateRequests accumulate test) · database-agent design S56 (DB11) · surfaced bởi `pre-golive-verify` workflow.
|
||||
|
||||
---
|
||||
|
||||
## Checklist debug bug mới
|
||||
|
||||
1. Build pass không? → fail → check using + package version compat
|
||||
|
||||
Reference in New Issue
Block a user