Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-c2-l1.md
2026-07-31 13:39:03 +07:00

14 KiB
Raw Blame History

c2-l1

LANE 1/3 — LENS QUYẾT-vs-CODE. Đối tượng: sub-invest-fable-b2-cum2.md (K4 sidebar 49-row · K5 cây groups · K6 admin 12-key blacklist; 0 mig).

Dòng-1: C2L1: 4/7 ĐÚNG · 0 SAI · 3 LỆCH — 7 finding (2 MAJOR · 3 MEDIUM · 2 LOW), 0 blocker về QUYẾT.

Findings

F-1 [MEDIUM · LỆCH lý-do, giữ kết-luận] "group-key CẦN CanRead row để render" — bác bằng code

  • Spec :24 biện hộ site-2 extend +7 group vì "group-key CẦN CanRead row để render".
  • GetMyMenuTreeQuery.cs:96static bool HasAccess(MenuNodeDto n) => n.CanRead || n.Children.Any(HasAccess); (filter :97-101) ⇒ node không có CanRead vẫn được giữ nếu bất kỳ con nào readable.
  • ⇒ CanRead cho Khkk_G2..G8 không phải điều kiện cần. Quyết định (extend site-2) vẫn hợp lý vì tính nhất-quán với G1, nhưng LÝ DO sai ⇒ đẻ bất-biến giả cho lượt sau.
  • Đề nghị: đổi câu lý do thành "đồng bộ với G1 (KhkkKeys():2185-2189 đang cấp CanRead cho Khkk_G1), KHÔNG phải điều kiện render".

F-2 [🔴 MAJOR · mâu thuẫn NỘI TẠI: acceptance A1/A2 không thể PASS ở nấc spec tự xếp]

  • Spec :119 — "Acceptance A1/A3-A6 đo được NGAY sau K4a"; :104 — ship K4a một mình thì "leaf RENDER được, LỌC chưa đúng"; :33 gọi đó là "silent-wrong".
  • Code bác: fe-user/src/components/Layout.tsx:336-338
    const path = resolvePath(node.key)
    const location = useLocation()
    if (!path) return null
    
    ⇒ leaf không có route bị DROP IM LẶNG (đúng gotcha #50 mà chính spec trích ở mục 7/10; comment cùng file :86 + :98 nói y vậy).
  • Route cho Khkk_G{n}_{...} chỉ sinh ở K4c mục 10 (regex resolvePath), mà sơ đồ :114 + thứ tự :117 xếp K4c CUỐI CÙNG vì "chờ K3".
  • ⇒ Trạng thái thật sau K4a-một-mình: 7 group Khkk_G2..G8 hiện rỗng, 42 leaf KHÔNG hiện ⇒ A1 ("48 leaf HIỆN sidebar") FAIL by-construction; A2 cũng không bấm được. Failure-mode tệ hơn mô tả của spec (vô hình ≠ lọc sai).
  • Đề nghị (rẻ): tách K4c mục 10 khỏi phụ-thuộc K3 và ship CÙNG K4a. Regex chỉ cần khuôn URL (/khkk/list?group=n, &filter=…, ?view=deleted, /khkk/create?group=n, /khkk/workflow-matrix?type=10&group=n) — không cần Code KHKK-N{n}. Chỉ mục 11 (WfView lọc Code === 'KHKK-N'+n) mới thật sự chờ K3. Sửa kèm: :104, :119, sơ đồ :106-115, checklist :125.

F-3 [MEDIUM · chú-thích đặt nhầm app] BÁC-2 đúng, nhưng K6 là "điều kiện tiên quyết ẩn" của K4c-13

  • Verify 2 dòng spec nêu: fe-admin/src/components/Layout.tsx:186-190 filterForAdmin đệ quy (.filter(n => !isAdminHidden(n.key)).map(n => ({...n, children: filterForAdmin(n.children)}))), gọi tại :315 ⇒ ẩn root = subtree không render ĐÚNG.
  • fe-admin không có WorkflowMatrixViewPage: grep toàn repo — file chỉ ở fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx; fe-admin chỉ có comment nhắc tên tại :130; route /khkk/workflow-matrix chỉ tồn tại fe-user/src/App.tsx:83 ĐÚNG (comment lệch-có-chủ-đích :129-133 khớp verbatim).
  • 🔴 Nhưng spec mục 13 + K6-4 dặn ghi comment khai lệch tại regex fe-user. Rủi ro nằm ở fe-admin: ai gỡ 'KeHoachKyKet' khỏi ADMIN_HIDDEN_KEYS lượt sau ⇒ 42 leaf render, resolvePath fe-admin trả null (fe-admin/Layout.tsx:268-270) ⇒ drop im lặng.
  • Đề nghị: comment nằm NGAY tại ADMIN_HIDDEN_KEYS / isAdminHidden (fe-admin/Layout.tsx:182-184), nội dung "gỡ KeHoachKyKet khỏi set ⇒ phải nới regex Khkk_G{n}_* cùng lượt, nếu không 42 leaf drop im lặng (#50)".

F-4 [MEDIUM · dụng cụ đo sai tầng] C1: SQL MenuItems LIKE 'AwV2%' KHÔNG chốt được N

  • Đo thật Dev LocalDB (SolutionErp_Dev): SELECT [Key] FROM MenuItems WHERE [Key] LIKE 'AwV2%' OR [Key] LIKE 'Khkk%'AwV2 = 2 row (AwV2_DuyetNcc, AwV2_DuyetNccPhuongAn), Khkk = 7 row (Khkk_G1 + 6 leaf) khớp "7 key cũ". AwV2_Contract = 0 hit toàn src/ · scripts/ · fe-*/src (chỉ có so chuỗi code === 'Contract'fe-admin/Layout.tsx:169).
  • 🔴 Row MenuItems tồn tại không kéo theo hiện sidebar: AwV2_ContractMenuKeys.All (MenuKeys.cs:180-183 chỉ 3 key AwV2) ⇒ site-1 :2058 không phát row Admin, cũng không nằm trong reviewKeys (:2210-2224) ⇒ resolved thiếu key ⇒ HasAccess=false (GetMyMenuTreeQuery.cs:96) ⇒ bị lọc khỏi cây dù row tồn tại. N=8 chỉ xảy ra khi prod có CẢ row MenuItems LẪN row Permissions CanRead (grant tay).
  • Đề nghị đổi phép đo C1 sang JOIN, chạy trên env nghiệm thu (prod), không phải Dev: SELECT m.[Key] FROM MenuItems m JOIN Permissions p ON p.MenuKey=m.[Key] JOIN Roles r ON r.Id=p.RoleId WHERE r.Name='Admin' AND p.CanRead=1 AND m.[Key] LIKE 'AwV2%'.

F-5 [LOW-MED · A5 đo nhãn sai dụng cụ] DisplayLabel che Label ở fe-user

  • MenuItem.cs:12-13 khai rõ: IsVisible=false → ẩn khỏi /menus/me fe-user, fe-admin vẫn thấy; DisplayLabel="X"fe-user render X thay Label, fe-admin giữ Label.
  • Xác nhận code: fe-user/Layout.tsx:204-206 effectiveLabel = (displayLabel && trim) || label; fe-admin 0 hit displayLabel.
  • ⇒ A5 "labelBackfill đổi đúng 1 nhãn G1" đo bằng UI ×2 app có thể ra 2 nhãn khác nhau nếu prod từng đổi tên qua MenuVisibility (Mig 27) ⇒ chẩn đoán nhầm "backfill không ăn".
  • Đề nghị: A5 đo CỘT — SELECT [Key],Label,DisplayLabel FROM MenuItems WHERE [Key]='Khkk_G1', khai rõ nếu DisplayLabel IS NOT NULL.

F-6 [🔴 MAJOR · nền drift treo vào thứ tự ship] "64→113 / 256→452" chỉ đúng NẾU cụm-1 land trước

  • Số học tự nhất quán : 49 = 7 + 42 · 64×4 = 256 · 113×4 = 452 · 64+49 = 113.
  • Nền hiện tại đo thật: MenuKeys.All đếm tay = 55 (Dashboard 1 + Master-set 4 + Catalogs 5 + Contracts/Forms/Reports 3 + PE 1 + Hrm 2 + HrmConfig 5 + Vehicles/Drivers 2 + Off 3 + PhongHop 4 + DeXuat 4 + DonTu 4 + DatXe/ItTicket/ChamCong/AttReport/HrmDash 5 + Personal 1 + KeHoachKyKet 1 + System-set 7 + AwV2 3) — khớp canonical docs/STATUS.md:470-471 (Policies 220 = 55×4 · Menu keys 55). Cụm-1 khai 55→64 (+9) tại spec-cum1-chi-tiet-31-07-2026.md:8.
  • 🔴 Nhưng cụm-2 tự xếp :118 "K6 + K4a chạy song song cụm-1 ngay hôm nay (0 chặn)" + sơ đồ :109-110 vẽ K4a độc lập ⇒ K4a có thể land TRƯỚC K1. Khi đó dòng đúng là 55→104 · 220→416; ghi "64→113 · 256→452" = ghi số SAI vào canonical (đúng loại lỗi mà B1/detector đang canh).
  • Đề nghị: khai drift theo DELTA (+49 key · +196 policy) + lệnh đo lại MenuKeys.All từ mã tại thời điểm commit; hoặc ràng buộc cứng "K4a ship SAU K1".
  • Phụ: MenuItems LIKE 'Khkk%' = 56 sau K4a (7 đo thật + 49 mới) — root KeHoachKyKet không khớp LIKE nên 56 đúng.

F-7 [LOW · neo số trong chú thích] comment :1959-1961 tự trỏ upsert STALE

  • Spec mục 3 trích comment DbInitializer.cs:1959-1961 ("sửa chữ trong tree.Add VÔ TÁC DỤNG") — comment đó tự ghi upsert ở ":1893-1905", nhưng upsert THẬT ở :1926-1943 (đúng như spec mục 2 ghi).
  • Diff K4a chạm ngay khối này ⇒ sửa luôn cite trong comment (bài S161: diff sửa mã phải sửa MỌI số/neo trong chú thích).

Bảng verify claim chịu lực (grep/đo, KHÔNG suy)

# Claim (spec) Kết Dòng chứng
a1 site-1 All-loop CHỈ Admin ĐÚNG DbInitializer.cs:2047 method · :2049 FindByNameAsync(AppRoles.Admin) · :2058 foreach (var menuKey in MenuKeys.All) · :2061 RoleId = adminRole.Id
a2 KhkkKeys():2185-2189 feed reviewKeys ĐÚNG (EXACT) :2185-2189 (7 key: Khkk_G1+6 leaf) → :2216 .Concat(KhkkKeys()).Concat(HdcKeys()) trong reviewKeys :2210-2224; method SeedAllRolesReviewReadPermissionsAsync :2144, gọi :2090
a3 site-2 = CanRead mọi role, skip-existing ĐÚNG :2225 roleManager.Roles · :2251 continue (non-Pe skip) · :2254-2260 insert CanRead=true, CanCreate=isPe(false), CanUpdate=false, CanDelete=false
a4 site-3 độc quyền cờ GHI, comment :2418-2419 ĐÚNG (verbatim) :2415 method · :2418-2419 "Khkk_G1 = group điều hướng, CanRead-all đã có từ nhánh read-only" · :2440-2447 nâng R/C/U + D · site-2 không bao giờ set C/U/D cho key non-Pe
a5 "KHÔNG retire site nào" ĐÚNG 3 site không giao nhau về cờ; thứ tự :2090 site-2 → :2097 revoke (chỉ Hrm*/Off*/Personal) → :2110 site-3 ⇒ Khkk không bị revoke
a6 đủ chỗ sửa cho 13-role THẤY leaf LỆCH thiếu ràng buộc thứ tự cho resolvePath fe-user ⇒ F-2. Không thiếu: fe-*/lib/menuKeys.ts (0 hit Khkk cả 2 file) · IsVisible mặc-định true (MenuItem.cs:15 + MenuItemConfiguration.cs:18) nên fe-user/Layout.tsx:200 không nuốt row mới
b :2427 13/13 role · Delete=Drafter+Admin :2437 · keys từ đâu ĐÚNG :2427 var roles = await roleManager.Roles.ToListAsync() (KHÔNG lọc) · AppRoles.cs:23-28 All = 13 · đo SELECT COUNT(*) FROM Roles = 13 · :2437 role.Name == Admin || Drafter · biến keys = :2420-2425, 7 phần tử, KHÔNG có Khkk_G1 ⇒ convention "group không nhận cờ ghi" là THẬT ⇒ site-3 "+42 leaf (không group)" nhất quán
b2 BÁC-1: "7 vai" là của PE, không áp KHKK ĐÚNG :2669-2675 roleNames = Drafter/DeptManager/Procurement/CostControl/ProjectManager/Director/AuthorizedSigner = 7
c fe-admin khỏi regex (đệ quy + thiếu page) ĐÚNG (+F-3) fe-admin/Layout.tsx:186-190 đệ quy, gọi :315 · WorkflowMatrixViewPage chỉ ở fe-user (fe-user/App.tsx:83 route /khkk/workflow-matrix) · comment :129-133
d1 bẫy content bắt truyền count tay ĐÚNG (EXACT) PipelineStageFolders.tsx:68-69 "Bắt buộc khi dùng content" · :74-76 stageCount · nhánh content render :164-165 TRƯỚC leaves ⇒ mất empty-state :166-167 + <ul> :169-202 · type :58-72 · ml-3 border-l :156 · STAGE_STYLE:39-44 · hideEmpty :228
d2 3 file mirror; PipelineTreePanel không cần đổi ĐÚNG PipelineTreePanel.tsx:16-17 import usePipelineStages+PipelineStageFolders · :93 buildStages · :299 <PipelineStageFoldersconsumer, groups chảy qua. Bonus: 4 file hiện byte-identical ×2 app (10.677 B · 15.819 B) ⇒ B2 đo được ngay
d3 builder GĐ2/GĐ3/GĐ4 cite ĐÚNG (±1) GĐ2 usePipelineStages.ts:188-206 (spec ghi 189-206) · approvalGroup 0 hit ⇒ đúng là chờ K2 · "(ngoài chỉ mục 200)" :224 (comment :222-223) · c?.hasSealedCopy :231 · c.type :240
e 12 key blacklist đủ root, không ẩn nhầm ĐÚNG (12/12 cite EXACT) Master :1760 · Contracts :1771 · KeHoachKyKet :1775 · HopDongCung :1776 · Forms :1788 · Reports :1789 · Workflows :1795 · PurchaseEvaluations :1797 · PeWorkflows :1798 · Hrm :1809 · Off :1827 · Personal :1853 (+HrmConfig parent Master :1816). Root trong seed = 12 (thêm Dashboard :1756, System :1790) ⇒ phủ 10 root + 2 con-System; CHỪA Dashboard + System-4-leaf :1791-1794 + AwV2 :1801-18030 root nghiệp vụ sót · 0 System-leaf ẩn nhầm · AwV2 sống
f SQL chốt N 7/8 LỆCH xem F-4 (đo Dev = 2 AwV2; cần JOIN Permissions + đo trên prod)
g drift 64→113 · 256→452 LỆCH nền số học đúng; nền 55/220 (docs/STATUS.md:470-471) ⇒ xem F-6

Verified-OK (không phải lỗi — ghi để lane khác khỏi false-flag)

  • AuthorizePolicyRegressionTests.cs:139 + :402 chỉ assert MenuKeys.All.Should().Contain(...), không assert |All| ⇒ thêm 49 key KHÔNG vỡ test hiện có; "1 test BE" ở checklist :129 là bổ sung, không phải sửa test cũ.
  • "Policy sinh thêm 49×4 registered-unused = vô hại" Program.cs:82 loop thuần All × Actions, không nơi nào đếm.
  • group KHÔNG nằm trong TRANSIENT_QUERY_KEYS (fe-user/Layout.tsx:307 = id,q,editHeader,page,phase,awId) ⇒ group mang danh tính trong queryMatches/isActive (:326, :347) ⇒ quyết định K4b mục 8 (6 URL G1 phải mang group=1) là BẮT BUỘC, spec ghi đúng.
  • Root KeHoachKyKet có con ⇒ render MenuGroup, không đi MenuLeaf ⇒ không đụng active-check; staticMap :79 trở thành đường chết vô hại.
  • fe-admin vẫn có pages/khkk/{KhkkListPage,KhkkCreatePage,KhkkDetailPage,KhkkWorkflowPanel}.tsx ⇒ mục 13 "fe-admin VẪN sửa 2 page (deep-link)" có cơ sở; PipelineTreePanel còn nhúng ở fe-admin/pages/khkk/KhkkListPage.tsx:139 + pages/contracts/HardCopiesPage.tsx:117 ⇒ cây K5 sống trên admin qua deep-link dù K6 ẩn menu (không mâu thuẫn; B1 nên đo trên fe-user và khai rõ).

Verdict

PASS-WITH-FLAGS — 0 blocker về QUYẾT (khuôn groups K5, blacklist-12 K6, 3-site-grant K4a đều đứng vững trước code). 7 flag: 2 MAJOR phải xử trước khi ship (F-2 tách regex khỏi K3 ⇒ A1 mới đo được · F-6 khai drift theo DELTA hoặc ràng buộc thứ tự), 3 MEDIUM (F-1 sửa lý-do · F-3 dời comment sang fe-admin · F-4 đổi SQL sang JOIN + đo prod), 2 LOW (F-5 A5 đo cột · F-7 vá cite stale trong comment).