11 KiB
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 S112–S114).
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 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, A–E 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 A–E 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 là id (task-id nội bộ Gitea), cột đầu của bảng :35-38.
🔴 ERRATA-CỦA-ERRATA @S159 (H2
harvest-curatorF-02 bắt — lead ghi on-behalf): bản đầu của chính dòng này gọi531là "test-count" — SAI. Header bảngcicd-verify-s157-diadiem.md:35=| id | run# | head_sha | status | created | updated |; dãyid529/530/531 ↔ run# 416/417/418 liên tục, khoá chặt (đối chứngcicd-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à
Draftermất quyền xóa nháp của chính mình (đo thật: 11/13 roleCanDelete=0) - 7c — seeder
:2120-2126skip-if-exists và không bao giờ chạmCanDelete⇒ 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ÔNGg.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 —
IgnoreQueryFilterschỉ ở đú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-2096và:2492-2496 - 11 — đổi nhãn phải qua
labelBackfill; sửatree.Addlà vô tác dụng trên DB đang chạy - 19 —
PeWorkflowPanel.tsxphả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:
- policy tồn tại nhưng 11/13 vai
CanDelete=0 - endpoint PUT sống nhưng 0 dây FE gọi
- policy chặn đúng approver cần dùng
- 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àorun.mdngay 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.