Files
solution-erp/.claude/agent-memory/cicd-monitor/MEMORY.md
pqhuy1987 e7e99d10f2 [CLAUDE] Docs: S73 closeout — Mig 54 PE gia de xuat + CCM duyet-done (STATUS/HANDOFF/session-log + review run-trace + agent-memory harvest)
- 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 <noreply@anthropic.com>
2026-06-18 16:32:41 +07:00

92 lines
24 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CI/CD Monitor Agent — Persistent Memory
> **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` + `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.
---
## 🎯 Role baseline
Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, verifies test gate + deploy ship + prod health. Tools: Read, Grep, Glob, Bash, WebFetch + 5 RAG MCP. Output: PASS/FAIL + evidence <500 words. Skills: `iis-deploy-runbook` + `dependency-audit-erp` + `ef-core-migration`. Spawn ~150K trade-off catch fail tự động.
---
## 🚨 Recurring CI/CD bug patterns (catch priority)
- **#39 act_runner github.com TCP timeout** run hang "Set up job" 21s. Log `dial tcp github.com:443 i/o timeout`. Fix: manual checkout bypass hardcoded `.gitea/workflows/deploy.yml` (pass #110). KHÔNG revert.
- **#40 npm cache `tsc not found`** `build_fe_admin` fail post `cache: npm`. DISABLED rolled back `a21790d`. KHÔNG re-enable.
- **#41 paths-ignore docs-only skip** code commit không trigger CI? Check `git diff --name-only HEAD~1 HEAD` vs `paths-ignore: ['docs/**','**/*.md','.claude/skills/**']`. Discovery #3: Gitea evaluates push *range* commits nếu 1 commit non-ignored file toàn range build (BENEFICIAL).
- **#25 IIS WebSocket** `notification-hub/negotiate` 401/404 prod. Fix: WebSocket module enable `web.config` site api (skill `iis-deploy-runbook`).
- **#48 SQLite tie-break** `OrderByDescending(CreatedAt).First()` pick wrong khi 2+ `.Add()` cùng frozen-clock. Fix: discriminator filter `.Where(Summary.Contains("Chuyển phase"))` BEFORE OrderBy.
- **Bundle hash unchanged = ship FAIL** push+action success nhưng prod không đổi. Verify via INDEX.HTML ref (`curl -s https://admin.solutions.com.vn/ | grep -oE '/assets/index-[A-Za-z0-9_-]+\.(js|css)'`), NOT by GETting a hash-named asset directly. Fix: SSH `Restart-WebAppPool`. Bundle hash verify MUST sau status=success (Run #242 false-positive lesson: check khi "running" stale hash).
- **🔴 #69 (S72 Run #312 OVERTURNS prior invariant) FE bundle hash is NON-DETERMINISTIC per rebuild. `deploy.yml` `Remove-Item fe-{admin,user}\* -Exclude web.config` + `Copy-Item dist\*` runs UNCONDITIONALLY every run (path-filter gates whole-workflow trigger, NOT per-step). Identical FE source DIFFERENT hash each deploy (Vite/rolldown non-reproducible chunk-id). PROVEN: governance-only `18fced6` (0 files in fe-*/src) rotated BOTH bundles admin `BgNCjwsG→fc_xkNpJ` (+717b) + user `CBvh0vtf→DP-tBcg0`, index.html `Last-Modified` matched deploy window. "BE-only/governance bundle MUST stay frozen" is FALSE; rotation is EXPECTED on every deploy. Bundle-rotate alone is NOT proof of FE content change to detect real FE change, diff `fe-*/src` in commit/range, NOT hash delta.**
- **⚠ SPA-fallback 200 trap (S72):** server rewrites `/*``/index.html` so GET `/assets/index-<ANYTHING>.js` returns **200** even for non-existent/fake hash (control `ZZdoesnotexist0.js`200 confirmed). Old-hash-still-200 is MEANINGLESS as persistence signal. RELIABLE bundle signal = parse index.html `<script src>`/`<link href>`, then GET that exact path + check `size_download` large (not white-screen) + `Last-Modified` in deploy window.
- **Migration drift prod vs repo** compare `ls .../Persistence/Migrations/*.cs` vs `sqlcmd __EFMigrationsHistory`. Fix: check `Program.cs` `app.MigrateDatabase()` + app pool recycle.
---
## 📋 5-stage checklist (EVERY run)
- **Stage 0 RAG infra:** `Get-Service Qdrant` Running + `http://localhost:6333/healthz`. Collection `proj_solution_erp` (prefix `proj_*` 7 project Discovery #8).
- **Stage 1 Push+filter:** `git log -1 --format='%H %s'` + `git log origin/main..HEAD` empty + diff vs paths-ignore (docs-only SKIPPED-DOCS return).
- **Stage 2 Gitea poll** (max 10 iter × 60s): API `.../actions/tasks?limit=5` (NOT `/runs` 404). Match `head_sha`. task table `updated_at` stale ~2min (gotcha #46) cross-check VPS mtime.
- **Stage 3 Test gate:** baseline **130 PASS** (58 Domain + 72 Infra). Phase 9 UAT exception lower OK (`feedback_uat_skip_verify`).
- **Stage 4 Post-deploy** (if SUCCESS): auth login bearer (admin + nv.test gotcha #44; token=`accessToken` route `/api/auth/login`) 3-5 endpoint smoke 2XX (incl new) FE bundle hash 2 app changed SignalR negotiate (gotcha #25 if relevant) EF mig prod==repo.
- **Stage 4.6 (S29 CRITICAL):** sqlcmd seed sample verify post-deploy (NOT chỉ schema). `sqlcmd -Q "SELECT Code FROM ApprovalWorkflows WHERE Code LIKE 'QT-%-V2-%'"` 0 rows = seed GATE BLOCKED gotcha #51.
- Discovery #4: ASP.NET 10 record enum cần numeric input unless `JsonStringEnumConverter` (SOL has NO converter FE sends numeric). #5: sqlcmd ssh Windows-auth cần `\\\\SQLEXPRESS` 4-backslash. #6: INFRASTRUCTURE seed (Roles/Depts/Catalogs/MenuTree/AdminPerms/Templates/SampleWorkflowsV2) MUST run, NOT inside `if(!demoSeedDisabled)`; DEMO seed (DemoUsers/Contracts/PE) OK gated gotcha #51.
- **Stage 5 Report** PASS/FAIL + evidence + MEMORY update.
---
## ⚠️ Anti-patterns (DO NOT)
1. Push fix code READ only, escalate em main · 2. Speculate fail without log · 3. Skip post-deploy bundle hash (biggest catch) · 4. Skip MEMORY · 5. Poll forever (max 10 iter) · 6. Auto-rollback (escalate + recommend) · 7. Verify docs-only (SKIPPED-DOCS return ngay)
---
## 🧠 SOLUTION_ERP CI/CD essentials (S40 verified)
- **Gitea:** `git.baocaogiaoduc.vn/vietreport-admin/solution-erp` · workflow `.gitea/workflows/deploy.yml` · paths-ignore `['docs/**','**/*.md','.claude/skills/**']`
- **Prod:** api/admin/eoffice `.solutions.com.vn` · SSH `ssh vietreport-vps` (Administrator, id_ed25519) · IIS site phys paths (S42 verified): API `C:\inetpub\solution-erp\api` · admin `\fe-admin` · user `\fe-user` (3 sites Started). DB `.\SQLEXPRESS`/`SolutionErp`/`vrapp` SQL-auth. **Conn string key = `ConnectionStrings.Default` (NOT `DefaultConnection`!)** read pw from prod appsettings.Production.json when `$env:PROD_DB_PASSWORD` empty.
- **SSHPS quoting (S42 lesson):** nested bashsshpowershell mangles `$var`/`\"`. Use `iconv UTF-16LE | base64` `powershell -EncodedCommand $B64`. Single-quote literal paths.
- **Tests baseline:** **263 PASS** (S62 Run #286 sha 7926c21 spec; 45 Domain + 218 Infra em-main supplied; supersedes prev 228/240/256). CI gate runs both test projects BEFORE build/deploy status=success test gate passed (`tasks` endpoint reports terminal as `status:success`, `conclusion` field NOT populated). Local grep undercounts (Theory/InlineData) trust CI conclusion. Phase 9 UAT mode skip per chunk OK.
- **Mig latest repo:** **Mig 53 `20260617060207_AddPeUrgentAndCeoApprovalThreshold`** (S71 Run#308; PE +IsUrgentByPro/+IsUrgentByCcm bit-default-0 + ApprovalWorkflows +CeoApprovalThreshold decimal(18,2)-nullable, 3 AddColumn no new table VERIFIED APPLIED PROD). Prev Mig 52 `AddHoSoLinkToPurchaseEvaluation` (S65 PE HoSoLink hyperlink-NAS) + Mig 51 `AddDepartmentParentId` (S65 org-tree loose-Guid) + Mig 50 `ReplaceBudgetModuleWithPeWorkItemBudgets` (S61 net-reduce). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/` (53 mig .cs non-designer total). Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. Table-count: `sys.tables` (is_ms_shipped=0, excl mighist) = **88** (S62 Run #286 verified S61 Budget-replace DROPPED tables 9388). Narrative-93 is STALE pre-S61 when commit touches no schema, 88 is correct, don't FAIL on 8893. Always cross-ref COMMIT scope vs ambient count.
- **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM, gotcha #44 check)
- **Bundle hash live S72:** admin `fc_xkNpJ` + css `C2Zvnd2f` · user `DP-tBcg0` + css `1bS4xeUF` (Run #312 sha 18fced6; index.html `Last-Modified` 2026-06-18T07:07:49Z = deploy window). Prev S71 #308: admin `BgNCjwsG`/user `CBvh0vtf`. **PER #69 (S72): bundle hash ROTATES on EVERY deploy regardless of FE change (non-deterministic rebuild). So "live hash" value is only a SNAPSHOT for THIS run — do NOT treat it as a frozen-baseline to compare next run against.** To detect a real FE ship, diff `fe-*/src` in commit/range NOT hash delta. ASYMMETRIC-deploy "anomaly" framing (S66) is RETIRED by #69: both apps rebuilt+rotated every deploy, asymmetry impossible to read from hashes alone. S50 mid-deploy transient lesson still holds: re-confirm hash AFTER status=success ALWAYS (anti-pattern #3).
- **DB pw (S42, when `$PROD_DB_PASSWORD` empty):** `vrapp/buKL3TGBkD0wDDbYVw65QeX9` read from `C:\inetpub\solution-erp\api\appsettings.Production.json``ConnectionStrings.Default`. Skill-doc path `C:\inetpub\apps\SolutionErp\Api` is STALE real path `C:\inetpub\solution-erp\api`. sqlcmd over SSH works direct (no UTF-16 encode needed). sys-catalog string-concat queries hit collation conflict (`Latin1_General_CI_AS_KS_WS` vs `SQL_Latin1_General_CP1_CI_AS`) add `COLLATE DATABASE_DEFAULT` per concatenated column.
## 🔑 Critical config (flag commit nếu tái xuất)
Node CI `20.x` (`feedback_node_cicd`) · MediatR `12.4.1` (gotcha #1, flag `Version="14`) · Swashbuckle `6.9.0` (gotcha #2) · act_runner manual checkout (#39) · npm cache DISABLED (#40, flag `cache: npm`)
---
## 🎯 Per-NV admin opt-in wire — 10-point checklist (cumulative S22→S23)
Cross-ref `feedback_per_nv_permission_scope`. Per-NV/per-Level refactor MUST verify: 1 Domain field · 2 EF `HasDefaultValue(false)` · 3 Mig 3-file · 4 Service read · 5 Domain+App DTO mirror · 6 Designer FE checkbox · 7 AwLevelDto+ToDto · 8 CreateAwLevelInput+Update mutation · 9 **Lookup discrimination** (`FirstOrDefault` ADD `ApproverUserId==actorId` + admin fallback) · 10 **Controller body record count == Command record count**. Bug latency 2-3 days prod silent khi miss 9-10. Scan `grep -n "FirstOrDefault.*Order.*==" *.cs` after OR-of-N refactor.
## 📊 Run stats baseline
BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code / 0s docs-only**. >5min → escalate.
---
## 📅 Recent runs (FIFO — older → archive/git)
- _(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-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 `<script src>` parse for hash (NOT GET hash-asset directly — SPA-fallback 200-traps). NEVER fixed code (READ-only).** Tag `[s74, run314, pass, fe-only-pe-empty-candidates-guard, no-migration-mig-stays-54, bundle-BOTH-js-rotate-Bv3jUCNo-BWlMBQz6, css-unchanged-X_45M1jX-Bbbo0M5H, asset-200-large-1.5MB-vs-fake-919b-spa-trap, last-modified-deploy-window-09-25-09-26z, health-4x200, test334-inferred-fe-only, deploy15of15, no-db-query-needed-fe-only, anon-gitea-api-both-token-absent, behavioral-anh-kiet-uat]`.
- _(S69b Run #307 sha=`1f8947e` PASS ~4m33s [BE-ONLY GOLIVE security "Văn phòng số" public Read+Create — NEW `SeedAllRolesOfficeModulePermissionsAsync` boot-after-revoke, allow-list 16 Office key upgrade-only; **GOLIVE-DB-VERIFIED-16of16-across-13-roles** sqlcmd prod Permissions (ALLOW-16 R∧C all roles · EXCLUDED-3 only-Admin leak0 · HRM/Personal Drafter still-0 · admin-not-downgraded), bundle-BOTH-frozen Wt54PHYl/B99fMU6X (BE-only no-rotate), no-mig-top-mig52, tables88, test292] → LESSON: BE-only golive-seed verify CORE-proof = prod Permissions DB query NOT bundle-frozen alone (seed=runtime-row-insert NOT EF-mig → no history/tables advance); FIFO-trimmed, full verbatim git `1f8947e`)_
- _(S77 #303 sha `6983609`, S76 #302, S75/S74 … pre-S78 verbatim → git `764fe70` + archive — FIFO trimmed to keep L1 under soft-cap)_
- _(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`)_
- **2026-06-18 S73 Run #313 (run_number 313, id=427) sha=`1d86abc` PASS ~5m22s (FULL-STACK: BE Mig 54 PE giá-đề-xuất PRO/CCM + CEO chọn giá-chốt + CCM duyệt-done ô-tích, anh Kiệt FDC. 19 files: BE 9 {Controller +2 endpoint suggested-price/{pro,ccm} + ApprovedPrice in finalize, App PeSuggestedPriceFeatures.cs NEW, PurchaseEvaluationFeatures, Dtos, IPurchaseEvaluationWorkflowService, Domain PE, Config} + 3 Mig-file + Service PurchaseEvaluationWorkflowService + FE 6 {PeDetailTabs+PeWorkflowPanel+types ×2-app} + 3 test {PeSuggestedPriceSetterAuthzTests, PeApprovedPriceFinalizeTests, PeCcmThresholdFinalizeTests}. deploy 14/14 session after #297#312 all PASS):** Push HEAD=`1d86abc` (nothing unpushed). `git diff --name-only 1d86abc~1 1d86abc -- '*Persistence/Migrations*'` = 3 files (Mig 54 .cs/.Designer.cs/Snapshot) — REAL EF migration. `.cs/.tsx` non-ignored → full pipeline RAN. GITEA_TOKEN + PROD_DB_PW BOTH absent env → anon Gitea API (worked, public repo) + DB pw from prod `appsettings.Production.json``ConnectionStrings.Default` (`vrapp`/`buKL3TGBkD0wDDbYVw65QeX9` len24, path `C:\inetpub\solution-erp\api`). Run IN-PROGRESS at spawn (running 15:51:47, exact head_sha match #313 deterministic). Poll-loop iter5 status=success (created 15:51:47→success 15:57:09 ≈5m22s). CI gate (both proj pre-deploy ⟹ status=success ⟹ test **334** baseline (45D+289I; +3 PeSuggestedPriceSetterAuthz+PeApprovedPriceFinalize+PeCcmThresholdFinalize) passed; `conclusion` empty — `tasks` terminal=`status:success` doesn't populate conclusion, trust success; 334 INFERRED gate-passes-pre-build NOT log-confirmed numeric). **🔑★ MIG 54 VERIFIED APPLIED PROD (sqlcmd over SSH ground-truth): prod `__EFMigrationsHistory` top5 = `20260618081758_AddPeSuggestedAndApprovedPrice`(54)→`AddPeUrgentAndCeoApprovalThreshold`(53)→`AddHoSoLinkToPurchaseEvaluation`(52)→`AddDepartmentParentId`(51)→`ReplaceBudgetModuleWithPeWorkItemBudgets`(50) == repo HEAD ✓ (DbInitializer auto-migrate-on-boot ran). ALL 5 NEW COLUMNS confirmed via sys.columns w/ types: `ProSuggestedMinPrice` decimal(18,2) null ✓ + `ProSuggestedMaxPrice` decimal(18,2) null ✓ + `CcmSuggestedPrice` decimal(18,2) null ✓ + `ApprovedPriceAmount` decimal(18,2) null ✓ + `ApprovedPriceSource` nvarchar null ✓ — match Mig 54 Up() spec. sys.tables=88 (excl mighist) UNCHANGED — 5 AddColumn no new table ✓.** **★ BUNDLE BOTH ROTATE (FE 2-app PE-UI change ⟹ both MUST rotate; verified AFTER status=success + STABLE 2nd-fetch no-transient per anti#3): admin ROTATE `fc_xkNpJ→OlNyG9OD`** ✓ (+css `C2Zvnd2f→X_45M1jX`) **+ user ROTATE `DP-tBcg0→DSzSLVtL`** ✓ (+css `1bS4xeUF→Bbbo0M5H`). Asset reachable 200 + LARGE: admin js 1,599,106b + user js 1,503,765b (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 08:55:32Z + user 08:56:33Z (=15:55VN deploy window) ✓. Smoke **4×200** health: api `/health/ready`+`/health/live` + admin root + eoffice root. **★ NEW ENDPOINT PROBE: `PUT /api/purchase-evaluations/{guid}/suggested-price/pro` unauth → 401 (NOT 404)** ✓ (route EXISTS controller line 102 `SetSuggestedPricePro→UpdatePeSuggestedPriceProCommand`, class-level `[Authorize]` enforced; PRO-only authz = unit-test PeSuggestedPriceSetterAuthzTests) + `/suggested-price/ccm` unauth → 401 ✓ + PE list unauth → 401 ✓ (all reachable+gated). 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only; mig-apply+FE-copy = expected boot/deploy side-effects). Behavioral PRO/CCM giá-đề-xuất + CEO chọn-giá + CCM duyệt-done = anh Kiệt UAT (em confirm Mig+5cols+endpoint+bundles shipped per spec). **VERDICT PASS: green CI + test 334 + Mig 54 applied (5 cols sys.columns ground-truth) + tables 88 + bundle BOTH rotate + new endpoint 401-not-404 + health 4×200. LESSON: full-stack PE w/ REAL Mig 54 = same 4-axis pattern as S71 #308 (BE+FE-both-rotate · Mig history-top advance + N cols sys.columns type-verify · sys.tables stays 88 AddColumn-only · new-endpoint 401-not-404 probe). Mig-applied proof = `__EFMigrationsHistory` top==repo-HEAD; if stuck Mig 53 ⟹ app pool didn't recycle ⟹ FAIL even if status=success+bundle-rotated. 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 — URI parse error); poll-loop backgrounded + Monitor grep-until FINAL; SSH→PS base64 `-EncodedCommand` UTF-16LE for appsettings-read AND sqlcmd; sqlcmd pw via here-string `$P=` + `[char]39` literal object-name quotes + `-W -h -1`; read DB pw from prod appsettings when both env empty. NEVER fixed code (READ-only).** Tag `[s73, run313, pass, full-stack-pe-suggested-price, mig54-AddPeSuggestedAndApprovedPrice-VERIFIED-APPLIED-PROD, 5-cols-sys-columns-confirmed-ProSuggestedMin/Max-CcmSuggested-decimal18-2-ApprovedPriceAmount-decimal-ApprovedPriceSource-nvarchar-all-null, history-top-advance-54, tables88-unchanged-addcolumn-only, bundle-BOTH-rotate-OlNyG9OD-DSzSLVtL, asset-200-large-1.5MB-vs-fake-919b-spa-trap, endpoint-suggested-price-pro-401-not-404-route-exists-ccm-401, pe-list-401-gated, health-4x200, test334-inferred, deploy14of14, no-regression, anon-gitea-api-both-token-absent, behavioral-anh-kiet-uat]`.
- _(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`)_
- _(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`.
---
## 🔄 Curate trigger
- >~30KB → archive recent runs → L2 `archive/<period>.md`. Dup failure patterns → merge. Stale >3mo → remove.
- **Last curate: 2026-06-17 S70 Harness-9 (em-main + Stage-B `wf_a58e0d15-beb`)** (65.2→23.2KB): L2 dark-matter recovery — 10 oldest run-records → `archive/2026-06.md` (ADDITIVE, original 58378B prefix byte-exact) + `archive/_INDEX.md` (mục-lục substring sha-keyed, Ctrl-F fallback, no line-hint) + `2026-0{5,6}.gist.md` (4-field, distill-gen:1). 0-byte-loss git `+13 -0` + sha (Stage C `wf_9520d8cd-4fe` + em-main self-gate, 2 reviewer no-return). Kept foundation + 2 newest full #308/#307 + stubs.
- **Prev curate: 2026-06-16 S66 em main** (86.8→29.2KB sed Run #286#232 incl #291 forensic) · S40 q4.
- **Prev curate: 2026-05-29 S40 em main proxy** (35.3→~21KB): archived Run #359/#243/#242/#241/#240 + S35/S36 startup → q4 + git d2f52ba; refreshed stale 120→130 test + Mig 34→40. Prev: S34 q3 · S32 q2 · S22 runs.