9.0 KiB
review-synthesis — S156 wave 2 · /fable-clone reviewer 4 lăng kính
Lead-written (P3/P4 refute + synthesize). Run
wf_1618ae3c-a17· 4/4 lane sạch, 0 chết, 0 rỗng (đóng gói C2 có tác dụng: ≤3 file/lane + ép khung rỗng lượt 1-2 + trần 25; lane tốn nhiều nhất 7/25 lượt). Nguồn:sub-review-verdict-1.md·sub-review-fidelity-2.md·sub-review-q2-3.md·sub-review-gaps-4.md
Bảng verdict
| Lăng kính | Verdict | Điểm |
|---|---|---|
| lens-verdict | LUNG-LAY |
11 (3H · 7M · 1L) |
| lens-fidelity | CO-VAN-DE |
10 (2H · 3M · 5L) |
| lens-q2 | THIEU-PHUONG-AN + thiên-vị-có-hướng |
14 (4H · 6M · 4L) |
| lens-gaps | 4/4 claim THẬT, 0 dương-giả | 2 đáng làm · 1 đổi khung · 1 sai tầng |
Tổng 35 điểm. Lõi verdict LAI SỐNG (cả 4 lane không lane nào bác thế chia đôi 7→12 dựng mới ⟂
13→21 tái dùng). Cái đổ là NHÃN CHI PHÍ và CÁCH ĐẶT VẤN ĐỀ, không phải kết luận.
A. 🔴 2 phát hiện MỚI — reviewer đào ra, invest KHÔNG có
A1. LỖ HỔNG AN NINH THẬT trên production (lead đã tự verify độc lập)
ContractWorkflowService.cs:48-66 — nhánh Reject chạy TRƯỚC MỌI GUARD:
if (decision == ApprovalDecision.Reject) {
... contract.Phase = TuChoi | TraLai
contract.SlaDeadline = null
SaveChanges(); return; ← thoát luôn, không qua guard nào phía dưới
}
0 kiểm actorRoles · 0 kiểm actor có phải người duyệt lượt này · 0 kiểm fromPhase.
Tầng 2 (authz API) cũng hở: ContractsController.cs:13 = đúng 1 [Authorize] trần, 0 per-action
policy, phủ 22 endpoint ghi. Đối chứng loại trừ "dự án không làm kiểu đó":
PurchaseEvaluationsController có 3 [Authorize(Policy...)].
⇒ MỌI user đã đăng nhập có thể Từ-chối / Trả-lại BẤT KỲ hợp đồng nào — kể cả HĐ đã DaPhatHanh
(terminal, đã phát hành). Và SlaDeadline bị xoá kèm.
🔴 Đây là gotcha #82 TÁI PHÁT (feedback_permission_grant_two_layers: display-layer ⟂ API-authz-layer
là 2 tầng độc lập; [Authorize] trần = lỗ hổng). Lần trước bắt ở ReportsController @S118 — cùng hình dạng.
KHÔNG liên quan tính năng mới; nó đang sống trên prod.
A2. PMH không trình được HĐ — 403 ngay bước 13/17
ContractWorkflowService.cs:70-79: gate trình DangSoanThao|TraLai → ChoDuyet đòi role
Drafter hoặc DeptManager. PMH (Phòng cung ứng) mang role Procurement ⇒ ForbiddenException.
Mà sơ đồ giao PMH trình ở cả b.13 lẫn b.17. Đúng lớp "cơ-chế đúng, thứ đi qua nó không có" — lớp đã
cắn 4 lần đợt PE, nay lần 5.
B. 1 claim của invest SAI SỰ THẬT (phải sửa trước khi trình owner)
Invest §1:51-55 viết cầu PE→HĐ "rơi nhánh V1 legacy (hardcoded policy fallback nếu V1 không có active def)".
Sai. Đo: grep WorkflowPolicyRegistry|WorkflowTypeAssignment trên ContractWorkflowService.cs = 0 hit.
Đường thật: :98 → :108-113 → :115-116 throw ConflictException.
⇒ Nếu activeWfId null thì HĐ sinh từ phiếu trình được nhưng KHÔNG AI DUYỆT ĐƯỢC — kẹt cứng ở ChoDuyet.
Hỏng CỨNG, không "degrade êm" như invest mô tả. Nặng hơn chứ không nhẹ hơn.
🔑 Vì sao invest sai: nó tin skill-doc contract-workflow (mô tả LoadPolicyAsync có fallback
WorkflowPolicyRegistry) hơn đĩa. Trên đĩa fallback đã chết ở đường transition, chỉ còn sống ở đường
hiển thị (ContractFeatures.cs:448-455) ⇒ lệch DISPLAY ⟂ GUARD, và doc stale che luôn cái lệch đó.
C. Q2 — cách đặt vấn đề THIÊN VỊ CÓ HƯỚNG (owner đã nói "cần bàn thêm", nên phần này quan trọng nhất)
| # | Vấn đề | Chứng |
|---|---|---|
| H1 | 3 phương án KHÔNG cùng phạm vi ⇒ so sánh chi phí vô nghĩa (apples-to-oranges) | option-space thật là 2 trục, không phải 3 điểm |
| H2 | Thiếu phương án tái dùng module Proposal (ApplicableType=4) |
có sẵn trong repo |
| M1 | Phương án "PE + workflow thứ 2" bị bác ở §1 nhưng không hiện trong Q2 ⇒ owner đọc §6 không biết nó tồn tại. Và lý do bác dựng "1 cột" thành bất-khả kiến trúc, trong khi chính tài liệu này coi AddColumn là rẻ ở chỗ khác (Mig 53 "3 AddColumn", Mig 67 "11 cột") ⇒ tiêu chuẩn kép |
|
| M2 | Thiếu phương án "Contract-sớm nhưng tách entity con" | |
| M3 | Hiệu ứng hào quang: dữ kiện "rẻ" DUY NHẤT (ApplicableType=10 append-only) lại gắn vào phương án ĐẮT NHẤT (a) — mà slot enum là lát mỏng nhất của chi phí (a) (thật: 4 bảng + ~600 LOC BE + ~1.956 LOC FE) |
|
| M4 | Bất đối xứng ngôn từ: (a) và (c) mở bằng lợi ích, chỉ (b) bị gắn tính từ rủi ro; 0 phương án nào có rủi ro định lượng. Câu chốt "Em nghiêng (a) hoặc (c) — (b) khuyên tránh" đặt khuyến nghị TRƯỚC lời mời chốt ⇒ thu hẹp còn 2 lựa chọn ngay trong câu hỏi | |
| HIGH | Chi phí là CẢM TÍNH dù bản sao đo được nằm sẵn trong repo (Proposal / Mig 38) |
Cách sửa (reviewer đề, lead đồng ý): mỗi phương án kèm 3 số cùng đơn vị — số bảng mới · LOC BE · LOC FE — lấy từ twin thật trong repo, không ước.
D. 4 lỗ invest tuyên bố: 0 DƯƠNG-GIẢ, nhưng 2 lỗ đóng khung sai
| Lỗ | Verdict reviewer |
|---|---|
| L1 cầu PE→HĐ bỏ pin V2 | ✅ THẬT, đáng làm (và hậu quả nặng hơn invest nói — xem §B) |
L2 CeoApprovalThreshold ghost-wire phía HĐ |
✅ THẬT, đáng làm — vòng lặp "đặt được → lưu → hiện lại → không ai đọc" khép kín, chứng từng mắt xích. AllowApproverFinalize cũng vắng |
L3 SLA hardcode AddDays(7) |
✅ THẬT nhưng đổi khung: là thụt lùi so với V1 và là lỗ toàn-V2 (cả PE), không riêng HĐ |
L4 AttachmentPurpose thiếu ký-nháy |
⚠️ Đúng chữ, sai tầng: Purpose hiện là nhãn KHÔNG AI THI HÀNH — validator chỉ IsInEnum(), 0 nhánh Purpose == nào trong Backend. Thêm enum là vô nghĩa nếu không dựng cổng đọc nó |
🔑 Reviewer chốt: "Invest không bịa lỗ nào; điểm yếu là ĐÓNG KHUNG, không phải bịa dữ kiện."
E. Điểm reviewer CỦNG CỐ cho invest (adversarial ≠ luôn hạ điểm)
Lập luận Q1 của invest ("phase bị gỡ chính là trạm của b.13→21") ĐỨNG, và reviewer tìm được chứng
MẠNH HƠN thứ invest dùng: bảng vai còn sống ContractFeatures.cs:369-371 map
DangKiemTraCCM→CostControl · DangTrinhKy→Director/AuthorizedSigner · DangDongDau→HrAdmin
= trùng khít chuỗi CCM→CEO→HR của b.17→18→19. Trùng 3 vai liên tiếp đúng thứ tự ≈ loại trừ "trùng
hợp tên gọi" — trong khi comment enum chỉ chứng được "đã deprecated", không chứng được "vì sao".
Đính chính biên của invest: chỉ b.14→19 rơi vào vùng phase-đã-gỡ. b.13 dùng DangSoanThao=2 và
b.20-21 dùng DaPhatHanh=9 — cả hai CÒN SỐNG. Invest viết "13→21" là nống biên.
F. Nhãn đúng cho verdict (thay "TÁI DÙNG")
"Tái dùng KHUNG V2 + WIRE MỚI 4 đường sống (visibility
ChoDuyet· inbox · notify approver kế · guard reject) + 1 rẽ nhánh tiền (ngưỡng CEO)"
ApproveV2Async gánh được KHUNG (steps :234 · level-group :246 · OR-of-N :259-260 · advance
:365-392 · terminal :371-385 không gen mã đúp) nhưng KHÔNG gánh được ĐƯỜNG SỐNG:
grep ChoDuyet ContractFeatures.cs = 0 hit ⇒ view-guard :343-349 + inbox :365-371 legacy-only
⇒ người duyệt không mở được phiếu đang chờ chính mình. Notify :407 chỉ báo Drafter.
G. Việc phải làm trước khi trình owner 14 câu hỏi
- Sửa claim SAI ở invest §1:51-55 (
hardcoded fallback→ConflictException, nâng lên mức CHẶN) - Siết biên §0/§1:
b.14→19chứ khôngb.13→21 - Thêm Q mới: vai người TRÌNH (
:70-79PMH 403) - Thêm cảnh báo A1 lỗ hổng reject — nhưng đây là việc RIÊNG, không thuộc spec BCH
- Q5 thêm phương án (d)
skipToFinal+AllowApproverSkipToFinal :322— đường bỏ-qua-CEO ĐÃ WIRE ở Contract V2, 0 code BE. Invest bỏ sót hẳn. - Viết lại Q2 theo 2 trục + 3 số đo cùng đơn vị lấy từ twin repo