Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k5.md
2026-08-01 10:38:46 +07:00

7.4 KiB
Raw Blame History

GATE-K5: IN-PROGRESS

Gate K5 — adversarial review cây pipeline GĐ2 mở 8 sub-folder nhóm (S166, 2026-08-01)

Vai: reviewer (READ-only, verdict-only). Ghi TỪNG MỤC, Edit sau mỗi mục (chống #53).

§0 Mốc đo (neo trước khi soi)

  • Lệnh: git status --porcelain + git rev-parse HEAD lúc 1st-measure.
  • 🔴 LỆCH TIỀN-ĐỀ #1: spec lead nói "diff chưa commit trên HEAD 88e7ecf". Đĩa đo được HEAD = ff4d4dc9d0b745867f83b5349f3c239d6b2ee798. Phải truy git log xem 88e7ecf nằm đâu trước khi tin base.
  • 🔴 RÀO GIT #1: git status?? menus.json ở ROOT repo (untracked, ngoài spec 4 file). Cần đo: file gì, có bị commit ké không.
  • Cây làm việc lúc mốc-0 (8 file M + 3 ??): 4 file code đúng spec + 4 file .claude/** + 3 untracked.

§1 Phân lập diff + rào git (đo, không tin lời khai)

  • git log: 88e7ecf LÀ tổ tiên của HEAD ⇒ tiền-đề lead đúng về nội dung, sai về tên mốc: giữa 88e7ecf và HEAD có 3 commit wal: flush. git diff --name-only 88e7ecf..HEAD = 6 file, TOÀN BỘ .claude/**, 0 file code ⇒ base code vẫn là 88e7ecf. Verdict K5 đo trên git diff (working-tree vs HEAD).
  • 🔴 Cây PHÌNH GIỮA LƯỢT (lần thứ 3 trong 3 gate liên tiếp): HEAD ff4d4dc lúc đo-0 → 13f7f82 sau ~2 tool-call. Phân lập: git diff --stat -- fe-admin fe-user src tests = đúng 4 file, +466/108 khớp spec TỪNG SỐ.
  • git status --porcelain -- src tests fe-admin fe-user = 0 dòng ?? ⇒ không có file code chưa track (bài "HIGH nằm ở git" KHÔNG tái phát trong vùng code).
  • 🔴 RÀO GIT — menus.json 44.656 B nằm ở ROOT repo, untracked (mtime 10:25, nội dung = payload /api/menus/me, sản phẩm phụ của lane cicd verify prod). Không phải file của K5, nhưng nếu lead commit bằng git add -A/git add . thì nó lên repo. Đây là finding về THAO TÁC COMMIT sắp tới, không phải về diff K5.
  • Cùng loại: .claude/agent-memory/implementer-frontend/archive/2026-H2.md untracked (hợp lệ, nên add có chủ đích).

§2 Build gate — TÔI TỰ CHẠY, không nhận lời khai

lệnh nơi chạy exit
npx tsc -b fe-user 0
npx tsc -b fe-admin 0
Lời khai lane (tsc -b ×2 exit 0 + npm run build ×2) khớp phần tôi đo lại. Bundle hash lane khai (index-CZUcTUUg.js / index-BZWvx0Y8.js) tôi KHÔNG kiểm lại — chưa deploy nên không có ô canonical để đối chứng; ghi rõ là lời khai chưa đo, đừng đọc thành đã-verify (bài K4a ③).

§3 SOI-1 — Node nhóm đúng nguồn + route khớp K4b

  • 1 nguồn, 0 map thứ hai. grep KHKK_APPROVAL_GROUPS|khkkGroupMenuLabel toàn fe-user/src+fe-admin/src: consumer = hook K5 (:217/:228/:230) + KhkkCreatePage + KhkkListPage, tất cả import từ @/types/khkk. Không có mảng 8 phần tử nào khác, không có bảng nhãn thứ hai.
  • 8 node dựng bằng KHKK_APPROVAL_GROUPS.map(...) (hằng [1..8] as const), nhãn khkkGroupMenuLabel(n) = "{n}. " + KHKK_APPROVAL_GROUP_LABELS[n]derive, không chép.
  • 🟢 ĐỐI CHIẾU LIVE (tôi tự đo, không nhận lời khai): so nhãn cây suy ra ⟂ nhãn thật trong payload /api/menus/me (menus.json prod) → 8/8 KHỚP TỪNG KÝ TỰ, cả 8 node Khkk_G1..G8 đều displayLabel = null. Tức hôm nay cây và sidebar gọi cùng một nhóm bằng cùng một tên.
  • Route: onOpenAll → /khkk/list?group=${n}. Đối chiếu K4b: Layout.tsx:217 khkkLeafPath(group,'List') = /khkk/list?group=${group}, và leaf tĩnh Khkk_List: '/khkk/list?group=1' (:63) ⇒ chuỗi TRÙNG KHÍT.
  • Nối end-to-end (3 chặng, đo cả 3): KhkkListPage.tsx:79-80 đọc searchParams.get('group') + rào ^[1-8]$:124 gửi group lên server → BE ContractSigningPlanFeatures.cs:757-758 q.Where(p => p.ApprovalGroup == request.ApprovalGroup). Không phải tham-số-trang-trí (khác hẳn bài K4a ①).
  • Sáng đúng mục sidebar: Layout.tsx:388-390 isActive = pathname khớp && queryMatches(...) (so khớp TẬP query) ⇒ ?group=n từ cây ăn khớp leaf nhóm n. Lời khai lane "trang tự sáng đúng mục" đúng, đã kiểm.

§4 SOI-2 — Phân phiếu, fallback, bất-biến Σ (1 MINOR)

  • KHÔNG có ca phiếu rơi khỏi cả 9 ngăn. Vòng :215-224 là if/else toàn phần: khớp typeof g === 'number' && GROUPS.includes(g) → ngăn n; mọi thứ còn lại (null · undefined · thiếu field · 0 · 9 · NaN · chuỗi) → khkkUngrouped. Không nhánh nào continue/bỏ qua.
  • Σ enforce ở đâu — trả lời thẳng: ở CẤU TRÚC, không phải ở kỷ luật. khkkLeaves và các ngăn cùng sinh từ một mảng khkkItems bằng một phép phân hoạch; stage.leaves giữ vai danh sách phẳng canonical và stageCount() (PipelineStageFolders.tsx:96) vẫn đếm trên nó ⇒ chip số của folder cha không thể lệch tổng các ngăn. console.warn :252 chỉ là chuông, và là DEV-only.
  • Thứ tự đúng: ngăn "(chưa phân nhóm)" push:238-245 TRƯỚC khi reduce tính Σ ở :251 ⇒ chuông không tự báo động giả.
  • Ngăn lạc gần như không thể xảy ra (đo BE): ApprovalGroupint non-null, validator InclusiveBetween(1,8) (:353-356), default 1 (:399), backfill Mig 71 = 1, và 0 đường update nào ghi lại field này. ⇒ nhánh fallback là phòng thủ, không phải đường sống.
  • 🟡 MINOR-1 — nhãn ngăn lạc hứa một đằng, đi một nẻo: onOpenAll: () => navigate('/khkk/list') (:243). Không kèm ?group= ⇒ BE không lọc gì cả, trang mở ra là toàn bộ phiếu của cả 8 nhóm, trong khi nhãn và tooltip nói "Mở danh sách: (chưa phân nhóm)". Sidebar cũng không sáng mục nào (query rỗng không khớp leaf nào). BE hiện không có bộ lọc "không thuộc nhóm nào" để trỏ tới, nên đây là hạn chế thật chứ không phải quên gõ. Đề nghị: bỏ onOpenAll cho riêng ngăn này (component đã có nhánh <span> khi thiếu onOpenAll, :151-155) — mất một cú bấm gây hiểu nhầm, không mất gì khác.

§5 SOI-3 — Ngăn rỗng render THẬT

Đo đường đi, không đọc lời khai — 3 chốt, không chốt nào lọc theo length:

  1. Dựng: KHKK_APPROVAL_GROUPS.map(...).map trên hằng 8 phần tử, 0 .filter trong cả khối.
  2. Vẽ: PipelineStageFolders.tsx:287-295 stage.groups !== undefined ? stage.groups.map(g => <GroupFolder…>) — vẽ thẳng mảng nhận được, không điều kiện phụ.
  3. Trong ngăn: GroupFolder count === 0 → vẫn render <details> đủ (chevron + tên bấm được + SoonChip), thân in "Chưa có phiếu ở nhóm này."
  • Nhánh "Chưa có dữ liệu ở giai đoạn này." (:296) nay bị nhánh groups chặn trước ⇒ GĐ2 rỗng hiện 8 ngăn thay vì 1 dòng. Đúng spec "HIỆN ĐỦ 8 kể cả rỗng".
  • Không đụng hideEmpty: vẫn lọc ở tầng stage bằng stageCount() = leaves.length ⇒ đang tìm-kiếm mà gói không có KHKK thì cả folder GĐ2 ẩn, không đổ ra 8 ngăn rỗng.

(đang tiếp tục §6)