19 KiB
19 KiB
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ừaSystemsubtree + AwV2)". Nhưng 2 mục owner đòi ẩn — "Quy trình Duyệt NCC" và "Quy trình HĐ" — đều là CON củaSystem, 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.
- Yêu cầu ẩn:
- 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:41vsDbInitializer.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
ApprovalGrouptrong 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-625—ListContractSigningPlansQuery(Phase, ProjectId, PurchaseEvaluationId, PendingMe)— không có group.spec:23K4 = "sidebar 48 leaf + grant seeder";spec:22K2 = "Mig 71 + validator + FE lines editor" ⇒ không ai khai list-filter.spec:39acceptance K4 đòi "leaf lọc đúngApprovalGroup".
- 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:28class-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ỉKeHoachKyKetvàoAll⇒ 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ịApprovalGroupcủa phiếu đã tồn tại; mà K4 (leaf lọc theo nhóm) + K5 (cây group-byapprovalGroup) đề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-byapprovalGroup) ·spec:39(K4 leaf lọcApprovalGroup) · 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ờapprovalGrouptừ cụm 1" mà không nói chờ K2 hay K3. - Evidence:
spec:14(quyết-định 3) vsspec:22(K2) vsspec: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 PEMenuKeys.cs:142-158(Group/List/Create/Pending/Approved/Deleted/WfView) ·spec:23chỉ ghi 1 mũi tên "chờapprovalGrouptừ 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
ApprovalWorkflowsV2thì 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:23K6 "(độc lập — kéo sớm được)" ·spec:22K3 "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:52vsspec: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:25—public Guid? ContractId { get; set; } // [C6 review-schema] HĐ sinh ra từ dòng này (W5).src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/ContractSigningPlanLineConfiguration.cs:26—b.HasIndex(x => x.ContractId);- Grep toàn
src/Backend(trừ Migrations) tìm phép GÁNContractIdcho 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.ymlrebuild FE vô-điều-kiện mỗi run") ·:1211cơ chế (Vite/rolldown emit content-hash không deterministic) ·:1213guard 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= 13public 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 testPASS (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Đ
DaPhatHanhkhô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) vsspec: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) vsspec: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.tscùng7144eeb7…;fe-{admin,user}/src/components/pipeline/PipelineTreePanel.tsxcùngd70ac315…⇒ đâ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-1787với chú thích ":1781 — đủ 6 leaf 'y chang Duyệt NCC'". 8 × 6 = 48 là NHÂN KHUÔN. - Cột
ApprovalGroupcũng KHÔNG phải khái-niệm mới — nó là bản sao vai trò củaEvaluationTypebên PE (MenuKeys.cs:55— "Module Duyệt NCC … 2 EvaluationType" chính là thứ sinh ra fan-outPe_<typeCode>_*). Hệ quả: đã mirror thì mirror trọn —EvaluationTypekhông rỗng từ lúc tạo, nênApprovalGroupcũ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:1794cho thấy Menu eOffice là con củaSystem, 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ướcToDictionaryAsync. ApprovedAmountper-Line có thật (ContractSigningPlanLine.cs:23) và spec trích:25choContractIdcũ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):
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 đó.F-L3-3+F-L3-4+F-L3-5— trụcApprovalGroup: 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á.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ũ".