- 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>
92 lines
24 KiB
Markdown
92 lines
24 KiB
Markdown
# 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 có 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.
|
||
- **SSH→PS quoting (S42 lesson):** nested bash→ssh→powershell 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 93→88). Narrative-93 is STALE pre-S61 — when commit touches no schema, 88 is correct, don't FAIL on 88↔93. 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.
|