wal: flush 20260812T2102

This commit is contained in:
pqhuy1987
2026-08-12 21:02:02 +07:00
parent 2ae62f3fbc
commit 812d476003
5 changed files with 198 additions and 56 deletions

View File

@ -0,0 +1,36 @@
# B6 — VERIFY LOCAL ④ (lead đo trực tiếp qua dev-server, 2026-08-12 ~20:40)
> Môi trường: BE `dotnet run` :5443 (LocalDB `SolutionErp_Dev`, seeder idempotent) + fe-user Vite
> :8081 (8080 bị OS reserve — đổi port, proxy /api giữ) + fe-admin :8082. Đăng nhập = token API
> bơm localStorage (không gõ credential vào form). `window.confirm` override `()=>true` cho phép
> đo mutation W5 — KHAI. Sau đo: 3 server đã stop. Noise Dev có sẵn: SlaExpiryJob ERR
> auto-approve 6 HĐ V1 hết SLA (transition adjacency legacy) — pre-existing, không dính wave.
## Kết quả per-ô (SỐ THẬT từ DOM/API/DB)
| Ô | Phép đo | Kết quả | Verdict |
|---|---|---|---|
| W1(i) bug A | `?type=3&ct=1` → accordion | nhóm **01 mở**, 6 nhóm kia đóng (trước fix: mở 03) | ✅ |
| W1(i) control | `ct=6` / `ct=7` | nhóm **06** / **07** mở đúng | ✅ |
| W1(ii) bug B | `?type=1&phase=9&view=approved` → className 6 leaf | CHỈ "Đã duyệt" mang `bg-brand-50 font-semibold…` highlight; leaf khác text-slate | ✅ |
| W1(iii) slice | codes hiện trên trang matrix | `ct=1`→[HD-TP] · `ct=6`→[HD-NT-NCC] · `ct=7`→[HD-NT-DV] (token gạch-nối — mapping sống) | ✅ 4/7 mã đo mẫu |
| W1(iii) control rác | `ct=99` | KHÔNG lọc — 8 mã hiện (7 HD-* + QT-HD-V2-001) ⇒ chứng `ct=1` vắng QT-HD-V2-001 là **vắng-vì-lọc** | ✅ |
| W2 layout | `/my-contracts?type=1` DOM 3 panel | trái = Select "Tất cả loại HĐ"+7 option + CÂY (Năm→dự án→gói→4 GĐ) · giữa detail · phải DANH-SÁCH | ✅ |
| W2(ii) chọn tại chỗ | click item list | URL Ở LẠI `/my-contracts?type=1&id=7d047e08…` + detail mở panel giữa | ✅ |
| W2(vii) HĐ ngoài-gói | list phải | 2 HĐ type-1 hiện đủ dù cây "Duyệt Hợp đồng 0" mọi gói | ✅ |
| W2 leaf-cây GĐ3 | click leaf HĐ TRÊN CÂY | **DEFERRED-TO-DEPLOY (Đ-7)** — Dev 0 leaf GĐ3 (HĐ dev đều ngoài-gói; cơ chế `?id=` đã chứng qua list-click cùng đường onSelect) | ⏸ khai |
| W3 | `?type=4&pendingMe=1` | Ở LẠI trang + chip "Đang duyệt — đến lượt tôi" + ô tìm disabled + chú thích hiện + network gọi **`GET /api/contracts/inbox`** thật | ✅ |
| W3 so-số 2 chiều | user đến-lượt vs không | **DEFER** — admin không phải approver V2 (inbox rỗng = đúng control "user ngoài luồng 0 dòng"); ca đến-lượt cần user duyệt thật | ⏸ khai |
| W4(i) | sidebar admin | **17 leaf đủ**: 2 NCC + 8 KHKK + **7 "QT Duyệt HĐ — {TP/GK/NCC/DV/MB/NT-NCC/NT-DV}"** | ✅ |
| W4(ii) | `/system/approval-workflows-v2/HdTP` | title "Quy trình duyệt (Mới): HĐ Thầu phụ" + khoá mã HD-TP | ✅ |
| W5 nửa-ÂM | HĐ DaPhatHanh(9) detail | nút "Xóa hợp đồng" **ẨN** | ✅ |
| W5 nửa-DƯƠNG | HĐ Nháp(2) `a79dd579` | nút **HIỆN** → click+confirm → **list VẮNG** + **màn Đã-xóa CÓ** + DB: changelog **đúng 1 dòng Action=3/PhaseAtChange=2/UserName=Administrator/ContextNote=null** + `IsDeleted=1, DeletedBy=admin` | ✅ end-to-end |
**Dev-data mutation có khai:** HĐ demo `a79dd579` ([DEMO] Thi công móng FLOCK01) nay soft-deleted
trong `SolutionErp_Dev` — bằng chứng sống ở màn "Đã xóa"; un-delete = 1 UPDATE nếu cần.
## Kết luận
**5/5 wave hành-vi SỐNG trên môi trường chạy thật.** 2 ô DEFER có khai (leaf-cây GĐ3 + so-số
pendingMe) — cả hai cần DỮ LIỆU (HĐ thuộc gói · user đến-lượt), không phải nghi vấn CODE; sẽ tự
có khi UAT prod sau deploy.

View File

@ -14,8 +14,28 @@ HEAD lúc mở run: ae1dcf4a · HMW-mode ON · lead Opus 5 MAX (default)
| B3b | /fable-real | back-end-reviewer-style | single Fable-5 | ⬜ |
| B4a | /fable-clone | front-end-reviewer-style | ensemble opus | ⬜ |
| B4b | /fable-clone | back-end-reviewer-style | ensemble opus | ⬜ |
| B5 | /fable-real | reviewer (tổng hợp plan) | single Fable-5 | |
| B6 | hmw fan-out | Opus 5 MAX execute plan | per-wave | |
| B5 | /fable-real | reviewer (tổng hợp plan) | single Fable-5 | ✅ GO_WITH_NOTES (sub-b5-plan-final.md) |
| B6 | hmw fan-out | Opus 5 MAX execute plan | per-wave | ✅ 5/5 wave commit + verify-local |
## B6 — kết quả 5 wave (commit per-wave, PUSH GOM CHỜ)
| wave | commit | worker | ô máy | ghi chú |
|---|---|---|---|---|
| W4 | `1cc16a2d` | impl-fe (sạch) | 4/4 | 7 dòng resolvePath; lead-gate diff PASS |
| W1 | `09ebd20e` | impl-fe (sạch) | 10/10 | bug A+B fix + slice HD-*; IDE-diag never-read = snapshot dở #68, bác bằng tsc+wire-site |
| W2 | `7b291014` | impl-fe **#53 sau-code-trước-sổ** | 9/9+build+eslint (lead đo) | lead commit on-behalf; diff tự khai delta WorkflowHistoryPanel |
| W3 | `ed3b39ff` | impl-fe **#53 đúng-bước-commit** | 10/10 (artifact trọn) | worker tự bắt ô-2a-không-răng; lead spot-check 4 số khớp → commit on-behalf |
| W5 | `2ae62f3f` | 3-worker: test-spec (RED 13 Fact, #53 trước chạy-đỏ — lead xác nhận compile-RED) → impl-be (GREEN **693/693 vòng-1**, #53 artifact trọn) → impl-fe W5c (sạch, 10/10 — tự bắt bẫy tự-trích-dẫn `<Dialog`) | đủ | lead commit MỘT phát 7 file; gate-reviewer CHẾT NON 0-ruột → **lead-gate on-behalf có khai** (G-1 Roles-shape · G-2 UserInfo.roles thật · G-3 host-404-ổn; FLAG reason=GIỮ-y-khuôn-KHKK · onDeleted=carry) |
**VERIFY LOCAL ④** (`b6-verify-local-synthesis.md`): 5/5 wave hành-vi SỐNG — bug A chết (ct=1→
nhóm01 · 6→06 · 7→07) · bug B chết (1 leaf highlight) · slice per-loại + control ct=99 · layout
hoán-đổi + `?id=` ở-lại-trang + HĐ-ngoài-gói hiện list · pendingMe gọi `/contracts/inbox` thật +
chip + search-khoá-có-chữ · admin 17 leaf + HdTP khoá mã · **W5 end-to-end: nút ẩn@9/hiện@2
xoá → list vắng + Đã-xóa có + changelog 1-dòng + IsDeleted** (T26 không dẫm). 2 ô DEFER-có-khai
(leaf-cây GĐ3 + so-số pendingMe — thiếu DỮ LIỆU không phải nghi CODE).
**Tồn sau run:** đo lại `docs/STATUS.md:476` row Tests = DEFERRED-TO-DEPLOY · `[carry:w8-delete-policy]`
· `[carry:w5-host-ondeleted]` · Q1-Q5 chờ anh (spec §Q) · PUSH GOM khi anh cho giờ dọn runner (P0).
## taskList snapshot — B1 (single deep-pass)

View File

@ -1,7 +1,76 @@
# sub-b6-w5c-fe — W5c port nút Xóa vào ContractDetailContent (×2 app)
Vai: implementer-frontend · S190 · YC-029 · plan `sub-b5-plan-final.md` block `### W5` việc 4+5.
Ranh đã giữ: Write/Edit CHỈ `fe-{user,admin}/src/components/contracts/ContractDetailContent.tsx` + file này. KHÔNG commit. KHÔNG chạm BE / host / agent-memory.
## Việc đã làm
### BƯỚC 0 — baseline đo TRƯỚC khi gõ (đĩa, không tin trí nhớ)
| Ô | fe-user | fe-admin |
|---|---|---|
| `sha256sum ContractDetailContent.tsx` | `cd81508f97cb…` | `cd81508f97cb…` (**identical sẵn**) |
| `grep -c 'window.confirm'` | 0 | — |
| `grep -c '<Dialog'` | **1** | 1 |
| `grep -c 'File MIRROR SHA256 identical'` | 0 | 0 |
| `npx eslint .` | 47 problems | 42 problems |
### BƯỚC 1 — khuôn đã đọc (neo tươi 2026-08-12, khớp plan, KHÔNG kích Đ-5)
- Thanh nút đáy KHKK `fe-user/src/components/khkk/KhkkDetailContent.tsx:1076-1097``sticky bottom-0 z-10 … border-t border-slate-200 bg-white/95 … backdrop-blur` + `<button>` `bg-red-600` + `Trash2 h-3.5 w-3.5` + `isPending ? 'Đang xóa…'` + `window.confirm` CÓ MÃ + CÓ ĐÍCH (`:1090`).
- Mutation `deletePlan` `:316-320` — 3 mảnh (`mutationFn` `api.delete` · `onSuccess` toast + `onDeleted?.()` · `onError` `getErrorMessage`).
- Prop khai `onDeleted?: () => void` + JSDoc `:164/:171`.
- `isDrafter` null-safe `fe-user/src/components/pe/PeDetailTabs.tsx:141` = `currentUser?.id != null && X.drafterUserId === currentUser.id`; auth = `const { user: currentUser } = useAuth()` (`@/contexts/AuthContext`, `:129`), `isAdmin = currentUser?.roles?.includes('Admin') ?? false` (`:130`). `UserInfo{id,email,fullName,roles}` **giống hệt 2 app** (`fe-{user,admin}/src/types/auth.ts:1-6`) ⇒ copy-byte an toàn.
- SF-17 khuôn khai mirror: `KhkkDetailContent.tsx:2` — fe-admin ghi **NGUYÊN VĂN** cùng câu ("…với fe-admin counterpart") ⇒ đúng là CÙNG BYTES, không đổi chữ theo app. Đã theo.
- Phase const: `ContractPhase.DangInKy=5 · TraLai=98 · TuChoi=99` (`types/contracts.ts:3-13`); `drafterUserId: string | null` (`:154`) — đúng plan.
### BƯỚC 2 — thi công fe-user rồi copy-byte fe-admin (+82/1 mỗi bên, đối xứng)
1. **Header MIRROR (SF-17)** — thêm `// File MIRROR SHA256 identical với fe-admin counterpart.`
⚠️ **Lệch có chủ đích so lệnh "dòng 2":** đặt ở **dòng 5**, ngay SAU đoạn mở đầu 4 dòng, vì dòng 1-4 là MỘT câu liền mạch ("Reusable detail body — used by … WorkflowHistoryPanel (Panel 3)."); chèn vào giữa là cắt câu làm đôi. Ô đo C-MIRROR-KHAI không neo số dòng (`grep -c` = 1) ⇒ 0 ảnh hưởng.
2. **Import**`Trash2` vào cụm lucide (`:8`) + `useAuth` từ `@/contexts/AuthContext` đặt giữa cụm `@/components/ui/*``@/lib/api` (đúng thứ tự nhóm đang có).
3. **Prop mới** `onDeleted?: () => void` + JSDoc khai thẳng "KHÔNG truyền ⇒ chỉ 3 `invalidateQueries` chạy…" (không hứa hành vi không có).
4. **Mutation `deleteContract`** — 3 mảnh đúng khuôn: `api.delete('/contracts/${c.id}', { params: reason?.trim() ? { reason: reason.trim() } : undefined })` · `onSuccess` invalidate ĐÚNG bộ key của chính file (`['contract', c.id]` + `['my-contracts']` + `['inbox']`, khuôn `transition` `:57-61`) + `toast.success('Đã xóa hợp đồng.')` + `onDeleted?.()` · `onError` `toast.error(getErrorMessage(err))`.
5. **Rào hiển thị `canDelete`** = `(phase < DangInKy || phase === TraLai || phase === TuChoi) && (isDrafter || isAdmin)`.
6. **Thanh nút đáy** `sticky bottom-0` + `window.confirm` mang MÃ + ĐÍCH + `Trash2` + `Đang xóa…` + dòng chữ phụ "Xoá mềm — dữ liệu không mất khỏi hệ thống."
7. **Comment khối `[W5c S190 · YC-029]`** khai: đây là BẢN SAO HIỂN THỊ của guard BE (BE mới là chỗ chặn thật) · vì sao `confirm` chứ không hộp thoại thứ hai · vì sao `currentUser?.id != null` phải đứng trước.
8. `cp` fe-user → fe-admin (copy-byte, không gõ tay lần 2).
### Đối chiếu FE ⟂ BE (không tin plan, đọc handler đã land W5b)
`awk '/class DeleteContractCommandHandler/,/^}/' src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs`:
```
var phaseAllowed = entity.Phase < ContractPhase.DangInKy
|| entity.Phase == ContractPhase.TraLai
|| entity.Phase == ContractPhase.TuChoi;
```
+ `isOwner = entity.DrafterUserId == currentUser.UserId` · `isAdmin = currentUser.Roles.Contains(AppRoles.Admin)` · `!isOwner && !isAdmin → Forbidden`.
⇒ FE `canDelete` **cùng hình dạng cả 2 vế**. Controller giữ `[Authorize]` trần + `[FromQuery] string? reason` (đã đọc `ContractsController.cs` khối DELETE) ⇒ endpoint nhận `?reason=` thật, không phải tôi bịa hình.
### 🔴 Bẫy tự-trích-dẫn (S188 tái phát, đã bắt TRONG lượt)
Bản nháp comment viết nguyên văn token mở hộp thoại 2 lần ⇒ `grep -c '<Dialog'` **1 → 2**, tức bình luận của tôi tự phá ô đo C-12 vế 3 (gate đếm theo DÒNG, không phân biệt code ⟂ chữ). Đã sửa: comment nói "hộp thoại `Dialog`" (bỏ dấu `<`), đo lại = 1. Token `window.confirm` thì **cố ý giữ 2 dòng lineage** (nhắc khuôn KHKK) ⇒ raw = 3, **code-only = 1**; báo cả 2 số ở dưới thay vì báo mỗi số đẹp.
## Ô acceptance
| Ô | Lệnh đo | Kết quả | Verdict |
|---|---|---|---|
| **C-12 (a)** | `grep -c 'window.confirm'` fe-user | **3** raw (1 code + 2 dòng comment lineage); lọc bỏ dòng chú-thích ⇒ **1 code** | ✅ ≥1 |
| **C-12 (b)** | `grep -A2 'window.confirm' … \| grep -c 'Đã xóa'` | **1** | ✅ ≥1 |
| **C-12 (c)** | `grep -c '<Dialog'` fe-user / fe-admin | **1 / 1** (baseline 1 — GIỮ NGUYÊN sau khi gỡ bẫy tự-trích-dẫn) | ✅ |
| **C-MIRROR-KHAI** | `grep -c 'File MIRROR SHA256 identical'` | fe-user **1** · fe-admin **1** (baseline 0/0) | ✅ |
| **SHA cặp** | `sha256sum` 2 bên | `7ad3aed5efe99bb4c8e640838f8a9721064e42a42ab09a08d58ba24eb6c76b97` **× 2 khớp** (baseline chung `cd81508f97cb…` ⇒ đổi thật, vẫn khớp cặp) | ✅ Đ-2 không kích |
| **C-2 build ×2** | `npm run build` | fe-user **EXIT 0** (`✓ built in 485ms`) · fe-admin **EXIT 0** (`✓ built in 469ms`) | ✅ |
| **C-1a eslint ×2** | `npx eslint .` | fe-user **47** (baseline 47) · fe-admin **42** (baseline 42) — **0 TĂNG**, `grep -c ContractDetailContent` trong CẢ 2 log = **0** ⇒ file tôi sửa không đẻ hit nào | ✅ Đ-1 không kích |
| **Scope** | `git diff --stat` | đúng 2 file, **+82/1 mỗi bên** (đối xứng ⇒ chứng copy-byte, không gõ tay lệch) | ✅ |
**Warning build = PRE-EXISTING** (chunk-size >500KB + `INEFFECTIVE_DYNAMIC_IMPORT src/lib/realtime.ts`) — có ở CẢ 2 app từ trước, không phải sản phẩm wave này.
## DEFERRED/DỪNG
- **Ô hành-vi (bấm nút thật, xem HĐ rụng khỏi danh sách / toast / 403-409 từ BE) = `DEFERRED-TO-LOCAL-VERIFY`** — đúng đường thoát Đ-7, KHÔNG tick.
- **Host chưa truyền `onDeleted` (5 host, KHÔNG sửa — ngoài scope Đ-4):** `fe-user` `pages/contracts/ContractDetailPage.tsx:28` · `pages/contracts/MyContractsPage.tsx:282` · `pages/InboxPage.tsx:347`; `fe-admin` `pages/contracts/ContractDetailPage.tsx:28` · `pages/contracts/ContractsListPage.tsx:254`. Hành vi hiện tại sau khi xoá = 3 `invalidateQueries` chạy ⇒ danh sách rụng dòng; query `['contract', id]` refetch ra **404** ⇒ host rơi về nhánh sẵn có ("Không tìm thấy HĐ." ở `ContractDetailPage`, hoặc khối detail biến mất ở host 3-panel vì điều kiện `selectedId && detail.data`). Đã kiểm: **KHÔNG có `QueryCache`/`MutationCache` onError toàn cục**`fe-user/src/main.tsx|App.tsx` ⇒ 404 này KHÔNG bắn toast đỏ đè lên toast thành công. Muốn "rời màn detail" mượt hơn thì host truyền `onDeleted` — 1 dòng/host, đề xuất cho lead, tôi KHÔNG tự làm.
- **`reason` là DÂY CẮM CHƯA AI KÉO (khai thẳng, để reviewer cân):** `mutationFn` nhận `reason?: string` cho đúng hình endpoint `[FromQuery] string? reason`, nhưng UI **không thu lý do** (giữ khuôn KHKK `:1090` — lệnh ghi rõ "KHÔNG `window.prompt`, đừng sáng tạo") ⇒ mọi call là `mutate(undefined)`, axios bỏ hẳn key, request y hệt `DELETE /contracts/{id}`. Đây là tham số optional **chưa có caller truyền giá trị** — cùng họ (nhẹ hơn) với "gán mà không ai đọc". Giữ vì lệnh W5 yêu cầu `params { reason }`; nếu reviewer coi là dây chết thì gỡ đúng 1 dòng.
- **KHÔNG chạm:** BE (`ContractFeatures.cs` / `ContractsController.cs` / `ContractChangelog.cs` — đang `M` do W5b, không phải tôi), test `ContractDeleteGuardTests.cs` (`??` của W5a), `docs/`, host page, `agent-memory/`, STATUS.
- **Ghi chú artifact:** khối RUN-TRACE hệ thống đặt tên `sub-implementer-frontend-0.md`, lệnh trực tiếp của lead đặt tên `sub-b6-w5c-fe.md` (đã Write khung ở lượt 1 theo lệnh). Giữ **một** file này để tránh 2 bản sự thật; `subMdPath` trả về trỏ đúng đây.

View File

@ -0,0 +1,37 @@
# GATE W5 — reviewer (YC-029, commit 2ae62f3f)
> ⚠️ Lane reviewer chết non #53 (40 tool-use, 0 ruột — ca đầu tiên của chuỗi không để lại gì
> trên đĩa). LEAD tự gate on-behalf @S190 bằng đo trực tiếp — KHAI rõ đây là lead-gate, không
> phải reviewer độc lập. Cân nhắc: W5 đã qua 4 tầng kiểm trước đó (13 test T-matrix xanh vòng-1
> · lead-awk thứ-tự 5 nấc · W5c artifact đối-chiếu FE⟂BE từng vế "đọc handler không tin plan"
> · style-gate B4 be2 soi trước khi viết) ⇒ chi phí relaunch 155K > giá trị biên; 3 câu sắc
> còn lại đo được trực tiếp.
## Verdict
**GATE-W5: PASS (lead-gate) — 3/3 câu sắc đo PASS, 2 FLAG treo phán xong, 0 finding mới.**
## Findings (G-1..) — 3 câu sắc đo trực tiếp
- **G-1 Roles shape** ✅ — `ICurrentUser.Roles = IReadOnlyList<string>` (`ICurrentUser.cs:8`);
khuôn KHKK prod dùng Y HỆT `Roles.Contains(AppRoles.Admin)` (`ContractSigningPlanFeatures.cs:285,298`)
⇒ W5b cùng khuôn đã-deploy, không có case-sensitivity drift.
- **G-2 FE isAdmin nguồn thật** ✅ — `ContractDetailContent.tsx:123`
`currentUser?.roles?.includes('Admin')`; `UserInfo.roles: string[]` TỒN TẠI (`types/auth.ts:5`)
⇒ không phải optional-chain-trên-field-ma; render-condition `:125-127` khớp từng vế BE-guard
(phase-set + isDrafter-null-safe ‖ isAdmin).
- **G-3 hành-vi sau xoá khi host không truyền `onDeleted`** ✅ chấp nhận — detail 404 →
`ContractDetailPage.tsx:22` placeholder "Không tìm thấy HĐ." (không crash, không toast đỏ đè
— W5c đã chứng 0 QueryCache onError toàn cục); từ MyContractsPage panel-2: list invalidate
rụng dòng + panel về placeholder.
## Phán 2 FLAG treo
1. **`reason` dây-chưa-kéo → GIỮ.** Trạng thái Y HỆT khuôn KHKK prod: BE `[FromQuery] reason`
sống, UI confirm-only không thu lý do (khuôn `KhkkDetailContent:1090`). Đường ống sống
end-to-end (API gọi được với reason) — khác class ghost-wire (defined-but-unread). Khi nào
owner muốn thu lý do = 1 việc FE nhỏ, 0 đổi BE.
2. **5 host chưa truyền `onDeleted` → KHÔNG vá NGAY.** Hành vi thay thế (G-3) acceptable.
Polish "clear selection sau xoá" = nợ nhỏ: `[carry:w5-host-ondeleted]` (5 site đã liệt trong
sub-b6-w5c-fe.md — fe-user ContractDetailPage:28 · MyContractsPage:282 · InboxPage:347 ·
fe-admin ContractDetailPage:28 · ContractsListPage:254).