From e7e99d10f2358b707d0a33b3842ba90455d26e6d Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 18 Jun 2026 16:32:41 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Docs:=20S73=20closeout=20=E2=80=94?= =?UTF-8?q?=20Mig=2054=20PE=20gia=20de=20xuat=20+=20CCM=20duyet-done=20(ST?= =?UTF-8?q?ATUS/HANDOFF/session-log=20+=20review=20run-trace=20+=20agent-m?= =?UTF-8?q?emory=20harvest)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - STATUS/HANDOFF S73: Mig 54 · test 334 · bundle Bv3jUCNo/BWlMBQz6 (Run #313 feature + #314 fix) - session log 2026-06-18-S73-pe-gia-de-xuat-ccm-done.md - run-trace runs/2026-06-18-mig54-pe-review (custom-inline review, bu post-hoc) + _ledger 2 row (R1 schema 1/4 + R2 free-text 2/3) - agent-memory flush 5 sub + reconcile implementer-frontend cwd-misland stray -> canonical Co-Authored-By: Claude Opus 4.8 --- .claude/agent-memory/cicd-monitor/MEMORY.md | 7 ++- .../implementer-frontend/MEMORY.md | 5 ++ .../investigator-codebase/MEMORY.md | 2 + .claude/agent-memory/reviewer/MEMORY.md | 10 ++++ .../agent-memory/test-specialist/MEMORY.md | 3 +- .../review-synthesis.md | 47 +++++++++++++++++++ .../runs/2026-06-18-mig54-pe-review/run.md | 24 ++++++++++ .claude/workflows/runs/_ledger.md | 2 + docs/HANDOFF.md | 22 ++++++++- docs/STATUS.md | 8 ++-- .../2026-06-18-S73-pe-gia-de-xuat-ccm-done.md | 27 +++++++++++ 11 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 .claude/workflows/runs/2026-06-18-mig54-pe-review/review-synthesis.md create mode 100644 .claude/workflows/runs/2026-06-18-mig54-pe-review/run.md create mode 100644 docs/changelog/sessions/2026-06-18-S73-pe-gia-de-xuat-ccm-done.md diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 02a270e..bc4ce61 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -70,11 +70,14 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code / ## 📅 Recent runs (FIFO — older → archive/git) -- **2026-06-18 S72 Run #312 (run_number 312) sha=`18fced6` PASS ~5m16s (GOVERNANCE-ONLY discipline-check: 26 `.md` + `.claude/workflows/hmw.js` + `.claude/agent-memory/memory-budget.json`. Harness-10 flat-refine adoption. ZERO production BE/FE/Mig code. Expected = true no-op deploy. deploy 13/13 session after #297–#308 all PASS):** Push origin/main = HEAD `18fced6` (nothing unpushed). `git diff --name-only 18fced6~1 18fced6` = 27 files; non-path-ignored = ONLY `hmw.js` + `memory-budget.json` (both tooling/governance, NOT prod code) → CI TRIGGERED (correct — `.js`/`.json` not in paths-ignore `['docs/**','**/*.md','.claude/skills/**']`). Migration files in diff = ZERO. GITEA_TOKEN absent in PS-scope → anon Gitea API (worked, public repo). PROD_DB_PW empty → DB pw from prod `appsettings.Production.json`→`ConnectionStrings.Default` (`vrapp`/`buKL3TGBkD0wDDbYVw65QeX9` len24, path `C:\inetpub\solution-erp\api`). Run IN-PROGRESS at spawn (running 14:04:11→14:06:24) — exact `head_sha` match RUN#312 (deterministic, not gotcha-#46 stale). Poll-loop iter4 status=success (created 14:04:11 → success 14:09:27 ≈5m16s). CI gate (both proj pre-build ⟹ status=success ⟹ test **306** baseline (45D+261I) passed; `conclusion` empty — `tasks` terminal=`status:success` doesn't populate conclusion, trust success; 306 INFERRED gate-passes-pre-build invariant NOT log-confirmed numeric). Smoke **4×200**: api `/health/ready`+`/health/live` + admin root + eoffice root (both pre- AND post-success). **🔑★ MIG VERIFIED NO-CHANGE (sqlcmd over SSH ground-truth): prod `__EFMigrationsHistory` top5 = `AddPeUrgentAndCeoApprovalThreshold`(Mig53)→`AddHoSoLinkToPurchaseEvaluation`(52)→`AddDepartmentParentId`(51)→`ReplaceBudgetModuleWithPeWorkItemBudgets`(50)→`AddWorkItemToPurchaseEvaluation`(49) == repo HEAD, NO new mig ✓. sys.tables=88 (excl mighist) UNCHANGED ✓.** ⟹ DB side = perfect no-op as expected for governance commit. **⚠️★ BUNDLE SURPRISE → RESOLVED BENIGN (NEW gotcha #69): expected FROZEN admin `BgNCjwsG`/user `CBvh0vtf`, but AFTER status=success BOTH ROTATED admin `BgNCjwsG→fc_xkNpJ` (+717b, 1,593,048b) + user `CBvh0vtf→DP-tBcg0` (1,497,701b), stable on 2nd-fetch (not mid-deploy transient). FORENSIC: commits ebd7e1c..18fced6 (9 commits, all `[CLAUDE] Docs:`/`Workflow:`) touched 0 files in `fe-{admin,user}/src/` + 0 in package.json/lock/vite/tsconfig (verified per-file). ROOT CAUSE: `deploy.yml` lines 161-167 `Remove-Item fe-*\* -Exclude web.config` + `Copy-Item dist\*` runs UNCONDITIONALLY every run (path-filter gates WHOLE-workflow trigger, not per-step) → FE rebuilt every deploy → Vite/rolldown emits NON-DETERMINISTIC hash from identical source. PROOF index.html `Last-Modified` 07:07:49Z(14:07VN) = inside deploy window → physically rewritten this run. CONTROL TEST: fake `/assets/index-ZZdoesnotexist0.js` → 200 (SPA `/*`→index.html fallback) ⟹ "old-hash-still-200" is MEANINGLESS, reliable signal = parse index.html refs (admin `fc_xkNpJ.js`+`C2Zvnd2f.css` · user `DP-tBcg0.js`+`1bS4xeUF.css`).** ⟹ **rotation is NOT an accidental FE change — it's the pipeline's normal non-deterministic-rebuild behavior; OVERTURNS prior "BE-only/governance ⟹ bundle frozen" invariant used in every prior verdict (held by coincidence, not mechanism).** Assets reachable 200 + large (not white-screen) ⟹ FE healthy. 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only; FE re-copy = expected unconditional deploy side-effect, governance content unchanged). **VERDICT PASS (true no-op for the COMMIT's intent): green CI + test 306 + Mig 53 unchanged + tables 88 + health 4×200; bundle rotation flagged-then-cleared as benign pipeline mechanism (#69), NOT accidental ship — confirmed by 0 fe-src diff. LESSON: governance/BE-only verify must NOT FAIL on bundle-rotate; reframe check = (a) Mig+tables unchanged ground-truth, (b) health 200, (c) FE-content-change detection via `git diff fe-*/src` NOT hash, (d) bundle hash useful only to confirm a DELIBERATE FE deploy DID ship (rotate present), never to assert FE DIDN'T change (rotate is unconditional). TOOLING: bash→`$LOCALAPPDATA/Temp/x.ps1` + `powershell.exe -File $(cygpath -w ...)`; Gitea `tasks` via Invoke-RestMethod with `-Body @{limit=10}` hashtable (string-interp `?limit=N` into encoded path → 'hostname could not be parsed' URI error — use -Body not query-string); poll-loop backgrounded + Monitor grep-until FINAL; SSH→PS base64 `-EncodedCommand` UTF-16LE for appsettings-read AND sqlcmd; sqlcmd pw via PS here-string `$P=` + `[char]39` for literal object-name quotes; `-W -h -1 -s '|'`; read DB pw from prod appsettings when `$PROD_DB_PASSWORD` empty. NEVER fixed code (READ-only).** Tag `[s72, run312, pass, governance-only-no-prod-code, hmw-js+memory-budget-json-triggers-ci, mig53-unchanged-VERIFIED-prod, tables88-unchanged, health-4x200, test306-inferred, BUNDLE-BOTH-ROTATE-fc_xkNpJ-DP-tBcg0-but-BENIGN, NEW-gotcha-69-fe-hash-nondeterministic-per-rebuild-deploy-unconditional, spa-fallback-200-trap-control-test, frozen-invariant-OVERTURNED, 0-fe-src-diff-confirms-no-accidental-ship, deploy13of13, no-regression]`. (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]`. +- _(S72 Run #312 sha=`18fced6` PASS ~5m16s [governance-only 26-md+hmw.js+memory-budget.json triggers-CI, ZERO prod code, Mig53-unchanged-VERIFIED-prod, tables88, health-4x200, test306-inferred, **BUNDLE-BOTH-ROTATE-but-BENIGN fc_xkNpJ/DP-tBcg0** → NEW gotcha #69 (FE-hash NON-deterministic per-rebuild, deploy.yml Remove+Copy fe-* UNCONDITIONAL every run, "frozen-on-BE-only" invariant OVERTURNED, SPA-200-trap control-test, real-FE-change = `git diff fe-*/src` NOT hash-delta), 0-fe-src-diff-confirms-no-accidental-ship] → gotcha #69 promoted to recurring-patterns §top; FIFO-trimmed, full verbatim git `18fced6`+`39d55d4`)_ +- _(S71 Run #308 sha=`ebd7e1c` PASS ~4m41s [FULL-STACK PE-urgent+CCM-threshold, **Mig53 AddPeUrgentAndCeoApprovalThreshold VERIFIED-APPLIED-PROD** 3-cols-sys-columns (IsUrgentByPro/Ccm bit0 + CeoApprovalThreshold decimal18-2-null), history-top-advance-53, tables88-addcolumn-only, bundle-BOTH-rotate BgNCjwsG/CBvh0vtf, endpoint-urgent-401-not-404, test306] → 4-axis full-stack pattern (BE+FE-both-rotate · history-top-advance + N-cols sys.columns type-verify · tables-stay-88 · endpoint-401-not-404); FIFO-trimmed, full verbatim git `ebd7e1c`)_ -- **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-18 S74 Run #314 (run_number 314, id=428) sha=`6aa4dcb` PASS ~5m12s (FE-ONLY guard: PE khóa nút "Xác nhận" khi phải chọn giá mà chưa có giá nào — empty-candidates guard, anh Kiệt FDC continuation of Mig 54. 2 files: `fe-admin/src/components/pe/PeWorkflowPanel.tsx` + `fe-user/src/components/pe/PeWorkflowPanel.tsx`, 8 insert / 2 delete. KHÔNG migration KHÔNG BE — Mig stays 54. deploy 15/15 session after #297–#313 all PASS):** Push HEAD=`6aa4dcb` (nothing unpushed). `git diff --name-only HEAD~1 HEAD -- '*Persistence/Migrations*'` = EMPTY ✓ (FE-only, no EF migration). `.tsx` non-ignored → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW both absent env → anon Gitea API (worked, public repo). Run IN-PROGRESS at spawn (running 16:21:42, exact head_sha match #314 deterministic). Pre-poll bundle baseline captured BEFORE poll-loop (anti#3): admin `OlNyG9OD`/css `X_45M1jX` + user `DSzSLVtL`/css `Bbbo0M5H` = still Run #313 live (deploy not yet shipped). Poll-loop iter6 status=success (created 16:21:42 → success 16:26:54 ≈5m12s; iters 1-5 all running). CI gate (both test proj pre-deploy ⟹ status=success ⟹ test **334** baseline FE-only no new test passed; `conclusion` empty — `tasks` terminal=`status:success` doesn't populate conclusion, trust success; 334 INFERRED gate-passes-pre-build NOT log-confirmed numeric). **★ BUNDLE BOTH ROTATE (FE 2-app PE-panel change ⟹ both EXPECTED to rotate per gotcha #69; verified AFTER status=success + STABLE 2nd-fetch no-transient per anti#3): admin JS ROTATE `OlNyG9OD→Bv3jUCNo`** ✓ (css `X_45M1jX` UNCHANGED — guard touched panel logic not CSS) **+ user JS ROTATE `DSzSLVtL→BWlMBQz6`** ✓ (css `Bbbo0M5H` UNCHANGED). Asset reachable 200 + LARGE: admin js 1,599,110b + user js 1,503,769b (control fake `/assets/index-ZZfakehash0.js`→200 size=919b SPA-fallback ⟹ real JS shipped, gotcha #69 SPA-200-trap distinguished by size). index.html `Last-Modified` admin 09:25:23Z + user 09:26:17Z (=16:25-16:26VN deploy window) ✓ — matches deploy.yml Remove+Copy fe-* runs. Smoke **4×200** health: api `/health/ready`+`/health/live` + admin root + eoffice root. **NO migration check needed** (FE-only per spec; Mig stays 54 from #313 — not re-queried). 0 regression. NO prod-data mutation (read-only curls; FE-copy = expected deploy side-effect). Behavioral empty-candidates guard (nút Xác nhận disabled khi list giá rỗng) = anh Kiệt UAT (em confirm bundles shipped per spec). **VERDICT PASS: green CI + test 334 (FE-only, no new test) + bundle BOTH JS rotate (CSS unchanged — guard = panel logic) + asset 200-large 1.5MB vs fake-919b + health 4×200. LESSON: FE-only guard verify = lightweight — bundle JS rotate (per-rebuild non-deterministic gotcha #69, but here REAL FE change so rotate expected AND confirms ship) + CSS-may-stay-frozen if no style touched (partial rotation OK, JS is the signal) + asset-size SPA-200-trap distinguishes real-JS-1.5MB from fallback-919b + skip Mig/DB-query entirely when 0 migration files in diff (FE-only). #314 fastest-confirm: no SSH/sqlcmd needed. TOOLING: bash POSIX → write PS to `$LOCALAPPDATA/Temp/x.ps1` + `powershell.exe -File $(cygpath -w ...)`; Gitea `tasks` via Invoke-RestMethod `-Body @{limit=N}` hashtable (NOT query-string); poll-loop backgrounded + grep-until FINAL; index.html `