8.5 KiB
sub-review-gaps-4 — reviewer ADVERSARIAL verify 4 lỗ (S156 BCH post-CEO flow)
KHUNG RỖNG ghi @lượt-1 TRƯỚC KHI ĐỌC (chống #53 return-garble). Append dần theo lượt. ⚠️ PATH-CONFLICT khai báo: task-body chỉ định file này (
sub-review-gaps-4.md); RUN-TRACE footer chỉ địnhsub-reviewer-3.md. Áp luật "fix-mới thắng footer-cũ" → ghi vào file này, khai trong return.
0. Meta
- Vai: reviewer adversarial (READ-only verdict, KHÔNG sửa code-tree)
- Nguồn claim:
sub-invest-bch-1.md(investigator) - Ngân sách: 25 lượt (1-2 khung · 3-18 verify · 19-25 chốt)
- Thang chấm mỗi lỗ: THẬT / DƯƠNG-GIẢ / ĐÚNG-NHƯNG-KHÔNG-QUAN-TRỌNG
- Kỷ luật phép đo: MỌI grep 0-hit phải kèm chứng-có-răng (grep chuỗi BIẾT CHẮC có, cùng file/cùng lệnh) trước khi kết luận.
1. Trạng thái 4 claim (điền dần)
| # | Claim (tóm) | Verdict | Bằng chứng file:line | Chứng-có-răng |
|---|---|---|---|---|
| L1 | cầu PE→HĐ pin V1 WorkflowDefinitionId, không nhận ApprovalWorkflowId V2 |
THẬT (cao) | CreateContractFromEvaluationFeatures.cs:68-71,108 vs ContractFeatures.cs:105,109; hệ quả ContractWorkflowService.cs:98 |
cùng-file có hit ApprovalWorkflowId ở :195,199 ⇒ pattern đúng chính tả, vắng-mặt trong initializer là THẬT |
| L2 | CeoApprovalThreshold 0 consumer phía HĐ |
THẬT (cao, ghost-wire) | field ApprovalWorkflow.cs:43; ApplicableType.Contract=3 ApprovalWorkflow.cs:57; ContractWorkflowService.cs:228-232 nạp aw mà không đọc; quyết-định terminal :366-385 thuần vị-trí; đối-chứng PE PurchaseEvaluationWorkflowService.cs:894 |
grep repo-wide CeoApprovalThreshold = 40+ hit; grep cùng-file ApproveV2Async = 7 hit ⇒ file greppable, 0-hit là THẬT |
| L3 | SLA V2 hardcode AddDays(7), Step không có SlaDays |
THẬT (vừa) | ApprovalWorkflow.cs:70-79 (Step 4 prop, 0 SLA); hardcode ContractWorkflowService.cs:341,358,390 (trong ApproveV2Async) + :85,182,194 |
SlaDays CÓ ở WorkflowDefinition.cs:37 (V1) + PurchaseEvaluationWorkflowDefinition.cs:32 ⇒ token đúng chính tả |
| L4 | AttachmentPurpose không phân biệt ký-nháy/ký-chính |
ĐÚNG-NHƯNG-KHÔNG-QUAN-TRỌNG (như phát biểu) | ContractAttachment.cs:5-11 đúng verbatim; nhưng 0 nhánh logic đọc Purpose: ContractAttachmentFeatures.cs:51,80,89 + ContractsController.cs:78 |
grep Purpose toàn Backend = 25+ hit (Office/PE/VehicleBooking) ⇒ pattern có răng; riêng nhánh Purpose == = 0 |
2. Nhật ký đo (append theo lượt)
Lượt 1-2 — khung rỗng
- Đã ghi file này trước khi đọc bất kỳ file nguồn nào. Chưa đo gì.
Lượt 3-10 — đo
L1. Cầu PE→HĐ chỉ truy V1: CreateContractFromEvaluationFeatures.cs:68-71 query db.WorkflowDefinitions.Where(w => w.ContractType == request.ContractType && w.IsActive), rồi :108 gán WorkflowDefinitionId = activeWfId. KHÔNG có dòng nào gán ApprovalWorkflowId. Nhánh tạo-HĐ-thường thì gán CẢ HAI: ContractFeatures.cs:105 (WorkflowDefinitionId = activeWfId) + :109 (ApprovalWorkflowId = request.ApprovalWorkflowId, comment ghi rõ "[Plan B S29 2026-05-22 Chunk E1] Pin V2 workflow nếu Drafter pick"). Cổng rẽ nhánh: ContractWorkflowService.cs:98 if (contract.ApprovalWorkflowId is Guid awId) → ApproveV2Async, else rơi V1 legacy :108-149.
Hệ quả ĐO ĐƯỢC của việc rơi legacy (không phải suy đoán): (a) skipToFinal ném cứng ConflictException — ContractWorkflowService.cs:104-106; (b) mất Section 5 ý-kiến-cấp-duyệt V2 — ContractDtos.cs:51-53 ghi "nếu null → V1 legacy KHÔNG Section 5 V2"; (c) mọi cờ per-slot V2 (F2/F5/F6) vô hiệu vì chỉ đọc trong ApproveV2Async.
Hạ-nhiệt trung thực (chống thổi phồng): HĐ sinh từ phiếu vẫn DUYỆT ĐƯỢC vì V1 WorkflowDefinition có seed thật (DbInitializer.cs:721,754) ⇒ activeWfId thường non-null. Đây là "rơi nhánh cũ", KHÔNG phải "hỏng". Rủi ro có điều kiện: nếu 1 ContractType không còn V1 def IsActive thì activeWfId=null → tới lượt duyệt mới ném ConflictException "HĐ chưa pin workflow definition" (ContractWorkflowService.cs:115-116) — tức lỗi hiện MUỘN ở bước duyệt chứ không chặn lúc tạo.
L2. 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:
- Đặt được: input "Ngưỡng giá trị gói CEO (đ)" render VÔ ĐIỀU KIỆN
ApprovalWorkflowsV2Page.tsx:907-912— không có bọcapplicableType === 1|2(mọi hitapplicableTypetrong file là routing/label/payload::262,268,417,733,761). - Nhận: validator chỉ chặn
< 0—ApprovalWorkflowV2AdminFeatures.cs:277-278; KHÔNG gate theoApplicableType. - Lưu:
ApprovalWorkflowV2AdminFeatures.cs:352passthrough. - Hiện lại: badge "Ngưỡng CEO: … đ"
ApprovalWorkflowsV2Page.tsx:474-479⇒ admin nhìn thấy xác nhận một luật KHÔNG tồn tại. - Không đọc:
ContractWorkflowService.ApproveV2Async:228-232nạp NGUYÊN entityaw(Include Steps→Levels) nhưng không hề chạmCeoApprovalThreshold; quyết định kết-thúc:366-385thuần vị-trí (nextIdx >= steps.Count).
Leo thang ngoài phát biểu của invest: cờ F5 AllowApproverFinalize ("Duyệt là kết thúc, không trình CEO") CŨNG vắng trong ContractWorkflowService.cs — grep 0 hit, trong khi AllowApproverSkipToFinal có 3 hit cùng file (:210,319,322) ⇒ chứng grep có răng. Vậy phía HĐ thiếu CẢ HAI cơ chế quyết-định-theo-giá-trị mà PE đã có. Lỗ rộng hơn 1 cột.
L3. Schema: ApprovalWorkflowStep (ApprovalWorkflow.cs:70-79) đúng 4 prop ApprovalWorkflowId / Order / Name / DepartmentId — không SLA; ApprovalWorkflowLevel:95-171 cũng không. Hardcode trong ApproveV2Async: :341 (skipToFinal), :358 (sang Cấp kế), :390 (sang Bước kế) — cả 3 đều AddDays(7).
Hai đính chính so với phát biểu của invest:
- Đây là THỤT LÙI so với V1, không phải "chưa làm": V1 đọc SLA theo cấu hình (
WorkflowPolicy.cs:199sla[s.Phase] = s.SlaDays is int d ? …, nguồnWorkflowDefinition.cs:37), và chính lúc tạo HĐ vẫn xàiworkflow.GetPhaseSla(...)(ContractFeatures.cs:110). - Không phải lỗ riêng của HĐ — là lỗ toàn schema V2: PE V2 hardcode y hệt (
PurchaseEvaluationWorkflowService.cs:268,534,838,935,960,1163).
SLA có răng thật (không phải số trang trí): SlaExpiryJob.cs:77 lọc SlaDeadline < now bắn thông báo quá hạn, :140-153 cảnh báo sắp hết hạn. Nên hệ quả = đồng hồ quá-hạn chạy sai nhịp cho MỌI bước V2, chứ không chặn luồng.
L4. Enum đúng verbatim: DraftExport=1 / ScannedSigned=2 / SealedCopy=3 / Other=99 (ContractAttachment.cs:5-11). Nhưng phát biểu bị đóng khung SAI TẦNG: Purpose hiện là nhãn không ai thi hành — validator chỉ IsInEnum() (ContractAttachmentFeatures.cs:51), lưu (:80), trả ra DTO (:89), controller mặc định Other (ContractsController.cs:78); không có nhánh Purpose == nào trong Backend, cũng không có ràng buộc "ScannedSigned chỉ ở phase DangInKy". PE còn chép nguyên nhận định này thành comment: PurchaseEvaluationWorkflowService.cs:190 "FE KHÔNG check Purpose enum → BE cũng không".
⇒ Thêm 2 giá trị enum ký-nháy/ký-chính sẽ KHÔNG tạo ra năng lực nào, vì không có mã nào đọc. Lỗ thật nằm chỗ khác và lớn hơn: đính-kèm không có cổng thi-hành theo mục-đích. Đồng thời có lối thoát rẻ sẵn có (Note — ContractAttachment.cs:21, và Other=99).
3. Kết luận
- 2 lỗ đáng làm: L1 (cầu PE→HĐ bỏ pin V2) và L2 (ngưỡng CEO là ghost-wire phía HĐ, kèm F5 cũng vắng).
- 1 lỗ thật nhưng phải đổi khung: L3 — là thụt-lùi-so-V1 và là lỗ toàn-V2 (cả PE), không riêng HĐ; hệ quả ở tầng thông-báo.
- 1 lỗ đúng-chữ nhưng sai-tầng: L4 — sửa enum là vô nghĩa nếu không dựng cổng đọc
Purpose. - 0 DƯƠNG-GIẢ. Invest không bịa lỗ nào; điểm yếu của invest là ĐÓNG KHUNG (L3 hẹp thành lỗi HĐ, L4 quy về schema thay vì thi-hành), không phải bịa dữ kiện.
END · TOTAL=4 CLAIM