Files
solution-erp/.claude/agent-memory/cicd-monitor/MEMORY.md
pqhuy1987 5e6dcc1479 [CLAUDE] Docs: S63 closeout S60-62 — re-tier STATUS/HANDOFF + count-flush (Mig 50, 88 bang, 263 test, 64 gotcha) + reconcile stray reviewer + gotcha #63/#64
Viet bu docs cho S60/S61/S62 (ship code prod-verified nhung chua closeout — drift bat o /session-start S63 qua git log).

- Reconcile stray reviewer cwd-misland: MOVE 2 file con fe-admin/.claude -> canonical + pointer (no overwrite 31KB) + xoa stray

- Commit harvest S61/S62: cicd-monitor MEMORY (Run #286) + gotcha #63 (EF RenameColumn sai-semantics) + #64 (Design-DB vs Dev-DB data-migrate)

- Count-flush 4 file: Mig 49->50, tables 93->88, test 240->263 (45D+218I), gotcha 62->64, menu 57->53, Budget module REMOVED->PeWorkItemBudgets

- Session-log bu 2026-06-12-S60-S62-pe-budget-workitem-softwarning.md. Docs-only -> CI skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:41:47 +07:00

106 lines
62 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`.
---
## 🎯 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 `curl -s https://admin.solutions.com.vn/ | grep -oE '/assets/index-[a-z0-9]+\.js'`. Fix: SSH `Restart-WebAppPool`. Bundle hash verify MUST sau status=success (Run #242 false-positive lesson: check khi "running" stale hash).
- **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 50 `20260612173224_ReplaceBudgetModuleWithPeWorkItemBudgets`** (S61; DROPS old Budget module tables + adds PeWorkItemBudgets schema net-reduce). Prev Mig 49 `AddWorkItemToPurchaseEvaluation` + 48 AddProjectMasterFields. Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/` (50 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 S62:** admin `0xKYGhhf` · user `C81ZdG9G` (Run #286 sha 7926c21, ROTATED from S61 `DsGZlNzT`/`DTL_bjzQ` PE budget soft-warning allow-negative FE×2). Prev-prev S59 `B1DtNT9C`/`D6uF3Mln` (now 2 deploys stale). S50 mid-deploy transient lesson: pre-success snapshot can show intermediate FE copy in-flight 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)
- **2026-06-13 S62 Run #286 (run_number 286, id400) sha=`7926c21` PASS ~4m41s (PE "vượt ngân sách" → SOFT-WARNING: gỡ chặn số âm — CROSS-STACK 1 BE validator-rule-removal + 2 FE PeDetailTabs ×2 + 1 test flip, NO migration):** Push `79ef8da..7926c21` 4 files: BE `PurchaseEvaluationFeatures.cs` (gỡ 1 FluentValidation rule `ExpectedRemainingAmount >= 0` trong `AdjustPurchaseEvaluationBudgetCommandValidator`) + `PeDetailTabs.tsx` ×2 app (allowNegative row8 + banner "Vượt ngân sách") + `PeWorkItemBudgetTests.cs` (flip 1 test). `.cs`+`.tsx` → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json``ConnectionStrings.Default`. Run IN-PROGRESS first poll (running 11:15) — correctly did NOT FAIL/verify-bundle-mid-flight (anti-pattern #3), polled iter5 status=success (started 11:14:00 → success 11:18:41 ≈4m41s). CI gate (both test proj pre-deploy ⟹ status=success ⟹ test gate **263** baseline (45 Domain + 218 Infra) passed; `tasks` endpoint reports terminal as `status:success`, `conclusion` empty — trust success). **Bundle ROTATE BOTH (load-bearing FE×2, verified AFTER status=success +re-confirm STABLE no transient — anti-pattern#3): admin `DsGZlNzT→0xKYGhhf` + user `DTL_bjzQ→C81ZdG9G`** ✓ both touched (FE changed both apps). Title "Solutions ERP · Admin" preserved. Health live+ready **200/200** + admin/eoffice root 200. Smoke: PE unauth **401** (auth gate real) + control `/api/zzz-not-a-route` **404** (routing live, 401 not catch-all). **NO migration** — prod `__EFMigrationsHistory` top = `20260612173224_ReplaceBudgetModuleWithPeWorkItemBudgets` (= S61 "Mig 50", Budget→PeWorkItemBudgets replace) == repo HEAD, GIỮ NGUYÊN ✓ (commit 0 migration files; repo 50 mig .cs total). sys.tables(excl mighist)=**88** (S61 replace-mig DROPPED Budget tables 93→88; convention/count shift from S61 NOT this commit — FE+validator-only cannot alter schema). 0 regression. **LESSON: validator-rule-removal (negative-allow soft-warning) = internal handler-pipeline behavior — cannot curl-assert "now accepts negative ExpectedRemainingAmount" without authed multi-step adjust flow → rely on +flip PeWorkItemBudgetTests in CI gate 263 passing + bundle-rotate-both (FE banner shipped). Table count 88 (not 93) is S61 Budget-replace aftermath, not regression — always cross-ref what the COMMIT touched vs ambient schema state.** Tag `[s62, run286, pass, pe-budget-soft-warning, allow-negative, cross-stack, bundle-rotate-both, no-mig, test263, tables88-s61-aftermath]`.
- **2026-06-12 S60 Run #283 (run_number 283) sha=`37122f0` PASS ~5m (PE guard 4-thông-tin mục 3 khi gửi duyệt + bypass người-soạn-trong-chuỗi-duyệt + rename heading "Đơn vị NCC/TP được chọn" — CROSS-STACK 1 BE service + 2 FE PeDetailTabs ×2 + 1 NEW test file):** Push `792c030..37122f0` 7 files: `PurchaseEvaluationWorkflowService.cs` (BE submit-guard + drafter-bypass) + `PeDetailTabs.tsx` ×2 app + `PeSubmitGuardAndBypassTests.cs` (NEW, +14 → 240→**254** expected) + 3 agent-memory `.md` (harvest-curator/investigator-codebase/test-specialist — `.claude/agent-memory/**` matches `**/*.md` glob → ignored, but `.cs`+`.tsx` present ⟹ whole-range builds, Discovery #3). GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod `appsettings.Production.json``ConnectionStrings.Default`. Run IN-PROGRESS first poll (running 11:55) — correctly did NOT FAIL, polled iter5 status=success (started ~11:54 → success 11:59:26 ≈5m). CI gate (both test proj pre-deploy ⟹ status=success ⟹ test gate 254 passed; `tasks` endpoint reports terminal as `status:success`, `conclusion` empty — trust success). **Bundle ROTATE BOTH (load-bearing, verified AFTER status=success — anti-pattern#3): admin `B1DtNT9C→akytoBnc` + user `D6uF3Mln→BzSdQmN0`** ✓ both touched (FE changed both apps). Brand `1F7DC1` preserved both HTML. Health live+ready **200/200** + admin/eoffice root 200. Smoke: PE unauth **401** + contracts unauth **401** + control `/api/zzz-not-a-route` **404** (auth gates real, routing live). **NO migration** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo, GIỮ NGUYÊN ✓ (PE.MaPhieu col, not Code). sys.tables(excl mighist)=**92** (convention diff vs narrative-93, no new table — col/logic-only). **DATA INTACT (no-touch verify, sqlcmd): PE_count=3 · PE/2026/A/001 EXISTS (=1, phiếu UAT thật giữ nguyên ✓) · Suppliers=23 · WorkItems=71** — counts vs S59 (PE was 1 #275, Suppliers 22 #278) GREW from legit ongoing-UAT (this commit = FE+BE-service, NO DbInitializer/seed change → cannot resurrect/wipe; growth is user activity not deploy-induced). 0 regression. **LESSON (cross-stack submit-guard + drafter-bypass = ship-proof via run-success + test 254 + bundle-rotate-both + PE-data-preserved; the guard/bypass logic is internal handler behavior — cannot curl-assert "block submit when mục-3 incomplete" or "skip drafter in chain" without authed multi-step flow → rely on +14 PeSubmitGuardAndBypassTests in CI gate passing). SSH→sqlcmd via `iconv UTF-16LE|base64`→`powershell -EncodedCommand` (nested bash→ssh→PS strips `$vars`/mangles quotes); PE code column = `MaPhieu` NOT `Code`.** Tag `[s60, run283, pass, pe-submit-guard, drafter-bypass-in-chain, cross-stack, bundle-rotate-both, no-mig, test254, pe-a001-preserved]`. **↳đợt2 (14:14): Run #284 (run_number 284, id398) sha=`6db195d` PASS ~4m31s — GỠ hành động "Từ chối" khỏi quy trình PE (chỉ còn Duyệt/Trả lại; CROSS-STACK Domain `PurchaseEvaluationPolicy.cs` + Infra `PurchaseEvaluationWorkflowService.cs` guard + FE `PeWorkflowPanel.tsx` ×2 app + 2 NEW test `PurchaseEvaluationPolicyTests`/`PurchaseEvaluationWorkflowServiceGuardTests`, +2 → 254→256 expected: 59 Domain + 197 Infra). Push `37122f0..6db195d` 6 files (.cs+.tsx → full pipeline). Tokens empty → anon Gitea API + prod appsettings DB pw `ConnectionStrings.Default`. Run IN-PROGRESS first poll (running 14:31) — correctly did NOT FAIL, polled iter5 status=success (14:30:51→14:35:22 ≈4m31s; CI both-proj-pre-deploy ⟹ success ⟹ 256-gate passed, `conclusion` empty trust success). Bundle ROTATE BOTH (verified AFTER status=success — anti#3): admin `akytoBnc→DSvM8h3A` + user `BzSdQmN0→Cs2Tt5n6` ✓ both touched. Health live+ready 200/200 + admin/eoffice root 200 + PE unauth 401 + control /api/zzz-not-a-route 404. NO migration — prod top=Mig 49 `AddWorkItemToPurchaseEvaluation`==repo GIỮ ✓. sys.tables(excl mighist)=92. DATA INTACT: PE_count=4 (grew from 3 @#283 — legit ongoing-UAT; BE-policy+FE-only NO seed change → cannot resurrect/wipe) · PE/2026/A/001 EXISTS (=1 phiếu UAT thật giữ ✓). 0 regression. LESSON: "Từ chối"-removal = internal policy/handler behavior, cannot curl-assert "reject action gone" without authed multi-step flow → rely on +2 PolicyTests/GuardTests in CI gate passing. Tag `[s60-dot2, run284, pass, pe-remove-reject-action, cross-stack, bundle-rotate-both, no-mig, test256, pe-a001-preserved]`.**
- **2026-06-11 S59-CLOSE Run #280 (run_number 280) sha=`69997da` PASS ~4m24s (FINAL đóng sổ session — FE-only ×2 PeDetailTabs+PeHeaderForm bỏ ô "Tên ngân sách" manual budget UAT vòng4):** Push `f21c55d..69997da` 4 `.tsx` (PeDetailTabs+PeHeaderForm ×2 app). **Run #279 (id393) sha=`f21c55d` (NCC table-fixed UAT vòng3) = `cancelled` @18:22:33 — supersede-BENIGN:** #280 push @18:22:34 (1s gap → Gitea concurrency-cancel in-flight) + `git merge-base --is-ancestor f21c55d 69997da`=TRUE ✓ (f21c55d preserved trong HEAD, ships via #280 — verified diff f21c55d→69997da chỉ +4 PeDetail/Header file, không re-touch 12 file vòng3). Tokens empty → anon Gitea API + prod appsettings DB. Poll iter4 status=success (18:22:34→18:26:58). **Bundle ROTATE BOTH FINAL (verified AFTER success +re-confirm STABLE no transient — anti#3): admin `BSh2fG2X→BKy_8OO9` + user `D22KfpPc→XcZ6PRyA`** ✓ session-close hash, brand `1F7DC1`+"Solutions ERP" preserved ×2. Health live+ready **200/200** + admin/eoffice root 200 + PE unauth 401 + control 404. **NO migration** (FE-only, Mig 49 held). LESSON (mirror Run #385 supersede-chain): same-SHA `cancelled` mid-flight = concurrency-supersede bởi newer push (1s HEAD-move), KHÔNG build/deploy-fault → ancestor-check TRUE = benign, verify prod qua SUCCESSFUL run #280 (NOT cancelled #279), KHÔNG escalate. Tag `[s59-close, run280-pass, run279-cancelled-benign, supersede-chain, fe-budget-name-remove-x2, bundle-rotate-both-FINAL, no-mig]`. **↳FINAL-v2 (tối): `80b64dd` (Run #281 cancelled-BENIGN) gỡ "Điều khoản thanh toán" 3-form ×2 → superseded bởi `792c030` Run #282 PASS ~4m (UAT vòng6 bỏ nút "+Thêm hạng mục" PeDetailTabs ×2). Ancestor 80b64dd⊂792c030=TRUE ✓ (792c030 chỉ re-touch PeDetailTabs, KHÔNG đụng PeHeaderForm/PeWorkspaceCreateView → paymentTerms-removal survives). Verify qua #282-success. Bundle ROTATE BOTH ĐÓNG-SỔ-THẬT (AFTER success +re-confirm STABLE no transient): admin `BKy_8OO9→B1DtNT9C` + user `XcZ6PRyA→D6uF3Mln` ✓, brand `1F7DC1` ok. Health live+ready 200/200 + 2 FE root 200. NO mig (FE-only). Lần thứ 3 liên tiếp supersede-chain (#279/#281 cancelled-benign) — pattern stable.**
- **2026-06-11 Run #278 (run_number 278) sha=`9c330d2` PASS ~3m45s (S59-đợt6 CROSS-STACK — BE SuppliersController POST hạ `[Authorize(Roles="Admin,CatalogManager")]` → class-level `[Authorize]` any-auth (anh chốt quick-add NCC đi-thầu phát sinh liên tục), PUT/DELETE GIỮ khóa Admin+CatalogManager; FE×2 PeDetailTabs AddSupplierDialog SearchableSelect+quick-create+upload-multi + PeWorkflowPanel ẩn Trả-lại/Từ-chối khi drafterUserId==currentUser):** Push `faed59f..9c330d2` 5 files: 4 FE `.tsx` (PeDetailTabs+PeWorkflowPanel ×2 app) + `SuppliersController.cs`. `.cs`+`.tsx` → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon Gitea API + DB pw từ prod appsettings.Production.json→`ConnectionStrings.Default` (`vrapp/buKL3...`). Run IN-PROGRESS first poll (running) — polled iter6 status=success. **Bundle ROTATE BOTH (load-bearing FE×2, verified AFTER status=success +re-confirm stable ×2 NO transient — anti-pattern#3): admin `ex7Tc92G→BSh2fG2X` + user `DzUeSk96→D22KfpPc`** ✓ both touched. Brand `1F7DC1`+"Solutions ERP" preserved. Health live+ready **200/200** + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo ✓. **★ AUTHZ PROBE 4-điểm (the change-point — asymmetric POST-open/DELETE-locked verify) ALL PASS:** (a) unauth POST /api/suppliers (no token) = **401** ✓ (vẫn phải login, KHÔNG anonymous — class `[Authorize]` giữ); (b1) nv.test (Drafter non-admin) POST `{code:ZZCICD-TEST,type:1}` = **201** ✓ id `bc64c0c0-...` (quick-add mở OK); (b2) nv.test DELETE same-id CÙNG token = **403** ✓ (Sửa/Xóa vẫn khóa Admin+CatalogManager — method-level attr giữ); cleanup admin DELETE = **204** + GET = **404** ✓ (probe gỡ sạch). Spot sqlcmd ground-truth: **WorkItems active=71 HELD** ✓ (no resurrect) + **Suppliers active=22** ✓ (==pre-probe; DELETE là SOFT `IsDeleted=1` → active count về 22, total=23 với 1 tombstone ZZCICD `IsDeleted=1` — by-design audit, KHÔNG leak: list/GET ẩn nó). ⚠️ API `/api/suppliers` list trả 20 (paginated/filtered default page — KHÔNG authoritative, dùng sqlcmd cho count thật). Test gate (CI both proj pre-deploy ⟹ success=passed). 0 regression. **LESSON: cross-stack authz-relax verify = probe CẢ asymmetry — (i) unauth vẫn 401 (relax ≠ anonymous), (ii) target-role action mở (201), (iii) SIBLING action vẫn locked (403 same token), (iv) cleanup soft-delete → active-count về baseline + tombstone total+1 (soft-delete ≠ rác nếu list/GET ẩn). API list-count KHÔNG tin (pagination) → sqlcmd `WHERE IsDeleted=0` cho count thật.** Tag `[s59-dot6, run278, pass, supplier-post-authz-relax, asymmetric-probe-401-201-403, soft-delete-cleanup, bundle-rotate-both, no-mig, wi71-held]`.
- **2026-06-11 Run #277 (run_number 277) sha=`faed59f` PASS ~4m09s (S59-đợt5 FE-only ×2 — NEW ui/SearchableSelect combobox gõ-lọc-bỏ-dấu + PeWorkspaceCreateView/PeHeaderForm Hạng mục+Dự án combobox + auto-fill Địa điểm từ Project.Location + PeDetailTabs paymentTerms Textarea; UAT 4-điểm screenshot 16:40):** Push `c869d26..faed59f` 8 files all `.tsx` (4 fe-admin + 4 fe-user, NEW SearchableSelect mirror ×2) → pipeline RAN. Tokens empty → anon API + prod appsettings pw. Run IN-PROGRESS first poll (running 17:40) — polled iter5 status=success (17:40:18→17:44:27). **Bundle ROTATE BOTH (verified AFTER success +re-confirm stable NO transient — anti-pattern#3): admin `BBA0KSWu→ex7Tc92G` + user `DzdTI18G→DzUeSk96`** ✓ both apps touched. Brand `1F7DC1` preserved both HTML. Health live+ready **200/200** + admin/eoffice root 200. **NO migration** (FE-only). Spot DB: **WorkItems active=71 HELD** (==#276 ✓ no resurrect — FE-only no restart risk) + PE=1 (info-only UAT leftover unchanged, NOT regression). Test gate (CI both proj pre-deploy ⟹ success=passed). 0 regression. LESSON: FE-only follow-up of a data-session needs only light WorkItems=71+PE re-confirm (no infra re-audit). SSH→sqlcmd pw-read: nested bash→ssh→PS strips `$vars` → use `iconv UTF-16LE|base64``powershell -EncodedCommand`. Tag `[s59-dot5, run277, pass, fe-searchableselect-x2, bundle-rotate-both, no-mig, wi71-held]`.
- **2026-06-11 Run #276 (run_number 276) sha=`c869d26` PASS ~4m33s (S59-đợt4 FINAL — rename 71 WorkItems theo format PMH anh Kiệt FDC: BE DbInitializer seed-tuple mới MAT-n/SUB-n/MEP-SUB-n/MEP-EQU-n + FE×2 sort numeric, SQL rename ran TAY prod TRƯỚC push giữ Id):** Push `bbd1554..c869d26` 9 files: 6 FE (`PeHeaderForm`+`PeWorkspaceCreateView`+`PurchaseEvaluationsListPage` ×2 app) + `DbInitializer.cs` + `s59-rename-workitems-pmh.sql` + generated.md. `.cs`+`.tsx` → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon API + prod appsettings pw `ConnectionStrings.Default`. Run IN-PROGRESS first poll (running 17:14) — polled iter5 status=success (17:14:14→17:18:47). **Bundle ROTATE BOTH (3 FE file both apps, verified AFTER success +re-confirm stable NO transient — anti-pattern#3): admin `DuU7OTym→BBA0KSWu` + user `DWyeTzf3→DzdTI18G`** ✓ both reachable 200, brand `1F7DC1`+title "Solutions ERP" preserved. Health live+ready **200/200** + admin/eoffice root 200 + PE unauth 401 + control 404. **NO migration** — prod top=Mig 49 `AddWorkItemToPurchaseEvaluation`==repo ✓. **★ RISK-POINT CLEARED (app restart ran SeedRealMasterDataAsync w/ NEW tuples — rename SQL ALREADY took → seed found all 71 new codes exist → added 0, NOT 142): WorkItems=ĐÚNG 71** (142=FAIL averted) + **old VT-/TP-/TB-=0** ✓ + **new MAT-/SUB-/MEP-=71** ✓ + spot `MEP-SUB-1`="1 MEP Sub MEP (Full)" EXACT ✓ + sample MAT-1/SUB-1/MEP-SUB-9/MEP-EQU-16 all present. PE=1 (info-only, UAT leftover A/001 — not resurrect). Test gate 240 (CI both proj pre-deploy ⟹ success=passed). 0 regression. **LESSON: rename-via-prod-SQL + seed-tuple-change verify = (a) target count EXACT (double = idempotent-seed re-added because SQL rename didn't take), (b) old-prefix=0 + new-prefix=full-count, (c) spot Code→Name exact match. Idempotent UNGATED seed re-runs on every restart → if hand-SQL rename succeeded BEFORE deploy, seed finds new codes present → adds 0 (safe); if rename failed → seed ADDS duplicates = 2× count = critical FAIL.** Tag `[s59-dot4, run276, pass, workitems-rename-pmh, bundle-rotate-both, no-mig, count-71-no-dup]`.
- **2026-06-11 Run #275 (run_number 275) sha=`bbd1554` PASS ~3m44s (S59-đợt3 BE-only DbInitializer GỠ seed 15 WorkItems demo — chị Trà Sol chốt giữ đúng 71 mã PMH + `scripts/s59-wipe-demo-workitems.sql` ran TAY prod TRƯỚC push 86→71):** Push `0eafcd3..bbd1554` 2 files `DbInitializer.cs` + wipe-sql ONLY, NO FE/Mig. `.cs` → full pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon API + prod appsettings pw `ConnectionStrings.Default`. Run IN-PROGRESS first poll (running 16:57) — polled iter6 status=success (16:57:41→17:01:25). **Bundle FROZEN admin `DuU7OTym` + user `DWyeTzf3`** (== #274 UNCHANGED ✓ CORRECT BE-only — verified AFTER status=success, no FE leak). **NO migration** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo ✓. Health live+ready **200/200** + admin/eoffice root 200. **★ RESURRECT-VERIFY (risk point — app recycle post-deploy did NOT re-seed demo WorkItems, seed-block removal HELD): WorkItems=71 (ACTIVE=71 INACTIVE=0 DELETED=0 — hard-wipe not soft) + 4 categories EXACT: MEP 9 / Thầu phụ-Xây dựng 30 / Thiết bị 16 / Vật tư-Xây dựng 16** ✓ + **demo-codes DAO-MONG/SON-NUOC/VC-PHE-THAI = 0** ✓ (SeedRealMasterDataAsync idempotent re-ran, added 0 — 71 codes all exist). ⚠️ **PARTIAL item 5 — PE=1 không phải 0:** 1 row `PE/2026/A/001` Phase=10 created 09:46:42 **UTC = 16:46 chiều local** (CreatedAt lưu UTC — em main annotate S59-end kẻo đọc nhầm timeline; sau wipe Run #273 ~15:36 local, là phiếu UAT thật user tạo buổi test chiều, KHÔNG do deploy resurrect — BE-only bundle-frozen, DbInitializer KHÔNG seed transactional PE). INFO-flag, NOT deploy-fail. Test gate 240 (CI both proj pre-deploy ⟹ success=passed). 0 regression. **LESSON: seed-removal verify (mirror wipe-verify #273) = (a) target table count exact + category breakdown + demo-code spot-check 0 AFTER recycle (idempotent real-seed re-runs but adds 0), (b) ACTIVE/DELETED split proves hard-wipe vs soft-delete. PE=1 leftover-UAT ≠ regression: check CreatedAt timestamp vs wipe-time — pre-wipe-then-new-create is legit UAT activity, only assert PE=0 if no UAT happened between wipe and deploy.** Tag `[s59-dot3, run275, pass-partial, be-seed-removal, workitems-71-no-resurrect, bundle-frozen, no-mig, pe1-uat-leftover]`.
- **2026-06-11 Run #273 (run_number 273) sha=`56882ac` PASS ~3m34s (S59 FE×2 PE-list tree regroup "Dự án(Năm)→Hạng mục→Phiếu" bỏ tầng NCC + prod data WIPE):** Push `1577927..56882ac` 3 files: `PurchaseEvaluationsListPage.tsx` ×2 app (SHA256 mirror identical, Panel-1 tree drop NCC tier) + `scripts/s59-wipe-testing-data.sql` (ran TAY prod TRƯỚC push, NOT in pipeline). `.tsx` present → pipeline RAN. ⚠️ GITEA_TOKEN+PROD_DB_PASSWORD empty → anon Gitea API (works, public) + read DB pw from prod `appsettings.Production.json``ConnectionStrings.Default`. Run IN-PROGRESS first poll (running 15:36) — polled iter5 status=success (15:36:26→15:40:00). **Bundle ROTATE BOTH (load-bearing, verified AFTER success +6s stable NO transient — anti-pattern#3): admin `DMm9rtNA→R9uGRxvw` + user `BUkOMn_Y→DikfX1RD`** ✓ both apps touched. Brand `1F7DC1` preserved both HTML. Health live+ready **200/200** + admin/eoffice root 200. **NO migration** — prod top=Mig 49 `AddWorkItemToPurchaseEvaluation`==repo ✓. sys.tables(excl mighist)=**92**. **★ WIPE-VERIFY (the risk point — app restart post-deploy did NOT resurrect demo data, DemoSeed:Disabled gate HELD): PE=0 · Contracts=0 · Notifications=0 · PESeq=0** (all wiped clean, stayed clean post-recycle) + **infra preserved: ApprovalWorkflows=7 all IsActive=1 · Projects=70 · WorkItems=86 · Users=55** — ALL counts EXACT match expected. Test gate **240** (CI both proj pre-deploy ⟹ success=passed). 0 regression. **LESSON: post-wipe deploy verify = confirm both halves — (a) demo/transactional tables stay 0 after app-pool recycle (gate held, no re-seed) AND (b) infra/master tables (AW/Projects/WorkItems/Users) untouched (wipe was surgical, not nuke). Count-exact on both sides = PASS.** Tag `[s59, run273, pass, fe-list-regroup-x2, bundle-rotate-both, prod-wipe-verify, no-mig]`.
- **2026-06-11 Run #274 (run_number 274) sha=`0eafcd3` PASS ~4m51s (S59-đợt2 FE×2 PE-list tree 4-tầng "Năm>Dự án>Hạng mục>Phiếu" — follow-up Run #273 đổi từ 2-tầng-gộp-label sang 4-tầng explicit):** Push `56882ac..0eafcd3` 2 files `PurchaseEvaluationsListPage.tsx` ×2 app ONLY (SHA256 mirror identical `95d524ee`). `.tsx` → pipeline RAN. GITEA_TOKEN+PROD_DB_PW empty → anon API + prod appsettings pw. Run IN-PROGRESS first poll (running 16:37) — polled iter6 status=success (16:37:06→16:41:57). **Bundle ROTATE BOTH (verified AFTER success +re-confirm stable NO transient — anti-pattern#3): admin `R9uGRxvw→DuU7OTym` + user `DikfX1RD→DWyeTzf3`** ✓ both touched. Health live+ready **200/200** + admin/eoffice root 200. **NO migration** (FE-only). Spot-check **PE=0** held post-deploy (FE-only no restart-resurrection risk; wipe gate still HELD from #273). Test gate 240 (CI both proj pre-deploy ⟹ success=passed). 0 regression. LESSON: FE-only follow-up of a wipe-session needs only light PE=0 re-confirm (no full infra re-audit) — restart risk already cleared #273. Tag `[s59-dot2, run274, pass, fe-list-4tier-x2, bundle-rotate-both, no-mig, pe-zero-held]`.
- **2026-06-11 Run #385#386 SUPERSEDE-CHAIN sha=`ea793a4` CANCELLED(benign)→shipped-via `3ebaf84` #386 PASS ~4m25s (S58 brand-accent polish x2 app then PE-workitem-merge):** Target push `6e53e33..ea793a4` 8 files FE polish CẢ 2 app (Layout/TopBar/PageHeader/DataTable each — stripe đỉnh + logo-zone tint + PageHeader accent bar + thead brand-50/60), NO BE/Mig. **Run #385 (run_number 271) status=`cancelled` @14:14:22 — NOT a fail: superseded by newer push `3ebaf84` (#386 run_number 272) landed @14:14:31 (Gitea concurrency-guard cancels in-flight same-branch run).** HEAD moved ea793a4→`3ebaf84`. **Verified ea793a4 IS ancestor of 3ebaf84 + the 8 polish files NOT re-touched by 3ebaf84 → polish PRESERVED in tree, ships via #386.** #386 adds 4 PE files (PeHeaderForm/PeWorkspaceCreateView ×2 app, anh Kiệt FDC 14:06 — gộp Tên gói thầu=chọn Hạng mục) → both apps rebuilt anyway. Polled #386 to status=`success` (started 14:14:31→14:18:56). **Bundle ROTATE BOTH (load-bearing, verified AFTER #386 success — anti-pattern #3): admin `CP4CB1ym→DMm9rtNA` (css `vMtY6u47→DDlKud5i`) + user `CKjwqnGL→BUkOMn_Y` (css `CV0H5hnq→BgAUPcnL`)** ✓ both touched → both rotate. **Brand preserved both apps: `1F7DC1` in HTML + `Be Vietnam Pro`+`1f7dc1` in CSS bundle; BONUS polish landed: `brand-50`/`brand-60` Tailwind classes present in BOTH CSS bundles** (thead/tint/accent shipped). Health live+ready **200/200** + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo, GIỮ NGUYÊN ✓ (neither ea793a4 nor 3ebaf84 has Mig). Smoke PE+contracts unauth=**401** + control `/api/zzz-not-a-route`=**404** (auth gates real). Test gate (CI both proj pre-deploy ⟹ #386 success=passed). Prior today #382/#383/#384 all PASS. **LESSON (cancelled ≠ fail — supersede-chain verify): a same-SHA run flipping to `cancelled` mid-flight is almost always Gitea concurrency-supersede by a newer push, NOT a build/deploy fault → MUST (1) check tasks list for newer run + HEAD movement, (2) `git merge-base --is-ancestor` confirm target commit preserved in new HEAD, (3) `git diff target..newHEAD -- <target-files>` empty ⟹ target changes survive, (4) verify prod via the SUCCESSFUL superseding run not the cancelled one. Do NOT report FAIL/escalate on a benign supersede-cancel.** Tag `[s58, run385-cancelled-benign, run386-pass, supersede-chain, brand-polish-x2, bundle-rotate-both, no-mig]`.
- **2026-06-11 Run #384 (run_number 270) sha=`e959f72` PASS ~4m30s (S58 FE-USER visual redesign density-first per AI_INFRA UI/UX guide — keep brand #1F7DC1/Be Vietnam Pro/slate; FE-USER-ONLY, ZERO BE/Mig/fe-admin):** Push `6c5fd26..e959f72` 1 commit 16 files: 14 fe-user (`index.css` tokens + 6 ui primitives Button/Dialog/Input/Label/Select/Textarea + 6 shell DataTable/EmptyState/Layout/PageHeader/PhaseBadge/TopBar + LoginPage) + 2 broadcasts `.md`. NO fe-admin, NO `.cs`, NO Mig. `.tsx`/`.css` present → NOT docs-skip, pipeline RAN. ⚠️ GITEA_TOKEN empty both shells → unauth public API (200, no token needed). Run IN-PROGRESS at first poll (status=running 13:51) — correctly did NOT FAIL, polled iter6 status=success (started 13:51:18 → 13:55:48). **ASYMMETRIC bundle (load-bearing) PASS: user ROTATE `BmZ3VHnm→CKjwqnGL`** (redesign shipped, verified AFTER status=success, stable on +recheck no transient) **+ admin FROZEN `CP4CB1ym`** (=#382 UNCHANGED ✓ scope-correct, NO fe-admin leak — mirror Run #378 asymmetric fe-admin-only logic, inverted). user `.js` HEAD 200 app/js 1.47MB + CSS rotate `index-CV0H5hnq.css` 200 63KB. **Brand preserved: `1F7DC1` in HTML + `Be Vietnam Pro`+`1f7dc1` in CSS bundle** ✓; title "Solutions ERP". Health live+ready **200/200** + admin/eoffice root 200. **NO migration** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo, GIỮ NGUYÊN ✓. sys.tables(excl mighist)=**92** (FE-only no new table). Smoke PE unauth=**401** + control `/api/zzz`=**404** (auth gates real). Test gate **240** (CI both proj pre-deploy ⟹ success=passed). 0 regression. Prior today #382(`5998163` lock-fix)+#383(`6c5fd26` hide-modules) both PASS as noted. **LESSON (single-app FE-USER redesign — asymmetric verify, inverse of #378):** PASS criteria asymmetric — user hash MUST rotate (ship-proof) AND admin hash MUST stay frozen (scope-proof, no accidental fe-admin redeploy). admin-unchanged is POSITIVE here. Visual-only CSS-token+className redesign rotates bundle exactly like logic change (Vite content-hash byte-sensitive). SSH→sqlcmd `<>`/`NOT LIKE '__%'` quoting traps: `<` mangled by PS redirect (use `!=`/CONCAT-CHAR), `_` is LIKE-wildcard (escape `'[_][_]%'`). Tag `[s58, run384, pass, fe-user-only-redesign, asymmetric-bundle-verify, no-mig, brand-preserved]`.
- **2026-06-11 Run #382 (run_number 268) sha=`5998163` PASS ~3m31s (S58 FIX the Run #381 lock NO-OP — DbInitializer.cs ONLY, BE-only, NO Mig/FE):** Push `dd117b7..5998163` 1 commit 1 file `DbInitializer.cs` (+28/-5). Fix: (1) `LockDemoSampleUsersAsync` union +20 UAT-matrix prod email (`{act,equ,fin,hra,pm,qs}.{nv,pp,tp}@`+`bod.{1,2}@`) into prior 14 named-person = 34-email list; (2) `DemoUserPassword` 11→12 chars (`User@123456``User@1234567`) fixing silent CreateAsync-fail vs prod `RequiredLength=12` (S56 helpdesk-inert root cause). `.cs` present → full pipeline RAN. Poll iter5 status=success (started 12:58:06 → 13:01:37). **Bundle FROZEN admin `CP4CB1ym` + user `BmZ3VHnm`** (= #381 UNCHANGED ✓ CORRECT for BE-only, verified AFTER status=success — NOT ship-fail). **NO migration** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo, GIỮ NGUYÊN ✓. sys.tables=**93** unchanged. Health live/ready 200 + admin/eoffice root 200. **THE FIX VERIFIED prod (Users table — note: custom Identity table name `Users` NOT `AspNetUsers`):** total **55** users · **21 active** · **34 inactive==34 locked-future** (== lock-list size exactly). 12-sample UAT-matrix all `active=0 locked=1` (#381 NO-OP now RESOLVED — these exist in prod + got locked ✓). Named-person 14/14 found+locked (CREATED this startup via 12-char pw fix + locked same run). **Must-stay-active 6/6** admin·catalog.manager·nv.test·chuong.phan@solution.com.vn(typo-domain)·**nv.cao+nv.truong** ALL `active=1` (IT helpdesk pool ALIVE — S56 ops-pending RESOLVED by pw fix, created this startup not in lock-list). **5 new real staff** (thanh.lethanh/anh.nguyen/tring.le/truong.le/long.nguyen) all CREATED+`active=1` ✓ (12-char pw passes RequiredLength=12). Smoke nv.test login OK (token 477) + GET /api/menus 200 + /purchase-evaluations 200. 0 regression. **LESSON: lock-by-email NO-OP (#381) was a DATA-mismatch not code-bug → S58 reconciled email-list to actual prod population (UAT-matrix created via admin UI, never in seed) + the 11-vs-12 pw bug was a SECOND latent cause silently blocking ALL non-existing-user CREATE on prod (RequiredLength=12) — same fix resurrected 16 named + 5 staff + helpdesk pool. Verify lock-fix = dump Users cohorts (active/inactive split + named exact-IN), NOT just total count.** Tag `[s58, run382, pass, fix-lock-noop, pw-11to12, be-only-bundle-frozen]`.
- **2026-06-11 Run #381 (run_number 267) sha=`dd117b7` PASS+1PARTIAL ~4m25s (S57bis PE gắn WorkItem Mig 49 + all-role Pe perm + menu Cá nhân regroup + lock-14-demo-user — cross-stack BE+FE×2+Mig+test, +12 PeWorkItemGuardTests→240):** 2-commit push: prev `17b23a4` (governance+hmw.js → Run #380 **cancelled**, superseded — correct, no FE/BE contract change) then `dd117b7` (PRODUCT, Run 381 = the deciding run). 26 files: Mig 49 `20260611044424_AddWorkItemToPurchaseEvaluation` (3-file, PE.WorkItemId Guid? loose-Guid NO physical FK + `IX_PurchaseEvaluations_WorkItemId`) + Domain `PurchaseEvaluation.cs` + Config + Features + DbInitializer (perm + `LockDemoSampleUsersAsync` + menu regroup) + MenuKeys + 3 master controllers (write-lock Admin/CatMgr) + FE×2 (PeDetailTabs/PeHeaderForm/PeWorkspaceCreateView/menuKeys/types). **Run IN-PROGRESS at first check (status=running 12:14) — polled to terminal** (12:14:16→12:18:41 ≈4m25s success). ⚠️ poll-grep gotcha: `"status"` field sits AFTER `"display_title"` in tasks JSON → `[^}]*"display_title"` regex cut before status (showed blank all 10 iters); final FULL-object parse `\{"id":381,...deploy.yml[^}]*\}` confirmed status=success. **Bundle ROTATE BOTH** admin `4SUwDLD8→CP4CB1ym` + user `XdKzt9LL→BmZ3VHnm` (PE in both apps ✓ shipped, verified AFTER status=success). **Mig 49 applied prod** (`__EFMigrationsHistory` top = AddWorkItem... ✓ + WorkItemId col=1 + IX=1). sys.tables=**93** (col-only, no delta). Health live/ready 200 + admin/eoffice 200. **Perm seed STRONG: Pe_* CanCreate=1 = 130 rows across 13 roles** (was 3-role → all-role open landed); PeWf%=0 + AwV2%=2 (designer stays admin-only ✓ no leak). Menu regroup ✓: Personal root@30 · Off_ChamCong→Personal@1 · **Hrm_Config**→Master@25 (spec said key `HrmConfig`, real key has underscore `Hrm_Config` — verify by ParentKey/Order NOT literal Key) · Contracts@31 · Hrm_Dashboard→Hrm@1. Smoke PE unauth 401 (/purchase-evaluations + /catalogs/work-items) vs control 404 (auth real). WorkItems VT/TP/MEP/TB=71. **⚠️ PARTIAL item 7 — lock-14-users is a prod NO-OP:** `LockDemoSampleUsersAsync` SHIPPED+RAN but its 14 hardcoded emails (`bod.huynh@`,`pm.nguyen@`,`fin.do@`,`qs.hoang@`...) **DON'T EXIST in prod** — real demo set uses dept.position scheme `bod.1@`/`bod.2@`/`pm.{nv,pp,tp}@`/`fin.{nv,pp,tp}@`/`qs.{nv,pp,tp}@` (34 users ALL active, INACTIVE_TOTAL=0). Each FindByEmail→null→locked=0. Guard `nv.cao`/`nv.truong` also absent (-1, vacuously safe); catalog.manager+admin confirmed active. NOT a deploy fail (code correct) — email list stale vs this DB seed. Escalated em main: reconcile lock-list to actual `*.{nv,pp,tp}@` scheme OR confirm named-person legacy users were ever seeded. **LESSON: lock/deactivate-by-email assertion returning 0/`-1` ⟹ ALWAYS dump actual `Users` set before scoring FAIL — code may have run as no-op against mismatched data, NOT broken.** Tag `[s57bis, run381, pass-partial, mig49-pe-workitem, allrole-perm-130, lock-noop-email-mismatch]`.
- **2026-06-09 Run #379 (run_number 265) sha=`a20cde8` PASS ~4m20s (S56 GOLIVE-HARDEN BE fixes — LeaveBalance concurrency + ItTicket authz-order + DocxRenderer null-guard + tests, ZERO FE/Mig):** Push `bef5825..a20cde8` 1 commit 13 files: 3 BE `LeaveOtApprovalFeatures.cs` (atomic ExecuteUpdate + Serializable tx vs lost-update) + `WorkflowAppsFeatures.cs` (authz reorder Forbidden-before-NotFound) + `DocxRenderer.cs` (null-guard) + 4 test files (+12 → 216→**228**) + 6 agent-memory `.md`. `.cs`+test present → NOT docs-skip, full pipeline RAN. **Run IN-PROGRESS at first check (status=running 17:51) — correctly did NOT FAIL, polled to terminal** (started 17:51:45 → updated 17:56:05 ≈4m20s status=success iter5). **Bundle FROZEN admin `4SUwDLD8` + user `XdKzt9LL`** (= #378, UNCHANGED ✓ CORRECT for BE-only — NOT ship-fail, mirror Run #243/#368; verified pre-deploy + post-success + +5s re-confirm, NO transient, NO unexpected rotation). **NO migration** — prod `__EFMigrationsHistory` top = `20260609020759_AddProjectMasterFields` (Mig 48) == repo latest, GIỮ NGUYÊN ✓ (BE-logic-only, schema untouched). sys.tables=**93** unchanged. Health live+ready **200/200** + admin/eoffice root 200. **Smoke changed-area endpoints (all gated, none crash):** `GET /it-tickets/assignable-staff` unauth=**401** · `PUT /it-tickets/{guid}/assign` unauth+body=**401** (authz-reorder fix live, route wired) · `GET /leave-balances/my` unauth=**401** (concurrency fix dll deployed) · control fake `/it-tickets/zzz-not-a-route`=**404** (proves 401s are real auth gates not catch-all). 0 regression. **Ship-proof for BE-only no-contract-change = run success + test 228 + Mig 48 unchanged + bundle frozen + health 200** (no observable API delta — fixes are internal handler logic: atomic tx / exception order / null-guard; cannot curl-assert lost-update fix, rely on +12 tests passing in CI gate). Tag `[s56, run379, pass, golive-harden, be-only-bundle-frozen, no-mig]`.
- **2026-06-09 (S56 pre-golive verify — NO deploy, read-only audit):** Re-verified prod truth at golive gate (HEAD `bef5825` docs-only → prod correctly = Run #378). build SolutionErp.slnx 0-err + fe-admin/fe-user npm build 0-TS each + test **216** (58D+158I) exact. Prod health live+ready 200; admin root serves `4SUwDLD8` / eoffice `XdKzt9LL` (== baseline, NO drift). `__EFMigrationsHistory` top = Mig 48 == repo; 92 tables. Master-data prod spot: Projects=70 (62 real+8 demo), CAL01.Investor=N'Công ty TNHH Calofic' exact, WorkItems real=71 (VT16/TP30/MEP9/TB16) of 86, Suppliers 3/3. **LESSON — local-vs-prod FE hash divergence is EXPECTED, not ship-fail:** fresh local `npm build` produces a DIFFERENT content-hash than CI-built prod artifact (node_modules/timestamp inputs not byte-reproducible) → load-bearing check is `prod-hash == documented-baseline`, NOT `== my-local-rebuild`. Don't false-alarm on local≠prod when HEAD unchanged. Tag [s56, pre-golive-verify, prod-truth-pass, local-vs-prod-hash-lesson].
- **2026-06-09 Run #378 (run_number 264) sha=`7feb53e` PASS ~4m24s (S55 Phase-1 FE-Admin VISUAL redesign density-first design-system NAMGROUP-ref keep brand #1F7DC1 — FE-ADMIN-ONLY, ZERO BE/Mig/fe-user):** Push `84fa638..7feb53e` 1 commit 15 files: 13 fe-admin (`index.css` design tokens + 6 ui primitives Button/Dialog/Input/Label/Select/Textarea + 6 shell DataTable/EmptyState/Layout/PageHeader/PhaseBadge/TopBar + DashboardPage) + 2 agent-memory `.md` (frontend-designer/reviewer). NO fe-user, NO `.cs`, NO Mig. `.tsx`/`.css` present → NOT docs-skip, pipeline RAN. **Run IN-PROGRESS at first check (status=running 11:51) — correctly did NOT FAIL, polled to terminal** (started 11:51:06 → updated 11:55:30 ≈4m24s status=success; updated_at froze 11:55:30 across 3 poll iters = terminal signal before status field parsed). **THE KEY PROOF — admin bundle ROTATE `B-d6893W→4SUwDLD8`** (✓ redesign shipped, verified AFTER status=success; pre-success snapshot 11:51 still showed OLD `B-d6893W` = anti-pattern #3 timing confirmed AGAIN; re-confirm +3s post-success = stable `4SUwDLD8`, NO transient this run). **fe-user bundle UNCHANGED `XdKzt9LL`** (= #377; untouched ✓ NOT ship-fail — correct, no fe-user file in commit). Admin root **200 text/html** + serves `<title>Solutions ERP · Admin</title>` + `<div id="root">` (app loads ✓). **NO migration** — prod `__EFMigrationsHistory` top = `20260609020759_AddProjectMasterFields` (Mig 48) == repo latest, GIỮ NGUYÊN ✓ (FE-only, BE/Domain untouched). Health live+ready **200/200** (both pre- and post-deploy). Test gate **216** (CI both proj pre-deploy ⟹ success=passed; `tasks` endpoint reports terminal as `status:success`, `conclusion` NOT populated — trust CI conclusion). 0 regression. **LESSON (single-app FE redesign — asymmetric bundle verify):** when ONLY fe-admin changes, the PASS criteria is asymmetric — admin hash MUST rotate (proof shipped) AND user hash MUST stay frozen (proof scope-correct, no accidental fe-user redeploy). User-unchanged is a POSITIVE signal here (mirror of BE-only Run #243/#368 where admin+user both stay frozen). Visual-only redesign (CSS tokens + className) rotates bundle exactly like logic change — Vite content-hash byte-sensitive. Status-grep gotcha: greedy `.*?` regex failed to isolate `"status"` field mid-poll → use `grep -oE '\\{"id":378,[^}]*\\}'` to capture full object then sub-grep status. Tag `[s55, run378, pass, fe-admin-only-redesign, asymmetric-bundle-verify, no-mig]`.
- **2026-06-09 Run #377 (run_number 263) sha=`69cb393` PASS ~4m33s (S55 HMW-P4 real master-data seed from Excel + Project +4 cols Mig 48 — cross-stack BE+FE×2+Mig+seed):** Push `f8640d6..69cb393` 1 commit 18 files: Mig 48 `20260609020759_AddProjectMasterFields` (3-file) + Domain `Project.cs` (+Year/Investor/Location/Package nullable) + `ProjectConfiguration.cs` + App `ProjectFeatures.cs` + **`DbInitializer.cs` NEW `SeedRealMasterDataAsync` (UNGATED, per-code idempotent)** + FE×2 `master/ProjectsPage.tsx` + `types/master.ts` + 1 test `MasterCatalogFilteredUniqueTests.cs` + 4 agent-memory .md. `.cs`+`.tsx`+Mig present → full pipeline RAN. **Run was IN-PROGRESS at first check (status=running 09:28) — correctly did NOT FAIL, polled to terminal** (started 09:27:19 → updated 09:31:52 ≈4m33s status=success). ⚠️ `jq` NOT in Bash-tool bash (env is bash not PS despite shell=PowerShell env-line) — parse JSON via `grep -oE '\"id\":377[^}]*'` fallback; the working first call only used `head -c` not jq. **Bundle ROTATE admin `DmjI8Cmn→B-d6893W` + user `YxL_MljK→XdKzt9LL`** (BOTH changed ✓ FE shipped both apps, verified AFTER status=success; pre-success snapshot 09:28 still showed OLD DmjI8Cmn/YxL_MljK = anti-pattern #3 timing confirmed again; re-confirm +3s post-success = stable, no transient). **Mig 48 applied prod** (`__EFMigrationsHistory` top = `...AddProjectMasterFields` ✓ + `COL_LENGTH('Projects','Investor')` EXISTS). **THE KEY CHECK — real master-data landed (4/4 spot PASS):** Projects spot6 (APVN01/ZOTE01/CAL01/MIDEA01/SAM01/TLB01)=**6** · WorkItems VT-/TP-/MEP-/TB-=**71** · Suppliers (TRUONGGIANG/TANPHU/TGN)=**3** · `CAL01.Investor` =EXACT match N'Công ty TNHH Calofic' (console showed `C<>ng` = sqlcmd codepage mangle of `ô`, NOT data corruption — confirmed via `WHERE Investor=N'...'` EXACT). Totals: Projects=**70** (62 real + 8 demo coexist ✓ ungated seed idempotent), WorkItems=**86**, 5 Projects carry Investor / 23 carry Year (Excel sparse-fill, only some rows enriched — expected). Health live+ready **200/200**. `GET /api/projects` unauth=**401** (route wired, auth gates ✓). 0 regression. **LESSON (ungated prod seed verify = count spot-checks NOT schema):** `SeedRealMasterDataAsync` runs unconditionally on every prod startup (NOT inside `if(!demoSeedDisabled)` — correct per gotcha #51 INFRASTRUCTURE-seed rule); verify = sqlcmd COUNT spot-checks of real Codes + N-literal EXACT match for unicode fields (console codepage will mangle Vietnamese diacritics → always re-assert via `=N'...'`, never trust raw sqlcmd console render). Tag `[s55, run377, pass, mig48-master-fields, real-seed-ungated, sqlcmd-codepage-lesson]`.
- **2026-06-08 Run #376 (run_number 262) sha=`ca4b602` PASS ~4m18s (S54 IT-staff self-reassign ticket — authz Admin-OR-IT + scoped capability endpoint, cross-stack, NO migration):** Push `18d397f..ca4b602` 1 commit 13 files: BE `WorkflowAppsFeatures.cs` (NEW `GetAssignableItStaffQuery` capability + `AssignItTicketHandler` authz Admin-OR-IT) + `ItTicketsController.cs` (NEW `GET /it-tickets/assignable-staff` + `/assign` LOWERED Authorize-Roles) + FE×2 `ItTicketsPage.tsx` (SHA256-identical) + `workflowApps.ts`×2 (+2 type) + `ItTicketReassignAuthzTests.cs` (+13 → 203→**216**) + 6 agent-memory `.md`. `.cs`+`.tsx` present → NOT docs-skip, full pipeline RAN. Poll iter5 status=success (started 16:12:23 → updated 16:16:41 ≈4m18s). **Bundle ROTATE admin `DfCfHUE9→DmjI8Cmn` + user `_3S0BPJ2→YxL_MljK`** (BOTH changed ✓ FE shipped, verified AFTER status=success; pre-deploy iter0 still showed OLD DfCfHUE9/_3S0BPJ2 — correct timing anti-pattern #3). **NO migration** — prod `__EFMigrationsHistory` top = `...FilterMasterCatalogUniqueIndexesByIsDeleted` (Mig 47) == repo latest, GIỮ NGUYÊN ✓ (DepartmentId reuse). sys.tables=**93** stable (no new table). Test gate **216** (CI both proj pre-deploy ⟹ success=passed; grep undercounts InlineData — trust CI). Health live+ready 200 + admin/eoffice root 200. **Smoke NEW endpoint:** `GET /api/it-tickets/assignable-staff` unauth=**401** (route wired, [Authorize] gates) · `PUT /api/it-tickets/{guid}/assign` unauth bare=**411** then **WITH body `-d '{}'`=401** (IIS demands Content-Length before auth eval; 411 is pre-auth Length-check NOT routing-miss) · control fake route `/it-tickets/zzz`=**404** (proves 401s are real auth gates not catch-all). 0 regression. **LESSON (411 vs 401 on bodyless PUT/POST):** unauth bodyless PUT/POST to a JSON-body endpoint returns **411 Length Required** from IIS BEFORE the [Authorize] filter runs — NOT a 404/route-miss. Re-send with `-d '{}'` to force auth eval → real 401. Consistent w/ Run #367 `PUT /adjust=411` + #364 `POST /approve=411` (same pattern, now explained). Tag `[s54, run376, pass, it-reassign-authz, no-mig, 411-precheck-lesson]`.
- **2026-06-08 Run #371 (run_number 257) sha=`30a99aa` PASS ~4m18s (S50 HMW-Wave2 P11-C Vehicle+Driver catalogs Mig 44 + gotcha #57 filtered-unique 3 HRM catalog Mig 45 — BE+FE×2+2Mig+tests):** Push `f8179c5..30a99aa` 1 commit 28 files: BE Domain `Vehicle.cs`/`Driver.cs` + App `HrmConfigFeatures.cs`+IApplicationDbContext + `HrmConfigsController` + 5 Config (Driver/Vehicle/LeaveType/OtPolicy/ShiftPattern) + DbContext + DbInitializer + MenuKeys + Mig44/45 (6 files) + FE×2 (HrmConfigsPage/Layout/menuKeys/hrm-config.ts) + `HrmConfigFilteredUniqueTests.cs` (+5 test → 181→**186**). All BE/FE/Mig, none in paths-ignore → CI ran. Poll iter3 status=success (started 10:32:58 → 10:37:16). **Bundle ROTATE admin `DPPTx2Kw→Cg9mvltU` + user `CjoUEsoV→YgqDvsqr`** (BOTH changed ✓ FE shipped, verified AFTER status=success). **NEW LESSON (timing trap):** pre-success snapshot showed transient `CVbyotwa`/`BBlyMlJH` (intermediate FE copy mid-deploy, NOT final) → re-verify post-success gave real `Cg9mvltU`/`YgqDvsqr`. Confirms anti-pattern #3 + Run #242 lesson: NEVER trust bundle hash until status=success; mid-deploy can show a 3rd transient hash. **Mig 44+45 auto-applied prod** (`__EFMigrationsHistory` top2 = FilterHrmCatalog... + AddVehicleAndDriver...). **Vehicles+Drivers tables EXIST**; sys.tables=**92** (was 90 +2, narrative-93 = convention diff, NOT missing). **gotcha #57 LIVE — all 5 idx filtered:** IX_{Vehicles,Drivers,LeaveTypes,ShiftPatterns,OtPolicies}_Code ALL `is_unique=1 filter=([IsDeleted]=(0))` (3 HRM ones LeaveType/Shift/OtPolicy were NULL pre-Mig45 → now filtered = proof applied). Health live+ready 200 + admin/eoffice index 200. New endpoint `GET /api/hrm-configs/{vehicles,drivers}` unauth=**401** (route wired, no crash) + admin auth=**200** seed 2/catalog (vehicles XE-01/XE-02, drivers TX-01/TX-02 ✓ DbInitializer infra seed ran). 0 regression. Tag `[s50, run371, pass, p11c-vehicle-driver, mig44-45, gotcha57-filtered-5idx]`.
- **2026-06-03 Run #369 (run_number 255) sha=`350b2bf` PASS ~4m13s (S48 FE-only login subtitle a11y `text-slate-500→600`, ZERO BE/Mig):** Push range `7bbfa5a..350b2bf` 2 commits: `009dd94` DOCS/GOVERNANCE-only (9 files: STATUS/HANDOFF + 3 adap-reports + error-ledger + session-log + frontend-designer MEMORY + session-end.md cmd — ALL `.md`/`.claude/**`) + `350b2bf` CODE 2 files `fe-{admin,user}/src/pages/LoginPage.tsx` (1-line each, slate-500→600 subtitle contrast). Mixed push: `.tsx` present → **NOT path-filter skipped, full pipeline RAN** (gotcha #41 Discovery #3 — ≥1 non-ignored file in range ⟹ whole range builds; docs commit alone would skip but `.tsx` overrides). Poll iter5 status=success (started 00:06:33 → 00:10:46). **Bundle ROTATE admin `Krjvg_3j→DPPTx2Kw` + user `6sNStgxa→CjoUEsoV`** (BOTH changed ✓ FE shipped — verified AFTER status=success; pre-deploy snapshot iter0 still showed OLD `Krjvg_3j`/`6sNStgxa`, correct timing per anti-pattern #3). **NO migration** — repo 43 == prod `__EFMigrationsHistory` 43, latest both `...FilterHolidayUniqueIndexByIsDeleted` (Mig 43 unchanged, BE/Domain untouched ✓). Health live+ready 200 + admin/eoffice index 200. Test gate 181 (CI both proj pre-deploy ⟹ success=passed). 0 regression. NEW LESSON: smallest possible FE change (1-line className) still rotates bundle hash — Vite content-hash sensitive to any source byte; mixed docs+tsx push is the canonical case where docs-only-skip does NOT apply. Tag `[s48, run369, pass, fe-only-a11y, mixed-push-not-skipped]`.
- **2026-06-01 Run #368 (run_number 254) sha=`0c5a014` PASS ~4m20s (S45 Mig 43 filter Holiday UNIQUE by IsDeleted + 3 HRM test gaps — BE+tests ONLY, ZERO FE):** Push range `dbbed15..0c5a014` 2 commits: `051b62b` Tests +27 (HrmConfigHolidayTests + EmployeeSatelliteTests + AuthorizePolicyRegressionTests-ext → baseline 154→**181**) + `0c5a014` Mig 43 `20260601064128_FilterHolidayUniqueIndexByIsDeleted` (drops+recreates `IX_Holidays_Year_Date` as filtered UNIQUE `WHERE [IsDeleted]=0`, was unfiltered) + HolidayConfiguration.cs edit + Case-7 test flip. 7 files, all BE+tests, none in paths-ignore → CI ran. Poll iter4 status=success (started 13:43:47 → 13:48:07). **Bundle hashes UNCHANGED admin `Krjvg_3j` + user `6sNStgxa`** (= #367) — CORRECT for BE-only push, NOT ship-fail (Run #243 precedent; ship-proof = Mig 43 applied, not bundle rotate). **Mig 43 auto-applied prod** (history top = `...FilterHolidayUniqueIndexByIsDeleted` ✓). **THE FIX VERIFIED prod:** `IX_Holidays_Year_Date | unique=1 | filter=([IsDeleted]=(0))` — filter_definition non-NULL = filtered UNIQUE live (soft-deleted holidays no longer collide on UNIQUE). Health live+ready 200 Healthy. `Holidays` table exists, 10 rows, 2 named idx (PK + filtered UNIQUE). Prod tables=90-by-sys.tables (index-only change, NO new table — consistent #364 delta). NEW LESSON: filtered-index migration verify = check `sys.indexes.filter_definition` non-NULL (NOT just mig-history row); index-only mig = bundle unchanged + table-count unchanged both EXPECTED. Tag `[s45, run368, pass, mig43-filtered-index, be-only-bundle-unchanged]`.
- **2026-05-30 Run #367 (run_number 253) sha=`82d7fcf` PASS ~4m08s (S42 P11-B LeaveBalance business logic, Mig 42):** Code commit 22 files (4 BE: Domain `LeaveBalance.cs` + App `LeaveBalanceFeatures.cs`/`LeaveOtApprovalFeatures` deduction hook + `LeaveBalancesController` + IApplicationDbContext + DbContext + Config + Mig42 3-file + 2 FE `WorkflowAppDetailPage`×2 +`workflowApps.ts`×2 + 2 tests + 4 agent-memory .md). Started 11:11:40 → success iter4 11:15:48. **Bundle rotate admin `BU8FTBRi→Krjvg_3j` + user `tepE4jvR→6sNStgxa`** (both changed ✓ FE shipped, verified AFTER status=success — pre-deploy snapshot still showed old hash, correct timing). **Mig 42 `20260530034336_AddLeaveBalances` auto-applied prod** (tables 90→**91**, `LeaveBalances` EXISTS). Schema ✓: UserId/LeaveTypeId/Year/EntitledDays/UsedDays/AdjustmentDays decimal + AuditableEntity soft-delete. **UNIQUE `IX_LeaveBalances_UserId_LeaveTypeId_Year`** + **FK→LeaveTypes del=NO_ACTION** (=Restrict) ✓. New endpoint smoke: `GET /api/leave-balances/my` unauth=**401** (route live not 404) + admin auth=**200** lazy-default 5 LeaveTypes (ANNUAL12/COMPASSIONATE3/MATERNITY180/SICK30/UNPAID0, all Used=0, `remainingDays`=entitled ✓ DTO shape has remainingDays/entitledDays) + `?year=2026` admin route 401 unauth + `PUT /adjust`=411 (route reg). health live/ready 200 Healthy. **NO seed gate concern** (plain table, lazy DTO — Stage 4.6 N/A). 0 regression. Note: prev run #366 (ffb2062 docs STATUS update) was a CODE-path push w/ status=success — NOT docs-only-skipped (commit touched only .md but Gitea still ran since prior range?); actually #366 display_title is Docs but ran full → confirms agent-memory .md NOT in paths-ignore (`.claude/skills/**` ignored, `.claude/agent-memory/**` NOT). Tag `[s42, run367, pass, p11b-leavebalance, mig42]`.
- **2026-05-30 Run #365 sha=`75df04e` PASS ~4m05s (S42 P11-A fix workflow picker 2-bug + SetWorkflow endpoint, NO mig):** 11 files BE+FE×2+test. Bundle rotate admin `BLA09-qv→6D4k-aRi` + user `CXvejOE-→DkME-974`. +4 `PUT /api/{leave,ot,travel,vehicle-bookings}/{id}/workflow` unauth=401. Test 144. NAMING RECONCILE: use real Gitea task id (#364=e7b66cd mem-labeled "#250"). Tag `[s42, run365, pass, p11a-setworkflow]`.
- **2026-05-30 Run #364 (mem #250) sha=`e7b66cd` PASS ~4m07s (S42 P11-A wire ApproveV2+LevelOpinions 4 WorkflowApps):** 1 commit BE+FE×2+Mig41+Tests. Status=success iter3. Bundle rotate admin `cWAXid0q→BLA09-qv` + user `CX79e2kZ→CXvejOE-`. **Mig 41 auto-applied prod** (latest=`20260530021936_WireWorkflowAppsApprovalV2`). Tables 84→**90** (+5: Leave/Ot/Travel/VehicleRequest LevelOpinions + WorkflowAppCodeSequences — ALL EXIST). 4 new endpoint smoke 200 auth (leave/ot/travel/vehicle-requests) + unauth 401 (route exists) + POST .../approve=411 (route reg). health live/ready 200. **Stage 4.6 seed gate PASS** (gotcha #51): 4 WF seeded prod despite DemoSeed:Disabled — QT-NP/OT/CT/XE-V2-001 AppType=5/6/7/9, verified call-site L142-145 OUTSIDE `if(!demoSeedDisabled)` gate. Test gate 141 (CI runs both proj pre-deploy). Note: table count 90 vs spec-expected 89 = baseline-count diff, NOT missing table (all 5 present). Stale doc drift deploy.yml comments "54/17 test" (cosmetic, flag em main). Tag `[s42, run250, pass, p11a-approvev2-workflowapps]`.
- **2026-05-28 Run #247 sha=`e54a22d` PASS 3m25s (S38 SKELETON 5-plan combo Mig 39+40 dual):** Push 1 commit mega `Domain+App+Infra+Api+FE×2`. ALL PASS. Bundle rotate admin `CGueDk22→cWAXid0q` + user `CEt0QRgX→CX79e2kZ`. Mig 39+40 dual auto-applied startup (90830→90839). 6 endpoint smoke 200 (leave/ot/travel/vehicle/it-tickets/hr-dashboard `totalEmployees=33 male=17 female=16`). 6 new tables + 8 menu seeded. 0 regression. Fastest S38 deploy. Tag `[s38, run247, pass, skeleton-combo]`.
- **Archived Run #246 (S37 Proposal Mig 37+38 — `/api/proposals` 200 + QT-DX-V2-001 AppType=4 seed + Stage 4.6 INFRASTRUCTURE-gated correct gotcha #51) + #359/#243/#242/#241/#240 + S35/S36 startup → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** Run #359 G-O2 Meeting Mig 36 · #243 HrmConfig BE 16 endpoint (BE-only bundle unchanged anti-pattern verify) · #242 FE inline forms 5 satellite · #241 Mig 35 HRM foundation · #240 satellite CRUD. Discovery #7 path-filter eval/** + #8 collection `proj_*`. KEY absorbed in essentials/Stage sections above.
- **[⚠️ VỊ TRÍ LẠC — entry MỚI 2026-06-11, thuộc FIFO slot giữa #384/#382 phía trên nhưng ghi lạc vào khu archive-zone này; curate-L2 ĐỪNG archive nhầm (H2 S58 P2 flagged); relocate khi curate] 2026-06-11 Run #383 (run_number 269) sha=`6c5fd26` PASS ~4m25s (S58b TẠM ẨN HRM/Văn phòng số/Cá nhân khỏi non-Admin + Danh mục xuống cuối — BE-only seed, NO Mig/FE):** Push `2aefb31..6c5fd26` 1 file `DbInitializer.cs` (+61/-5): NEW `RevokeTemporarilyHiddenModulesAsync` (set 4 CRUD=false MỌI role TRỪ Admin trên `Hrm%`+`Off%`+`Personal`, idempotent, KHÔNG xóa row) + `SeedAllRolesReviewReadPermissionsAsync` scope THU HẸP còn Master/Catalogs/Pe_* + menu `Master` Order 20→80. `.cs` present → full pipeline RAN. Run IN-PROGRESS at first check (status=running 13:36) — correctly did NOT FAIL, polled to terminal (started 13:36:15 → success 13:40:40 iter5). **Bundle FROZEN admin `CP4CB1ym` + user `BmZ3VHnm`** (= Run #382 UNCHANGED ✓ CORRECT BE-only — verified AFTER status=success, NOT ship-fail). **NO Mig** — prod `__EFMigrationsHistory` top = Mig 49 `AddWorkItemToPurchaseEvaluation` == repo, GIỮ NGUYÊN ✓. Health live/ready 200 + admin/eoffice root 200. **6 prod sqlcmd ALL PASS exact (DB SolutionErp, custom tables `MenuItems`/`Permissions`/`Roles`):** MasterOrder=**80** ✓ · HiddenReadNonAdmin=**0** ✓ (revoke landed, `Hrm%`+`Off%`+`Personal` all CRUD=false non-Admin) · HiddenReadAdmin=**29** (>0, Dev est 28, Admin GIỮ ✓) · PeCreateNonAdmin=**120** (Pe_* untouched ✓) · MasterReadNonAdmin=**48** (Master still visible ✓). **Menu-tree smoke (gotcha #44 dual-role):** nv.test `/api/menus/me` 200 → keys = Master/Suppliers/Projects/Departments + Catalogs/Catalog* + all Pe_DuyetNcc*/Pe_DuyetNccPhuongAn* — **ZERO Hrm/Hrm_*/Off/Off_*/Personal** ✓; admin counter-check VẪN CÒN Hrm/Hrm_Config*/Off/Off_*/Personal + Contracts/Budgets/System ✓ (revoke scoped non-Admin only). 0 regression. **LESSON (seed-only permission-revoke verify = sqlcmd matrix + dual-role menu-tree, NOT bundle/endpoint):** RevokeTemporarilyHiddenModulesAsync runs UNCONDITIONALLY on startup (NOT gated — correct, it's a permission-correction not demo-seed); verify = COUNT(CanRead non-Admin)=0 for revoked-prefix + COUNT Admin>0 (kept) + COUNT untouched-scope (Pe/Master) unchanged + `/api/menus/me` key-set diff between regular-user (modules gone) and admin (modules present). FE has NO PermissionGuard per-route (commit note) → direct-URL still renders trang, mức "tạm ẩn" = menu hide + permission matrix only; acceptable pre-golive. Tag `[s58b, run383, pass, revoke-hidden-modules, master-order-80, be-only-bundle-frozen, no-mig, dual-role-menutree]`.
- **Archived Run #232 (S29 gotcha #51 catch — SeedSampleContractWorkflowV2 nested in demoSeedDisabled → empty V2 dropdown, hoist fix) → `archive/2026-05-q4.md` + git. Smart Friend ROI 4× cumulative (S22 #44 + S25 #48 + S29 ApplicableType + S29 DemoSeed).**
---
## 🔄 Curate trigger
- >~30KB → archive recent runs → L2 `archive/<period>.md`. Dup failure patterns → merge. Stale >3mo → remove.
- **Last 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 + Stage 3 111→130. Foundation (gotcha patterns + Stage 0-5 + Stage 4.6 + 10-point + Discovery #4-8) preserved. Prev: S34 q3 · S32 q2 · S22 runs.