[CLAUDE] Docs: S91 closeout — go-live wipe + seed-gate + user-change-password + backup (gotcha #75/#76)

Closeout phiên go-live 26/06 (4 commit prod-verified đã push: guide-fix 159b69d · wipe ba42c59
· seed-gate 55494ad · user-change-password e81e87f + backup local).
- STATUS/HANDOFF +S91 entry, state re-ground: Mig 59 · 88 bang · 431 test (45D+386I) · gotcha 76
  · menu 54 · bundle admin DgQyuG7f/user BmbQon23 · prod 17 user/7 NCC/0 PE-HD
- gotchas #75 (SeedDemoMasterData ungated per-code -> resurrect NCC demo moi restart)
  + #76 (deploy render appsettings.Production.json tu .example, KHONG file gitignored)
- session log 2026-06-26-S91 + feedback_wipe_durability_check_reseed (user memory)
- harvest cicd-monitor + test-specialist diary (self-written, verified)
- .gitignore +backups/ +*.bak

Docs/memory-only -> CI skip (paths-ignore docs/** + **/*.md + .gitignore).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-26 13:07:01 +07:00
parent e81e87ff9f
commit f98a743074
9 changed files with 148 additions and 28 deletions

View File

@ -70,16 +70,10 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code /
## 📅 Recent runs (compressed — full verbatim → `archive/2026-06.md` via `archive/_INDEX.md`) ## 📅 Recent runs (compressed — full verbatim → `archive/2026-06.md` via `archive/_INDEX.md`)
- **S90 #465 (id=465, run_number 351) `a71753b` PASS ~4m56s (FULL-STACK PE "nháp riêng tư" + "giá đề xuất CCM" fix, NO-MIG — anh Kiệt FDC UAT)** — 2-commit push `d085436..a71753b`: `f7bd97a` FE-only gỡ `!readOnly` over-gate trên PRO/CCM suggested-price inputs (both apps PeDetailTabs ×2, +18/8) + `a71753b` BE PE draft-private-to-creator (PurchaseEvaluationFeatures.cs +18/4 — chặn leak phiếu Nháp sang role khác + V2-approver) + new test PeDraftVisibilityTests. Files 4: FE 2 (PeDetailTabs admin+user) + BE 1 (PurchaseEvaluationFeatures) + 1 test. **NO `*Migrations*`/`*Persistence*` in range diff** (logic-only, no schema). status=success ⟹ test gate passed (**baseline 421** = 45 Domain + 376 Infra = 419 +2; logs 404 anon so count not log-scraped — trusted gate+baseline). **Bundle JS BOTH ROTATE (real fe-src PeDetailTabs ×2 ⟹ new content-hash): admin js `BeyLzf5i`→`BXixQDuS` · user js `CPe970sY`→`BMgckUPd`** (pre-deploy LIVE baseline = Run #350 web.config, NOT brief's stale #348 `BQq2eTP6`/`dHa64I59`); **css FROZEN admin `DWQ-DgfZ` user `Dr0-ikKX` — correct, FE logic-only [un-gate inputs] no new utility class** (gotcha #69-inverse). Real-ship proven (SPA-trap defeated): admin js 1,628,895b Last-Modified 07:43:18Z=14:43:18+07 in-window (run 14:39:38→14:44:34 = 07:39:38→07:44:34Z) · user js 1,532,796b 07:44:10Z in-window · control fake `ZZfakehash00.js`→200 but 919b text/html (size-check meaningful). **BE dll ALL 4 fresh in-window: Api 07:42:07Z · Application 07:40:13Z (PurchaseEvaluationFeatures draft-private live) · Infrastructure 07:40:37Z · Domain 07:39:56Z.** **Mig FROZEN 59** (repo MIGCOUNT=59 + prod history HEAD==`AddCcmBudgetPeriodToPurchaseEvaluation`==repo + HistCount=59 no-pending — correct NO-MIG). Health api ready/live 200 + admin/eoffice 200 (4×200). GET /api/purchase-evaluations + /api/menus + /api/approval-workflows-v2 all 401 unauth (wired NOT 404/500). **S88 cache headers LIVE: admin+eoffice index `Cache-Control: no-cache` + assets `immutable,max-age=31536000`** (web.config S88 hardening confirmed in prod). PE fix BE-internal (draft-visibility filter) + FE un-gate = no new endpoint → 421-test covers draft-visibility behavior, no curl-probe. gotcha #69-inverse re-confirmed (real fe-src ⟹ js rotates, css frozen logic-only). ⚠️ **brief's prev-hash #348 was STALE — Run #350 (`ca136d8` web.config) rotated bundle since; ALWAYS re-snapshot LIVE pre-deploy, don't trust brief's claimed prev.** SSH→PS `$_.LastWriteTimeUtc` mangled nested-double-quote → base64 EncodedCommand worked; sqlcmd `-Q` mangled → scp .sql + `-i` file cleanest. DB pw `buKL3TGBkD0wDDbYVw65QeX9` (no `vrapp/` prefix), conn key `ConnectionStrings.Default`, prod path `C:\inetpub\solution-erp\api`. - **🟢 S91 #470 (id=470, run_number 356) `55494ad` GO ~5m11s (GO-LIVE 26/06 Phase-01 PRO+CCM — VERIFY-NO-RESURRECT, BE-ONLY DbInitializer gate)** — 1-file `DbInitializer.cs` gate `SeedDemoMasterDataAsync` sau cờ `DemoSeed:Disabled` (prod=true) chống restart re-seed 18 NCC demo. NO `*Migrations*`/fe-src. status=success ⟹ test gate passed (baseline 422=45 Domain+377 Infra per spec; logs 404 anon, trusted gate+baseline). **PURPOSE = no-resurrect verify (NOT bundle-hash).** Pre-restart DB (binary cũ): Suppliers=7/demo-code=0/PE=0. **Post-restart (binary GATE mới): Suppliers=7 · demo-code=0 (tuyệt đối, 18 demo NCC-VLXD/CDT-VIN/CDT-SUNGROUP/NTP-XD/TD-NEHOANG/DV-CLEAN… KHÔNG re-seed) · PE=0 · PeWorkItemBudgets=0 · Notifications=0** ⟹ gate HIỆU LỰC. **3-evidence binary-mới-thực-chạy-DbInitializer** (chống đọc-state-process-cũ false-pass): (1) `Infrastructure.dll` UtcMod 03:54:35Z in-window (DbInitializer sống Infrastructure) · (2) `Api.dll` 03:56:04Z in-window (run 03:53:32Z→03:58:43Z UTC = 10:53:32→10:58:43+07) · (3) **`w3wp PID=10616 StartUtc=03:58:19Z` + `PID=4428 03:59:04Z` recycle SAU run-window** ⟹ app pool started binary mới + chạy DbInitializer với cờ gate → demo gated. ⚠️ **API in-proc w3wp (no SolutionErp.Api.exe process — ANCM in-process); shared VPS nhiều w3wp [VIETREPORT pools] → match pool by StartTime-in/after-window, KHÔNG by tên process.** Smoke 4×200 (health/live+ready, eoffice, admin). Wipe-S91 (scripts/s91-wipe-golive.sql, backup `SolutionErp_S91_pregolivewipe.bak`) + gate khớp → prod sạch 7 NCC thật, PeSeq reset A/001. **No re-run wipe.** **🆕 NEW PATTERN — go-live no-resurrect verify = (a) pre+post-restart DB-count diff [demo-code MUST stay 0] + (b) dll Last-Modified in-window [code-gate ship] + (c) w3wp StartTime ≥ run-window [pool recycled ⟹ DbInitializer ran]; thiếu (c) thì post-count chỉ là state cũ CHƯA chứng minh gate.** DB integrated-auth `-E` (no pw). CLIXML noise → `grep -v CLIXML/Objs/progress`.
- **S89 #348 (id=462) `1aa3bdb` PASS ~5m10s (FULL-STACK PE D2 multi-winner outward, NO-MIG)** — create-contract 1→N per winner (command→List<Guid>, multi-winner handler in CreateContractFromEvaluationFeatures, Controller returns `{contractIds}`) + list-card winner-names projection (PurchaseEvaluationFeatures + Dtos WinnerSupplierNames). Files 12: BE 4 (Controller/CreateContractFromEvaluationFeatures/Dtos/PurchaseEvaluationFeatures) + FE **both apps** 6 (PeDetailTabs+PeListPanel+types ×2) + 2 test (CreateContractFromEvaluationMultiWinnerTests + PeListWinnerNamesProjectionTests). **NO `*Migrations*`/`*Persistence*` in diff** (git diff EMPTY — logic-only, no schema). status=success ⟹ test gate passed (**baseline 419** = 45 Domain + 374 Infra = 413 +6; logs 404 anon so count not log-scraped — trusted gate+baseline). **Bundle JS BOTH ROTATE (real fe-src PeDetailTabs+PeListPanel ⟹ new content-hash): admin js `CP_iKhTu`→`BQq2eTP6` · user js `DUe0fndl`→`dHa64I59`** (#347 baseline); **css FROZEN admin `Pltrs5ku` user `Foiw55B6` = #347/#346 — correct, D2 FE logic-only no new utility class** (gotcha #69-inverse). Real-ship proven (SPA-trap defeated): admin js 1,628,901b Last-Modified 03:34:13Z=10:34+07 in-window (run 10:30:30→10:35:40 = 03:30:30→03:35:40Z) · user js 1,532,802b 03:35:07Z in-window · control fake `ZZfakehash00.js`→200 but 919b text/html (size-check meaningful). **BE dll ALL 4 fresh in-window: Api 03:33:00Z (Controller `{contractIds}` live) · Application 03:31:04Z (CreateContractFromEvaluation multi-winner handler + PE winner-names projection live) · Infrastructure 03:31:28Z · Domain 03:30:47Z.** **Mig FROZEN 59** (repo MIGCOUNT=59 + prod history HEAD==`AddCcmBudgetPeriodToPurchaseEvaluation`==repo + HistCount=59 no-pending) + sys.tables 89 incl __EFMigrationsHistory = 88 business (no schema change). **Endpoint live: `POST /api/purchase-evaluations/{id}/create-contract` bodyless→411 (IIS Length-Required pre-auth), +json-body→401 (wired: NOT 404=route-exists, NOT 500=no-crash; `{contractIds}` multi-winner contract live).** Health api ready/live + admin + eoffice 4×200. GET /api/purchase-evaluations + /api/menus + /api/approval-workflows-v2 all 401 unauth (wired). gotcha #69-inverse re-confirmed (real fe-src ⟹ js rotates, css frozen when logic-only). ⚠️ **DB pw = `buKL3TGBkD0wDDbYVw65QeX9` (NOT `vrapp/buKL3...` — `vrapp/` was user/pw-combined-notation in memory essentials, actual Password field has no prefix; read from prod `appsettings.Production.json` ConnectionStrings.Default).** SSH→PS `$_.LastWriteTimeUtc` mangled in nested double-quote → base64 EncodedCommand worked; sqlcmd `-Q` quoting mangled → scp .sql + `-i` file worked cleanest. conn key `ConnectionStrings.Default`, prod path `C:\inetpub\solution-erp\api`. - _(S90 #465 `a71753b` FULL-STACK PE draft-private + un-gate suggested-price js-rotate-css-FROZEN baseline-421 · S89 #348 `1aa3bdb` FULL-STACK PE create-contract-1→N multi-winner + winner-names-projection endpoint-411→401-wired baseline-419 · S88-quad #347 `73cce1f` FULL-STACK PE D1 Block-B re-key + CEO-notify-persist baseline-413 → all PASS NO-MIG (frozen-59), full verbatim → archive `2026-06.md` via `_INDEX`)_
- **S88-quad #347 (id=461) `73cce1f` PASS ~5m04s (FULL-STACK PE D1 stability, NO-MIG)** — Block-B multi-winner financials re-key (PurchaseEvaluationFeatures) + CEO-notify SaveChanges-persist (PurchaseEvaluationUrgentFeatures +5 lines) + setter RELOCATE PeSuggestedPriceFeatures→PeWorkItemBudgetFeatures (56/+57) + HoSoLink trim + #70 guard + Dtos comment. Files 12: BE 5 (Dtos+4 features) + FE **both apps** 4 (PeDetailTabs+types ×2) + 3 test (PeHoSoLinkTests+PeWorkItemBudgetTests EXTEND, **new PeUrgentNotifyDirectorTests +237 lines**). **NO `*Migrations*`/`*Persistence*` in diff** (logic-only re-key, no schema). status=success ⟹ test gate passed (**baseline 413** = 45 Domain + 368 Infra = 402 +11; logs 404 anon so count not log-scraped — trusted gate+baseline). **Bundle JS BOTH ROTATE (real fe-src #70-guard+HoSoLink-trim ⟹ new content-hash): admin js `BABhPh2s`→`CP_iKhTu` · user js `DLulO6uP`→`DUe0fndl`** (#346 baseline); **css FROZEN admin `Pltrs5ku` user `Foiw55B6` — correct, D1 FE logic-only no new utility class**. Real-ship proven: admin js 1,628,726b Last-Modified 02:57:57Z=09:57+07 in-window (run 09:54:15→09:59:19 = 02:54:15→02:59:19Z) · user js 1,532,627b 02:58:50Z in-window · SPA-trap control `ZZfakehash00.js`→200 but 919b text/html (size-check meaningful). **BE dll ALL 4 fresh in-window: Api 02:56:42Z · Infrastructure 02:55:16Z (PeWorkItemBudgetFeatures setter-relocate live) · Application 02:54:51Z (PurchaseEvaluationFeatures Block-B + UrgentFeatures CEO-persist live) · Domain 02:54:36Z.** **Mig FROZEN 59** (MIGCOUNT=59, prod history HEAD==`AddCcmBudgetPeriodToPurchaseEvaluation`==repo, no-pending; correct NO-MIG D1) + sys.tables 88 (89 incl __EFMigrationsHistory). Health api ready/live + admin + eoffice 4×200. GET /api/purchase-evaluations + /api/menus + /api/approval-workflows-v2 all 401 unauth (wired NOT 404/500). gotcha #69-REFINE inverse re-confirmed (real fe-src ⟹ js rotates, css frozen when logic-only). conn key `ConnectionStrings.Default`, prod path `C:\inetpub\solution-erp\api` (skill-doc `\apps\...` STALE). ⚠️ SSH→PS table-count `<>'literal'` mangled via base64/CLIXML AGAIN → use count-incl-history1 (88) workaround.
- _(S88-bis #346 `0d3c0cf` FE-only PE layout "4. Thông tin chọn thầu" 2-files js+css-BOTH-rotate baseline-402 · S88 #345 `9b1ee0c` FULL-STACK Section-B 3-col + **Mig 59 `AddCcmBudgetPeriodToPurchaseEvaluation`** AddColumn-no-backfill VERIFIED-PROD + setter `PATCH .../budget/ccm-period` PATCH-401-not-411 + 7 authz-test → PASS, FIFO-trimmed, full verbatim git + archive `_INDEX`)_ - _(S88-bis #346 `0d3c0cf` FE-only PE layout "4. Thông tin chọn thầu" 2-files js+css-BOTH-rotate baseline-402 · S88 #345 `9b1ee0c` FULL-STACK Section-B 3-col + **Mig 59 `AddCcmBudgetPeriodToPurchaseEvaluation`** AddColumn-no-backfill VERIFIED-PROD + setter `PATCH .../budget/ccm-period` PATCH-401-not-411 + 7 authz-test → PASS, FIFO-trimmed, full verbatim git + archive `_INDEX`)_
- **S87 #344 (id=458) `f1b10e9` PASS ~5m11s (FULL-STACK PE CCM 3-fix + Mig 58, anh Kiệt FDC)** — multi-winner SUM (SelectWinner→SupplierIds[] array body) + budget=0 valid-submit + per-level auto-finalize (`ApproveV2Async if matchingLevel.AllowApproverFinalize`). Files: BE 13 (Controller/App/Domain/Infra/Service + Mig 3-file) + FE **BOTH apps** 7 (PeDetailTabs/PeWorkflowPanel/types ×2 + fe-admin ApprovalWorkflowsV2Page Designer) + 6 test. **Mig 58 `20260624071509_AddPeMultiWinnerAndLevelFinalize`** = 2 AddColumn bit (`PurchaseEvaluationSuppliers.IsWinner` default0 + `ApprovalWorkflowLevels.AllowApproverFinalize` default0) + 1 backfill `UPDATE ... SET IsWinner=1 WHERE pes.SupplierId=pe.SelectedSupplierId` — VERIFIED-APPLIED-PROD (history top==`AddPeMultiWinnerAndLevelFinalize`==repo HEAD; both cols sys.columns=1; **backfill ran: winners=13 == pe_with_SelectedSupplierId=13 / 30 total supplier rows** — exactly 1 winner per pre-existing selected PE). Test gate=success ⟹ passed (**baseline 395** = 45 Domain + 350 Infra per spec; was 397 2 because per-level-finalize moved runtime-flag→auto-config, NOT regression). **Bundle BOTH ROTATE (real fe-src ⟹ new content-hash, contrasts #69-REFINE BE-only): admin js `aKuNip19`→`CXCKlNay` (css FROZEN `BKB2NsgQ`, logic-only diff no new chunk), user js `BUsL0HeQ`→`Di40ewrF` (css FROZEN `BaqeI4iG`).** Real-ship proven: admin js 1,621,660b Last-Modified 08:26:32Z=15:26+07 in-window · user js 1,525,569b 08:27:26Z in-window · control fake-hash `ZZfakehash0.js`→200 but 919b text/html (SPA-trap confirms 200-alone meaningless). Endpoint live: `POST /api/purchase-evaluations/{id}/select-winner` bodyless→411 (IIS Length-Required pre-auth), +json-body→401 (wired, NOT 404/400/500; `SelectWinnerBody(IReadOnlyList<Guid>? SupplierIds)``SelectPurchaseEvaluationWinnerCommand` array contract live). Health api ready/live + admin + eoffice 4×200. ⚠️ **ambient sys.tables=89 (was 88) — NOT from Mig 58** (AddColumn-only, 0 new table; newest table `PeWorkItemBudgets` 06-13 Mig 50, none created 06-24); 88 baseline last re-grounded S62, +1 pre-existing drift in Mig 50-57 range, harmless. ⚠️ prod path `C:\inetpub\solution-erp\api` (skill-doc `\apps\SolutionErp\Api` STALE — re-confirmed); conn key `ConnectionStrings.Default`; base64 EncodedCommand + 1 path-retry. gotcha #69-REFINE inverse confirmed (real fe-src ⟹ hash DOES rotate). - _(S87 #344 `f1b10e9` FULL-STACK PE CCM 3-fix + **Mig 58 `AddPeMultiWinnerAndLevelFinalize`** 2-AddColumn-bit+backfill winners=13 VERIFIED-PROD, select-winner endpoint-411→401-wired, baseline-395 · S86 #343 `ae4f106` BE-only notify-exclude-drafter baseline-377 ship-proof=Infrastructure.dll-Last-Modified · S85 #342 `d4f6021` DOCS+SQL-no-op INVERTED-PASS-when-FROZEN baseline-374 (gotcha #73 TZ + #69-REFINE) · S83 #333 `fc1f19d` BE-only PE-guard-V2-workflow-required hash-FROZEN-but-shipped baseline-356 · S78 #330 `7886fd0` cross-stack enum-attach-on-approve · S76 #318 `e33481e` Mig56-ProInitial/Adjust backfill-4rows → all PASS, full verbatim → archive `2026-06.md` via `_INDEX`)_
- **S86 #343 (id=457) `ae4f106` PASS ~5m00s (BE-ONLY notify-filter)** — PE "cần duyệt" notify EXCLUDE drafter + 1 new test (`PeApproverNotifyExcludesDrafterTests`) + STATUS + agent-memory. Only-BE-src: 1 file `PurchaseEvaluationWorkflowService.cs`, ZERO `fe-*/src`, ZERO `*Migrations*`. status=success ⟹ test gate passed (**baseline 377** = 45 Domain + 332 Infra per spec; S85 374 +3; reviewer's 2 transient parallel-collection flake did NOT surface red in CI — gate green). **Ship-proof #69-REFINE (BE-only) CONFIRMED via dll Last-Modified in run-window (16:37:38→16:42:35+07 = 09:37:38→09:42:35Z):** Api.dll 09:40:04Z · **Application.dll 09:38:12Z · Infrastructure.dll 09:38:34Z** (PurchaseEvaluationWorkflowService lives in Infrastructure → recompiled fresh) — all 3 in-window. **FE bundle FROZEN (correct, 0 fe-src diff ⟹ byte-identical): admin `aKuNip19`/`BKB2NsgQ`, user `BUsL0HeQ`/`BaqeI4iG` = S85 baseline.** Mig FROZEN 57 (`AddPeSuggestedPriceNotes`, prod head==repo) + tables 88. Prod 4×200 (api ready/live, admin, eoffice) + PE wired (GET /api/purchase-evaluations + /api/menus both 401 unauth, NOT 404/500). Notify-filter BE-internal no-new-endpoint → test 377 covers behavior (no curl-probe of notify logic). gotcha #69-REFINE reaffirmed (BE-only: dll Last-Modified = sole ship-proof, hash-delta N/A). ⚠️ SSH transient drop ×3 mid-session (`Connection closed by 103.124.94.38`) — base64 EncodedCommand + retry-loop recovered; not a deploy issue.
- **S85 #342 `d4f6021` PASS ~5m00s (DOCS+SQL NO-OP, INVERTED criteria)** — 6×.md + `scripts/s85-wipe-uat-data.sql` (only non-ignored path → CI triggers; whole-workflow still builds FE+publish). **PASS-when-FROZEN, NOT rotate.** Verified: status=success ⟹ test gate passed (baseline **374** = 45 Domain + 329 Infra per STATUS.md canonical, logs API 404 anon so count not log-scraped — trusted gate+baseline) + **bundle FROZEN admin `aKuNip19`/user `BUsL0HeQ` (correct: 0 fe-src diff ⟹ byte-identical ⟹ same hash, gotcha #69-REFINE)** but DID ship (admin js Last-Modified 08:56:34Z=15:56+07 IN-window 15:53→15:58, real 1.62MB/user 1.52MB vs SPA-trap 919b, 2nd-fetch byte-stable). Prod 4×200 (ready/live/admin/eoffice) + endpoint smoke 4×200 (PE/awf-v2/menus/projects). **Prior S85 deploys still live: R6 TZ fix `49ac086` → `createdAt:"…7331140Z"` trailing-Z CONFIRMED; R5 UpdateDraft `1263c0d` → both ancestors of d4f6021, shipped via #341 (#340 cancelled but #341 success built later commit incl TZ).** SignalR negotiate 411 (bodyless-POST IIS, wired, notif untouched). gotcha #73 (TZ) + #69-REFINE reaffirmed.
- **S83 #333 `fc1f19d` PASS ~4m59s** — BE-only PE-guard (require ApprovalWorkflowId V2 at create-validator + submit-branch ConflictException; close FE-only-validation gap → phiếu reached "Đã gửi duyệt" w/ null workflow = V1-legacy-banner trap). +2 validator tests → **test 356** (gate=success ⟹ pass). NO-mig (frozen 57, prod head `AddPeSuggestedPriceNotes`==repo, no-pending) + ZERO fe-*/src diff. **Bundle hash UNCHANGED (admin `CsJetgZH`/user `BVS0ApIm` = #330) BUT shipped — Last-Modified 17:10:35/17:11:27 in-window** (real js 1.62/1.52MB vs SPA-trap 919b) → **gotcha #69-REFINE: 0-FE-src ⟹ byte-identical ⟹ same-hash; Last-Modified is true ship-proof.** Guard BE-only no-endpoint → no curl-probe (356 tests cover RED→GREEN). Health api/ready+live 200, admin/eoffice 200, api-root 404 (no `/` route, expected).
- **S78 #330 `7886fd0` PASS ~4m56s** — cross-stack NO-MIG enum `ApprovalAttachment=5` (attach-on-approve) + FE 2-app PeWorkflowPanel modal-upload. Verified: empty-migrations-diff + Mig-frozen-57 + tables88 + bundle BOTH rotate js+css (real 1.6/1.5MB vs fake-919b) + endpoint bodyless-POST-411→re-probe-body-401-wired + health 4×200. → _INDEX.
- **S76 #318 `e33481e` PASS ~4m58s** — full-stack Mig56 ProInitial/ProAdjust VERIFIED-APPLIED-PROD (sys.columns decimal(18,2)) + **5th-axis BACKFILL-verify** 4-rows-0-violation (gotcha #64 prod-data-UPDATE-first-time) + history-advance-55→56 + tables88 + bundle BOTH+css rotate + health 4×200. Pre-deploy DB snapshot (Mig55) → post (Mig56) = unambiguous proof. → _INDEX.
- _(S83 #325 `e29391e` FE-only budget-subitem indent js-rotate-css-FROZEN test351 · S77 #329 `e823694` FE-only banner Trả-lại · #328 `424131d` BE-only notify-block test-gate-KEY · #327 `fa6654b` FE-list-restructure · #326 `b5aa72d` BE-authz asymmetric · #323 #322 #321 #320 PE UX batch → all PASS, FIFO-trimmed, full verbatim git + archive `_INDEX`)_ - _(S83 #325 `e29391e` FE-only budget-subitem indent js-rotate-css-FROZEN test351 · S77 #329 `e823694` FE-only banner Trả-lại · #328 `424131d` BE-only notify-block test-gate-KEY · #327 `fa6654b` FE-list-restructure · #326 `b5aa72d` BE-authz asymmetric · #323 #322 #321 #320 PE UX batch → all PASS, FIFO-trimmed, full verbatim git + archive `_INDEX`)_
- _(S74-bis #315 `8655ebf` Mig55 CcmNote · S73 #313 `1d86abc` Mig54 5-cols + endpoint-401-not-404-probe · S69b #307 `1f8947e` Office-golive seed-16/16-DB-query-proof → archive `_INDEX` + git)_ - _(S74-bis #315 `8655ebf` Mig55 CcmNote · S73 #313 `1d86abc` Mig54 5-cols + endpoint-401-not-404-probe · S69b #307 `1f8947e` Office-golive seed-16/16-DB-query-proof → archive `_INDEX` + git)_
- **Older runs (S75 #301 ← S62 #286 ← … → S29 #232) full verbatim → git `d2f52ba` + `archive/2026-06.md`** (incl #291 06-16 FAIL forensic [gotcha #65]); pre-S38 → `archive/2026-05-{runs,q2,q3,q4}.md`. - **Older runs (S75 #301 ← S62 #286 ← … → S29 #232) full verbatim → git `d2f52ba` + `archive/2026-06.md`** (incl #291 06-16 FAIL forensic [gotcha #65]); pre-S38 → `archive/2026-05-{runs,q2,q3,q4}.md`.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,59 @@
---
name: baseline-history
description: L2 archive — verbose test-count history chain + aged FIFO entries (S54→D2). On-demand only; MEMORY.md index keeps one-liners.
metadata:
type: project
---
# Baseline count history (verbose) + aged FIFO
> Moved out of `MEMORY.md` index 2026-06-26 (index over 24KB read cap). Each entry was a full test-design note; detail also recoverable from the test files + session logs. Newest first.
## Count progression (one-line chain)
254 (S60) → 263 (S61 +22 PeWorkItemBudget, 14 BudgetPolicy Domain drop) → 286 (S67 +23 HRM) → 292 (S69 +6 Office) → 306 (S69b +14 PE) → 334 (S72 +28 PE Mig54) → 339 (S74 +5 CcmNote) → 344 (S76 +5 PRO-split) → 351 (S77 +7 suggested-price NOTE) → 354 (S77b symmetric, superseded) → 366 (S78-S84, MEMORY chưa ghi từng delta) → 371 (S85 +5 urgent drafter-unset) → 374 (S85b +3 UpdateDraft WF-preserve) → 377-395 (S86-S86d) → 402 (S87 +7 CCM-budget-period) → **419 (D2 +6 PE Mig58 multi-winner + translation-smoke)**.
## D2 — PE Mig 58 multi-winner (test-after + EF translation-smoke), +6 → 419
`CreateContractFromEvaluationMultiWinnerTests.cs` (Application ns, 5) cho `CreateContractFromEvaluationCommand` single→multi-winner trả `List<Guid>` (1 HĐ per đơn vị IsWinner). Handler 4-dep `(db, ICurrentUser, IContractWorkflowService, IContractCodeGenerator)` wire THẬT (mirror CreateContractCommandApplicableTypeTests S33): ContractCodeGenerator(db,clock)+ContractWorkflowService — BeginTransactionAsync SERIALIZABLE chạy SẠCH SQLite, KHÔNG try/skip. IdentityFixture. 5 case: (a)≥2 winner→2 ids·2 Contract·Phase=DangSoanThao·SupplierId khớp winner·**GiaTri per-winner = SUM ThanhTien báo giá của ĐÚNG winner (q.PESupplierId==winnerRow.Id)** 140/260·2 mã DISTINCT·pe.ContractId==ids[0] / (b)single→**GiaTri = SUM details.ThanhTienNganSach (LEGACY 800, NOT quote-total 140)**·1 HĐ·TenHopDong null→fallback TenGoiThau / (c)idempotency→ConflictException`*đã tạo HĐ rồi*`+count 2 / (d)0 winner→Conflict`*chưa chọn NCC/TP trúng thầu*`+0 HĐ / (e)not-DaDuyet→Conflict`*DaDuyet*`+0 HĐ. ⚠️**SEED-FK: PurchaseEvaluationQuote HARD FK PESupplierId→Supplier Restrict (cfg:135)** → mỗi Quote ref 1 PESupplier-row thật (random Guid→SQLite 19); Contract.SupplierId/ProjectId no FK vật lý nhưng handler load Project(NotFound)+supplierMap→seed real. `PeListWinnerNamesProjectionTests.cs` (1) TRANSLATION-SMOKE cho EF subquery `WinnerSupplierNames` collection-projection → **EF TRANSLATE OK no InvalidOperationException trên SQLite**: 2/1/0-winner. SqliteDbFixture read-only. No prod bug.
## S87 — PE Mig 59 CCM-budget-period setter (test-after authz), +7
`PeCcmBudgetPeriodSetterAuthzTests.cs` cho `SetPeCcmBudgetPeriodCommand` — Section B 3-cột Dự án|PRO|CCM, CCM nhập "NS kỳ này" cột riêng độc-lập PRO. Handler 2-dep: NotFound→role-gate Admin|CostControl else Forbidden`*CCM*`→absolute-set CcmBudgetPeriodAmount→Changelog→Save. 7 test: Admin/CostControl set · Drafter/Procurement→Forbidden+no-set · value=0 valid+validator(0-valid/âm-invalid) · null=clear (pre-seed 500tr, gotcha #73)+validator null-valid · unknownPE→NotFound TRƯỚC authz. Validator `GreaterThanOrEqualTo(0).When(HasValue)`. ⚠Changelog.UserId Guid? no FK User → random-Guid an toàn. No prod bug.
## S86 — PE drafter-notify-exclusion REGRESSION (bug-fix test-alongside), +3
em main fix `PurchaseEvaluationWorkflowService.LogTransitionAsync` (~:1083-1135) khối notify "Phiếu cần bạn duyệt" +filter `&& l.ApproverUserId != evaluation.DrafterUserId` → người tạo phiếu KHÔNG nhận chuông cần-duyệt phiếu mình tạo. `PeApproverNotifyExcludesDrafterTests.cs` (Services ns) 3: (1)CORE 1-Step-2-Level Cấp1=approverL1(actor) Cấp2={drafter,otherL2}; actorL1 Approve→advance→NeedApproval recipients EXCLUDE drafter INCLUDE otherL2 / (2)Cấp2=CHỈ drafter→recipients EMPTY / (3)SANITY terminal DaDuyet drafter VẪN nhận chuông KẾT-QUẢ. ⚠️**actor-exclusion KHÔNG che bug** (actor=Cấp1, drafter=Cấp2). **RED-on-old-code PROVEN qua git stash**. Spy=`CapturingNotificationService` (records recipients+title). No prod touch (verify qua stash, restore exact).
## S85b — PE UpdateDraft WF-preserve ROOT-FIX bug-class S42 (test-after bug-fix), +3
em main fix `UpdatePurchaseEvaluationDraftCommandHandler` (PurchaseEvaluationFeatures.cs:285) ABSOLUTE-SET→NULL-SAFE `if(request.ApprovalWorkflowId is not null)`. `PeUpdateDraftWorkflowPreserveTests.cs` 3: (1)PRESERVE Nháp WF=W + null request → GIỮ W + sanity update / (2)CHANGE W1→W2 (≠null)→đổi / (3)bonus BudgetPeriodAmount null-safe S61. ⚠️`ApprovalWorkflowId` FK Restrict → PE seed PHẢI ref WF thật (`SeedWorkflowAsync` ApplicableType=DuyetNcc). No prod bug sau fix.
## S85 — PE urgent DRAFTER-self-unset (test-after authz), +5
em main restructure `PurchaseEvaluationUrgentFeatures.cs` thêm nhánh UNSET cho NGƯỜI KHAI (`isDrafter = currentUser.UserId == entity.DrafterUserId`) → drafter GỠ clear CẢ 2 cờ, SET vẫn role-only. EXTEND `PeUrgentToggleAuthzTests.cs` 11→16: FakeCurrentUser.UserId→init + SeedPeAsync +drafterUserId. NEW 5: Drafter UNSET→clear cả 2 no-Forbidden / Finance UNSET→Forbidden`*GỠ*` / Drafter SET→Forbidden`*đánh dấu*` / Drafter UNSET chỉ-ByCcm→clear cả 2 / Drafter+Procurement-no-DM UNSET→clear CẢ 2 qua isDrafter (isDrafter ưu tiên/độc lập isProTp). No prod bug.
## S77c — urgent-toggle SYMMETRIC→ASYMMETRIC CORRECTION
anh chốt FINAL gate BẤT-ĐỐI-XỨNG theo `request.IsUrgent`; em main sửa prod `PurchaseEvaluationUrgentFeatures.cs:49-55` `isPro = IsUrgent ? hasPro : (hasPro && isDeptManager)` (+isCcm). REWRITE `PeUrgentToggleAuthzTests.cs` 12→11: **SET = role-chức-năng-đủ** (plain Procurement/CostControl SET→OK; bỏ DeptManager-restriction) / **UNSET = role AND DeptManager** (plain-PRO/CCM UNSET→Forbidden+no-mutate; PRO+DM/CCM+DM UNSET→clear-own preserve-other; Admin→both) / Drafter/Finance SET→Forbidden / UnknownPe→NotFound. No prod touch — test theo CODE (S34).
## S77 — PE Mig 57 suggested-price NOTE (test-after, mirror CcmNote), +7
`UpdatePeSuggestedPricePro/CcmCommand` +`Note` (string?) absolute-set→`Pro/CcmSuggestedPriceNote` (incl null=clear); rides SAME role-gate as price (PRO/Admin · CCM/Admin) fail-closed TRƯỚC side-effect; validator `Note.MaximumLength(1000)`. Handler ctor 2-dep + record `Note=null` default → 5 existing call-sites still compile, KHÔNG cần vá arity. NEW 7 → `PeSuggestedPriceSetterAuthzTests.cs`: PRO-note+Min/Max persist (note KHÔNG clobber price) / Admin-PRO / CCM-note+price / null-clear PRO (pre-seed note→null, AsNoTracking re-read) / null-clear CCM / non-priv→Forbidden+unchanged / PRO↔CCM independent + validator 1001-invalid·1000-valid. No prod bug.
## S76 — PE Mig 56 PRO-column-split (spec-change + compile-fix arity), +5
`UpdatePeBudgetProCommand` record 3→4 param `(PeId, ProInitialAmount, ProAdjustmentAmount, ProNote)`; handler set ProInitial+ProAdjust absolute-set (KHÔNG còn ProEstimateAmount). Validator ProInitial>=0, ProAdjustment ÂM OK, ProNote max1000. Handler ctor 2-dep UNCHANGED. Capability `FullAmount` (PurchaseEvaluationFeatures.cs:849-864): hasCcm?CCM:proFull(ProInitial+ProAdjust); FullIsEstimate=!hasCcm&&hasPro; DTO +2 field. Compile-fix 5 PRO call-site +arg ProAdjust + assert `.ProEstimateAmount``.ProInitialAmount` + full-fallback seed 500→ProInitial. NEW 5: PRO set both ÂM-adjust persist / validator ProInitial<0 invalid / ProAdjust ÂM valid / full=proFull(150) / full=ÂM-adjust(70 no-clamp). No prod bug ProEstimateAmount=LEGACY backfillProInitial.
## S74 — PE Mig 55 CcmNote (compile-fix arity + test-after), +5
`PeWorkItemBudget.CcmNote` (nvarchar1000) + `UpdatePeBudgetCcmCommand` record 34 `(PeId,Initial,Adjustment,CcmNote)` + DTO +CcmNote. Handler ctor 2-dep UNCHANGED. Compile-fix 3 call-site (PeWorkItemBudgetTests 388/407/421) +`null` arg4. NEW 5 (region 4b mirror ProNote): CostControl/Admin setpersist / null=clear (pre-seed prove) / ProcurementForbidden+no-mutate (role-gate 152-157 TRƯỚC side-effect, AsNoTracking re-read) / all-persist-together. No prod bug.
## S72 — PE Mig 54 anh Kiệt FDC (spec-change + 2 test-after FINANCIAL), +28
Mig 54 PE fields: ProSuggestedMin/MaxPrice + CcmSuggestedPrice + ApprovedPriceAmount + ApprovedPriceSource; TransitionAsync/ApproveV2Async +3 param finalizeByCcmDelegation(bool)+approvedPriceAmount(decimal?)+approvedPriceSource(string?). ** CCM-finalize AUTOOPT-IN UPDATE `PeCcmThresholdFinalizeTests` 611:** chỉ finalize khi flag=true + fail-closed order (832-851: threshold-nullConflict / roleCostControlForbidden / winnerQuoteTotal>=ceoThreshold strict-`<`→Conflict) → ApplyApprovedPriceOnFinalize(human price BẮT BUỘC)→DaDuyet. **② NEW `PeApprovedPriceFinalizeTests` 10 — ApplyApprovedPriceOnFinalize private-static:** valid-price→bind / null-price→Conflict NOT-finalized / garbage-source→Conflict / Theory 4 source {Ncc,ProMin,ProMax,Ccm} / **isSystem null-price→no-throw qua REFLECTION** (⚠OBSERVATION: isSystem KHÔNG reachable qua public ApproveV2Async — approve gate decision==Approve vs isSystem cần AutoApprove; PE no SLA-job → defensive/dead, test UNIT). Reflection unwrap TargetInvocationException→inner. **③ NEW `PeSuggestedPriceSetterAuthzTests` 13:** PRO/CCM setter handler 2-dep authz + validator + NotFound-TRƯỚC-authz. No prod bug — OPT-IN an-toàn-hơn AUTO.
## S69b — PE 2 feature anh Kiệt FDC (test-before-merge SECURITY+FINANCIAL), +14
**FEATURE B value-threshold CCM-finalize (`PeCcmThresholdFinalizeTests.cs` 5, ApproveV2Async 816-854):** CCM duyệt + ceoThreshold!=null + `winnerQuoteTotal < ngưỡng` STRICT-less-than (838) + chưa-slot-cuối → DaDuyet skip-CEO. Cover: <ngưỡngDaDuyet / ==ngưỡngadvance / >ngưỡng→advance / threshold-null→advance / non-CCM→advance / CCM-last-slot→DaDuyet via normal-advance no-double. **FEATURE A urgent-toggle authz (`PeUrgentToggleAuthzTests.cs` 9, 4-dep):** PRO→ByPro / CCM→ByCcm / Admin→both / else→Forbidden. multi-role PRO+CCM no-Admin→else-if short-circuit chỉ ByPro. unknown→NotFound. No prod bug (strict-`<` + else-if priority intentional). [superseded by S77c for urgent SET/UNSET asymmetry]
## S69 — Office golive permission-seed regression (test-after SECURITY), +6
`OfficeModulePermissionSeedTests.cs` mirror HrmProfilePermissionSeedTests reflection (2 private-static revoke→seed via GetMethod NonPublic|Static; MenuItem TRƯỚC Permission FK Cascade). KHÁC HRM: Office grant Read AND Create trên 16-key allow-list. Load-bearing: excluded-3 (OffPhongHopManage/OffAttendanceReport/OffChamCong) stay hidden + upgrade-only preserves admin-raised. No prod bug.
## S60 — UAT anh Kiệt 2 feature PE submit branch (test-after), +14
`PeSubmitGuardAndBypassTests.cs`. **F1 Section 3 guard (8):** submit branch build `missing` 4 mục → ConflictException prefix `'Chưa đủ thông tin mục 3 ...'` join `' · '`. Cover thiếu cả 4 / winner-only / quote=0 / budget / comparison / attachment-gắn-NCC(PES_Id!=null) KHÔNG đếm / đủ-manual / đủ-BudgetId. **F2 drafter-bypass (6, V2-only):** k=drafterSlots.Max(Order) → auto Cấp 1..k. ⚠GUARD-FIRST: bypass-test PHẢI dựng PE đủ 4 ĐK. Seed: SeedWinnerWithQuoteAsync / SeedComparisonAttachment(PES_Id=null) / SeedWorkflowAsync(Guid[][]). Opinion-only-ownSlot invariant. No prod bug.
## S57bis / S56 (notes kept)
- S57bis spec-drift: `NotEmpty()` on `Guid?` does NOT catch `Guid.Empty` (FV 7.2 treats default(Guid?)==null) → relies on create-handler FK-guard (defense-in-depth, no prod bug).
- S56 gotcha: `ExecuteUpdateAsync` BYPASS change tracker → stale-tracked-read needs `.AsNoTracking()` re-read; shipped tx = IsolationLevel.Serializable per em-main post-review.
## S54 — ItTicket reassign authz (test-before-merge SECURITY), +13
`ItTicketReassignAuthzTests.cs`. Pattern: authz-capability = seed 2-dept (IT+KT) + fake ICurrentUser role/dept matrix → CanReassign flag + Forbidden/Conflict guard + empty-staff 0-leak + no-mutation. Forbidden red-able BY-CONTRAST (non-IT vs IT-staff identical-setup). Data-driven handler-authz = enforcement point (KHÁC Pattern 10 reflection static `[Authorize]`). No prod bug.

2
.gitignore vendored
View File

@ -111,3 +111,5 @@ tmp/
# `fe-*/.claude/agent-memory/...`. Canonical agent-memory ở ROOT `.claude/` vẫn tracked # `fe-*/.claude/agent-memory/...`. Canonical agent-memory ở ROOT `.claude/` vẫn tracked
# (negation `!.claude/**` trên). Pattern AFTER negation → last-match-wins cho path FE-app. # (negation `!.claude/**` trên). Pattern AFTER negation → last-match-wins cho path FE-app.
fe-*/.claude/ fe-*/.claude/
backups/
*.bak

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,43 @@
# S91 (2026-06-26) — Go-live day: guide fix + wipe + seed-gate + user-change-password + backup local
**Mode:** HMW-mode ON · em-main-led (security-sensitive + #53 risk → KHÔNG fan-out lớn, báo-trước) · 4 sub spawn (tooling-auditor + harvest-curator @start · cicd-monitor + test-specialist trong feature).
**Trigger:** anh forward email anh Kiệt SOL-IT "THÔNG BÁO GO-LIVE Trình duyệt So sánh Giá — 26/06, Phase-01 Phòng Mua hàng (PRO) + Kiểm soát chi phí (CCM)". Sau đó: wipe data cũ ("múc nó phát em") → user-change-password ("user chưa tự đổi pass được hả em") → backup local ("sao lưu local tk các thứ để dùng lại").
## 4 commit prod-verified (+ backup local)
### #1 `159b69d` — Guide Word bỏ "Từ chối" + fix caption (docs-only, CI-skip)
Guide `docs/Huong-dan-Duyet-NCC.docx` (đã đính kèm email go-live, phát toàn công ty) còn ghi **"Từ chối" 2 chỗ** (Bước 4 + caption Hình 9) — app gỡ nút Từ chối từ S60, chỉ còn **Duyệt/Trả lại**. Caption Hình 9 còn **mâu thuẫn với ảnh nó chú thích** (ảnh chụp S90 = 2 nút, caption = 3). + 12 caption "Hinh"→"Hình" (thiếu dấu từ S90 ASCII-gen). Sửa qua skill docx **unpack→edit XML→repack** (giữ nguyên 13 ảnh: 1 logo+12 màn). Pack `--validate false` (validator dính bug cp1252 đọc UTF-8 VN, kể cả numbering.xml không sửa). Verify độc-lập **python-docx strict-open**: Từ chối=0, caption=12, media=13.
### #2 `ba42c59` — Wipe go-live `scripts/s91-wipe-golive.sql` (prod-executed)
anh Kiệt "xóa data cũ" trước go-live. Recon prod read-only → PE=16 · PeWorkItemBudgets=16 · Notifications=94 · Suppliers=26 [19 demo tháng-4 + 3 thật 06-09 + 4 thật 06-23] · Users=17 · Projects=70 · WorkItems=71. **AskUserQuestion ×2 chốt scope:** budgets→xóa hết · suppliers→GIỮ 7 thật (CreatedAt≥06-01), xóa 19 demo (`<06-01`). ⚠️ **S85 cắt theo 06-11 = SAI cho lần này** (4 NCC thật nằm SAU 06-11) → recon-before-change bắt; dùng mốc 06-01 (demo toàn tháng-4, thật toàn tháng-6). **Backup bước-0** `SolutionErp_S91_pregolivewipe.bak` (XACT_ABORT + `-b` abort-trước-wipe). **2 guard atomic:** PE>16→ABORT [chống xóa phiếu thật mới tạo go-live-day] · Suppliers-deleted≠19→ROLLBACK. Kết quả: PE/budgets/notif/contracts→0, Suppliers→7, cascade child→0, PeSeq→0 (**phiếu thật đầu tiên = PE/2026/A/001**).
### #3 `55494ad` — Gate SeedDemoMasterData (root-cause: chống resurrect NCC demo)
**Phát hiện TRƯỚC khi deploy:** `SeedDemoMasterDataAsync` (DbInitializer.cs:111) chạy **UNGATED** (ngoài mọi `if (!demoSeedDisabled)`) + per-code idempotent → 18 NCC demo (codes thiếu sau wipe) sẽ TỰ Add lại mỗi restart/IIS-recycle → wipe KHÔNG bền (gotcha **#75**). Fix gate sau `DemoSeed:Disabled` (prod=true, mirror Contracts/PE/Workflow demo). Verify config-flow: prod `appsettings.Production.json` render từ `.example` mỗi deploy (KHÔNG file gitignored — gotcha **#76**), `.example` không có DemoSeed → fallback `appsettings.json Disabled=true` → gate hiệu-lực. Deploy → **verify demo_codes=0 qua 2 restart** (binary Last-Modified fresh + count THẬT từ prod, KHÔNG tin cicd-monitor garbled-return).
### #4 `e81e87f` — User tự đổi mật khẩu (BE + FE 2-app + 10 test)
anh Kiệt "user chưa tự đổi pass được hả em" → đúng (chỉ admin Reset hộ, KHÔNG self-service). AskUserQuestion → build cả 2 app. **BE:** `Auth/Commands/ChangePassword/ChangePasswordCommand` + `POST /api/auth/change-password [Authorize]` — lấy user qua `ICurrentUser.UserId`; `UserManager.ChangePasswordAsync` verify mật khẩu HIỆN TẠI (Identity); fail-`PasswordMismatch`→field `CurrentPassword` "Mật khẩu hiện tại không đúng."; rule ≥12 + khác-cũ (validator); vô hiệu refresh token sau đổi (mirror ResetPassword). **FE 2-app SHA-mirror:** `ChangePasswordDialog` (3 ô current/new/confirm + validate client) wire vào `TopBar` UserMenu ("Đổi mật khẩu" trên "Đăng xuất"). **Test:** +10 `ChangePasswordCommandTests` (đúng/sai pass-cũ keyed-field, <12 boundary, ==, chưa-auth/UserId-null/not-found, RefreshToken-cleared). **Bug self+cross-caught:** CS0104 `ValidationException` ambiguous (FluentValidation vs custom) em-main bắt qua build TRƯỚC test-specialist return (test-specialist độc-lập bắt CÙNG bug = 2-observer-converge). Full suite **422→431**.
### + Backup local (anh "sao lưu local tk các thứ để dùng lại")
`backups/SolutionErp_golive_clean_2026-06-26.bak` (21MB, full DB clean go-live state) + `backups/accounts.txt` (17 tk: email·tên·status·phòng·role, clean -W). `.gitignore` +`backups/`+`*.bak`. backups/ trong Dropbox sync cloud (flag anh).
## Cách chạy (em-main-led)
Browser-guide định chụp [#74 carry] Chrome-MCP screenshot **kẹt SPA-SignalR document_idle** (CDP capture/eval timeout) + browser chưa-login eoffice anh "à thôi tự check" defer. Security-feature (change-password) = em-main-led BE+FE (báo-TRƯỚC: cross-stack+security-sensitive+#53-risk) + test-specialist (10 test, return tốt) + build/test gate thay reviewer/cicd fan-out. **#53:** cicd-monitor return GARBLED (lạc sang memory-curate) em-main self-verify từ prod (suppliers=7, demo_codes=0, endpoint 401, binary timestamp).
## State THẬT (re-ground)
Mig **59** · 88 bảng · **431 test** (45D + 386I, +10 ChangePassword) · gotcha **76** (+#75 +#76) · menu 54 · bundle admin **`DgQyuG7f`**/user **`BmbQon23`** · prod **17 user / 7 NCC / 70 dự án / 71 hạng mục / 0 PE-HĐ** (clean go-live).
## Lessons
- **Wipe KHÔNG bền nếu không gate re-seed** (#75, `feedback_wipe_durability_check_reseed`): xóa data = triệu chứng; nguồn re-populate (DbInitializer seeder ungated per-code) gốc. TRƯỚC/SAU prod-wipe audit seeder. Root-cause>symptom (mirror #73/S85).
- **Deploy render prod-config từ `.example`** (#76): sửa config-gitignored = vô-tác-dụng; effective = appsettings.json ⊕ rendered-.example.
- **inspect-before-change prod**: S85 supplier-cut-date stale → recon thật bắt 4 NCC-thật-sau-mốc (mirror `feedback_prod_config_inspect_before_change`).
- **em-main-led security-feature**: báo-trước-khi-lệch HMW-auto-Workflow (mode-ON) hợp-lệ cho security-sensitive; build/test gate + test-specialist độc-lập = đủ rigor; #53 cicd garbled → self-verify-prod.
## NEXT
- **🔴 anh:** ⚠️ restart CLI · review guide docx Word · đóng 2 tab eoffice em mở · UAT đổi-mật-khẩu (avatar→Đổi mật khẩu).
- **🔴 em:** curate test-specialist L1 OVER (carry nhiều session) · monthly 2026-07-01 (re-tier mega-line STATUS/HANDOFF bloat · ef-core SKILL Mig→59 · docs/CLAUDE Budget-removed · 3 vocab-fork alias-map).
- **Pending product:** Section B cột Dự-án · Ngưỡng giá CEO Mig 54 Designer UAT · "C" chuyển phiếu→dự án chờ spec.
## §L closeout
- §L.a: 0 RCA hard (CS0104 + seed-resurrect đều CAUGHT pre-prod = gate hoạt động, không phải prod-bug). Wipe/deploy đúng quy-trình (backup+guard+verify).
- §L.b: (a) STATUS+HANDOFF +S91 ✓ · (c) sleep-check `last_sleep_at=2026-06-25` <7d skip · test-specialist L1 OVER carry-flag · (d) spawn-record: tooling-auditor[start/CLEAN-diff] · harvest-curator[start/CLEAN] · cicd-monitor[deploy-verify/return-GARBLED-#53em-main self-verify-prod] · test-specialist[+10 test/return-OK/bắt-CS0104] · (g) tooling: 0 skill/plugin mới · (h) User-Mark: 4 Active-High display-only, 0 governance-decision-mới = n-a.
- INFRA-ADOPTION: **N/A** (no AI_INFRA infra adoption pure product/ops + doc).

View File

@ -1280,6 +1280,24 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
3. **Crop:** bỏ chrome trình-duyệt top + taskbar bottom (top=92/bottom=72). Banner CDP **"Claude started debugging this browser"** hiện sau mỗi `navigate` (CDP re-attach), tự ẩn sau vài giây ảnh chụp ngay-sau-navigate cần crop top146; verify nhanh bằng **montage dải-trên 12 ảnh** đọc 1 lần. 3. **Crop:** bỏ chrome trình-duyệt top + taskbar bottom (top=92/bottom=72). Banner CDP **"Claude started debugging this browser"** hiện sau mỗi `navigate` (CDP re-attach), tự ẩn sau vài giây ảnh chụp ngay-sau-navigate cần crop top146; verify nhanh bằng **montage dải-trên 12 ảnh** đọc 1 lần.
4. **Chèn:** `python-docx` `cell...add_picture(width=Inches(6.0))` A4 content = **6.27"** nên 6.3" TRÀN; dùng 6.1". 4. **Chèn:** `python-docx` `cell...add_picture(width=Inches(6.0))` A4 content = **6.27"** nên 6.3" TRÀN; dùng 6.1".
---
### 75. Seed demo UNGATED + per-code idempotent → RE-ADD data đã wipe mỗi restart (wipe KHÔNG bền) (Session 91)
**Triệu chứng:** Sau wipe 18 NCC demo (`s91-wipe-golive.sql`) cho go-live sạch, định deploy `SeedDemoMasterDataAsync` (DbInitializer.cs:111) chạy NGOÀI mọi `if (!demoSeedDisabled)` + idempotent per-code (`existingSupplierCodes.Contains(s.Code) ? continue : Add`). Code 18 NCC demo giờ THIẾU (đã xóa) mỗi restart/IIS-recycle TỰ Add lại Suppliers 725, undo go-live clean.
**Cơ chế:** wipe = triệu chứng; nguồn re-populate (seed) gốc. Demo seed cố-ý-ungated (Production.json comment "KEEP SeedMasterData") để giữ data UAT nhưng go-live cần demo GONE. Khác user test (38): S89 đã gỡ KHỎI MẢNG `SeedDemoUsers` không resurrect.
**Fix (root-cause, `55494ad`):** gate `SeedDemoMasterDataAsync` sau `DemoSeed:Disabled` (=true prod, mirror Contracts/PE/Workflow demo đã gated). Prod skip wipe BỀN; Dev (false) vẫn seed local. **Verify:** deploy restart 2× `demo_codes=0` (binary Last-Modified fresh + count THẬT từ prod). **Quy tắc:** TRƯỚC/SAU mọi prod data-wipe audit DbInitializer seeder cho ungated per-code re-seed (resurrect). Liên-quan #73 / `feedback_root_cause_over_symptom`.
### 76. `deploy.yml` render `appsettings.Production.json` từ `.example` template mỗi deploy → prod config KHÔNG lấy từ file gitignored (Session 91)
**Triệu chứng:** Sửa `appsettings.Production.json` (gitignored) tưởng đổi config prod vô-tác-dụng; `git add` báo file ignored.
**Cơ chế:** `deploy.yml:146-154` mỗi deploy đọc `appsettings.Production.json.example` (tracked) `ConvertFrom-Json` set `DB_CONNECTION`+`JWT_SECRET` từ secrets **ghi đè** `C:\inetpub\...\appsettings.Production.json`. File local gitignored KHÔNG bao giờ deploy. Effective config = `appsettings.json` (tracked) rendered-Production.json (từ `.example`).
**Fix/Quy tắc:** đổi prod config = sửa `.example` (tracked) HOẶC `appsettings.json`, KHÔNG file local gitignored. `.example` KHÔNG `DemoSeed` prod fallback `appsettings.json DemoSeed:Disabled=true` (đúng fallback đã giữ Contracts/PE gated nhiều tháng gate #75 hiệu-lực prod).
**An toàn prod:** chỉ XEM/scroll/screenshot KHÔNG bấm nút mutate (Gửi duyệt/Duyệt/Trả lại/Tạo ). Mở `+ Thêm mới` (form `?mode=new`) KHÔNG tạo draft cho tới khi sửa field (auto-save on-edit). **An toàn prod:** chỉ XEM/scroll/screenshot KHÔNG bấm nút mutate (Gửi duyệt/Duyệt/Trả lại/Tạo ). Mở `+ Thêm mới` (form `?mode=new`) KHÔNG tạo draft cho tới khi sửa field (auto-save on-edit).
**References:** `$env:TEMP\guideshots\capture.ps1` (session-scratch) · `docs/Huong-dan-Duyet-NCC.docx`. Pattern reusable user-memory `pattern_prod_screenshot_to_docx`. **References:** `$env:TEMP\guideshots\capture.ps1` (session-scratch) · `docs/Huong-dan-Duyet-NCC.docx`. Pattern reusable user-memory `pattern_prod_screenshot_to_docx`.