# run: 2026-08-04-S171-khkk-ui-mirror-pe

**Phiên:** S171 (L10 window-3) · **Ngày:** 2026-08-04 · **Lead:** em-main (Opus 5)
**Đề-bài owner (verbatim):** *"OK hiện thì điều chỉnh lại giao diện chỗ KKHĐ lại nhé, lấy của Duyệt NCC làm mẫu: Panel 1: Hình gửi 1 · Panel 3: Duyệt và History tương tự (Hình 2) · Panel 2 - Giữa: Cấu trúc tương tự như duyệt NCC (Phần content nhập phía trên, duyệt ghi chú phía dưới) - Hình 3."*
**Engine:** `/fable-real investigator-codebase` (LỆNH-A single deep-pass) → `/fable-clone reviewer` (LỆNH-B ensemble) → giao Opus 5 MAX thực-thi.

---

## Bối-cảnh ẢNH (subagent KHÔNG xem được ảnh — lead mô-tả lại, đây là nguồn duy nhất về ý-đồ UI)

Owner gửi 3 screenshot màn **Duyệt NCC** (PE) đang chạy prod, làm MẪU cho KHKK:

**Ảnh 1 — Panel 1 (trái) + khung tổng:**
- Header trang: icon clipboard + `Duyệt NCC` + badge đếm `45`.
- Panel trái: ô search placeholder `Tìm mã / tên gói thầu / dự án…` · dropdown `Tất cả trạng thái` · cây thu-gọn được `📅 Năm 2026` kèm badge `45`.
- Panel giữa khi chưa chọn: empty-state icon + `Chọn phiếu ở danh sách`.
- Panel phải khi chưa chọn: empty-state `Quy trình duyệt sẽ hiện khi chọn phiếu.` (có nút ✕).

**Ảnh 2 — Panel 3 (phải), thứ tự dọc:**
1. Tiêu đề `Quy trình duyệt` + dòng meta `QT-DN-V2-001 v04 · Quy trình Duyệt NCC`.
2. Banner xanh lá: `Phiếu đã kết thúc tại Phòng Kiểm soát Chi phí · Cấp 2 — không qua CEO.`
3. Các **Bước**: `Bước 1 — Phòng 1` + chip tên phòng (`Phòng Cung ứng`); trong mỗi bước là các **Cấp** (`Cấp 1 đã duyệt` / `Cấp 2 đã duyệt`), mỗi cấp liệt kê NV kèm badge vai (`NS PRO`, `NS CCM`); người duyệt thật in đậm, các NV cùng cấp còn lại hiện dạng `/ Tên` mờ. Bước chưa tới **làm mờ toàn khối** + chip `Kết thúc tại Cấp 2`. Có badge đặc-biệt `⚑ Duyệt thay CEO`.
4. `File đính kèm khi duyệt` — phụ đề `File do người duyệt tải lên trong quá trình duyệt.` + hàng file (tên · size · nút xem · nút tải).
5. `Lịch sử duyệt (6)` — mỗi entry: badge hành-động (`Duyệt` xanh / `Trả lại` vàng kèm `→ Lùi về Cấp 2`) · tên người · `[Bước N — Cấp M]` · timestamp `HH:mm:ss dd/M/yyyy` · nội-dung ghi-chú nhiều dòng.

**Ảnh 3 — Panel 2 (giữa), owner khoanh ĐỎ phần dưới:**
- **Trên** = content nhập: bảng số liệu có dòng đánh số (`8. Giá trị thực hiện dự kiến còn lại`, `9. Giá trị tổng thực hiện dự kiến` — dòng tổng nền xanh đậm, số âm màu đỏ trong ngoặc) → card `THÔNG TIN CHỌN THẦU` (`① TÊN NCC/TP ĐƯỢC CHỌN` nền xanh nhạt · `2–3. GIÁ ĐỀ XUẤT TỪ PRO/CCM` Min/Max · `Giá chốt duyệt` + badge `Giá NCC` · `4. Bảng so sánh giá` hàng attachment · `5. Link hồ sơ` + nút `Copy`).
- **Dưới (KHOANH ĐỎ)** = `5. Ý KIẾN CẤP DUYỆT (SIGN-OFF THEO WORKFLOW)`: nhóm theo **BƯỚC → PHÒNG**, mỗi nhóm có header trái (`BƯỚC 1 — PHÒNG 1` + chip tên phòng) và meta phải (`2/2 cấp đã duyệt · 5 NV tham gia`); bên trong là **lưới 2 cột** các card ý-kiến: `Cấp N — Tên người` + badge `✓ Đã duyệt` + nội-dung ghi-chú (nhiều dòng, giữ xuống dòng) + timestamp; ý-kiến rỗng render placeholder `(duyệt — không ý kiến)`. Bước chưa duyệt render `— Chưa có ý kiến duyệt.`
- Cây trái trong ảnh này cho thấy **cấu trúc 4 folder theo giai-đoạn** dưới mỗi gói thầu: `Duyệt NCC` → `Kế hoạch Hợp đồng` → `Duyệt Hợp đồng` → `Hợp đồng cứng` (3 cái sau đang badge `SẮP`), và card phiếu mang chip `GẤP (PRO)` · `⚑ Kết thúc · Phòng 2` · `Đã duyệt` + mã `PE/2026/A/019` + timestamp + tên người.

---

## Ground-truth lead đã ĐO trước khi phóng (để lane khỏi đo lại)

| Sự-thật | Bằng-chứng |
|---|---|
| PE 3-panel grid = `lg:grid-cols-[400px_1fr_360px]` | `fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx:397` |
| PE panel 1/2/3 = `PeListPanel` · `PeDetailTabs` · `PeWorkflowPanel` | cùng file `:658 :678` + imports `:28 :31` |
| KHKK **list** hiện là **2-panel** `lg:grid-cols-[19rem_minmax(0,1fr)]` + `PipelineTreePanel` | `fe-admin/src/pages/khkk/KhkkListPage.tsx:197-198` |
| KHKK **detail** là **trang riêng full-width**, `KhkkWorkflowPanel` render **INLINE ngay dưới header** (không phải rail phải) | `fe-admin/src/pages/khkk/KhkkDetailPage.tsx:411` + comment `:9` |
| Cả PE lẫn KHKK đều **SHA256 IDENTICAL** giữa fe-admin ⟂ fe-user | đo @S171: 4/4 cặp file `same=YES` |
| LOC | PE Workspace 147 · PE List 807 · `PeDetailTabs` 179.376 B · `PeWorkflowPanel` 63.472 B · Khkk Detail 1.093 · Khkk List 361 · KhkkWorkflowPanel 491 |
| KHKK **chưa có** `components/khkk/` — mọi thứ nằm trong `pages/khkk/` | `ls fe-admin/src/components/khkk/` → không tồn tại |

🔴 **Delta cốt lõi:** PE = **một màn 3-panel hợp nhất** (chọn ở trái → nội-dung giữa → workflow/history phải). KHKK = **list 2-panel** + **detail trang riêng** có workflow **xếp chồng trên** nội-dung. Đây là khoảng cách kiến-trúc, không phải khoảng cách CSS.

---

## taskList snapshot

### Stage A — `/fable-real` (LỆNH-A, 1 lane, `tier:'fable'`)

| # | role | label | nhiệm-vụ |
|---|---|---|---|
| 1 | `investigator-codebase` | `Core investigator-codebase invest s171` | Deep-pass MAP toàn-diện PE-vs-KHKK trên 3 panel + đề-xuất bố-trí đồng-nhất, phân-loại rõ **GIỐNG-được** ⟂ **KHÁC-buộc-phải-khác** (+ lý-do domain). Propose-only. |

### Stage B — `/fable-clone` (LỆNH-B, ensemble ≥3 lane, `tier:'opus'`)

| # | role | label | lăng-kính |
|---|---|---|---|
| 1 | `reviewer` | `Gate reviewer lens-fidelity s171` | Trung-thành ảnh/ý-đồ owner + "tương tự thì giống nhau" |
| 2 | `reviewer` | `Gate reviewer lens-feasibility s171` | Khả-thi kỹ-thuật: data-shape KHKK có đủ nuôi 3 panel không |
| 3 | `reviewer` | `Gate reviewer lens-regression s171` | Rủi-ro vỡ cái đang chạy: route · policy `KeHoachKyKet` (gotcha #85) · mirror 2-app · Pattern 16-bis |

### Stage C — thực-thi

Opus 5 MAX theo `spec-khkk-ui-mirror-pe-04-08-2026.md` (lead ghi SAU khi verify — engine KHÔNG tự ghi).

---

## Stages

- [x] A — `/fable-real investigator-codebase` deep-pass (Fable 5, `wf_b76692d4-3a4`, 756s, 33 tool-use, return SẠCH 0-garble) → `sub-investigator-codebase-invest-s171.md` **44.830 B** · **VERDICT=27 giống-được / 6 buộc-khác / 8 data-gap**
      🔴 **2 đính-chính vào Ground-truth của lead** (bảng trên SAI 1 dòng): (Đ-1) PE panel 1 **KHÔNG** phải `PeListPanel` — viết INLINE `PurchaseEvaluationsListPage.tsx:399-639`; `PeListPanel` chỉ 1 call-site = `PurchaseEvaluationWorkspacePage.tsx:84` ⇒ **không có component list dùng-lại được**. (Đ-2) **Đã có tiền-lệ mới hơn:** `ContractsListPage.tsx:125` (GĐ3, S168, 255 dòng) = bản PE-3-panel gọt gọn ⇒ KHKK là **mắt-xích lệch duy nhất** giữa GĐ1 và GĐ3; chép khuôn từ GĐ3, KHÔNG chép từ PE 807 dòng.
      🔴 **Đảo ước-lượng:** `ContractSigningPlanWorkflowService.cs:534/:547` ĐANG GHI approvals + changelog THẬT ⇒ "Lịch sử duyệt" không phải xây-lại, chỉ thiếu **~5 field DTO + 1 endpoint**.
- [x] B — `/fable-clone reviewer` ×3 lăng-kính (`wf_832dadb2-93f`, Opus, 470s, 52 tool-use) — 🔴 **CẢ 3 LANE CHẾT** *"You've hit your session limit · resets 6:10pm"*, engine trả `verdict: null` ×3. **NHƯNG ĐĨA CỨU 2,95/3**: khung-rỗng-lượt-1-2 giữ trọn 74.070 B.
      · `lens-feasibility` 26.461 B **HOÀN TẤT** — `8 HELD / 6 BROKE / 6 bỏ-sót`
      · `lens-regression` 24.432 B **HOÀN TẤT** — `8 HELD / 4 BROKE / 10 bỏ-sót`
      · `lens-fidelity` 23.177 B **DỞ** — §6 (phép đo P3-12) + §8 (lỗ tự khai) trống, VERDICT còn `?`. Lead derive từ §7 bảng của chính nó ≈ 12 HELD / 6 BROKE / 3 bỏ-sót (**nhãn lead-derive, KHÔNG phải lane tự kết**); lead **tự chạy phép §6 còn thiếu** → lật P3-12
- [x] C — lead verify + refute/synthesize → **`spec-khkk-ui-mirror-pe-04-08-2026.md` 35.017 B** (utf8-strict OK, đủ 3 heading ①②③). Post-wave `git status`: **0 residual-write** ngoài run-folder
- [x] C-bis — ✅ **3 QUYẾT-ĐỊNH OWNER chốt 2026-08-04**, spec cập-nhật **43.087 B**: (1) panel-1 = **`PipelineTreePanel`** — owner **BÁC** đề-xuất đảo của lead, đọc đủ 4 rủi-ro trên thẻ chọn rồi vẫn chọn ⇒ quyết-định có thông-tin đầy-đủ; lead chuyển 4 rủi-ro thành **mitigation CƯỠNG-HÀNH** + acceptance C-5..C-8 (*chọn HÌNH ≠ chấp-nhận VỠ*) · (2) panel-3 rỗng = **CHỈ hộp dashed y PE** ⇒ **đóng miễn-trừ-thứ-7** · (3) file-khi-duyệt = **upload THẬT** ⇒ BE-7 `+ApprovalAttachment=3` policy `KeHoachKyKet.Update`, bẫy F-6 "UI chết" tự tan
- [x] D — Opus 5 MAX thực-thi (`wf_6fd33d7a-a20`, 1.883s, 91 tool-use, 422K tok) — **BE xong 7/7 return SẠCH** · **FE chết ở lượt TRẢ VỀ** (`API Error: Connection closed mid-response`, `verdict:null`) nhưng **CODE không mất byte nào**
      🔴 **Chống-#53 cứu MỘT NỬA:** FE ghi khung rỗng lượt 1-2 **đúng luật** nhưng **KHÔNG append** ⇒ nhật-ký **642 B RỖNG** ⇒ mất trọn phần NGHĨ. Đối-chứng cùng phiên: 3 lane reviewer + lane BE đều append ⇒ cứu trọn. ⇒ **khung rỗng = CẦN, KHÔNG ĐỦ; cái cứu thật là APPEND sau mỗi đơn-vị việc.** Lead ghi biên-bản **on-behalf** vào `sub-implementer-frontend-s171.md`
      🔴 **6 IDE diagnostic ĐÁNH LỪA (gotcha #68):** báo CS0246 ×2 ở controller + TS2304 `BuildStagesOptions` — trông như vỡ nặng. **Build thật sau khi agent dừng: 0 lỗi cả 3.** Tin diagnostic mà rollback thì đã vứt một mẻ việc đúng
      ✅ **Đo được:** 20/20 acceptance tĩnh ×2 app · **mirror SHA256 8/8 byte-identical** · **E-2 = 0 hit** path cấm · build 3/3 PASS · test **643/644**
      🟢 **Hồi-quy chéo B1 CHẶN ĐÚNG** (`usePipelineStages.ts:292-297` conditional-spread): PE `:524` chỉ truyền `stage1Content` ⇒ **giữ nguyên 8 ngăn N1-N8**; `PipelineTreePanel:314` `stage2Options ?? {}` ⇒ cây PE/GĐ3 nguyên vẹn
      🟡 **1 test FLAKY** `Create_DifferentEvaluationType_DoesNotAffect_Other` — full-suite fail **2→1→1**, chạy RIÊNG **0/0/0** ⇒ bất-định; `PeWorkflowAdminTests.cs` tạo `new SqliteDbFixture()` RIÊNG ×6 test ⇒ flake hạ-tầng test, 0 giao KHKK. 🔴 **baseline HEAD sạch CHƯA đo** (worktree fail đường-dẫn Dropbox) ⇒ **CHƯA được nói "flake có sẵn"**
      🔴 **E-7 THIẾU** — bảng "cột nào chết" là thứ CHỈ lane biết (nó quyết bỏ cột nào); lead đọc được kết-quả, không đọc được lý-do ⇒ **phải soi diff bằng mắt TRƯỚC UAT**, bỏ qua = đúng ca M-7 mất-chức-năng-im-lặng
- [ ] E — còn nợ: **E-7 bảng cột-chết** · baseline test HEAD · 5 acceptance RUNTIME (B-3 · E-5 UAT fe-user:8080 · E-6 deep-link fe-admin · C-5 negative-control · D-9 vế tự-ẩn)

## Synthesis

### Lead refute — 3 chốt lead ÁP LÊN bản invest sau phản-biện

1. 🔴 **Panel 1 KHÔNG dùng `PipelineTreePanel`** (đảo §5 bản invest). 4/5 phát-hiện nghiêm nhất hội tụ đúng chỗ này: `canPe` khoá cây ⇒ user **chỉ có quyền KHKK mất TRẮNG danh-sách** (display-layer khoá authz-layer đang mở — mặt còn lại #82) · trần 200 = **mất đường tới phiếu**, không có "trang 2" · `usePipelineStages.ts:211` cây **tự navigate rời màn 3-panel** · `content` **nuốt `groups`** im lặng (`PipelineStageFolders.tsx:285-287`). Mirror trung-thành = **KHKK dựng cây từ danh-sách CỦA CHÍNH KHKK** (đúng như PE dùng `yearGroups` từ query `pe-list`, KHÔNG dùng `PipelineTreePanel`) ⇒ 3 rủi-ro tan hết.
2. 🔄 **Miễn-trừ 6 → 3.** P1-4 lật GIỐNG-được (khác nguồn dữ-liệu ≠ khác bố-trí) · P2-7 lật GIỐNG-được (`readOnly` là prop HOST, `PurchaseEvaluationsListPage.tsx:663` — không cần workspace) · P3-12 hạ xuống CHỜ-ANH (**lead đo `DbInitializer.cs:554`: 3 Bước × ≤2 Cấp = tới 6 trạm, seed `:620` tự ghi "sao từ workflow Duyệt NCC prod"** ⇒ tiền-đề *"3 trạm ngắn"* SAI đo được). **+ miễn-trừ THỨ 7 không khai** (F-5: §5 âm-thầm đảo P3-16 panel-3 empty-state) → đẩy thành CÂU HỎI 2.
3. ✅ **Giữ quyết-định A, THAY lý-do.** Lý-do cũ (*"cả hai đường generic đều đụng PE prod"*) là **nhị-phân giả** — có đường (iii) adapter 1 chiều nằm trọn trong KHKK, 0 dòng PE. Lý-do đúng: trích `StepOpinionsBox` khỏi `components/pe/` **mâu-thuẫn trực-tiếp acceptance A-10 của chính nó**. *Kết-luận đúng, chứng-minh hỏng* — giữ kết-luận, sửa chứng-minh.

### 6 acceptance của bản invest bị bẻ gãy khi CHẠY THẬT (đã thay bản sửa trong spec)

| Gốc | Vỡ thế nào |
|---|---|
| **A-13** (canh QUYỀN) | Vỡ **cả hai đầu**: `:15` comment chứa "Authorize"+"Policy" ⇒ **bơm tử-số**; `:17 :236 :241` comment *nhắc tên* `[Authorize]` trần ⇒ vế "0 dòng trần" **FAIL trên file 100% sạch** (citation-trap). → thay bằng neo `^\s*\[` + fault-inject chứng răng |
| **A-6** (canh bug S155) | **Kiểm NHẦM KHOÁ** — cấm `phase` (vô hại, transient) và bỏ sót `filter`/`view` (đúng 2 khoá làm vỡ) ⇒ `setSearchParams({group,filter:'DaDuyet'})` = **đúng bug S155** vẫn PASS. Inverted check |
| **A-10** (canh hồi-quy) | **Đo sai trục** — thiếu `hooks/usePipelineStages.ts` + `components/pipeline/` ⇒ sửa 2 path đó có thể làm **trang PE mất 8 ngăn GĐ2** mà A-10 PASS 100% |
| **A-7b** | Chạy trên **chính mẫu PE** ra **3 hit**, không phải 5 ⇒ implementer viết ĐÚNG sẽ FAIL; cách duy nhất PASS = **bỏ số đếm động cho vừa regex** = Goodhart |
| **A-7d** | Đo **chuỗi chữ**, mục-tiêu là **vị-trí khối** ⇒ đổi tiêu-đề "Ý kiến duyệt" là PASS trong khi khối vẫn ở panel 3 |
| **A-8c** | Grep-trên-source ⇒ khối **UI CHẾT vẫn PASS 100%** (không có đường ghi attachment + khối tự-ẩn khi rỗng ⇒ không bao giờ render trên UAT) |

### 3 khối owner NHÌN THẤY trong ảnh mà bản invest đánh rơi

**F-1** EmptyState panel 2 *"Chọn phiếu ở danh sách"* (Ảnh 1 — trạng-thái ĐẦU TIÊN người dùng thấy; bản invest ghi ở §1.2 rồi rơi khỏi §3/§5/§7) · **F-2** chip `⚑ Kết thúc · Phòng N` trên card panel 1 (Ảnh 3 — **0 công BE**, cột Mig 71 đã có) · **F-3** liệt-kê NV mỗi Cấp, người duyệt in đậm + NV khác `/ Tên` mờ (Ảnh 2).

### Tin tốt được verify độc-lập (đừng cắt ngân-sách vì nghi ngờ)

Lane feasibility **vào để lật D-2 và không lật được**: `LogSigningPlanTransitionAsync` **6 call-site phủ TRỌN** Pending/Approve/**Reject-Return** (`:204 :283 :296 :311 :325 :393`), `Approvals.Add` ở `:534` **không `if` nào bọc** ⇒ entry *"Trả lại → Lùi về Cấp 2"* của Ảnh 2 **SẼ CÓ dữ-liệu**. Và **A-0 chặn-cửa GỠ ĐƯỢC** — `usePipelineLinkage()` đã có sẵn `khkkByPeId` (`:123-134,147`), **0 query mới**.

### Ngược lại — 1 chỗ RẺ bị ước-lượng sai chiều

`FinalizeStepName`/`FinalizeLevelName` **không phải "field append"**: `ContractSigningPlan.cs:51` chỉ có `bool EndedByLevelFinalize`, **không cột nào lưu Bước/Cấp nào**. Phải **dẫn xuất** — port ~20 dòng projection **3 subquery** từ `PurchaseEvaluationFeatures.cs:665-683`. Bản invest cảnh-báo *"dễ sai đắt nhất theo cả hai chiều"* rồi **tự dính chiều còn lại**.

---

## Ledger — ĐÓNG @S172 (vá mồ-côi, `ctx-verifier` cờ-2 + `lead-gap` FLAG-5)

🔴 **Vì sao có mục này:** file này từng có **0 hit** cho `cicd` · `deploy` · `push` · `commit` · `998ea55` · `#446/#447` (6 từ, control-dương `canPe`=1 trong chính file ⇒ phép grep không rỗng), ledger dừng ở `[ ] E`. Hệ quả đo được: `cicd-verify-998ea55.md` **mồ côi ngay trong run-folder của nó** ⇒ spawn nào PIN `run.md` để nối việc sẽ **không thấy việc duy nhất đang dở**. Đây là class **mồ-côi-trong-chính-nhà-mình** — path đúng, file đúng, **chỉ thiếu người trỏ VÀO** (đảo chiều của inbound-pointer check S166).

- [x] **E — deploy + verdict.** 2 commit: `998ea55` (Contract, 21 file) + `bf0200b` (Docs, 4 điểm stale hậu-ring S170); push `4b1905d..bf0200b`, 0 commit treo.
- [x] **cicd verdict = PASS** → `cicd-verify-998ea55.md` (26.247 B, 0 ô `_(chờ đo)_`). Test-gate #447 **644/644 Failed 0** khớp baseline **tách-phần** Δ0 · bundle 4/4 rotate khớp log CI **tới byte** · 2 endpoint MỚI **401** · mig repo 71 = prod 71 **set-diff 0/0 hai chiều**, tables 97 · smoke 8/8.
- [x] **Chân #44/#85 đóng @S172** — 🔴 phạm-vi = **2 tổ-hợp vai** (`Drafter+Procurement` · `CostControl+DeptManager`), **chưa loại** nhánh vai thường khác vẫn 403. Xem `cicd-verify-998ea55.md §6.5`.
- [ ] **Nợ E-7** bảng "cột nào chết, chuyển đi đâu" (8 cột + 6 KpiCard → card 400px) — 🔴 lý do bỏ cột **mất theo lane FE chết ở lượt trả về**, phải soi diff bằng MẮT, CẤM suy đoán. Nhà: `docs/HANDOFF.md` NEXT-em @S172 #1 + `[carry:e7-cot-chet-khkk]`.
- [ ] **5 acceptance RUNTIME chờ UAT** — nhà: `docs/HANDOFF.md` NEXT-em @S172 #2 + `[carry:acceptance-runtime-khkk]`. Nay (a)-(c) đo được nhờ credential non-admin còn sống (slot 64).
- [ ] **Nợ harvest 143.682 B** — 4 vai của run này chưa về sổ (invest-cb · impl-be · impl-fe · reviewer×3). Nhà: `docs/HANDOFF.md` slot (71) + `[carry:harvest-no-s168-s171]`.

> 🔴 **Bài để lần sau khỏi lặp:** run-folder **PHẢI tự trỏ vào artifact nó đẻ ra**. Detector orphan đo **TÊN FILE**, không đo *"ai đang trỏ vào ai"* — nên một artifact đầy đủ vẫn có thể chết câm ngay trong nhà của nó. Kiểm rẻ: `grep -c '<sha-commit>' run.md` phải > 0 sau khi deploy.
