[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:
pqhuy1987
2026-06-09 20:20:51 +07:00
parent a20cde89fb
commit a62e797332
11 changed files with 108 additions and 20 deletions

View File

@ -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.
- **SSHPS quoting (S42 lesson):** nested bashsshpowershell 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 9293 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.