Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-clone-l3-b1b.md
2026-07-31 11:55:53 +07:00

19 KiB
Raw Blame History

lens3-seq — thứ tự + khuôn + đo được

Lens 3/3 ensemble B1b. Target: spec-4gd-khkk-tong-quat-31-07-2026.md (+ draft-yeu-cau-owner.md = nguồn PIN). 3 câu: Q3 thứ tự/phụ thuộc wave · Q4 chế-thêm trái luật khuôn · Q5 acceptance đo được.

Findings

F-L3-1 [HIGH] — Whitelist "chừa System subtree" GIỮ LẠI đúng 2 mục owner ra lệnh ẨN

  • Claim: spec §② quyết-định 7 = "fe-admin filterForAdmin đảo blacklist→whitelist (chừa System subtree + AwV2)". Nhưng 2 mục owner đòi ẩn — "Quy trình Duyệt NCC" và "Quy trình HĐ" — đều là CON của System, nên whitelist đó giữ chúng lại.
  • Evidence:
    • Yêu cầu ẩn: draft-yeu-cau-owner.md:52 — "+ 2 mục quy trình cũ (Quy trình Duyệt NCC · Quy trình HĐ)" nằm trong danh sách ẨN; chừa = "Người dùng · Vai trò · Phân quyền · Menu eOffice" + "Quy trình duyệt (Mới)".
    • src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs:1795(MenuKeys.Workflows, "Quy trình HĐ", MenuKeys.System, 95, ...)
    • DbInitializer.cs:1798(MenuKeys.PeWorkflows, "Quy trình Duyệt NCC", MenuKeys.System, 95, ...)
    • DbInitializer.cs:1801(MenuKeys.ApprovalWorkflowsV2, "Quy trình duyệt (Mới)", MenuKeys.System, 96, ...) ⇒ "+ AwV2" trong spec là THỪA, và chính chỗ thừa đó tố cáo tác-giả tưởng AwV2 nằm NGOÀI System.
  • Sev: HIGH (sai chức-năng so với lời owner, phát hiện được ngay bằng đọc seeder).
  • Đề nghị: spec phải phát biểu rule theo TẬP KEY tường minh (4 leaf System + AwV2 subtree), KHÔNG theo "subtree System"; kèm acceptance liệt-kê-key.

F-L3-2 [HIGH] — Acceptance K6 MÙ với chính lỗi F-L3-1

  • Claim: spec:41 — "sidebar admin chỉ còn HỆ THỐNG + Quy trình duyệt (Mới)". Nếu implement theo quyết-định 7, 2 mục quy trình cũ vẫn hiện — nhưng chúng hiện BÊN TRONG nhóm "HỆ THỐNG", nên người chấm đọc câu acceptance vẫn thấy "chỉ còn HỆ THỐNG + …" ⇒ PASS.
  • Evidence: spec-4gd-khkk-tong-quat-31-07-2026.md:41 vs DbInitializer.cs:1795/1798 (2 mục là con System).
  • Sev: HIGH (acceptance không có răng đúng ở trục đang sai).
  • Đề nghị: acceptance = liệt-kê ĐÚNG N mục lá còn thấy trong sidebar admin (tên + số), FAIL nếu dư/thiếu 1 mục.

F-L3-3 [HIGH] — K4 "leaf lọc đúng ApprovalGroup" cần tham-số lọc mà KHÔNG wave nào nhận nuôi

  • Claim: 48 leaf lọc theo nhóm cần ApprovalGroup trong list-query BE; query hiện chỉ có 4 tham số, và K2/K4 không khai việc thêm.
  • Evidence:
    • src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs:622-625ListContractSigningPlansQuery(Phase, ProjectId, PurchaseEvaluationId, PendingMe) — không có group.
    • spec:23 K4 = "sidebar 48 leaf + grant seeder"; spec:22 K2 = "Mig 71 + validator + FE lines editor" ⇒ không ai khai list-filter.
    • spec:39 acceptance K4 đòi "leaf lọc đúng ApprovalGroup".
  • Sev: HIGH (acceptance nằm ngoài scope wave đang chấm ⇒ hoặc trượt, hoặc lấn scope im lặng).
  • Đề nghị: gán rõ chủ wave cho ApprovalGroup (cột + query-param + FE param), 1 dòng DoD.

F-L3-4 [HIGH] — 8 nhóm chỉ tách được ở tầng HIỂN THỊ; API là MỘT khoá chung ⇒ "0 rác 403" không đo được cái cần đo

  • Claim: 48 leaf fan-out nhưng mọi endpoint KHKK dùng chung bộ policy KeHoachKyKet.{Read,Create,Update,Delete}; không có authz theo nhóm. Acceptance "13/13 role 0 rác 403" chỉ bắt được lỗi grant-lệch (leaf hiện mà thiếu quyền root), MÙ hoàn toàn với rò-rỉ chéo-nhóm.
  • Evidence:
    • src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs:28 class-level [Authorize(Policy = "KeHoachKyKet.Read")]; :36/:53/:59/:71/:77 Read · :84 Create · :93 Update · :103 Delete — 1 bộ khoá cho TẤT CẢ.
    • src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs:42 + :180 — chỉ KeHoachKyKet vào All ⇒ 4 policy, không có policy per-nhóm.
    • Rào duy nhất hiện có là IDOR-scope theo người, không theo nhóm: ContractSigningPlanFeatures.cs:636-640 (comment "phiếu MÌNH soạn phiếu đã rời nháp thuộc quy trình mình có chân duyệt").
  • Sev: HIGH (spec bán "8 nhóm" như đơn-vị phân-quyền; thực tế là nhãn hiển thị).
  • Đề nghị: spec khai thẳng "phân-tách nhóm = HIỂN THỊ, không phải authz" (và nếu owner muốn authz thì đó là wave riêng); acceptance K4 đổi thành so-khớp TẬP: với mỗi role, {leaf render} vs {Permission row KeHoachKyKet} + {phiếu list trả về} — đo bằng truy vấn, không bằng click tay.

F-L3-5 [HIGH] — ApprovalGroup cho phiếu CŨ không được định nghĩa ⇒ phiếu cũ có thể biến mất khỏi cả sidebar lẫn cây

  • Claim: K2 acceptance chỉ lo Lines (CatalogEntryId=NULL), không nói gì về giá trị ApprovalGroup của phiếu đã tồn tại; mà K4 (leaf lọc theo nhóm) + K5 (cây group-by approvalGroup) đều lọc theo nó. Nhóm NULL/0 không có folder ⇒ vắng mặt IM LẶNG (đúng lớp "lọc = vanish" đã gặp).
  • Evidence: spec:37 (K2 acceptance — chỉ CatalogEntryId=NULL) · spec:23 (K5 group-by approvalGroup) · spec:39 (K4 leaf lọc ApprovalGroup) · entity hiện KHÔNG có cột nhóm: src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlan.cs:22-45.
  • Sev: HIGH.
  • Đề nghị: chốt backfill tường minh (mặc định nhóm 1 vì quyết-định 6 giữ Khkk_G1 đổi label thành nhóm 1) + acceptance đếm: COUNT(phiếu) trước = Σ COUNT(phiếu) hiện trong 8 folder.

F-L3-6 [MED] — Chủ sở-hữu cột ApprovalGroup bị chẻ đôi giữa quyết-định 3 và wave K2

  • Claim: quyết-định 3 (workflow) gói luôn "plan +cột ApprovalGroup int + validator line-cùng-nhóm" ⇒ thuộc K3; nhưng K2 mô tả "phiếu mang hạng-mục+nhóm (Mig 71 …)" ⇒ thuộc K2. Cụm 2 lại ghi "chờ approvalGroup từ cụm 1" mà không nói chờ K2 hay K3.
  • Evidence: spec:14 (quyết-định 3) vs spec:22 (K2) vs spec:23 (mở đầu cụm 2).
  • Sev: MED (2 wave cùng đụng 1 cột ⇒ hoặc trùng migration, hoặc rơi kẽ).
  • Đề nghị: 1 dòng "cột X do wave Y đẻ", các wave khác chỉ đọc.

F-L3-7 [MED] — Cổng owner (OG-1, OG-2) nằm NGOÀI đồ thị wave, không có chốt chặn trước wave phụ thuộc

  • Claim: OG-1 (cardinality) quyết hình dạng UNIQUE của K2 và luật gộp của K7; OG-2 (roster vai→user) chặn chính việc seed của K3 ("cần anh gật trước khi seed"). Nhưng K1→K2→K3 được xếp như chuỗi chạy thẳng, không wave nào có DoD "gate đã chốt".
  • Evidence: spec:27-28 (OG-1, OG-2) · spec:22 (cụm 1 chạy thẳng) · spec:36-38 (checklist K1/K2/K3 không có dòng gate).
  • Sev: MED-HIGH (fan-out B4 sẽ hoặc đứng, hoặc tự chế roster; OG-1 lật SAU K2 = phải sửa migration đã land).
  • Đề nghị: mỗi wave phụ-thuộc-gate có dòng đầu "PRE: OG-n đã chốt (dẫn chứng: …)"; OG-1 phải chốt TRƯỚC K2, OG-2 TRƯỚC K3.

F-L3-8 [MED] — "Cụm 2 chờ cụm 1" gộp 3 phụ-thuộc KHÁC nhau vào 1 mũi tên

  • Claim: trong 6 leaf/nhóm của K4, phụ thuộc thực khác nhau: (a) MenuItem row + grant seeder + thứ tự sidebar = KHÔNG phụ thuộc gì (tầng Identity, chạy song song cụm 1 được); (b) 4 leaf danh-sách (Danh sách/Đang duyệt/Đã duyệt/Đã xóa) + Thao tác = chờ ApprovalGroup (K2 hoặc K3, xem F-L3-6) + chờ query-param (F-L3-3); (c) leaf "Luồng duyệt" (WfView) = chờ K3 (8 workflow tồn tại), KHÔNG chờ K2.
  • Evidence: bộ 6 leaf khuôn ở DbInitializer.cs:1780-1787 (KHKK) và factory PE MenuKeys.cs:142-158 (Group/List/Create/Pending/Approved/Deleted/WfView) · spec:23 chỉ ghi 1 mũi tên "chờ approvalGroup từ cụm 1".
  • Sev: MED (vừa nối-thừa gây tuần-tự-hoá phí, vừa giấu 1 phụ-thuộc THẬT là K4→K3).
  • Đề nghị: tách bảng phụ-thuộc theo LEAF-GROUP, ghi rõ K4a (không phụ thuộc) / K4b (K2) / K4c (K3).

F-L3-9 [MED] — K6 "độc lập, kéo sớm được" đúng CÓ ĐIỀU KIỆN: 8 designer của K3 phải nằm dưới ApprovalWorkflowsV2

  • Claim: sau khi đảo sang whitelist, mọi menu MỚI mặc định BỊ ẨN ở admin (fail-closed). K3 sinh "designer entry" cho 8 nhóm; nếu chúng không phải con của ApprovalWorkflowsV2 thì K6-kéo-sớm làm 8 designer vô hình — mà không ai báo lỗi.
  • Evidence: khuôn hiện tại có đúng 2 con dưới AwV2: DbInitializer.cs:1802-1803 (Duyệt NCC (Mới) / Duyệt NCC và Giải pháp (Mới)) · spec:23 K6 "(độc lập — kéo sớm được)" · spec:22 K3 "designer entry".
  • Sev: MED.
  • Đề nghị: ghi ràng buộc "8 designer = con của ApprovalWorkflowsV2" vào DoD K3, và thêm dòng acceptance K6 "sau K3, đếm được 8 designer trong sidebar admin".

F-L3-10 [MED] — Spec nén "8 designer" của owner thành "designer entry" (số ít, không số)

  • Claim: draft đòi "Quy trình duyệt (Mới)" += 8 designer nhóm-duyệt; spec K3 chỉ còn "designer entry", checklist K3 (spec:38) không có dòng nào đếm designer.
  • Evidence: draft-yeu-cau-owner.md:52 vs spec:22 + spec:38.
  • Sev: MED (mất số ⇒ mất phép đo; đúng lớp "nhãn ✓ không có phép đo").
  • Đề nghị: checklist K3 thêm "sidebar admin hiện đúng 8 designer nhóm; mở designer nhóm n load đúng workflow KHKK-N{n}".

F-L3-11 [HIGH] — K7 hỏi "W5/W6 đã land chưa" bằng câu NHỊ-PHÂN, trong khi trạng thái thật là NỬA-LAND

  • Claim: cầu KHKK→HĐ hiện có ĐỦ phần schema (cột + index) nhưng KHÔNG có một chỗ ghi nào. Ai "RE-ĐO" bằng cách soi schema sẽ đọc ra "đã land" và bỏ qua đúng phần còn thiếu.
  • Evidence:
    • src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs:25public Guid? ContractId { get; set; } // [C6 review-schema] HĐ sinh ra từ dòng này (W5).
    • src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/ContractSigningPlanLineConfiguration.cs:26b.HasIndex(x => x.ContractId);
    • Grep toàn src/Backend (trừ Migrations) tìm phép GÁN ContractId cho dòng kế-hoạch: 0 kết quả — chỉ ra 1 comment (ContractSigningPlanFeatures.cs:1185) và 1 khai-báo index. Không có write-site.
  • Sev: HIGH.
  • Đề nghị: đổi câu RE-ĐO từ nhị-phân sang 3 nấc ĐO ĐƯỢC — (a) cột/index có chưa, (b) write-site có chưa (grep phép gán), (c) test/E2E chứng đường quay ngược — và nêu xử lý cho nấc giữa.

F-L3-12 [HIGH] — Acceptance K6 "diff bundle fe-user = 0" đo bằng thứ gotcha #69 đã tuyên bố KHÔNG ổn định

  • Claim: bundle hash không deterministic và CI rebuild FE vô-điều-kiện mỗi run ⇒ "diff bundle = 0" hoặc luôn FAIL, hoặc bị lách bằng cách không build lại.
  • Evidence: docs/gotchas.md:1207 (tiêu đề #69: "FE bundle hash KHÔNG deterministic + deploy.yml rebuild FE vô-điều-kiện mỗi run") · :1211 cơ chế (Vite/rolldown emit content-hash không deterministic) · :1213 guard chính thức: "Phát-hiện FE-ship THẬT = git diff fe-admin/src fe-user/src <range>, KHÔNG tin hash-delta".
  • Sev: HIGH (phép đo sai bản chất, đã có luật ngược trong repo).
  • Đề nghị: viết lại thành git diff --stat <base>..<head> -- fe-user/src = 0 file.

F-L3-13 [HIGH] — "13/13 role 0 rác 403" PASS được bằng kết quả suy biến (không cấp quyền cho ai)

  • Claim: nếu grant seeder không cấp leaf KHKK cho role nào thì 0 leaf render ⇒ 0 lệnh gọi ⇒ 0 rác 403 ⇒ acceptance PASS trong khi tính năng vô hình. Thiếu ma-trận KỲ VỌNG (role nào PHẢI thấy nhóm nào) thì phép đo chỉ có 1 chiều.
  • Evidence: spec:39 · anchor con số "13" có thật (src/Backend/SolutionErp.Domain/Identity/AppRoles.cs = 13 public const string) nhưng seeder quyền hiện chỉ phủ 7 vai (DbInitializer.cs:2671-2675: Drafter, DeptManager, Procurement, CostControl, ProjectManager, Director, AuthorizedSigner) ⇒ 6 vai còn lại không có kỳ-vọng nào được khai.
  • Sev: HIGH.
  • Đề nghị: acceptance 2 chiều — (i) mọi leaf render đều gọi được (0 rác 403); (ii) số leaf render của từng vai == số kỳ-vọng khai trước trong bảng 13 dòng.

F-L3-14 [MED] — K1 "bảng danh mục N dòng == số extract file gốc (khai N)" là mệnh đề TỰ ĐÚNG

  • Claim: N do chính bước extract sinh ra rồi lại lấy N so với N ⇒ 0 bit thông tin; không có đối-chứng độc lập.
  • Evidence: spec:36 · đối-chứng độc lập DUY NHẤT nằm ở draft và spec đã bỏ mất: draft-yeu-cau-owner.md:42 — "Tổng danh mục ~85 dòng (đếm từ ảnh trang 5-9)".
  • Sev: MED.
  • Đề nghị: acceptance = "N khớp ~85 (đếm ảnh trang 5-9); lệch > 5 dòng phải giải trình từng dòng lệch".

F-L3-15 [MED] — Mọi dòng "test PASS" không có SỐ ⇒ xoá test vẫn PASS

  • Claim: dotnet test PASS (K1), "test validator ÂM PASS" (K2), "test bridge PASS" (K7) đều không nêu baseline hay số ca thêm.
  • Evidence: spec:36, :37, :42 · baseline có thật trong draft: draft-yeu-cau-owner.md:67 — "Test: 590 PASS (45D+545I) baseline".
  • Sev: MED.
  • Đề nghị: mỗi wave ghi "≥ 590 + k, khai k và tên ca".

F-L3-16 [MED] — K8 dry-run: 3 guard nêu ở phần rủi ro KHÔNG xuống tới dòng acceptance

  • Claim: quy ước ZZTEST, HỐ-1 (HĐ DaPhatHanh không xoá được qua API) và "notification bắn user thật" đều nằm ở §② nhưng dòng checklist K8 chỉ đòi chạy hết trạm + screenshot.
  • Evidence: spec:24 (ZZTEST) + spec:32 (HỐ-1, notification) vs spec:43 (acceptance K8 — không có 3 thứ đó).
  • Sev: MED (dry-run trên prod để lại rác không xoá được).
  • Đề nghị: K8 thêm 3 dòng đo được — tiền-tố ZZTEST trên mọi bản ghi tạo mới; danh sách bản ghi không thể xoá + cách sống chung; tắt/chuyển hướng thông báo trước khi chạy.

F-L3-17 [MED] — Dòng chung "mỗi wave: cicd PASS + bundle byte-verify (#77)" sinh nhiễu cho wave BE-only

  • Claim: K1/K2/K3 gần như thuần BE, nhưng bundle vẫn rotate mỗi run (#69) ⇒ "byte-verify" luôn thấy đổi mà không có nghĩa; đồng thời dòng này ngụ ý mỗi wave đều deploy prod, trong khi không wave nào khai bước deploy.
  • Evidence: spec:44 · docs/gotchas.md:1211 (rebuild vô-điều-kiện + hash rotate) · :1301 (#77 là về cache-lag sau deploy).
  • Sev: MED.
  • Đề nghị: tách 2 loại wave — wave có ship FE mới cần #77; wave BE-only chỉ cần cicd PASS + smoke API.

F-L3-18 [MED] — Guard "grep MỌI consumer Lines" không có dòng acceptance nào; bề mặt đếm được là 16

  • Claim: đổi cardinality của Lines (1 dòng/NCC → N dòng/NCC) là lớp lỗi S87/S88, nhưng checklist K2 không đo việc rà consumer.
  • Evidence: spec:13 + spec:32 (guard) vs spec:37 (acceptance không có) · bề mặt: 10 chỗ chạm .Lines ở BE (ContractSigningPlanFeatures.cs:389/412/417/500/524/568, ContractSigningPlanLineConfiguration.cs:29, ContractSigningPlanWorkflowService.cs:111/296/303) + 6 file FE (fe-{user,admin}/src/pages/khkk/KhkkCreatePage.tsx, KhkkDetailPage.tsx, types/khkk.ts).
  • Sev: MED.
  • Đề nghị: acceptance "liệt 16 chỗ, mỗi chỗ ghi ĐÃ-XỬ-N hoặc KHÔNG-ĐỔI kèm lý do".

F-L3-19 [LOW] — K5 "2 file pipeline SHA-identical" thiếu tên file (bar hiện đang ĐẠT)

  • Evidence: spec:40 · đo thực tế bây giờ: fe-{admin,user}/src/hooks/usePipelineStages.ts cùng 7144eeb7…; fe-{admin,user}/src/components/pipeline/PipelineTreePanel.tsx cùng d70ac315… ⇒ đây là bar hợp lệ, chỉ cần nêu đích danh 2 đường dẫn.
  • Sev: LOW. Đề nghị: chép 2 đường dẫn vào dòng acceptance.

F-L3-20 [LOW] — Checklist không có dòng 3-file rule cho Mig 70/71

  • Evidence: spec:36/:37 (K1/K2 đẻ migration) không nhắc; luật nằm ở draft-yeu-cau-owner.md:67 ("mig mới = 3-file rule").
  • Sev: LOW. Đề nghị: thêm "3 file migration cùng commit" vào DoD K1 + K2.

Đã nghi rồi BÁC BỎ (nêu kèm chứng — không để nghi ngờ trôi thành cáo buộc)

  • 48 leaf KHÔNG phải chế thêm. Khuôn Duyệt NCC đúng là 1 group + 6 leaf: MenuKeys.cs:142-158 (…Group/List/Create/Pending/Approved/Deleted/WfView); KHKK hiện đã dựng đúng bộ đó: DbInitializer.cs:1780-1787 với chú thích ":1781 — đủ 6 leaf 'y chang Duyệt NCC'". 8 × 6 = 48 là NHÂN KHUÔN.
  • Cột ApprovalGroup cũng KHÔNG phải khái-niệm mới — nó là bản sao vai trò của EvaluationType bên PE (MenuKeys.cs:55 — "Module Duyệt NCC … 2 EvaluationType" chính là thứ sinh ra fan-out Pe_<typeCode>_*). Hệ quả: đã mirror thì mirror trọn — EvaluationType không rỗng từ lúc tạo, nên ApprovalGroup cũng phải có backfill (xem F-L3-5).
  • Tầng sub-folder trong cây là owner ĐÒI, không phải tự chế: draft-yeu-cau-owner.md:14 — "Thêm các sub folder con ở đây".
  • Nghi "whitelist bỏ rơi Menu eOffice" — SAI: DbInitializer.cs:1794 cho thấy Menu eOffice là con của System, nằm trong vùng chừa.
  • Nghi "N dòng/NCC làm vỡ tra cứu NCC" — SAI: ContractSigningPlanFeatures.cs:524-526 đã có .Distinct() trước ToDictionaryAsync.
  • ApprovedAmount per-Line có thật (ContractSigningPlanLine.cs:23) và spec trích :25 cho ContractId cũng đúng ⇒ K7 không đọc field ma.

Verdict

FAIL — spec chưa nên đem đi fan-out ở dạng hiện tại. 6 lỗi HIGH, 8 MED, 2 LOW, 6 điểm nghi đã bác bỏ.

Ba lỗi chặn đường (phải sửa trước B2):

  1. F-L3-1 + F-L3-2 — luật ẩn admin giữ lại đúng 2 mục owner ra lệnh ẩn, và acceptance được viết theo cách không thể phát hiện chuyện đó.
  2. F-L3-3 + F-L3-4 + F-L3-5 — trục ApprovalGroup: thiếu chủ wave cho tham số lọc, thiếu backfill cho phiếu cũ, và "8 nhóm" chỉ tồn tại ở tầng hiển thị vì mọi endpoint dùng chung một bộ khoá.
  3. F-L3-11 — câu hỏi RE-ĐO nhị-phân sẽ trả lời "đã land" trên một trạng thái mới land nửa chừng.

Về khuôn (Q4): spec không vi phạm luật "giống nhau — chỉ khác form" ở chỗ dễ nghi nhất (48 leaf, cột nhóm, tầng sub-folder đều là nhân khuôn hoặc do owner đòi). Chỗ lệch khuôn thật sự là cơ chế ẩn admin: đảo blacklist thành whitelist đổi luôn kiểu hỏng mặc định của menu mới (mọi menu thêm sau sẽ tự ẩn ở admin), và đó là thay đổi hành vi vượt quá yêu cầu "ẩn các mục cũ".