From f36aab893401fd5823354d5406a75e49912dc8e4 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 17 Jun 2026 23:52:51 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Docs:=20adopt=20Harness-9=20?= =?UTF-8?q?=E2=80=94=20L2=20archive=20dark-matter=20recovery=20(4=20sub)?= =?UTF-8?q?=20+=20adap=202-workflow=20mandate=20(S70)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3-stage Workflow run-id evidence: investigate wf_be952f3c-97f / implement wf_a58e0d15-beb / audit wf_9520d8cd-4fe. PART 1 (L2 recovery): 4 over-cap sub (cicd-monitor/investigator-codebase/reviewer/implementer-backend) curated L1->L2 byte-exact + archive/_INDEX.md (substring sha-keyed pointers, no line-hints) + .gist.md (4-field distill, distill-gen:1, verbatim frozen). All 4 MEMORY.md now < 25KB auto-inject cap (closes P1 curate-debt). ~240KB archive no longer RAG-dark. 0-byte-loss git+sha verified (Stage C audit + em-main self-gate on 2 reviewer StructuredOutput no-returns). Read-side gap fixed (MEMORY.md L5 header -> _INDEX). + memory-budget.json (seed-by-measure) + scripts/measure-agent-memory.ps1 + .ragignore guard. PART 2/3 (process mandate): every adap = 2 separate workflows (implement + review) + report with run-id; short-but-needs-confirm still requires review. Codified in .claude/commands/adap-apply.md + agents/README.md (Upgrade S70) + session-start.md (§2.1.2 budget-audit, pending-restart). adap-report + email-back to AI_INFRA (body-hash 7c07b716e775). Co-Authored-By: Claude Opus 4.8 --- .claude/agent-memory/cicd-monitor/MEMORY.md | 12 +- .../cicd-monitor/archive/2026-05.gist.md | 59 ++++++++++ .../cicd-monitor/archive/2026-06.gist.md | 65 +++++++++++ .../cicd-monitor/archive/2026-06.md | 13 +++ .../cicd-monitor/archive/_INDEX.md | 106 ++++++++++++++++++ .../implementer-backend/MEMORY.md | 20 +--- .../archive/2026-05.gist.md | 51 +++++++++ .../archive/2026-06.gist.md | 53 +++++++++ .../implementer-backend/archive/2026-06.md | 29 +++++ .../implementer-backend/archive/_INDEX.md | 66 +++++++++++ .../investigator-codebase/MEMORY.md | 27 +---- .../archive/2026-05-q4.md | 13 +++ .../archive/2026-05.gist.md | 47 ++++++++ .../archive/2026-06.gist.md | 41 +++++++ .../investigator-codebase/archive/2026-06.md | 25 +++++ .../investigator-codebase/archive/_INDEX.md | 56 +++++++++ .claude/agent-memory/memory-budget.json | 43 +++++++ .claude/agent-memory/reviewer/MEMORY.md | 21 +--- .../reviewer/archive/2026-05.gist.md | 32 ++++++ .../reviewer/archive/2026-06.gist.md | 26 +++++ .../agent-memory/reviewer/archive/2026-06.md | 29 +++++ .../agent-memory/reviewer/archive/_INDEX.md | 31 +++++ .claude/agents/README.md | 3 +- .claude/commands/adap-apply.md | 11 ++ .claude/commands/session-start.md | 7 ++ .ragignore | 11 ++ broadcasts/_index.md | 1 + ...7-se-to-ai_infra-harness-9-adopt-report.md | 42 +++++++ ...harness-9-l2-recovery-and-adap-workflow.md | 92 +++++++++++++++ scripts/measure-agent-memory.ps1 | 41 +++++++ 30 files changed, 998 insertions(+), 75 deletions(-) create mode 100644 .claude/agent-memory/cicd-monitor/archive/2026-05.gist.md create mode 100644 .claude/agent-memory/cicd-monitor/archive/2026-06.gist.md create mode 100644 .claude/agent-memory/cicd-monitor/archive/_INDEX.md create mode 100644 .claude/agent-memory/implementer-backend/archive/2026-05.gist.md create mode 100644 .claude/agent-memory/implementer-backend/archive/2026-06.gist.md create mode 100644 .claude/agent-memory/implementer-backend/archive/2026-06.md create mode 100644 .claude/agent-memory/implementer-backend/archive/_INDEX.md create mode 100644 .claude/agent-memory/investigator-codebase/archive/2026-05-q4.md create mode 100644 .claude/agent-memory/investigator-codebase/archive/2026-05.gist.md create mode 100644 .claude/agent-memory/investigator-codebase/archive/2026-06.gist.md create mode 100644 .claude/agent-memory/investigator-codebase/archive/_INDEX.md create mode 100644 .claude/agent-memory/memory-budget.json create mode 100644 .claude/agent-memory/reviewer/archive/2026-05.gist.md create mode 100644 .claude/agent-memory/reviewer/archive/2026-06.gist.md create mode 100644 .claude/agent-memory/reviewer/archive/2026-06.md create mode 100644 .claude/agent-memory/reviewer/archive/_INDEX.md create mode 100644 .ragignore create mode 100644 broadcasts/outbox/ai_infra/2026-06-17-se-to-ai_infra-harness-9-adopt-report.md create mode 100644 docs/governance/adap-reports/2026-06-17-Governance-harness-9-l2-recovery-and-adap-workflow.md create mode 100644 scripts/measure-agent-memory.ps1 diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 8d6036b..2c0e6d2 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -2,7 +2,7 @@ > **Persistent diary cross-session.** Auto-injected first ~200 lines at spawn (L1 HOT). > Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 `archive/` on-demand · L3 RAG `search_memory` just-in-time. Keep entry ≤ 1.5K chars (gotcha #53). -> Full verbatim run history pre-S40 → git `d2f52ba` + `archive/2026-05-{runs,q2,q3,q4}.md`. +> Full verbatim run history pre-S40 → git `d2f52ba` + `archive/2026-05-{runs,q2,q3,q4}.md` + `archive/2026-06.md`. 🗺️ **Lookup map (Harness-9 S70): `archive/_INDEX.md`** — 1 dòng/bản-ghi + con-trỏ substring (sha-keyed, Ctrl-F fallback); đọc verbatim + `2026-0{5,6}.gist.md` (nén 4-field) theo nhu cầu. --- @@ -71,19 +71,9 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code / - **2026-06-17 S71 Run #308 (id422) sha=`ebd7e1c` PASS ~4m41s (FULL-STACK: BE Mig 53 + ApproveV2 CCM-finalize-by-threshold + endpoint PUT /urgent + FE 2-app PE cờ-gấp UI + designer ngưỡng. PE "cờ gấp PRO/CCM + CCM duyệt-final theo ngưỡng giá trị" anh Kiệt FDC. 27 files: BE 9 {Controller +SSetUrgent, App PurchaseEvaluationUrgentFeatures.cs NEW, PurchaseEvaluationFeatures, CreateContractFromEvaluation, AwV2AdminFeatures, Dtos, Domain PE+AW, Config} + 3 Mig-file + Service PurchaseEvaluationWorkflowService + FE 6 {PeDetailTabs+ListPage+types ×2-app + ApprovalWorkflowsV2Page admin-only} + 2 test {PeUrgentToggleAuthzTests, PeCcmThresholdFinalizeTests} + 3 agent-memory .md. deploy 12/12 session after #297–#307 all PASS):** Push parent `1f8947e..ebd7e1c` (1 commit). `git diff --name-only 1f8947e ebd7e1c -- '*Persistence/Migrations*'` = 3 files (Mig 53 .cs/.Designer.cs/ModelSnapshot.cs) — REAL EF migration this time (contrast S69b/S70 which had ZERO). `.cs/.tsx` non-ignored → full pipeline RAN. GITEA_TOKEN present (PS-scope) → authed Gitea API; PROD_DB_PW empty → DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (`vrapp`/`buKL3TGBkD0wDDbYVw65QeX9`, path `C:\inetpub\solution-erp\api`). Run IN-PROGRESS at spawn (running 13:27:58, 1st-seen updated 13:28:54) — pre-deploy NOT snapshotted as baseline (anti#3: prev-live = #306/#307 spec admin `Wt54PHYl`/user `B99fMU6X`). Poll-loop iter6 status=success (created 13:27:58 → success-update 13:32:39 ≈4m41s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test **306** baseline (45D+261I; +14 PeUrgentToggleAuthz+PeCcmThresholdFinalize) passed; `conclusion` empty — `tasks` terminal=`status:success` không populate conclusion, trust success; 306 INFERRED gate-passes-pre-build invariant NOT log-confirmed numeric). **★ BUNDLE BOTH ROTATE (FE-both-app PE-UI ⟹ both MUST rotate; verified AFTER status=success + re-confirm STABLE 2nd-fetch identical no-transient — anti#3): admin ROTATE `Wt54PHYl→BgNCjwsG`** ✓ **+ user ROTATE `B99fMU6X→CBvh0vtf`** ✓. Asset reachable 200: admin js 1,592,331b + user js 1,496,984b (not white). Smoke **4×200** health: api `/health/ready`+`/health/live` + admin root + eoffice root. **★ ENDPOINT /urgent PROBE: `PUT /api/purchase-evaluations/{guid}/urgent` unauth → 401 (NOT 404)** ✓ — route EXISTS + class-level `[Authorize]` any-auth enforced (controller `[HttpPut("{id:guid}/urgent")] SetUrgent → SetPurchaseEvaluationUrgentCommand`; behavioral PRO/CCM-only authz = unit-test PeUrgentToggleAuthzTests, not probed). PE list unauth → 401 ✓ (reachable, gated). **🔑★ MIG 53 VERIFIED APPLIED PROD (sqlcmd over SSH, ground-truth not inference): prod `__EFMigrationsHistory` top5 = `20260617060207_AddPeUrgentAndCeoApprovalThreshold`(53)→`AddHoSoLinkToPurchaseEvaluation`(52)→`AddDepartmentParentId`(51)→`ReplaceBudgetModuleWithPeWorkItemBudgets`(50)→`AddWorkItemToPurchaseEvaluation`(49) == repo HEAD ✓ (DbInitializer auto-migrate-on-boot ran; app pool recycled w/ new binary). 3 NEW COLUMNS confirmed via sys.columns: `PurchaseEvaluations.IsUrgentByCcm`=bit is_nullable=0 ✓ + `PurchaseEvaluations.IsUrgentByPro`=bit is_nullable=0 ✓ + `ApprovalWorkflows.CeoApprovalThreshold`=decimal is_nullable=1 precision=18 scale=2 ✓ — ALL match Mig 53 Up() spec exactly. sys.tables=88 (excl mighist) UNCHANGED — 3 AddColumn no new table ✓.** 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only; migration-apply = expected boot-side-effect of deploy). Behavioral CCM-finalize-by-threshold + cờ-gấp UI render = anh Kiệt UAT (em confirm Mig applied + columns + endpoint route + bundles shipped per spec). **LESSON: full-stack PE commit w/ REAL Mig = verify ALL 4 axes: (1) BE+FE both-bundle ROTATE (FE 2-app), (2) Mig history-top advance + 3 columns sys.columns ground-truth (NOT just "schema looks right" — query each col type/nullable/precision vs Up() spec), (3) sys.tables stays 88 (AddColumn-only ⟹ no table delta; rotate-to-89 would = unexpected CREATE TABLE leak), (4) new endpoint 401-not-404 unauth probe (route-exists proof; authz-logic = unit-test domain not curl). Mig-applied-on-prod proof = `__EFMigrationsHistory` top == repo-HEAD (DbInitializer MigrateAsync boot-hook) — if top stuck at Mig 52 ⟹ app pool didn't recycle / migrate-on-boot failed ⟹ FAIL even if status=success+bundle-rotated (deploy shipped binary but DB-side incomplete). Distinguishes this run from S69b/S70 (zero-migration BE/FE — those had NO history-advance EXPECTED). TOOLING: Bash=POSIX → write PS to `$LOCALAPPDATA/Temp/x.ps1` + `powershell.exe -NoProfile -File "C:\Users\pqhuy\AppData\Local\Temp\x.ps1"` (⚠️ bash→Windows path auto-convert mangles `/tmp/` → use `$LOCALAPPDATA/Temp` explicit Windows path); Invoke-RestMethod for Gitea `tasks` (match `head_sha -eq sha`, poll-loop in PS not bash — bash jq absent/python3 broken); SSH→PS base64 `-EncodedCommand` (UTF-16LE Unicode) for BOTH appsettings-read AND sqlcmd; sqlcmd pw-literal via PS here-string `$P=` var inside encoded payload + query-string built w/ `[char]39` for embedded single-quotes (object-name literals); `-W -h -1` no-header + `-s '|'` pipe-delim; read DB pw from prod appsettings when `$PROD_DB_PASSWORD` empty. NEVER fixed code (READ-only).** Tag `[s71, run308, pass, full-stack-pe-urgent-ccm-threshold, mig53-AddPeUrgentAndCeoApprovalThreshold-VERIFIED-APPLIED-PROD, 3-cols-sys-columns-confirmed-IsUrgentByPro-IsUrgentByCcm-bit0-CeoApprovalThreshold-decimal18-2-null, history-top-advance-53, tables88-unchanged-addcolumn-only, bundle-BOTH-rotate-BgNCjwsG-CBvh0vtf, asset-200-reachable, endpoint-urgent-401-not-404-route-exists, pe-list-401-gated, health-4x200, test306-inferred, deploy12of12, no-regression, behavioral-ccm-finalize-anh-kiet-uat]`. - **2026-06-17 S69b Run #307 (run_number 307, id421) sha=`1f8947e` PASS ~4m33s (BE-ONLY GOLIVE security hướng-ra-prod "Văn phòng số" public Read+Create mọi role — NEW `SeedAllRolesOfficeModulePermissionsAsync` DbInitializer.cs chạy lúc API boot SAU `RevokeTemporarilyHiddenModulesAsync` để THẮNG revoke (mirror S65 HRM/Hồ-sơ-NS pattern), allow-list 16 Office key grant CanRead+CanCreate=true upgrade-only (row tồn tại→nâng, chưa có→tạo R+C=true U/D=false, KHÔNG hạ KHÔNG đụng Update/Delete); +6 test `OfficeModulePermissionSeedTests.cs` (286→292). KHÔNG FE KHÔNG migration. deploy 11/11 session after #297–#306 all PASS):** Push `c556f6c..1f8947e` (1 commit, 5 files: DbInitializer.cs + 1 test + 3 agent-memory `.md` cicd/reviewer/test-spec). Diff `git diff --name-only c556f6c 1f8947e -- '*Migrations*' '*Persistence/Migrations*'` = EMPTY ✓ (DbInitializer.cs ở `Persistence/` NOT `Persistence/Migrations/` — seed runtime-idempotent NOT EF-migration). `.cs` non-ignored → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW env empty trong bash-scope (env:GITEA_TOKEN có ở PS-scope; PROD_DB_PW thật rỗng) → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp` len24). Run IN-PROGRESS at spawn (running 10:33:40→10:38) — pre-deploy bundle baseline captured BEFORE poll-loop (anti#3): admin `Wt54PHYl` + user `B99fMU6X` — both == S70 #306 spec baseline (still live, deploy not yet shipped). Polled iter5 status=success (created 10:33:40 → success-update 10:38:13 ≈4m33s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test **292** baseline (45D+247I; +6 OfficeModulePermissionSeedTests) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` không populate `conclusion`, trust success; 292 INFERRED gate-passes-pre-build invariant NOT log-confirmed numeric). **★ BUNDLE BOTH FROZEN (BE-only ⟹ both MUST stay = pre-deploy live; verified AFTER status=success): admin `Wt54PHYl` UNCHANGED** ✓ **+ user `B99fMU6X` UNCHANGED** ✓ (no FE touch → no content-hash rotation; rotate-w/o-FE-change = anomaly, did NOT happen). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top5 = `AddHoSoLinkToPurchaseEvaluation`(Mig52)→`AddDepartmentParentId`(51)→`ReplaceBudgetModuleWithPeWorkItemBudgets`(50)→`AddWorkItemToPurchaseEvaluation`(49)→`AddProjectMasterFields`(48) == repo HEAD GIỮ NGUYÊN ✓. **sys.tables=88** (sqlcmd COUNT excl mighist — unchanged, seed inserts/updates ROWS not tables). **🔑★ GOLIVE DB VERIFIED THỰC SỰ ÁP PROD (seed chạy lúc boot ⟹ proof = prod Permissions, NOT chỉ binary-shipped): sqlcmd `Roles` (13 total: Accounting/Admin/AuthorizedSigner/CatalogManager/CostControl/DeptManager/Director/Drafter/Equipment/Finance/HrAdmin/Procurement/ProjectManager). (1) ALLOW-LIST 16/16 R=1∧C=1 cho Drafter (non-admin demo-role) — Off,Off_Dashboard,Off_DanhBa,Off_PhongHop(+View+Book),Off_DeXuat(+List+Create+Inbox),Off_DonTu(+Leave+Ot+Travel),Off_DatXe,Off_ItTicket ✓. (2) CROSS-ROLE: MỌI 13 role count=16 R∧C (golive ÁP TOÀN BỘ không Drafter-fluke) ✓. (3) EXCLUDED-3 (Off_PhongHop_Manage/Off_AttendanceReport/Off_ChamCong) Drafter R=0∧C=0 ✓ + LEAK-check: CHỈ Admin có R=1 trên 3 key này (0 non-admin leak) ✓. (4) HRM/Personal Drafter KHÔNG mở: Hrm_Dashboard=0, all 7 Hrm_Config*=0, Personal=0 ✓; Hrm=1 + Hrm_HoSo=1 (S65 public-read GIỮ NGUYÊN không đổi) ✓. (5) Admin KHÔNG hạ: full Office incl 3 excluded all R=1∧C=1 ✓.** ⟹ boot-time seed CHẠY THẬT trên prod (app pool recycled w/ new binary post-deploy; nếu seed CHƯA chạy thì non-admin sẽ vẫn 0 — nhưng 16/16 across 13 roles = đã chạy). Menu-tree non-admin via API NOT tested (DB-query mục 4 đủ proof per spec). 0 regression. NO prod-data mutation ngoài chính golive seed (read-only curls + sqlcmd SELECT-only; seed-write là chủ đích của deploy). Visual menu-render = anh UAT. **LESSON: BE-only GOLIVE security-seed verify = both bundles MUST stay FROZEN (no FE → no rotate; rotate=anomaly) + Mig-top + sys.tables MUST stay prev (DbInitializer seed = runtime row insert/update, NOT EF-migration → KHÔNG advance __EFMigrationsHistory NOR sys.tables) + **CORE proof = prod Permissions DB query**, NOT bundle-frozen alone (frozen chỉ chứng minh FE không đổi, KHÔNG chứng minh API binary mới deploy + seed chạy — phải query Permissions cho non-admin role thấy allow-list grant + cross-role để loại fluke + leak-check excluded chỉ-Admin). upgrade-only seed THẮNG revoke vì chạy SAU trong SeedAsync. golive-state AMBIENT-after-this-deploy (parent commit c556f6c chưa có method này → pre-1f8947e non-admin Office = 0; bằng chứng "đã chạy" = 16/16). TOOLING (re-confirmed S70): Bash=POSIX (`$var`/`$env:` mangle through bash→ssh→PS layers) → write PS to `/tmp/x.ps1` + `powershell.exe -NoProfile -File "C:/.../Temp/x.ps1"` (⚠️ heredoc em-dash/non-ASCII corrupts → ASCII-only in PS source); jq ABSENT + system python3 broken → Invoke-RestMethod cho Gitea `tasks` (match `head_sha -eq sha`, `?limit=N` URI-direct); SSH→PS base64 `-EncodedCommand` (UTF-16LE `[Text.Encoding]::Unicode`) cho BOTH appsettings-read AND sqlcmd; sqlcmd pw-literal qua single-quote in B64-payload (no `$` survives clean), `-U/-P` direct + `-W -s '|'` pipe-delim + `-h -1` no-header; read DB pw from prod appsettings when `$PROD_DB_PASSWORD` empty. NEVER fixed code (READ-only).** Tag `[s69b, run307, pass, be-only-golive-vanphongso-public-read-create-allowlist16, SeedAllRolesOfficeModulePermissionsAsync-boot-after-revoke, bundle-BOTH-frozen-Wt54PHYl-B99fMU6X, no-mig-top-stays-mig52, tables88, GOLIVE-DB-VERIFIED-16of16-across-13-roles, excluded3-canread0-only-admin-leak0, hrm-personal-still-0, hrm-hoso-public-unchanged, admin-not-downgraded, test292-inferred, deploy11of11, seed-not-migration-no-history-advance, no-regression]`. -- **2026-06-17 S70 Run #306 (run_number 306, id420) sha=`c556f6c` PASS ~4m42s (FE-ONLY re-skin "Văn phòng số" TOÀN MODULE 10 page PURO layout — PageHeader/KpiCard/WidgetCard apply + CSS Hồ sơ NS; presentation-only "phẫu thuật giữ nguyên logic", byte-identical logic. 20 .tsx: fe-admin 11 {AttendanceReport,InternalDirectory,ItTickets,MeetingCalendar,MeetingRooms,ProposalCreate,ProposalDetail,ProposalsList,WorkflowAppDetail,WorkflowAppsList}Page + fe-user 9 mirror (no AttendanceReport — admin-only) + 3 agent-memory .md (cicd/fe-designer/reviewer). NO BE/NO migration/NO new menu key. deploy 10/10 session after #297–#305 all PASS):** Push `a8bbdae..c556f6c` (1 commit, 23 files). `git diff --name-only a8bbdae c556f6c -- '*Migrations*' '*Persistence*'` = EMPTY ✓. `.tsx` non-ignored → full pipeline RAN (3 `.md` agent-memory match paths-ignore but co-mixed w/ 20 tsx → Discovery#3 range any-non-ignored ⟹ whole build). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp` len24). Run IN-PROGRESS at spawn (status=running 09:57:54→09:58:40) — pre-deploy baseline captured BEFORE poll-loop: admin `Bl2o_kUq` (S69 #305 live) + user `BImrKQNn` (S69 #305 live) — both == spec baseline (still live, deploy not yet shipped, anti#3 honored no mid-flight verify). Polled status=success (started 09:57:54 → upd 10:02:36 ≈4m42s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` không populate `conclusion`, trust success; 286 INFERRED gate-passes-pre-build invariant NOT log-confirmed). **★ BUNDLE BOTH ROTATE (FE-both-app 10-page re-skin ⟹ both MUST rotate; verified AFTER status=success + re-confirm STABLE 2nd-fetch identical no-transient — anti#3): admin ROTATE `Bl2o_kUq→Wt54PHYl` (css `CKzdDktL→BpHtX3vS`)** ✓ **+ user ROTATE `BImrKQNn→B99fMU6X` (css `eoxUcs8v→DXRSCQW7`)** ✓. BOTH required → BOTH did. Asset reachable 200: admin js 1,588,241b + user js 1,493,955b (not white). ⚠️ Prod CI hashes ≠ spec local-build (fe-user `C8-p69Kn`/`DezuRkK9`, fe-admin `yFhLO2Wp`/`Dd2WiO6n`) — EXPECTED CI-rebuild content-hash divergence (only matters NEW≠baseline, BOTH rotated confirmed). Smoke **4×200** health: api `/health/ready`+`/health/live` + admin root + eoffice root. **Office-backed API live (admin bearer): GET /api/proposals 200 · /api/it-tickets 200 · /api/meeting-rooms 200 · /api/employees 200** (data layer healthy, not 500). `/api/workflow-apps`→404 = WRONG-route-guess NOT regression (FE-only can't change BE routing; real route differs — leave-requests/don-tu; FE uses api-client wrapper, route grep didn't land). **SPA deep-link 6 Office routes admin all 200** (/proposals, /it-tickets, /workflow-apps/leave, /directory, /meeting-calendar, /office/dashboard serve index.html — not 404/white). **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (prev-2 = AddDepartmentParentId Mig51 + ReplaceBudgetModuleWithPeWorkItemBudgets Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged). **★ OFFICE-HIDDEN CONFIRMED (non-admin Drafter=nv.test): sqlcmd `Off_Dashboard` perm row ONLY for Admin (1/13 roles) ✓ + Drafter has ALL 17 Off_* keys present but EVERY one CanRead=0 ✓ → menu gates on CanRead=1 ⟹ non-admin sees NO Office menu.** ⚠️ **LESSON-CORRECTION vs S69: my coarse `GROUP BY COUNT(*) WHERE MenuKey LIKE 'Off%'` first showed "all 13 roles ~18 Off perms" — MISLEADING; those are perm-ROWS-with-CanRead=0 (existence ≠ access). Must filter `CanRead=1` OR inspect per-key. MenuItems.IsVisible=1 on all Off keys = admin-side global-visible flag; user-visibility = IsVisible AND role.CanRead. Office-hidden state is AMBIENT (FE-only commit cannot touch Permissions/MenuItems seed — git diff zero BE/DbInitializer) = identical pre-c556f6c.** 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual per-page render = anh user UAT (em chỉ confirm bundle shipped + route không 500/trắng per spec). **TOOLING (re-confirmed + NEW): Bash tool = POSIX bash → ⚠️ system `python3` BROKEN (ZKBioTime Python311 on PATH → `SRE module mismatch` fatal) AND `jq` NOT installed → MUST use `powershell.exe -NoProfile -File "C:/.../tmp/x.ps1"` + `Invoke-RestMethod` for ALL JSON parse (Gitea tasks match `head_sha -eq sha`); poll-loop in pure-bash w/ jq SILENTLY returns empty (no error) — verify parser works before trusting loop. SSH→PS base64 `-EncodedCommand` (UTF-16LE) for appsettings-read AND sqlcmd; sqlcmd string-LITERAL via `[char]39` concat (NOT doubled-quote); `-U/-P` direct (no -ConnectionString flag); `Roles`/`Permissions(RoleId,MenuKey,CanRead)`/`MenuItems([Key],IsVisible)`. NEVER fixed code (READ-only).** Tag `[s70, run306, pass, fe-only-vanphongso-reskin-10page-PURO, fe-both-app-bundle-BOTH-rotate-Wt54PHYl-B99fMU6X, asset-200-reachable, office-api-200-proposals-ittickets-meetingrooms-employees, workflow-apps-404-wrong-route-not-regression, spa-deeplink-6route-200, no-mig-top-stays-mig52, tables88-verified, office-hidden-confirmed-Off_Dashboard-admin-only-drafter-canread0, lesson-canread0-rows-not-access, ambient-not-deploy-caused, jq-absent-python3-broken-use-powershell, deploy10of10, no-regression, test286-inferred]`. -- **2026-06-17 S69 Run #305 (run_number 305, id419) sha=`a8bbdae` PASS ~4-5m (FE BOTH-APP foundation "Văn phòng số" + index.css sync + BE menu-seed NO-mig: 3 shared component mới PageHeader/KpiCard/WidgetCard + OfficeDashboardPage landing route `/office/dashboard` 4-place-wire BOTH apps + `fe-admin/src/index.css` SYNCED (Hồ sơ NS accent tokens, rotate admin mạnh) + BE menu key `Off_Dashboard` (MenuKeys.cs L100 + DbInitializer.cs L1825 seed parent=`Off` Order0 LayoutDashboard); deploy 9/9 session after #297–#304 all PASS):** Push `764fe70..a8bbdae` (1 commit, 20 files). Diff: FE 14 files (2× {PageHeader,KpiCard,WidgetCard,OfficeDashboardPage,App.tsx,Layout.tsx,menuKeys.ts} + fe-admin index.css) + BE 2 (MenuKeys.cs + DbInitializer.cs) + 4 agent-memory `.md`. `.tsx`/`.cs`/`.css` non-ignored → full pipeline RAN (the 4 `**/*.md` agent-memory match paths-ignore but co-mixed w/ code → Discovery#3 range any-non-ignored ⟹ whole build). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp`). Run IN-PROGRESS first 4 polls (running 09:26→09:29) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `CNUv1jxY` (S78 #304 live) + user `CpOskeS1` (S78 #304 live) — both == spec S68 baseline (still live, deploy not yet shipped). Polled iter5 status=success (started ~09:25 → success 09:29:50 ≈4-5m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success; 286 INFERRED from gate-passes-pre-build invariant NOT log-confirmed numerically). **★ BUNDLE BOTH ROTATE (FE-both-app + index.css sync ⟹ both MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no-transient — anti#3): admin ROTATE `CNUv1jxY→Bl2o_kUq`** ✓ (Văn phòng số + index.css shipped) **+ user ROTATE `CpOskeS1→BImrKQNn`** ✓ (foundation shipped). BOTH required → BOTH did. ⚠️ **Prod CI hashes (`Bl2o_kUq`/`BImrKQNn`) ≠ spec local-build (`TbkadgKd`/`DrxDysO7`) — EXPECTED divergence (CI rebuilds w/ different Node/npm/dep-resolution → different content-hash; only matters NEW≠baseline, BOTH rotated confirmed ship). Spec assumption "prod khớp local nếu source-identical" holds ONLY if byte-identical build-env — it is NOT (S77 same lesson).** Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 764fe70 a8bbdae -- '*Migrations*'` = EMPTY; menu-seed is runtime-idempotent DbInitializer NOT EF-migration → top did NOT advance). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, menu-seed inserts rows not tables). **★ MENU-SEED VERIFIED (NEW check this run — DbInitializer seed ungated reaches prod by design): sqlcmd `SELECT [Key],ParentKey,Label,IsVisible FROM MenuItems WHERE [Key]='Off_Dashboard'` → 1 row Key=`Off_Dashboard` Parent=`Off` Label="Bảng điều khiển Văn phòng số" IsVisible=1 ✓.** **★ OFFICE-HIDDEN CONFIRMED (RevokeTemporarilyHiddenModulesAsync StartsWith("Off") scope — DbInitializer.cs L2172): sqlcmd `SELECT r.Name FROM Permissions p JOIN Roles r ON r.Id=p.RoleId WHERE p.MenuKey='Off_Dashboard'` → ONLY `Admin` (CanRead=1), 1 row / 13 roles total ✓ → non-admin NO Off_Dashboard perm → Office stays hidden (revoke executed, admin auto via All[]).** 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "Dashboard landing render / Hồ sơ NS CSS" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88+menu-seed+office-hidden. **LESSON: FE-both-app + index.css-sync + BE-menu-seed (NO-EF-mig) verify = both bundles MUST ROTATE + Mig-top + sys.tables MUST stay prev (menu-seed = DbInitializer runtime row-insert, NOT a migration → does NOT advance __EFMigrationsHistory NOR sys.tables; verify seed via direct MenuItems SELECT not via mig-count). Office-hidden = query Permissions-by-role: temporarily-hidden module has perm row ONLY for Admin (All[] auto-grant) after RevokeTemporarilyHidden runs. ⚠️ Prod CI bundle-hash ≠ local-build-hash is NORMAL — never FAIL on hash-mismatch-to-local, only FAIL if NOT-rotated-from-baseline. TOOLING (re-confirmed S78): Bash=POSIX (`$var`/`$env:`/`''` literal-quote mangle through bash→ssh→PS→sqlcmd layers) → write PS to `.ps1` + `powershell -File "ABS/FWD/SLASH.ps1"`; Gitea `tasks` via `Invoke-RestMethod` (match `head_sha -eq sha`, `?limit=N` honored URI-direct); SSH→PS base64 `-EncodedCommand` (UTF-16LE iconv) for BOTH appsettings-read AND sqlcmd; ⚠️ **sqlcmd string-LITERAL in query: doubled `''x''` BREAKS (PS single-quote-string closes early) → build literal via `[char]39 + "x" + [char]39` concat (NOT `''`)**; this sqlcmd build supports `-U/-P` direct (no -ConnectionString flag); table name `Roles` NOT `AspNetRoles`, `Permissions(RoleId,MenuKey,CanRead)`; CLIXML/Progress stdout-noise grep-filter out. NEVER fixed code (READ-only).** Tag `[s69, run305, pass, fe-both-app-vanphongso-foundation, shared-PageHeader-KpiCard-WidgetCard, index-css-sync-admin, bundle-BOTH-rotate-Bl2o_kUq-BImrKQNn, prod-ci-hash-NE-local-EXPECTED, be-menu-seed-Off_Dashboard-NO-mig, no-mig-top-stays-mig52, tables88-verified, menu-seed-verified-sqlcmd, office-hidden-confirmed-admin-only-1of13, sqlcmd-char39-literal-not-doubled-quote, deploy9of9, no-regression, test286-inferred]`. -- **2026-06-16 S78 Run #304 (run_number 304, id418) sha=`37752eb` PASS ~4m (FE BOTH-APP 1-line CSS-precedence fix: "Hồ sơ NS" employee-detail banner NAME đen→trắng — `

` rendered BLACK do unlayered `h1,h2,h3,h4{color:#0b1220}` index.css thắng `text-white` (Tailwind v4 @layer precedence); fix = `text-white`→`text-white!` important-modifier + `text-xl font-extrabold`→`text-lg font-bold`. 2 file `EmployeesListPage.tsx` fe-user+fe-admin SHA256-IDENTICAL `8BBAEC34…`, 1-line each, NO BE/mig/index.css; deploy 8/8 session after #297–#303 all PASS):** Push `6983609..37752eb` (1 commit). Diff 2 files: both `.tsx` only → not in paths-ignore → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp` len24). Run IN-PROGRESS first 7 polls (running 19:55→19:58) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `D532XZKG` (S77 #303 live) + user `CuFaBoWt` (S77 #303 live) — both == spec baseline (still live, deploy not yet shipped). Polled iter8 status=success (started ~19:55 → success 19:59:26 ≈4m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success; exact CI count NOT log-confirmed numerically — Gitea logs web-UI-only anon, 286 INFERRED from gate-passes-pre-build invariant). **★ BUNDLE BOTH ROTATE (the change-point — FE-BOTH-app, both EmployeesListPage changed ⟹ both bundles MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient — anti#3): admin ROTATE `D532XZKG→CNUv1jxY`** ✓ (`text-white!` name-color fix shipped) **+ user ROTATE `CuFaBoWt→CpOskeS1`** ✓ (same page shipped). BOTH required per spec → BOTH did (mirror S74/S75/S76/S77 pure-FE-both-app pattern — both same-SHA256 file ⟹ both rotate; frozen sibling here = ship-fail flag). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 6983609 37752eb -- '*Migrations*' '*Persistence*'` = EMPTY; FE cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "tên NV trắng render đúng" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-between-2-apps is a SOURCE-claim (git), not runtime DOM-equality; CSS-precedence-actually-fixed is render-time, NOT provable by curling bundle (the `!important` text is in dist css per spec-claim, but visual-black-vs-white is browser-render). **LESSON: pure-FE-BOTH-app 1-line CSS-fix verify (8th consecutive deploy this session, both apps same component SHA256-identical) = both bundles MUST ROTATE (≠ asymmetric one-app where sibling frozen; ≠ BE-only where both frozen). Even a 1-line `text-white`→`text-white!` change → new content-hash both apps. Migration top + sys.tables MUST stay = prev (FE-only). No BE call-site/DTO/endpoint smoke (no API surface — render-only). TOOLING (re-confirmed S77): Bash tool is POSIX bash (`Select-String`/`$var`/`$env:` unavailable inline) → write PowerShell to `.ps1` + invoke `powershell -File "ABSOLUTE/FORWARD/SLASH.ps1"` (Bash EATS backslash in `tmp\x.ps1`→`tmpx.ps1`); parse Gitea `tasks` via `Invoke-RestMethod`+native object (match `head_sha -like sha*`, `limit=N` ignored); SSH→PS nested-quote mangles → base64 `-EncodedCommand` (UTF-16LE) BOTH the appsettings-read AND the sqlcmd-query (no `$` in B64 passes bash+ssh clean); CLIXML progress-stream on stdout harmless (data rows clean below); read DB pw from prod appsettings when PROD_DB_PW empty. NEVER fixed code (READ-only).** Tag `[s78, run304, pass, fe-both-app-hoso-name-color-fix-text-white-important, fe-both-2files-sha256-identical-8BBAEC34, bundle-BOTH-rotate-CNUv1jxY-CpOskeS1, css-precedence-tailwind-v4-layer, no-mig-top-stays-mig52, tables88-verified, deploy8of8, ssh-encodedcommand-base64, no-regression, test286-inferred]`. - _(S77 #303 sha `6983609`, S76 #302, S75/S74 … pre-S78 verbatim → git `764fe70` + archive — FIFO trimmed to keep L1 under soft-cap)_ -- **2026-06-16 S77-ARCHIVED Run #303 sha=`6983609` PASS ~5m (FE BOTH-APP additive "Hồ sơ NS" banner text-polish — name `text-xl font-extrabold`+drop-shadow, meta `text-[13px] font-medium text-white`, status badge → solid status-colored pill emerald/amber/slate. 2 file `EmployeesListPage.tsx` fe-user+fe-admin SHA256-IDENTICAL `F013B748…`, NO BE/mig/index.css; +docs 2 files `{dep-audit SKILL.md gotcha 64→65, root CLAUDE.md test 263→286}` no-build-impact; deploy 7/7 session after #297–#302 all PASS):** Push range `231a7b0..6983609` (2 commits). Diff 4 files: 2 `.tsx` + `CLAUDE.md` + `dependency-audit-erp/SKILL.md`. `.tsx` → not in paths-ignore → full pipeline RAN (docs alone would SKIP, but tsx present ⟹ build per Discovery#3 push-range any-non-ignored ⟹ whole build). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`). Run IN-PROGRESS first 6 polls (running 19:36→19:39) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `CcrZqfht` (S76 #302 live) + user `DniDFUB_` (S76 #302 live) — both == spec baseline (still live, deploy not yet shipped). Polled iter6 status=success (started ~19:35 → success 19:40 ≈5m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success; could NOT extract exact CI count — Gitea logs web-UI-only anon, 286 INFERRED from gate-passes-pre-build invariant NOT log-confirmed numerically). **★ BUNDLE BOTH ROTATE (the change-point — FE-BOTH-app, both EmployeesListPage changed ⟹ both bundles MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient — anti#3): admin ROTATE `CcrZqfht→D532XZKG`** ✓ (banner polish shipped) **+ user ROTATE `DniDFUB_→CuFaBoWt`** ✓ (same page shipped). BOTH required per spec → BOTH did (mirror S74/S75/S76 pure-FE-both-app pattern — both same-SHA256 file ⟹ both rotate; frozen sibling here = ship-fail flag). ⚠️ Local-build hashes (fe-user `SuT9mDAQ`, fe-admin `7ICczYiQ` per spec) ≠ deployed CI hashes — EXPECTED (CI rebuilds; only matters NEW≠baseline, confirmed). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 231a7b0 6983609 -- '*Migrations*' '*Persistence*'` = EMPTY; FE+docs cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "banner text-xl/drop-shadow/status-pill render đúng" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-between-2-apps is a SOURCE-claim (git), not runtime DOM-equality. **LESSON: pure-FE-BOTH-app additive verify (7th consecutive deploy this session, both apps same component SHA256-identical) = both bundles MUST ROTATE (≠ asymmetric one-app where sibling frozen; ≠ BE-only where both frozen). Docs-files in same range as tsx do NOT suppress build (range any-non-ignored ⟹ build). Migration top + sys.tables MUST stay = prev (FE-only). No BE call-site/DTO/endpoint smoke (no API surface — render-only). TOOLING (re-confirmed S76): Bash tool EATS inline `$var`/`$env:`/`@{}` in PS strings → write PowerShell to `.ps1` + run `-File`; parse Gitea `tasks` via `Invoke-RestMethod`+native object (match `head_sha -like sha*`, `limit=N` ignored); SSH→sqlcmd base64 `EncodedCommand` (UTF-16LE via iconv, no `$` in B64 passes bash clean), CLIXML progress-stream stdout harmless grep-filter out; ⚠️ this `sqlcmd` build does NOT support `-ConnectionString` flag → parse `User Id`/`Password` from conn-string via regex → `-U/-P`; read DB pw from prod appsettings when PROD_DB_PW empty. NEVER fixed code (READ-only).** Tag `[s77, run303, pass, fe-both-app-hoso-banner-textpolish, fe-both-2files-sha256-identical-F013B748, bundle-BOTH-rotate-D532XZKG-CuFaBoWt, docs-in-range-no-suppress-build, no-mig-top-stays-mig52, tables88-verified, deploy7of7, sqlcmd-no-connstring-flag-use-U-P, no-regression, test286-inferred]`. -- **2026-06-16 S76 Run #302 (run_number 302, id416) sha=`536dd6b` PASS ~4m (FE BOTH-APP additive: PE "Link hồ sơ ổ mạng" render upgrade — đường-dẫn-ổ-mạng từ chữ+Copy → `` bấm-thử mở Explorer + GIỮ nút Copy dự phòng. 2 file `PeDetailTabs.tsx` fe-user+fe-admin SHA256-IDENTICAL `b415023b…`, +46/−14 mỗi file, NO BE/mig/index.css/Employee-page; deploy 6/6 session after #297/#298/#299/#300/#301 all PASS):** Push 2 files `{fe-admin,fe-user}/src/components/pe/PeDetailTabs.tsx`. `.tsx` → not in paths-ignore → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default`. Run IN-PROGRESS first 6 polls (running 14:54:20→14:56:57) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `I1fpLeYw` (S75 #301 live) + user `DrQYkzh0` (S75 #301 live) — both == spec baseline. Polled iter7 status=success (started ~14:53 → success 14:57:13 ≈4m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success). **★ BUNDLE BOTH ROTATE (the change-point — FE-BOTH-app, both PeDetailTabs changed ⟹ both bundles MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient — anti#3): admin ROTATE `I1fpLeYw→CcrZqfht`** ✓ (file:// link shipped) **+ user ROTATE `DrQYkzh0→DniDFUB_`** ✓ (same component shipped). BOTH required per spec → BOTH did (mirror S74/S75 pure-FE-both-app pattern — both same-SHA256 file ⟹ both rotate; frozen sibling here = ship-fail flag). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 536dd6b~1 536dd6b -- '*Migrations*'` = EMPTY; FE cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "link file:// bấm mở Explorer / nút Copy" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-between-2-apps is a SOURCE-claim (git), not runtime DOM-equality (file:// scheme browsers thường block từ https-origin → click có thể no-op tùy browser, đó là lý do GIỮ Copy dự phòng — không kiểm chứng được qua curl). **LESSON: pure-FE-BOTH-app additive verify (6th consecutive deploy this session, both apps same component SHA256-identical) = both bundles MUST ROTATE (≠ asymmetric one-app where sibling frozen; ≠ BE-only where both frozen). Migration top + sys.tables MUST stay = prev (FE-only). No BE call-site/DTO/endpoint smoke (no API surface — render-only). TOOLING (re-confirmed S75): Bash tool EATS inline `$var`/`$env:` in `powershell -Command` → write PowerShell to `.ps1` + run `-File`; `certutil -hashfile … SHA256` for SHA256 (NOT findstr-pipe — quoting breaks); parse Gitea `tasks` via `Invoke-RestMethod`+native object (match `head_sha -eq sha`, `limit=N` ignored); SSH→sqlcmd base64 `EncodedCommand` (UTF-16LE, no `$` in B64 passes bash clean), CLIXML progress-stream stdout harmless; read DB pw from prod appsettings when PROD_DB_PW empty (path `C:\inetpub\solution-erp\api`, key `ConnectionStrings.Default`). NEVER fixed code (READ-only).** Tag `[s76, run302, pass, fe-both-app-pe-link-hoso-file-scheme, fe-both-2files-sha256-identical-b415023b, bundle-BOTH-rotate-CcrZqfht-DniDFUB_, no-mig-top-stays-mig52, tables88-verified, deploy6of6, no-regression, test286]`. - _(S75 Run #301 sha=`6df1b2d` PASS ~2.5m [FE-both-app PE Link-hồ-sơ auto-detect render, bundle-BOTH-rotate I1fpLeYw/DrQYkzh0, no-mig-mig52, tables88, test286, fastest-streak] → FIFO-trimmed, full verbatim git + `archive/2026-06.md`)_ - _(S74 Run #300 sha=`91aaf05` PASS [FE-both-app list-redesign flex-row, bundle-BOTH-rotate PxiZQkaw/B36hGoKd, user-unfroze-from-s71-streak, no-mig-mig52, tables88, test286, TOOLING: ps1-file-not-inline-dollar + ssh-encodedcommand-base64] → FIFO-trimmed, full verbatim git + `archive/2026-06.md`)_ -- **2026-06-16 S73 Run #299 (id413) sha=`bcd619d` PASS ~3m (TESTS-ONLY BE: +3 files `tests/SolutionErp.Infrastructure.Tests/Application/{DepartmentTreeTests,PeHoSoLinkTests,HrmProfilePermissionSeedTests}.cs` = +23→286 total (45D+241I). NO prod code, NO FE, NO migration; deploy 3/3 session after #297/#298 both success):** `.cs` not in paths-ignore → full pipeline RAN. Run IN-PROGRESS first poll (running 14:06) → polled iter4 status=`success` (~14:09 ≈3m); `conclusion` empty (terminal=success, trust). **CI test gate = the focus: gate runs BOTH test projects BEFORE build ⟹ status=success ⟹ 23 new tests passed on CI (not just local 286).** Could NOT extract exact CI count — Gitea `runs/413/logs`+`jobs` endpoints all **404 anon** (logs web-UI-only without auth token; GITEA_TOKEN empty). Inferred 286 from success (gate-passes-pre-build invariant) — NOT log-confirmed numerically. **★ BUNDLE BOTH-FROZEN (BE-only ⟹ neither app may rotate), verified AFTER status=success +2nd-fetch STABLE no transient (anti#3): admin `xkSz9BfE` FROZEN ✓ + user `BumgrwCJ` FROZEN ✓** (==pre-deploy baseline both; rotate-on-BE-only = anomaly→none). Smoke **4×200**: api `/health/ready`+`/health/live`+admin root+eoffice root. **NO migration** — prod `__EFMigrationsHistory` top=`AddHoSoLinkToPurchaseEvaluation`(Mig52)==repo HEAD GIỮ NGUYÊN ✓ (prev-2 Mig51 `AddDepartmentParentId`+Mig50 `ReplaceBudgetModule…` chain intact; commit 0 mig files). **sys.tables=88** ✓ (sqlcmd excl mighist, unchanged). 0 regression, NO prod-data mutation (read curls+SELECT-only). **TOOLING LESSON (supersedes S72 #298): `python3`/`py -3` on this box = BROKEN ZKBioTime embed (`AssertionError: SRE module mismatch` on `import re`/`json`) — version banner works but stdlib import dies. USE PowerShell `Invoke-RestMethod`+native object access (NOT ConvertFrom-Json on cached temp file — temp got stale snapshot once) to parse Gitea tasks; `limit=N` param IGNORED (returns full 299, match by id anyway). SSH→sqlcmd: nested bash→ssh→PS ate `$var` (S42) → use `iconv UTF-16LE|base64`→`powershell -EncodedCommand`; CLIXML progress-stream noise on stdout is harmless, data lines clean. Read DB pw from prod `appsettings.Production.json`→`ConnectionStrings.Default` when PROD_DB_PW empty (path `C:\inetpub\solution-erp\api`). NEVER fixed code (READ-only).** Tag `[s73, run299, pass, tests-only-be, +23-test-286-CI-gate-inferred-not-logcount, bundle-both-frozen-xkSz9BfE-BumgrwCJ, no-mig-top-mig52, tables88, deploy3of3, python3-broken-use-powershell-IRM, no-regression]`. -- **2026-06-16 S72 Run #298 (run_number 298, id412) sha=`292d64d` PASS ~4m20s (FE-Admin MIRROR "Hồ sơ Nhân sự" master-detail từ fe-user: overwrite EmployeesListPage.tsx cũ 1200→1602 dòng SHA256-IDENTICAL với fe-user + index.css +4 accent palette teal/amberx/violet/greenx + utility `.icon-chip`/`.app-gradient-brand`/`.card-accent`/`.stat-value` — FE-ADMIN-ONLY 2 files, NO BE, NO migration, NO fe-user; ASYMMETRIC NGƯỢC deploy-1 S71 #297 (đó fe-user-rotate/admin-frozen, đây admin-rotate/user-frozen)):** Deploy 2/2 trong session (deploy-1 = `ab4e681` #297 PASS fe-user `BumgrwCJ`; #297=success TRƯỚC push này nên KHÔNG bị cancel — confirmed in tasks list). Push 2 files `fe-admin/src/index.css`(+81) + `fe-admin/src/pages/hrm/EmployeesListPage.tsx`(1020+/537−, 1476-line overwrite). `.tsx`+`.css` → not in paths-ignore → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (`buKL3...buKL3TGBkD0wDDbYVw65QeX9`). Run IN-PROGRESS first poll (running 13:52) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline snapshot captured BEFORE poll: admin `BDwV5d0X` (S68 frozen-since, must rotate) + user `BumgrwCJ` (S71 deploy-1 live, must stay frozen). ⚠️ POLL-PARSER BUG: my `tr ','|grep -A4 '"id":412'` returned empty status ×10 iter (false negative — grep -A on tr-newlined JSON misanchors) → re-queried with `python3 json.load` → status=`success` (started 13:51:27 → updated 13:55:47 ≈4m20s). LESSON-tooling: parse Gitea tasks JSON via python3 NOT tr/grep -A (multi-field-per-object splits wrong); always cross-check with a clean parser before trusting empty-status. CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — trust success). **★ BUNDLE ASYMMETRIC NGƯỢC-deploy-1 (the change-point — FE-one-app verify) ALL PASS, verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient (anti#3): admin ROTATE `BDwV5d0X→xkSz9BfE`** ✓ (EmployeesListPage mirror + accent index.css shipped — admin un-froze from S68's BDwV5d0X frozen-streak S68→S71, now rotated correctly cho fe-admin change) **+ user FROZEN `BumgrwCJ`==deploy-1-baseline** ✓ (fe-user 0 files touched deploy-2 → MUST NOT rotate = correct; rotate-when-untouched = anomaly→flag). Health api live+ready **200/200** (real endpoints `/health/ready`+`/health/live`, NOT `/health`) + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact; commit 0 mig files — FE cannot alter schema → top did NOT advance). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "mirror identical / chữ xanh đậm brand-800 / accent đẹp" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-with-fe-user is a SOURCE-claim (git diff), not runtime-verified by me (can't compare rendered DOM cross-app). **LESSON: deploy-2 of same-session asymmetric-NGƯỢC = (a) verify prior deploy (#297) NOT cancelled by this push (it was success first — confirmed in tasks); (b) status=success ⟹ deploy ran; (c) THIS-app bundle ROTATE + OTHER-app FROZEN — direction FLIPS vs deploy-1 (deploy-1 fe-user-rotate/admin-frozen → deploy-2 admin-rotate/user-frozen); admin un-froze from multi-session frozen-streak = EXPECTED when fe-admin finally changes (frozen-streak break is normal, not anomaly); (d) migration top + sys.tables MUST stay = prev (FE-only). Re-query Gitea JSON with python3 when tr/grep parse yields empty — parser bug ≠ run-stuck. No BE call-site/DTO/endpoint smoke (no API surface). SHA256-source-identical = git-level claim, runtime-mirror-equality not provable by curl. NEVER fixed code (READ-only).** Tag `[s72, run298, pass, fe-admin-mirror-hoso-from-feuser, fe-admin-only-2files, bundle-asymmetric-NGUOC-admin-rotate-user-frozen, admin-unfroze-from-s68-streak, no-mig-top-stays-mig52, tables88-verified, deploy2of2-prior297-not-cancelled, poll-parser-bug-use-python3, no-regression, test263]`. -- **2026-06-16 S71 Run #297 (run_number 297, id411) sha=`ab4e681` PASS ~4m21s (FE-User "Hồ sơ NS" cosmetic: đồng nhất font/size + chữ đen `text-slate-900/800`→xanh-đậm `text-brand-800` cho value/tên + bỏ `text-xs` nhánh mono — FE-USER-ONLY 1 file, NO BE, NO migration; 4th consecutive same-page S68→S71):** Push 1 file `fe-user/src/pages/hrm/EmployeesListPage.tsx` (className màu chữ + size, KHÔNG đụng BE/Mig/dep/fe-admin). `.tsx` → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (len24). Run IN-PROGRESS first poll (running 13:43) — correctly did NOT verify-bundle-mid-flight (anti#3), pre-deploy baseline snapshot captured BEFORE poll iter0: user `DbVv6rsf` (S70 baseline) + admin `BDwV5d0X`. Polled iter5 status=success (started ~13:42 → success ~13:46:48 ≈4m21s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — trust success). **★ BUNDLE ASYMMETRIC (the change-point — FE-one-app verify) ALL PASS, verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient (anti#3): user ROTATE `DbVv6rsf→BumgrwCJ`** ✓ (EmployeesListPage cosmetic shipped — re-rotated from S70's DbVv6rsf, 4th consecutive same-page FE-user deploy = new content-hash each, normal) **+ admin FROZEN `BDwV5d0X`==baseline** ✓ (fe-admin 0 files touched → MUST NOT rotate = correct; rotate-when-untouched = anomaly→flag). Health api live+ready **200/200** (⚠️ `/health` spec'd → 404, real endpoint `/health/ready`+`/health/live` per iis-deploy-runbook skill — both 200, API healthy) + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (commit 0 mig files — `git diff --name-only | grep Migrations/` = NONE; FE-only cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "chữ xanh đậm/đồng nhất size" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88. **LESSON: pure-FE-one-app cosmetic verify (4th consecutive same-page) = (a) status=success ⟹ deploy ran; (b) target-app bundle ROTATE + sibling-app FROZEN (re-rotate of same page across sessions = NORMAL, each deploy=new hash; rotate-sibling-when-untouched = anomaly); (c) migration top + sys.tables MUST stay = prev (FE cannot alter schema — advancing = bug); (d) health 200×2 (/ready+/live, NOT /health). ⚠️ `/health` 404 ≠ unhealthy — SOL uses `/health/ready`+`/health/live` (skill-doc), spec literal `/health` is wrong-path; cross-check skill before flagging. No BE call-site/DTO/endpoint smoke needed (no API surface). Tokens empty: anon Gitea + prod-appsettings DB pw works. NEVER fixed code (READ-only).** Tag `[s71, run297, pass, fe-user-hoso-cosmetic-brand800, fe-only-1file, bundle-asymmetric-user-rotate-admin-frozen, no-mig-top-stays-mig52, tables88-verified, health-ready-not-health, no-regression, test263]`. -- **2026-06-16 S70 Run #295 (run_number 295, id409) sha=`456c7a7` PASS ~4m21s (FE-User "Hồ sơ Nhân sự" layout 2-cột: cây tổ chức + list chồng cột trái, detail cột phải + tô màu panel chi tiết — FE-USER-ONLY 1 file, NO BE, NO migration; follow-up S69 Run #294 same page):** Push `ec517f7..456c7a7` 1 file `fe-user/src/pages/hrm/EmployeesListPage.tsx` (309+/262−, 571-line layout rewrite). `.tsx` → full pipeline RAN. Both tokens SET this run (GITEA_TOKEN+PROD_DB_PW present — unlike S65-S69 empty; DB pw still works via prod `appsettings.Production.json`→`ConnectionStrings.Default` `buKL3...` len24). Run IN-PROGRESS first poll (running 11:36) — correctly did NOT verify-bundle-mid-flight (anti#3), polled iter6 status=success (started ~11:36 → success 11:40:46 ≈4m21s). Pre-deploy baseline snapshot iter0: user `CZfo_PFZ` (S69 baseline) + admin `BDwV5d0X` — captured BEFORE poll, correct timing. CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — trust success). **★ BUNDLE ASYMMETRIC (the change-point — FE-one-app verify) ALL PASS, verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient (anti#3): user ROTATE `CZfo_PFZ→DbVv6rsf`** ✓ (EmployeesListPage layout-2col shipped — re-rotated from S69's CZfo_PFZ, consecutive FE-user deploys on SAME page = new content-hash each, normal) **+ admin FROZEN `BDwV5d0X`==baseline** ✓ (fe-admin 0 files touched → MUST NOT rotate = correct; rotate-when-untouched = anomaly→flag). Health api live+ready **200/200** + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (commit 0 mig files — `git diff --name-only | grep Migrations/` = NONE; FE-only cannot alter schema → top did NOT advance past S68's Mig 52; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). sys.tables stays 88 by construction (no schema touch — skipped explicit count, FE-only). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "2-cột đẹp/tô-màu" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged. **LESSON: pure-FE-one-app verify (3rd consecutive same-page FE-user S68→S69→S70) = (a) status=success ⟹ deploy ran; (b) target-app bundle ROTATE + sibling-app FROZEN (asymmetric — re-rotate of already-rotated page across sessions is NORMAL, each deploy=new hash; rotate-sibling-when-untouched = anomaly); (c) migration top MUST stay = prev (FE cannot alter schema — advancing top on FE-only commit = bug); (d) health 200×3. No BE call-site/DTO/endpoint smoke needed (no API surface). Tokens-present vs empty: both paths work — when GITEA_TOKEN set use `token` header, but anon also fine for public repo. NEVER fixed code (READ-only).** Tag `[s70, run295, pass, fe-user-hoso-2col-layout, fe-only-1file, bundle-asymmetric-user-rotate-admin-frozen, no-mig-top-stays-mig52, tables88, no-regression, test263, tokens-present]`. -- **2026-06-16 S68 Run #293 (run_number 293, id407) sha=`5a0aaa4` PASS ~5m00s (FE-User "Hồ sơ Nhân sự" 3-panel master-detail 5-tab rewrite + PE "Link hồ sơ" hyperlink NAS + rename "Dự trù PRO"→"Ngân sách PRO" — CROSS-STACK BE Mig 52 + FE×2; 2-commit `318860a`(FE-User EmployeesListPage) + `5a0aaa4`(PE cross-stack)):** Push `6ce5803..5a0aaa4` 15 files: BE `PurchaseEvaluation.cs`(+HoSoLink) + `PurchaseEvaluationConfiguration.cs` + `PurchaseEvaluationFeatures.cs`(Create/Update cmd +trailing optional `HoSoLink=null`) + `PurchaseEvaluationDtos.cs`(Detail DTO +hoSoLink) + Mig 52 `20260616035929_AddHoSoLinkToPurchaseEvaluation`(3-file) + FE `EmployeesListPage.tsx`(fe-user, 3-panel rewrite cây-tổ-chức+list+5-tab) + PE×2 (`PeDetailTabs.tsx`+`PeWorkspaceCreateView.tsx`+`types/purchaseEvaluation.ts` both apps, mục E hyperlink + label rename). `.cs`+`.tsx`+Mig → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (`buKL3...`). Run IN-PROGRESS first poll (running 11:15) — correctly did NOT verify-bundle-mid-flight (anti#3), polled iter5 status=success (started 11:13:57 → success 11:18:57 ≈5m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; BE Create/Update +trailing optional param=null ⟹ call-sites UNBROKEN) passed; `conclusion` empty — trust success). **★ MIGRATION APPLIED (the KEY): prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation`** ✓ (Mig 52; ADVANCED from prev top Mig 51 `AddDepartmentParentId`; DbInitializer auto-migrate on startup ran — api ready=200 confirms boot OK post-mig). Mig content = AddColumn `HoSoLink nvarchar(1000) maxLength=1000 NULL` (AddColumn-only, NO new table → **sys.tables stays 88** verified). DB-level: `sys.columns WHERE object_id=OBJECT_ID('PurchaseEvaluations') AND name='HoSoLink'` = 1 row ✓ (column physically present prod). **★ BUNDLE BOTH ROTATE (verified AFTER status=success +re-confirm STABLE 2nd-fetch no transient — anti#3): admin `Df06fmpq→BDwV5d0X`** ✓ (PE files shipped — was FROZEN since S67, now correctly rotated) **+ user `DxK3fCfh→DXkyUjtQ`** ✓ (EmployeesListPage + PE files shipped). Both required to rotate per spec → BOTH did. Health api live+ready **200/200** + admin/eoffice root 200. **★ NEW-FIELD live smoke (admin bearer, route `/api/auth/login` field `accessToken` len468):** `GET /api/purchase-evaluations/{48154149...}` (phiếu thật `PE/2026/A/015`) = **200** ✓ JSON detail carries `"hoSoLink":null` (key PRESENT, null cho phiếu cũ = backward-compat ✓ — Detail DTO wired AND Create/Update signature-change did NOT break GET path) · `GET /api/departments/tree` = **200** ✓ (Phase B dependency from Mig 51, still live). 0 regression. NO prod-data mutation (read-only GETs + sqlcmd SELECT-only). **LESSON: nullable-AddColumn cross-stack verify = (a) `__EFMigrationsHistory` top ADVANCED to new Mig (Mig 52) + api ready=200 (auto-migrate boot OK); (b) `sys.tables`=88 unchanged (AddColumn ≠ new table) + `sys.columns` confirms col present; (c) Detail DTO field-presence via authed GET on a REAL phiếu — `"hoSoLink":null` for old rows proves both DTO-wiring AND backward-compat (trailing-optional Create/Update param won't break GET — confirmed 200); (d) cross-stack BE+FE×2 = BOTH bundles rotate (admin un-froze from S67). Tokens empty: anon Gitea + prod-appsettings DB pw works (Bash tool is POSIX bash NOT PS despite shell=PowerShell env — `$env:`/`jq`/`python3` unavailable; parse via `tr ','|grep` + read pw via `ssh→appsettings`).** Tag `[s68, run293, pass, pe-hosolink-mig52-applied, fe-user-hoso-3panel-rewrite, pe-link-hoso-hyperlink, rename-dutru-to-ngansach, bundle-both-rotate-admin-unfroze, hosolink-null-in-dto-backward-compat, no-new-table-88, test263]`. - _(S67 Run #292 [dept-parentid Mig51-applied-after-#291-fail, retry-chain CS7036-fix, bundle-asymmetric, tree-endpoint-200] + S65 #289 [hrm-hoso public-readonly seed-only, asymmetric read200/write403] → FIFO-trimmed, full verbatim git `d2f52ba` + `archive/2026-06.md`)_ - **Older runs (S66 #290 ← S62 #286 06-13 ← … → S29 #232) full verbatim archived → git `d2f52ba` + `archive/2026-06.md`** (incl #291 06-16 FAIL forensic [lesson=gotcha #65 + #292-inline] + #383 ex-VITRILAC); pre-S38 → `archive/2026-05-{runs,q2,q3,q4}.md`. diff --git a/.claude/agent-memory/cicd-monitor/archive/2026-05.gist.md b/.claude/agent-memory/cicd-monitor/archive/2026-05.gist.md new file mode 100644 index 0000000..ee05b33 --- /dev/null +++ b/.claude/agent-memory/cicd-monitor/archive/2026-05.gist.md @@ -0,0 +1,59 @@ +--- +distill-gen: 1 +period: 2026-05 (S21→S37) +source-verbatim: archive/2026-05-q2.md (1) · 2026-05-q3.md (3) · 2026-05-q4.md (7) · 2026-05-runs.md (20 run-records + 6 [meta] curate-headers) +pointer-style: substring (Ctrl-F) back-resolve; each line names its FILE then → substring:"" +fields-per-line: VIỆC · KẾT-LUẬN(+commit/Mig) · BÀI-HỌC · BẤT-NGỜ · [confidence] +note: compression is a REPORTED number, not a target. Markers tagged ‹M#›. Even-older verbatim → git d2f52ba. +--- + +# CI/CD Monitor — GIST 2026-05 (4-field distilled) + +> Distilled from the four 2026-05 archive files. Open the named file + Ctrl-F the trailing substring for the full block. `‹M#›` = checklist-marker survival tag. + +## ★ gotcha #48 SQLite tie-break (the critical catch+fix pair) — q3 + runs.md + +- VIỆC ‹M1›: Run #215 FAIL → #216 PASS pair (S25 t1-t2, Plan AB changelog visibility refactor). KẾT-LUẬN ‹M1›: #215 `cdfd542` FAIL at test_infra (51/53) — 2 ReturnMode tests `Expected changelog.ContextNote not to be `; #216 `8c05947` PASS. ROOT CAUSE: `ApplyReturnModeAsync` adds NEW Changelog (EntityType=Workflow, NO ContextNote) in same SaveChanges as LogTransitionAsync (has ContextNote); test `.OrderByDescending(c=>c.CreatedAt).First()` with **SQLite + frozen test-clock → both rows SAME CreatedAt → tie-break returns WRONG (Workflow) entry**. FIX: add `.Where(c=>c.Summary!.Contains("Chuyển phase"))` discriminator BEFORE OrderByDescending (Plan AB code NOT reverted). BÀI-HỌC ‹M1›: **discriminator filter BEFORE OrderBy on frozen-clock ties**; UAT skip-test risky for >100-LOC BE refactor — CI caught BEFORE prod. BẤT-NGỜ: 8-min turnaround; test gate spared prod broken audit-trail. [cao] → file: archive/2026-05-q3.md · substring:"Run #215 FAIL → Run #216 PASS (gotcha #48 SQLite tie-break catch+fix pair)" +- VIỆC ‹M1›: [meta] curate-note for #215 (S25 t1 Plan AB FAIL). KẾT-LUẬN: records the gotcha #48 birth + 3 fix approaches. BÀI-HỌC ‹M1›: multi-Changelog.Add() in one txn + OrderByDescending(CreatedAt) tie risk in SQLite frozen-clock. BẤT-NGỜ: none (curate log). [vừa] → file: archive/2026-05-runs.md · substring:"added S25 t1 Plan AB Run #215 entry" + +## Per-NV opt-in wire — the 10-point checklist births (S23-S25) — runs.md + +- VIỆC: Run #194 (S23 t1 Plan K, Mig 31 per-Approver-slot refactor). KẾT-LUẬN: **PARTIAL** `098baa6` — Mig 31 + schema OK BUT K3 DTO-mirror INCOMPLETE: `AwLevelDto` missing `AllowApproverSkipToFinal` (only in a comment, not a record-param) + `ToDto` ctor missing it → FE Designer 7th checkbox ships but BE never returns value → no round-trip. BÀI-HỌC: schema-OK ≠ wired; check Application DTO + Handler expose the new column. BẤT-NGỜ: 7th-checkbox silently no-op. [cao] → file: archive/2026-05-runs.md · substring:"Run #194 id=308 sha=`098baa6`" +- VIỆC: Run #195 (S23 t1 K10 hotfix). KẾT-LUẬN: PASS `0062fcb` — AwLevelDto +AllowApproverSkipToFinal + ToDto + CreateAwLevelInput + handler init. BÀI-HỌC: **8-surface-point checklist** (AwLevelDto + CreateAwLevelInput were the 2 gaps em-main+Reviewer missed). BẤT-NGỜ: round-trip needs 4 edits across DTO/Input/Handler. [cao] → file: archive/2026-05-runs.md · substring:"Run #195 id=309 sha=`0062fcb`" +- VIỆC: Run #201 (S23 t4 Plan N HOTFIX). KẾT-LUẬN: PASS `fb3c22c` — per-NV lookup discrimination at `PurchaseEvaluationFeatures.cs:765` (`FirstOrDefault(Order==X && ApproverUserId==actor) ?? admin-fallback`). Live: NV-Test 7/7 TRUE vs admin 1/7 (fallback). BÀI-HỌC: **surface-point 9 = lookup discrimination in lookup-site handler**; bug 2 days prod silent (Mig 29 deploy → S23 t4 catch). BẤT-NGỜ: 3× refactor (Mig 29/30/31) all missed point 9. [cao] → file: archive/2026-05-runs.md · substring:"Run #201 id=315 sha=`fb3c22c`" +- VIỆC: Run #202 (S23 t5 Plan O HOTFIX). KẾT-LUẬN: PASS `a1c8386` — cascade 4 lookup-sites same pattern (Service:204, :258-260, DetailFeatures:75-76, Features:314-315). Live: NV-Test POST transition → **409 mode-mismatch NOT 403 actor-mismatch = discrimination active**. BÀI-HỌC: scan ALL `grep -n "FirstOrDefault.*Order.*==" *.cs` after OR-of-N refactor; surfaced pre-existing TransitionPeBody schema gap. BẤT-NGỜ: 3-day prod-bug latency. [cao] → file: archive/2026-05-runs.md · substring:"Run #202 id=316 sha=`a1c8386`" +- VIỆC: Run #203 (S23 t6 Plan P HOTFIX). KẾT-LUẬN: PASS `1727bd5` — Controller `TransitionPeBody` record +3 fields (ReturnMode/ReturnTargetUserId/SkipToFinal). Live: admin numeric `4` → 204; string `"Drafter"` → 400. BÀI-HỌC: **point-10 = Controller body record param-count MUST mirror Command record**; **Discovery#4 = ASP.NET10 record-with-enum needs NUMERIC input (no JsonStringEnumConverter registered)**. BẤT-NGỜ: missing body fields silently `default` (no 400) → FE wire dead 2 prod days. [cao] → file: archive/2026-05-runs.md · substring:"Run #203 id=317 sha=`1727bd5`" +- VIỆC: Run #200 (S23 t3 Plan M, Service F1 OneLevel/OneStep edge-case Bước1 reset). KẾT-LUẬN: PASS `f4055a1`, Mig31 unchanged. BÀI-HỌC ‹M3›: **Discovery#3 anomaly — docs-only TIP `f4055a1` STILL triggered CI** (reinforces push-RANGE eval not just tip). BẤT-NGỜ: anomaly is BENEFICIAL (catches verify on docs commits). [cao] → file: archive/2026-05-runs.md · substring:"Run #200 id=314 sha=`f4055a1`" + +## S25 FE audit-recovery + S24 filter/menu (runs.md) + +- VIỆC: Run #221 (S25 t7 Plan AF FE userMap fallback). KẾT-LUẬN: PASS `506cada`, bundle rotate `C8TvDy7r`/`BvcWrq2z`. BÀI-HỌC: FE-only fallback Map from embedded bundle (drafter+approvals+approvers+opinions); **Discovery: 503 mid-deploy expected (app-pool recycle ~5-15s, self-recovers); auth route `/api/auth/login` field `accessToken` NOT `token`**. BẤT-NGỜ: /api/v1/auth/login = 404. [cao] → file: archive/2026-05-runs.md · substring:"Run #221 id=335 sha=`506cada`" +- VIỆC: Run #220 (S25 t6 Plan AE Changelog UserName 9-sites batch). KẾT-LUẬN: PASS `9ea62be`, BE-only bundle FROZEN. BÀI-HỌC: preventive batch-fix ALL Changelog.Add() sites (PE=9), not just the 1 reported; `UserName = currentUser.FullName ?? Email`. BẤT-NGỜ: stale empty-userName rows are pre-fix history (forward-only fix). [vừa] → file: archive/2026-05-runs.md · substring:"Run #220 id=334 sha=`9ea62be`" +- VIỆC: Run #219 (S25 t5 Plan AD) + #218 (S25 t4 Plan AC2). KẾT-LUẬN: both PASS (`0aaf2df`, `25837b6`), bundles rotate. BÀI-HỌC: FE synthetic-rows from Changelog entityType=5 + ContextNote keyword; parse comment for semantic next-target hint (reusable Contract/Budget audit recovery). BẤT-NGỜ: dual-phase badges mostly self-loop noise (ChoDuyet→ChoDuyet). [vừa] → file: archive/2026-05-runs.md · substring:"Run #219 id=333 sha=`0aaf2df`" +- VIỆC: Run #210 (S24 t1 Plan AA, IsUserSelectable filter + Pe_DuyetNcc_WfView menu + sidebar). KẾT-LUẬN: PASS `ac2c859`, 4/4 wire, bundle rotate `Dmk--X6w`/`Bd4gh3Tp`. BÀI-HỌC: new-filter-param + new-menu-key + Order-shift-idempotent + **non-admin Read 200 NOT 403** (gotcha #44 any-auth read). BẤT-NGỜ: none. [vừa] → file: archive/2026-05-runs.md · substring:"Run #210 id=324 sha=`ac2c859`" +- VIỆC: Run #214 (Plan AA wrap fix sidebar), #209 (Plan U truncate), #208/#207 (Plan T DemoSeed disable + wipe ~720 rows), #204 (Plan Q FE banner). KẾT-LUẬN: all PASS (`ee0902a`,`86d8806`,`7b7b28f`,`0b97840`,`108268a`). BÀI-HỌC: **DemoSeed:Disabled flag gates 5 demo-seed methods (NOT infra seed) — proven end-to-end via force-recycle no-reseed**; #214 = baseline before Plan AB fail. BẤT-NGỜ: appsettings.Production.json is .gitignore'd → flag default committed in appsettings.json. [vừa] → file: archive/2026-05-runs.md · substring:"Run #207 sha=`0b97840`" + +## Foundation + Discovery births (S21-S22) — runs.md + q3 + +- VIỆC ‹M3›: S22-chốt cumulative verify. KẾT-LUẬN: PASS, 104 test, Mig30. BÀI-HỌC ‹M3›: **Discovery#3 push-range eval — `b079b27`(BE Mig30)+`b04a11a`(FE) pushed batched → single Run on TIP**; #190 CANCELLED by concurrency-supersede (next push within 3min) = NORMAL not fault. BẤT-NGỜ: `**/*.md` glob matches `.claude/agent-memory/*.md` at any depth → those commits DO skip (gotcha #47 disproven for .md, kept preventive for non-.md state files). [cao] → file: archive/2026-05-runs.md · substring:"Verify S22 chốt cuối cumulative (push range `3d725c4..cc8a7d3` 12 commits) VERDICT=PASS" +- VIỆC: Run #188 (S22 Plan D/C/E). KẾT-LUẬN: PASS `a74e671`, 103 test, Plan E strict-V2-scope (nv.test 8 < admin 17). BÀI-HỌC: **Discovery#1 rate-limit 429 at ~5 login/min → cache token across endpoints**; **Discovery#2 `.claude/agent-memory/**` NOT in paths-ignore** (later refined — `**/*.md` still catches .md). BẤT-NGỜ: token-cache pattern avoids 429. [vừa] → file: archive/2026-05-runs.md · substring:"Run #188 id=302 sha=a74e671" +- VIỆC: Run #187 (S21 t5 Mig 29 Allow*-per-NV refactor + Designer 5-checkbox). KẾT-LUẬN: PASS `c0af9e0`, Mig29 applied (Levels +5 Allow*, Users +1, Workflows −6 dropped). BÀI-HỌC: **Discovery — Gitea task-table `updated_at` stale ~2min → cross-check VPS file mtime** (gotcha #46). BẤT-NGỜ: backfill 48/48 Levels correct, 0/13 Users (preserve). [vừa] → file: archive/2026-05-runs.md · substring:"Run #187 id=301 sha=c0af9e0" +- VIỆC: Run #186 (S21 t3+t4 gotcha#45 Trả-lại + F1/F2/F3 advanced-options + Mig 28). KẾT-LUẬN: PASS `eea86fd` baseline 3m32s, 84 test. BÀI-HỌC: **Discovery — Gitea Actions list endpoint = `/api/v1/repos/.../actions/tasks` NOT `/actions/runs` (404); public no-auth read OK**. BẤT-NGỜ: none. [cao] → file: archive/2026-05-runs.md · substring:"Run #186 id=300 sha=eea86fd" +- VIỆC: Setup baseline (agent init) + Verify-S22 (q3 copy, Discovery#3 first-surfaced). KẾT-LUẬN: init/PASS. BÀI-HỌC: 5-stage checklist + bundle-hash-verify pattern established; S22-verify also lives in q3 (cross-file dup with runs.md — keyed distinctly). BẤT-NGỜ: none. [thấp] → file: archive/2026-05-q3.md · substring:"2026-05-12 — Setup baseline" + +## Plan B Contract V2 + Hrm/Office (S29-S37) — q2 + q4 + +- VIỆC ‹M2›: Run #231 Plan B Contract V2 wire kick-off. KẾT-LUẬN: **PARTIAL** `3e92584` — PASS deploy (Mig 32+33 applied, bundles rotate) + seed-gap: `SeedSampleContractWorkflowV2` nested in DemoSeed gate → QT-HD-V2-001 NOT seeded → FE dropdown EMPTY → V2 not UAT-testable. Resolved Run #232 (`38f1c4d`, carve out of gate). BÀI-HỌC ‹M2›: **gotcha #51 birth — infra seed must not sit under demoSeedDisabled**. BẤT-NGỜ: ~150 LOC ApproveV2Async shipped with 0 test (gotcha #48 high-risk pattern). [cao] → file: archive/2026-05-q2.md · substring:"Run #231 (id=345) Plan B Contract V2 wire kick-off VERDICT=PARTIAL" +- VIỆC ‹M2›‹M22›: Run #350 (S33 Mig 34 EmployeeProfile + Plan C BW1-BW7 +9 test). KẾT-LUẬN: PASS `48a99e1`, test_infra 62. BÀI-HỌC ‹M2›: **gotcha #51 INFRASTRUCTURE-seed verify — SeedDemoEmployeeProfilesAsync correctly NOT gated; EmployeeProfiles=33 (16 demo+14 Solutions+3 admin)**. BẤT-NGỜ ‹M22›: **mem-labeled "#350" ≠ real Gitea run_number** (this is a memory-id; reconcile via run_number). [cao] → file: archive/2026-05-q4.md · substring:"Run #350 (S33 Plan B G-H1 Mig 34 EmployeeProfile" +- VIỆC: Run #237 (Hrm CQRS /api/employees + FE×2) + #238 (Danh-bạ /api/directory G-O1). KẾT-LUẬN: both PASS (`79a8343`,`ea440da`), bundles rotate. BÀI-HỌC: **BE namespace mới `SolutionErp.Application.Office` → MediatR auto-discovery WORKS (no manual registration, no gotcha #1)**; menu-seed Off/Off_DanhBa + Permissions auto-grant. BẤT-NGỜ: directory 34 rows @solutions.com.vn populated. [vừa] → file: archive/2026-05-q4.md · substring:"Run #238 (S34 Plan 2 G-O1 Danh bạ nội bộ" +- VIỆC: Run #222-#227 (S26 Plan AG PE-List tree-view UI iteration). KẾT-LUẬN: PASS `0bf6c7e`, bundle rotate. BÀI-HỌC: **hybrid-verify — spawn CICD-monitor 1× at Phase-wire start (ROI good for solo-dev iteration); polish chunks em-main self-verify (avoid re-spawn ~150K × N)**. BẤT-NGỜ: none. [vừa] → file: archive/2026-05-q4.md · substring:"Run #222-#227 cumulative — Plan AG series PE List tree view" +- VIỆC: S33 startup health-check [audit] + S32 wrap [meta] + S32 startup [audit]. KẾT-LUẬN: HEALTHY/no-run. BÀI-HỌC: **Discovery#7 — `eval/**` MISSING from paths-ignore → RAG eval-JSON commits trigger wasteful ~3m30s deploy**; cert api.solutions.com.vn notAfter 2026-07-23 (auto-renew ~06-23). BẤT-NGỜ: RAG-healthy 2949 chunks. [thấp] → file: archive/2026-05-q4.md · substring:"S33 startup health-check — em main spawn read-only verify, VERDICT=HEALTHY" + +## Curate-note meta-headers (the remaining 5 [meta]) — runs.md + +- VIỆC: 5 S40-curate notes (S23 t5 #202, t6 #203, S24 t1 #210, S25 t3 #217, t5 #219). KẾT-LUẬN: [meta] curate logs (thin — point to the rich run-record above). BÀI-HỌC: #217 note carries **Discovery#5 — sqlcmd Windows-auth over ssh needs `\\\\SQLEXPRESS` 4-backslash** (`\\SQLEXPRESS` = 0 silent output). BẤT-NGỜ: none. [thấp] → file: archive/2026-05-runs.md · substring:"added S25 t3 Plan AC Run #217 entry" + +## ‹M7› ‹M19› cross-period markers (live in L1 foundation + spanning) + +- VIỆC ‹M7›: gotcha #25 IIS WebSocket. KẾT-LUẬN: `notification-hub/negotiate` 401/404 prod → fix = enable WebSocket module in `web.config` site api (skill iis-deploy-runbook). BÀI-HỌC ‹M7›: SignalR negotiate failure = IIS WebSocket module not enabled. BẤT-NGỜ: this pattern lives in L1 MEMORY.md foundation (Stage 4), not in a dated 2026-05 record — recorded here for marker survival. [cao] → file: (L1) MEMORY.md · substring:"notification-hub/negotiate" +- VIỆC ‹M19›: BUNDLE-HASH lineage chain admin/user. KẾT-LUẬN: rotate chain spans #247→#308 across both gist periods; each record's bundle-hash transition is load-bearing ship-proof. BÀI-HỌC ‹M19›: every bundle-hash transition MUST survive — admin/user FE hashes are the canonical ship-evidence (e.g. early `CzesdXLh`/`DP-gH4LW` → … → live `BgNCjwsG`/`CBvh0vtf`). BẤT-NGỜ: prod CI hash ≠ local-build hash is NORMAL (CI rebuild). [cao] → file: archive/2026-05-runs.md · substring:"Run #186 id=300 sha=eea86fd" diff --git a/.claude/agent-memory/cicd-monitor/archive/2026-06.gist.md b/.claude/agent-memory/cicd-monitor/archive/2026-06.gist.md new file mode 100644 index 0000000..c34e12f --- /dev/null +++ b/.claude/agent-memory/cicd-monitor/archive/2026-06.gist.md @@ -0,0 +1,65 @@ +--- +distill-gen: 1 +period: 2026-06 (+ tail of late-05 runs that live in 2026-06.md) +source-verbatim: archive/2026-06.md (39 records, FROZEN — do NOT re-compress this gist) +pointer-style: substring (Ctrl-F) back-resolve into archive/2026-06.md; each line ends → substring:"" +fields-per-line: VIỆC · KẾT-LUẬN(+commit/sys-truth) · BÀI-HỌC · BẤT-NGỜ · [confidence] +note: compression is a REPORTED number, not a target — every marker below is preserved in full-enough words to re-identify. Markers tagged ‹M#›. +--- + +# CI/CD Monitor — GIST 2026-06 (4-field distilled, read before opening verbatim) + +> Distilled from `archive/2026-06.md`. Open that file + Ctrl-F the trailing substring to read the full run-record. `‹M#›` = checklist-marker survival tag (Stage-C gate, 22 total). + +## PE cross-stack + budget (S60-S62) + +- VIỆC: PE "vượt ngân sách" soft-warn, PeWorkItemBudgets per-gói-thầu (Mig 50 replaces Budget module). KẾT-LUẬN: PASS, commit `7926c21`; **sys.tables 93→88** post-S61 Budget-drop, `sys.tables(is_ms_shipped=0, excl mighist)=88` correct. BÀI-HỌC: when commit touches no schema, 88 is right — don't FAIL on the 88-vs-93 (88↔93) ambiguity, always cross-ref COMMIT-scope vs ambient count. BẤT-NGỜ ‹M20›: table-count 88-vs-93 — narrative-93 is STALE pre-S61; S61 Budget-replace DROPPED 93→88; `sys.tables(is_ms_shipped=0)=88` correct post-S61. [cao] → substring:"Run #286 (run_number 286, id400)" +- VIỆC: PE guard 4-thông-tin mục 3 cross-stack. KẾT-LUẬN: PASS commit `37122f0`. BÀI-HỌC: cross-stack guard verify. BẤT-NGỜ ‹M21›: **PE code column = `MaPhieu` NOT `Code`** (custom naming — sqlcmd must SELECT MaPhieu). [vừa] → substring:"Run #283 (run_number 283) sha=`37122f0`" +- VIỆC: S59 series — đợt6 cross-stack BE (`9c330d2`), đợt5 FE×2 (`faed59f`), đợt3 BE-only DbInitializer (`bbd1554`), đợt2 FE×2 PE-list tree (`0eafcd3`), CLOSE đóng sổ (`69997da`). KẾT-LUẬN: all PASS. BÀI-HỌC: multi-đợt same-session FIFO; BE-only=both-bundle-frozen, FE×2=both-rotate. BẤT-NGỜ: none. [vừa] → substring:"Run #280 (run_number 280) sha=`69997da`" +- VIỆC ‹M18›: S59 FE×2 PE-list tree regroup (`56882ac`), in the post-wipe window after the DemoSeed tables were cleared. KẾT-LUẬN: PASS commit `56882ac`. BÀI-HỌC ‹M18›: **post-wipe deploy = verify BOTH halves — demo tables stay 0 after app-pool recycle (no accidental re-seed) AND infrastructure/master untouched** (don't check only one side). BẤT-NGỜ: recycle did NOT re-populate demo rows (DemoSeed:Disabled holds). [cao] → substring:"Run #273 (run_number 273) sha=`56882ac`" +- VIỆC: S59-đợt4 rename WorkItems → 71 hạng mục via prod-SQL + idempotent-seed. KẾT-LUẬN: PASS commit `c869d26`, WorkItems count=71 EXACT no-dup. BÀI-HỌC ‹M17›: **rename-via-prod-SQL + idempotent-seed = verify target count EXACT; double = re-added-dup = FAIL** (71-no-dup). BẤT-NGỜ: none. [cao] → substring:"Run #276 (run_number 276) sha=`c869d26`" + +## #291 FAIL forensic — the CS7036 class (S?, 06-16) + +- VIỆC ‹M6›‹M8›‹M9›: TEST-GATE COMPILE BREAK. KẾT-LUẬN: **FAIL ~64s**, commit `8c8179c`; **CS7036** — `CreateDepartmentCommand` record gained REQUIRED 5th positional `ParentId`; un-updated call site **`MasterCatalogFilteredUniqueTests.cs:63`** (old 4-arg) breaks whole slnx build → deploy GATED, prod stayed baseline (bundles FROZEN + Mig NOT applied). BÀI-HỌC ‹M6›: this is **gotcha #65** root-cause canonical = spec-change-miss class (record ctor grows positional param → stale call-site). BẤT-NGỜ: green-elsewhere but one stale test call-site fails entire build; deploy-gate protected prod. [cao] → substring:"Run #291 (run_number 291, id405) sha=`8c8179c`" + +## Lock/auth quirks (S58) + +- VIỆC ‹M15›: S58 FIX the #381 lock NO-OP. KẾT-LUẬN: PASS commit `5998163`; lock-noop → pw-11to12. BÀI-HỌC ‹M15›: **lock/deactivate-by-email returning 0/-1 = dump actual Users set BEFORE scoring FAIL** (don't assume target row exists). BẤT-NGỜ ‹M21›: custom Identity table = **`Users` NOT `AspNetUsers`**. [cao] → substring:"Run #382 (run_number 268) sha=`5998163`" +- VIỆC: S57bis PE gắn WorkItemId loose-Guid no-FK (Mig 49). KẾT-LUẬN: **PASS+1PARTIAL** commit `dd117b7`. BÀI-HỌC: AddColumn+CreateIndex no-new-table. BẤT-NGỜ: partial on one axis. [vừa] → substring:"Run #381 (run_number 267) sha=`dd117b7`" + +## Supersede-chain + bundle-frozen/asymmetric/rotate discipline (S55-S59) + +- VIỆC ‹M10›‹M22›: SUPERSEDE-CHAIN benign. KẾT-LUẬN: same-SHA run flipped to CANCELLED mid-flight, shipped via `3ebaf…` (mem id #385/#386). BÀI-HỌC ‹M10›: **same-SHA run flipping to cancelled = Gitea concurrency-supersede by newer push, NOT fault; verify `merge-base --is-ancestor` + diff-empty + the SUCCESSFUL run, do NOT escalate**. BẤT-NGỜ ‹M22›: mem-labeled run ids (#385/#386) differ from real Gitea task ids — reconcile via run_number. [cao] → substring:"Run #385→#386 SUPERSEDE-CHAIN sha=`ea793a4`" +- VIỆC ‹M13›: S58 FE-USER visual redesign. KẾT-LUẬN: PASS commit `e959f72`, asymmetric fe-user-only rotate. BÀI-HỌC ‹M13›: **asymmetric single-app FE: changed-app hash MUST rotate AND other-app MUST stay frozen** (#384 fe-user-only ↔ #378 fe-admin-only — direction flips). BẤT-NGỜ: none. [cao] → substring:"Run #384 (run_number 270) sha=`e959f72`" +- VIỆC ‹M12›: S56 GOLIVE-HARDEN BE fixes. KẾT-LUẬN: PASS commit `a20cde8`. BÀI-HỌC ‹M12›: **BE-only bundle FROZEN = correct, NOT ship-fail; ship-proof = Mig-applied / test-count** (not bundle-rotate). BẤT-NGỜ: none. [cao] → substring:"Run #379 (run_number 265) sha=`a20cde8`" +- VIỆC: S56 pre-golive re-verify prod truth. KẾT-LUẬN: [audit] no-deploy read-only. BÀI-HỌC: read-only audit pattern (no ship event). BẤT-NGỜ: none. [thấp] → substring:"S56 pre-golive verify — NO deploy" +- VIỆC ‹M13›: S55 Phase-1 FE-Admin visual redesign. KẾT-LUẬN: PASS commit `7feb53e`, asymmetric fe-admin-only rotate / other frozen. BÀI-HỌC ‹M13›: changed-app rotate + sibling frozen (the admin-side mirror of #384). BẤT-NGỜ: none. [cao] → substring:"Run #378 (run_number 264) sha=`7feb53e`" +- VIỆC ‹M14›: S55 HMW-P4 real master-data seed (Mig 48: 62 dự án + 71 hạng mục + 3 NCC). KẾT-LUẬN: PASS commit `69cb393`. BÀI-HỌC ‹M14›: **ungated SeedRealMasterDataAsync verify = sqlcmd COUNT spot-check + `=N'…'` EXACT for unicode** (reaches prod by design, idempotent per-code). BẤT-NGỜ: none. [cao] → substring:"Run #377 (run_number 263) sha=`69cb393`" + +## SLA / IT-ticket / 411 (S54) + +- VIỆC ‹M16›: S54 IT-staff self-reassign ticket. KẾT-LUẬN: PASS commit `ca4b602`. BÀI-HỌC ‹M16›: **411-vs-401 — unauth bodyless PUT/POST returns 411 from IIS BEFORE [Authorize]; resend `-d {}` (empty body) to get real 401** (paired with #367). BẤT-NGỜ: 411 ≠ unhealthy, it's Content-Length-Required pre-auth. [cao] → substring:"Run #376 (run_number 262) sha=`ca4b602`" + +## Earlier P11 / Office / Holiday (S42-S52, dates 05-28→06-08) + +- VIỆC ‹M3›‹M5›‹M11›: S50 HMW-Wave2 P11-C Vehicle+Driver (Mig 44/45). KẾT-LUẬN: PASS commit `30a99aa`; saw a mid-deploy transient bundle hash (`CVbyotwa`) before the final one settled. BÀI-HỌC ‹M3›: **gotcha #41 Discovery#3 — Gitea evaluates push RANGE; ≥1 non-ignored file ⟹ whole range builds** (mixed docs+tsx NOT skipped). ‹M5›: **gotcha #57 — soft-delete UNIQUE index MUST filter `WHERE [IsDeleted]=0`** (Mig 44/45 filtered-unique). ‹M11› anti-pattern timing: **NEVER trust bundle hash until status=success — mid-deploy shows a transient 3rd hash** (#371 transient `CVbyotwa`, #378); always re-confirm hash AFTER status=success + stable 2nd-fetch (anti#3). BẤT-NGỜ: 3rd transient hash exists only during the deploy window. [cao] → substring:"Run #371 (run_number 257) sha=`30a99aa`" +- VIỆC: S48 FE-only login subtitle a11y (`350b2bf`); S45 Mig 43 filter Holiday UNIQUE (`0c5a014`); S42 P11-B LeaveBalance (`82d7fcf`); S42 P11-A workflow-picker 2-bug + SetWorkflow (`75df04e`). KẾT-LUẬN: all PASS. BÀI-HỌC: Holiday filtered-unique = same #57 family; FE-only login = bundle-rotate-only. BẤT-NGỜ: none. [vừa] → substring:"Run #368 (run_number 254) sha=`0c5a014`" +- VIỆC ‹M22›: S42 P11-A wire ApproveV2 + LevelOpinions. KẾT-LUẬN: PASS commit `e7b66cd`. BÀI-HỌC: V2 approve wire. BẤT-NGỜ ‹M22›: **mem-labeled "#250" ≠ real Gitea id — reconcile via run_number** (here run_number not in mem label; trust git sha). [vừa] → substring:"Run #364 (mem #250) sha=`e7b66cd`" +- VIỆC: S38 SKELETON 5-plan combo (Mig 39+40 dual). KẾT-LUẬN: PASS commit `e54a22d`. BÀI-HỌC: dual-migration single deploy. BẤT-NGỜ: none. [thấp] → substring:"Run #247 sha=`e54a22d`" +- VIỆC ‹M4›: ⚠️ VỊ-TRÍ-LẠC entry (FIFO slot between #384/#382; carries #383 dual-role menu detail). KẾT-LUẬN: note. BÀI-HỌC ‹M4›: **gotcha #44 — dual-role menu-tree `/api/menus/me` for nv.test** (Drafter+CCM must merge both roles' menu keys; #383/#381). BẤT-NGỜ ‹M22›: mem run id ≠ real Gitea — this entry was mis-placed in FIFO. [vừa] → substring:"VỊ TRÍ LẠC — entry MỚI 2026-06-11" +- VIỆC: S37 Proposal Mig 37+38 (/api/proposals 200 + QT-DX-V2-001 AppType=4). KẾT-LUẬN: PASS [archived]. BÀI-HỌC: AppType=4 seed reach prod. BẤT-NGỜ: none. [thấp] → substring:"Archived Run #246 (S37 Proposal Mig 37+38" +- VIỆC ‹M2›: S29 gotcha #51 catch. KẾT-LUẬN: PASS [archived]. BÀI-HỌC ‹M2›: **gotcha #51 — INFRASTRUCTURE seed (SampleWorkflowsV2) must NOT be nested in `if(!demoSeedDisabled)`; symptom = empty V2 dropdown; hoist fix**. BẤT-NGỜ: demo-flag silently gated an infra seed. [cao] → substring:"Archived Run #232 (S29 gotcha #51 catch" + +## Curated-down from L1 this session (S68-S78, dates 06-16/06-17) — the 10 moved records + +- VIỆC ‹M19›: CROSS-STACK PE HoSoLink (Mig 52) + FE-User Hồ-sơ-NS 3-panel + rename Dự-trù→Ngân-sách. KẾT-LUẬN: PASS commit `5a0aaa4`; **Mig 52 applied** (history-top advanced 51→52), `sys.columns` HoSoLink present, **no-new-table 88**; bundle BOTH rotate `BDwV5d0X`/`DXkyUjtQ`. BÀI-HỌC ‹M19›: bundle-hash lineage load-bearing (admin un-froze); nullable-AddColumn cross-stack verify = history-top advance + sys.tables-unchanged + Detail-DTO field-presence via authed GET on REAL phiếu. BẤT-NGỜ: `hoSoLink:null` for old rows proves DTO-wiring AND backward-compat. [cao] → substring:"Run #293 (run_number 293, id407) sha=`5a0aaa4`" +- VIỆC ‹M13›: FE-User Hồ-sơ-NS 2-col layout. KẾT-LUẬN: PASS commit `456c7a7`; asymmetric user-rotate `DbVv6rsf` / admin frozen; no-mig stays Mig52. BÀI-HỌC ‹M13›: 3rd-consecutive same-page FE-user — re-rotate same page each deploy = NORMAL, sibling-frozen-when-untouched. BẤT-NGỜ: tokens-present this run (vs empty other runs) — both auth paths work. [cao] → substring:"Run #295 (run_number 295, id409) sha=`456c7a7`" +- VIỆC ‹M13›: FE-User Hồ-sơ-NS cosmetic brand800. KẾT-LUẬN: PASS commit `ab4e681`; asymmetric user-rotate `BumgrwCJ` / admin frozen. BÀI-HỌC: **health = `/health/ready`+`/health/live` (200×2), NOT `/health`** (404 ≠ unhealthy — cross-check skill before flag). BẤT-NGỜ: `/health` literal is wrong-path. [cao] → substring:"Run #297 (run_number 297, id411) sha=`ab4e681`" +- VIỆC ‹M13›: FE-Admin MIRROR Hồ-sơ-NS from fe-user (asymmetric NGƯỢC). KẾT-LUẬN: PASS commit `292d64d`; admin ROTATE `xkSz9BfE` / user FROZEN (direction flips vs prior deploy). BÀI-HỌC ‹M13›: deploy-2 same-session asymmetric-NGƯỢC = verify prior deploy NOT cancelled + this-app-rotate/other-frozen; admin un-froze from multi-session frozen-streak = EXPECTED. BẤT-NGỜ: **poll-parser bug — `tr,|grep -A` on JSON misanchors → re-query with python3; parser-bug ≠ run-stuck**. [cao] → substring:"Run #298 (run_number 298, id412) sha=`292d64d`" +- VIỆC ‹M12›: TESTS-ONLY BE +23 test (→286). KẾT-LUẬN: PASS commit `bcd619d`; bundle BOTH FROZEN `xkSz9BfE`/`BumgrwCJ`; CI test-gate runs both projects BEFORE build ⟹ status=success ⟹ 23 new passed (286 INFERRED not log-count). BÀI-HỌC ‹M12›: BE-only ⟹ both bundles MUST stay frozen (rotate=anomaly); ship-proof=test-count not bundle. BẤT-NGỜ: **`python3` BROKEN on box (ZKBioTime embed SRE-module-mismatch) → use PowerShell Invoke-RestMethod**. [cao] → substring:"Run #299 (id413) sha=`bcd619d`" +- VIỆC: FE-both-app PE Link-hồ-sơ `file://` render upgrade (keep Copy fallback). KẾT-LUẬN: PASS commit `536dd6b`; bundle BOTH rotate `CcrZqfht`/`DniDFUB_`; tables88. BÀI-HỌC: pure-FE-both-app = both rotate; `file://` browsers may block from https-origin (hence Copy fallback) — not curl-verifiable. BẤT-NGỜ: none. [vừa] → substring:"Run #302 (run_number 302, id416) sha=`536dd6b`" +- VIỆC: FE-both-app Hồ-sơ-NS banner text-polish. KẾT-LUẬN: PASS commit `6983609`; bundle BOTH rotate `D532XZKG`/`CuFaBoWt`. BÀI-HỌC ‹M3›: **docs-files in same push-range as .tsx do NOT suppress build** (range any-non-ignored ⟹ build — Discovery#3 corollary). BẤT-NGỜ: none. [cao] → substring:"Run #303 sha=`6983609` PASS" +- VIỆC ‹M13›: FE-both-app 1-line CSS-precedence fix (name `text-white`→`text-white!`). KẾT-LUẬN: PASS commit `37752eb`; bundle BOTH rotate `CNUv1jxY`/`CpOskeS1`; tables88. BÀI-HỌC: even 1-line both-app change → new content-hash both; Tailwind-v4 @layer precedence (unlayered h2 beat text-white). BẤT-NGỜ: SHA256-identical-between-2-apps is a SOURCE claim (git), not runtime DOM-equality. [cao] → substring:"Run #304 (run_number 304, id418) sha=`37752eb`" +- VIỆC: S69 FE-both-app Văn-phòng-số foundation + index.css sync + BE menu-seed `Off_Dashboard` (NO-EF-mig). KẾT-LUẬN: PASS commit `a8bbdae`; bundle BOTH rotate `Bl2o_kUq`/`BImrKQNn`; **no-mig top stays Mig52** (menu-seed = DbInitializer runtime row-insert NOT migration); menu-seed verified via MenuItems SELECT; office-hidden = perm row only-Admin 1/13. BÀI-HỌC: prod CI bundle-hash ≠ local-build-hash is NORMAL (CI rebuild) — FAIL only if NOT-rotated-from-baseline. BẤT-NGỜ: sqlcmd string-literal doubled `''x''` BREAKS in PS → build via `[char]39` concat. [cao] → substring:"Run #305 (run_number 305, id419) sha=`a8bbdae`" +- VIỆC: S70 FE-only re-skin Văn-phòng-số 10-page PURO. KẾT-LUẬN: PASS commit `c556f6c`; bundle BOTH rotate `Wt54PHYl`/`B99fMU6X`; office-API live (proposals/it-tickets/meeting-rooms/employees 200); office-hidden confirmed `Off_Dashboard` admin-only / Drafter CanRead=0. BÀI-HỌC: **CanRead=0 perm-ROWS exist but ≠ access** (menu gates on CanRead=1) — must filter CanRead=1, existence≠access; office-hidden is AMBIENT (FE-only can't touch Permissions seed). BẤT-NGỜ: `/api/workflow-apps`→404 = wrong-route-guess NOT regression (FE-only can't change BE routing). [cao] → substring:"Run #306 (run_number 306, id420) sha=`c556f6c`" diff --git a/.claude/agent-memory/cicd-monitor/archive/2026-06.md b/.claude/agent-memory/cicd-monitor/archive/2026-06.md index 7815d26..ed9a208 100644 --- a/.claude/agent-memory/cicd-monitor/archive/2026-06.md +++ b/.claude/agent-memory/cicd-monitor/archive/2026-06.md @@ -35,3 +35,16 @@ - **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. - **[⚠️ VỊ TRÍ LẠC — entry MỚI 2026-06-11, thuộc FIFO slot giữa #384/#382 phía trên nhưng ghi lạc vào khu archive-zone này; curate-L2 ĐỪNG archive nhầm (H2 S58 P2 flagged); relocate khi curate] 2026-06-11 Run #383 (run_number 269) sha=`6c5fd26` PASS ~4m25s (S58b TẠM ẨN HRM/Văn phòng số/Cá nhân khỏi non-Admin + Danh mục xuống cuối — BE-only seed, NO Mig/FE):** Push `2aefb31..6c5fd26` 1 file `DbInitializer.cs` (+61/-5): NEW `RevokeTemporarilyHiddenModulesAsync` (set 4 CRUD=false MỌI role TRỪ Admin trên `Hrm%`+`Off%`+`Personal`, idempotent, KHÔNG xóa row) + `SeedAllRolesReviewReadPermissionsAsync` scope THU HẸP còn Master/Catalogs/Pe_* + menu `Master` Order 20→80. `.cs` present → full pipeline RAN. Run IN-PROGRESS at first check (status=running 13:36) — correctly did NOT FAIL, polled to terminal (started 13:36:15 → success 13:40:40 iter5). **Bundle FROZEN admin `CP4CB1ym` + user `BmZ3VHnm`** (= Run #382 UNCHANGED ✓ CORRECT BE-only — verified AFTER status=success, NOT ship-fail). **NO Mig** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo, GIỮ NGUYÊN ✓. Health live/ready 200 + admin/eoffice root 200. **6 prod sqlcmd ALL PASS exact (DB SolutionErp, custom tables `MenuItems`/`Permissions`/`Roles`):** MasterOrder=**80** ✓ · HiddenReadNonAdmin=**0** ✓ (revoke landed, `Hrm%`+`Off%`+`Personal` all CRUD=false non-Admin) · HiddenReadAdmin=**29** (>0, Dev est 28, Admin GIỮ ✓) · PeCreateNonAdmin=**120** (Pe_* untouched ✓) · MasterReadNonAdmin=**48** (Master still visible ✓). **Menu-tree smoke (gotcha #44 dual-role):** nv.test `/api/menus/me` 200 → keys = Master/Suppliers/Projects/Departments + Catalogs/Catalog* + all Pe_DuyetNcc*/Pe_DuyetNccPhuongAn* — **ZERO Hrm/Hrm_*/Off/Off_*/Personal** ✓; admin counter-check VẪN CÒN Hrm/Hrm_Config*/Off/Off_*/Personal + Contracts/Budgets/System ✓ (revoke scoped non-Admin only). 0 regression. **LESSON (seed-only permission-revoke verify = sqlcmd matrix + dual-role menu-tree, NOT bundle/endpoint):** RevokeTemporarilyHiddenModulesAsync runs UNCONDITIONALLY on startup (NOT gated — correct, it's a permission-correction not demo-seed); verify = COUNT(CanRead non-Admin)=0 for revoked-prefix + COUNT Admin>0 (kept) + COUNT untouched-scope (Pe/Master) unchanged + `/api/menus/me` key-set diff between regular-user (modules gone) and admin (modules present). FE has NO PermissionGuard per-route (commit note) → direct-URL still renders trang, mức "tạm ẩn" = menu hide + permission matrix only; acceptable pre-golive. Tag `[s58b, run383, pass, revoke-hidden-modules, master-order-80, be-only-bundle-frozen, no-mig, dual-role-menutree]`. - **Archived Run #232 (S29 gotcha #51 catch — SeedSampleContractWorkflowV2 nested in demoSeedDisabled → empty V2 dropdown, hoist fix) → `archive/2026-05-q4.md` + git. Smart Friend ROI 4× cumulative (S22 #44 + S25 #48 + S29 ApplicableType + S29 DemoSeed).** + +> Curated S71 (2026-06-17) Harness-9 from L1 MEMORY.md (over-cap 65.2KB → <25KB). Appended VERBATIM below: 10 full run-records #306/#305/#304/#303/#302/#299/#298/#297/#295/#293 (newest→oldest) — byte-exact move, FIFO-trimmed from L1 hot. L1 kept #308/#307 full + stubs. + +- **2026-06-17 S70 Run #306 (run_number 306, id420) sha=`c556f6c` PASS ~4m42s (FE-ONLY re-skin "Văn phòng số" TOÀN MODULE 10 page PURO layout — PageHeader/KpiCard/WidgetCard apply + CSS Hồ sơ NS; presentation-only "phẫu thuật giữ nguyên logic", byte-identical logic. 20 .tsx: fe-admin 11 {AttendanceReport,InternalDirectory,ItTickets,MeetingCalendar,MeetingRooms,ProposalCreate,ProposalDetail,ProposalsList,WorkflowAppDetail,WorkflowAppsList}Page + fe-user 9 mirror (no AttendanceReport — admin-only) + 3 agent-memory .md (cicd/fe-designer/reviewer). NO BE/NO migration/NO new menu key. deploy 10/10 session after #297–#305 all PASS):** Push `a8bbdae..c556f6c` (1 commit, 23 files). `git diff --name-only a8bbdae c556f6c -- '*Migrations*' '*Persistence*'` = EMPTY ✓. `.tsx` non-ignored → full pipeline RAN (3 `.md` agent-memory match paths-ignore but co-mixed w/ 20 tsx → Discovery#3 range any-non-ignored ⟹ whole build). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp` len24). Run IN-PROGRESS at spawn (status=running 09:57:54→09:58:40) — pre-deploy baseline captured BEFORE poll-loop: admin `Bl2o_kUq` (S69 #305 live) + user `BImrKQNn` (S69 #305 live) — both == spec baseline (still live, deploy not yet shipped, anti#3 honored no mid-flight verify). Polled status=success (started 09:57:54 → upd 10:02:36 ≈4m42s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` không populate `conclusion`, trust success; 286 INFERRED gate-passes-pre-build invariant NOT log-confirmed). **★ BUNDLE BOTH ROTATE (FE-both-app 10-page re-skin ⟹ both MUST rotate; verified AFTER status=success + re-confirm STABLE 2nd-fetch identical no-transient — anti#3): admin ROTATE `Bl2o_kUq→Wt54PHYl` (css `CKzdDktL→BpHtX3vS`)** ✓ **+ user ROTATE `BImrKQNn→B99fMU6X` (css `eoxUcs8v→DXRSCQW7`)** ✓. BOTH required → BOTH did. Asset reachable 200: admin js 1,588,241b + user js 1,493,955b (not white). ⚠️ Prod CI hashes ≠ spec local-build (fe-user `C8-p69Kn`/`DezuRkK9`, fe-admin `yFhLO2Wp`/`Dd2WiO6n`) — EXPECTED CI-rebuild content-hash divergence (only matters NEW≠baseline, BOTH rotated confirmed). Smoke **4×200** health: api `/health/ready`+`/health/live` + admin root + eoffice root. **Office-backed API live (admin bearer): GET /api/proposals 200 · /api/it-tickets 200 · /api/meeting-rooms 200 · /api/employees 200** (data layer healthy, not 500). `/api/workflow-apps`→404 = WRONG-route-guess NOT regression (FE-only can't change BE routing; real route differs — leave-requests/don-tu; FE uses api-client wrapper, route grep didn't land). **SPA deep-link 6 Office routes admin all 200** (/proposals, /it-tickets, /workflow-apps/leave, /directory, /meeting-calendar, /office/dashboard serve index.html — not 404/white). **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (prev-2 = AddDepartmentParentId Mig51 + ReplaceBudgetModuleWithPeWorkItemBudgets Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged). **★ OFFICE-HIDDEN CONFIRMED (non-admin Drafter=nv.test): sqlcmd `Off_Dashboard` perm row ONLY for Admin (1/13 roles) ✓ + Drafter has ALL 17 Off_* keys present but EVERY one CanRead=0 ✓ → menu gates on CanRead=1 ⟹ non-admin sees NO Office menu.** ⚠️ **LESSON-CORRECTION vs S69: my coarse `GROUP BY COUNT(*) WHERE MenuKey LIKE 'Off%'` first showed "all 13 roles ~18 Off perms" — MISLEADING; those are perm-ROWS-with-CanRead=0 (existence ≠ access). Must filter `CanRead=1` OR inspect per-key. MenuItems.IsVisible=1 on all Off keys = admin-side global-visible flag; user-visibility = IsVisible AND role.CanRead. Office-hidden state is AMBIENT (FE-only commit cannot touch Permissions/MenuItems seed — git diff zero BE/DbInitializer) = identical pre-c556f6c.** 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual per-page render = anh user UAT (em chỉ confirm bundle shipped + route không 500/trắng per spec). **TOOLING (re-confirmed + NEW): Bash tool = POSIX bash → ⚠️ system `python3` BROKEN (ZKBioTime Python311 on PATH → `SRE module mismatch` fatal) AND `jq` NOT installed → MUST use `powershell.exe -NoProfile -File "C:/.../tmp/x.ps1"` + `Invoke-RestMethod` for ALL JSON parse (Gitea tasks match `head_sha -eq sha`); poll-loop in pure-bash w/ jq SILENTLY returns empty (no error) — verify parser works before trusting loop. SSH→PS base64 `-EncodedCommand` (UTF-16LE) for appsettings-read AND sqlcmd; sqlcmd string-LITERAL via `[char]39` concat (NOT doubled-quote); `-U/-P` direct (no -ConnectionString flag); `Roles`/`Permissions(RoleId,MenuKey,CanRead)`/`MenuItems([Key],IsVisible)`. NEVER fixed code (READ-only).** Tag `[s70, run306, pass, fe-only-vanphongso-reskin-10page-PURO, fe-both-app-bundle-BOTH-rotate-Wt54PHYl-B99fMU6X, asset-200-reachable, office-api-200-proposals-ittickets-meetingrooms-employees, workflow-apps-404-wrong-route-not-regression, spa-deeplink-6route-200, no-mig-top-stays-mig52, tables88-verified, office-hidden-confirmed-Off_Dashboard-admin-only-drafter-canread0, lesson-canread0-rows-not-access, ambient-not-deploy-caused, jq-absent-python3-broken-use-powershell, deploy10of10, no-regression, test286-inferred]`. +- **2026-06-17 S69 Run #305 (run_number 305, id419) sha=`a8bbdae` PASS ~4-5m (FE BOTH-APP foundation "Văn phòng số" + index.css sync + BE menu-seed NO-mig: 3 shared component mới PageHeader/KpiCard/WidgetCard + OfficeDashboardPage landing route `/office/dashboard` 4-place-wire BOTH apps + `fe-admin/src/index.css` SYNCED (Hồ sơ NS accent tokens, rotate admin mạnh) + BE menu key `Off_Dashboard` (MenuKeys.cs L100 + DbInitializer.cs L1825 seed parent=`Off` Order0 LayoutDashboard); deploy 9/9 session after #297–#304 all PASS):** Push `764fe70..a8bbdae` (1 commit, 20 files). Diff: FE 14 files (2× {PageHeader,KpiCard,WidgetCard,OfficeDashboardPage,App.tsx,Layout.tsx,menuKeys.ts} + fe-admin index.css) + BE 2 (MenuKeys.cs + DbInitializer.cs) + 4 agent-memory `.md`. `.tsx`/`.cs`/`.css` non-ignored → full pipeline RAN (the 4 `**/*.md` agent-memory match paths-ignore but co-mixed w/ code → Discovery#3 range any-non-ignored ⟹ whole build). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp`). Run IN-PROGRESS first 4 polls (running 09:26→09:29) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `CNUv1jxY` (S78 #304 live) + user `CpOskeS1` (S78 #304 live) — both == spec S68 baseline (still live, deploy not yet shipped). Polled iter5 status=success (started ~09:25 → success 09:29:50 ≈4-5m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success; 286 INFERRED from gate-passes-pre-build invariant NOT log-confirmed numerically). **★ BUNDLE BOTH ROTATE (FE-both-app + index.css sync ⟹ both MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no-transient — anti#3): admin ROTATE `CNUv1jxY→Bl2o_kUq`** ✓ (Văn phòng số + index.css shipped) **+ user ROTATE `CpOskeS1→BImrKQNn`** ✓ (foundation shipped). BOTH required → BOTH did. ⚠️ **Prod CI hashes (`Bl2o_kUq`/`BImrKQNn`) ≠ spec local-build (`TbkadgKd`/`DrxDysO7`) — EXPECTED divergence (CI rebuilds w/ different Node/npm/dep-resolution → different content-hash; only matters NEW≠baseline, BOTH rotated confirmed ship). Spec assumption "prod khớp local nếu source-identical" holds ONLY if byte-identical build-env — it is NOT (S77 same lesson).** Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 764fe70 a8bbdae -- '*Migrations*'` = EMPTY; menu-seed is runtime-idempotent DbInitializer NOT EF-migration → top did NOT advance). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, menu-seed inserts rows not tables). **★ MENU-SEED VERIFIED (NEW check this run — DbInitializer seed ungated reaches prod by design): sqlcmd `SELECT [Key],ParentKey,Label,IsVisible FROM MenuItems WHERE [Key]='Off_Dashboard'` → 1 row Key=`Off_Dashboard` Parent=`Off` Label="Bảng điều khiển Văn phòng số" IsVisible=1 ✓.** **★ OFFICE-HIDDEN CONFIRMED (RevokeTemporarilyHiddenModulesAsync StartsWith("Off") scope — DbInitializer.cs L2172): sqlcmd `SELECT r.Name FROM Permissions p JOIN Roles r ON r.Id=p.RoleId WHERE p.MenuKey='Off_Dashboard'` → ONLY `Admin` (CanRead=1), 1 row / 13 roles total ✓ → non-admin NO Off_Dashboard perm → Office stays hidden (revoke executed, admin auto via All[]).** 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "Dashboard landing render / Hồ sơ NS CSS" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88+menu-seed+office-hidden. **LESSON: FE-both-app + index.css-sync + BE-menu-seed (NO-EF-mig) verify = both bundles MUST ROTATE + Mig-top + sys.tables MUST stay prev (menu-seed = DbInitializer runtime row-insert, NOT a migration → does NOT advance __EFMigrationsHistory NOR sys.tables; verify seed via direct MenuItems SELECT not via mig-count). Office-hidden = query Permissions-by-role: temporarily-hidden module has perm row ONLY for Admin (All[] auto-grant) after RevokeTemporarilyHidden runs. ⚠️ Prod CI bundle-hash ≠ local-build-hash is NORMAL — never FAIL on hash-mismatch-to-local, only FAIL if NOT-rotated-from-baseline. TOOLING (re-confirmed S78): Bash=POSIX (`$var`/`$env:`/`''` literal-quote mangle through bash→ssh→PS→sqlcmd layers) → write PS to `.ps1` + `powershell -File "ABS/FWD/SLASH.ps1"`; Gitea `tasks` via `Invoke-RestMethod` (match `head_sha -eq sha`, `?limit=N` honored URI-direct); SSH→PS base64 `-EncodedCommand` (UTF-16LE iconv) for BOTH appsettings-read AND sqlcmd; ⚠️ **sqlcmd string-LITERAL in query: doubled `''x''` BREAKS (PS single-quote-string closes early) → build literal via `[char]39 + "x" + [char]39` concat (NOT `''`)**; this sqlcmd build supports `-U/-P` direct (no -ConnectionString flag); table name `Roles` NOT `AspNetRoles`, `Permissions(RoleId,MenuKey,CanRead)`; CLIXML/Progress stdout-noise grep-filter out. NEVER fixed code (READ-only).** Tag `[s69, run305, pass, fe-both-app-vanphongso-foundation, shared-PageHeader-KpiCard-WidgetCard, index-css-sync-admin, bundle-BOTH-rotate-Bl2o_kUq-BImrKQNn, prod-ci-hash-NE-local-EXPECTED, be-menu-seed-Off_Dashboard-NO-mig, no-mig-top-stays-mig52, tables88-verified, menu-seed-verified-sqlcmd, office-hidden-confirmed-admin-only-1of13, sqlcmd-char39-literal-not-doubled-quote, deploy9of9, no-regression, test286-inferred]`. +- **2026-06-16 S78 Run #304 (run_number 304, id418) sha=`37752eb` PASS ~4m (FE BOTH-APP 1-line CSS-precedence fix: "Hồ sơ NS" employee-detail banner NAME đen→trắng — `

` rendered BLACK do unlayered `h1,h2,h3,h4{color:#0b1220}` index.css thắng `text-white` (Tailwind v4 @layer precedence); fix = `text-white`→`text-white!` important-modifier + `text-xl font-extrabold`→`text-lg font-bold`. 2 file `EmployeesListPage.tsx` fe-user+fe-admin SHA256-IDENTICAL `8BBAEC34…`, 1-line each, NO BE/mig/index.css; deploy 8/8 session after #297–#303 all PASS):** Push `6983609..37752eb` (1 commit). Diff 2 files: both `.tsx` only → not in paths-ignore → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`, uid `vrapp` len24). Run IN-PROGRESS first 7 polls (running 19:55→19:58) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `D532XZKG` (S77 #303 live) + user `CuFaBoWt` (S77 #303 live) — both == spec baseline (still live, deploy not yet shipped). Polled iter8 status=success (started ~19:55 → success 19:59:26 ≈4m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success; exact CI count NOT log-confirmed numerically — Gitea logs web-UI-only anon, 286 INFERRED from gate-passes-pre-build invariant). **★ BUNDLE BOTH ROTATE (the change-point — FE-BOTH-app, both EmployeesListPage changed ⟹ both bundles MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient — anti#3): admin ROTATE `D532XZKG→CNUv1jxY`** ✓ (`text-white!` name-color fix shipped) **+ user ROTATE `CuFaBoWt→CpOskeS1`** ✓ (same page shipped). BOTH required per spec → BOTH did (mirror S74/S75/S76/S77 pure-FE-both-app pattern — both same-SHA256 file ⟹ both rotate; frozen sibling here = ship-fail flag). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 6983609 37752eb -- '*Migrations*' '*Persistence*'` = EMPTY; FE cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "tên NV trắng render đúng" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-between-2-apps is a SOURCE-claim (git), not runtime DOM-equality; CSS-precedence-actually-fixed is render-time, NOT provable by curling bundle (the `!important` text is in dist css per spec-claim, but visual-black-vs-white is browser-render). **LESSON: pure-FE-BOTH-app 1-line CSS-fix verify (8th consecutive deploy this session, both apps same component SHA256-identical) = both bundles MUST ROTATE (≠ asymmetric one-app where sibling frozen; ≠ BE-only where both frozen). Even a 1-line `text-white`→`text-white!` change → new content-hash both apps. Migration top + sys.tables MUST stay = prev (FE-only). No BE call-site/DTO/endpoint smoke (no API surface — render-only). TOOLING (re-confirmed S77): Bash tool is POSIX bash (`Select-String`/`$var`/`$env:` unavailable inline) → write PowerShell to `.ps1` + invoke `powershell -File "ABSOLUTE/FORWARD/SLASH.ps1"` (Bash EATS backslash in `tmp\x.ps1`→`tmpx.ps1`); parse Gitea `tasks` via `Invoke-RestMethod`+native object (match `head_sha -like sha*`, `limit=N` ignored); SSH→PS nested-quote mangles → base64 `-EncodedCommand` (UTF-16LE) BOTH the appsettings-read AND the sqlcmd-query (no `$` in B64 passes bash+ssh clean); CLIXML progress-stream on stdout harmless (data rows clean below); read DB pw from prod appsettings when PROD_DB_PW empty. NEVER fixed code (READ-only).** Tag `[s78, run304, pass, fe-both-app-hoso-name-color-fix-text-white-important, fe-both-2files-sha256-identical-8BBAEC34, bundle-BOTH-rotate-CNUv1jxY-CpOskeS1, css-precedence-tailwind-v4-layer, no-mig-top-stays-mig52, tables88-verified, deploy8of8, ssh-encodedcommand-base64, no-regression, test286-inferred]`. +- **2026-06-16 S77-ARCHIVED Run #303 sha=`6983609` PASS ~5m (FE BOTH-APP additive "Hồ sơ NS" banner text-polish — name `text-xl font-extrabold`+drop-shadow, meta `text-[13px] font-medium text-white`, status badge → solid status-colored pill emerald/amber/slate. 2 file `EmployeesListPage.tsx` fe-user+fe-admin SHA256-IDENTICAL `F013B748…`, NO BE/mig/index.css; +docs 2 files `{dep-audit SKILL.md gotcha 64→65, root CLAUDE.md test 263→286}` no-build-impact; deploy 7/7 session after #297–#302 all PASS):** Push range `231a7b0..6983609` (2 commits). Diff 4 files: 2 `.tsx` + `CLAUDE.md` + `dependency-audit-erp/SKILL.md`. `.tsx` → not in paths-ignore → full pipeline RAN (docs alone would SKIP, but tsx present ⟹ build per Discovery#3 push-range any-non-ignored ⟹ whole build). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (path `C:\inetpub\solution-erp\api`). Run IN-PROGRESS first 6 polls (running 19:36→19:39) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `CcrZqfht` (S76 #302 live) + user `DniDFUB_` (S76 #302 live) — both == spec baseline (still live, deploy not yet shipped). Polled iter6 status=success (started ~19:35 → success 19:40 ≈5m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success; could NOT extract exact CI count — Gitea logs web-UI-only anon, 286 INFERRED from gate-passes-pre-build invariant NOT log-confirmed numerically). **★ BUNDLE BOTH ROTATE (the change-point — FE-BOTH-app, both EmployeesListPage changed ⟹ both bundles MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient — anti#3): admin ROTATE `CcrZqfht→D532XZKG`** ✓ (banner polish shipped) **+ user ROTATE `DniDFUB_→CuFaBoWt`** ✓ (same page shipped). BOTH required per spec → BOTH did (mirror S74/S75/S76 pure-FE-both-app pattern — both same-SHA256 file ⟹ both rotate; frozen sibling here = ship-fail flag). ⚠️ Local-build hashes (fe-user `SuT9mDAQ`, fe-admin `7ICczYiQ` per spec) ≠ deployed CI hashes — EXPECTED (CI rebuilds; only matters NEW≠baseline, confirmed). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 231a7b0 6983609 -- '*Migrations*' '*Persistence*'` = EMPTY; FE+docs cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "banner text-xl/drop-shadow/status-pill render đúng" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-between-2-apps is a SOURCE-claim (git), not runtime DOM-equality. **LESSON: pure-FE-BOTH-app additive verify (7th consecutive deploy this session, both apps same component SHA256-identical) = both bundles MUST ROTATE (≠ asymmetric one-app where sibling frozen; ≠ BE-only where both frozen). Docs-files in same range as tsx do NOT suppress build (range any-non-ignored ⟹ build). Migration top + sys.tables MUST stay = prev (FE-only). No BE call-site/DTO/endpoint smoke (no API surface — render-only). TOOLING (re-confirmed S76): Bash tool EATS inline `$var`/`$env:`/`@{}` in PS strings → write PowerShell to `.ps1` + run `-File`; parse Gitea `tasks` via `Invoke-RestMethod`+native object (match `head_sha -like sha*`, `limit=N` ignored); SSH→sqlcmd base64 `EncodedCommand` (UTF-16LE via iconv, no `$` in B64 passes bash clean), CLIXML progress-stream stdout harmless grep-filter out; ⚠️ this `sqlcmd` build does NOT support `-ConnectionString` flag → parse `User Id`/`Password` from conn-string via regex → `-U/-P`; read DB pw from prod appsettings when PROD_DB_PW empty. NEVER fixed code (READ-only).** Tag `[s77, run303, pass, fe-both-app-hoso-banner-textpolish, fe-both-2files-sha256-identical-F013B748, bundle-BOTH-rotate-D532XZKG-CuFaBoWt, docs-in-range-no-suppress-build, no-mig-top-stays-mig52, tables88-verified, deploy7of7, sqlcmd-no-connstring-flag-use-U-P, no-regression, test286-inferred]`. +- **2026-06-16 S76 Run #302 (run_number 302, id416) sha=`536dd6b` PASS ~4m (FE BOTH-APP additive: PE "Link hồ sơ ổ mạng" render upgrade — đường-dẫn-ổ-mạng từ chữ+Copy → `` bấm-thử mở Explorer + GIỮ nút Copy dự phòng. 2 file `PeDetailTabs.tsx` fe-user+fe-admin SHA256-IDENTICAL `b415023b…`, +46/−14 mỗi file, NO BE/mig/index.css/Employee-page; deploy 6/6 session after #297/#298/#299/#300/#301 all PASS):** Push 2 files `{fe-admin,fe-user}/src/components/pe/PeDetailTabs.tsx`. `.tsx` → not in paths-ignore → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default`. Run IN-PROGRESS first 6 polls (running 14:54:20→14:56:57) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline captured BEFORE poll-loop: admin `I1fpLeYw` (S75 #301 live) + user `DrQYkzh0` (S75 #301 live) — both == spec baseline. Polled iter7 status=success (started ~14:53 → success 14:57:13 ≈4m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **286** baseline (45D+241I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — `tasks` endpoint terminal=`status:success` doesn't populate `conclusion`, trust success). **★ BUNDLE BOTH ROTATE (the change-point — FE-BOTH-app, both PeDetailTabs changed ⟹ both bundles MUST rotate; verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient — anti#3): admin ROTATE `I1fpLeYw→CcrZqfht`** ✓ (file:// link shipped) **+ user ROTATE `DrQYkzh0→DniDFUB_`** ✓ (same component shipped). BOTH required per spec → BOTH did (mirror S74/S75 pure-FE-both-app pattern — both same-SHA256 file ⟹ both rotate; frozen sibling here = ship-fail flag). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (`git diff --name-only 536dd6b~1 536dd6b -- '*Migrations*'` = EMPTY; FE cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "link file:// bấm mở Explorer / nút Copy" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-between-2-apps is a SOURCE-claim (git), not runtime DOM-equality (file:// scheme browsers thường block từ https-origin → click có thể no-op tùy browser, đó là lý do GIỮ Copy dự phòng — không kiểm chứng được qua curl). **LESSON: pure-FE-BOTH-app additive verify (6th consecutive deploy this session, both apps same component SHA256-identical) = both bundles MUST ROTATE (≠ asymmetric one-app where sibling frozen; ≠ BE-only where both frozen). Migration top + sys.tables MUST stay = prev (FE-only). No BE call-site/DTO/endpoint smoke (no API surface — render-only). TOOLING (re-confirmed S75): Bash tool EATS inline `$var`/`$env:` in `powershell -Command` → write PowerShell to `.ps1` + run `-File`; `certutil -hashfile … SHA256` for SHA256 (NOT findstr-pipe — quoting breaks); parse Gitea `tasks` via `Invoke-RestMethod`+native object (match `head_sha -eq sha`, `limit=N` ignored); SSH→sqlcmd base64 `EncodedCommand` (UTF-16LE, no `$` in B64 passes bash clean), CLIXML progress-stream stdout harmless; read DB pw from prod appsettings when PROD_DB_PW empty (path `C:\inetpub\solution-erp\api`, key `ConnectionStrings.Default`). NEVER fixed code (READ-only).** Tag `[s76, run302, pass, fe-both-app-pe-link-hoso-file-scheme, fe-both-2files-sha256-identical-b415023b, bundle-BOTH-rotate-CcrZqfht-DniDFUB_, no-mig-top-stays-mig52, tables88-verified, deploy6of6, no-regression, test286]`. +- **2026-06-16 S73 Run #299 (id413) sha=`bcd619d` PASS ~3m (TESTS-ONLY BE: +3 files `tests/SolutionErp.Infrastructure.Tests/Application/{DepartmentTreeTests,PeHoSoLinkTests,HrmProfilePermissionSeedTests}.cs` = +23→286 total (45D+241I). NO prod code, NO FE, NO migration; deploy 3/3 session after #297/#298 both success):** `.cs` not in paths-ignore → full pipeline RAN. Run IN-PROGRESS first poll (running 14:06) → polled iter4 status=`success` (~14:09 ≈3m); `conclusion` empty (terminal=success, trust). **CI test gate = the focus: gate runs BOTH test projects BEFORE build ⟹ status=success ⟹ 23 new tests passed on CI (not just local 286).** Could NOT extract exact CI count — Gitea `runs/413/logs`+`jobs` endpoints all **404 anon** (logs web-UI-only without auth token; GITEA_TOKEN empty). Inferred 286 from success (gate-passes-pre-build invariant) — NOT log-confirmed numerically. **★ BUNDLE BOTH-FROZEN (BE-only ⟹ neither app may rotate), verified AFTER status=success +2nd-fetch STABLE no transient (anti#3): admin `xkSz9BfE` FROZEN ✓ + user `BumgrwCJ` FROZEN ✓** (==pre-deploy baseline both; rotate-on-BE-only = anomaly→none). Smoke **4×200**: api `/health/ready`+`/health/live`+admin root+eoffice root. **NO migration** — prod `__EFMigrationsHistory` top=`AddHoSoLinkToPurchaseEvaluation`(Mig52)==repo HEAD GIỮ NGUYÊN ✓ (prev-2 Mig51 `AddDepartmentParentId`+Mig50 `ReplaceBudgetModule…` chain intact; commit 0 mig files). **sys.tables=88** ✓ (sqlcmd excl mighist, unchanged). 0 regression, NO prod-data mutation (read curls+SELECT-only). **TOOLING LESSON (supersedes S72 #298): `python3`/`py -3` on this box = BROKEN ZKBioTime embed (`AssertionError: SRE module mismatch` on `import re`/`json`) — version banner works but stdlib import dies. USE PowerShell `Invoke-RestMethod`+native object access (NOT ConvertFrom-Json on cached temp file — temp got stale snapshot once) to parse Gitea tasks; `limit=N` param IGNORED (returns full 299, match by id anyway). SSH→sqlcmd: nested bash→ssh→PS ate `$var` (S42) → use `iconv UTF-16LE|base64`→`powershell -EncodedCommand`; CLIXML progress-stream noise on stdout is harmless, data lines clean. Read DB pw from prod `appsettings.Production.json`→`ConnectionStrings.Default` when PROD_DB_PW empty (path `C:\inetpub\solution-erp\api`). NEVER fixed code (READ-only).** Tag `[s73, run299, pass, tests-only-be, +23-test-286-CI-gate-inferred-not-logcount, bundle-both-frozen-xkSz9BfE-BumgrwCJ, no-mig-top-mig52, tables88, deploy3of3, python3-broken-use-powershell-IRM, no-regression]`. +- **2026-06-16 S72 Run #298 (run_number 298, id412) sha=`292d64d` PASS ~4m20s (FE-Admin MIRROR "Hồ sơ Nhân sự" master-detail từ fe-user: overwrite EmployeesListPage.tsx cũ 1200→1602 dòng SHA256-IDENTICAL với fe-user + index.css +4 accent palette teal/amberx/violet/greenx + utility `.icon-chip`/`.app-gradient-brand`/`.card-accent`/`.stat-value` — FE-ADMIN-ONLY 2 files, NO BE, NO migration, NO fe-user; ASYMMETRIC NGƯỢC deploy-1 S71 #297 (đó fe-user-rotate/admin-frozen, đây admin-rotate/user-frozen)):** Deploy 2/2 trong session (deploy-1 = `ab4e681` #297 PASS fe-user `BumgrwCJ`; #297=success TRƯỚC push này nên KHÔNG bị cancel — confirmed in tasks list). Push 2 files `fe-admin/src/index.css`(+81) + `fe-admin/src/pages/hrm/EmployeesListPage.tsx`(1020+/537−, 1476-line overwrite). `.tsx`+`.css` → not in paths-ignore → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (`buKL3...buKL3TGBkD0wDDbYVw65QeX9`). Run IN-PROGRESS first poll (running 13:52) — correctly did NOT verify-bundle-mid-flight (anti#3); pre-deploy baseline snapshot captured BEFORE poll: admin `BDwV5d0X` (S68 frozen-since, must rotate) + user `BumgrwCJ` (S71 deploy-1 live, must stay frozen). ⚠️ POLL-PARSER BUG: my `tr ','|grep -A4 '"id":412'` returned empty status ×10 iter (false negative — grep -A on tr-newlined JSON misanchors) → re-queried with `python3 json.load` → status=`success` (started 13:51:27 → updated 13:55:47 ≈4m20s). LESSON-tooling: parse Gitea tasks JSON via python3 NOT tr/grep -A (multi-field-per-object splits wrong); always cross-check with a clean parser before trusting empty-status. CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — trust success). **★ BUNDLE ASYMMETRIC NGƯỢC-deploy-1 (the change-point — FE-one-app verify) ALL PASS, verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient (anti#3): admin ROTATE `BDwV5d0X→xkSz9BfE`** ✓ (EmployeesListPage mirror + accent index.css shipped — admin un-froze from S68's BDwV5d0X frozen-streak S68→S71, now rotated correctly cho fe-admin change) **+ user FROZEN `BumgrwCJ`==deploy-1-baseline** ✓ (fe-user 0 files touched deploy-2 → MUST NOT rotate = correct; rotate-when-untouched = anomaly→flag). Health api live+ready **200/200** (real endpoints `/health/ready`+`/health/live`, NOT `/health`) + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact; commit 0 mig files — FE cannot alter schema → top did NOT advance). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "mirror identical / chữ xanh đậm brand-800 / accent đẹp" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88; SHA256-identical-with-fe-user is a SOURCE-claim (git diff), not runtime-verified by me (can't compare rendered DOM cross-app). **LESSON: deploy-2 of same-session asymmetric-NGƯỢC = (a) verify prior deploy (#297) NOT cancelled by this push (it was success first — confirmed in tasks); (b) status=success ⟹ deploy ran; (c) THIS-app bundle ROTATE + OTHER-app FROZEN — direction FLIPS vs deploy-1 (deploy-1 fe-user-rotate/admin-frozen → deploy-2 admin-rotate/user-frozen); admin un-froze from multi-session frozen-streak = EXPECTED when fe-admin finally changes (frozen-streak break is normal, not anomaly); (d) migration top + sys.tables MUST stay = prev (FE-only). Re-query Gitea JSON with python3 when tr/grep parse yields empty — parser bug ≠ run-stuck. No BE call-site/DTO/endpoint smoke (no API surface). SHA256-source-identical = git-level claim, runtime-mirror-equality not provable by curl. NEVER fixed code (READ-only).** Tag `[s72, run298, pass, fe-admin-mirror-hoso-from-feuser, fe-admin-only-2files, bundle-asymmetric-NGUOC-admin-rotate-user-frozen, admin-unfroze-from-s68-streak, no-mig-top-stays-mig52, tables88-verified, deploy2of2-prior297-not-cancelled, poll-parser-bug-use-python3, no-regression, test263]`. +- **2026-06-16 S71 Run #297 (run_number 297, id411) sha=`ab4e681` PASS ~4m21s (FE-User "Hồ sơ NS" cosmetic: đồng nhất font/size + chữ đen `text-slate-900/800`→xanh-đậm `text-brand-800` cho value/tên + bỏ `text-xs` nhánh mono — FE-USER-ONLY 1 file, NO BE, NO migration; 4th consecutive same-page S68→S71):** Push 1 file `fe-user/src/pages/hrm/EmployeesListPage.tsx` (className màu chữ + size, KHÔNG đụng BE/Mig/dep/fe-admin). `.tsx` → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (len24). Run IN-PROGRESS first poll (running 13:43) — correctly did NOT verify-bundle-mid-flight (anti#3), pre-deploy baseline snapshot captured BEFORE poll iter0: user `DbVv6rsf` (S70 baseline) + admin `BDwV5d0X`. Polled iter5 status=success (started ~13:42 → success ~13:46:48 ≈4m21s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — trust success). **★ BUNDLE ASYMMETRIC (the change-point — FE-one-app verify) ALL PASS, verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient (anti#3): user ROTATE `DbVv6rsf→BumgrwCJ`** ✓ (EmployeesListPage cosmetic shipped — re-rotated from S70's DbVv6rsf, 4th consecutive same-page FE-user deploy = new content-hash each, normal) **+ admin FROZEN `BDwV5d0X`==baseline** ✓ (fe-admin 0 files touched → MUST NOT rotate = correct; rotate-when-untouched = anomaly→flag). Health api live+ready **200/200** (⚠️ `/health` spec'd → 404, real endpoint `/health/ready`+`/health/live` per iis-deploy-runbook skill — both 200, API healthy) + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (commit 0 mig files — `git diff --name-only | grep Migrations/` = NONE; FE-only cannot alter schema → top did NOT advance; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). **sys.tables=88 verified** (sqlcmd COUNT excl mighist — unchanged, no schema touch). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "chữ xanh đậm/đồng nhất size" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged+tables88. **LESSON: pure-FE-one-app cosmetic verify (4th consecutive same-page) = (a) status=success ⟹ deploy ran; (b) target-app bundle ROTATE + sibling-app FROZEN (re-rotate of same page across sessions = NORMAL, each deploy=new hash; rotate-sibling-when-untouched = anomaly); (c) migration top + sys.tables MUST stay = prev (FE cannot alter schema — advancing = bug); (d) health 200×2 (/ready+/live, NOT /health). ⚠️ `/health` 404 ≠ unhealthy — SOL uses `/health/ready`+`/health/live` (skill-doc), spec literal `/health` is wrong-path; cross-check skill before flagging. No BE call-site/DTO/endpoint smoke needed (no API surface). Tokens empty: anon Gitea + prod-appsettings DB pw works. NEVER fixed code (READ-only).** Tag `[s71, run297, pass, fe-user-hoso-cosmetic-brand800, fe-only-1file, bundle-asymmetric-user-rotate-admin-frozen, no-mig-top-stays-mig52, tables88-verified, health-ready-not-health, no-regression, test263]`. +- **2026-06-16 S70 Run #295 (run_number 295, id409) sha=`456c7a7` PASS ~4m21s (FE-User "Hồ sơ Nhân sự" layout 2-cột: cây tổ chức + list chồng cột trái, detail cột phải + tô màu panel chi tiết — FE-USER-ONLY 1 file, NO BE, NO migration; follow-up S69 Run #294 same page):** Push `ec517f7..456c7a7` 1 file `fe-user/src/pages/hrm/EmployeesListPage.tsx` (309+/262−, 571-line layout rewrite). `.tsx` → full pipeline RAN. Both tokens SET this run (GITEA_TOKEN+PROD_DB_PW present — unlike S65-S69 empty; DB pw still works via prod `appsettings.Production.json`→`ConnectionStrings.Default` `buKL3...` len24). Run IN-PROGRESS first poll (running 11:36) — correctly did NOT verify-bundle-mid-flight (anti#3), polled iter6 status=success (started ~11:36 → success 11:40:46 ≈4m21s). Pre-deploy baseline snapshot iter0: user `CZfo_PFZ` (S69 baseline) + admin `BDwV5d0X` — captured BEFORE poll, correct timing. CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; FE-only ⟹ 0 BE call-site risk) passed; `conclusion` empty — trust success). **★ BUNDLE ASYMMETRIC (the change-point — FE-one-app verify) ALL PASS, verified AFTER status=success +re-confirm STABLE 2nd-fetch identical no transient (anti#3): user ROTATE `CZfo_PFZ→DbVv6rsf`** ✓ (EmployeesListPage layout-2col shipped — re-rotated from S69's CZfo_PFZ, consecutive FE-user deploys on SAME page = new content-hash each, normal) **+ admin FROZEN `BDwV5d0X`==baseline** ✓ (fe-admin 0 files touched → MUST NOT rotate = correct; rotate-when-untouched = anomaly→flag). Health api live+ready **200/200** + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation` (Mig 52) == repo HEAD GIỮ NGUYÊN ✓ (commit 0 mig files — `git diff --name-only | grep Migrations/` = NONE; FE-only cannot alter schema → top did NOT advance past S68's Mig 52; prev-2 = `AddDepartmentParentId` Mig51 + `ReplaceBudgetModuleWithPeWorkItemBudgets` Mig50 chain intact). sys.tables stays 88 by construction (no schema touch — skipped explicit count, FE-only). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Visual "2-cột đẹp/tô-màu" NOT verified (anh xem mắt) — only ship+rotate+health+mig-unchanged. **LESSON: pure-FE-one-app verify (3rd consecutive same-page FE-user S68→S69→S70) = (a) status=success ⟹ deploy ran; (b) target-app bundle ROTATE + sibling-app FROZEN (asymmetric — re-rotate of already-rotated page across sessions is NORMAL, each deploy=new hash; rotate-sibling-when-untouched = anomaly); (c) migration top MUST stay = prev (FE cannot alter schema — advancing top on FE-only commit = bug); (d) health 200×3. No BE call-site/DTO/endpoint smoke needed (no API surface). Tokens-present vs empty: both paths work — when GITEA_TOKEN set use `token` header, but anon also fine for public repo. NEVER fixed code (READ-only).** Tag `[s70, run295, pass, fe-user-hoso-2col-layout, fe-only-1file, bundle-asymmetric-user-rotate-admin-frozen, no-mig-top-stays-mig52, tables88, no-regression, test263, tokens-present]`. +- **2026-06-16 S68 Run #293 (run_number 293, id407) sha=`5a0aaa4` PASS ~5m00s (FE-User "Hồ sơ Nhân sự" 3-panel master-detail 5-tab rewrite + PE "Link hồ sơ" hyperlink NAS + rename "Dự trù PRO"→"Ngân sách PRO" — CROSS-STACK BE Mig 52 + FE×2; 2-commit `318860a`(FE-User EmployeesListPage) + `5a0aaa4`(PE cross-stack)):** Push `6ce5803..5a0aaa4` 15 files: BE `PurchaseEvaluation.cs`(+HoSoLink) + `PurchaseEvaluationConfiguration.cs` + `PurchaseEvaluationFeatures.cs`(Create/Update cmd +trailing optional `HoSoLink=null`) + `PurchaseEvaluationDtos.cs`(Detail DTO +hoSoLink) + Mig 52 `20260616035929_AddHoSoLinkToPurchaseEvaluation`(3-file) + FE `EmployeesListPage.tsx`(fe-user, 3-panel rewrite cây-tổ-chức+list+5-tab) + PE×2 (`PeDetailTabs.tsx`+`PeWorkspaceCreateView.tsx`+`types/purchaseEvaluation.ts` both apps, mục E hyperlink + label rename). `.cs`+`.tsx`+Mig → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json`→`ConnectionStrings.Default` (`buKL3...`). Run IN-PROGRESS first poll (running 11:15) — correctly did NOT verify-bundle-mid-flight (anti#3), polled iter5 status=success (started 11:13:57 → success 11:18:57 ≈5m). CI gate (both proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45D+218I; BE Create/Update +trailing optional param=null ⟹ call-sites UNBROKEN) passed; `conclusion` empty — trust success). **★ MIGRATION APPLIED (the KEY): prod `__EFMigrationsHistory` top = `20260616035929_AddHoSoLinkToPurchaseEvaluation`** ✓ (Mig 52; ADVANCED from prev top Mig 51 `AddDepartmentParentId`; DbInitializer auto-migrate on startup ran — api ready=200 confirms boot OK post-mig). Mig content = AddColumn `HoSoLink nvarchar(1000) maxLength=1000 NULL` (AddColumn-only, NO new table → **sys.tables stays 88** verified). DB-level: `sys.columns WHERE object_id=OBJECT_ID('PurchaseEvaluations') AND name='HoSoLink'` = 1 row ✓ (column physically present prod). **★ BUNDLE BOTH ROTATE (verified AFTER status=success +re-confirm STABLE 2nd-fetch no transient — anti#3): admin `Df06fmpq→BDwV5d0X`** ✓ (PE files shipped — was FROZEN since S67, now correctly rotated) **+ user `DxK3fCfh→DXkyUjtQ`** ✓ (EmployeesListPage + PE files shipped). Both required to rotate per spec → BOTH did. Health api live+ready **200/200** + admin/eoffice root 200. **★ NEW-FIELD live smoke (admin bearer, route `/api/auth/login` field `accessToken` len468):** `GET /api/purchase-evaluations/{48154149...}` (phiếu thật `PE/2026/A/015`) = **200** ✓ JSON detail carries `"hoSoLink":null` (key PRESENT, null cho phiếu cũ = backward-compat ✓ — Detail DTO wired AND Create/Update signature-change did NOT break GET path) · `GET /api/departments/tree` = **200** ✓ (Phase B dependency from Mig 51, still live). 0 regression. NO prod-data mutation (read-only GETs + sqlcmd SELECT-only). **LESSON: nullable-AddColumn cross-stack verify = (a) `__EFMigrationsHistory` top ADVANCED to new Mig (Mig 52) + api ready=200 (auto-migrate boot OK); (b) `sys.tables`=88 unchanged (AddColumn ≠ new table) + `sys.columns` confirms col present; (c) Detail DTO field-presence via authed GET on a REAL phiếu — `"hoSoLink":null` for old rows proves both DTO-wiring AND backward-compat (trailing-optional Create/Update param won't break GET — confirmed 200); (d) cross-stack BE+FE×2 = BOTH bundles rotate (admin un-froze from S67). Tokens empty: anon Gitea + prod-appsettings DB pw works (Bash tool is POSIX bash NOT PS despite shell=PowerShell env — `$env:`/`jq`/`python3` unavailable; parse via `tr ','|grep` + read pw via `ssh→appsettings`).** Tag `[s68, run293, pass, pe-hosolink-mig52-applied, fe-user-hoso-3panel-rewrite, pe-link-hoso-hyperlink, rename-dutru-to-ngansach, bundle-both-rotate-admin-unfroze, hosolink-null-in-dto-backward-compat, no-new-table-88, test263]`. diff --git a/.claude/agent-memory/cicd-monitor/archive/_INDEX.md b/.claude/agent-memory/cicd-monitor/archive/_INDEX.md new file mode 100644 index 0000000..b6838ad --- /dev/null +++ b/.claude/agent-memory/cicd-monitor/archive/_INDEX.md @@ -0,0 +1,106 @@ +# CI/CD Monitor — Archive INDEX (table of contents, NO content) + +> **Purpose:** L2 archive is "dark-matter" (not in RAG). This index is the map: 1 line per archived record so a future spawn can locate + open the right verbatim block on-demand without reading 220KB cold. +> **Pointer style = SUBSTRING (Ctrl-F) primary.** Each line ends `→ · substring:""`. The string is grep-verified `count==1` inside its target file (76/76 unique at build time). Open the file, Ctrl-F the string → lands on the record start. Fallback if a future edit shifts text: search the bare `Run #NNN` + nearest date. +> **Pointer key choice:** keyed on 7-char git sha OR `Run #NNN (run_number NNN, idNNN)` heading-prefix. `2026-06.md` has ZERO markdown headings (all bullet records) → anchor-slug impossible there → sha/run-number used. `2026-05-runs.md` mixes 6 meta-headers + 20 run-records under near-identical `### DATE — Run #NNN sha=… VERDICT=` headings (slug-collision) → sha disambiguates. Some `Run #NNN` appear in BOTH a heading AND a body cross-ref → pointer keyed on the record-START form (`Run #NNN (run_number…` / `sha=\`…\` PASS`) which is unique. +> **Archives are FROZEN / additive-only.** Records are never edited in place; this index + the `.gist.md` files are the only additive layers. +> **SORTED by DATE (newest → oldest).** Labels: `[meta]` = curate-note (not a rich run-record) · `[stub→git]` = FIFO-trim pointer-to-git, thin · `[audit]` = read-only verify, no deploy · `[FAIL]`/`[PARTIAL]` = non-PASS verdict. +> +> **Files indexed:** `2026-06.md` (39 records, no headings) · `2026-05-runs.md` (20 run-records + 6 meta-headers) · `2026-05-q4.md` (7) · `2026-05-q3.md` (3) · `2026-05-q2.md` (1). Total **76 records**. +> Even-older verbatim (S29 #232 ← pre-S38) also lives in git `d2f52ba`. Gist companions: `2026-06.gist.md` + `2026-05.gist.md`. + +--- + +## 2026-06 (file: `archive/2026-06.md`) + +- 2026-06-17 · FE-only re-skin Văn phòng số 10-page PURO · PASS bundle-BOTH-rotate Wt54PHYl/B99fMU6X, office-hidden confirmed, tables88 · `2026-06.md` · substring:"Run #306 (run_number 306, id420) sha=`c556f6c`" +- 2026-06-17 · FE-both-app Văn phòng số foundation + index.css + BE menu-seed · PASS bundle-BOTH-rotate Bl2o_kUq/BImrKQNn, no-mig stays Mig52, menu-seed verified · `2026-06.md` · substring:"Run #305 (run_number 305, id419) sha=`a8bbdae`" +- 2026-06-16 · FE-both-app 1-line CSS-precedence fix (name text-white!) · PASS bundle-BOTH-rotate CNUv1jxY/CpOskeS1, tables88 · `2026-06.md` · substring:"Run #304 (run_number 304, id418) sha=`37752eb`" +- 2026-06-16 · FE-both-app Hồ sơ NS banner text-polish · PASS bundle-BOTH-rotate D532XZKG/CuFaBoWt, docs-in-range-no-suppress · `2026-06.md` · substring:"Run #303 sha=`6983609` PASS" +- 2026-06-16 · FE-both-app PE Link-hồ-sơ file:// render · PASS bundle-BOTH-rotate CcrZqfht/DniDFUB_, tables88 · `2026-06.md` · substring:"Run #302 (run_number 302, id416) sha=`536dd6b`" +- 2026-06-16 · TESTS-ONLY BE +23 test (286) · PASS bundle-BOTH-frozen xkSz9BfE/BumgrwCJ, CI-gate-inferred, python3-broken-use-PS · `2026-06.md` · substring:"Run #299 (id413) sha=`bcd619d`" +- 2026-06-16 · FE-Admin MIRROR Hồ sơ NS from fe-user · PASS bundle-asymmetric-NGƯỢC admin-rotate xkSz9BfE/user-frozen, poll-parser-bug-use-python3 · `2026-06.md` · substring:"Run #298 (run_number 298, id412) sha=`292d64d`" +- 2026-06-16 · FE-User Hồ sơ NS cosmetic brand800 · PASS bundle-asymmetric user-rotate BumgrwCJ/admin-frozen, health-ready-not-health · `2026-06.md` · substring:"Run #297 (run_number 297, id411) sha=`ab4e681`" +- 2026-06-16 · FE-User Hồ sơ NS 2-col layout · PASS bundle-asymmetric user-rotate DbVv6rsf/admin-frozen, no-mig Mig52, tokens-present · `2026-06.md` · substring:"Run #295 (run_number 295, id409) sha=`456c7a7`" +- 2026-06-16 · CROSS-STACK PE HoSoLink Mig 52 + FE-User 3-panel + rename Dự trù→Ngân sách · PASS Mig52-applied + bundle-both-rotate BDwV5d0X/DXkyUjtQ, hoSoLink-null-DTO-backward-compat, no-new-table-88 · `2026-06.md` · substring:"Run #293 (run_number 293, id407) sha=`5a0aaa4`" +- 2026-06-13 · CROSS-STACK PE PeWorkItemBudgets vượt-ngân-sách soft-warn (Mig 50 net 93→88) · PASS · `2026-06.md` · substring:"Run #286 (run_number 286, id400)" +- 2026-06-12 · CROSS-STACK PE guard 4-thông-tin mục 3 · PASS · `2026-06.md` · substring:"Run #283 (run_number 283) sha=`37122f0`" +- 2026-06-11 · S59-CLOSE final đóng sổ · PASS · `2026-06.md` · substring:"Run #280 (run_number 280) sha=`69997da`" +- 2026-06-11 · S59-đợt6 CROSS-STACK BE · PASS · `2026-06.md` · substring:"Run #278 (run_number 278) sha=`9c330d2`" +- 2026-06-11 · S59-đợt5 FE-only ×2 · PASS · `2026-06.md` · substring:"Run #277 (run_number 277) sha=`faed59f`" +- 2026-06-11 · S59-đợt4 rename WorkItems 71 idempotent-seed · PASS rename-via-prod-SQL target-count-EXACT no-dup · `2026-06.md` · substring:"Run #276 (run_number 276) sha=`c869d26`" +- 2026-06-11 · S59-đợt3 BE-only DbInitializer · PASS · `2026-06.md` · substring:"Run #275 (run_number 275) sha=`bbd1554`" +- 2026-06-11 · S59 FE×2 PE-list tree regroup · PASS · `2026-06.md` · substring:"Run #273 (run_number 273) sha=`56882ac`" +- 2026-06-11 · S59-đợt2 FE×2 PE-list tree · PASS · `2026-06.md` · substring:"Run #274 (run_number 274) sha=`0eafcd3`" +- 2026-06-11 · SUPERSEDE-CHAIN (benign cancel→shipped) · CANCELLED-benign→PASS-via-3ebaf merge-base-ancestor-not-fault · `2026-06.md` · substring:"Run #385→#386 SUPERSEDE-CHAIN sha=`ea793a4`" +- 2026-06-11 · S58 FE-USER visual redesign · PASS asymmetric fe-user-only · `2026-06.md` · substring:"Run #384 (run_number 270) sha=`e959f72`" +- 2026-06-16 · TEST-GATE COMPILE BREAK (CS7036 ParentId 5th positional, call-site :63 4-arg) · **[FAIL]** ~64s deploy-gated prod-stayed-baseline, gotcha #65 root cause · `2026-06.md` · substring:"Run #291 (run_number 291, id405) sha=`8c8179c`" +- 2026-06-11 · S58 FIX #381 lock NO-OP (lock-noop→pw-11to12) · PASS dump-Users-set-before-scoring-FAIL · `2026-06.md` · substring:"Run #382 (run_number 268) sha=`5998163`" +- 2026-06-11 · S57bis PE gắn WorkItemId loose-Guid (Mig 49) · PASS+1PARTIAL · `2026-06.md` · substring:"Run #381 (run_number 267) sha=`dd117b7`" +- 2026-06-09 · S56 GOLIVE-HARDEN BE fixes · PASS · `2026-06.md` · substring:"Run #379 (run_number 265) sha=`a20cde8`" +- 2026-06-09 · S56 pre-golive re-verify prod truth · **[audit]** no-deploy read-only · `2026-06.md` · substring:"S56 pre-golive verify — NO deploy" +- 2026-06-09 · S55 Phase-1 FE-Admin visual redesign · PASS asymmetric fe-admin-only rotate, other-frozen · `2026-06.md` · substring:"Run #378 (run_number 264) sha=`7feb53e`" +- 2026-06-09 · S55 HMW-P4 real master-data seed (Mig 48, 62 dự án+71 hạng mục+3 NCC) · PASS ungated-seed sqlcmd-COUNT + =N'…' EXACT unicode · `2026-06.md` · substring:"Run #377 (run_number 263) sha=`69cb393`" +- 2026-06-08 · S54 IT-staff self-reassign ticket · PASS · `2026-06.md` · substring:"Run #376 (run_number 262) sha=`ca4b602`" +- 2026-06-08 · S50 HMW-Wave2 P11-C Vehicle+Driver · PASS Discovery#3 mixed-docs+tsx whole-range-builds · `2026-06.md` · substring:"Run #371 (run_number 257) sha=`30a99aa`" +- 2026-06-03 · S48 FE-only login subtitle a11y · PASS · `2026-06.md` · substring:"Run #369 (run_number 255) sha=`350b2bf`" +- 2026-06-01 · S45 Mig 43 filter Holiday UNIQUE · PASS · `2026-06.md` · substring:"Run #368 (run_number 254) sha=`0c5a014`" +- 2026-05-30 · S42 P11-B LeaveBalance business · PASS · `2026-06.md` · substring:"Run #367 (run_number 253) sha=`82d7fcf`" +- 2026-05-30 · S42 P11-A workflow-picker 2-bug + SetWorkflow · PASS · `2026-06.md` · substring:"Run #365 sha=`75df04e`" +- 2026-05-30 · S42 P11-A wire ApproveV2+LevelOpinions · PASS (mem #250 ≠ real Gitea id — reconcile via run_number) · `2026-06.md` · substring:"Run #364 (mem #250) sha=`e7b66cd`" +- 2026-05-28 · S38 SKELETON 5-plan combo Mig 39+40 dual · PASS · `2026-06.md` · substring:"Run #247 sha=`e54a22d`" +- 2026-05-?? · S37 Proposal Mig 37+38 (/api/proposals 200 + QT-DX-V2-001 AppType=4) · PASS [archived] · `2026-06.md` · substring:"Archived Run #246 (S37 Proposal Mig 37+38" +- 2026-06-11 · ⚠️ VỊ-TRÍ-LẠC entry (mem run id ≠ real Gitea — FIFO slot between #384/#382) · note · `2026-06.md` · substring:"VỊ TRÍ LẠC — entry MỚI 2026-06-11" +- 2026-05-22 · S29 gotcha #51 catch (SeedSampleContractWorkflowV2 nested in demoSeedDisabled) · PASS [archived] · `2026-06.md` · substring:"Archived Run #232 (S29 gotcha #51 catch" + +## 2026-05 late (file: `archive/2026-05-q4.md`) + +- 2026-05-27 · BE+FE×2 Danh bạ nội bộ /api/directory (G-O1) · PASS bundle-both-rotate, menu-seed Off/Off_DanhBa, MediatR auto-discovery Office namespace · `2026-05-q4.md` · substring:"Run #238 (S34 Plan 2 G-O1 Danh bạ nội bộ" +- 2026-05-26 · BE Hrm CQRS /api/employees + FE×2 + menu (G-H1 Phase2) · PASS bundle-both-rotate, EmployeeProfiles=33 idempotent · `2026-05-q4.md` · substring:"Run #237 (S33 Plan B G-H1 Phase 2 Task 4+5+6" +- 2026-05-26 · BE Mig 34 EmployeeProfile + Plan C BW1-BW7 +9 test (62 Infra) · PASS gotcha#51 INFRASTRUCTURE-seed EmployeeProfiles=33 not-gated · `2026-05-q4.md` · substring:"Run #350 (S33 Plan B G-H1 Mig 34 EmployeeProfile" +- 2026-05-26 · S33 startup health-check · **[audit]** HEALTHY, last-5-runs success, Discovery#7 eval/** missing-from-paths-ignore wasteful-trigger · `2026-05-q4.md` · substring:"S33 startup health-check — em main spawn read-only verify, VERDICT=HEALTHY" +- 2026-05-26 · S32 wrap curate + Phase9→10 · **[meta]** no-run docs-only skip · `2026-05-q4.md` · substring:"S32 wrap — em main proxy curate + Phase 9 stabilize done" +- 2026-05-26 · S32 startup verify (foundation freshness + 3-endpoint smoke) · **[audit]** no-CI-poll, RAG-healthy 2949-chunks · `2026-05-q4.md` · substring:"S32 startup verify — no CI poll" +- 2026-05-21 · S26 Plan AG PE-List tree-view UI iteration (#222-#227 cumulative) · PASS hybrid-verify spawn-1×-per-phase-wire · `2026-05-q4.md` · substring:"Run #222-#227 cumulative — Plan AG series PE List tree view" + +## 2026-05 mid (file: `archive/2026-05-runs.md`) + +### Run-records (20) +- 2026-05-19 · S25 t7 Plan AF FE userMap fallback resolve historical · PASS bundle-rotate C8TvDy7r/BvcWrq2z, Discovery 503-mid-deploy + auth-route /api/auth/login accessToken · `2026-05-runs.md` · substring:"Run #221 id=335 sha=`506cada`" +- 2026-05-19 · S25 t6 Plan AE Changelog UserName 9-sites batch · PASS BE-only bundle-frozen, preventive-batch-fix-all-Add()-sites · `2026-05-runs.md` · substring:"Run #220 id=334 sha=`9ea62be`" +- 2026-05-19 · S25 t5 Plan AD Lịch-sử-duyệt redesign next-target-hint · PASS FE-only bundle-rotate, parse-comment-semantic-hint · `2026-05-runs.md` · substring:"Run #219 id=333 sha=`0aaf2df`" +- 2026-05-19 · S25 t4 Plan AC2 FE merge recover historical Reject · PASS bundle-rotate, synthetic-rows-from-Changelog-entityType5-ContextNote · `2026-05-runs.md` · substring:"Run #218 id=332 sha=`25837b6`" +- 2026-05-15 · S24 t1 Plan AA wrap fix sidebar · PASS last-deploy-before-AB, baseline CZdXQ2eo/DCwhhey2 · `2026-05-runs.md` · substring:"Run #214 id=328 sha=`ee0902a`" +- 2026-05-15 · S24 t1 Plan AA IsUserSelectable filter + WfView menu + sidebar (4/4 wire) · PASS bundle-rotate, Order-shift-idempotent, non-admin-read-200-not-403 · `2026-05-runs.md` · substring:"Run #210 id=324 sha=`ac2c859`" +- 2026-05-15 · S23 t11 Plan U FE sidebar truncate · PASS long-label ellipsis+title-tooltip · `2026-05-runs.md` · substring:"Run #209 sha=`86d8806`" +- 2026-05-15 · S23 t10 Plan T5+T6 docs final (DemoSeed wipe ~720 rows + force recycle no-reseed) · PASS flag-proven-end-to-end · `2026-05-runs.md` · substring:"Run #208 sha=`7b7b28f`" +- 2026-05-15 · S23 t10 Plan T DemoSeed disable (flag in appsettings, 5 demo-seed gated) · PASS · `2026-05-runs.md` · substring:"Run #207 sha=`0b97840`" +- 2026-05-15 · S23 t7 Plan Q FE banner mx-5 fix · PASS bundle-rotate QZIPWD-g/DaLTMGcx · `2026-05-runs.md` · substring:"Run #204 sha=`108268a`" +- 2026-05-15 · S23 t6 Plan P HOTFIX Controller TransitionPeBody +3 fields · PASS Discovery#4 ASP.NET10-record-enum-needs-numeric (no JsonStringEnumConverter), point-10 body-record-mirror-count · `2026-05-runs.md` · substring:"Run #203 id=317 sha=`1727bd5`" +- 2026-05-15 · S23 t5 Plan O HOTFIX cascade 4 lookup-sites (point 9) · PASS 409-mode-not-403-actor proves discrimination, Discovery#3-reinforce-3rd, grep-FirstOrDefault-Order-scan · `2026-05-runs.md` · substring:"Run #202 id=316 sha=`a1c8386`" +- 2026-05-15 · S23 t4 Plan N HOTFIX per-NV lookup discrimination :765 · PASS 7/7-vs-1/7-per-actor, bug-2days-prod, point-9 lookup-site · `2026-05-runs.md` · substring:"Run #201 id=315 sha=`fb3c22c`" +- 2026-05-15 · S23 t3 Plan M Service F1 OneLevel/OneStep edge-case Bước1 reset · PASS Discovery#3-anomaly docs-only-tip-triggered, Mig31-unchanged · `2026-05-runs.md` · substring:"Run #200 id=314 sha=`f4055a1`" +- 2026-05-14/15 · S23 t1 K10 hotfix AwLevelDto +AllowApproverSkipToFinal · PASS 8-surface-point-checklist (AwLevelDto+CreateAwLevelInput were the gaps) · `2026-05-runs.md` · substring:"Run #195 id=309 sha=`0062fcb`" +- 2026-05-14 · S23 t1 Plan K Mig 31 per-Approver-slot refactor · **[PARTIAL]** Mig+schema OK but K3 DTO-mirror INCOMPLETE (AwLevelDto missing AllowApproverSkipToFinal — comment-only not record-param) → 7th-checkbox no round-trip · `2026-05-runs.md` · substring:"Run #194 id=308 sha=`098baa6`" +- 2026-05-13 · S22-chốt cumulative verify · PASS Discovery#3 push-range-eval (b079b27+b04a11a batched→single-run-on-tip), #190 cancelled-by-concurrency-normal, 104 test, Mig30 · `2026-05-runs.md` · substring:"Verify S22 chốt cuối cumulative (push range `3d725c4..cc8a7d3` 12 commits) VERDICT=PASS" +- 2026-05-13 · S22 Plan D Users-toggle + Plan C 14-test + Plan E strict-V2-scope · PASS 103 test, Discovery#1 rate-limit-429-backoff, Discovery#2 agent-memory-NOT-in-paths-ignore (later disproven) · `2026-05-runs.md` · substring:"Run #188 id=302 sha=a74e671" +- 2026-05-13 · S21 t5 Mig 29 refactor Allow*-per-NV + Designer 5-checkbox · PASS Mig29 applied, Discovery task-table-updated_at-stale-2min cross-check-VPS-mtime · `2026-05-runs.md` · substring:"Run #187 id=301 sha=c0af9e0" +- 2026-05-13 · S21 t3+t4 gotcha#45 Trả-lại + F1/F2/F3 advanced-options + Mig 28 · PASS baseline, Discovery API-tasks-not-/runs-404 · `2026-05-runs.md` · substring:"Run #186 id=300 sha=eea86fd" + +### Curate-note meta-headers (6) — [meta] +- 2026-05-19 · curate-note S25 t5 Plan AD #219 · **[meta]** · `2026-05-runs.md` · substring:"added S25 t5 Plan AD Run #219 entry" +- 2026-05-19 · curate-note S25 t3 Plan AC #217 (Discovery#5 sqlcmd 4-backslash \\\\SQLEXPRESS) · **[meta]** · `2026-05-runs.md` · substring:"added S25 t3 Plan AC Run #217 entry" +- 2026-05-19 · curate-note S25 t1 Plan AB #215 (gotcha#48 SQLite tie-break FAIL) · **[meta]** · `2026-05-runs.md` · substring:"added S25 t1 Plan AB Run #215 entry" +- 2026-05-15 · curate-note S24 t1 Plan AA #210 · **[meta]** · `2026-05-runs.md` · substring:"added S24 t1 Plan AA Run #210 entry" +- 2026-05-15 · curate-note S23 t6 Plan P #203 (Discovery#4 numeric-enum) · **[meta]** · `2026-05-runs.md` · substring:"added S23 t6 Plan P HOTFIX Run #203 entry" +- 2026-05-15 · curate-note S23 t5 Plan O #202 (4-site discrimination, TransitionPeBody gap surfaced) · **[meta]** · `2026-05-runs.md` · substring:"added S23 t5 Plan O HOTFIX Run #202 entry" + +## 2026-05 early (file: `archive/2026-05-q3.md`) + +- 2026-05-19 · ★ Run #215 FAIL → #216 PASS pair — **gotcha #48 SQLite tie-break** catch+fix (.Where(Summary.Contains "Chuyển phase") BEFORE OrderByDescending(CreatedAt).First()) · FAIL→PASS · `2026-05-q3.md` · substring:"Run #215 FAIL → Run #216 PASS (gotcha #48 SQLite tie-break catch+fix pair)" +- 2026-05-13 · Verify S22 chốt cuối cumulative (Discovery#3 first-surfaced, 33 users, 104 test) · PASS · `2026-05-q3.md` · substring:"Verify S22 chốt cuối cumulative (push range `3d725c4..cc8a7d3` 12 commits VERDICT=PASS" +- 2026-05-12 · Setup baseline (agent init, 44 gotchas + 5-stage + 3 skills) · init · `2026-05-q3.md` · substring:"2026-05-12 — Setup baseline" + +## 2026-05 early (file: `archive/2026-05-q2.md`) + +- 2026-05-22 · Run #231 Plan B Contract V2 wire kick-off · **[PARTIAL]** PASS-deploy + seed-gap (DemoSeed flag gated QT-HD-V2-001 → empty dropdown) → resolved Run #232 (gotcha #51) · `2026-05-q2.md` · substring:"Run #231 (id=345) Plan B Contract V2 wire kick-off VERDICT=PARTIAL" diff --git a/.claude/agent-memory/implementer-backend/MEMORY.md b/.claude/agent-memory/implementer-backend/MEMORY.md index ade341a..181fe98 100644 --- a/.claude/agent-memory/implementer-backend/MEMORY.md +++ b/.claude/agent-memory/implementer-backend/MEMORY.md @@ -2,7 +2,7 @@ > **Persistent diary cross-session.** Auto-injected first ~200 lines at spawn (L1 HOT). > Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 `archive/` on-demand · L3 RAG `search_memory` just-in-time. Keep entry ≤ 1.5K chars (gotcha #53). -> Full verbatim history pre-S40 → git `d2f52ba` + `archive/2026-05-q1..q4.md`. +> Full verbatim history pre-S40 → git `d2f52ba` + `archive/2026-05-q1..q4.md` + `archive/2026-06.md`. 🗺️ **Lookup map (Harness-9 S70): `archive/_INDEX.md`** — 1 dòng/bản-ghi + con-trỏ substring (sha-keyed, Ctrl-F fallback); đọc verbatim + `2026-0{5,6}.gist.md` (nén 4-field) theo nhu cầu. > **Renamed S39:** implementer → implementer-backend (.NET half). FE patterns → `implementer-frontend` MEMORY. Test patterns → `test-specialist` MEMORY. --- @@ -84,24 +84,6 @@ UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT i - **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]`. - -- **S53 gotcha #57 EXT BE — filter 3 Master Code unique indexes + Mig 46 local catch-up (Mig 47 `FilterMasterCatalogUniqueIndexesByIsDeleted`, index-only no-table):** Test-before RED→GREEN driven (test-specialist `MasterCatalogFilteredUniqueTests`, 3 FAIL on unfiltered → must turn GREEN). gotcha #57 4th+5th+6th cumulative (S45 Holiday Mig 43, S51 HRM×3 Mig 45 → now Department/Project/Supplier). Edit 3 config Code unique: `b.HasIndex(x=>x.Code).IsUnique()` → `+.HasFilter("[IsDeleted] = 0")`. **KEY: copied EXACT filter string byte-for-byte from HolidayConfiguration:18 + LeaveTypeConfiguration:19** (spaces around `=`, NOT guessed) → snapshot+SQL Server consistent with 13 existing filtered indexes. SupplierConfiguration: ONLY Code index filtered, `HasIndex(x=>x.Type)` :25 LEFT untouched (verified snapshot 3590 Type no-filter). Mig diff CLEAN: Up=3×DropIndex+3×CreateIndex filtered, Down=3×reverse unfiltered (no drift, no extra table/col). Master entities HAVE global `HasQueryFilter(!IsDeleted)` (unlike HRM) — app-check `db.X.AnyAsync(Code==req.Code)` filters soft-deleted → passed; then bare DB index counted it → UNIQUE violation 500. Filter aligns DB index with app-check. **Mig 46 local catch-up:** S52 left local LocalDB stuck at Mig 45 (prod had 46 via CI, local gap). `database update` to BOTH DBs applied Mig 46 (`AddSlaFieldsToItTicket`) THEN Mig 47 — residual closed. Dev override `--connection SolutionErp_Dev`; Design factory-default `SolutionErp_Design` (both `(localdb)\MSSQLLocalDB`). Build 0 err (2 pre-existing DocxRenderer warn). Full suite 203 GREEN (58 Domain + 145 Infra, +3 new). KHÔNG touch FE/test/commit (em main commits). Tag `[s53, gotcha-57-ext, mig47, mig46-catchup, filter-byte-for-byte]`. - -- **S52 P11-D Wave2 BE — ItTicket round-robin + SLA (Mig 46 `AddSlaFieldsToItTicket`, 3 AddColumn no-table) [proxy by em main: agent killed session-limit trước MEMORY step]:** Entity +SlaDueAt/SlaWarnedSent/SlaBreached. `CreateItTicketHandler`: `SlaWindow` static map (Urgent4/High8/Medium24/Low72h) **shared với `ItTicketSlaJob`** (single-source) → `e.SlaDueAt=CreatedAt+window`. Round-robin least-loaded: `db.Users.Where(DepartmentId==itDeptId && IsActive).OrderBy(db.ItTickets.Count(assigned && !Closed && !Resolved)).ThenBy(Id).First()` (itDept = `Departments.Code=="IT"`); no IT-staff → unassigned. NEW `ItTicketSlaJob:BackgroundService` mirror SlaExpiryJob (30s warmup/15min loop/scope) NHƯNG **KHÔNG auto-transition** — chỉ breach (SlaDueAt` sau SlaExpiryJob. `AssignItTicketCommand` + PUT `/{id}/assign` `[Authorize(Roles=Admin)]`. DTO +SlaDueAt/SlaBreached + projection. **DbInitializer `SeedItDepartmentStaffAsync` KEY ordering: PHẢI chạy SAU `SeedDemoUsersAsync`** (method đó reconcile 2 user dept về PRO/CCM mỗi boot → override về IT sau, end-state deterministic) + dept "IT" thứ 10 + gán nv.cao/nv.truong (sample user, idempotent). Build 0-err. Tag `[s52, p11-d, mig46, round-robin, sla-job, seed-ordering]`. - -- **S52 P11-E+F Wave1 BE migration-FREE (4 file new + 3 edit, NO mig):** Case 1/2 deterministic ~98% em main. **P11-F** (2 LOC): `CreateItTicketHandler` set `e.MaTicket = WorkflowAppCodeGen.GenerateMaDonTuAsync(db,"IT",clock.Now.Year,clock,ct)` TRƯỚC `db.ItTickets.Add` — gen lúc Create (kanban no-workflow, khác Leave/OT gen lúc Submit). Helper = `internal static` cùng ns Office, gọi trực tiếp no-using. Format `IT/2026/001`. **P11-E** report chấm công: NEW `AttendanceReportFeatures.cs` (DTO 3 record EXACT + GetAttendanceReportHandler) + NEW `IAttendanceReportExcelExporter`(App/Reports/Services) + NEW Infra `AttendanceReportExcelExporter`(ClosedXML mirror ContractExcelExporter, sync `Export(dto)` no-DB) + DI scoped + Controller +2 endpoint `[Authorize(Roles=Admin)]`. **KEY gotcha day-type:** DayOfWeek+holidaySet KHÔNG EF-translate → `.ToListAsync()` rồi group/classify IN-MEMORY C#. **KEY gotcha type:** `Holiday.Date`=**DateOnly** KHÔNG DateTime (spec viết HashSet nhầm) → `HashSet` + so `DateOnly.FromDateTime(a.AttendanceDate.Date)`. OtPolicy active fallback 1.5/2.0/3.0. Day-type prio: holiday→weekend(Sat/Sun)→weekday. OtWeighted=Σ(cấp×hệ số). FullName denorm ưu tiên `Attendance.UserFullName` rồi User.FullName. DeptName LEFT JOIN. RenderResult=(Content,FileName,ContentType) ns Forms.Services. Controller inject exporter ctor. Build 0 err (2 pre-existing DocxRenderer warn). KHÔNG touch FE/test/mig/ItTicket-khác. Routes: GET `/api/attendances/report?year&month&departmentId` + `/report/excel`. Tag `[s??, p11-e-f, wave1, no-mig, day-type-in-memory, dateonly-holiday]`. - -- **S51 P11-C HMW W1 — Vehicle+Driver catalogs HrmConfigs (Mig 44 `AddVehicleAndDriverCatalogs`, 9 add-point ~12 file/edit):** Pattern 12-bis catalog-mega 4th cumulative. 2 entity (`Vehicle`/`Driver`:AuditableEntity Domain/Hrm) + 2 EF config (mirror `HolidayConfiguration` filtered, NOT buggy bare LeaveType) + 2 DbSet (IAppDbContext+ApplicationDbContext) + Mig 3-file + HrmConfigFeatures Region5/6 (DTO+List/Create/Update/Delete CQRS mirror Region1 EXACT) + Controller +2 route group (8 endpoint, GET public + POST/PUT/DELETE `[Authorize(Roles=Admin)]`) + MenuKeys +2 const +All array + DbInitializer (menu 2 leaf + SeedHrmConfigsAsync guard&seed 2 veh+2 drv). **gotcha #57 KEY:** Code UNIQUE `.HasFilter("[IsDeleted]=0")` — Mig diff verified CLEAN 2 CreateTable + 2 filtered IX no drift. Validator MaxLength = em main schema (Code50/Name200/Plate20/Phone20/LicNum50/LicClass20/Desc500), SeatCount GreaterThanOrEqualTo(0). Admin perm AUTO-grant: `SeedAdminPermissionsAsync` + `Program.cs:78` both iterate `MenuKeys.All` → +All array = 8 policy + Admin row auto (no manual grant code). HRM no HasQueryFilter → `.Where(!IsDeleted)` manual. Applied BOTH DB. Build 0 err (2 pre-existing DocxRenderer warn). RAG/Qdrant DOWN → all Read/Grep on-disk. Spec deterministic ~98% em main → ACCEPT Case 1. KHÔNG touch FE/test/commit. Tag `[s51, p11-c, mig44, vehicle-driver, catalog-mega]`. -- **S43 P11-B Wave 1 — LeaveBalance business logic (Mig 42 `AddLeaveBalances`, 7 file: 1 entity + 1 config + 2 DbSet edit + Mig 3-file + 1 hook edit + 1 Features + 1 Controller):** Case 1/3 deterministic ~98% em main spec. Pattern 12-ter-adjacent single-entity: entity `LeaveBalance:AuditableEntity` (UserId/LeaveTypeId/Year + EntitledDays/UsedDays/AdjustmentDays decimal(5,2), nav LeaveType). Config FK LeaveType WithMany() **Restrict** (catalog no cascade) + UNIQUE composite (UserId,LeaveTypeId,Year) + IX UserId. Mig diff CLEAN: 1 CreateTable + 3 IX, no drift. Applied BOTH DB (Dev `SolutionErp_Dev` + Design default). **Deduction hook:** insert in `ApproveLeaveRequestHandler` terminal else (DaDuyet branch) ONLY — UPSERT LeaveBalance, `bal.UsedDays += p.NumDays`, exactly-once guaranteed by early guard `Status != DaGuiDuyet throw`. OtRequest/Travel/Vehicle UNTOUCHED (only Leave has balance). CQRS `Application.Hrm`: DTO RemainingDays=Entitled+Adjustment−Used COMPUTED (not stored) + GetMy/GetUser lazy-merge (load active LeaveTypes + balances → in-memory merge, synth default when no row — KHÔNG EF LEFT JOIN translate) + AdjustLeaveBalanceCommand admin upsert (HasValue-gated). **Policy resolved:** HRM admin convention = `[Authorize(Roles="Admin")]` NOT menu policy (verified HrmConfigsController write endpoints) — used on GET-by-user + PUT /adjust; /my = `[Authorize]`. Controller injects IDateTime for year default `clock.Now.Year` (thin, no DateTime.Now hardcode). HRM no HasQueryFilter → `.Where(!IsDeleted)` manual everywhere. KHÔNG touch FE/test/commit. Build 0 err (2 pre-existing DocxRenderer warn). Tag `[s43, p11-b-w1, mig42, leave-balance, single-entity]`. -- **S42 P11-A SEED — 4 sample ApprovalWorkflow V2 for WorkflowApps (DbInitializer.cs only, +4 method ~210 LOC + 4 call):** Case 1 mechanical mirror `SeedSampleProposalWorkflowV2Async` EXACT × 4 (Leave5/Ot6/Travel9/Vehicle7). Each: idempotent `AnyAsync(ApplicableType==X)` guard → resolve approver `binh.le@solutions.com.vn` (SAME user as Proposal/Contract seed, null→LogWarning+return) → 1 ApprovalWorkflow (Version=1, IsActive+IsUserSelectable=true, ActivatedAt=UtcNow) + 1 Step (Order=1, Name="Cấp duyệt", DepartmentId=CCM?.Id) + 1 Level (Order=1, ApproverUserId). Codes QT-NP/OT/CT/XE-V2-001. Wired 4 calls after SeedSampleProposalWorkflowV2Async (NOT gated DemoSeed, gotcha #51 infra seed). Enum verified Grep. Build 0 err 0 warn. Bash tool = bash NOT PowerShell despite env hint (use `cd && cmd | grep`). Spec deterministic 100% → ACCEPT Case 1. NOT touched App/Controller/FE/test/Mig. Tag `[s42, p11-a, seed, mirror-proposal-exact]`. -- **S42 P11-A Wave 2b APP — wire ApproveV2 CQRS Travel+Vehicle (`TravelVehicleApprovalFeatures.cs` ~830 LOC + 2 controller edit):** Cookie-cutter mirror Wave 2a / ProposalFeatures Region 2. 1 new file ns `Application.Office`: 2 module × (DetailDto + LevelOpinionDto + GetById JOIN Step/Level metadata + UpdateDraft + Submit + Approve UPSERT+advance + Reject TuChoi + Return TraLai+RejectedFromStatus) + 1 shared `internal static TravelVehicleCodeGen.GenerateMaDonTuAsync` (Serializable tx, `WorkflowAppCodeSequences` Prefix-keyed, prefix `DT/CT/{year}` Travel & `DX/XE/{year}` Vehicle, format `{prefix}/{seq:D3}` — D3 no year segment per spec). KEY gotcha: WorkflowAppStatus enum DIFFERS from ProposalStatus int values (DaGuiDuyet=2 not 1, TraLai=3) → mirror by SEMANTIC enum member not literal. Owner = `RequesterUserId` (not DrafterUserId). Submit verify wf.ApplicableType==Travel9/Vehicle7 else Conflict. 2 controller +6 route each (GET{id}/PUT/submit/approve/reject/return) nested body records, CreatedAtAction. KHÔNG sửa WorkflowAppsFeatures.cs/Leave/Ot/FE/test/seed. Build 0 err. Spec deterministic ~98% em main → ACCEPT Case 1/2. Tag `[s42, p11-a, wave-2b, mirror-proposal-region2]`. -- **Archived S42 P11-A Wave 2a APP (LeaveOt ApproveV2 CQRS) → git history (S65b FIFO trim):** KEY absorbed Pattern 4 + WorkflowAppCodeGen Serializable-tx Prefix-keyed `{prefix}/{year}/{seq:D3}` + WorkflowAppStatus enum DIFFERS ProposalStatus (mirror SEMANTIC member not literal) + Owner=RequesterUserId. Detail in Wave 2b entry above + S56 em-main Serializable note. -- **S41 P11-A Wave 1 SCHEMA — wire ApproveV2+LevelOpinions 4 WorkflowApps (Mig 41 `WireWorkflowAppsApprovalV2`):** Pattern 12-bis cookie-cutter mirror Proposal Mig 38, 13× cumulative. 11 file: 5 entity (4 `{Leave,Ot,Travel,Vehicle}RequestLevelOpinion` + shared `WorkflowAppCodeSequence` Prefix-PK) + 5 EF config (auto-discover ApplyConfigurationsFromAssembly, no manual register) + edit 4 parent (nav LevelOpinions + `WorkflowAppStatus? RejectedFromStatus`) + edit enum (`TravelRequest=9`) + 2 DbSet (IAppDbContext+ApplicationDbContext, 5 each). Mig diff CLEAN: 5 CreateTable + 4 AddColumn (no drift). FK Cascade parent + Restrict Level + UNIQUE composite ({Parent}Id, ApprovalWorkflowLevelId). Applied BOTH DB (Dev + Design). Build 0 err. Wave 2 (App/Controller) + Wave 4 (test) deferred per spec. Spec deterministic 100% (em main chose schema) → ACCEPT Case 1. Tag `[s41, p11-a, 12-bis-13x, mig41]`. -- **Archived S35 G-H2 BE-CRUD-4-catalog + S29 Plan-B-Chunk-C Contract-V2-mirror → `archive/2026-05-q4.md` (S65 FIFO trim):** KEY absorbed Pattern 12-bis foundation above (catalog-mega + HRM `.Where(!IsDeleted)` manual + Validator MaxLength=EF-source). -- **Archived FE/test + older BE entries → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** S35 FE inline forms 5 satellite (→ frontend domain) · S34 test bundle +10 [Fact] 130 PASS (→ test-specialist domain) · S33 Task 5 EmployeesListPage · S32 wrap/startup. KEY absorbed in Patterns above + split pointers. --- diff --git a/.claude/agent-memory/implementer-backend/archive/2026-05.gist.md b/.claude/agent-memory/implementer-backend/archive/2026-05.gist.md new file mode 100644 index 0000000..c642123 --- /dev/null +++ b/.claude/agent-memory/implementer-backend/archive/2026-05.gist.md @@ -0,0 +1,51 @@ +distill-gen: 1 +# Gist — 2026-05 archive (q1+q2+q3+q4 · S21-S35) · 4-field distillation + +> **distill-gen: 1** — already-distilled; do NOT re-compress. Each block = VIỆC · KẾT-LUẬN(+commit/file:line) · BÀI-HỌC · BẤT-NGỜ, ending `→ substring:"…"` that grep-resolves UNIQUE in the named verbatim file. Pointer-style = substring (Ctrl-F), git-SHA / Mig-name / phrase keyed (dates collide). value tag: cao/vừa/thấp. +> Covers verbatim files: 2026-05-q1.md (S21-24), 2026-05-q2.md (S25-29), 2026-05-q3.md (meta wraps), 2026-05-q4.md (S32-33 + S35/S29 stubs). Companion: `2026-06.gist.md` (S41-55). + +--- + +## q1 · S21-S24 (PE workflow V2 per-NV flags + ReturnMode + FE tree-prep) + +**[cao] PE per-NV flag schema chain (Mig 29/30/31) + EF backfill discipline.** VIỆC: across S21-S23 wire per-NV admin opt-in flags on ApprovalWorkflowLevel (AllowDrafterEdit/AllowApproverEditSection1/AllowApproverSkipToFinal); Mig 31 swapped F2 storage Users→ApprovalWorkflowLevels via ADD-DROP no-BACKFILL (accept losing 4 prod-user values). KẾT-LUẬN: Pattern 7 reinforced; both DB (Dev+Design) applied; 6 BE file cookie-cutter. BÀI-HỌC: **EF migration BACKFILL reorder pattern** + per-NV permission scope split natural by role (Drafter→User table, Approver→Level table carry ApproverUserId FK); clarify default-behavior vs admin-opt-in BEFORE expanding default scope. BẤT-NGỜ: UserConfiguration.cs does NOT exist — User configured inline in ApplicationDbContext.OnModelCreating ~L86, EF picks prop change with no HasDefaultValue. → substring:"Mig 31 schema swap F2 storage Users → ApprovalWorkflowLevels" + +**[cao] REFUSE-class boundary validated (S21/S22 100% refuse).** VIỆC: em main classified all S21 t3-t5 + all S22 turns as cross-stack reasoning chain (BE Mig+Service+DTO+FE Designer+test) → REFUSE criteria #3 (cross-stack >2 layer) + #4 (bug-fix reasoning chain); em main solo executed. KẾT-LUẬN: REFUSE-class = cross-stack >2 layer + bug-fix reasoning chain = em-main-solo (Smart-Friend bar held). BÀI-HỌC: **gotcha #45 = bug + reasoning** (not mechanical) drove S21 REFUSE; strict scope matched Anthropic "tightly interdependent coding" warning. BẤT-NGỜ: S22 mismatches — API auth field is `accessToken` not `token` (seed script 401); Dialog `size="xl"` unsupported (only sm/md/lg); PE.changelogs absent from PeDetailBundle (TS2339). → substring:"gotcha #45 = bug + reasoning" + +**[vừa] FE Designer per-slot checkbox + label polish (S23 K1/K3/pre-A).** VIỆC: ApprovalWorkflowsV2Page.tsx — 7th checkbox AllowApproverSkipToFinal + banner rewrite + slot label `Quyền duyệt {fullName}` lookup from usersList query (line 873). KẾT-LUẬN: fe-admin only (fe-user has no Designer); 0 TS err, bundle 1395KB unchanged. BÀI-HỌC: EditLevelEntry lacks approverFullName → lookup `usersList.data?.find(u=>u.id===entry.approverUserId)` (Option A). BẤT-NGỜ: per-slot mirror reinforced 3× cumulative (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2). → substring:"UI polish slot label Designer Mig 31 prep" + +**[vừa] Zombie F2 cleanup (S23 K5).** VIỆC: drop dead F2 endpoint + UsersPage "Skip cuối" column + DTO field + stale narrative comments (Reviewer Major #1/#2 + Minor #3/#4), 9 file. KẾT-LUẬN: +42/-94 LOC; grep `AllowDrafterSkipToFinal`/`allow-skip-final`/`FastForward` = ZERO across src. BÀI-HỌC: post-refactor full cleanup atomic 1 commit. BẤT-NGỜ: none. → substring:"Cleanup zombie F2 endpoint + UsersPage column" + +**[cao] ReturnMode test discipline (S23 K7 + S24 M2).** VIỆC: K7 delete 3 deprecated Drafter-F2 tests + add 3 Approver-F2 (`ApproveV2_SkipToFinal_*`), reuse Pattern 11 SeedApproverF2WorkflowAsync (2 Step×2 Level verify skip-to-terminal no fallthrough); M2 add 2 [Fact] reset-Bước1Cấp1-keeps-ChoDuyet. KẾT-LUẬN: 104 PASS (K7) → 106 PASS (M2); file:line `PurchaseEvaluationWorkflowServiceReturnModeTests.cs:253` was the broken K1-flagged ref fixed here. BÀI-HỌC: extend SeedWorkflowAsync helper with optional params (default false) instead of cloning — no compat break to 4 existing tests. BẤT-NGỜ: Changelog Summary is fixed `"Chuyển phase {from}→{to}"`; mode text ("không lùi được") lands in ContextNote not Summary. → substring:"Mig 31 Approver F2 service regression tests" + +**[vừa] FE label rename + read-only matrix (S24 M3 + Plan AA-B).** VIỆC: M3 rename Phase=TraLai(98) "Trả lại"→"Cần chỉnh sửa lại" 4 file ×2 app (status-display refs only, NOT action verb); Plan AA-B new WorkflowMatrixViewPage fe-user (10-col table, GET `?isUserSelectable=true` server-filter Mig 25). KẾT-LUẬN: both builds 0 TS err. BÀI-HỌC: read-only mirror of admin Designer = drop mutations + subset DTO + filter BE-side. BẤT-NGỜ (marker #18): **shadcn fe-user lacks Card/Badge** (only Button/Dialog/Input/Label/Select/Textarea) → inline `
` + `` fallback. → substring:"FE user read-only matrix view workflow V2 ghim" + +**[thấp] REFUSE cumulative log + AA wrap (S23 t4-t11, S24 AA).** VIỆC: 8 consecutive plans em-main-solo (Plan N..U), incl destructive sqlcmd cleanup prod (~720 rows) + DemoSeed:Disabled flag; AA wrap added Patterns 13/14/15. KẾT-LUẬN: REFUSE 4/4 polish chunks correct (criteria #6 <30min / #2 UX). BÀI-HỌC: audit shadcn fe-user/components/ui BEFORE import (subset lib). BẤT-NGỜ: none. → substring:"8 plan consecutive em main solo" + +## q2 · S25-S29 (audit-log refactor + FE tree + master mirror + Contract V2 dropdown) + +**[cao] Changelog 4-mode uniform log + FE substring filter (S25 AB-A, commit cdfd542).** VIỆC: ApplyReturnModeAsync lines 215-378 refactor — Drafter early-return → if/else common path, single Changelog.Add() at end covering 4 modes (`Trả lại ({modeName}): {summary}`); FE PeDetailTabs ×2 app filter by substring summary ("Trả lại"/"ngân sách") not enum. KẾT-LUẬN: commit cdfd542, +146/-95 LOC, 3 file; no new SaveChanges (caller TransitionAsync saves). BÀI-HỌC: refactor early-return→common-path so ONE log call covers N branches; FE filter via substring keyword (maintainable, no BE schema migrate). cross-ref Pattern 4. BẤT-NGỜ: none. → substring:"ApplyReturnModeAsync` lines 215-378 refactor" + +**[cao] PE list tree view — Pattern 19 (S26 Plan AG, commit 0bf6c7e).** VIỆC: flat PE list → Outlook 2-level folder tree (Project>Gói thầu) via useMemo group + HTML `
/` + Tailwind named groups `group/proj`+`group-open/proj:rotate-90` + localStorage Set persist. KẾT-LUẬN: commit 0bf6c7e, +346/-116 LOC, ×2 app SHA256 IDENTICAL. BÀI-HỌC: **Pattern 19** native details/summary + named-groups + localStorage Set = free tree UI when no Accordion lib (Space/Enter accessible, 0 JS state/node). BẤT-NGỜ: nested SAME-name `group` inherits parent state→both rotate sync (must use distinct named groups). → substring:"`0bf6c7e` 2 file +346/-116 LOC" + +**[cao] 4 master pages byte-identical mirror — Pattern 16 (S27 CA-B, commit 06a441c).** VIỆC: move 4 master pages fe-admin→fe-user via copy (no modify) + menuKeys +5 Catalogs* + App.tsx routes. KẾT-LUẬN: commit 06a441c, 6 file, 948 LOC mirror, SHA256 byte-identical 4 file (C1760788/BDF0529E/68213D62/6F482614). BÀI-HỌC: **Pattern 16** byte-identical admin→user mirror when parity confirmed → SHA256 verify post-write, regression-safe (admin UAT-passed). saved `pattern_master_page_mirror.md`. BẤT-NGỜ: PowerShell `$_` in ForEach-Object eaten by Bash-tool shell-escape → use `Get-FileHash f1,f2 -Algorithm SHA256` list literal. → substring:"Commit `06a441c` 6 file changed" + +**[cao] Contract V2 dropdown FE mirror — Pattern 12-bis cross-module (S29 Plan B-D, commit 62b50d1).** VIỆC: ContractCreatePage ×2 app +useQuery `approval-workflows-v2-contract` (ApplicableType=3, client filter isUserSelectable) + Select dropdown + wire ApprovalWorkflowId into CreateContractCommand (BE field pre-added Chunk E1). KẾT-LUẬN: commit 62b50d1, 2 file, +88 LOC; both builds 0 TS err. BÀI-HỌC: **Pattern 12-bis** PE→Contract clean (same useQuery/Select/filter/POST shape, only discriminator ApplicableType swap); Pattern 16-bis 4-place mirror check (Page/App.tsx/menuKeys/Layout — last 3 N/A here, route enhancement). BẤT-NGỜ: none. → substring:"Commit `62b50d1` clean 2 file" + +**[vừa] S25 wrap — Patterns 16/17/18 NEW.** VIỆC: post-AB-A wrap; 6 follow-ups em-main-solo (synthetic Reject rows, dedupe 5s bucket, userMap fallback). KẾT-LUẬN: Pattern 16 (preventive systemic batch fix N-sites replace_all context-aware key), 17 (FE merge synthetic Changelog rows for audit recovery no-DB-write), 18 (FE userMap fallback from embedded domain data, no extra fetch). BÀI-HỌC: batch-fix 9 same-pattern sites in 1 idempotent pass. BẤT-NGỜ: none. → substring:"Plan AB Chunk A spawn 1× ~12K Case 1" + +## q3 · meta wraps (S27-S29 retrospectives + setup) — mostly [meta] + +**[vừa] S29 wrap — race-condition lesson + Pattern 12-bis NEW.** VIỆC: busiest S29 (5 spawn): 4 master mirror + Plan B 4 chunks (A2 Mig 32 / C Mig 33 ContractLevelOpinions / D FE dropdown / E3 stopped). KẾT-LUẬN: Pattern 12-bis NEW (cross-module entity cookie-cutter PE→Contract 4-file). BÀI-HỌC (marker #16): **race-condition em-main + Implementer parallel BE → stash em-main WIP to build-verify clean; forward SEQUENTIAL A→B→C when both touch BE, NOT parallel**; complex FE mirror w/ type-extend+new-component → em-main-solo more reliable when ambiguity >20%. BẤT-NGỜ: none. → substring:"busiest agent S29 với 5 spawn total" + +**[meta] S28 governance + S27 retrospective.** VIỆC: Layer A distributed governance active; S27 retrospective 2/8 tasks were delegable (Case1+2) but registry empty→forced em-main-solo (~30min loss). KẾT-LUẬN: Pattern 7 proven 4× = Layer-B promote candidate; Pattern 20 (5 PS scripts mirror family). BÀI-HỌC: tag schema `[pattern, phase-, ]`; NO self-authorize cross-project rule. BẤT-NGỜ: none. → substring:"wrap-up retrospective REFUSE analysis" + +**[meta] curate (S29-era) + setup baseline (S11).** VIỆC: archived 12 q1 entries (38.8KB→~22-24KB); S11 agent init w/ 5 patterns S1-S20. KẾT-LUẬN: foundation patterns preserved across curates. BÀI-HỌC: KEEP-vs-CUT §6.5 = em-main judgment (reserve from Implementer). BẤT-NGỜ: none. → substring:"Implementer agent initialized" + +## q4 · S32-S33 + S35/S29 trim-stubs + +**[cao] HRM catalog-mega + Contract V2 (S35 G-H2 + S29 Plan B-C stubs) — Pattern 12-bis foundation.** VIỆC: S35 BE CRUD 4 HRM sub-catalog (HrmConfigFeatures 372 LOC, 16 endpoint, 4 sub × 4 verb); S29 Mig 33 ContractLevelOpinions (8 file +4265 LOC autogen). KẾT-LUẬN: [stub→git d2f52ba] — KEY absorbed into L1 Pattern 12-bis. BÀI-HỌC (markers #14,#15): **HRM entities have NO global HasQueryFilter(!IsDeleted) (unlike Master) → list query MUST `.Where(!IsDeleted)` MANUAL**; **Validator MaxLength = EF config source-of-truth NOT spec (Code=50 not spec's 20)** — verify, don't trust spec blind. BẤT-NGỜ: 130 test baseline preserved through both. → substring:"S35 G-H2 BE CRUD 4 catalog — archived S65 FIFO trim" + +**[vừa] FE HRM scaffold (S33 G-H1 T5).** VIỆC: 3 NEW page ×2 app (employee.ts/EmployeesListPage/EmployeeCreatePage) ultra-minimal read-only 6-section `
`. KẾT-LUẬN: SHA256 IDENTICAL ×2 app (CCFC7066/DC859C89/C796F25D); 120 test preserved. BÀI-HỌC: Pattern 16-bis 4-place mirror 4th + Pattern 12-bis FE port PE→Hrm 4th. BẤT-NGỜ: 0 ambiguity (spec deterministic 100%). → substring:"S33 Plan B G-H1 Task 5" + +**[meta] S32 wrap + startup.** VIỆC: curate q2 (36.2→27.5KB); Plan G 11-module backlog documented; size FLAG 36.2KB>25KB. KẾT-LUẬN: 17 patterns confirmed (12-bis + 16-bis SAVED); RAG live rerank 0.824/0.801/0.793 post-CLI-restart. BÀI-HỌC: self-FLAG over-cap, defer curate to em-main §6.5. BẤT-NGỜ: none. → substring:"S32 startup — context verify + RAG live confirm" diff --git a/.claude/agent-memory/implementer-backend/archive/2026-06.gist.md b/.claude/agent-memory/implementer-backend/archive/2026-06.gist.md new file mode 100644 index 0000000..9500026 --- /dev/null +++ b/.claude/agent-memory/implementer-backend/archive/2026-06.gist.md @@ -0,0 +1,53 @@ +distill-gen: 1 +# Gist — 2026-06 archive (S41-S55) · 4-field distillation + +> **distill-gen: 1** — already-distilled; do NOT re-compress. Each block = VIỆC · KẾT-LUẬN(+Mig/file:line) · BÀI-HỌC · BẤT-NGỜ, ending `→ substring:"…"` grep-UNIQUE in `2026-06.md` (14 bullets moved byte-exact from MEMORY.md L87-L104, S? Harness-9 curate). Pointer-style = substring (Ctrl-F), Mig-name / phrase keyed. value tag: cao/vừa/thấp. +> The final subsection distills L1-HOT markers (S56/S57bis/S65) that REMAIN in MEMORY.md (not moved) — included here only so the coverage checklist resolves in one place; their verbatim lives in MEMORY.md, pointer notes say so. + +--- + +## P11-A WorkflowApps wave (S41-S42) — schema + app + seed + +**[cao] Mig 41 schema — 4 WorkflowApps ApproveV2 + LevelOpinions (S41 P11-A W1).** VIỆC: cookie-cutter mirror Proposal Mig 38 — 5 entity (4 {Leave,Ot,Travel,Vehicle}RequestLevelOpinion + shared WorkflowAppCodeSequence Prefix-PK) + 5 EF config (auto-discover ApplyConfigurationsFromAssembly) + 4 parent nav + enum TravelRequest=9 + 2 DbSet. KẾT-LUẬN: Mig 41 WireWorkflowAppsApprovalV2; diff CLEAN 5 CreateTable+4 AddColumn; FK Cascade parent + Restrict Level + UNIQUE composite; both DB. BÀI-HỌC: Pattern 12-bis 13× cumulative. BẤT-NGỜ: none (spec deterministic 100%). → substring:"S41 P11-A Wave 1 SCHEMA — wire ApproveV2+LevelOpinions" + +**[cao] App-layer ApproveV2 CQRS (S42 Wave 2a LeaveOt stub + 2b Travel/Vehicle).** VIỆC: per-module DetailDto+LevelOpinionDto+GetById(JOIN Step/Level)+UpdateDraft+Submit+Approve(UPSERT+advance)+Reject+Return; shared internal CodeGen Serializable-tx. KẾT-LUẬN: TravelVehicleApprovalFeatures ~830 LOC; codes `{prefix}/{seq:D3}` (D3 NO year segment per spec) vs Leave/Ot `{prefix}/{year}/{seq:D3}`. BÀI-HỌC: **WorkflowAppCodeGen Serializable-tx Prefix-keyed**; **WorkflowAppStatus enum DIFFERS ProposalStatus int values (DaGuiDuyet=2 not 1, TraLai=3) → mirror by SEMANTIC enum member NOT literal**; Owner=RequesterUserId (not DrafterUserId); Submit verify wf.ApplicableType else Conflict. BẤT-NGỜ: 2a content absorbed (stub→git) into Pattern 4 + this 2b entry. → substring:"S42 P11-A Wave 2b APP — wire ApproveV2 CQRS Travel+Vehicle" + +**[cao] Seed 4 sample workflow V2 (S42 P11-A) — gotcha #51.** VIỆC: DbInitializer mirror SeedSampleProposalWorkflowV2Async EXACT ×4 (Leave/Ot/Travel/Vehicle), each idempotent AnyAsync(ApplicableType) guard → 1 Workflow+1 Step+1 Level, codes QT-NP/OT/CT/XE-V2-001. KẾT-LUẬN: wired 4 calls; build 0 err 0 warn. BÀI-HỌC (marker #4): **gotcha #51 — infra seed NOT gated by DemoSeed:Disabled** (by-design reaches prod, like SeedDemoMasterData/Catalogs). BẤT-NGỜ: Bash tool runs bash NOT PowerShell despite env hint → use `cd && cmd | grep`. → substring:"S42 P11-A SEED — 4 sample ApprovalWorkflow V2" + +## P11-B/C HRM business + catalogs (S43, S51) + +**[cao] LeaveBalance deduct exactly-once (S43 P11-B W1) — Mig 42.** VIỆC: entity LeaveBalance:AuditableEntity (UserId/LeaveTypeId/Year + Entitled/Used/Adjustment decimal(5,2)); FK LeaveType Restrict + UNIQUE composite (UserId,LeaveTypeId,Year); deduction hook in ApproveLeaveRequestHandler terminal DaDuyet branch ONLY. KẾT-LUẬN: Mig 42 AddLeaveBalances; RemainingDays = Entitled+Adjustment−Used COMPUTED (not stored); GetMy/GetUser lazy-merge in-memory (no EF LEFT JOIN). BÀI-HỌC (marker #12): **deduct exactly-once GUARANTEED by early guard `Status != DaGuiDuyet throw`**; HRM admin convention `[Authorize(Roles="Admin")]` not menu policy. BẤT-NGỜ: OtRequest/Travel/Vehicle untouched — only Leave has balance. → substring:"S43 P11-B Wave 1 — LeaveBalance business logic" + +**[cao] Vehicle+Driver catalogs (S51 P11-C W1) — Mig 44, gotcha #57.** VIỆC: 2 entity + 2 EF config (mirror filtered HolidayConfiguration NOT buggy bare LeaveType) + 2 DbSet + HrmConfigFeatures Region5/6 + Controller 8 endpoint (GET public, write Admin) + MenuKeys + DbInitializer seed. KẾT-LUẬN: Mig 44 AddVehicleAndDriverCatalogs; diff CLEAN 2 CreateTable+2 filtered IX. BÀI-HỌC (markers #57,#13,#14): **Code UNIQUE `.HasFilter("[IsDeleted]=0")`**; admin perm AUTO via MenuKeys.All (SeedAdminPermissions + Program.cs:78 both iterate); HRM no HasQueryFilter→`.Where(!IsDeleted)` manual. BẤT-NGỜ: RAG/Qdrant DOWN → all Read/Grep on-disk. → substring:"S51 P11-C HMW W1 — Vehicle+Driver catalogs HrmConfigs" + +## P11-D/E/F WorkflowApps wave2 (S52) + +**[cao] ItTicket round-robin + SLA (S52 P11-D W2) — Mig 46.** VIỆC: entity +SlaDueAt/SlaWarnedSent/SlaBreached; CreateItTicketHandler SlaWindow static map (Urgent4/High8/Medium24/Low72h) shared w/ ItTicketSlaJob; round-robin least-loaded assign (itDept=Departments.Code=="IT"); new ItTicketSlaJob:BackgroundService (breach+warn only, NO auto-transition). KẾT-LUẬN: Mig 46 AddSlaFieldsToItTicket 3 AddColumn no-table; AssignItTicketCommand PUT /{id}/assign [Authorize(Roles=Admin)]. BÀI-HỌC: **SeedItDepartmentStaffAsync MUST run AFTER SeedDemoUsersAsync** (that method reconciles 2 users to PRO/CCM each boot → override to IT after, end-state deterministic). BẤT-NGỜ: agent killed session-limit before MEMORY → proxy by em main. → substring:"S52 P11-D Wave2 BE — ItTicket round-robin + SLA" + +**[cao] Attendance report + IT codegen (S52 P11-E+F) — NO mig.** VIỆC: P11-F MaTicket gen at Create (kanban no-workflow) `IT/2026/001`; P11-E AttendanceReportFeatures + IAttendanceReportExcelExporter (ClosedXML mirror ContractExcelExporter). KẾT-LUẬN: 2 endpoint [Authorize(Roles=Admin)] GET `/api/attendances/report` + `/report/excel`. BÀI-HỌC: **DayOfWeek+holidaySet NOT EF-translate → `.ToListAsync()` then group/classify IN-MEMORY C#**; **Holiday.Date = DateOnly NOT DateTime (spec wrote HashSet wrong) → HashSet**; day-type prio holiday→weekend→weekday; OtWeighted=Σ(level×coef). BẤT-NGỜ: spec type-error caught (DateOnly). → substring:"S52 P11-E+F Wave1 BE migration-FREE" + +## Master + cross-stack (S53, S54, S55) + +**[cao] Filter 3 Master unique indexes (S53 gotcha #57 EXT) — Mig 47 + Mig 46 catch-up.** VIỆC: test-before RED→GREEN; edit 3 config Code unique `+.HasFilter("[IsDeleted] = 0")` (Department/Project/Supplier); Supplier ONLY Code filtered, Type index left untouched. KẾT-LUẬN: Mig 47 FilterMasterCatalogUniqueIndexesByIsDeleted; Mig 46 local catch-up closed LocalDB gap; 203 GREEN. BÀI-HỌC (markers #2,#57): **gotcha #57 — soft-delete UNIQUE must filter [IsDeleted]=0**; **copied filter string BYTE-FOR-BYTE from HolidayConfiguration:18 + LeaveTypeConfiguration:19** (spaces around `=`, not guessed) → snapshot+SQL consistent w/ 13 existing filtered idx. BẤT-NGỜ: Master HAS global HasQueryFilter (unlike HRM) → app-check passes but bare DB index counts soft-deleted → 500; filter aligns. → substring:"S53 gotcha #57 EXT BE — filter 3 Master Code unique indexes" + +**[cao] ItTicket reassign capability + fail-closed authz (S54 cross-stack) — NO mig.** VIỆC: new GetAssignableItStaffQuery capability endpoint + AssignItTicketHandler authz Admin-OR-dept-IT→ForbiddenException, assignee-must-be-IT→ConflictException; controller /assign lowered Roles=Admin→[Authorize]. KẾT-LUẬN: 2 patterns saved (controller-lower-authorize-handler-finegrained + scoped-capability-endpoint-anti-silent-403); reviewer chain-verified role-string "Admin" real (AppRoles→SeedRoles→JWT→cu.Roles). BÀI-HỌC (marker #11): **fail-closed authz — Forbidden BEFORE NotFound** (avoid existence-oracle); ICurrentUser lacks DepartmentId → query db.Users. BẤT-NGỜ: em main reconciled 2 patterns from stray src/Backend/.claude (cwd-relative Write mishap). → substring:"S54 ItTicket reassign cross-stack — IT-staff self-service" + +**[vừa] Menu leaf AttendanceReport (S54 Task D) — NO mig.** VIỆC: 3 insert — MenuKeys const Off_AttendanceReport + All[] + DbInitializer menu tuple (Order 8, parent Off). KẾT-LUẬN: admin perm auto via MenuKeys.All 2-point (SeedAdminPermissions :1916 + Program.cs:78). BÀI-HỌC: idempotent seed upsert (existing prod gets leaf on restart, existing rows only Order-reconciled). BẤT-NGỜ: none. → substring:"S54 Task D BE — promote AttendanceReport to sidebar menu leaf" + +**[cao] Real master-data import (S55) — Mig 48 + ungated seed.** VIỆC: Project +4 prop (Year int?, Investor/Location/Package, maxlen 250/500/300); SeedRealMasterDataAsync 3 tuple-loop per-code idempotent, wired UNGATED after SeedCatalogsAsync. KẾT-LUẬN: Mig 48 AddProjectMasterFields 4 AddColumn no-table; seeds 62 Project+71 WorkItem+3 Supplier; runtime Dev proof landed. BÀI-HỌC: real-data import reaches prod by-design (DemoSeed:Disabled NOT gate); FLOCK01 collision demo↔real → per-code skip (demo wins). BẤT-NGỜ: WorkItem divider row "THIẾT BỊ" dropped; agent return truncated #53 → proxy by em main. → substring:"S55 master-data import (Mig 48 `AddProjectMasterFields`" + +## [stub→git d2f52ba] older absorbed (S35/S40 trim) + +**[thấp] S35 BE-CRUD-4-catalog + S29 Contract-V2-mirror.** KEY absorbed into L1 Pattern 12-bis foundation (catalog-mega + HRM `.Where(!IsDeleted)` manual + Validator MaxLength=EF). → substring:"Archived S35 G-H2 BE-CRUD-4-catalog + S29 Plan-B-Chunk-C Contract-V2-mirror" + +**[thấp] S40 curate — FE/test + older BE → git d2f52ba.** S35 FE inline forms 5 satellite + S34 test +10 (130 PASS) + S33 EmployeesListPage + S32 wrap/startup. → substring:"Archived FE/test + older BE entries → `archive/2026-05-q4.md` + git d2f52ba (S40 curate)" + +--- + +## L1-HOT markers (still in MEMORY.md — NOT moved; distilled here for coverage completeness) + +> These live VERBATIM in MEMORY.md (kept hot). Pointers below target MEMORY.md, not 2026-06.md. + +- **[cao] S56 GOLIVE-HARDEN — ExecuteUpdate atomic + fail-closed (markers #10,#11).** LeaveBalance lost-update → `ExecuteUpdateAsync` server-side row-lock inside tx (em-main post-review bumped to `IsolationLevel.Serializable`); AssignItTicket existence-oracle → Forbidden guard BEFORE NotFound (fail-closed). STALE-TRACKED caveat: ExecuteUpdate bypasses tracker → don't re-add `bal.UsedDays +=` (double-count). → substring(MEMORY.md):"S56 GOLIVE-HARDEN 3 BE fix" +- **[cao] S57bis/S65 loose-Guid FK guard (marker #13).** loose-Guid FK (WorkItemId S57bis, Department.ParentId S65) = NO physical FK; guard `AnyAsync(x.Id==id && x.IsActive)`→Conflict (mirror S43); UpdateDraft null-safe `if (request.WorkItemId is not null)` to avoid null-ing bug-class S42. → substring(MEMORY.md):"FK-guard loose-Guid" +- **[vừa] L1 Pattern foundation markers (#3,#5,#9b).** gotcha #17 = EF migration 3-file rule (Pattern 2, BẮT BUỘC commit `.cs`+`.Designer.cs`+snapshot); gotcha #65 = `dotnet build SolutionErp.slnx` includes 2 test projects; Mig 29/30/31 per-NV admin opt-in (Pattern 7, proven 4×). → substring(MEMORY.md):"EF migration 3-file rule (gotcha #17" diff --git a/.claude/agent-memory/implementer-backend/archive/2026-06.md b/.claude/agent-memory/implementer-backend/archive/2026-06.md new file mode 100644 index 0000000..5b7969e --- /dev/null +++ b/.claude/agent-memory/implementer-backend/archive/2026-06.md @@ -0,0 +1,29 @@ +# Implementer-Backend — Archive 2026-06 (S41-S55 verbose, curated S? Harness-9) + +> **Archived:** 2026-06-17 Harness-9 Stage B curate (em main proxy). 14 verbose Recent-activity bullets moved BYTE-EXACT from MEMORY.md (L87-L104) to keep L1 HOT slim < 25KB. +> **Scope:** S55 master-import → S41 P11-A Wave 1 schema (+ 3 already-condensed Archived-stubs pointing git d2f52ba / earlier q4). FIFO chronological NEWEST-first as they appeared in L1. +> **FROZEN:** entries below are verbatim copies — do NOT reflow/edit. Re-grounding of stale counts is a separate pass (additive-only). +> **KEEP in MEMORY.md:** header+role+split boundary + BE Patterns 1-4/7-9/12-bis/12-ter + anti-patterns+BE conventions + Curate trigger + 6 newest Recent-activity (06-17 Off_Dashboard → S56 GOLIVE-HARDEN). + +--- + +## Moved Recent-activity entries (verbatim, FIFO newest-first as in L1) + +- **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]`. + +- **S53 gotcha #57 EXT BE — filter 3 Master Code unique indexes + Mig 46 local catch-up (Mig 47 `FilterMasterCatalogUniqueIndexesByIsDeleted`, index-only no-table):** Test-before RED→GREEN driven (test-specialist `MasterCatalogFilteredUniqueTests`, 3 FAIL on unfiltered → must turn GREEN). gotcha #57 4th+5th+6th cumulative (S45 Holiday Mig 43, S51 HRM×3 Mig 45 → now Department/Project/Supplier). Edit 3 config Code unique: `b.HasIndex(x=>x.Code).IsUnique()` → `+.HasFilter("[IsDeleted] = 0")`. **KEY: copied EXACT filter string byte-for-byte from HolidayConfiguration:18 + LeaveTypeConfiguration:19** (spaces around `=`, NOT guessed) → snapshot+SQL Server consistent with 13 existing filtered indexes. SupplierConfiguration: ONLY Code index filtered, `HasIndex(x=>x.Type)` :25 LEFT untouched (verified snapshot 3590 Type no-filter). Mig diff CLEAN: Up=3×DropIndex+3×CreateIndex filtered, Down=3×reverse unfiltered (no drift, no extra table/col). Master entities HAVE global `HasQueryFilter(!IsDeleted)` (unlike HRM) — app-check `db.X.AnyAsync(Code==req.Code)` filters soft-deleted → passed; then bare DB index counted it → UNIQUE violation 500. Filter aligns DB index with app-check. **Mig 46 local catch-up:** S52 left local LocalDB stuck at Mig 45 (prod had 46 via CI, local gap). `database update` to BOTH DBs applied Mig 46 (`AddSlaFieldsToItTicket`) THEN Mig 47 — residual closed. Dev override `--connection SolutionErp_Dev`; Design factory-default `SolutionErp_Design` (both `(localdb)\MSSQLLocalDB`). Build 0 err (2 pre-existing DocxRenderer warn). Full suite 203 GREEN (58 Domain + 145 Infra, +3 new). KHÔNG touch FE/test/commit (em main commits). Tag `[s53, gotcha-57-ext, mig47, mig46-catchup, filter-byte-for-byte]`. + +- **S52 P11-D Wave2 BE — ItTicket round-robin + SLA (Mig 46 `AddSlaFieldsToItTicket`, 3 AddColumn no-table) [proxy by em main: agent killed session-limit trước MEMORY step]:** Entity +SlaDueAt/SlaWarnedSent/SlaBreached. `CreateItTicketHandler`: `SlaWindow` static map (Urgent4/High8/Medium24/Low72h) **shared với `ItTicketSlaJob`** (single-source) → `e.SlaDueAt=CreatedAt+window`. Round-robin least-loaded: `db.Users.Where(DepartmentId==itDeptId && IsActive).OrderBy(db.ItTickets.Count(assigned && !Closed && !Resolved)).ThenBy(Id).First()` (itDept = `Departments.Code=="IT"`); no IT-staff → unassigned. NEW `ItTicketSlaJob:BackgroundService` mirror SlaExpiryJob (30s warmup/15min loop/scope) NHƯNG **KHÔNG auto-transition** — chỉ breach (SlaDueAt` sau SlaExpiryJob. `AssignItTicketCommand` + PUT `/{id}/assign` `[Authorize(Roles=Admin)]`. DTO +SlaDueAt/SlaBreached + projection. **DbInitializer `SeedItDepartmentStaffAsync` KEY ordering: PHẢI chạy SAU `SeedDemoUsersAsync`** (method đó reconcile 2 user dept về PRO/CCM mỗi boot → override về IT sau, end-state deterministic) + dept "IT" thứ 10 + gán nv.cao/nv.truong (sample user, idempotent). Build 0-err. Tag `[s52, p11-d, mig46, round-robin, sla-job, seed-ordering]`. + +- **S52 P11-E+F Wave1 BE migration-FREE (4 file new + 3 edit, NO mig):** Case 1/2 deterministic ~98% em main. **P11-F** (2 LOC): `CreateItTicketHandler` set `e.MaTicket = WorkflowAppCodeGen.GenerateMaDonTuAsync(db,"IT",clock.Now.Year,clock,ct)` TRƯỚC `db.ItTickets.Add` — gen lúc Create (kanban no-workflow, khác Leave/OT gen lúc Submit). Helper = `internal static` cùng ns Office, gọi trực tiếp no-using. Format `IT/2026/001`. **P11-E** report chấm công: NEW `AttendanceReportFeatures.cs` (DTO 3 record EXACT + GetAttendanceReportHandler) + NEW `IAttendanceReportExcelExporter`(App/Reports/Services) + NEW Infra `AttendanceReportExcelExporter`(ClosedXML mirror ContractExcelExporter, sync `Export(dto)` no-DB) + DI scoped + Controller +2 endpoint `[Authorize(Roles=Admin)]`. **KEY gotcha day-type:** DayOfWeek+holidaySet KHÔNG EF-translate → `.ToListAsync()` rồi group/classify IN-MEMORY C#. **KEY gotcha type:** `Holiday.Date`=**DateOnly** KHÔNG DateTime (spec viết HashSet nhầm) → `HashSet` + so `DateOnly.FromDateTime(a.AttendanceDate.Date)`. OtPolicy active fallback 1.5/2.0/3.0. Day-type prio: holiday→weekend(Sat/Sun)→weekday. OtWeighted=Σ(cấp×hệ số). FullName denorm ưu tiên `Attendance.UserFullName` rồi User.FullName. DeptName LEFT JOIN. RenderResult=(Content,FileName,ContentType) ns Forms.Services. Controller inject exporter ctor. Build 0 err (2 pre-existing DocxRenderer warn). KHÔNG touch FE/test/mig/ItTicket-khác. Routes: GET `/api/attendances/report?year&month&departmentId` + `/report/excel`. Tag `[s??, p11-e-f, wave1, no-mig, day-type-in-memory, dateonly-holiday]`. + +- **S51 P11-C HMW W1 — Vehicle+Driver catalogs HrmConfigs (Mig 44 `AddVehicleAndDriverCatalogs`, 9 add-point ~12 file/edit):** Pattern 12-bis catalog-mega 4th cumulative. 2 entity (`Vehicle`/`Driver`:AuditableEntity Domain/Hrm) + 2 EF config (mirror `HolidayConfiguration` filtered, NOT buggy bare LeaveType) + 2 DbSet (IAppDbContext+ApplicationDbContext) + Mig 3-file + HrmConfigFeatures Region5/6 (DTO+List/Create/Update/Delete CQRS mirror Region1 EXACT) + Controller +2 route group (8 endpoint, GET public + POST/PUT/DELETE `[Authorize(Roles=Admin)]`) + MenuKeys +2 const +All array + DbInitializer (menu 2 leaf + SeedHrmConfigsAsync guard&seed 2 veh+2 drv). **gotcha #57 KEY:** Code UNIQUE `.HasFilter("[IsDeleted]=0")` — Mig diff verified CLEAN 2 CreateTable + 2 filtered IX no drift. Validator MaxLength = em main schema (Code50/Name200/Plate20/Phone20/LicNum50/LicClass20/Desc500), SeatCount GreaterThanOrEqualTo(0). Admin perm AUTO-grant: `SeedAdminPermissionsAsync` + `Program.cs:78` both iterate `MenuKeys.All` → +All array = 8 policy + Admin row auto (no manual grant code). HRM no HasQueryFilter → `.Where(!IsDeleted)` manual. Applied BOTH DB. Build 0 err (2 pre-existing DocxRenderer warn). RAG/Qdrant DOWN → all Read/Grep on-disk. Spec deterministic ~98% em main → ACCEPT Case 1. KHÔNG touch FE/test/commit. Tag `[s51, p11-c, mig44, vehicle-driver, catalog-mega]`. +- **S43 P11-B Wave 1 — LeaveBalance business logic (Mig 42 `AddLeaveBalances`, 7 file: 1 entity + 1 config + 2 DbSet edit + Mig 3-file + 1 hook edit + 1 Features + 1 Controller):** Case 1/3 deterministic ~98% em main spec. Pattern 12-ter-adjacent single-entity: entity `LeaveBalance:AuditableEntity` (UserId/LeaveTypeId/Year + EntitledDays/UsedDays/AdjustmentDays decimal(5,2), nav LeaveType). Config FK LeaveType WithMany() **Restrict** (catalog no cascade) + UNIQUE composite (UserId,LeaveTypeId,Year) + IX UserId. Mig diff CLEAN: 1 CreateTable + 3 IX, no drift. Applied BOTH DB (Dev `SolutionErp_Dev` + Design default). **Deduction hook:** insert in `ApproveLeaveRequestHandler` terminal else (DaDuyet branch) ONLY — UPSERT LeaveBalance, `bal.UsedDays += p.NumDays`, exactly-once guaranteed by early guard `Status != DaGuiDuyet throw`. OtRequest/Travel/Vehicle UNTOUCHED (only Leave has balance). CQRS `Application.Hrm`: DTO RemainingDays=Entitled+Adjustment−Used COMPUTED (not stored) + GetMy/GetUser lazy-merge (load active LeaveTypes + balances → in-memory merge, synth default when no row — KHÔNG EF LEFT JOIN translate) + AdjustLeaveBalanceCommand admin upsert (HasValue-gated). **Policy resolved:** HRM admin convention = `[Authorize(Roles="Admin")]` NOT menu policy (verified HrmConfigsController write endpoints) — used on GET-by-user + PUT /adjust; /my = `[Authorize]`. Controller injects IDateTime for year default `clock.Now.Year` (thin, no DateTime.Now hardcode). HRM no HasQueryFilter → `.Where(!IsDeleted)` manual everywhere. KHÔNG touch FE/test/commit. Build 0 err (2 pre-existing DocxRenderer warn). Tag `[s43, p11-b-w1, mig42, leave-balance, single-entity]`. +- **S42 P11-A SEED — 4 sample ApprovalWorkflow V2 for WorkflowApps (DbInitializer.cs only, +4 method ~210 LOC + 4 call):** Case 1 mechanical mirror `SeedSampleProposalWorkflowV2Async` EXACT × 4 (Leave5/Ot6/Travel9/Vehicle7). Each: idempotent `AnyAsync(ApplicableType==X)` guard → resolve approver `binh.le@solutions.com.vn` (SAME user as Proposal/Contract seed, null→LogWarning+return) → 1 ApprovalWorkflow (Version=1, IsActive+IsUserSelectable=true, ActivatedAt=UtcNow) + 1 Step (Order=1, Name="Cấp duyệt", DepartmentId=CCM?.Id) + 1 Level (Order=1, ApproverUserId). Codes QT-NP/OT/CT/XE-V2-001. Wired 4 calls after SeedSampleProposalWorkflowV2Async (NOT gated DemoSeed, gotcha #51 infra seed). Enum verified Grep. Build 0 err 0 warn. Bash tool = bash NOT PowerShell despite env hint (use `cd && cmd | grep`). Spec deterministic 100% → ACCEPT Case 1. NOT touched App/Controller/FE/test/Mig. Tag `[s42, p11-a, seed, mirror-proposal-exact]`. +- **S42 P11-A Wave 2b APP — wire ApproveV2 CQRS Travel+Vehicle (`TravelVehicleApprovalFeatures.cs` ~830 LOC + 2 controller edit):** Cookie-cutter mirror Wave 2a / ProposalFeatures Region 2. 1 new file ns `Application.Office`: 2 module × (DetailDto + LevelOpinionDto + GetById JOIN Step/Level metadata + UpdateDraft + Submit + Approve UPSERT+advance + Reject TuChoi + Return TraLai+RejectedFromStatus) + 1 shared `internal static TravelVehicleCodeGen.GenerateMaDonTuAsync` (Serializable tx, `WorkflowAppCodeSequences` Prefix-keyed, prefix `DT/CT/{year}` Travel & `DX/XE/{year}` Vehicle, format `{prefix}/{seq:D3}` — D3 no year segment per spec). KEY gotcha: WorkflowAppStatus enum DIFFERS from ProposalStatus int values (DaGuiDuyet=2 not 1, TraLai=3) → mirror by SEMANTIC enum member not literal. Owner = `RequesterUserId` (not DrafterUserId). Submit verify wf.ApplicableType==Travel9/Vehicle7 else Conflict. 2 controller +6 route each (GET{id}/PUT/submit/approve/reject/return) nested body records, CreatedAtAction. KHÔNG sửa WorkflowAppsFeatures.cs/Leave/Ot/FE/test/seed. Build 0 err. Spec deterministic ~98% em main → ACCEPT Case 1/2. Tag `[s42, p11-a, wave-2b, mirror-proposal-region2]`. +- **Archived S42 P11-A Wave 2a APP (LeaveOt ApproveV2 CQRS) → git history (S65b FIFO trim):** KEY absorbed Pattern 4 + WorkflowAppCodeGen Serializable-tx Prefix-keyed `{prefix}/{year}/{seq:D3}` + WorkflowAppStatus enum DIFFERS ProposalStatus (mirror SEMANTIC member not literal) + Owner=RequesterUserId. Detail in Wave 2b entry above + S56 em-main Serializable note. +- **S41 P11-A Wave 1 SCHEMA — wire ApproveV2+LevelOpinions 4 WorkflowApps (Mig 41 `WireWorkflowAppsApprovalV2`):** Pattern 12-bis cookie-cutter mirror Proposal Mig 38, 13× cumulative. 11 file: 5 entity (4 `{Leave,Ot,Travel,Vehicle}RequestLevelOpinion` + shared `WorkflowAppCodeSequence` Prefix-PK) + 5 EF config (auto-discover ApplyConfigurationsFromAssembly, no manual register) + edit 4 parent (nav LevelOpinions + `WorkflowAppStatus? RejectedFromStatus`) + edit enum (`TravelRequest=9`) + 2 DbSet (IAppDbContext+ApplicationDbContext, 5 each). Mig diff CLEAN: 5 CreateTable + 4 AddColumn (no drift). FK Cascade parent + Restrict Level + UNIQUE composite ({Parent}Id, ApprovalWorkflowLevelId). Applied BOTH DB (Dev + Design). Build 0 err. Wave 2 (App/Controller) + Wave 4 (test) deferred per spec. Spec deterministic 100% (em main chose schema) → ACCEPT Case 1. Tag `[s41, p11-a, 12-bis-13x, mig41]`. +- **Archived S35 G-H2 BE-CRUD-4-catalog + S29 Plan-B-Chunk-C Contract-V2-mirror → `archive/2026-05-q4.md` (S65 FIFO trim):** KEY absorbed Pattern 12-bis foundation above (catalog-mega + HRM `.Where(!IsDeleted)` manual + Validator MaxLength=EF-source). +- **Archived FE/test + older BE entries → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** S35 FE inline forms 5 satellite (→ frontend domain) · S34 test bundle +10 [Fact] 130 PASS (→ test-specialist domain) · S33 Task 5 EmployeesListPage · S32 wrap/startup. KEY absorbed in Patterns above + split pointers. diff --git a/.claude/agent-memory/implementer-backend/archive/_INDEX.md b/.claude/agent-memory/implementer-backend/archive/_INDEX.md new file mode 100644 index 0000000..afe50a3 --- /dev/null +++ b/.claude/agent-memory/implementer-backend/archive/_INDEX.md @@ -0,0 +1,66 @@ +# Archive Index — implementer-backend (L2 catalog, NO content) + +> **Purpose:** rescue L2 archive "dark-matter" (not in RAG). One line per archived record so a reader can locate + Ctrl-F the verbatim text. This index holds NO record bodies — see `.gist.md` for 4-field distillations, and the named archive file for full verbatim. +> **Pointer style = substring (Ctrl-F) PRIMARY.** Each row ends `substring:"…"` — a string that greps UNIQUE (count=1) inside its target file. Open the file, Ctrl-F the string, land on the record. Fallback hint = the record's `## /### ` heading (date + session). NO line-number hints (archives are frozen but line numbers drift if ever re-touched). +> **Why not date pointers:** dates COLLIDE — q1 has 5× `2026-05-14` + 4× `2026-05-15` + 2× `2026-05-13`; q2 2× `05-19` + 2× `05-22`; q3 4× `05-22`; q4 3× `05-26`. So every pointer is keyed on a git-SHA (`cdfd542`/`0bf6c7e`/`06a441c`/`62b50d1`) or a distinctive Mig-name / phrase, never the bare date. +> **Archives are FROZEN / additive-only.** `2026-05-q1..q4.md` are verbatim history (do NOT edit). `2026-06.md` (new, Harness-9 curate) holds 14 bullets moved byte-exact out of MEMORY.md L87-L104. +> **Labels:** `[meta]` = curate/governance/setup note (low code value). `[stub→git]` = FIFO-trim stub whose real content lives in git `d2f52ba` (do not expect rich body). +> **Sorted by DATE** (ISO 05-xx first; then session-keyed S41→S55 records in `2026-06.md`, which are chronologically post-`05-26`). + +--- + +## archive/2026-05-q1.md (S21-S24 · 12 records · verbose) + +- 2026-05-13 · REFUSE-class log (S21 t3-t5) · 3× REFUSE cross-stack; gotcha #45 bug+reasoning; per-NV split + EF backfill-reorder pattern saved · substring:"gotcha #45 = bug + reasoning" +- 2026-05-13 · REFUSE-class log (S22) · 100% REFUSE; state 30 mig/104 test; 4 S22 mismatches (accessToken, size=xl, changelogs field) · substring:"30 migrations** (+1 Mig 30 AllowApproverEditSection1" +- 2026-05-14 · FE Designer label polish (S23 pre-A) · ApprovalWorkflowsV2Page.tsx:873 fullName lookup from usersList · substring:"UI polish slot label Designer Mig 31 prep" +- 2026-05-14 · BE Mig 31 schema swap (S23 K1) · F2 storage Users→ApprovalWorkflowLevels, ADD-DROP no-BACKFILL, both DB · substring:"Mig 31 schema swap F2 storage Users → ApprovalWorkflowLevels" +- 2026-05-14 · FE Designer 7th checkbox (S23 K3) · AllowApproverSkipToFinal + banner rewrite, per-slot mirror 3× · substring:"FE Admin Designer 7th checkbox AllowApproverSkipToFinal" +- 2026-05-14 · BE+FE zombie cleanup (S23 K5) · drop F2 endpoint/column/DTO/stale comments, 9 file +42/-94 · substring:"Cleanup zombie F2 endpoint + UsersPage column" +- 2026-05-14 · Test Approver F2 regression (S23 K7) · del 3 Drafter F2 + add 3 Approver F2; 104 PASS; SeedApproverF2WorkflowAsync · substring:"Mig 31 Approver F2 service regression tests" +- 2026-05-15 · Test ReturnMode edge (S24 M2) · 2 [Fact] reset Bước1Cấp1 keep ChoDuyet; 106 PASS · substring:"Plan M Chunk M2" +- 2026-05-15 · FE rename label (S24 M3) · Phase=TraLai(98) "Trả lại"→"Cần chỉnh sửa lại", 4 file ×2 app · substring:"rename Phase=TraLai (98) display label" +- 2026-05-15 · REFUSE-class log (S23 t4-t11) · 8 plans em main solo; Plan R/S/T destructive sqlcmd ~720 rows; DemoSeed flag · substring:"8 plan consecutive em main solo" +- 2026-05-15 · FE read-only matrix (S24 Plan AA B) · WorkflowMatrixViewPage fe-user; shadcn lacks Card/Badge→inline fallback · substring:"FE user read-only matrix view workflow V2 ghim" +- 2026-05-15 · [meta] wrap (S24 Plan AA) · Patterns 13/14/15 NEW; REFUSE 4/4 correct; shadcn fe-user subset note · substring:"shadcn fe-user KHÔNG có `Card`/`Badge`" + +## archive/2026-05-q2.md (S25-S29 · 5 records · verbose) + +- 2026-05-19 · BE Changelog refactor (S25 AB-A) · commit cdfd542; ApplyReturnModeAsync 215-378 4-mode uniform log; FE substring filter · substring:"Commit `cdfd542` 3 file +146/-95 LOC" +- 2026-05-19 · [meta] wrap (S25) · Patterns 16/17/18 NEW (batch fix / FE synthetic rows / userMap fallback); 6 follow-ups em main solo · substring:"Plan AB Chunk A spawn 1× ~12K Case 1" +- 2026-05-21 · FE PE list tree (S26 Plan AG) · commit 0bf6c7e; Pattern 19 details/summary + localStorage Set; ×2 app SHA256 match · substring:"`0bf6c7e` 2 file +346/-116 LOC" +- 2026-05-22 · FE 4 master pages mirror (S27 CA-B) · commit 06a441c; Pattern 16 byte-identical admin→user SHA256; PS $_ gotcha · substring:"Commit `06a441c` 6 file changed" +- 2026-05-22 · FE Contract V2 dropdown (S29 Plan B-D) · commit 62b50d1; Pattern 12-bis + 16-bis ×2 app; ApplicableType=3 · substring:"Commit `62b50d1` clean 2 file" + +## archive/2026-05-q3.md (S27-S29 wraps + setup · 5 records · mostly meta) + +- 2026-05-22 · [meta] S29 wrap · 5 spawn busiest; Pattern 12-bis NEW; stash em-main WIP race trick; forward SEQUENTIAL not parallel BE · substring:"busiest agent S29 với 5 spawn total" +- 2026-05-22 · [meta] S28 governance · Layer A distributed; Pattern 7 proven 4× Layer-B candidate; no self-authorize cross-project · substring:"S28 wrap Layer A governance distributed active" +- 2026-05-22 · [meta] S27 retrospective · 2/8 tasks delegable but registry empty→em-main solo; Pattern 20 (5 PS scripts mirror) · substring:"wrap-up retrospective REFUSE analysis" +- 2026-05-22 · [meta] curate (S29-era) · archived 12 q1 entries; 38.8KB→~22-24KB · substring:"Archived 12 verbose Recent activity entries S21 t3" +- 2026-05-11 · [meta] setup baseline · agent initialized; 5 patterns S1-S20; awaiting first SendMessage · substring:"Implementer agent initialized" + +## archive/2026-05-q4.md (S32-S33 + S35/S29 trim-stubs · 5 records) + +- 2026-05-26 · FE HRM scaffold (S33 G-H1 T5) · 3 NEW page ×2 app SHA256 IDENTICAL; Pattern 16-bis 4th + 12-bis 4th · substring:"S33 Plan B G-H1 Task 5" +- 2026-05 · [stub→git] BE CRUD 4 catalog (S35 G-H2) · HrmConfigFeatures 372 LOC 16 endpoint; HRM no HasQueryFilter→.Where(!IsDeleted); Validator MaxLength=EF (Code=50 not 20) · substring:"S35 G-H2 BE CRUD 4 catalog — archived S65 FIFO trim" +- 2026-05 · [stub→git] Contract V2 mirror (S29 Plan B-C) · Mig 33 ContractLevelOpinions; Pattern 12-bis 1st; 8 file +4265 LOC autogen · substring:"S29 Plan B Chunk C Contract V2 mirror — archived S65 FIFO trim" +- 2026-05-26 · [meta] S32 wrap · curate q2 36.2→27.5KB; Plan G 11-module backlog; S33 pending tasks list · substring:"S32 wrap — em main proxy curate + Plan G 11 module" +- 2026-05-26 · [meta] S32 startup · size FLAG 36.2KB>25KB; 17 patterns confirmed; RAG live rerank 0.824/0.801/0.793 · substring:"S32 startup — context verify + RAG live confirm" + +## archive/2026-06.md (S41-S55 · 14 records moved byte-exact from MEMORY.md L87-L104, S? Harness-9 curate · chronologically post-2026-05-26) + +- S41 · BE schema 4 WorkflowApps (P11-A W1) · Mig 41 WireWorkflowAppsApprovalV2; Pattern 12-bis 13×; 5 CreateTable+4 AddColumn both DB · substring:"S41 P11-A Wave 1 SCHEMA — wire ApproveV2+LevelOpinions" +- S42 · [stub→git] BE App LeaveOt ApproveV2 (Wave 2a) · absorbed Pattern 4 + WorkflowAppCodeGen Serializable-tx {prefix}/{year}/{seq:D3} + status enum DIFFERS ProposalStatus + Owner=RequesterUserId · substring:"Archived S42 P11-A Wave 2a APP (LeaveOt ApproveV2 CQRS) → git history" +- S42 · BE App Travel+Vehicle ApproveV2 (Wave 2b) · TravelVehicleApprovalFeatures ~830 LOC; mirror SEMANTIC enum member not literal; D3 no-year · substring:"S42 P11-A Wave 2b APP — wire ApproveV2 CQRS Travel+Vehicle" +- S42 · BE seed 4 workflow V2 (P11-A) · DbInitializer mirror SeedSampleProposalWorkflowV2 ×4; UNGATED gotcha #51; Bash tool=bash not PowerShell · substring:"S42 P11-A SEED — 4 sample ApprovalWorkflow V2" +- S43 · BE LeaveBalance logic (P11-B W1) · Mig 42 AddLeaveBalances; deduct exactly-once via early Status!=DaGuiDuyet throw; RemainingDays computed · substring:"S43 P11-B Wave 1 — LeaveBalance business logic" +- S51 · BE Vehicle+Driver catalogs (P11-C W1) · Mig 44; Pattern 12-bis catalog-mega 4×; gotcha #57 Code UNIQUE HasFilter [IsDeleted]=0; admin perm via MenuKeys.All · substring:"S51 P11-C HMW W1 — Vehicle+Driver catalogs HrmConfigs" +- S52 · BE attendance report + IT codegen (P11-E+F) · NO mig; day-type classify IN-MEMORY (DayOfWeek+holidaySet no EF-translate); Holiday.Date=DateOnly not DateTime · substring:"S52 P11-E+F Wave1 BE migration-FREE" +- S52 · BE ItTicket round-robin+SLA (P11-D W2) · Mig 46 AddSlaFieldsToItTicket; SlaWindow shared w/ job; SeedItDeptStaff AFTER SeedDemoUsers ordering · substring:"S52 P11-D Wave2 BE — ItTicket round-robin + SLA" +- S53 · BE filter 3 Master unique idx (gotcha #57 EXT) · Mig 47; copy filter string byte-for-byte from HolidayConfiguration; Mig 46 local catch-up; 203 GREEN · substring:"S53 gotcha #57 EXT BE — filter 3 Master Code unique indexes" +- S54 · BE menu leaf AttendanceReport (Task D) · NO mig; admin perm auto via MenuKeys.All 2-point; idempotent seed upsert · substring:"S54 Task D BE — promote AttendanceReport to sidebar menu leaf" +- S54 · BE ItTicket reassign capability (cross-stack) · NO mig; fail-closed authz Forbidden BEFORE NotFound (existence-oracle); 2 patterns saved · substring:"S54 ItTicket reassign cross-stack — IT-staff self-service" +- S55 · BE master-data import · Mig 48 AddProjectMasterFields 4 AddColumn; SeedRealMasterDataAsync UNGATED 62 Project+71 WorkItem+3 Supplier; runtime Dev proof · substring:"S55 master-data import (Mig 48 `AddProjectMasterFields`" +- S35 · [stub→git] BE CRUD 4 catalog + Contract V2 (S40 trim) · KEY absorbed into Pattern 12-bis foundation (catalog-mega + HRM .Where(!IsDeleted) + Validator MaxLength=EF) · substring:"Archived S35 G-H2 BE-CRUD-4-catalog + S29 Plan-B-Chunk-C Contract-V2-mirror" +- S35 · [stub→git] FE/test + older BE (S40 curate) · git d2f52ba: S35 FE inline forms 5 satellite + S34 test +10 + S33 EmployeesListPage + S32 wrap/startup · substring:"Archived FE/test + older BE entries → `archive/2026-05-q4.md` + git d2f52ba (S40 curate)" diff --git a/.claude/agent-memory/investigator-codebase/MEMORY.md b/.claude/agent-memory/investigator-codebase/MEMORY.md index 79eb2c7..ac0b7cb 100644 --- a/.claude/agent-memory/investigator-codebase/MEMORY.md +++ b/.claude/agent-memory/investigator-codebase/MEMORY.md @@ -2,7 +2,7 @@ > **Persistent diary cross-session.** Auto-injected first ~200 lines at spawn (L1 HOT). > Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 `archive/` on-demand · L3 RAG `search_memory` just-in-time. Keep entry ≤ 1.5K chars (gotcha #53). -> Full verbatim history pre-S40 → git `d2f52ba` + `archive/2026-05-q1..q4.md`. +> Full verbatim history pre-S40 → git `d2f52ba` + `archive/2026-05-q1..q4.md` + `archive/2026-06.md`. 🗺️ **Lookup map (Harness-9 S70): `archive/_INDEX.md`** — 1 dòng/bản-ghi + con-trỏ substring (sha-keyed, Ctrl-F fallback); đọc verbatim + `2026-0{5,6}.gist.md` (nén 4-field) theo nhu cầu. > **Renamed S39:** investigator → investigator-codebase (internal half; external → investigator-api). --- @@ -76,24 +76,6 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte - **[→ git pre-S60]** S60 recon#2 V2-engine-map (ApprovalWorkflow.cs Step/Level Order 1-based per-step; OR-of-N=N rows cùng Order service GroupBy:475; ApproveV2Async:446-634 guard+UPSERT+advance; notify DRAFTER-only:748; skipToFinal F2:561-602 = precedent advance-không-ghi-opinion) · S60 PE Section-3 submit-guard (submit path POST/pe/{id}/transitions→TransitionAsync:38 ROLE-only guard NO data-check; Section-3 mục a/b/c/d map — SUPERSEDED bởi S65ter post-Mig50 Budget-drop; test mirror PurchaseEvaluationWorkflowServiceGuardTests). Full text git. -- **2026-06-11 (S59 recon — prod test-data wipe + PE tree Hạng mục, prod+on-disk):** ⭐ **Prod:** PE=10 active (1 Nháp + 1 DaDuyet(7) + 8 ChoDuyet(10), MaPhieu A/031-040, ALL WorkItemId NULL) + child 20/10/20/28/138/18/18 (Sup/Det/Quote/Appr/Chg/Att/LvlOp); Contracts=7 ALL `[DEMO]` 05-08 pin V1 (AwId NULL) + Appr15 + details15; Budgets/WorkflowApps/Proposals/Attendances/Meetings ALL 0; Notifications 64. Seq: PE/2026/A=40 B=1; CT=7 demo prefix LastSeq=1. **FK:** PE child CASCADE trừ `Quotes→PE NO_ACTION` (multi-path; Plan R S23 proved single `DELETE FROM PurchaseEvaluations` OK — NO_ACTION check end-of-statement sau cascade Details→Quotes). Contract child ALL CASCADE. PE.ApprovalWorkflowId Restrict → wipe PE trước khi xóa AW QT-DN-V2-001 v1 (inactive, còn 1 PE pin). AW V2=8: 7 ghim KEEP. **Uploads orphan:** purchase-evaluations/ 19 folder vs 10 PE → ~10 orphan từ S23 (file không xóa); contracts/ 1. **Demo gate OK:** SeedDemoContracts/PE TRONG `DemoSeed:Disabled` (DbInitializer:80,131-132) → wipe không resurrect. **Surprise:** Users 55 total / 21 active — 20 user THẬT batch 2026-06-11 06:01 (S58 seed fix ăn; thanh.lethanh NOW EXISTS — stale S57bis mem; chuong.phan typo-domain VẪN active song song twin). **FE tree:** `pe/PurchaseEvaluationsListPage.tsx:138-179` Project>Year(createdAt :150)>Supplier; SHA256 identical 2 app; PeListItem ĐÃ có workItemId/Name (types :116-118, BE Features :514/570/644) → đổi tree FE-only. Tag `[s59-recon, prod-wipe, pe-tree-workitem]`. - -- **2026-06-11 (S57bis lock no-op — prod user census, on-disk+prod):** ⭐ `LockDemoSampleUsersAsync` (DbInitializer.cs:1552, chạy CUỐI :98) hardcode 14 named-person email (bod.huynh/pm.nguyen/fin.do/qs.hoang…) = population CHỈ CÓ TRÊN DEV. **Prod 34 user ALL-active:** 20 UAT-matrix placeholder hand-created batch 2026-05-13 15:04-05, scheme `{act,equ,fin,hra,pm,qs}.{nv,pp,tp}@` + `bod.{1,2}@` (FullName tự khai "ACT NV - Drafter+Accounting", "[Bypass]"/"[SkipFinal]" = test Mig 29-31 flags) + 9 real staff hand-created 05-04→05-12 + `binh.lethanh@` (người thật Lê Thanh Bình — seed dùng `thanh.lethanh@` KHÔNG tồn tại prod) + `chuong.phan@solution.com.vn` TYPO-domain dup (twin đúng tạo 05-12) + admin/catalog.manager/nv.test. **ROOT CAUSE seed-user never-on-prod:** prod `Identity:Password:RequiredLength=12` (appsettings.Production.json) vs `DemoUserPassword="User@123456"`=11 chars → CreateAsync silent-fail MỌI startup từ prod-init 04-21 (code comment :1675-79 đã biết); Dev fallback 8 (DependencyInjection.cs:67 `?? 8`, Development.json no Identity section) → Dev đủ 33 user named-person. `bod.1@` NEVER in git pickaxe = tạo tay qua admin UI, không phải seed. Surprise: _Dev hiện CŨNG chưa khóa (Locked=0; LockoutEnd=MaxValue sẽ persist qua reconcile re-activate :1714 nếu từng chạy) → lock chưa từng execute against _Dev runtime. Fix cần 20 email prod-thật; GIỮ binh.lethanh + 9 real + admin/catalog.manager; `nv.test@` = creds smoke-verify (khóa = vỡ cicd smoke). Tag `[s58, s57bis-lock-noop-recon, prod-user-census, pwd-policy-env-divergence]`. - -- **2026-06-11 (S57bis PE recon — 4 đầu việc sếp, on-disk):** ⭐ PE entity NO Year, NO WorkItem link (`PurchaseEvaluation.cs:15` ProjectId req; Detail free-text `PurchaseEvaluationDetail.cs:10-13`). Create cmd `PurchaseEvaluationFeatures.cs:19-30`; MaPhieu gen-AT-CREATE `:114-116` format `PE/{YYYY}/{A|B}/{Seq:D3}` (`PurchaseEvaluationCodeGenerator.cs:23`). Main create UI = `PeWorkspaceCreateView.tsx` (:151 workflow-select isUserSelectable ĐẦU TIÊN → tenGoiThau → projectId → DiaDiem → MoTa → PaymentTerms → budget; canSubmit :129 = wf+project+ten). PE controller class-`[Authorize]` ONLY no policy → mở menu là đủ, no silent-403. Pe_* leaves NOT in `MenuKeys.All` (chỉ root :156); PE defaults 7 role × 11 key (root + 2type×{group,WfView,List,Create,Pending}) `DbInitializer.cs:2098-2160`. S57 `SeedAllRolesReviewReadPermissionsAsync:1993-2001` InReviewScope EXCLUDES Pe; extend đúng = `key == MenuKeys.PurchaseEvaluations` EXACT (prefix "Pe" sẽ dính PeWorkflows admin!) — root inherit cascade (`GetMyMenuTreeQuery.cs:49-82`). Demo gate: prod `appsettings.json:35 DemoSeed:Disabled=true` → 7 `[DEMO]` HĐ + 4 `[DEMO]` PE (MaPhieu `[DEMO]-A-001`) KHÔNG lên prod; UNGATED trên prod = 31 users + 18 demo NCC + 8 demo project (:2244-2315) + real 62/71/3 (:2329-2522). ⚠️ Clear-demo gotcha: seed re-add per-code idempotent MỖI startup → xóa DB-only sẽ resurrect, phải gỡ khỏi DbInitializer code. WorkItem write Admin-only (`CatalogsController:113-130`) — CatalogManager có menu-perm nhưng API write bị chặn. Tag `[s57bis, pe-recon, demo-inventory]`. - -- **2026-06-10 (S57 perm-broaden blocks A/B/E/F — on-disk):** ⭐ **BE AUTHZ SPLIT (decision-critical for E):** Config controllers gate WRITE behind `[Authorize(Roles="Admin")]`, READ open to any-authed: `HrmConfigsController.cs:15` class `[Authorize]`+GET open `:19-21`, all POST/PUT/DEL `Roles="Admin"`; `CatalogsController.cs:14` same (write `:23+` Admin); `MeetingRoomsController.cs:15` same (comment `:9-10` explicit). → granting FE read on Hrm_Config/Catalogs/MeetingRooms = pure UI-visibility, BE already correct. **BUT Master 3 controllers = class `[Authorize]` ONLY, no per-action role:** `SuppliersController.cs:17`, `ProjectsController.cs:11`, `DepartmentsController.cs:11` — ANY authed user can POST/PUT/DELETE via API (FE menu perm is only gate, not BE-enforced). Flag em-main: making Suppliers/Projects/Departments visible-to-all ⇒ staff can write master incl. S55 prod data unless add `[Authorize(Roles="Admin,CatalogManager")]` or per-action policy. **S55 PROD DATA location:** `SeedRealMasterDataAsync` `:2267-2460` writes to **Projects**(62, `:2270`), **WorkItems**(71=CatalogWorkItems key, `:2430-2438`), **Suppliers**(3, `:2440-2456`) — all ungated idempotent per-code. **10 departments now** (`SeedDepartmentsAsync:2104`): 9 orig + IT (`:2115`). **31 demo users** (`SeedDemoUsersAsync:1553-1609`): dept spread BOD4/PM2/CCM9/PRO6/QS2/FIN2/ACT1/EQU1/HRA2; roles = mostly Drafter/CostControl/Procurement + 1 CatalogManager (`catalog.manager@`, dept PRO, `:1608`). NO user has zero-role; every demo user authenticatable. **Inherit-root display:** `GetMyMenuTreeQuery.cs:96 HasAccess = CanRead OR Children.Any(HasAccess)` → a non-inherit root (Hrm/Off/Master) auto-shows if ANY child has CanRead, so granting leaves is enough to reveal the root node (root row itself optional for display, but grant it too for cleanliness). Inherit-roots (Contracts/Pe/Wf/PeWf) cascade root→child so root-row-only suffices there. Menu already S57-edited: `Personal` group + `Off_ChamCong` re-parent Off→Personal via `parentBackfill:1908`. Tag `[s57-perm, be-authz-split, master-write-open, s55-data, 31user]`. - -- **2026-06-10 (S57 perm-broaden RECON blocks C/D — RAG down, on-disk):** ⭐ **SEED MODEL:** `SeedAdminPermissionsAsync` `DbInitializer.cs:1939-1977` Admin loops `MenuKeys.All` CRUD=true skip-existing dedup (`:1950/:1952`). Calls 2 sub: (a) `SeedPurchaseEvaluationPermissionDefaultsAsync` `:2036-2098` → **7 roles** {Drafter,DeptManager,Procurement,CostControl,ProjectManager,Director,AuthorizedSigner} Read+Update on PE keys only; (b) `SeedCatalogManagerPermissionsAsync` `:1984-2029` → role **CatalogManager** full-CRUD 9 master keys. **NO generic per-employee Read seed** — plain Drafter user sees ONLY PE keys; DOESN'T see Off_*/Hrm_*/Master/Contracts. **Most non-admin staff today see ~nothing but PE.** GetMyMenuTree `:96` filters CanRead=true. **Permission entity** `Permission.cs:3-15`: RoleId/MenuKey/CanRead/Create/Update/Delete. Dedup=app-level skip-existing per(RoleId,MenuKey), NO DB upsert. **13 AppRoles** `AppRoles.cs:23`: Admin(system)+12 employee. **4 inherit-roots** `GetMyMenuTreeQuery.cs:56-84`: Contracts/Workflows/PurchaseEvaluations/PeWorkflows — root grant auto-cascades to child IF child no own row (`:66`). Hrm/Off/Master NOT inherit → each leaf needs own row or add to switch. **GRANT-ALL pattern (block D):** mirror CatalogManager seeder but loop `roleManager.Roles` (all 13) × chosen key-set, CanRead=true only, insertion AFTER SeedCatalogManagerPermissionsAsync `:1976`. Tag `[s57-perm-recon, seed-model, no-employee-default, inherit-4root]`. - -- **2026-06-10 (menu-order cross-repo recon SE↔NAMGROUP, RAG down, on-disk):** ⭐ **SE menu seed = `SeedMenusAsync` `DbInitializer.cs` tuple-list** (NOT partial). Văn phòng số root `Off` (Order=29) `:1769-1792`; HR root `Hrm` "Nhân sự" (Order=28) `:1754-1767` (+`Hrm_Dashboard` appended out-of-order `:1791`). ⚠️ **HR SCATTERED 2 roots:** `Hrm` holds only Hồ sơ+Cấu hình HRM(6 leaf)+Dashboard; transactional HR (Nghỉ phép/OT/Công tác/Đặt xe/Chấm công/Báo cáo CC) live under `Off` as `Off_DonTu_*`/`Off_DatXe`/`Off_ChamCong`/`Off_AttendanceReport`. **SEED = UPSERT that RE-SETS Order** (`:1845-1871` `if(existing.Order!=o){existing.Order=o}`) → reorder in code propagates to Dev/prod next deploy, NO migration. BUT Label/ParentKey/Icon NOT touched on existing rows (`:1855` comment) — rename needs separate `labelBackfill` dict `:1874`. **Order = BE-only:** `GetMyMenuTreeQuery.cs:35 OrderBy(m.Order)`; both FE `Layout.tsx` render `useAuth().menu` as-is, `staticMap`+`menuKeys.ts` = key→route ONLY (no sort). FE needs NO edit for pure reorder. **NAMGROUP "Puro" = hardcoded FE array (NOT DB seed),** index=order: client `InternalLayout.tsx:83-118` (Nhân sự 3-item / Văn phòng số 6-item FLAT / Chấm công under separate "Cá nhân" group); admin `AdminLayout.tsx:87-119` splits by function not HR/office. NAMGROUP `Đơn từ`/`Phòng họp`/`Đề xuất` = flat single links (no sub-children) vs SE deep-nested. Tag `[menu-order, se-namgroup, seed-upsert-order, fe-be-driven, s57]`. - -- **2026-06-09 (S56 pre-golive verify — 4 logic streams, all PASS):** Audited P11-B/D/E/F + ApproveV2 + catalogs + S55 master-wiring. **LeaveBalance** deduction exactly-once (terminal DaDuyet, guard `Status!=DaGuiDuyet` :296 blocks re-approve), FK guard Create+UpdateDraft→Conflict; **AttendanceReport** classify day-type IN-MEMORY (Holiday DateOnly HashSet), OtPolicy multiplier; **MaTicket** gen-on-Create Serializable IT/2026/NNN. Tests cover (LeaveBalance 9 + AttReport 2 + codegen 3 = 29 green). ApproveV2 4-module flatten Steps→Levels correct; **Travel/Vehicle ApproveV2 = 0 test** (cookie-cutter of tested Leave/OT — add 2 smoke post-golive). master-data **idempotency PROVEN** (DbInitializer re-run → counts identical, per-code guard :2310/:2404/:2422). **⚠️ PROD FACT (corrects stale S52 mem):** dept "IT"/Phòng CNTT DOES exist (Id 65CC6307…) but has **0 active users** on prod → ItTicket auto-assign no-ops, reassign dropdown empty, SLA job no notify-target. Pre-golive ops fix: assign ≥1 real user to dept IT (1 UPDATE, no code). Tag [s56, pre-golive-verify, logic-pass, dept-IT-empty-prod, travel-vehicle-untested]. - -- **2026-06-09 (S56 Phase 2 FE-redesign RECON — 25 page audit, on-disk):** ⭐ **NOT a rewrite** — S55 already redesigned ui-primitives + DataTable + shell → any page importing `ui/{Button,Input}`+`DataTable` AUTO-inherits density. **Hover-hidden quick-win nearly absent:** repo-wide grep `opacity-0`×`group-hover` = **only 1 real site** `ContractCreatePage.tsx:196` (EXCLUDED scope) + DataTable.tsx:15 is a comment forbidding it (good). In-scope = **0 hover-hidden fixes**. **DataTable adoption split:** only 5/25 use `` (Suppliers/Projects/Departments/Users/Forms); 12 pages roll RAW `` (MeetingRooms/Catalogs/HrmConfigs/EmployeesList/Proposals/WorkflowApps/Attendance×2/MenuVisibility/Roles) → custom density pass needed. **Drawer ≥8-field candidates = 3:** Suppliers (9 fld, Dialog), Projects (10 fld, Dialog), Users-CREATE (~8 fld w/ roles multiselect, 4 Dialogs total but only create is big). All currently big-Dialog → convert. **NO `Drawer.tsx` exists** (`ui/` = Button/Dialog/Input/Label/Select/Textarea only) → build first. **Bậc-thang reference ALREADY EXISTS:** `EmployeesListPage.tsx` (1200L) = canonical inline add/edit-row for 5 satellites (`setEditing{X}Id` + `addingX` mutex, :256-356) → extract `InlineEditRow` pattern from here, reuse for Catalogs/HrmConfigs/MeetingRooms (these 3 currently edit via Dialog :251/:316/:232, ≤7 cols → bậc-thang candidates). **Modal-detail = NONE:** ProposalDetail:275 + WorkflowAppDetail:424 Dialogs are tiny action-confirm (just `