- **S166 (08-01) gate K4b nối `?group=` end-to-end — PWF 4 MAJOR/5 MINOR, 0 blocker:** lõi ĐÚNG (derive-thuần + reset-trong-render `navKey`, 6/6 ca chuyển leaf hết cần F5; BE `/deleted` mirror list-sống ĐÚNG TỪNG NÉT, vế lọc SAU IDOR; sha mirror ×3 khớp; build 0E + `tsc -b`×2 EXIT=0 **tôi tự chạy**). **Bài thu:** ① 🔴 **mỏ-neo XUYÊN FILE** — wave-B sửa file A làm sai chú-thích-neo-số-dòng trong file B (`Layout.tsx:204` trỏ `KhkkListPage :61/:62`, đĩa = `:74/:76`), 2 wave vào **1 commit** ⇒ sai từ **byte-0** chứ không trôi dần; đo 2 đầu rẻ: `git show <base>:<file> | grep -n` vs đĩa. Tái phát bài K4a ② sau ĐÚNG 1 wave, lần này khác file ⇒ quét mỏ-neo phải quét **file BỊ dời**, không chỉ file đang sửa. ② **phán NGƯỢC 1 FLAG của lane** (queryKey mang giá-trị-ĐÃ-GIẢI-MÃ): ĐÚNG, vì khoá cache định danh **REQUEST** không định danh URL — kèm **điều-kiện-phản-chứng** ("sai nếu raw `filter` chảy vào JSX", đã grep hết đường đi) thay vì gật "OK". ③ **HIGH-nằm-ở-git tái xuất** — sắp commit gộp mà `git status --porcelain -- src tests | grep '^??'` vẫn ra test K4a **chưa track** ⇒ mã seed lên repo, test chứng minh nó thì không. ④ **cây phình giữa review** lại xảy ra (HEAD +2 commit, +123 dòng) nhưng phân lập được bằng `git diff --stat -- <đường-code>` + sha lại file mirror ⇒ **.claude-only, verdict giữ**; đây là cách rẻ để không phải soi lại từ đầu. ⑤ tham-số-trang-trí **không tự khỏi**: K4b đẻ instance thứ 8 (`Khkk_WfView?group=1`) khi consumer vẫn 0 hit. ⑥ positional-record + object-initializer: điều-kiện ẨN = base phải là **record** (record không kế thừa class) — kiểm base rồi hãy phán "compile được". Tag `[s166, moneo-xuyen-file, khoa-cache-dinh-danh-request, high-o-git, cay-phinh]`**Evidence:**`runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4b.md`
- **S166 (08-01) gate K4a 49-row menu KHKK 8-nhóm — PWF 4/0 blocker:** lõi ĐÚNG hiếm thấy (bộ-sinh 1 nguồn feed 3 phía `All`/seed-row/grant ⇒ khớp by-construction; 8/8 nhãn khớp TỪNG KÝ TỰ; suite **620/620 tôi tự chạy**). **Bài thu:** ① 🔴 **tham-số-trang-trí** — URL leaf mang `?group=n` mà `grep "get('group')"`×2 app = **0 hit** ⇒ 7 nhóm × 6 leaf mở ra CÙNG nội dung, `WfView` hiện toàn bộ workflow type-10; *đo "route có tồn tại" là chưa đủ, phải đo **AI ĐỌC** từng param* (đối xứng ca S164 "tham-số-chết-vì-thiếu-tầng-UI", lần này chết ở tầng ĐỌC). ② **mỏ-neo tự-vô-hiệu ngay lúc land** — 2 comment mới trỏ dòng trong CHÍNH file đang sửa, sai đúng **+34 = kích thước khối vừa chèn** (tác giả tính trên bản trước khi chèn); cùng diff đó vừa DỌN lỗi y hệt ở BE (`1893`) ⇒ **mỏ neo trỏ vào file mình đang sửa phải đo SAU khi chèn**, hoặc bỏ số dùng grep-anchor. ③ **ô canonical sai đo-được bằng 1 curl** — STATUS ghi bundle `mySTlx42`/`CZAYiWWa`, live = `1yiNV4VH`/`DoULfmdT` (2 css KHỚP ⇒ chứng đo đúng site, không phải đo nhầm). ④ **bất-biến từng-vỡ-thật lại là bất-biến 0 test** (revoker không đụng `Khkk_*`; thêm 1 chữ vào filter thì 620/620 vẫn xanh, đúng kịch bản Run #423 mà chính comment dẫn lại) — khuôn có sẵn `Revoke_DoesNotTouch_PeModule`. ⑤ test gọi seeder LẺ qua reflection ⇒ chuỗi `SeedAsync` (grant→revoke→site-3) ngoài vùng test ⇒ **restart-thật KHÔNG được coi là đã phủ**. Tag `[s166, tham-so-trang-tri, moneo-tu-vo-hieu, o-canonical-sai-1-curl]`**Evidence:**`runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4a.md`
- **S166 (08-01) vá F-1 ô-tích opt-out `applyLevelFinalize` KHKK — PWF 7 finding (3 MAJOR):** vá ĐÚNG kỹ thuật (mirror 2 app chứng bằng **blob-SHA in ngay trong `git diff` index-line** — rẻ hơn sha256 tay; body không rò field; cấp thường bất-biến từng bit). **Bài thu:** ① 🔴 **khuôn-nguồn có 2 tầng, chú-thích nói dối tầng dưới** — implementer trích ĐÚNG dòng JSX PE `:772-791` + nhãn "khuôn PE S96 default tick", nhưng `useState` PE `:61` = `false` (S97 owner **ĐẢO** sang opt-IN) và chính chú-thích PE `:769-771` cũng stale ⇒ **đo khuôn phải đo GIÁ-TRỊ KHỞI-TẠO, không đọc chú-thích**; tái phát S165 F-1 (đo sai tầng). ② **cây làm việc PHÌNH GIỮA LƯỢT REVIEW** — đo `git status` 2 lần cách ~40 tool-call: 5 file→7 file (+121 dòng K4a, `All` 64→113) ⇒ **verdict phải neo mốc đo + re-đo `--stat` TRƯỚC khi phát verdict**, nếu không lead commit ké code chưa qua cổng. ③ vắng-mặt-đọc-thành-sạch: `isApproved ⇒ mọi Bước 'Done'` ⇒ CEO chưa đụng vẫn "đã duyệt", dấu-vết duy nhất = THIẾU dòng "✓ ký". ④ **sợi-dây-1-argument** (projection `l.AllowApproverFinalize`) 0 test ⇒ xoá là control biến mất mà 614 test + tsc vẫn xanh. Tag `[s166, khuon-2-tang-chu-thich-noi-doi, cay-phinh-giua-review, sợi-day-1-argument]`**Evidence:**`runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-f1-checkbox.md`
- ⚠️ **NỢ CURATE @S166-K4b:** file đang ~20KB (quá ngưỡng 17.1KB, VẪN dưới read-limit 24.4KB nên chưa mất dòng nào). CỐ Ý không curate vội giữa lượt gate — curate gấp là đúng kịch bản `cut-not-moved` S102. Phiên sau: move VERBATIM 2-3 entry S166 (K4a/F-1/K4b) → `archive/2026-07.md`, probe moved-not-cut TRƯỚC khi xoá khỏi L1; detail đầy đủ đã nằm sẵn ở `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-*.md` (git-tracked).
- Hook-cap **>17.1KB** (24.4KB read-limit — đổi từ ~30KB cũ, S109) → archive recent → L2 `archive/<period>.md` (append additive) + `_INDEX.md` substring pointer. Stale >3mo → remove.
@ -35,3 +35,232 @@ K4a (MenuKeys/DbInitializer/Layout-regex/types/STATUS/test) ĐÃ qua gate K4a
-`ContractSigningPlan.ApprovalGroup` là `int`**không nullable**, default `= 1` (`ContractSigningPlan.cs:41`) ⇒ so sánh `== request.ApprovalGroup` (int? vs int) dịch SQL thành so sánh giá trị, không sinh ca NULL-semantics.
- **Không có validator biên 1..8** cho `/deleted` — nhưng list SỐNG cũng không có ⇒ **consistent**, ghi nhận chứ không tính lỗi. `?group=999` = lọc rỗng (200 + `items: []`, `total: 0`), không 500. `?group=abc` = model-binding fail ⇒ 400 do `[ApiController]`. Cả hai hành vi giống hệt list sống.
- FE gửi `group` cho **cả 2 nhánh** (`KhkkListPage.tsx:124`, nằm ngoài tam-nguyên `deletedView` nên áp cho cả `/contract-signing-plans` lẫn `/deleted`) ⇒ khớp BE mới. Title/subtitle màn "Đã xóa" in tên nhóm — hết là lời nói dối vì server đã lọc thật.
**Cơ chế lane dùng:** KHÔNG phải `useEffect`. Là hai lớp:
1.**Derive thuần** — `urlFilter`/`urlPhase`/`deletedView`/`group` đọc thẳng `searchParams` mỗi render (`:74-80`). `useSearchParams` bám `location` nên đổi query (cùng route) VẪN re-render ⇒ 3 trục danh tính luôn tươi. Trước K4b `phase` là `useState(initialFilter…)` — khởi tạo 1 lần, đây đúng là nguồn của lỗi "phải F5".
2.**Lớp override 3-trạng-thái** — `chipPhase: KhkkPhaseValue | null | undefined`, `undefined` = chưa bấm chip (nghe URL), `null` = user chọn "Tất cả", số = user chọn trạng thái. Tách `undefined` khỏi `null` là ĐÚNG và cần: gộp lại thì bấm "Tất cả" trên leaf `?filter=ChoDuyet` sẽ bị URL kéo ngược.
3.**Reset trong render** (`:95-101`) — `navKey = ${group}|${urlFilter}|${deletedView}`, so với `lastNavKey`, khác thì `setLastNavKey` + `setChipPhase(undefined)` + `setPage(1)`. Đây là khuôn React chính thống "điều chỉnh state khi prop/input đổi", KHÔNG phải `useEffect` ⇒ không có lượt vẽ bằng dữ liệu cũ.
**Bảng ca chuyển tôi soi từng ca:**
| Ca | navKey trước → sau | reset `chipPhase` | reset `page` | `phase` cuối |
|---|---|---|---|---|
| leaf→leaf cùng nhóm khác filter (`?group=2` → `?group=2&filter=DaDuyet`) | `2\|\|` → `2\|DaDuyet\|` | CÓ | CÓ | `DaDuyet` |
| leaf→leaf khác nhóm cùng filter (`?group=2&filter=ChoDuyet` → `?group=5&filter=ChoDuyet`) | `2\|ChoDuyet\|` → `5\|ChoDuyet\|` | CÓ | CÓ | `ChoDuyet` (đúng, chỉ đổi `group`) |
| vào deleted (`?group=3&filter=DaDuyet` → `?group=3&view=deleted`) | `3\|DaDuyet\|` → `3\|\|del` | CÓ | CÓ | `null` |
| ra khỏi deleted | `3\|\|del` → `3\|\|` | CÓ | CÓ | `null` |
| root trần `/khkk/list` ↔ leaf nhóm 1 `?group=1` | `\|\|` ↔ `1\|\|` | CÓ | CÓ | `null` |
| mount lần đầu | `lastNavKey` khởi tạo = `navKey` | KHÔNG (đúng) | KHÔNG | theo URL |
⇒ **Vá-5 ĐẠT.** Cả 6 ca đều re-render đúng, không cần F5.
**2 ghi chú kỹ thuật (không phải lỗi):**
- Lượt render bị React huỷ (lượt phát hiện `navKey` đổi) vẫn chạy tới `useQuery` với `page`/`chipPhase` CŨ. TanStack v5 `getOptimisticResult` sẽ `build()` một ô cache rỗng cho khoá cũ đó; effect không chạy trên render bị huỷ nên **không có fetch thừa**, ô rỗng bị gc. Vô hại.
-`search` CỐ Ý không reset (lane FLAG-4, lead giữ). Nó nằm trong `queryKey` nên không đẻ sai dữ liệu; chỉ là lựa chọn UX.
**MINOR-1 (không phải hồi quy):** bấm LẠI đúng leaf đang đứng ⇒ `navKey` không đổi ⇒ chip override KHÔNG nhả. Ví dụ: đứng ở "Đang duyệt" nhóm 1, bấm chip "Tất cả", rồi bấm lại mục "Đang duyệt" trong sidebar → vẫn hiện tất cả. Hình dạng này ĐÃ có trước K4b (khi đó `phase` là `useState` khởi tạo 1 lần) nên K4b không làm xấu đi. Muốn chữa thì so bằng `location.key` thay vì `navKey`.
**Đủ trục chưa — đối chiếu với ĐÚNG tập tham số gửi lên server** (`:117-128`): `phase` (bỏ khi deleted), `group`, `search`, `page`, `pageSize` (hằng), + chính `url` (`/contract-signing-plans` vs `/deleted`).
| Thứ quyết định response | Có trong khoá? |
|---|---|
| endpoint (`deletedView`) | CÓ |
| `group` | CÓ (vá-3, trục mới) |
| `phase` | CÓ |
| `search` | CÓ |
| `page` | CÓ |
| `pageSize` | hằng `PAGE_SIZE` — không cần |
⇒ **Không thiếu trục nào.**
**PHÁN FLAG-2 (lane nêu, lead nhận, tôi phán lại — ĐỘC LẬP): dùng `phase` đã giải mã là ĐÚNG, không phải nhân nhượng.**
- Lý do quyết định: khoá cache phải định danh **REQUEST**, không định danh **URL**. `filter` thô không bao giờ được gửi đi; thứ gửi đi là `phase`. Hai chuỗi `filter` khác nhau mà cùng giải mã ra một `phase` sẽ sinh **hai request byte-identical** ⇒ gộp một ô cache là đúng, tách ra mới là lỗi (đôi cache, đôi lượt mạng cho cùng một câu hỏi).
- Ca lệch mà spec hỏi (`?filter=rác` → `phase=null` → trùng khoá với "tất cả"): **hành vi ĐÚNG**. `?filter=rác` khiến trang hiển thị mọi trạng thái, y hệt "tất cả" — cùng màn hình, cùng dữ liệu, nên cùng ô cache. Không có gì để phân biệt.
- Chỗ raw `filter` VẪN cần thì nó CÓ mặt: `navKey` (`:95`) dùng `urlFilter` THÔ, nên hai leaf `?filter=rác1` / `?filter=rác2` vẫn được coi là hai lượt điều hướng khác nhau và vẫn reset chip/page. Hai vai trò tách bạch đúng chỗ.
- Điều kiện để phán này SAI (nêu ra để sau này ai đổi thì biết): nếu trang bắt đầu render khác nhau theo chuỗi `filter` thô (ví dụ in tên bộ lọc lấy từ URL). Hiện `urlFilter` chỉ chảy vào `urlPhase` + `navKey`, không chảy vào JSX — tôi đã grep hết đường đi của nó trong file.
**MINOR-5:** trong `deletedView`, `phase` vẫn nằm trong khoá nhưng KHÔNG được gửi (`:119`). Chip bị ẩn ở màn xoá (`:201 {!deletedView && …}`) và không leaf nào sinh URL vừa `filter` vừa `view=deleted`, nên thực tế `phase` luôn `null` ở đó. Chỉ URL gõ tay mới đẻ được 2 ô cache cho cùng một response. Vô hại, có sẵn từ trước K4b.
---
## MỤC 3 — FLAG-1: bản vá LEAD TỰ VIẾT (BE 3 chỗ + FE 2 nhánh) — soi kỹ nhất
*(số đo chi tiết đã ghi ở M3 phía trên; đây là phần phán)*
**3 chỗ BE — đối chiếu từng nét với list SỐNG:**
| | List sống (K2, `:756-758` + Controller `:42,49`) | `/deleted` (K4b, lead viết) | Khớp? |
| vị trí lọc | SAU khối IDOR, TRƯỚC `Search` | SAU khối IDOR, TRƯỚC `Search` | KHỚP |
| vị ngữ | `p.ApprovalGroup == request.ApprovalGroup` | y hệt | KHỚP |
**Positional-record trộn object-initializer có compile đúng thứ tự không** (spec hỏi thẳng): CÓ, và không có bẫy thứ tự. C# chạy constructor positional TRƯỚC, rồi mới gán object-initializer; hai tập không giao nhau (`ApprovalGroup` chỉ ở ctor; `Page/PageSize/Search/SortDesc` chỉ ở initializer, đều là `init` accessor của `PagedRequest`). Điều kiện ngầm khiến nó hợp lệ: `PagedRequest` phải là **record** (record không kế thừa được class) — đã kiểm: `public abstract record PagedRequest` (`Common/Models/PagedResult.cs:15`). `dotnet build` 0E do tôi tự chạy xác nhận.
**Validator biên 1..8:** KHÔNG có, ở CẢ HAI endpoint ⇒ **consistent, ghi nhận chứ không trừ điểm** (đúng như spec dự liệu). Hành vi đo được bằng suy luận kiểu:
-`?group=abc` → bind fail → **400** tự động do `[ApiController]`.
-`?group=` (rỗng) → `null` → xem tất cả.
- Không có ca nào NỚI phạm vi thấy được, vì vế lọc nằm SAU IDOR và chỉ thu hẹp.
**FE 2 nhánh:**`group: group ?? undefined` (`:124`) đặt NGOÀI biểu thức tam nguyên `deletedView`, nên áp cho cả `/contract-signing-plans` lẫn `/deleted` — đúng "cả 2 nhánh". FE chỉ gửi khi `group` đã qua `/^[1-8]$/` (`:80`) nên rác không tới server. Title/subtitle màn xoá in tên nhóm (`:166,175`) — hợp lệ VÌ server đã lọc thật; nếu thiếu vá BE thì đúng là "tên nhóm nói dối" như chú thích lead viết.
🔴 **MAJOR-A — `sợi-dây-1-argument`, 0 test.** Toàn bộ tính năng này treo trên 2 mẩu: `(group)` ở Controller và 3 dòng `if` ở handler. Tôi grep `tests/` cho `ListDeletedContractSigningPlansQuery` → **0 hit**; không test nào chạm handler này. Phép thử phản chứng: xoá `(group)` khỏi `new ListDeletedContractSigningPlansQuery(group)` ⇒ tính năng chết câm, mà `dotnet build` vẫn 0E, bộ test lead báo 41/41 vẫn xanh, `tsc`×2 vẫn EXIT=0 — **không phép kiểm nào TRƯỢT**. §7 cho phép test-after với feature mới nên đây không phải điều kiện chặn commit, nhưng phải vào sổ nợ K4c: 1 test "hai phiếu xoá khác nhóm, `?group=n` chỉ trả 1" là đủ. (Khuôn có sẵn: `ContractSigningPlanApprovalTests`.)
---
## MỤC 4 — staticMap G1 vs regex nhóm 2..8: có key nào trùng-match 2 đường không?
**Đo:**`staticMap` (fe-user `:63-87`) dùng 6 key **KHÔNG-infix**: `Khkk_List`, `Khkk_Create`, `Khkk_Pending`, `Khkk_Approved`, `Khkk_WfView`, `Khkk_Deleted`. Regex (`:212`) là `^Khkk_G([1-8])_(WfView|List|Create|Pending|Approved|Deleted)$` — **bắt buộc có khúc `G{n}_`**. Hai tập **rời nhau theo cấu trúc**: không chuỗi nào vừa có vừa không có infix. Thêm nữa `if (staticMap[key]) return staticMap[key]` (`:139`) chặn TRƯỚC regex, nên kể cả có trùng thì staticMap thắng — thứ tự an toàn.
**Đo phía dữ liệu (không suy từ code FE):**`MenuKeys.KhkkGroupNumbers = [2,3,4,5,6,7,8]` (`MenuKeys.cs:72`) ⇒ BE **không hề sinh** key `Khkk_G1_*`; `grep -rn "Khkk_G1_" src/Backend fe-admin/src fe-user/src` = **0 hit**. Nhánh `1` trong dải `[1-8]` của regex là nhánh CHẾT — lane đã khai rõ là cố ý, tôi xác nhận vô hại hôm nay.
⚠️ **Bẫy ngủ (ghi để người sau biết, KHÔNG tính lỗi hôm nay):** nếu sau này ai seed `Khkk_G1_List`, sẽ có **hai leaf khác key nhưng cùng URL**`/khkk/list?group=1` (một từ staticMap, một từ regex). `queryMatches` (`:353-374`) so khớp active bằng **URL chứ không bằng key** ⇒ tái hiện đúng lớp bug UAT S155 "mục render sau thắng". Ai chuyển nhóm 1 sang khuôn infix thì phải **bỏ 6 dòng staticMap cùng lượt**.
| `Khkk_WfView` | `/khkk/workflow-matrix?type=10&group=1` | route sống, **`group` KHÔNG có người đọc** — xem MAJOR-C |
`group` không nằm trong `TRANSIENT_QUERY_KEYS` (`:349` = `id, q, editHeader, page, phase, awId`) ⇒ so khớp active NGHIÊM theo `group`; 8 leaf cùng hành động khác nhóm phân biệt được, và root trần `/khkk/list` (0 key query) không khớp leaf `?group=1` (1 key) vì `queryMatches` so cả SỐ LƯỢNG khoá. Đúng ý đồ.
🔴 **MAJOR-C — tham-số-trang-trí lan sang staticMap.**`grep "searchParams.get('group')"` trên cả 2 app = **4 hit, TẤT CẢ nằm ở `KhkkListPage` + `KhkkCreatePage`**; `WorkflowMatrixViewPage`**0 hit**. Tức `Khkk_WfView` (mới thêm `&group=1` trong ĐÚNG diff K4b này) và 7 leaf `Khkk_G{2..8}_WfView` (K4a) đều mở ra **cùng một ma trận workflow type-10**, không lọc theo nhóm. Đây là đúng finding ① tôi đã nêu ở gate K4a; K4b **không sửa mà thêm cái thứ 8**. Lead đã disposition FLAG-3 = K4c/K5 theo build-order ⇒ tôi KHÔNG chặn commit vì nó, nhưng ghi rõ: hôm nay `&group=1` ở dòng `Khkk_WfView` chỉ có tác dụng **phân biệt active-state**, KHÔNG có tác dụng lọc nội dung. Ai đọc URL mà tưởng nó lọc thì hiểu sai.
---
## MỤC 5 — Mirror 2 app
Bảng sha256 đã ghi ở **M2**: `KhkkListPage.tsx` = `fa4c7aa268f35eeb` ở CẢ HAI app (khớp đúng `fa4c7aa2` spec yêu cầu xác minh sau khi lead cp lại), `KhkkCreatePage.tsx` = `545b4c2fe0ebae88`×2, `types/khkk.ts` = `7a99b86518948093`×2. Không file nào lệch.
`Layout.tsx` lệch by-design và **lệch có khai ở cả hai phía**:
- admin `Khkk_WfView` → `/system/approval-workflows-v2/ContractSigningPlan` (Designer), **cố ý KHÔNG mang `group`** — chú thích ngay tại chỗ (`fe-admin/.../Layout.tsx:37-39`) nói rõ Designer không đọc tham số này. Tôi đồng ý: mirror mù ở đây mới là lỗi.
- 5 dòng `/khkk/*` còn lại của admin ĐÃ mang `group=1` (`:41-45`) ⇒ không có ca "một app nói dối, một app nói thật".
- admin không nới regex `Khkk_G*` — hệ quả 42 leaf nhóm 2..8 drop im lặng bên admin; **đã khai bằng khối ràng-buộc-ngược tại `isAdminHidden`** (`:199-210`) và là trạng thái K4a chấp nhận, không phải phát sinh mới ở K4b.
---
## MỤC 6 — Hồi quy đường cũ không-param
| Đường | Kỳ vọng | Đo được | |
|---|---|---|---|
| `/khkk/list` trần (root `KeHoachKyKet`, fe-user staticMap `:83`) | xem TẤT CẢ nhóm | `groupParam === null` ⇒ `group = null` ⇒ `group ?? undefined` ⇒ axios bỏ tham số ⇒ BE `ApprovalGroup is null` ⇒ không thêm vế `Where` | ĐÚNG |
| tiêu đề trang khi trần | không in tên nhóm | `group !== null ? … : 'Kế hoạch ký kết HĐ'` | ĐÚNG |
| `/khkk/list?group=rác` | không tự đoán nhóm 1 | regex `/^[1-8]$/` trượt ⇒ `null` ⇒ xem tất cả | ĐÚNG |
**MINOR-2:** trên `KhkkCreatePage`, hai nút "Huỷ" (`:165`, `:373`) đều `navigate('/khkk/list')`**không mang `group`** — người dùng đi từ leaf nhóm 5 vào lập phiếu, bấm Huỷ thì rơi về danh sách MỌI nhóm. Bất đối xứng với chiều đi (nút "Lập kế hoạch" ở `KhkkListPage:186` có mang `group`). Không sai dữ liệu, chỉ mất ngữ cảnh.
**MINOR-3:** đi từ `/khkk/create?group=3` sang `/khkk/create` (trần) thì ô chọn GIỮ 3, trong khi chú thích `:64` viết "không có tham số thì giữ nguyên default cũ" — chữ và việc lệch nhau một nhịp (nhánh `if (presetGroup !== null)``:82` cố ý không đụng khi về null). Hành vi thực tế hợp lý hơn chữ; sửa CHỮ là đủ.
**Đã kiểm giao thoa preset × rào nhóm-đã-dùng:** vào `?group=3` rồi chọn PE đã có kế hoạch nhóm 3 ⇒ `selectPe` (`:147-154`) nhảy sang nhóm trống đầu tiên; nếu người dùng đổi leaf sang nhóm đã dùng thì `groupTaken` bật, nút Lưu **disabled** (`:380`) + dòng đỏ (`:313`). Preset KHÔNG phá được rào 409. ĐẠT.
---
## MỤC 7 — Mỏ neo số dòng (tái phát bài K4a ②)
🔴 **MAJOR-B — mỏ neo bị CHÍNH diff này làm sai, và sai ngay lúc land.**
`fe-user/src/components/Layout.tsx:204-205` viết:
> ``Tên tham số `filter` / `view` lấy ĐÚNG từ `KhkkListPage` (`:61` `get('filter')` → `FILTER_TO_PHASE`, `:62` `get('view') === 'deleted'`)``
Đo hai đầu:
-`git show 7a903cf:fe-admin/src/pages/khkk/KhkkListPage.tsx | grep -n` → `61: get('filter')`, `62: get('view')` — **đúng ở thời điểm K4a viết**.
- Trên đĩa hôm nay (sau K4b) → **`74:``get('filter')`, `76:``get('view')`** — lệch +13 / +14, đúng bằng khối chú thích + derive mà K4b chèn vào đầu component.
Vì K4a và K4b **vào CHUNG một commit** (lead đã khai), mỏ neo này sẽ sai **từ byte đầu tiên** của commit — không phải trôi dần theo thời gian. Đây đúng lớp lỗi tôi đã nêu ở gate K4a mục ②, tái phát sau đúng một wave, lần này **xuyên file** (K4b sửa file A làm hỏng chú thích trong file B). Mỉa mai đo được: chính khối chú thích đó, cách 2-3 dòng, dặn hai lần *"grep tên hằng — đừng neo số dòng"* và *"số dòng trượt theo edit"*.
**Tiêu chí nghiệm thu để gỡ:** bỏ `:61`/`:62`, thay bằng mỏ neo grep-được (ví dụ trích chính chuỗi `searchParams.get('filter')` / `FILTER_TO_PHASE`), hoặc cập nhật thành `:74`/`:76`**và** đo lại sau khi mọi edit của cả 2 wave đã nằm yên. Sửa 1 dòng, không đụng logic, không cần build lại.
**Quan sát kèm (KHÔNG tính cho K4b — nợ có sẵn từ K2):**`ContractSigningPlanFeatures.cs:332` viết ``client hiện tại (`KhkkCreatePage.tsx:73-75` ×2 app) CHƯA gửi trường này``. Hai vế đều đã sai TRƯỚC K4b: `:73-75` pre-K4b là chú thích của query `workflows`, không phải chỗ dựng body; và client **CÓ** gửi `approvalGroup` (`KhkkCreatePage.tsx:119`, do chính K2 thêm). K4b chỉ đẩy nó lệch thêm 21 dòng. Ghi vào sổ nợ, đừng tính vào cổng này.
---
## MỤC 8 — Bảng 6 hạng mục + nghiệm thu
| # | Hạng mục | Kết quả | Ghi chú |
|---|---|---|---|
| 1 | Wire BE / feature claim | **PASS** | `group` đi trọn FE→BE ở CẢ 2 nhánh; BE lọc thật, `total`/số-trang nói thật |
| 2 | Toàn vẹn schema | **PASS** | 0 migration, 0 đổi cột; `ApprovalGroup` là `int` non-null default 1 nên vế lọc không sinh ca NULL |
| 3 | Bảo mật | **PASS** | vế lọc đặt SAU IDOR (chỉ thu hẹp) ×2 handler; `[Authorize(Policy="KeHoachKyKet.Read")]` giữ nguyên cả class lẫn action; không endpoint mới |
| 4 | Chất lượng mã | **PASS-WITH-FLAGS** | build 0E + tsc ×2 EXIT=0 (tôi tự chạy); mirror sha ×3 khớp; **MAJOR-B** mỏ neo sai |
| 5 | Độ phủ test | **PASS-WITH-FLAGS** | §7 test-after cho feature mới ⇒ không chặn; **MAJOR-A** vế lọc `/deleted` 0 test, xoá đi vẫn xanh toàn bộ |
| 6 | Chất lượng văn xuôi | **N-A** | không có nội dung hướng ra ngoài trong diff K4b |
**Nghiệm thu spec-cụm2 (đo, không suy):**
- *"bấm leaf→leaf ĐỔI kết quả không cần F5"* → **ĐẠT**, 6/6 ca chuyển ở bảng MỤC 1.
- *"URL G1 6 leaf mang `group=1`"* → **ĐẠT**: fe-user 6/6 (kể cả `WfView`); fe-admin 5/5 mục trỏ `/khkk/*`, riêng `WfView` trỏ Designer nên cố ý không mang `group` (lệch đã khai ở cả hai file).
**Xử lý 4 FLAG của lane:** FLAG-1 → lead vá đủ 3 chỗ BE + 2 nhánh FE, tôi xác nhận ĐÚNG (còn nợ test). FLAG-2 → tôi phán **KHÔNG phải lỗi**, phase-decoded là lựa chọn đúng (lập luận + điều kiện phản chứng ở MỤC 2). FLAG-3 → carry sang K4c, nhưng K4b vừa đẻ thêm instance thứ 8 (MAJOR-C). FLAG-4 → giữ, `search` nằm trong `queryKey` nên không sai dữ liệu.
---
## VERDICT
**GATE-K4B: PASS-WITH-FLAGS (3 MAJOR, 5 MINOR) — không có lỗi CHẶN.**
Đủ điều kiện vào commit gộp K4a+K4b. Đề nghị làm 1 việc rẻ TRƯỚC khi commit:
- **MAJOR-B** (sửa 1 dòng chú thích `fe-user/.../Layout.tsx:204-205`) — vì nó sai ngay tại commit này chứ không trôi dần, sửa sau sẽ phải đẻ commit riêng cho một dòng chữ.
Hai cái còn lại vào sổ nợ K4c, KHÔNG chặn hôm nay:
- **MAJOR-A** — 1 test cho vế lọc nhóm ở `/deleted` (2 phiếu xoá khác nhóm, `?group=n` trả đúng 1).
- **MAJOR-C** — `WorkflowMatrixViewPage` đọc `?group=` (đang là tham số trang trí ở 8 URL).
MINOR 1-5 tuỳ lead, không cái nào đụng dữ liệu.
END — gate K4b đóng.
---
## MỤC 9 — Re-đo NGAY TRƯỚC khi phát verdict (bài S166 "cây phình giữa lượt review")
Giữa lượt review cây CÓ đổi: HEAD `14ea2ea` → `bc1e7c9` (2 commit `wal:` mới), `git diff --stat` tổng 541→664 dòng thêm. Tôi đo lại để verdict không neo vào ảnh chụp cũ:
- `git diff --stat 14ea2ea..HEAD` = **3 file, TẤT CẢ trong `.claude/`** (memory implementer-frontend, `sub-implfe-k4b.md`, chính artifact này). **0 file code.**
- `git diff --stat -- src fe-admin fe-user tests docs` = **13 file, 444+/59−**, từng dòng số **trùng khít** lần đo đầu (Layout admin 28, ListPage 90 ×2, CreatePage 24 ×2, types 25 ×2, Layout user 50, Controller 5, Features 9, MenuKeys 42, DbInitializer 79, STATUS 12).
⇒ Verdict phát trên đúng thứ tôi đã soi. Không có code lọt vào sau lưng cổng.
🔴 **MAJOR-D — nguy cơ lúc commit, KHÔNG phải lỗi mã.** `git status --porcelain -- src tests` cho ra **`?? tests/SolutionErp.Infrastructure.Tests/Application/KhkkGroupMenuSeedTests.cs`** — file test của K4a vẫn **CHƯA ĐƯỢC TRACK**. Commit gộp mà lead dùng `git commit -am` hoặc liệt file bằng tay thiếu dòng này thì bộ test 49 dòng menu đi kèm K4a sẽ **rơi lại ngoài commit**: mã seed lên repo, test chứng minh nó thì không. Cổng rẻ, chạy trước khi gõ commit:
```
git status --porcelain -- src tests | grep '^??' # phải RỖNG
```
Nếu chưa rỗng thì `git add` đúng đường dẫn đó rồi mới commit.
---
## TOTAL — chốt cuối (đính chính con số ở MỤC 8: là **4 MAJOR**, không phải 3; MAJOR-D sinh ra sau lượt re-đo)
| MAJOR-A | major | vế lọc nhóm ở `/deleted` **0 test** — xoá đi vẫn xanh toàn bộ (`sợi-dây-1-argument`) | sổ nợ K4c |
| MAJOR-B | major | mỏ neo `fe-user/.../Layout.tsx:204-205` trỏ `KhkkListPage :61/:62`, đĩa là `:74/:76` — sai từ byte-0 của commit | **trước commit** (sửa 1 dòng chữ) |
| MAJOR-C | major | `?group=` trên `WfView` là tham số trang trí, 0 người đọc; K4b thêm instance thứ 8 | sổ nợ K4c/K5 (lead đã disposition) |
| MAJOR-D | major | `tests/…/KhkkGroupMenuSeedTests.cs` còn `??` chưa track — commit gộp dễ bỏ rơi test của K4a | **trước commit** (`git add` rồi commit) |
| MINOR-1..5 | minor | bấm-lại-leaf không nhả chip · "Huỷ" mất `group` · chữ-vs-việc ở `:64` CreatePage · `phase` thừa trong khoá khi deleted · search `/deleted` chỉ khớp `MaKeHoach` | tuỳ lead |
Cả 4 FLAG của lane đã xử lý: FLAG-1 xác nhận vá đúng · FLAG-2 phán **không phải lỗi** (có điều-kiện-phản-chứng) · FLAG-3 carry K4c · FLAG-4 giữ.
END — gate K4b đóng, artifact hoàn chỉnh tới dòng này.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.