Files
solution-erp/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/implement-synthesis.md
2026-07-29 08:58:23 +07:00

127 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# implement-synthesis — S155 · PE nút XÓA phiếu ở màn DUYỆT (approver-side)
> **run-id:** `2026-07-27-S155-pe-delete-approver` · phiên-LOGIC L7, window 2
> **Viết:** 2026-07-29 (retro-harvest @S159) — 🔴 **KHAI THẲNG: file này viết SAU, không phải ghi trong lúc làm.**
> Nguồn = artifact trên đĩa của chính run này + prod-state đã đo. Mọi con số dưới đây **derive từ file cụ thể**, không từ trí nhớ.
> Tiền lệ retro-harvest: `22ea8c5` (đã làm đúng việc này cho 6 folder S112S114).
---
## 0. Vì sao file này tồn tại (và vì sao nó ra muộn 2 ngày)
Run này **đã hoàn tất và đã ship prod**, nhưng thiếu file `*-synthesis.md` phẳng ⇒ Sàn-3 tín-hiệu ① xếp nó vào **orphan run-folder** ở mọi lần bootstrap sau đó. Đây đúng **kẽ dạng-4** mà [`tiep.md §0`](../../commands/tiep.md) khai sẵn: *"run.md kể kết-quả mà không có heading synthesis nào"* — máy đo **tên file**, không đo **đã harvest chưa**.
Anh chốt @S159: *"nối mạch tiếp cho xong hẳn rồi bootstrap"* ⇒ đóng nợ bằng cách viết thật, không dán nhãn.
---
## 1. Đề bài (nguyên văn UAT, không diễn giải)
Phiếu `PE/2026/A/046` · dự án FLOCK03 · hạng mục MAT-16 · trạng thái **Đã gửi duyệt** · cờ GẤP(PRO). Khối HÀNH ĐỘNG chỉ có `✓ Duyệt` · `← Trả lại`**thiếu Xóa**.
**Tra Sol (user):** *"Chỗ duyệt - tạo giúp em nút xóa với"* · *"mấy bạn bắt sai cái gói thầu"* · *"phải xóa thì nó mới ko có lũy kế lên"*
**Anh (owner):** *"Hic hôm trước có cái chỗ cho huy / mọi người nói là ko cần cái đó / giờ lại cần"*
🔴 **Đây là lỗi SỐ LIỆU, không phải UX.** `ComputePendingAsync` (S134) tính phiếu `ChoDuyet` có winner vào **lũy kế TẠM TÍNH** ⇒ phiếu bấm sai gói thầu đang treo ở bước duyệt **đang ăn ngân sách hạng mục sai**. `Trả lại` đẩy về `TraLai=98` chứ **không triệt tiêu** ⇒ số vẫn lệch. Đúng thứ user mô tả bằng chữ *"lũy kế lên"*.
---
## 2. Owner chốt — 3 lượt, 13 quyết định
| Lượt | # | Câu | Anh chốt |
|---|---|---|---|
| 2 | (5) | Quyền xóa gắn vào đâu | Cờ **`AllowApproverDelete` per-Cấp-duyệt** — **bác** `Department.ManagerUserId` |
| 2 | (6) | Màn "Đã xóa" | **CHỈ XEM**, chưa làm khôi phục |
| 2 | (7) | Sửa quy trình duyệt | *"thêm người/điều chỉnh quyền → **KHÔNG** cần tạo mới; đổi **quy trình** mới bắt buộc tạo mới"* |
| 3 | (8) | Xóa nháp vs xóa-khi-duyệt | **TÁCH 2 quyền riêng** |
| 3 | (9) | Cờ F6 per-người hay per-cấp | **PER-NGƯỜI** (`matchingLevel`) |
| 3 | (10) | `AllowApproverFinalize` | **PHÁ VỠ** |
| 3 | (11) | `CeoApprovalThreshold` | **PHÁ VỠ** |
| 3 | (12) | `Step.DepartmentId` | **AN TOÀN** |
| 3 | (13) | Cách chạy hmw | **CHIA 2 ĐỢT — F trước, AE sau** |
🔴 **Khai trung thực về (5):** quyết này **đổi nghĩa** so với chữ đầu *"người đứng đầu phòng"* → thành *"cấp nào admin tick"*. Owner chọn **với trade-off đặt rõ trên bàn** — KHÔNG phải lead hiểu nhầm rồi hợp thức hoá sau.
🎁 **Lời giải gọn cho (8)** (giữ vì nó là lý do `Menu keys=54`/`Policies=216` **không đổi**): đường xóa **nháp GIỮ NGUYÊN không policy** (Drafter không mất gì) · đường xóa-khi-duyệt = **endpoint RIÊNG** mang policy **root có sẵn** `PurchaseEvaluations.Delete`**0 key mới**.
---
## 3. Kết quả từng stage (tick theo ĐĨA, không theo ledger cũ)
| Stage | Verdict | Artifact |
|---|---|---|
| S1 — 2 Invest BE/FE | ⚠️ **cả 2 dính `#53`**, cứu từ đĩa + SendMessage-resume làm nốt | `sub-invest-be-1.md` 31.371 B · `sub-invest-fe-2.md` 22.074 B |
| S1-bis — Invest #3 (menu · "Đã xóa" · "người đứng đầu phòng") | ✅ | `sub-invest-menu-3.md` 21.748 B |
| S1-ter — Invest #4 (workflow versioning) | ✅ **25 phát hiện** | `sub-invest-wfver-4.md` 29.479 B |
| S2 — spec + checklist | ✅ **37.490 B**, 2 phần, ~50 mục | `spec-pe-delete-approver.md` |
| S3 — review spec | **PASS-WITH-FLAGS — 17 FLAG (4H/8M/5L)** · F.0 **không bác được** sau 6 đường tấn công · lead vá 4H+8M vào spec | `reviewer-spec-review.md` 28.259 B |
| S4-đợt1 — hmw 5 wave (schema F6 + Mig) | ✅ **DEPLOY `b1bae77` · CICD PASS 6/6** | `cicd-verify-dot1.md` · `reviewer-diff-dot1.md` 47.113 B |
| Đ2-W1 — BE | ✅ xóa-by-approver + list deleted + menu seed + changelog | `sub-d2-be-1.md` |
| Đ2-W2 — FE | ✅ nút xóa + 3 mục menu + màn "Đã xóa" | `sub-d2-fe-2.md` · `sub-d2-fe-3.md` |
| Đ2-W3 — test + review + deploy | reviewer **FAIL 12 FLAG** (H1 = DTO thiếu field) → lead vá → **DEPLOY `4464a46`** | `reviewer-diff-dot2.md` 51.970 B · `sub-d2-test-4.md` |
| Fix-nhanh UAT | ✅ **`f4494cf`** — click "Danh sách" lại highlight "Đã duyệt" | xem §4 |
**Phân đợt vì sao:** §F.0 chứng minh AE **không chạy được** nếu thiếu F ⇒ đợt 1 xong là UAT được ngay, biết chắc nền đúng trước khi xây tiếp; blast-radius ≥6 module nên tách để truy lỗi dễ.
---
## 4. 🔴 ĐÍNH CHÍNH ledger — `f4494cf` ĐÃ được verify
`run.md:173` ghi: *"**cicd verify cho commit này CHƯA chạy**"*. **Dòng đó STALE.** Đối chứng 2 nguồn độc lập:
| Nguồn | Bằng chứng |
|---|---|
| `cicd-verify-s157-diadiem.md:38` | bảng pre-state: `id` **531** · Run **#418** · `f4494cfe` · **success** · 07-27 14:36:54→14:42:35 |
| `cicd-verify-s157-diadiem.md:58` | bundle `BK8w3QxU` / `DYnxSI0T` **khớp chính xác baseline STATUS S156**, `Last-Modified` trỏ đúng run #418 |
| `changelog/sessions/2026-07-28-0930-*.md:17` | **cicd PASS 6/6** — lead tự đo @S156: endpoint 2/2 → 401 (control bịa 404 chứng phép đo có phân biệt) · smoke 8/8 · **D3 giao-tập-ID = 0** (37 sống ⟂ 9 xóa) · bundle rotate ×2 + 4/4 byte-marker |
⚠️ **Giới hạn khai thẳng — đừng đọc quá:** KHÔNG có artifact `cicd-verify` **riêng** cho `f4494cf`. Cái tồn tại là (a) Gitea Run #418 **success** ghi trong bảng pre-state của lượt sau, và (b) phép đo 6/6 của **lead** ghi trong session log. Hai vết độc lập, đủ để lật dòng ledger — **không đủ** để nói "cicd-monitor đã chấm riêng commit này".
🔸 **Errata phụ (nhỏ, không load-bearing):** session log `:17` viết *"Run #531 success 5m41s"* — số Run thật là **#418**; `531`**`id` (task-id nội bộ Gitea)**, cột đầu của bảng `:35-38`.
> 🔴 **ERRATA-CỦA-ERRATA @S159 (H2 `harvest-curator` F-02 bắt — lead ghi on-behalf):** bản đầu của chính dòng này gọi `531` là **"test-count"** — **SAI**. Header bảng `cicd-verify-s157-diadiem.md:35` = `| id | run# | head_sha | status | created | updated |`; dãy `id` 529/530/531 ↔ run# 416/417/418 **liên tục, khoá chặt** (đối chứng `cicd-verify-dot1.md:40` *"id=529 · run_number=#416"* + `cicd-verify-dot2.md:41` *"Run #417 (task id=530)"*). Test-count thời điểm đó = **532** (§7 file này), **không phải 531** — lệch đúng 1 nên suy-diễn sai nghe rất lọt tai.
> ⭐ **Mỉa mai đáng giữ:** errata này kết bằng *"đọc bảng đúng cột"* trong khi **chính nó đọc sai cột**. Phần SỬA (Run thật = #418) vẫn ĐÚNG và load-bearing; hỏng ở **lời giải thích**. Đây là mislabel, không phải bịa — và là lý do vòng kiểm độc lập tồn tại.
---
## 5. Ràng buộc CỨNG — thứ đáng mang sang việc sau
Đây là phần **tái sử dụng được**, không phải sử ký:
- **7a** — đường xóa **nháp** GIỮ NGUYÊN không policy; gắn vào là `Drafter` mất quyền xóa nháp của chính mình (**đo thật: 11/13 role `CanDelete=0`**)
- **7c** — seeder `:2120-2126` **skip-if-exists****không bao giờ chạm `CanDelete`** ⇒ sửa seeder **KHÔNG vá được prod**, phải tick tay hoặc viết upgrade-path
- **B2** — guard xóa-khi-duyệt = đúng lượt **∧** `matchingLevel.AllowApproverDelete` (**PER-ROW**, khuôn `:859`, **KHÔNG** `g.Any(...)` `:1182`)
- **8a** — `IgnoreQueryFilters()` **GỠ** filter chứ không ĐẢO ⇒ **BẮT BUỘC** kèm `.Where(x => x.IsDeleted)`
- **8b** — handler mới **mặc định 0 rào** ⇒ phải sao khối IDOR `PurchaseEvaluationFeatures.cs:596-617`
- **D3** — `IgnoreQueryFilters` chỉ ở **đúng 1 handler mới**; CẤM helper dùng chung trả `IQueryable`
- **12** — thêm key vào **CẢ HAI** danh sách permission seed `:2092-2096` **và** `:2492-2496`
- **11** — đổi nhãn phải qua `labelBackfill`; sửa `tree.Add`**vô tác dụng** trên DB đang chạy
- **19** — `PeWorkflowPanel.tsx` phải giữ **md5 giống nhau ×2 app**
---
## 6. ⭐ Bài học đắt nhất — lớp lỗi lặp ĐÚNG 4 LẦN trong riêng cửa này
**"Cơ-chế đúng, thứ đi qua nó không có"** — cả 4 ca **build sạch + test xanh**:
1. policy **tồn tại** nhưng 11/13 vai `CanDelete=0`
2. endpoint PUT **sống** nhưng **0 dây FE** gọi
3. policy **chặn đúng** approver cần dùng
4. DTO **thiếu field** FE đang đọc
⇒ Chốt chặn đã dựng: **T27** (fault-inject xác nhận **có răng**). Đây là cùng họ với memory `feedback_mechanism_right_data_absent` — và là lần thứ 4 nó tái diễn trong **một cửa sổ**.
🔸 **Bài phụ (meta, lộ ra khi viết file này):** ledger `run.md` **tụt hậu so với đĩa** và tự khai điều đó ở dòng bắt đầu bằng *"🔧 Ledger này tụt hậu so WAL tới `/tiep` @S156"* — nhưng dòng tự khai ấy vẫn để nguyên claim sai ở dòng ngay trên nó (*"cicd verify cho commit này CHƯA chạy"*).
> 🔧 **Cite đổi sang NEO VĂN BẢN @S159** (H2 F-03 + method m#24): bản đầu dẫn `run.md:174`, và số đó **chết sau 2 phút** vì chính lead thêm 2 dòng vào `run.md` ngay sau khi viết file này (nay là `:176`). Luật rút ra: **file được dẫn nằm trong CÙNG đợt sửa ⇒ cite bằng neo văn bản, không bằng `:NNN`** — hoặc tick ledger TRƯỚC rồi mới viết synthesis. **Doc tự khai về mình là nguồn YẾU NHẤT.** Cách chữa rẻ: tick ledger **ngay tại thời điểm deploy**, không đợi cửa sau đối chứng.
---
## 7. State sau run (đo tươi, canonical = `docs/STATUS.md`)
Mig **68** `AddPeAllowApproverDelete` (AddColumn trên `ApprovalWorkflowLevel`, **no new table**, 89 bảng giữ, reversible Down) · test **532 → 562 (+30)** · commit chain `b1bae77``4464a46``f4494cf`.
---
<!-- END implement-synthesis · S155 · TOTAL=7 mục · retro-harvest @S159 2026-07-29 -->