# 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 55 `20260618105426_AddCcmNoteToPeWorkItemBudget`** (S74-bis Run#315 sha 8655ebf; PeWorkItemBudgets +`CcmNote nvarchar(1000) nullable` — 1 AddColumn no new table — VERIFIED APPLIED PROD: history-top==repo, sys.columns `nvarchar maxlen=2000 null=1` [2000=byte-len 1000char×2], tables88). Prev Mig 54 `AddPeSuggestedAndApprovedPrice` (S73 #313, 5 PE price cols) + 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 S86:** admin js `B0gboSAg` + css `C6tz9Bw5` · user js `DbDg7pM-` + css `Cz5W9rFn` (Run #328 sha 424131d BE-only PE-workflow-notify-block — bundle UNCHANGED-FROZEN from #327 [no FE-source change; hash-frozen on no-FE-change EXPECTED-OK per #69], index.html `Last-Modified` admin 08:26:20Z + user 08:27:13Z ADVANCED from #327's 08:00/08:01Z = real deploy 15:26-15:27VN rebuilt+copied FE + API recompiled+app-pool-recycled; real-JS 1,612,842b/1,517,219b vs fake-919b. Prev #327 same hashes via FE-list-restructure). _S85 Run #327 sha fa6654b FE-only PE-list-restructure inline-3panel-vs-overlay + `?expand=1` decoupled; index.html `Last-Modified` admin 2026-06-19T08:00:41Z + user 08:01:35Z = deploy 15:00-15:01VN; **BOTH JS+CSS rotate** [layout-touching restructure → new css chunk]; real-JS 1,612,842b/1,517,219b vs fake-control 919b/895b). Prev #326: admin js `DwXqn37C`/css `D1qzFQOK` · user js `COXMCv7E`/css `DggBL_MW` (BE-authz button-gate, only-JS-rotate css-frozen). Prev #325: admin js `BhnNMucS` · user js `B1VebpXc`. ⚠️ **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)

- _(S71 Run #308 sha=`ebd7e1c` PASS ~4m41s [FULL-STACK PE-urgent+CCM-threshold, **Mig53 AddPeUrgentAndCeoApprovalThreshold VERIFIED-APPLIED-PROD** 3-cols-sys-columns, history-top-advance-53, tables88-addcolumn-only, bundle-BOTH-rotate, endpoint-urgent-401-not-404, test306] → 4-axis full-stack pattern; FIFO-trimmed, full verbatim git `ebd7e1c`)_
- _(S84 Run #326 sha=`b5aa72d` PASS ~4m48s [CROSS-STACK NO-MIG, BE-AUTHZ-LOGIC-ONLY urgent-toggle asymmetric (SET=function-role PRO/CCM/Admin; UNSET=+DeptManager) + FE PeDetailTabs ×2-app button-gate + PeUrgentToggleAuthzTests rewritten, 4 files no-mig; **empty `git diff -- '*Migrations*'` = strongest no-schema signal**; Mig UNCHANGED-57 history-top NOT advance (correct for no-mig), tables88; bundle BOTH JS rotate DwXqn37C/COXMCv7E **CSS FROZEN** D1qzFQOK/DggBL_MW (button-gate reuse utilities); real-JS 1.6/1.5MB vs fake-919b/895b SPA-200-trap-by-size; LM 07:45/07:46z; test354-inferred CI-gate-IS-KEY-since-authz-changed; health-4x200] → NO-MIG 3-axis (CI-gate-test-pass KEY · Mig frozen history-top-NOT-advance · tables88) + bundle-both-js-rotate; contrast S81 has-mig advance; for authz-only trust test-gate not curl; FIFO-trimmed, full verbatim git `b5aa72d`)_

- **2026-06-19 S87 Run #329 (run_number 329, id=443) sha=`e823694` PASS ~4m50s (FE-ONLY, 10th + FINAL deploy session — `PeDetailTabs.tsx` ×2-app added amber banner for Trả-lại phiếu in READ-view: instructions to edit + re-submit. EXACTLY 2 files SHA256-identical. ZERO BE, ZERO migration. Local build PASSED both apps):** Push HEAD=`e823694` nothing-unpushed, subject "PE phieu Tra lai them banner huong dan gui duyet lai (che do Xem)". `git diff --name-only e823694~1 e823694` = exactly `fe-{admin,user}/src/components/pe/PeDetailTabs.tsx` — pure FE, no `*Persistence/Migrations*` touched (empty mig-diff = strongest no-schema signal). 2 `.tsx` non-ignored → full pipeline RAN. Pre-poll bundle baseline captured BEFORE poll (anti#3): admin JS `B0gboSAg`/css `C6tz9Bw5` + user JS `DbDg7pM-`/css `Cz5W9rFn` = matches prompt's prior #327-FE-baseline EXACTLY (=#328 was BE-only so FE stayed frozen ⟹ #327 hashes still live = clean baseline, deploy NOT yet shipped). GITEA_TOKEN absent → anon Gitea API worked (public repo). Run RUNNING at spawn (15:38:21, exact head_sha `e823694202` matched run#329 id=443; #328=`424131d`+#327=`fa6654b` both confirmed `success` in task list = prompt's stated prior runs). Poll-loop (background, foreground-sleep blocked Windows) iter6 status=success (created 15:38:21→success 15:43:11 ≈**4m50s**; iter1-5 running 45s-cadence; `updated_at` advanced each iter). **★ CI TEST GATE: both test proj run pre-build ⟹ status=success ⟹ test 354 passed (FE-only, NO test/BE change ⟹ 354 unchanged from #328). `conclusion` field empty (tasks terminal=`status:success`, trust success NOT log-confirmed numeric).** **★ MIG UNCHANGED PROD (sqlcmd-over-SSH ground-truth, captured PRE-poll AND re-confirmed POST-deploy): history-top STILL Mig 57 `20260619070051_AddPeSuggestedPriceNotes` == repo HEAD latest mig (`ls Migrations/*.cs` top), did NOT advance both queries (correct — no mig in commit) ✓. sys.tables=88 UNCHANGED both pre+post — FE-only no schema ✓.** **🔑★ BUNDLE BOTH ROTATE (FE 2-app PeDetailTabs real source change ⟹ EXPECTED per #69; verified AFTER status=success + STABLE 2nd-fetch identical-size no-transient per anti#3): admin JS ROTATE `B0gboSAg→BqKD3Y23` + css `C6tz9Bw5→BTszpA4r` ✓ + user JS ROTATE `DbDg7pM-→Cn-i349D` + css `Cz5W9rFn→CnWwt3Oc` ✓ (both css rotated — banner is layout/style-touching).** Asset reachable 200 + LARGE + STABLE: admin js 1,613,634b (fetch#1==fetch#2 identical) + user js 1,518,011b (fetch#1==fetch#2 identical) — both 2nd-fetch byte-stable ⟹ no mid-deploy transient. Control fake `/assets/index-ZZfakehash0.js`→200 size admin 919b + user 895b SPA-fallback ⟹ real JS shipped (gotcha #69 SPA-200-trap distinguished by SIZE not 200). index.html `Last-Modified` ADVANCED admin 08:41:48z + user 08:42:42z (=15:41-15:42VN deploy window, advanced from pre-deploy #327's 08:00/08:01z) ✓ — confirms deploy rebuilt+copied FE. Smoke **4×200**: api `/health/ready`+`/health/live` BOTH 200 + admin root + eoffice root. 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only). Behavioral Trả-lại banner = anh-Kiêt/UAT visual (em confirms in-app). **VERDICT PASS: green CI run#329 + test-gate-354-passed + Mig-UNCHANGED-57 (history-top NOT advance, confirmed pre+post) + tables88 + bundle BOTH-rotate (js+css both apps, real-size 1.6/1.5MB vs fake-919b, 2nd-fetch-stable) + LM-advanced-confirms-real-FE-ship + health 4×200. CONFIRMED LIVE BUNDLES for session-end docs: admin js `BqKD3Y23`/css `BTszpA4r` + user js `Cn-i349D`/css `CnWwt3Oc`. LESSON: FE-ONLY-NO-MIG = bundle-rotate-real-size-vs-fake (KEY ship-proof since FE-source changed, gotcha #69 rotation EXPECTED but real-ship distinguished by real-SIZE + LM-advanced + SHA256-identical-source NOT hash-delta) + 2-axis prod-verify (Mig-frozen/tables88 confirmed pre+post). 2nd-fetch byte-identical = best-practice stability check rules out mid-deploy transient (S50 lesson). Contrast S86-BE-only (bundle FROZEN) vs this S87-FE-only (bundle ROTATE both js+css) — both PASS, but FE-source-change IS observable via real-size+LM+SHA256-source, BE-only via test-gate+health-post-recycle. SESSION CLOSED 10/10 deploy all-PASS. TOOLING: bash POSIX curl index.html grep `/assets/index-…` + `-w size_download/http_code` + `-I last-modified`; poll background grep-until-TERMINAL then Monitor-until-loop wait; SSH→sqlcmd direct `-W -h -1` pw-inline (`vrapp`/path `C:\inetpub\solution-erp\api` ConnectionStrings.Default), grep `^[0-9]{14}_`+`TABLES=`; pre-poll baseline snapshot BEFORE status=success per anti#3. NEVER fixed code (READ-only).** Tag `[s87, run329, pass, fe-only, 10th-FINAL-deploy, pe-tra-lai-banner-PeDetailTabs-amber-read-view-edit-resubmit-instructions, 2-files-PeDetailTabs-x2app-SHA256-identical, empty-migrations-diff-no-schema, CI-test-gate-354-unchanged-FE-only-trust-success, mig-UNCHANGED-57-history-top-NOT-advance-pre-and-post, tables88-unchanged, bundle-BOTH-ROTATE-admin-B0gboSAg-to-BqKD3Y23-css-C6tz9Bw5-to-BTszpA4r-user-DbDg7pM-to-Cn-i349D-css-Cz5W9rFn-to-CnWwt3Oc, both-css-rotate-banner-layout-touching, real-JS-1613634-1518011-vs-fake-919-895-spa-200-trap-by-size, 2nd-fetch-byte-identical-no-transient-stability, LM-advanced-08-41-08-42z-from-pre-08-00-08-01z-real-FE-ship, health-4x200-ready-live-admin-eoffice, CONFIRMED-LIVE-BUNDLES-session-end-docs-admin-BqKD3Y23-BTszpA4r-user-Cn-i349D-CnWwt3Oc, FE-only-bundle-rotate-KEY-ship-proof-real-size-vs-fake, contrast-s86-BE-frozen-vs-s87-FE-rotate-both-pass, FE-source-change-observable-via-real-size-LM-sha256, session-closed-10of10-all-pass, anon-gitea-api, poll-background-iter6-4m50s, pre-poll-baseline-anti3]`.

- _(S86 Run #328 sha=`424131d` PASS ~4m48s [BE-ONLY 9th-deploy, `PurchaseEvaluationWorkflowService.LogTransitionAsync` notify-block toPhase==ChoDuyet→NotifyManyAsync current-level approvers, 1-file, empty-migrations-diff, CI-test-gate-354-KEY-since-BE-logic-changed-trust-success, Mig-UNCHANGED-57-history-NOT-advance, tables88, bundle-BOTH-hash-FROZEN B0gboSAg/DbDg7pM- css C6tz9Bw5/Cz5W9rFn same-as-predeploy-OK-per-#69, real-ship-proof=LM-advanced-08-26/08-27z+API-health-200-post-recycle NOT hash-delta, real-JS-1.6/1.5MB-vs-fake-919b, health-4x200] → BE-logic-only test-gate-is-load-bearing (not curl-verifiable) + 2-axis prod-verify; contrast S85-FE-rotated vs S86-BE-frozen both-PASS hash-delta-uninformative; FIFO-trimmed, full verbatim git `424131d`)_

- _(S85 Run #327 sha=`fa6654b` PASS ~4m47s [FE-ONLY 8th-deploy, PE-list-restructure inline-3panel-vs-overlay + `?expand=1` decoupled-from-id, 2-files-PurchaseEvaluationsListPage-SHA256-identical-5048fd3a, empty-migrations-diff, Mig-UNCHANGED-57-history-top-NOT-advance, tables88, bundle BOTH JS+CSS rotate B0gboSAg/DbDg7pM- css C6tz9Bw5/Cz5W9rFn (layout-touching → css-rotate; css-rotate-vs-frozen both-OK-#69-informational), real-JS-1.6/1.5MB-vs-fake-919b-spa-200-trap-by-size, LM-08-00/08-01z-advanced, test354-inferred, health-4x200] → FE-only 2-axis (bundle-rotate-real-size-vs-fake + Mig-frozen/tables88); real-ship-proof=SHA256-identical-files+real-size NOT hash-delta; FIFO-trimmed, full verbatim git `fa6654b`)_

- _(S81 Run #323 sha=`94e0e12` PASS ~4m53s [FULL-STACK HAS-MIGRATION Mig 57 `AddPeSuggestedPriceNotes` — PE +2 note-cols ProSuggestedPriceNote+CcmSuggestedPriceNote nvarchar(1000), VERIFIED-APPLIED-PROD history-top-advance-56→57 + both-cols-sys.columns-maxlen2000-null, NO-backfill-no-Sql-in-Up, tables88-addcolumn-only, bundle-BOTH-js-rotate CPm4LTqm/BWJUAqEI css-FROZEN-note-fields-reuse-utilities, real-JS-1.6/1.5MB-vs-fake-919b, Last-Modified-07-12/07-13z, test351-plus7-PeSuggestedPriceSetterAuthzTests, deploy22/22, pre-deploy-Mig56-then-post-Mig57-unambiguous, gotcha70-peFetching-guard, 4-axis-minus-5th-backfill-vs-s76] → has-mig 4-axis (BE-js-rotate · history-advance+N-col-type-verify · tables88 · health-4x200); FIFO-trimmed, full verbatim git `94e0e12`)_

- _(S80 Run #322 sha=`3b98845` PASS ~4m41s [FE-ONLY REVERT 2-file PE-list layout-revert `max-w-5xl`→`grid-cols-[400px_1fr_360px]` 3-panel bám-trái + focus-overlay-kept, 2-PE-pages-SHA256-identical `d689fcd`, NO-mig-NO-BE empty-diff, bundle-BOTH-js+css-rotate a-fg9XMt/X0D56bXM css D1qzFQOK/DggBL_MW (layout-revert real-style both), real-JS-1.6/1.5MB-vs-fake-919b, Last-Modified-06-56/06-57z, Mig56-UNCHANGED-prod-history-still-56-NOT-advance, tables88, health-4x200, test344, deploy21/21, REVERT-verifies-same-as-forward-direction-irrelevant] → FE-only 2-axis; FIFO-trimmed, full verbatim git `3b98845`)_

- _(S79 Run #321 sha=`398b01d` PASS ~4m40s [FE-ONLY 4-file BOTH-app focus-mode overlay full-bleed slide-right hide-menu+list, 2-PE-pages-SHA256-identical, NO-mig-NO-BE, bundle-BOTH-js+css-rotate BD7a0lRK/DuIaUe_X css BqU8lEbO/CtcXFEs4 (overlay=real-style both apps), real-JS-1.6MB/1.5MB-vs-fake-919b, Last-Modified-06-31/06-32z-deploy-window, Mig56-UNCHANGED-prod-history-still-56-NOT-advance, tables88, health-4x200, test344, deploy20/20, real-FE-ship-proof=`git diff fe-*/src` not hash-delta gotcha#69] → FIFO-trimmed, full verbatim git `398b01d`)_

- **2026-06-19 S76 Run #318 (run_number 318, id=432) sha=`e33481e` PASS ~4m58s (FULL-STACK: BE Mig 56 `AddProBudgetSplitToPeWorkItemBudget` — PE ngân-sách MA-TRẬN 3 cột Dự-án|PRO|CCM, PRO split số ban-hành + điều-chỉnh + badge quyền NS theo role; anh Kiệt FDC continuation Mig 50/53/54/55. 19 files: BE 9 {Controller, App ApprovalWorkflowV2AdminFeatures+PurchaseEvaluationDtos+PeWorkItemBudgetFeatures+PurchaseEvaluationFeatures, Domain PeWorkItemBudget.cs, Config PeWorkItemBudgetConfiguration} + 3 Mig-file + FE 6 {PeDetailTabs+PeWorkflowPanel+types ×2-app, admin +ApprovalWorkflowsV2Page} + 1 test PeWorkItemBudgetTests + .gitignore. deploy 17/17 session after #297–#315 all PASS):** Push HEAD=`e33481e` (nothing unpushed). `git diff --name-only e33481e~1 e33481e -- '*Persistence/Migrations*'` = 3 files (Mig 56 .cs/.Designer.cs/Snapshot) — REAL EF mig. Mig56 Up() read = **2× `AddColumn<decimal>`: `ProAdjustmentAmount` + `ProInitialAmount` both decimal(18,2) precision18 scale2 nullable** + `migrationBuilder.Sql(UPDATE PeWorkItemBudgets SET ProInitialAmount=ProEstimateAmount WHERE ProEstimateAmount IS NOT NULL AND ProInitialAmount IS NULL)` data-migrate (phiếu cũ 1-cột giữ số PRO ở cột ban-hành); Down() 2× DropColumn — matches spec exactly, AddColumn-only no new table. `.cs/.tsx` non-ignored → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW BOTH absent env → anon Gitea API (worked, public repo) + DB pw read prod `appsettings.Production.json`→`ConnectionStrings.Default` (`vrapp`/`buKL3TGBkD0wDDbYVw65QeX9`, `.\SQLEXPRESS`/`SolutionErp`, path `C:\inetpub\solution-erp\api`). Run IN-PROGRESS at spawn (running 11:03:00, exact head_sha match #318 deterministic; NOTE prompt said "Run #316" but #316=CANCELLED `ae957c4` — actual run for `e33481e` = #318 matched by head_sha NOT run_number). **★ PRE-DEPLOY DB SNAPSHOT captured BEFORE poll-loop (proves deploy hadn't shipped): prod history-top = Mig 55 `AddCcmNoteToPeWorkItemBudget`, ProInitial+ProAdjustment cols ABSENT (only ProEstimateAmount present), tables88.** Pre-poll bundle baseline (anti#3): admin `BYF5vIMJ`/css `X_45M1jX` + user `CB-tiRxd`/css `Bbbo0M5H` = still #315 live. Poll-loop iter6 status=success (created 11:03:00→success 11:07:58 ≈4m58s; iter1-5 running). CI gate (both proj pre-build ⟹ status=success ⟹ test **344** expected (45D+299I; +1 PeWorkItemBudgetTests vs #315's 339) passed; `conclusion` empty — `tasks` terminal=`status:success` doesn't populate conclusion, trust success; 344 INFERRED gate-passes-pre-build NOT log-confirmed numeric). **🔑★ MIG 56 VERIFIED APPLIED PROD (sqlcmd-over-SSH ground-truth, POST-deploy re-query): history-top advanced Mig55→Mig56 `20260619024427_AddProBudgetSplitToPeWorkItemBudget` == repo HEAD ✓ (DbInitializer auto-migrate-on-boot ran on app-pool recycle). BOTH cols EXIST sys.columns: `ProAdjustmentAmount` decimal len9 prec18 scale2 null=1 ✓ + `ProInitialAmount` decimal len9 prec18 scale2 null=1 ✓ — match Mig 56 spec. sys.tables=88 UNCHANGED — 2 AddColumn no new table ✓. ★ DATA-MIGRATE VERIFIED: 4 rows `ProEstimateAmount IS NOT NULL AND ProInitialAmount=ProEstimateAmount` (backfill ran on prod data) + 0 violation rows `estimate-set-but-initial-null` ✓ — UPDATE-Sql executed on real prod data per gotcha #64.** **★ BUNDLE BOTH ROTATE (FE 2-app PeDetailTabs+PeWorkflowPanel + admin ApprovalWorkflowsV2Page ⟹ both EXPECTED per gotcha #69; verified AFTER status=success + STABLE 2nd-fetch no-transient per anti#3): admin JS ROTATE `BYF5vIMJ→BhFDF9IJ` + css `X_45M1jX→DuqjXB6Y`** ✓ (css rotated too — admin had real style change ApprovalWorkflowsV2Page) **+ user JS ROTATE `CB-tiRxd→BAkuRl3C` + css `Bbbo0M5H→JQfATaQB`** ✓. Asset reachable 200 + LARGE: admin js 1,603,616b + user js 1,507,842b (control fake `/assets/index-ZZfakehash0.js`→200 size 919b/895b SPA-fallback ⟹ real JS shipped, gotcha #69 SPA-200-trap distinguished by size). index.html `Last-Modified` admin 04:06:36Z + user 04:07:28Z (=11:06-11:07VN deploy window) ✓. Smoke **4×200** health: api `/health/ready`+`/health/live` + admin root + eoffice root. 0 regression. NO prod-data mutation (read-only curls + sqlcmd SELECT-only; mig-apply+backfill-UPDATE+FE-copy = expected boot/deploy side-effects). Behavioral PRO budget-matrix 3-col + role-badge = anh Kiệt UAT (em confirm Mig56+2cols+backfill+bundles shipped per spec). **VERDICT PASS: green CI + test 344 + Mig 56 applied (ProInitial+ProAdjustment decimal(18,2) sys.columns ground-truth, history advanced 55→56) + tables 88 + backfill 4-rows-0-violation + bundle BOTH rotate (css too) + health 4×200. LESSON: full-stack-2-AddColumn-WITH-DATA-MIGRATE = S74-bis 4-axis + 5th-axis BACKFILL-VERIFY (count rows where new-col=source-col AND 0 violation) — gotcha #64 prod-data-UPDATE-runs-first-time confirmed real. PRE-deploy snapshot at spawn (Mig55+cols-absent) → POST-deploy (Mig56+2cols+4-backfill) = unambiguous proof, best-practice capture-baseline-before-poll when run still building. admin css ALSO rotated (real ApprovalWorkflowsV2Page style) — distinguishes from S74-bis css-frozen (tab-logic-only). Mig-applied proof = `__EFMigrationsHistory` top==repo-HEAD; if stuck Mig 55 ⟹ 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; poll-loop backgrounded + grep-until FINAL; SSH→PS base64 `-EncodedCommand` UTF-16LE for appsettings-read AND sqlcmd; sqlcmd pw inline + `-W -h -1`. NEVER fixed code (READ-only).** Tag `[s76, run318, pass, full-stack-pe-budget-matrix-3col, mig56-AddProBudgetSplitToPeWorkItemBudget-VERIFIED-APPLIED-PROD, 2-cols-sys-columns-ProInitialAmount-ProAdjustmentAmount-decimal18-2-prec18-scale2-null, history-top-advance-55-to-56, tables88-unchanged-addcolumn-only, DATA-MIGRATE-backfill-4rows-ProInitial-eq-ProEstimate-0-violation-gotcha64-prod-data-update-first-time, bundle-BOTH-rotate-BhFDF9IJ-BAkuRl3C, css-BOTH-rotate-DuqjXB6Y-JQfATaQB-admin-ApprovalWorkflowsV2Page-real-style, asset-200-large-1.6MB-vs-fake-919b-spa-trap, last-modified-deploy-window-04-06-04-07z, health-4x200, test344-inferred-plus1-PeWorkItemBudgetTests, deploy17of17, pre-deploy-db-snapshot-mig55-then-post-mig56-unambiguous, prompt-said-316-but-actual-318-matched-by-head-sha-316-cancelled, anon-gitea-api-both-token-absent, behavioral-anh-kiet-uat, 5th-axis-backfill-verify-new]`.
- _(S74-bis Run #315 sha=`8655ebf` PASS ~4m54s [FULL-STACK Mig 55 `AddCcmNoteToPeWorkItemBudget` CcmNote nvarchar(1000) VERIFIED-APPLIED-PROD sys.columns maxlen2000-bytelen, history-advance-54→55, tables88-addcolumn-only, bundle-BOTH-rotate Bv3jUCNo→BYF5vIMJ/BWlMBQz6→CB-tiRxd css-frozen, asset-200-large-vs-fake-919b, health-4x200, test339, pre-deploy-snapshot-mig54-unambiguous, prompt-said-314-actual-315-head-sha] → 4-axis full-stack (BE+FE-both-rotate · history-advance+col-type-verify · tables88 · health-4x200); FIFO-trimmed, full verbatim git `8655ebf`)_
- _(S78 Run #320 sha=`8e68ed1` PASS ~4m46s [FE-ONLY 7-file PeUrgentChips PE-urgent-pill pro-red/ccm-blue list+inbox-all-surfaces, DTO-carries-isurgent-since-S69-Mig53 ⟹ NO-mig-NO-BE empty-diff, bundle-BOTH-JS-rotate DsSg6RRz/DGxI5U7A BOTH-css-FROZEN DyECY611/JQfATaQB (pill reuses utility classes no css chunk), real-JS-1.6/1.5MB-vs-fake-919b, mig-UNCHANGED-prod-still-56 tables88, health-ready+live-200 (bare-/health-404≠regression skill-routes), test344-unchanged] → FE-only 2-axis, no-mig-proof=history-unchanged-NOT-advance; FIFO-trimmed, full verbatim git `8e68ed1`)_
- _(S77 Run #319 sha=`21d1f4e` PASS ~4m46s [FE-ONLY 2-file PeDetailTabs PE-budget Block-A `<table>` grid-excel, NO-mig-NO-BE empty-diff, bundle-BOTH-JS-rotate jOqxW4-p/DbsznVvR + admin-css-ALSO-rotate DyECY611 (Block-A real style) user-css-frozen JQfATaQB (asymmetric-ok #69), real-JS-1.6/1.5MB-vs-fake-919b, health-4x200, test344-unchanged] → FE-only 2-axis; FIFO-trimmed, full verbatim git `21d1f4e`)_
- _(S74 #314 sha `6aa4dcb` FE-ONLY guard-PeWorkflowPanel no-mig-54 bundle-rotate health-4x200 + S69b #307 sha `1f8947e` BE-ONLY GOLIVE Office public Read+Create seed-16of16-across-13-roles bundle-FROZEN[seed≠mig] → lessons: FE-only skip-sqlcmd-when-0-mig · BE-only-seed CORE-proof = prod Permissions DB-query NOT bundle-frozen-alone [seed=runtime-row-insert no history/tables advance]; FIFO-trimmed, full verbatim git `6aa4dcb`/`1f8947e`)_
- _(S77 #303 sha `6983609`, S76 #302, S75/S74 … pre-S78 verbatim → git `764fe70` + archive — FIFO trimmed to keep L1 under soft-cap)_
- **2026-06-19 S83 Run #325 (id=439) sha=`e29391e` PASS ~4m39s (FE-ONLY tiny, 6th deploy session, NO-mig-NO-BE): 2 files `PeDetailTabs.tsx` ×2-app SHA256-identical `67b2a4da` (budget table sub-items indent + dash-prefix "– Ngân sách Ban hành lần đầu / – Giá trị kỳ này" to distinguish from numbered parent rows). diff ZERO Migrations/* → mig untouched. PRE-deploy snapshot (anti#3, before poll): admin `C6fx-0ea`/user `N3sW4Div` = EXACT #324 live = clean baseline + Mig57 `AddPeSuggestedPriceNotes` history-top + tables88. Poll iter5 status=success (`tasks` anon-API head_sha match, created 14:24:54→success 14:29:33 VN ≈**4m39s**; iter1-4 running). CI gate both-proj-pre-build⟹success⟹test **351** inferred (no test/BE Δ vs #324; conclusion empty per `tasks`-terminal-norm). POST-deploy: **bundle BOTH JS ROTATE** admin `C6fx-0ea→BhnNMucS` + user `N3sW4Div→B1VebpXc` (FE 2-app real Δ⟹both EXPECTED gotcha#69) verified AFTER success + STABLE 2nd-fetch identical (no mid-deploy transient). **CSS BOTH FROZEN** admin `D1qzFQOK`/user `DggBL_MW` (indent/dash reuses existing utility → no new css chunk; asymmetric js-rotate/css-frozen OK #69 — same signature as S81/S82 note/color). Real-JS admin 1,611,075b + user 1,515,457b vs FAKE-control `ZZfakehash0.js` 919b (SPA-200-trap distinguished by SIZE). index.html `Last-Modified` admin 07:28:14Z + user 07:29:09Z (=14:28-14:29VN deploy window, advanced from pre #324 07:12/07:13... wait pre was C6fx live 07:20). **Mig UNCHANGED prod: history-top still Mig57 `AddPeSuggestedPriceNotes` + tables88** (no boot-migrate side-effect, correct for no-mig commit; pre AND post both Mig57). Smoke **4×200**: api/health/ready+live + admin + eoffice. 0 regression, NO prod-data mutation (read-only). VERDICT PASS: green CI + test351 + bundle BOTH-js-rotate (css-frozen, indent/dash reuse utility) + real-1.6/1.5MB-vs-fake-919b + 2nd-fetch-stable + Mig-frozen-57 + tables88 + 4×200. LESSON: FE-only-styling-tweak 3-axis (BE-skip · bundle-both-js-rotate-css-frozen · mig-frozen) = identical pattern S82-color & S81-note MINUS BE/mig axis. css-frozen+js-rotate = EXPECTED for indent/dash/color/note tweaks reusing existing Tailwind utilities (no layout/grid Δ → no new css chunk) — distinguishes from layout-change runs (S79/S80) where css rotated too. 6th-deploy session ALL-PASS (#320?–#325 streak). TOOLING: ps1-file→base64-EncodedCommand UTF-16LE for SSH (appsettings-read + sqlcmd); poll foreground 12×45s grep-until-terminal; sqlcmd pw read prod appsettings→ConnectionStrings.Default (`C:\inetpub\solution-erp\api`). NEVER fixed code (READ-only).** Tag `[s83, run325, pass, fe-only-tiny-NO-mig-NO-BE, 2-PeDetailTabs-sha256-identical-67b2a4da, budget-subitem-indent-dash-prefix, bundle-BOTH-JS-rotate-BhnNMucS-B1VebpXc, css-BOTH-FROZEN-D1qzFQOK-DggBL_MW-indent-dash-reuse-utility, asymmetric-js-rotate-css-frozen-ok-gotcha69-same-as-s81-s82, real-JS-1.6MB-1.5MB-vs-fake-919b-spa-200-trap-by-size, 2nd-fetch-stable-no-transient-anti3, last-modified-07-28-07-29z-deploy-window-advanced, mig-UNCHANGED-57-AddPeSuggestedPriceNotes-history-top-pre-AND-post, tables88, test351-inferred-no-be-change, health-4x200, deploy-6th-session-all-pass, pre-deploy-baseline-EXACT-324-clean, 3-axis-fe-only-styling-tweak]`._
- _(S82 Run #324 sha=`e42d103` PASS ~4m45s [FE-ONLY tiny 5th-deploy, 2 PeDetailTabs sha256-identical `45580b6` budget-neg-amt red-paren `fmtVndSigned`, NO-mig-NO-BE, bundle-BOTH-JS-rotate C6fx-0ea/N3sW4Div, css-BOTH-FROZEN D1qzFQOK/DggBL_MW color-class-reuse-utility, real-JS-1.6/1.5MB-vs-fake-919b, Last-Modified-07-20/07-21z, Mig-UNCHANGED-57 tables88, health-4x200, test351, pre-deploy-baseline-EXACT-323-clean, 3-axis-fe-only-color] → FIFO-trimmed, full verbatim git `e42d103`)_
- _(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`)_
- _(S73 Run #313 sha=`1d86abc` PASS ~5m22s [FULL-STACK Mig 54 `AddPeSuggestedAndApprovedPrice` PE giá-đề-xuất PRO/CCM + CEO chọn giá-chốt + CCM duyệt-done; **5 cols VERIFIED-APPLIED-PROD sys.columns** ProSuggestedMin/Max+CcmSuggested+ApprovedPriceAmount decimal(18,2)+ApprovedPriceSource nvarchar all-null, history-advance-53→54, tables88-addcolumn-only, bundle-BOTH-rotate fc_xkNpJ→OlNyG9OD/DP-tBcg0→DSzSLVtL +css, asset-200-large-vs-fake-919b, **NEW-ENDPOINT-PROBE PUT /suggested-price/pro unauth→401-NOT-404 route-exists class-Authorize** +ccm→401 +list→401, health-4x200, test334, prompt-said-N-actual-313-head-sha] → 4-axis full-stack + endpoint-401-not-404-probe; FIFO-trimmed, full verbatim git `1d86abc`)_
- _(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.
