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

13 KiB
Raw Blame History

c2-l2

LANE 2/3 — lens ĐỦ-vs-SPEC cho cụm-2 (K4 sidebar 49-row · K5 cây groups · K6 admin 12-key blacklist). Nguồn chấm: sub-invest-fable-b2-cum2.md + spec-4gd-khkk-tong-quat-31-07-2026.md (§③ cụm-2, §② QĐ6/7, OG-8) + spec-cum1-chi-tiet-31-07-2026.md (12 vá). Tất cả evidence dưới đây tao tự đo lại trên ĐĨA/DB (không tin lời khai của lane).

Findings

HIGH

H1 — queryKey thiếu group ⇒ 8 nhóm dùng CHUNG 1 ô cache (silent-wrong, build xanh).

  • Claim của lane: K4b mục 7 (sub-invest-fable-b2-cum2.md:31) liệt đúng 2 chỗ — searchParams.get('group') cạnh :60-61 và truyền vào params :75-82 (cả nhánh /deleted :74) — KHÔNG có queryKey.
  • Evidence: fe-user/src/pages/khkk/KhkkListPage.tsx:70queryKey: ['khkk-list', { phase, search, page, deletedView }]. File này SHA-identical với fe-admin (sha256 16 ký tự đầu = 40a650bf6964ab4b cả 2 app) ⇒ lỗi nhân đôi.
  • Hệ quả: đổi ?group= KHÔNG đổi khoá cache ⇒ leaf nhóm 2 render danh sách nhóm 1 (rồi bị ghi đè chéo). tsc + npm run build + test đều XANH. Cùng họ với S159-③ (2 khoá cho 1 resource) nhưng CHIỀU NGƯỢC: 1 khoá cho 8 resource.
  • Sev: HIGH. Đề nghị: mục 7 thêm gạch "group VÀO queryKey :70"; A2 thêm bước "bấm G1→G2→G1 KHÔNG F5, số phải đổi".

H2 — Cỗ máy làm 42 leaf mới bấm-được (regex) nằm ở K4c (chờ K3), nhưng acceptance A1/A2 đo nó ở nấc K4a/K2 ⇒ A1 bất-khả-PASS.

  • sub-invest:43 A1 = "48 leaf HIỆN sidebar" và :119 khẳng định "A1/A3-A6 đo được NGAY sau K4a". Nhưng URL cho Khkk_G{n}_* chỉ sinh ở mục 10 (:37) = K4c, mà K4c :114/:120 CHỜ K3.
  • Evidence code: fe-user/src/components/Layout.tsx:338 if (!path) return null trong MenuLeaf ⇒ key không có trong staticMap/regex = DROP IM LẶNG (chính gotcha #50 mà file này tự dán ở :151-153). Sau K4a-only: 7 group-row hiện, 42 leaf KHÔNG render ⇒ A1 đếm 6, không phải 48.
  • Sev: HIGH (acceptance khẳng định đo được ở nấc mà nó chắc chắn trượt).
  • Đề nghị: tách mục 10 → 10a (List/Pending/Approved/Deleted/Create — 0 phụ thuộc, kéo về K4a) + 10b (WfView filter theo Code — giữ ở K4c chờ K3). Hoặc sửa A1 thành "đo sau K4a+10a".

H3 — 2 con số drift STATUS SAI trong MỌI kịch bản của chính thứ tự ship lane đề xuất.

  • Lane: sub-invest:27/:128 "Menu keys 64→113 · Policies 256→452 (55→64 đã khai cụm-1)".
  • Canonical đo tươi: docs/STATUS.md:471 Menu keys = 55; :470 Policies = 220 (DERIVED |MenuKeys.All| × 4).
  • Base 64 = 55 + 9, mà 9 key cụm-1 = 1 ContractCatalog (K1) + 8 AwV2_KhkkN* (K3) theo spec-cum1-chi-tiet:8. Thứ tự ship lane đề xuất (:117) là K6 → K4a → [K2] → K4b+K5 → [K3] → K4c ⇒ lúc commit K4a, K3 chưa land ⇒ STATUS đang ở 55 hoặc 56, không bao giờ 64. Ghi "64→113" = ghi sai canonical mà governance-detectors.ps1 đọc.
  • Sev: HIGH (số mang nhãn đo-lường; phép kiểm làm nó trượt = grep 'Menu keys' docs/STATUS.md ngay trước commit).
  • Đề nghị: khai DELTA (+49 key / +196 policy) + luật "đọc row STATUS ngay trước commit rồi cộng delta" (B1).

MED

M1 — Lý do giữ site-2 SAI (kết luận vẫn ổn). sub-invest:24 khẳng định "group-key CẦN CanRead row để render". Đo: src/Backend/SolutionErp.Application/Permissions/Queries/GetMyMenuTree/GetMyMenuTreeQuery.cs:96-97static bool HasAccess(MenuNodeDto n) => n.CanRead || n.Children.Any(HasAccess)cha render khi CON có CanRead. Extend site-2 cho 7 group-key = tuỳ chọn (thêm ~91 row Permission), không phải điều kiện render. Giữ hay bỏ đều được — nhưng phải sửa lý do, kẻo lượt sau suy sai sang cây khác.

M2 — "3 site" bỏ sót site thứ 4 lịch sử (revoker #84); tao đo hộ: an toàn. RevokeTemporarilyHiddenModulesAsync (DbInitializer.cs:2286) được gọi ở :2097GIỮA site-2 (:2090) và site-3 (:2110). Prefix revoke đo được :2297-2300 = Hrm* / Off* / PersonalKhkk* KHÔNG dính. Kết luận lane đúng nhưng chưa từng soi chỗ đã lật 447/494 row (#84). Đề nghị: thêm 1 dòng "site-4 revoker: đã soi, 0 match Khkk" — A5 (restart ×2) chính là phép thử nó.

M3 — phase không re-sync theo URL ⇒ A2 "lọc đúng group+phase" sẽ trượt khi bấm leaf→leaf. fe-user/src/pages/khkk/KhkkListPage.tsx:63-65 khởi tạo useState(initialFilter ? FILTER_TO_PHASE[...] : null) — chỉ chạy lúc MOUNT; grep cả file: setPhase( duy nhất :153 (chip lọc). Route /khkk/list không keyed (fe-user/src/App.tsx:81 element={<KhkkListPage />}) ⇒ đổi query KHÔNG remount. deletedView thì đọc thẳng searchParams :61 nên phản ứng đúng — lệch nhau trong cùng 1 file. Pre-existing với 4 leaf G1, nhưng ×8 nhóm và A2 đo trúng nó. Đề nghị: K4b thêm việc "derive phase từ searchParams" HOẶC hạ A2 xuống "mỗi leaf mở bằng F5".

M4 — "Thao tác" (Create) bị dời từ K4b sang K4c ⇒ trễ 1 wave vô cớ + lệch spec owner đã đọc. spec-4gd-khkk-tong-quat:29 xếp "K4b 4 leaf list + Thao tác (chờ K2 param)"; sub-invest:29 đổi tiêu đề K4b thành "4 leaf list" và đẩy Create preset xuống :39 (mục 12, K4c "chờ K3"). Preset chỉ cần picker của vá-4 cụm-1 (K2) — 0 phụ thuộc K3. Đề nghị: trả mục 12 về K4b (đi cùng 10a ở H2).

M5 — 8 nhãn menu tự chốt ngoài artifact OG-6, mà chi phí sửa-sau KHÔNG rẻ như lane nói. sub-invest:131 "owner chỉnh CHỮ tự do… không chặn wave nào" >< chính :11 khai "sửa chữ trong tree.Add VÔ TÁC DỤNG DB đang chạy" (comment DbInitializer.cs:1959-1961) ⇒ mỗi lần owner đổi chữ sau khi seed = +1 dòng labelBackfill + restart. Bảng OG-6 owner đang soát (danh-muc-sp002-transcribe.md:7-16) chỉ có cột Nhóm/Ký-chính/Nháy — không chứa 8 nhãn này. Kèm 1 sai lệch nội dung: nhãn N2 "Kết cấu, hạ tầng & DV hỗ trợ thi công (A2+A3)" mất vế "phá dỡ" (transcribe:29 A2 = "Kết cấu, hạ tầng, phá dỡ"; dòng :30 A2-01 = "Thầu phụ hạ tầng & phá dỡ"). 6/8 nhãn còn lại khớp transcribe (N7 rút gọn thành tên KHỐI "BCH công trường" — chấp nhận được). Đề nghị: đính 8 nhãn vào chính file OG-6 để owner soát 1 lượt, trước K4a.

M6 — Quyết "fe-admin KHÔNG nới regex" buộc chặt vào K6, mà K6 là display-only đảo được bất cứ lúc nào. sub-invest:40/:94 (dead-by-K6) >< :93 (route admin còn nguyên, thu gọn = menu). Nếu owner bỏ ẩn KeHoachKyKet trên admin ⇒ 48 leaf DROP IM LẶNG (#50), không ai nhớ ràng buộc. Đề nghị: nới regex fe-admin luôn (rẻ, 2 file đang SHA-identical) HOẶC ghi ràng buộc ngược ngay tại ADMIN_HIDDEN_KEYS: "gỡ key này ⇒ PHẢI nới regex Layout.tsx trước".

M7 — [Q6 tự-chốt] "Group RỖNG không render" đi ngược scope "cây mirror sidebar". sub-invest:55. Spec ① :7 gọi mảnh-3 là "cây mirror sidebar"; DoD K5 spec-tổng:30 = "sub-folder khớp sidebar". Sidebar LUÔN đủ 8 nhóm (menu-row cố định) còn cây ẩn nhóm rỗng ⇒ 2 mặt hết mirror; user tìm "nhóm 5" trên cây tưởng mất. Lý do lane nêu (8 folder rỗng = noise) hợp lý nhưng đây là quyết-định hiển-thị của owner. Đề nghị: hỏi 1 câu / hoặc mặc định hiện đủ 8 + toggle "ẩn nhóm rỗng".

LOW

L1 — A5 đo nhãn G1 có thể PASS-ở-DB nhưng SAI-ở-UI. MenuItem.cs:13,16: DisplayLabel override label cho fe-user (IsVisible=false ẩn khỏi /menus/me). labelBackfill sửa Label, không sửa DisplayLabel. Đo Dev: 10/10 row Khkk* + KeHoachKyKetDisplayLabel = NULL, IsVisible = 1 ⇒ hiện an toàn. Đề nghị A5 thêm vế AND DisplayLabel IS NULL.

L2 — [PASS mạnh, có số] Tập key K6 ĐỦ trên Dev; C1 chốt N = 7. Đo SolutionErp_Dev: MenuItems TOTAL = 142 (khớp con số STATUS ghi cho prod); root (ParentKey NULL) = 12 = {Dashboard 10, PurchaseEvaluations 25, KeHoachKyKet 26, Hrm 28, Off 29, Personal 30, Contracts 31, HopDongCung 32, Forms 40, Reports 50, Master 80, System 90} ⇒ 10/12 nằm trong ADMIN_HIDDEN_KEYS, 2 chừa đúng {Dashboard, System}; con của System = 7 {ApprovalWorkflowsV2, MenuVisibility, Permissions, PeWorkflows, Roles, Users, Workflows} ⇒ ẩn 2 (Workflows, PeWorkflows), chừa 5. AwV2_* = đúng 2 row, 0 AwV2_ContractN = 7 lá (Dashboard + Users + Roles + Permissions + MenuVisibility + 2 AwV2). Còn lại chỉ cần xác nhận prod.

L3 — "route-map ×2 app khai trong PR" trỏ artifact không tồn tại. 0 hit route-map trong docs/PROJECT-MAP.md|STATUS.md|HANDOFF.md; và cụm-2 KHÔNG đẻ route React nào — 48 leaf tái dùng /khkk/list|create|workflow-matrix đã có (fe-user/src/App.tsx:81-83). Surface drift thật = resolvePath trong Layout.tsx (fe-user) + 2 row STATUS. Đề nghị đổi chữ thành "bảng key→URL dán PR body".

L4 — Thứ tự sidebar 4 GĐ không liền mạch (đo Dev, xem L2): GĐ1=25, GĐ2=26, Hrm 28 / Off 29 / Personal 30, GĐ3=31, GĐ4=32. Sau K4a, GĐ2 phình thành 8 nhóm ngồi giữa dải bị chen. Owner S159 vẽ "menu = 4 giai đoạn" (DbInitializer.cs:1772-1773) nên đây là thứ anh ấy sẽ thấy ngay. Không thuộc scope — đề nghị hỏi 1 câu (đổi Order 27/28), cơ chế upsert re-set Order đã có (:1926-1943).

L5 — Lý luận bất-đối-xứng giữa K4c và K5 về bề mặt admin. PipelineTreePanel render trong fe-admin/src/pages/khkk/KhkkListPage.tsx:139fe-admin/src/pages/contracts/HardCopiesPage.tsx:117 ⇒ sau K6 hai trang này mất lối vào từ menu admin (chỉ deep-link). Lane đã khai chuyện "dead-by-K6" cho regex nhưng KHÔNG khai cho K5. Giữ mirror (đúng §3.9) nhưng nên khai 1 dòng để lượt sau không tưởng thừa.

L6 — B2 khả thi, 0 nợ drift ẩn (đo trước): 6/6 cặp file ×2 app hiện SHA-identical — usePipelineStages.ts 7144eeb7… · PipelineStageFolders.tsx d449cc8d… · PipelineTreePanel.tsx d70ac315… · KhkkListPage.tsx 40a650bf… · KhkkCreatePage.tsx ea51e762… · types/khkk.ts 8b757f5b… (types/khkk.ts ĐÃ tồn tại sẵn ⇒ K5 mục 5 là mở rộng, không phải tạo mới).

Điểm chịu được soi (khai để không hạ chuẩn 1 chiều)

  • P1 — mục 5 "sửa grounding 13 vai" ĐÚNG: DbInitializer.cs:2427 loop roleManager.Roles (mọi role), allowDelete = Admin || Drafter (:2436-2437); "7 vai" thật sự là seeder PE :2669-2675. Lane bác đúng đề bài, có chứng.
  • P2 — "extend KhkkKeys()" là BẮT BUỘC, không thừa: InReviewScope(key) => key.StartsWith("Pe_") (DbInitializer.cs:2158) ⇒ đưa 49 key vào MenuKeys.All KHÔNG tự kéo chúng vào site-2.
  • P3 — trích dẫn dòng sạch 100%: tao soi ~25 cite (seed :1760/:1771/:1775/:1776/:1780-1787/:1788/:1789/:1791-1794/:1795/:1797/:1798/:1801-1803/:1809/:1827/:1853 · KhkkKeys():2185-2189 · SeedKeHoachKyKetAccessAsync:2415 + keys :2420-2425 · isAdminHidden:182-184 + filterForAdmin:186-190 + comment S57 :179-181 · fe-user Layout.tsx:59-62/:79/:82-83/:341-347 · KhkkListPage:60-61/:74/:75-82 · PipelineStageFolders:58-72/:68-69/:74-76 · usePipelineStages:189+/:222-224/:231/:240) — 0 sai.
  • Q5 trả lời: việc "viết lại comment :177-184" CÓ trong danh sách (K6 mục 2) và mô tả đúng: :179-181 = "[S57] BỎ ẩn Danh mục… (đảo S29 hide)" ⇒ QĐ7 là lần đảo THỨ 2. Không rơi.
  • Q1 trả lời gọn: K4a-labelBackfill ✓ · K4b ?group ✓ (thiếu queryKey — H1) · K4c WfView-lọc-Code ✓ (mục 11, KHÔNG rớt) · Create-preset ✓ nhưng sai wave (M4) · K5 GĐ3/4 group-by c.type ✓ (mục 4, có làm, không chỉ GĐ2) · K5 fallback "(chưa phân nhóm)"/"(ngoài chỉ mục)" ✓ · K6 git diff --stat -- fe-user/src = 0 file ✓ (C2). Mục RƠI thật sự = 1: queryKey.
  • Q4 trả lời: precedent-split khai ĐỦ 2 khuôn (sub-invest:9 + evidence DbInitializer.cs:2193-2195). Chỗ người build dễ nhầm = (a) tưởng "vào All ⇒ site-2 tự có" — bác bằng P2; (b) tưởng "không có row ⇒ không render" — bác bằng M1.

Verdict

PASS-WITH-FLAGS — 16 finding (3 HIGH · 7 MED · 6 LOW), 0 blocker chặn ship nếu H1+H2+H3 được áp trước khi K4a/K4b vào việc. Nền cụm-2 chắc: tập key K6 đo được là ĐỦ (L2), trích dẫn sạch 100% (P3), 2 claim chịu-lực đúng (P1/P2). Lỗ nằm ở 3 chỗ quen thuộc: cache-key (H1), thứ-tự-vs-acceptance (H2), số-tuyệt-đối-trong-doc (H3).