Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-c1-l2.md
2026-07-31 13:04:48 +07:00

13 KiB
Raw Blame History

c1-l2 đủ-vs-spec

Lane 2/3 — review cụm-1 (sub-invest-fable-b2-cum1.md) đối chiếu spec-v2 (spec-4gd-khkk-tong-quat-31-07-2026.md). Lens: ĐỦ-vs-SPEC (nghĩa vụ nào của spec-v2 KHÔNG được cụm-1 gánh) — mọi claim đo lại trên đĩa.

VERDICT: FAIL (cổng tiền-implement) — 17 finding / 3 HIGH. Nền vững (anchor ~95% chính xác, 0 trôi số canonical); thủng ở acceptance mù: cả 3 HIGH đều thuộc lớp "impl SAI vẫn PASS 100% checkbox". Vá bằng chỉnh acceptance + bổ sweep, KHÔNG cần làm lại invest.


A. Bảng phủ 9 quyết định §② (Q1)

Nghĩa vụ Cụm-1 gánh ở Kết
②.1 OG-1 gộp schema GỘP-capable PRE :79 ghi tên; việc THẬT = swap UNIQUE :86 nhưng không dán nhãn OG-1; 0 acceptance riêng ⚠️ 0-bit gate
②.2 per-nhóm + line hạng mục cột + UNIQUE + rào + Σ + 16-consumer :84-87 · :111-112 · :132 · :136-153 (17 dòng) (trừ M1, M6)
②.3 8 Code + K2-chủ-cột seed 8 + validator + "K3 chỉ đọc" :185-192 · :210 · :130
②.4 vai 2 tầng "khai limitation trong doc K1" chỉ 2 chú-thích cột :21,:400 deliverable doc trong commit K1 :240, 0 acceptance :69-75 RƠI
②.5 NĐUQ tham khảo cột text, seed null :23
②.6 backfill=1 (+42-leaf→K4a) default 1 + COUNT==Σ8 :83 + acceptance :168
②.7 admin ẩn cụm-1 chỉ cần CHỪA subtree AwV2 :214 (đúng phạm vi)
②.8 nguồn + OG-6 chặn seed-land :7 · :241 · :256 = 3 chỗ
②.9 freeze freeze tại finalize + né #81-EXT K2.b :90-107 ⚠️ H2 + M4

PRE-gate (2 câu hỏi đích danh): OG-6 chặn seed-land K1 = , 3 chỗ (:7/:241/:256). OG-2 verify-LIVE trước seed K3 = , 3 chỗ (:176/:229/:243) — và khả thi thật: ApprovalWorkflowsV2Controller.cs:17-21 GET api/approval-workflows-v2 [Authorize] trần, DTO có IsActive (ApprovalWorkflowV2AdminFeatures.cs:176) ⇒ phân biệt được LIVE/archived.

14 vá b1b thuộc cụm-1 (8 mục): #2 · #3 · #4 · #5 BE/UI (M1) · #6 · #7 · #11 · #13 .


Findings

HIGH

H1 — leaf ContractCatalog NGOÀI All + "grant qua SeedAdminPermissionsAsync loop" = tự mâu thuẫn ⇒ menu KHÔNG BAO GIỜ hiện. Cụm-1 :57. Đo: DbInitializer.cs:2058 foreach (var menuKey in MenuKeys.All) ⇒ key ngoài All không sinh Permission row. GetMyMenuTreeQuery.cs build cây THUẦN từ db.Permissions + filter HasAccess = CanRead || childkhông có admin-bypass; inherit-root chỉ 4 (Contracts/Workflows/PurchaseEvaluations/PeWorkflows), Catalogs KHÔNG có. ⇒ 0 row ⇒ leaf bị filter, kể cả Admin. Tiền lệ dẫn SAI: Pe_* sống được CHÍNH VÌ PurchaseEvaluations là inherit-root; HopDongCung thì 0 menu-row. Khuôn THẬT của leaf catalog: MenuKeys.cs:168 Catalogs, CatalogUnits, CatalogMaterials, CatalogServices, CatalogWorkItemscả 5 đều TRONG All. Lý do (iv) "0 policy/menu-key mới = 0 drift STATUS row Policies" (:47) = Goodhart: giữ số doc đứng yên bằng cách làm menu chết. Acceptance K1.f :69-75 không có mục nào đo "leaf hiện trong sidebar" ⇒ lỗi ship với 7/7 xanh. Đề nghị: chọn 1 trong 2 — (a) vào All và KHAI drift Menu keys 55→56 + Policies 220→224 (2 row cùng lúc); (b) grant tường minh kiểu SeedKeHoachKyKetAccessAsync. Thêm acceptance "login Admin thấy leaf ở nhóm Danh mục" + 1 test seed-grant.

H2 — K3.⚠️ liệt 3 việc code, SÓT ≥3 site "ép false" ⇒ tính năng Chương-tick chết ở lần admin lưu lại quy trình đầu tiên, mà mọi acceptance K3 vẫn PASS. Cụm-1 :181 liệt (a) nhánh service (b) cột runtime (c) gỡ khoá FE :151-152. Sót:

  • ApprovalWorkflowV2AdminFeatures.cs:380-381 — CREATE ép false server-side: AllowApproverSkipToFinal/Finalize = typeEnum != ApprovalWorkflowApplicableType.ContractSigningPlan && l.… (comment :377-379 "ép false server-side (FE đã ép ở levelFlagsPayload — đây chặn curl bypass)").
  • :591-594 PUT sửa-tại-chỗ: echo lệch → ConflictException "không được bật/tắt cờ …".
  • :605-610 dòng MỚI mang cờ → ConflictException.
  • FE ApprovalWorkflowsV2Page.tsx:247-258 levelFlagsPayload(e, lockEarlyFinalize)allowApproverFinalize: lockEarlyFinalize ? false : …, bật bởi :638 earlyFinalizeLocked = applicableType === TYPE_INT_CONTRACT_SIGNING_PLAN. Gỡ note :151-152 mà không gỡ 2 chỗ này ⇒ UI mở nhưng payload vẫn false. Vì sao acceptance mù: K3.d test 2 & 5 đo trên workflow SEED (ghi thẳng DbContext, không qua command) ⇒ xanh. Đây đúng lớp #81-EXT mà chính doc viện dẫn — doc quét kỹ Phase = nhưng không quét AllowApproverFinalize. Đề nghị: bổ sweep grep -rn "AllowApproverFinalize\|AllowApproverSkipToFinal" vào K3, liệt đủ site vào PR + 1 test "tạo version mới qua CreateCommand cho type-10 GIỮ được cờ".

H3 — CreateContractSigningPlanCommand +ApprovalGroup required, nhưng KhkkCreatePage bị dispose "KHÔNG-ĐỔI" ⇒ tạo phiếu vỡ (hoặc feature vô hình); acceptance curl-only không bắt. Cụm-1 :111 (+ApprovalGroup int, validator InclusiveBetween(1,8)) ⟂ :151 K2.f row 16-17 KhkkCreatePage.tsx:8 = "KHÔNG-ĐỔI đợt này" ⟂ :112 lại đòi sửa KhkkCreatePage ×2 app. Đo: fe-admin/src/pages/khkk/KhkkCreatePage.tsx:72-77 body = { peId: purchaseEvaluationId, approvalWorkflowId, … }api.post('/contract-signing-plans', body)không có approvalGroup. Header :3 khai "File MIRROR SHA256 identical với fe-admin" ⇒ phải sửa cả 2 app. Không sửa: approvalGroup=0 → 400 mọi lần tạo. Nếu impl né bằng default=1: mọi phiếu FE tạo ra đều nhóm 1 ⇒ feature vô hình. K2.h :169 đo bằng curl ⇒ bỏ qua FE hoàn toàn. Đề nghị: 1 dòng acceptance "tạo phiếu nhóm 2 từ UI ×2 app".

MEDIUM

M1 — nửa-UI của OG-7 có trong văn, 0 acceptance / 0 test / 0 dòng commit. :112 mô-tả nút "Tạo thêm cho nhóm khác" + existingGroups: int[]; K2.g :157-163 không có test; K2.h :167-172 không có mục; commit-K2 :242 chỉ ghi "DTO + FE types". Impl SAI vẫn PASS: BE mở rào, UI giữ 409 cũ ⇒ đúng thứ owner nhìn thấy thì vắng.

M2 — nhãn đo-lường SAI: "hit IgnoreQueryFilters đầu tiên trong src/Backend — S155 đo 0-hit" (:38). Đo lại: 29 hit trong src/Backend (trừ Migrations) — 18 ApprovalWorkflowV2AdminFeatures.cs · 4 PeSoftDeleteFeatures.cs · 3 ContractSigningPlanFeatures.cs (chính module đang spec) · 3 ContractFeatures.cs · 1 PurchaseEvaluationsController.cs. Số ĐÚNG & chịu lực = 0 hit trong DbInitializer.cs (đo: 0). Đơn thuốc vẫn ĐÚNG (chứng DbInitializer.cs:2970 db.Projects.Select(p => p.Code) thiếu IgnoreQueryFilters — verified). Sửa nhãn hoặc bỏ nhãn.

M3 — K3.⚠️(a) mở lại quyết định owner đã ĐÓNG bằng chữ "PHÁ VỠ" @S155, doc không nhắc một chữ. 5 nơi ghi: ContractSigningPlanWorkflowService.cs:44-46 · ApprovalWorkflowV2AdminFeatures.cs:377 · fe-{admin,user}/src/pages/khkk/KhkkWorkflowPanel.tsx:8 · docs/HANDOFF.md:77 (vẫn nằm ở mục "🔸 Không chặn W1-W2" = CHƯA chốt). Spec v2 §④ OG-3 có câu owner @S164 nghe như tái-cho-phép và lead đã dán "🔄 SỬA @B2c1", nhưng câu đó nói về NĐUQ/ngưỡng CEO; đảo một phán quyết mang chữ "PHÁ VỠ" phải hỏi owner TƯỜNG MINH. → trả lời Q4.

M4 — freeze tuyên "xong" ở cụm-1 nhưng lỗ duy nhất bịt bằng việc cụm-2. :107 tự khai: "xoá mềm line sau DaDuyet làm Σ :711 trôi ÂM THẦM — chặn bằng phase-guard", mà phase-guard nằm ở K4b (cụm-2). Đo: Features.cs:709-711 Σ tính LIVE từ ContractSigningPlanLines ⇒ soft-delete 1 line đổi tổng phiếu ĐÃ DUYỆT. K2.g test 5 chỉ đo ApprovedAmount != null.

M5 — neo chéo gãy: "Mig 71 item 6" không tồn tại. :181 ("gắn vào Mig 71 item 6 (K2.a)") + :242 ("Mig 71 gồm cả item 6") ⟂ K2.a :83-88 chỉ có item 1-5. Impl đọc K2.a rời → Mig 71 thiếu EndedByLevelFinalize → K3 kẹt hoặc phải đẻ Mig 72 trái QUYẾT :242.

M6 — CatalogEntryId bắt-buộc hay không: 3 câu không thể cùng đúng. :87 "line MỚI/SỬA bắt buộcCatalogEntryId" ⟂ :113 auto-gen create "mồi CatalogEntryId = null" (mọi phiếu tạo sau Mig 71 đều sinh line null) ⟂ :126 "null → CHO QUA". Cách đoán quyết định bẫy NULL-unique (:87) có nổ hay không.

LOW

  • L1:105 gắn cờ 🔴 "PHẢI XỬ" cho WorkflowService.cs:342 do đọc nhầm chú thích :39. Đo: ContractSigningPlanWorkflowService.cs:36-42 đã KHAI RÕ service có ĐÚNG MỘT nhánh set DaDuyet, và :39 là trích nguồn-copy ContractWorkflowService.cs:190-193 như thứ CỐ Ý không chép. :342 nằm trong ReturnOrRejectAsync (:310-318, targetPhase ∈ {TraLai,TuChoi}; guard :320-323 đòi Phase == ChoDuyet). Phương án (i) "gọi ApplyApprovedAmounts ngay trước :342" = thuốc SAI. Phương án (ii) test-khoá-hành-vi hợp lệ, bổ cho ContractSigningPlanApprovalTests.cs:523-533 (T3 hiện chỉ khoá write-path Admin-approve).
  • L2:11 đặt file Domain/Master/ContractCatalogEntry.cs "(cạnh WorkItem)"; WorkItem.cs thật ở src/Backend/SolutionErp.Domain/Master/Catalogs/WorkItem.cs. 2 vế không cùng đúng.
  • L3 — GroupCode dòng CEO: :19 liệt giá trị CEO:40 bảo lấy heading "(A1..C3, 5.2)" ⇒ ra "5.2". Heading nguồn: danh-muc-sp002-transcribe.md:131 "## 5.2 — CEO".
  • L4ContractSigningPlanFeatures.cs:32 tự khai luật "positional record, field append-CUỐI (đừng chèn giữa — vỡ FE mapping)"; K2.e :131 liệt 3 DTO cần +field nhưng không nói vị trí.
  • L5AddColumn … defaultValue: 1 (:83) để lại DEFAULT constraint vĩnh viễn ⇒ insert quên set ApprovalGroup IM LẶNG thành nhóm 1 thay vì fail (cùng gốc M6/H3).
  • L6 — đếm QUYẾT: lead hỏi "3 QUYẾT của lane", đo được 5 (:44+:47 policy · :84 loose-Guid · :90+:96 freeze-reuse · :210 convention-parse · :242 gộp-Mig-71).
  • L7:86 trỏ "vùng :24-26" để lấy tên index EF-sinh; index thật ở ContractSigningPlanLineConfiguration.cs:22-24.
  • L8 — acceptance :167 (SUM(PeReferenceAmount) trước == sau migrate) gần 0-bit: Mig 71 không chạm cột tiền ⇒ không impl-sai nào làm nó TRƯỢT. Răng thật ở test :163 (mồi 0 khi tách dòng) — nên nêu vế đó.

Kháng được soi (đo lại, ĐÚNG)

  • P1 — bảng #81-EXT :100-105: grep -rnE "Phase\s*=[^=]" → đúng 4 site (Features.cs:373 · WorkflowService.cs:174/:278/:342); "DbInitializer 0 site KHKK" đo được 0. Cảnh báo "đừng grep \.Phase" xác nhận đúng.
  • P2 — 13/13 email+tên K3.a khớp DbInitializer.cs:1613-1632 từng dòng; bẫy long.nguyen@ (HRA :1629) có thật; ô ⚠️ Trưởng/Trình có thật (:1625 Lê Tự Đăng Trình ⟂ :1626 Lê Trần Đăng Trường).
  • P3 — "KHKK không đọc bảng NS live": đo lại chỉ 1 hit source (Features.cs:162 nhãn DTO) ⇒ lý do bỏ 11 cột PE Mig 67 đứng vững.
  • P4 — khuôn authz catalog: CatalogsController.cs:13-14 + đúng 12 [Authorize(Roles = "Admin")] + policy-per-action 0 hit. Số khớp tuyệt đối.
  • P5 — 86 & phân bố 5/10/5/23/14/15/13/1 tự-kiểm từ transcribe: A1=5 · A2 1+A3 9=10 · A4=5 · B1=23 · B2=14 · B3=15 · C1 6+C2 3+C3 4=13 · CEO=1 ⇒ Σ=86 ✓; EN có mặt 86/86; 8 cột map đủ (NduqNote seed null, khớp ⚠️3).
  • P6 — 0 trôi số canonical: STATUS Tests 590 (:472) · SQL tables 96 (:466) · Migrations 69 (:465) ⇒ "Mig 70/71" · "96→97" · "baseline ~590 +17" đều đúng.
  • P7 — OG-2 verify-LIVE khả thi đúng như mô tả (route + IsActive đều có).
  • P8 — anchor Features.cs :34 · :342-347 · :350 · :389 · :458-462 · :570-574 · :622-625 · :711 · :1201-1202 và Line.cs:15/:17-20/:23/:25 · LineConfig:22-26/:29 · Layout.tsx:166-172đo lại khớp hết.

Verdict

FAIL (cổng tiền-implement) — 17 finding: 3 HIGH · 6 MEDIUM · 8 LOW, 8 điểm kháng-soi. Chặn: H1 (menu chết), H2 (feature owner-yêu-cầu chết im lặng), H3 (tạo phiếu vỡ). Cả 3 cùng một lớp: acceptance không có phép đo nào TRƯỢT khi impl sai. Cần owner gật: M3 (đảo phán quyết "PHÁ VỠ" @S155).