IMPLBE-K4A: DONE — B0-B5 lane đo, B6 + chốt = lead on-behalf @S166 (lane chết #53 sau B5, code ĐÃ LAND ĐỦ)

# Lane BE K4a — 49 row menu KHKK 8-nhóm + MenuKeys.All + labelBackfill + grant

Bắt đầu: 2026-08-01. 0-migration. Scope: `MenuKeys.cs` + `DbInitializer.cs` ONLY.

## Nhật ký từng bước (append sau MỖI file sửa xong)

- [x] B0 artifact tạo (dòng này = bằng chứng file ra đời TRƯỚC mọi Read)
- [x] B1 đọc spec pin 2 file + re-ground anchor THẬT (đo lại, không tin số spec):
  - `DbInitializer.cs` khối menu KHKK G1: `:1950-1963` (root `:1951` + `Khkk_G1 :1956` + 6 leaf `:1958-1963`) — icon group `FileCheck`, leaf `Network/List/Plus/CheckCircle2/CheckCheck/Trash2`, Order 1-6 ✔ khớp brief
  - upsert-block THẬT (trước khi tôi sửa): `existingItems` `:2111` → foreach `:2114-2131`; nhánh key-đã-tồn-tại chỉ re-set Order rồi `continue` = `:2116-2128`
  - labelBackfill dict `:2141-2183` · apply-loop `:2184-2198` ✔
  - 2 comment stale tự-trỏ `":1893-1905"`: `:2095` và `:2149` ✔ (số THẬT sẽ đo LẠI sau khi thêm 49 row)
  - site-1 `SeedAdminPermissionsAsync` loop `MenuKeys.All` `:2246` (chỉ Admin) · site-2 `KhkkKeys()` `:2373-2377` (feed `reviewKeys :2398-2412`, CanRead mọi role, skip-existing) · site-3 `SeedKeHoachKyKetAccessAsync` `:2603` · site-4 revoker gọi `:2285`, prefix `Hrm*/Off*/Personal` `:2487-2488` → **0 match `Khkk`** (đã soi trực tiếp, đúng vá-11)
  - `MenuKeys.All` hiện đếm tay = **64** (khớp mốc brief) — số THẬT sẽ đo bằng runtime ở B6
  - `GetMyMenuTreeQuery.cs:96` `HasAccess(n) = n.CanRead || n.Children.Any(HasAccess)` ⇒ **group-key KHÔNG cần CanRead row của chính nó** (vá-10 đúng: lý-do "group cần row để render" trong invest doc là SAI) — nhưng leaf thì CẦN
  - nhãn 8 nhóm đối chiếu nguồn `danh-muc-sp002-transcribe.md:22-29` → khớp 8/8 brief, N2 CÓ vế "phá dỡ" ✔
- [x] B2 `MenuKeys.cs` +49 key vào `All` — **CHỆCH-KHUÔN KHAI RÕ**: brief nói "+49 const", tôi làm **bộ sinh** (`KhkkGroupNumbers` + `KhkkLeafActions` + `KhkkGroup(n)`/`KhkkLeaf(n,a)` → `KhkkGroupMenuKeys` 49 phần tử) rồi `.. KhkkGroupMenuKeys` vào `All`. LÝ DO: cùng 1 bộ sinh feed CẢ 3 phía (`All` · seed menu-row · grant site-2) ⇒ khớp **by-construction**; 49 const tay = mirror 147 chuỗi ở 3 nơi, đúng loại drift làm nổ FK Permission (invariant T6 K1). Đúng đề xuất spec `sub-invest-fable-b2-cum2.md:9` ("factory mirror khuôn ContractTypeGroup"); kết quả quan sát được y hệt (49 key vào All). Khai báo ĐẶT TRƯỚC `All` vì static field initializer chạy theo thứ tự văn bản. Kèm vá-15 (2 khuôn `Ct_*`/`Pe_*` ngoài-All ⟂ Khkk/AwV2 trong-All) + ghi DELTA (+49 key/+196 policy, KHÔNG neo mốc tuyệt đối — vá-2). Build Domain riêng: **0 error / 0 warning**.
- [x] B3 `DbInitializer.cs` tree +49 row: loop đặt sau vòng `PeWf_*`, duyệt CHÍNH `MenuKeys.KhkkGroupNumbers` × `KhkkLeafActions` (group Order=n, icon `FileCheck`; 6 leaf Order 1-6 nhãn/icon MIRROR Y G1). 2 dict nhãn: `khkkGroupLabels` (8 nhãn owner-soát, N2 CÓ "phá dỡ") + `khkkLeafMeta` — thiếu nhãn ⇒ KeyNotFound **fail-fast lúc seed** thay vì FK nổ về sau.
- [x] B4 labelBackfill +`Khkk_G1` — LANE ĐÃ LAND (lead kiểm @S166: entry `["Khkk_G1"] = "1. Thiết bị,
  máy móc, vật tư phụ (A1)"` ở dict cuối, kèm comment "7 nhóm G2..G8 là key MỚI đi nhánh INSERT,
  không cần backfill" — đúng. ⚠️ lead suýt kết luận "thiếu entry" vì `grep | head -8` nuốt hit
  — bài phép-trích-cụt, đọc vùng dict trực tiếp mới thấy).
- [x] B5 site-2 `KhkkKeys().Concat(MenuKeys.KhkkGroupMenuKeys)` + vá-10 (comment lý-do đúng: group-key
  KHÔNG cần CanRead row của chính nó — `GetMyMenuTreeQuery.cs:96` HasAccess đệ-quy theo con; leaf CẦN)
  + vá-11 comment revoker (restart ×2 = phép thử, acceptance A5) + vá-13 CẢ 2 comment stale `"1893"`
  → 0-hit (đã trỏ `:2162-2179` + mỏ neo grep `existingItems.TryGetValue`) — LANE ĐÃ LAND, lead verify
  0-hit grep + đọc vùng.
- [x] B6 (lead on-behalf): build Infra 0-error · **full suite 614/0 exit-0 SAU diff** (Domain 45 +
  Infra 569 — DbInitializer đổi mà idempotent/revoke/permission-seed tests giữ xanh) · đếm by-source:
  `KhkkGroupNumbers[7] × (1 group + 6 leaf) = 49` key spread `.. KhkkGroupMenuKeys` vào `All:244`
  ⇒ All 64→113 kỳ vọng, Policies 256→452 (+49×4=196). 🔴 Số RUNTIME (All.Count thật + policy đếm DB)
  chốt ở lane test K4a (PS5.1 không load net10 DLL — reflection từ shell bất-khả); DELTA khai đúng
  vá-2 (không neo mốc tuyệt đối, kèm lệnh đo lại).

END sub-implbe-k4a — VERDICT=DONE (49 key by-construction 3-phía All/seed/grant · labelBackfill G1 ·
vá 10/11/13/15 land · suite 614/0 · runtime-count → lane test; commit = lead sau gate)
