[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>
This commit is contained in:
pqhuy1987
2026-06-15 20:41:47 +07:00
parent 7926c2129c
commit 5e6dcc1479
12 changed files with 190 additions and 28 deletions

View File

@ -47,10 +47,10 @@ Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, ve
- **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:** **228 PASS** (S56 Run #379 sha a20cde8; Domain 58 + Infra 170 = +12 golive-harden `ItTicketReassignAuthzTests`/`LeaveBalanceTests`/`WorkflowAppApproveV2Tests`/`DocxRendererTests` vs prev 216). 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 48 `20260609020759_AddProjectMasterFields`** (S55; AddColumn-only, Project +Year/Investor/Location/Package nullable, NO new table; kèm `SeedRealMasterDataAsync` ungated). Prev Mig 47 FilterMasterCatalog... + 46 AddSlaFieldsToItTicket. Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`. Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. Table-count: `sys.tables` (is_ms_shipped=0) count = **93** (S56 Run #379 verified, Mig 48 col-only no delta); narrative also 93 now reconciled. Don't FAIL on 9293 convention diff.
- **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 S59 (CLOSE-OUT FINAL-v2):** admin `B1DtNT9C` · user `D6uF3Mln` (Run #282 sha 792c030, ROTATED from #280 `BKy_8OO9`/`XcZ6PRyA` supersede-chain #281`80b64dd`cancelled#282 ships paymentTerms-removal + "+Thêm hạng mục" drop ×2). Bundle size ~800KB/750KB gz. 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).
- **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)
@ -68,6 +68,7 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code /
## 📅 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]`.