wal: flush 20260727T1330

This commit is contained in:
pqhuy1987
2026-07-27 13:30:58 +07:00
parent a21482ecd7
commit d21b554f29
4 changed files with 106 additions and 1 deletions

View File

@ -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``?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)

View File

@ -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]`

View File

@ -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 | `<Dialog size="sm">` riêng, `variant="danger"`, 4 gạch đầu dòng hệ quả + `<Textarea maxLength={2000}>` lý do |
**Vì sao đụng 1 token ở điều-kiện khối ngoài** `next.length > 0` → `(next.length > 0 || canDeleteWhileApproving)`:
khối "Hành động:" chỉ render khi có phase kế. Nếu `nextPhases` rỗng mà 4 rào xóa vẫn đúng thì nút sẽ bị
khối cha nuốt IM LẶNG. Nới bằng chính cờ đã qua đủ 4 rào ⇒ không mở rộng bề mặt.
**`pe-budget` có thật** (đo: 6 queryKey `pe-*` sống trong fe-user, `pe-budget` là 1) — không phải key bịa.
### 2.2 Mirror ×2 — kiểm alias TRƯỚC khi copy (không copy mù)
| Kiểm | fe-user | fe-admin |
|---|---|---|
| alias `@/*` (tsconfig.app + vite) | `./src/*` | `./src/*` — **KHỚP** |
| `hooks/usePermission.ts` | có | có (**md5 IDENTICAL**) |
| `lib/menuKeys.ts` → `PurchaseEvaluations` | `:23` | `:23` |
| `Dialog` props | `open/onClose/title/children/footer/size` | **y hệt** (file DIVERGE nhưng chỉ ở style) |
| `Button` variant `danger` | có | có (class khác, key giống) |
⇒ copy an toàn. `md5` sau copy: **`eb55781aae32c8982d9f8d1e4b9b0e26` ×2** + `git diff --no-index` exit 0.
### 2.3 Đính chính snapshot lead gửi giữa chừng (gotcha #68)
Lead báo `Trash2/MenuKeys/onDeleted/can/deleteOpen/canDeleteWhileApproving` **declared-but-never-read**.
Đó là **diagnostic CŨ chụp giữa lúc agent đang sửa**. Đo lại từ đĩa: cả 6 đều có site render, và
`tsconfig.app.json` bật **`noUnusedLocals: true` + `noUnusedParameters: true`** ⇒ `tsc --noEmit -p tsconfig.app.json`
**EXIT 0** đã là bằng chứng KHÔNG còn symbol thừa. (Chỉ tin build chạy SAU agent.)

View File

@ -96,3 +96,63 @@ Số TRƯỚC → SAU (exact, không chỉ "giảm"):
<!-- flush 2 -->
## 5. KẾT QUẢ chạy lọc (10 ca mới): **9 PASS / 1 FAIL**
```
Failed! - Failed: 1, Passed: 9, Skipped: 0, Total: 10 - SolutionErp.Infrastructure.Tests.dll
T26_Delete_WritesChangelogWithDeleteAction_AndCarriesReasonWhenProvided [FAIL]
Expected logs to contain 2 item(s) ..., but found 0: {empty}.
```
## 6. 🔴🔴 BUG PRODUCTION — vết xóa (changelog + LÝ DO) KHÔNG BAO GIỜ được ghi
Giả thuyết ở mục 3 **đúng**. Không suy đoán — đã đo bằng chẩn đoán ChangeTracker riêng (file tạm, đã xóa sau khi đọc):
```
stateAfterAdd=Added | navFixedUp=True | parentColl=1 | cascadeTiming=Immediate
| stateAfterRemove=Detached | rowsInDb=0
```
Diễn giải từng bước, khớp đúng mã:
1. `PeSoftDeleteFeatures.cs:126` `Changelogs.Add(...)` ⇒ state **Added**; EF fixup tự gắn nó vào `pe.Changelogs` (navigation 2 chiều `PurchaseEvaluationConfiguration.cs:78`, `OnDelete(Cascade)`) — `parentColl=1` chứng minh.
2. `PeSoftDeleteFeatures.cs:142` `Remove(pe)``CascadeDeleteTiming.Immediate` (mặc định) cascade **ngay tại dòng này**; dependent đang `Added` bị cascade ⇒ EF chuyển thành **Detached** (xóa entity Added = gỡ theo dõi, không phải Deleted).
3. `:143` `SaveChanges`**0 row**. `AuditingInterceptor` chạy ở `SavingChanges` = **SAU** điểm mất mát ⇒ việc nó đổi Deleted→Modified không cứu được vết.
**Prod dính y hệt.** Khác biệt duy nhất giữa harness của tôi và prod là interceptor, mà interceptor nằm SAU chỗ mất. Bằng chứng gián tiếp trong chính suite này: T25b xanh ⇒ xóa mềm chạy đúng, `DeletedBy`/`DeletedAt` đúng — **chỉ LÝ DO bị nuốt IM LẶNG**. Đúng lớp lỗi mà spec §B4 dựng ra để chống.
**Grep cùng lớp (bài học root-cause):** đây là site DUY NHẤT trong `src/Backend` mang hình "Add con → Remove cha → 1 SaveChanges". Đường xóa nháp cũ `PurchaseEvaluationFeatures.cs:1408` không ghi changelog nên không dính. Các file khác có `Changelogs.Add` đều xóa CON (supplier/detail) chứ không xóa PE ⇒ nguyên tắc (PE) sống ⇒ không cascade.
**KHÔNG tự sửa** (production = ngoài phạm vi). Giữ T26 ĐỎ + khai, không nới assert. Hướng vá để lead quyết (đều nằm trong `PeSoftDeleteFeatures.cs`): (a) `SaveChanges` cho changelog trước rồi Remove (2 lượt ghi, cần transaction bao ngoài); (b) đặt `CascadeDeleteTiming.Never` quanh lời gọi; (c) không dùng `Remove()` mà set thẳng `IsDeleted/DeletedAt/DeletedBy`. Tôi không chọn hộ.
<!-- flush 3 -->
## 7. SỐ THẬT — full suite `dotnet test SolutionErp.slnx --nologo -v minimal`
```
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 - SolutionErp.Domain.Tests.dll
Failed! - Failed: 1, Passed: 515, Skipped: 0, Total: 516 - SolutionErp.Infrastructure.Tests.dll
```
| | Domain | Infra | Tổng |
|---|---|---|---|
| Baseline (lead đo) | 45 | 506 | **551** pass |
| Sau đợt này | 45 | 516 | **561** total — 560 pass / **1 fail** |
- **+10 test** (đúng 10 ca được giao), tất cả nằm trong 1 file mới.
- **0 hồi quy:** Infra passing 515 9 ca mới xanh = **506** = đúng con số Infra cũ ⇒ không ca cũ nào bị tôi làm đỏ.
- 1 FAIL duy nhất = **T26**, giữ đỏ có chủ ý (bug production ở mục 6), assert KHÔNG bị nới.
## 8. Sản phẩm
- File MỚI (duy nhất): `tests/SolutionErp.Infrastructure.Tests/Application/PeDeleteByApproverTests.cs`
- File tạm dùng để chẩn đoán ChangeTracker: đã **XÓA** sau khi đọc kết quả (kiểm lại: 0 file `Zz*` còn sót).
- KHÔNG chạm `src/Backend`, KHÔNG chạm `fe-*`, KHÔNG sửa `IdentityFixture`/`SqliteDbFixture` dùng chung.
## 9. Ghi chú bàn giao
- `SoftDeleteFixture` (trong chính file test, ~90 LOC) là fixture ĐẦU TIÊN của repo wire `AuditingInterceptor`. Bất kỳ test tương lai nào đo hành vi `Remove()` = xóa mềm đều nên dùng khuôn này thay vì set `IsDeleted` thủ công.
- T24b là ca duy nhất phân biệt được `matchingLevel.AllowApproverDelete` (đúng) với `g.Any(...)` (sai). Nếu ai đó "gọn hoá" chỗ đó, T24b đỏ ngay.
- T22c dùng 2 đối chứng (drafter + approver) chứ không 1 — để số 0 của người ngoài không thể là hệ quả của dữ liệu rỗng.
<!-- END sub-d2-test-4 -->