- **S161-W2 (07-29) KHKK CRUD review — PWF 12 FLAG 3C/5M/4m `[chết #53 sau Trục-4 (13.9KB ghi-từng-trục sống trọn) → lead đo Trục 5-7 + verdict on-behalf; 11 FIXED cùng lượt + 1 GIỮ]`:** 🔴 **Trục-1 hợp-đồng FE↔BE VỠ 6 điểm khi 2 lane viết SONG SONG từ cùng spec** — tsc+build+574-test đều XANH cả 6: route picker lệch tên (F-1, 404 đội lốt empty-state vì TanStack nuốt lỗi + `?? []`) · body `purchaseEvaluationId` vs `PeId` (F-2 — **D1 của BE lập luận "FE đọc cùng spec sẽ khớp" = GIẢ ĐỊNH, không phải phép đo; FE đã KHÔNG làm thế**) · dossier body thiếu planId (F-3) · FE chỉ gọi POST khi BE ép Id=null ⇒ Sửa-đẻ-bản-sao im lặng (F-4) · `peTenGoiThau` (F-5) · `winnerSupplierNames` BE không có (F-6 — **bị F-1 CHE: mảng rỗng nên .map chưa chạy, vá F-1 xong mới lòi** = 2-lỗi-che-nhau). **Luật rút: đối chiếu hợp-đồng = so ROUTE+FIELD từng cái giữa types-FE và record-BE trên ĐĨA, đừng tin lời khai lane nào.** · F-8/F-9 security: **list bịt mà detail/download 0-rào ⇒ rào list chỉ còn là UX** — đối chứng twin PE :877-899 CÓ guard + S89 nháp-riêng-tư ⇒ thụt chuẩn, không phải "khuôn vốn thế" · F-12 INNER-join Projects (global-filter) = picker silent-vanish — lớp "vắng-mặt trông giống ổn". Trục-4 picker-khớp-rào ĐẠT (3/3 rào PE mirror đủ). Tag `[s161-w2, hop-dong-2-lane-song-song-6-diem-dut, loi-khai-la-gia-dinh, 2-loi-che-nhau, list-bit-detail-mo-rao-thanh-ux, inner-join-global-filter-vanish]`
- **[→ archive/2026-07.md @S161-curate] S159 đợt-5 (07-29) menu 6-leaf HĐ + `/contracts/deleted` (PWF 0H/4M/6L):** chú-thích-mang-SỐ phải chạy phép đếm (comment "2 chỗ IgnoreQueryFilters" — thật 3 file/17 site) · cookie-cutter phải soi CẢ phần bản gốc đã CHẶN (thiếu `disabled={deletedView}` ⇒ panel trắng) · kiểm hide-filter TRƯỚC khi kêu thiếu mirror 2-app · `tsc` + `noUnusedLocals` mạnh hơn grep cho claim "đã cắt".
- **S161 (07-29) W1 KHKK tiền-commit (Mig 69 + seeder + Designer type-10) — PWF 10 FLAG 1H/3M/4m/2L:** 🔴 **HIGH lại nằm ở `git` chứ không ở mã (S155 tái diễn):** 2/3 file Mig 69 UNTRACKED `??` mà snapshot đã tracked-M ⇒ `commit -a` nạp model 7 bảng nhưng BỎ migration; build+566 test vẫn xanh ⇒ **gate = `git status --porcelain -- src tests | grep '^??'` phải RỖNG ngay trước commit**. · **MAJOR bắt được nhờ đi NGƯỢC lời-khai FE:** comment FE tự khai "nhãn lấy từ BE `applicableTypeLabel`" ⇒ đi kiểm bờ BE: `AwLabels.Type` chỉ 3 entry, `GetValueOrDefault(type, type.ToString())` ⇒ UI hiện **"ContractSigningPlan"** tiếng Anh và `useState(\`Quy trình ${label}\`)` **GHI chuỗi Anh vào cột Name DB** — lớp *hợp-đồng-đứt-2-bờ*, tsc+build+test đều xanh. **Luật rút: câu comment dạng "bên kia lo" = ĐI ĐỌC BÊN KIA.** · seeder 5/5 PASS (call sau revoke `:2109`>`:2096`; revoker chỉ `Hrm|Off|Personal` ⇒ không lật 2-chiều; `TryGetValue` tuple `(RoleId,MenuKey)` — khuôn 1-role sẽ ném dup-key, unique-index `PermissionConfiguration:26` cứu) nhưng **acceptance "đo CỜ" chưa đo được ở đâu**: Dev DB **0 row** `Khkk*` cả MenuItems lẫn Permissions (menus 100 vs prod 142) ⇒ chưa nhánh nào từng chạy. · **`grep`-cùng-lớp bằng TOKEN-SONG-SINH** (`VehicleBookingLevelOpinions` repo-wide) mạnh hơn grep tên-mới: chứng 3/3 site đủ + 0 site thứ 4. · 2 chú thích cùng file nay SAI SỐ ("7 bảng"/"7 khối" → 8) + comment nguồn `MenuKeys.cs:37` "CỐ Ý NGOÀI All" nay sai + anchor `(:614)` trỏ dòng TRƯỚC-commit (đúng = :645) ⇒ **diff sửa mã phải sửa MỌI số/neo trong chú thích cùng file**. · STATUS.md `Menu keys 54`/`Policies 216` không đổi dù |All|=55 ⇒ derived-drift. Tag `[s161, high-o-git-khong-o-ma, hop-dong-dut-2-bo-nhan-BE, token-song-sinh-sweep, seeder-upgrade-if-exists, comment-mang-so-va-neo]`
| 6 | Commit + push + cicd (3-chân-kiềng + 2-bearer NẾU có tài khoản non-Admin — nv.test KHÔNG tồn tại prod, xem w1-synthesis tồn-2) | lead + `cicd-monitor` | [ ] |
| 4 | Lead: build ×2 + suite + acceptance §③-B sweep | lead | [x] build BE 0W/0E ×2 lượt · suite **574/0**×2 lượt (trước + SAU 12 fix) · FE ×2 PASS + **marker trong RUỘT bundle 1/1/1** (chứng build thật — 466ms rolldown-warm) · SHA-pair 4/4 giữ |
| 5 | Reviewer adversarial (CRUD endpoints POST/PUT/DELETE — bắt buộc) | `reviewer` | [x] **PWF 12 finding (3 CRIT · 5 MAJ · 4 MIN)** — Trục-1 đứt-2-bờ VỠ NẶNG 6 điểm (route picker · peId · dossier planId · PUT-vs-POST · peTenGoiThau · winnerSupplierNames — **2 lỗi che nhau F-1/F-6**; "lời-khai bên-kia-sẽ-khớp = giả định, không phải phép đo") + F-8/F-9 security detail+download 0-rào (PE twin CÓ — thụt chuẩn S89) → **11 FIXED + 1 GIỮ (F-10 kế thừa khuôn PE)**; vai chết #53 sau Trục-4 → lead đo nốt Trục 5 (test ĐỘNG ĐẠT) + 6 (boundary 0-diff ĐẠT) + 7 (tự-kiểm khai bias) + verdict on-behalf. `sub-reviewer-3.md` END+TOTAL=12 |
| 6 | Commit + push + cicd (3-chân-kiềng + 2-bearer NẾU có tài khoản non-Admin — nv.test KHÔNG tồn tại prod, xem w1-synthesis tồn-2) | lead + `cicd-monitor` | [!] commit **`6cbc6ad`** (20 file +5.020/−23, F-0 gate ĐẠT) PUSHED → cicd spawn nền (6 phép: CI 574 · 0-mig MIGTOP giữ · menu 142 · marker `approved-pe-awaiting-plan` 2 app · smoke KHKK GET · CỜ giữ) |
**Tồn W2:** F-10 upload/delete-attachment no-owner-check = lỗ CHUNG khuôn PE — bịt ĐỒNG BỘ 2 module 1 đợt riêng (cần anh gật vì đụng PE đang chạy prod).
## Ràng buộc mang theo
- 🔴 CẤM copy phần DUYỆT của Proposal (bẫy flatten :427-439 — OR-of-N thành AND; duyệt = W3 con-trỏ-đôi mirror PE).
| *(FE KHÔNG khai)* PUT `/{id}/dossier-items/{itemId}``:120` · GET `/inbox``:47` · GET `/{id}/attachments/{attId}/view``:171` | — | mã BE chưa ai gọi (F-4 + wire-sẵn W3) |
"[S89] Nháp = RIÊNG TƯ" chặn cả approver xem nháp. KHKK thả trọn ⇒ **thụt lùi so với chuẩn đã chốt S22/S89.**
- Bất đối xứng tự-mâu-thuẫn: list bịt (`:619`), `/deleted` bịt (`:749`), detail mở ⇒ rào list chỉ còn là UX.
- Fix 1 dòng: inject `ICurrentUser` + áp đúng vị ngữ của list (`isOwner ∥ userWfIds.Contains(wfId) && Phase != DangSoanThao`)
→ `ForbiddenException`.
### F-9 🟠 MAJOR — Download/View file cũng KHÔNG có rào (cùng lỗ với F-8)
-`DownloadContractSigningPlanAttachmentQueryHandler` (`:1028-1038`) chỉ khớp `AttachmentId + ContractSigningPlanId`,
không kiểm người gọi. Có `KeHoachKyKet.Read` + 2 GUID là tải được file scan của phiếu bất kỳ.
- Cùng gốc F-8; vá F-8 mà quên đường này thì file vẫn hở (2 site).
### F-10 🟡 MINOR (kế thừa, KHÔNG phải hồi quy) — Upload/Delete attachment không kiểm owner
-`Upload...Handler:951-966` và `Delete...Handler:1048-1053` chỉ kiểm "attachment thuộc đúng phiếu",
KHÔNG kiểm owner/approver ⇒ ai có `KeHoachKyKet.Update` cũng xoá được file của phiếu người khác, MỌI phase
(kể cả `DaDuyet`).
- Đối chứng công bằng: `PurchaseEvaluationAttachmentFeatures.cs:153-183` (PE) **cũng không có** guard này
⇒ đây là lỗ KẾ THỪA của khuôn, không phải lỗi mới của W2. Ghi để lead quyết (mở module mới là dịp bịt).
---
## §3 — TRỤC 3: Attachment handler
| Điểm kiểm | Kết quả |
|---|---|
| `DossierItemId` thuộc đúng phiếu (không FK vật lý) | ✔ CÓ tự kiểm `:961-966``AnyAsync(d.Id == did && d.ContractSigningPlanId == plan.Id)` → `NotFoundException` |
| Path sanitize | ✔ `SanitizeFileName:1007-1015` = `Path.GetFileName` (chặt `../`, chặt cả `C:\`) + thay `GetInvalidFileNameChars` + `TrimStart('.')` + cắt 200. Path lưu = `contract-signing-plans/{planId}/{attId}_{safe}` ⇒ không traversal |
**Trục-7 (FE solo-đuôi lead): tự-kiểm CÓ KHAI BIAS** — build ×2 + coming-soon 0/0 + whitelist matrix additive (`1|2|3` giữ nguyên hành vi, +10); fe-admin WfView→Designer là mapping theo bản chất app (admin dựng, user xem). Chưa có mắt độc lập — reviewer wave sau soi lại được từ diff.
| F-9 download/view 0 rào | MAJ-security | ✅ FIX — cùng helper áp Download handler (phục vụ cả 2 route) |
| F-10 upload/delete no-owner | MIN kế-thừa | ⏸ GIỮ — lỗ CHUNG khuôn PE (đối chứng PE :153-183 cũng không có) → bịt ĐỒNG BỘ 2 module 1 đợt riêng (ghi tồn run.md) |
| F-12 INNER-join Projects | MIN | ✅ FIX — LEFT join + null-safe Name/Code |
**Sau fix:** build BE 0W/0E · suite **574/0 GIỮ** · FE build ×2 PASS + **marker trong RUỘT bundle** (awaiting-plan/peTenGoiThau/peId = 1/1/1 mỗi app — chứng build thật, không cache) · SHA-pair 4/4 giữ.
**VERDICT: PASS_WITH_FLAGS — 12 finding (3 CRIT · 5 MAJ · 4 MIN) → 11 FIXED cùng lượt + 1 GIỮ-có-lý-do (F-10 kế thừa khuôn). ĐỦ ĐIỀU KIỆN COMMIT.**
<!-- END sub-reviewer-3 · TOTAL=12 FLAG -->
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.