`.
### F-4 (MINOR) — R5: `!canPe` đổi thông điệp Panel-3 từ chỉ dẫn sang than phiền quyền; đo prod cho thấy hiện chưa vai nào dính
Đường quyền, đọc thẳng từ mã chứ không suy:
- `usePipelineStages.ts:83` — `canPe = PE_MENU_KEYS.some(k => can(k))`, với `PE_MENU_KEYS` ở `:55` là `['PurchaseEvaluations','Pe_DuyetNcc_List','Pe_DuyetNccPhuongAn_List']`.
- Khi `canPe` sai, `PipelineTreePanel.tsx:111` đặt `enabled: canPe` nên truy vấn `pipeline-pe-index` **không chạy** ⇒ **không sinh rác 403**, đúng ý đồ đã khai ở `usePipelineStages.ts:5-8`.
- Nhưng Panel-3 khi đó hiển thị khung cây rỗng, huy hiệu "**0 gói**", và câu ở `:216-220`: "Bạn chưa có quyền xem phiếu Duyệt NCC nên cây toàn trình chưa hiển thị được." Câu này nằm **trên trang Hợp đồng**, thay cho câu cũ "Quy trình duyệt sẽ hiện khi chọn HĐ." ⇒ người dùng mất chỉ dẫn hữu ích và nhận về một lời than phiền lạc đề.
**Đo prod** `/api/permissions/by-role/{id}` trên 13 vai (tất cả trả `http=200`): `PurchaseEvaluations`.canRead bằng True ở **13/13 vai** ⇒ nhánh `!canPe` **hiện không xảy ra với vai nào**.
**Control-âm của cùng phép đo:** `Permissions`.canRead và `Users`.canRead bằng False ở **12/13 vai** (chỉ `Admin` True; số dòng quyền là 169 so với 191) ⇒ phép đo có phân biệt thật, không phải "True tuốt vì đọc sai trường".
Kết luận: F-4 là **rủi ro tương lai** (nếu ai đó gỡ quyền PE của một vai), không phải lỗi đang chảy máu. Không chặn commit.
### F-5 (MINOR) — R5: cây bơm thêm tối đa ba truy vấn index vào lượt tải đầu của ba trang
`PipelineTreePanel` gọi `usePipelineStages`, hàm này gọi `usePipelineLinkage`, sinh ra ba truy vấn — `pipeline-pe-index` (`PipelineTreePanel.tsx:100`), `pipeline-khkk-index` (`usePipelineStages.ts:90`) và `pipeline-contract-index` (`:108`) — mỗi truy vấn lấy `pageSize=200`.
Trên InboxPage, MyContractsPage và ContractsListPage, truy vấn `pipeline-contract-index` gọi `GET /contracts?pageSize=200` **song song** với truy vấn danh sách của chính trang (`/contracts/inbox` ở `InboxPage.tsx:71`, `/contracts` ở MyContractsPage) — cùng một tài nguyên nhưng khác `queryKey`, nên mỗi lần vào trang mà chưa chọn HĐ là hai lần nạp danh sách hợp đồng. Đo prod cho thấy 13/13 vai đều có đủ `canPe`, `canKhkk` và `canContract` ⇒ **cả ba truy vấn đều bắn cho mọi vai**, không vai nào được miễn. Mức này chấp nhận được vì `staleTime` là 60 giây và đây là hệ quả nội tại của phương án D, nhưng nên biết trước để không bất ngờ khi UAT phản ánh trang tải chậm hơn.
### F-6 (MINOR) — R1 phụ: trade-off của D khai thiếu một vế — ô lọc cây mất chữ khi chọn HĐ rồi bỏ chọn
Vì điều kiện là `{!selectedId &&
}`, việc chọn một HĐ sẽ **gỡ bỏ (unmount)** component. Hệ quả:
- `expandedSet` **sống sót** nhờ `localStorage` (`PipelineTreePanel.tsx:119-126` đọc, `:170` ghi).
- `search` (`:94`) và `mobileOpen` (`:95`) là state thuần nên **mất trắng**: người dùng gõ "FLOCK" để lọc cây, bấm xem một HĐ, quay lại thì ô lọc rỗng và cây bung về nhánh mặc định.
Spec D ở `sub-implfe-gd3.md` §1 chỉ khai trade-off "cây nhảy chỗ khi chọn HĐ", không khai việc mất chữ đang lọc. Đề nghị bổ sung vế này khi báo owner để owner chốt trên thông tin đầy đủ.
### F-7 (MINOR) — R8 mobile: không hồi quy, nhưng lệch với ba host cũ
- Host là `