4.8 KiB
sub-reviewer-1 — T2 review đối-kháng cây 4-folder GĐ (S162, 2026-07-30)
Ghi-đĩa-trong-lúc-làm (chống #53). Append theo từng mục soi. Verdict cuối ở cuối file.
Phạm vi đo (từ đĩa, git status --porcelain + git diff --stat)
- 6 file tracked M:
fe-{user,admin}/src/pages/pe/PurchaseEvaluationsListPage.tsx·.../khkk/KhkkListPage.tsx·.../contracts/HardCopiesPage.tsx— tổng +168/−16. - 6 file MỚI untracked:
fe-{user,admin}/src/components/pipeline/{PipelineStageFolders,PipelineTreePanel}.tsx+fe-{user,admin}/src/hooks/usePipelineStages.ts. - Ngoài src: memory frontend-designer (MEMORY.md + archive/2026-06.md + archive/_INDEX.md),
.session-counter.json, run-folder artifacts (2 png + 2 harness .txt + sub-frontend-designer-0.md).
MỤC 1 — Claim "khối <li> phiếu giữ nguyên từng byte" — ĐẠT
git diff -- .../pe/PurchaseEvaluationsListPage.tsx (cả 2 app, diff IDENTICAL):
- Hunk A
@@ -29,6 +29,8— +2 import. 0 xoá. - Hunk B
@@ -47,6 +49,14— +8 dòng (6 comment +usePipelineStages()+filterActive). 0 xoá. - Hunk C
@@ -507,7 +517,12— xoá ĐÚNG 1 dòng<ul className="ml-3 divide-y divide-slate-100 border-l border-slate-200">. - Hunk D
@@ -607,6 +622,7— +1 dòng) })} />. 0 xoá. ⇒ Tổng xoá = 1 dòng. Khối<li>KHÔNG có dòng-nào ⇒ CLAIM ĐỨNG. Class cũ không mất:ml-3 border-l border-slate-200→ propclassNamewrapper;divide-y divide-slate-100giữ ở<ul>trong.
MỤC 6 — Biên giới — ĐẠT
git diff --name-only(tracked): 0 filesrc/Backend, 0tests/, 0 seeder/menu (#84), 0PePipelineStrip.tsx. Chỉ 6 file FE + memory/run-artifact ngoài src.grep -rn "export default"trong 6 file mới ⇒ NONE (named export only, đúng §3.9).- Nhãn VERBATIM
PipelineStageFolders.tsx:22-27:1:'Duyệt NCC'·2:'Kế hoạch Hợp đồng'·3:'Duyệt Hợp đồng'·4:'Hợp đồng cứng'— khớp lời owner từng chữ. - Mirror SHA256 6/6 PAIR (tự đo lại, không tin bảng designer):
PipelineStageFolders
05c93c3619a9cfec· PipelineTreePanel76e5dafb239e402a· usePipelineStagese762d5eb9c3a057a· PE page1d8d8f75c7d2cc14· Khkk page40a650bf6964ab4b· HardCopies paged30a911e7483f96a. types/contracts.tslệch 2 app CHỈ ở giá trịContractPhaseColor(cùng bộ key 1-10,99) ⇒ hook SHA-chung import an toàn,badgeClassundefined vẫn có fallbackPipelineStageFolders.tsx:189.
MỤC 3 — Permission-gate 2 tầng — 1 LỖ THẬT (latent) + 1 điểm sai luận-cứ
Key FE (usePipelineStages.ts:41-44) vs BE:
Khkk_List/Khkk_G1/KeHoachKyKet— cả 3 tồn tại thật (DbInitializer.cs:2184-2188KhkkKeys +MenuKeys.cs:41).Hdc_<7 code>+HopDongCung— tồn tại thật (DbInitializer.cs:1868/2191).Ct_<code>_List+Contracts— tồn tại thật.can(k)=node.canRead(usePermission.ts:20), findNode không thấy ⇒ false ⇒ OR-nhiều-key hợp lệ về mặt đọc cây.
🔴 F: OR-nhiều-key ĐÁ NHAU với policy API cho GĐ2. ContractSigningPlansController.cs:36 = [Authorize(Policy="KeHoachKyKet.Read")], và MenuPermissionHandler.cs:40 so khớp CHÍNH XÁC p.MenuKey == req.MenuKey (KHÔNG kế thừa, KHÔNG OR con). ⇒ role có Khkk_List=1 mà KeHoachKyKet=0 sẽ: FE cho canKhkk=true → gọi API → 403 → khkkFailed → GĐ2 hiện "—" dưới MỌI gói thầu, và claim "0 rác 403" vỡ. Đo Dev DB: 13/13 role đều có Contracts=1 ∧ KeHoachKyKet=1 ⇒ HIỆN CHƯA vỡ; prod chưa đo được (chỉ localhost+secret). Là lỗ THIẾT KẾ chờ 1 lần chỉnh ma trận quyền.
🟡 luận-cứ trong comment usePipelineStages.ts:10-13 ("gate phải OR nhiều key vì root có thể canRead=false") đúng cho TẦNG HIỂN THỊ nhưng sai đích cho tầng API: query nào cũng nên gate bằng ĐÚNG key policy của endpoint (KeHoachKyKet cho /contract-signing-plans).
🟢 /contracts KHÔNG có policy action-level (ContractsController.cs:16-17, chỉ [Authorize] trần class) ⇒ nhánh enabled: canContract || canHardCopy KHÔNG sinh 403. Đổi lại: đây là bare-[Authorize] sẵn có của hệ (#82), diff này không làm tệ thêm nhưng có nới hiển-thị: user chỉ được cấp Hdc_ThauPhu vẫn thấy leaf GĐ4 của MỌI loại HĐ (usePipelineStages.ts:211) vì gate là OR-toàn-nhóm chứ không theo c.type.
🟢 Không quyền ⇒ enabled:false (:77, :91, PipelineTreePanel.tsx:108) ⇒ KHÔNG gọi API; folder khoá render nhánh riêng KHÔNG <details> (PipelineStageFolders.tsx:99-113). Fetch lỗi ⇒ failed ⇒ badge "—" + body "Không tải được…" (:140-156) ⇒ cây không vỡ. ĐẠT.