Files
solution-erp/.claude/workflows/runs/2026-07-27-S156-bch-post-ceo-flow/sub-review-gaps-4.md
2026-07-27 17:00:32 +07:00

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ỉ định sub-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 ConflictExceptionContractWorkflowService.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ọc applicableType === 1|2 (mọi hit applicableType trong file là routing/label/payload: :262,268,417,733,761).
  • Nhận: validator chỉ chặn < 0ApprovalWorkflowV2AdminFeatures.cs:277-278; KHÔNG gate theo ApplicableType.
  • Lưu: ApprovalWorkflowV2AdminFeatures.cs:352 passthrough.
  • 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-232 nạp NGUYÊN entity aw (Include Steps→Levels) nhưng không hề chạm CeoApprovalThreshold; quyết định kết-thúc :366-385 thuầ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:

  1. Đâ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:199 sla[s.Phase] = s.SlaDays is int d ? …, nguồn WorkflowDefinition.cs:37), và chính lúc tạo HĐ vẫn xài workflow.GetPhaseSla(...) (ContractFeatures.cs:110).
  2. 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ó (NoteContractAttachment.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