From d21b554f296cef00199e20063e61c497c3cca569 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 27 Jul 2026 13:30:58 +0700 Subject: [PATCH] wal: flush 20260727T1330 --- .claude/WAL.md | 7 ++- .../agent-memory/test-specialist/MEMORY.md | 2 + .../sub-d2-fe-3.md | 38 ++++++++++++ .../sub-d2-test-4.md | 60 +++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/.claude/WAL.md b/.claude/WAL.md index 0f44532..f48c21a 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -60,7 +60,12 @@ chain: [~] A-ĐỢT-2 W1 hmw XONG-MỘT-PHẦN — cả 2 sub mất return. Đo đĩa: **BE code LAND** (file mới `PeSoftDeleteFeatures.cs` + controller `:163` `by-approver` + `:174` `deleted` + `MenuKeys:145-146` + `DbInitializer`) build 0E — **NHƯNG 0 test mới** (551 y nguyên) · **FE gần như CHƯA làm** (chỉ `types/purchaseEvaluation.ts` ×2) [x] ✅ **HỢP-ĐỒNG LIÊN-LANE GIỮ ĐƯỢC ở phía BE** — route + menu-key đúng canonical từng ký tự. FE chưa gọi (0 hit) vì chưa làm tới [!] 🔴 **LEAD SAI, sub FE bác ĐÚNG**: lead bảo "vá lệch `fe-admin:107` thiếu `WfView`" — sub đo ra tiền-đề KHÔNG ĐỨNG (`WorkflowMatrixViewPage.tsx` CHỈ có ở fe-user; admin không page/route/types) ⇒ thêm regex một mình = **đẻ link chết** "Trang này chưa được build" = regression NHÌN THẤY ĐƯỢC, tệ hơn hiện trạng. Lead chép kết-luận của investigator đợt 1 mà không hỏi trang đích có tồn tại không. **LEAD CHỐT đường (c): GIỮ NGUYÊN**, gỡ khỏi phạm vi (admin đã có Designer sửa được, matrix chỉ-xem là thừa) -[!] wf: A-ĐỢT-2 W1-bis — 2 spawn TRỰC TIẾP song song (KHÔNG qua hmw): `implementer-frontend` làm phần chính FE (`sub-d2-fe-3.md`) · `test-specialist` viết 10 test T21-T26 (`sub-d2-test-4.md`) +[x] ✅ **A-ĐỢT-2 FE XONG** (sau 1 lần resume) — `tsc -p tsconfig.app.json` **SẠCH cả 2 app** · **hợp-đồng NỐI ĐỦ 2 CHIỀU**: `by-approver` **6 hit** · `/deleted` **4 hit** · **md5 mirror KHỚP** `eb55781a…` ×2 · `Layout.tsx` ×2 map `Approved`→`?phase=7` / `Deleted`→`?deleted=1`; fe-admin CỐ Ý không có `WfView` (giữ quyết định (c)). ⇒ **KHÔNG lặp lại F3 đợt 1** +[x] ✅ **A-ĐỢT-2 test XONG + lead vá T26** — **561 PASS / 0 FAIL** (45 Domain + 516 Infra, +10 test). `test-specialist` giữ T26 ĐỎ CÓ CHỦ Ý (đúng luật: bug production, không nới assert) + grep-cùng-lớp xác nhận site duy nhất + tự dọn file chẩn-đoán (khác agent đợt 1 bỏ quên `ZZ_DIAG`) +[x] 🔴 **BUG T26 + cách vá (đáng ghi memory)**: `Add(changelog)` rồi `Remove(pe)` ⇒ EF chạy cascade client-side **NGAY tại `Remove()`** (`CascadeDeleteTiming.Immediate`) ⇒ **DETACH** changelog đang `Added` ⇒ SaveChanges ghi **0 row IM LẶNG**. Đo: `stateAfterAdd=Added → stateAfterRemove=Detached → rowsInDb=0`. Trớ trêu: phiếu KHÔNG bị xóa thật (interceptor đổi Deleted→Modified = xóa MỀM) ⇒ **cascade hành động theo một vụ xóa KHÔNG BAO GIỜ xảy ra**. **Lead vá = ĐẢO THỨ TỰ** (`Remove` TRƯỚC, `Add` SAU) — rẻ hơn cả 3 hướng sub đề (2×SaveChanges+transaction · `CascadeDeleteTiming.Never` · set tay IsDeleted) +[!] wf: A-ĐỢT-2 reviewer — spawn TRỰC TIẾP `a64f...` soi diff đợt 2 trước deploy; ưu tiên: vá-đảo-thứ-tự của lead (chưa ai soi, hỏi có đúng trên SQL Server không chứ không chỉ SQLite) · 3 bẫy `IgnoreQueryFilters`/IDOR/D3 · guard per-NGƯỜI · labelBackfill +[~] A-ĐỢT-2 test cũ: 516 total, 1 FAIL = `T26_Delete_WritesChangelogWithDeleteAction_AndCarriesReasonWhenProvided`. Handler CÓ ghi `ChangelogAction.Delete` (`PeSoftDeleteFeatures.cs:126-131`) ⇒ đỏ ở chi-tiết khác (nghi phần mang LÝ DO). `test-specialist` còn chạy (testhost lock ⇒ build báo MSB3027 = **KHÔNG phải lỗi mã**), chờ nó khai +[~] wf: A-ĐỢT-2 W1-bis — 2 spawn TRỰC TIẾP song song (KHÔNG qua hmw): `implementer-frontend` làm phần chính FE (`sub-d2-fe-3.md`) · `test-specialist` viết 10 test T21-T26 (`sub-d2-test-4.md`) [~] wf: A-ĐỢT-2 W1 hmw — run-id `wf_55f6201f-a52` · anh lệnh "làm tiếp luôn" 2026-07-27 (KHÔNG chờ UAT đợt 1). 2 task SONG SONG: BE (xóa-by-approver + list /deleted + menu seed + changelog + 10 test) · FE (nút xóa panel + 3 mục menu + màn Đã xóa + md5-mirror) [ ] 🔴 **HỢP-ĐỒNG LIÊN-LANE đã chốt TRƯỚC khi phóng** (bài học đợt 1: 2 nửa build sạch mà không nối được nhau — F3) — canonical ở `run.md` §ĐỢT 2: `DELETE /api/purchase-evaluations/{id}/by-approver` (policy `PurchaseEvaluations.Delete`) · `GET /api/purchase-evaluations/deleted` (policy `.Read`) · giữ NGUYÊN `DELETE /{id}` xóa-nháp KHÔNG policy · key `Pe_{code}_Approved` + `Pe_{code}_Deleted` (KHÔNG vào `MenuKeys.All`) · route FE `?phase=7` và `?deleted=1` [ ] A-ĐỢT-2 W2: test + reviewer → deploy (theo đúng gate đợt 1: test xanh ∧ reviewer không ĐỪNG-DEPLOY ∧ F8 `git add` đích danh) diff --git a/.claude/agent-memory/test-specialist/MEMORY.md b/.claude/agent-memory/test-specialist/MEMORY.md index b63ccc6..f3d7c72 100644 --- a/.claude/agent-memory/test-specialist/MEMORY.md +++ b/.claude/agent-memory/test-specialist/MEMORY.md @@ -1,5 +1,7 @@ # Test-Specialist Agent — Persistent Memory +- **S155 (07-27) ĐỢT 2 PE xóa-khi-đang-duyệt + màn "Đã xóa" [run 2026-07-27-S155-pe-delete-approver · sub-d2-test-4, diary 13.9KB ghi-trong-lúc-làm]:** +10 `PeDeleteByApproverTests.cs` (T21 lũy kế **4 HƯỚNG KHÁC NHAU** · T22/T22b/T22c màn Đã xóa · T23/T24/T24b cờ+lượt · T25 authz reflection · T25b chiều thuận · T26 changelog). Full **561 = 560 PASS / 1 FAIL** (45D + 516I); 0 hồi quy (Infra cũ 506 nguyên vẹn). **1 FAIL = T26 GIỮ ĐỎ = BUG PROD**: `PeSoftDeleteFeatures.cs` Add changelog (:126) → `Remove(pe)` (:142) → `CascadeDeleteTiming.Immediate` cascade **ngay tại Remove**; dependent đang `Added` bị cascade ⇒ **Detached** (không phải Deleted) ⇒ 0 row; `AuditingInterceptor` chạy ở SavingChanges = SAU điểm mất ⇒ không cứu. Đo bằng ChangeTracker diag (`Added→Detached`, `rowsInDb=0`), KHÔNG suy đoán. Grep cùng-lớp: site DUY NHẤT có hình "Add con → Remove cha → 1 SaveChanges". REPORT lead, KHÔNG tự sửa. **Hạ tầng MỚI:** `SoftDeleteFixture` (ngay trong file test, ~90 LOC) = fixture ĐẦU TIÊN của repo wire `AuditingInterceptor` — BẮT BUỘC khi đo `Remove()`=xóa-mềm, vì `TestApplicationDbContext` KHÔNG wire ⇒ mặc định **XÓA CỨNG** ⇒ màn "Đã xóa" rỗng ⇒ assert đúng ở CẢ HAI thế giới (đúng bẫy vacuous). Số học T21 nhớ kỹ: xóa phiếu ChoDuyet ⇒ PrevSubmitted↓ · PendingSelected↓ · **PrevSelected + PendingSubmitted BẤT ĐỘNG** (seed thêm peer DaDuyet + TraLai để 2 số "không đổi" ≠ 0 ở cả 2 phía). Tag `[s155, pe-delete-by-approver, cascade-detach-added-child, softdelete-fixture, 4-huong]` + - **S153 (07-26) T4 ctx-adopt FAULT-INJECT máy `mind-check` #8-#17+#21 [run 2026-07-26-S153-ctx-adopt · lane trước đứt sau #7, file đĩa nguyên → tiếp được] `[engine: opus-5]`:** **18/18 PASS 0 FAIL**. KHÔNG phải test C# (**baseline 509 KHÔNG đổi**) — đối tượng = `scripts/session_ctx.py mind-check`, mọi mutation trong cây tạm qua seam `--root`, repo read-only. Bắt **1 GAP THẬT**: ở closed-mode **XOÁ con-trỏ → cờ/exit 0** còn **GIỮ con-trỏ đang-chờ trung thực → TRƯỢT/exit 1** ⇒ đường-rẻ-nhất-để-xanh là đường SAI (chữ luật cấm, gradient thưởng) → REPORT em main, KHÔNG tự sửa. **Bài học mới quan trọng nhất:** fixture `draft-ts` MALFORMED ra exit 0 — `inject_A` neo `## MIND-1` sau khi `renum(...,2)` đã đổi heading ⇒ `str.find`=-1 ⇒ chèn GIỮA heading ⇒ `ts:` không ở đầu dòng ⇒ máy ĐÚNG mà vẫn xanh. **Fixture-hỏng cho-xanh trông Y HỆT máy-đúng cho-xanh** ⇒ phải soi RUỘT fixture (`cat -A`/`repr` từng dòng), KHÔNG chỉ đọc exit code. 2 kỹ thuật xài lại: (i) **cặp CÙNG-RUỘT khác 1 biến** — cap=32 vs cap=1 trên cùng tệp = chứng đọc-config-LIVE; `_end` giả `_pause=9` vs không = chứng nguồn-đếm-là-ĐĨA, dùng `diff` 2 output để chứng dòng đo BYTE-IDENTICAL; (ii) **đo wall-clock phải gồm nhánh ĐẮT** — lệnh literal đi nhánh early-exit (repo chưa có `_mind`) = tự đo nhánh rẻ rồi gọi là chi phí thật; đo 55-64ms/lượt vs ước 140-300ms ⇒ lệch 2.2-5.4×, giao lead điền lại. Tag `[s153, t4-fault-inject, mind-check, fixture-bug-vacuous-pass, cung-ruot-1-bien]` - **S134 (07-17) PE lũy-kế TẠM TÍNH TEST-C [wf_97d6a582-b7e · return sạch 0-garble] `[engine: opus-worker]`:** +8 `PePendingAccumulationTests.cs` C1-C8 (file MỚI, mirror seed PeBudgetFreezeTests :277-336): C1 ChoDuyet+winner→delta2+PriorPes · C2 TuChoi/DaDuyet loại sạch · C3 nháp list-only/TraLai delta1 · C4 window CreatedAt · C5 delta âm signed · **C6 complementary-no-double-count: gọi CẢ ComputeAsync + ComputePendingAsync cùng seed, assert EXACT-NUMBERS 2 phía (PrevSelected 90tr/1 bất động ⟂ PendingSelected 50tr/1)** · C7 frozen→0/null qua query handler · C8 D4 changelog positive+contrast (filter `ContextNote.Contains("[S134 pending-prior]")` #48). Full **528/0** (45D+483I, +8). Learned: (1) **direct-call accumulator = synthetic curPeId + CreatedAt muộn, KHÔNG cần row phiếu-đang-xét** (chỉ filter Id!=/CreatedAt<); (2) regression "bất động" mạnh nhất = C6-style exact-numbers 2 hàm cùng seed; (3) FluentAssertions `ContainInOrder(params T[])` + because-string overload-clash khi T=Guid → bọc `new[]{...}` trước. Surprise: D4 no-SaveChanges-in-helper → row chỉ persist sau TransitionAsync. Tag `[s134, pependingaccumulation, c6-exact-numbers-complementary, 8-test]` diff --git a/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-d2-fe-3.md b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-d2-fe-3.md index 2abfe2e..b2c6df3 100644 --- a/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-d2-fe-3.md +++ b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-d2-fe-3.md @@ -63,3 +63,41 @@ Brief ghi gate dùng `actorInV2Level`. Đo thì nó **rộng hơn BE**: ⇒ Dùng `actorIsCurrentApprover` = `!!currentUser?.id && v2Approvers.some(a => a.userId === currentUser.id)` (tập con CHẶT của `actorInV2Level`, khớp 1-1 rào BE). Khai ở đây để reviewer không tưởng tôi đọc lệch brief. + +--- + +## 2. ✅ BƯỚC 1-2 XONG — nút + dialog + mirror + +### 2.1 `PeWorkflowPanel.tsx` (viết ở fe-user → `cp` sang fe-admin) +| Chỗ sửa | Nội dung | +|---|---| +| import | +`Trash2` (lucide) · +`MenuKeys` (`@/lib/menuKeys`) · +`usePermission` (`@/hooks/usePermission`) | +| prop | +`onDeleted?: () => void` — prop **THỨ 4**. KHÔNG tái dùng `onApproved` (khác nghĩa: duyệt ≠ xóa) | +| state | `deleteOpen` · `deleteReason` | +| gate | `canDeleteWhileApproving` = `!readOnly` ∧ `phase===ChoDuyet` ∧ `actorIsCurrentApprover` ∧ `levelOptions?.allowApproverDelete === true` ∧ `can(MenuKeys.PurchaseEvaluations,'Delete')` | +| mutation | `api.delete('/purchase-evaluations/${id}/by-approver', { data: { reason } })` → invalidate `pe-list` + `pe-detail` + **`pe-budget`** rồi `onDeleted?.()` | +| render nút | **CẠNH** `next.map`, là **anh em** của output map trong cùng `flex` — KHÔNG trong map | +| dialog | `` riêng, `variant="danger"`, 4 gạch đầu dòng hệ quả + `