@ -50,7 +50,7 @@ Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, ve
- ** Tests baseline: ** **181 PASS** ( S45 Run #368 sha 0c5a014 ; Domain 58 + Infra 123 = +27 HRM coverage gaps: HrmConfigHolidayTests + EmployeeSatelliteTests + AuthorizePolicyRegressionTests-ext vs prev 154 ). 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 .
- ** Tests baseline: ** **181 PASS** ( S45 Run #368 sha 0c5a014 ; Domain 58 + Infra 123 = +27 HRM coverage gaps: HrmConfigHolidayTests + EmployeeSatelliteTests + AuthorizePolicyRegressionTests-ext vs prev 154 ). 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 43 `20260601064128_FilterHolidayUniqueIndexByIsDeleted`** ( S45 ; index-only change , prod tables stay 90-by-sys . tables / 91-by-doc — NO new table ). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/` . Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5` . ⚠ ️ Table-count drift: `sys.tables` count = 90 ( verified S42 #364 + S45 #368 ), CLAUDE . md narrative = 91 — counting-convention diff , NOT missing table . Don ' t FAIL on 90 .
- ** Mig latest repo: ** **Mig 43 `20260601064128_FilterHolidayUniqueIndexByIsDeleted`** ( S45 ; index-only change , prod tables stay 90-by-sys . tables / 91-by-doc — NO new table ). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/` . Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5` . ⚠ ️ Table-count drift: `sys.tables` count = 90 ( verified S42 #364 + S45 #368 ), CLAUDE . md narrative = 91 — counting-convention diff , NOT missing table . Don ' t FAIL on 90 .
- ** Bearer: ** admin `admin@solutions.com.vn/Admin@123456` ( full ) · UAT `nv.test@solutions.com.vn/TestUser@123456` ( Drafter CCM , gotcha #44 check )
- ** 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 S42 : ** admin `Krjvg_3j ` · user `6sNStgxa ` ( Run #367 sha 82d7fcf ). Prev admin `BU8FTBRi ` · user `tepE4jvR ` ( #366/ffb2062 ). Bundle size ~ 800KB / 750KB gz .
- ** Bundle hash live S48 : ** admin `DPPTx2Kw ` · user `CjoUEsoV ` ( Run #369 sha 350b2bf , login subtitle a11y ). Prev admin `Krjvg_3j ` · user `6sNStgxa ` ( #368/0c5a014 — unchanged BE-only ). Bundle size ~ 800KB / 750KB gz .
- ** 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 .
- ** 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)
## 🔑 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)
## 📅 Recent runs (FIFO — older → archive/git)
- **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-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 #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 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]` .