wal: flush 20260729T2121
This commit is contained in:
@ -18,7 +18,9 @@ chain:
|
|||||||
- [x] gate 5-anchor W2 PASS 5/5 (A4 line-drift content-found; A5 KeHoachKyKet∈All:180 THẬT; phân xử lệch: mã sinh @CREATE per spec, entity-comment W1 sai → worker sửa)
|
- [x] gate 5-anchor W2 PASS 5/5 (A4 line-drift content-found; A5 KeHoachKyKet∈All:180 THẬT; phân xử lệch: mã sinh @CREATE per spec, entity-comment W1 sai → worker sửa)
|
||||||
- [x] wf W2 đợt-1 `wf_6f0aad26-229`: 2/2 lane chết #53 NHƯNG sub-md cứu trọn (BE 16.5KB — 15 endpoint policy 15/15 + 4 quyết D1-D4 lead duyệt cả 4 · FE 4 file SHA-pair) → lead solo đuôi FE 4-place (WfView 2-app-2-đường + whitelist matrix +10) → **build BE 0W/0E + FE ×2 PASS + coming-soon 0/0**; harvest 2 diary xong
|
- [x] wf W2 đợt-1 `wf_6f0aad26-229`: 2/2 lane chết #53 NHƯNG sub-md cứu trọn (BE 16.5KB — 15 endpoint policy 15/15 + 4 quyết D1-D4 lead duyệt cả 4 · FE 4 file SHA-pair) → lead solo đuôi FE 4-place (WfView 2-app-2-đường + whitelist matrix +10) → **build BE 0W/0E + FE ×2 PASS + coming-soon 0/0**; harvest 2 diary xong
|
||||||
- [x] wf W2 đợt-2 `wf_7490179a-70b`: test-specialist chết #53 sau khi file LAND — **8 test** (2 PIN + 6 acceptance, gồm không-đốt-mã-khi-409 + fixture-có-interceptor cho soft-delete) → lead suite tươi **574/0** (566+8 ≥ sàn 568); harvest diary xong
|
- [x] wf W2 đợt-2 `wf_7490179a-70b`: test-specialist chết #53 sau khi file LAND — **8 test** (2 PIN + 6 acceptance, gồm không-đốt-mã-khi-409 + fixture-có-interceptor cho soft-delete) → lead suite tươi **574/0** (566+8 ≥ sàn 568); harvest diary xong
|
||||||
- [!] reviewer W2 ĐANG CHẠY nền (trục chính: hợp-đồng FE↔BE đứt-2-bờ [2 lane viết song song] · IDOR D3 · picker-khớp-rào · test-coverage-claim · boundary 0-diff) → PASS thì commit/push → cicd
|
- [x] reviewer W2: **12 finding (3 CRIT đứt-2-bờ + F-8/F-9 security 0-rào detail/download)** — chết #53 sau Trục-4, lead đo nốt 5/6/7 + finalize on-behalf → **11 FIXED + 1 GIỮ (F-10 kế thừa PE)** → build+suite GIỮ 574/0 + marker ruột-bundle 1/1/1
|
||||||
|
- [x] commit W2 `6cbc6ad` (20 file +5.020/−23) PUSHED
|
||||||
|
- [!] wf: cicd-monitor verify `6cbc6ad` ĐANG CHẠY (CI 574 · 0-mig · menu 142 · marker `approved-pe-awaiting-plan` · smoke KHKK · CỜ giữ) → PASS ⇒ W2 ĐÓNG → W3 (duyệt 3 trạm — wave NẶNG NHẤT) hoặc W6 (độc lập, nhẹ hơn) — thứ tự đồ thị: W3 cần W2 ✅, W6 độc lập chạy lúc nào cũng được
|
||||||
- [ ] đợt-2 SAU khi wf về: test-specialist + database-agent (song song) → lead acceptance §③-B (build·test·sqlcmd 96·Down sạch·đo CỜ) → reviewer → commit/push → cicd-monitor 3-chân-kiềng
|
- [ ] đợt-2 SAU khi wf về: test-specialist + database-agent (song song) → lead acceptance §③-B (build·test·sqlcmd 96·Down sạch·đo CỜ) → reviewer → commit/push → cicd-monitor 3-chân-kiềng
|
||||||
- [ ] setup thủ công TRƯỚC W5 (SAU W1 vì type=10 cần mig 69): dựng workflow type=3 + type=10 3-trạm qua Designer (seed chỉ 1 trạm)
|
- [ ] setup thủ công TRƯỚC W5 (SAU W1 vì type=10 cần mig 69): dựng workflow type=3 + type=10 3-trạm qua Designer (seed chỉ 1 trạm)
|
||||||
- [ ] housekeeping @pause/closeout kế: nén MIND-1 5.447B (trần `_mind` 83,2% — MIND-5 chắc vượt) · re-carry 2 ý D rơi (vá-GỐC-Phase-3.5-refresh {mới-nêu} CÒN SỐNG + khuôn-5-đợt→sổ bền {gần-chốt}) · STATUS:6 bổ arc dry-run S160 (mù có lý do — land 15:27 trước pipeline) · hạ nhãn DR/WF-3-trạm đã-định-đoạt · E đếm 7 sub-file (không 6) · mở lại slot-45 trần `_mind` (tiền-đề lật: file SỐNG 83,2%) · quyết bookkeeping OVERDUE:light (cặp H24 chạy @S159 bookend mà `last_audit` đứng 27)
|
- [ ] housekeeping @pause/closeout kế: nén MIND-1 5.447B (trần `_mind` 83,2% — MIND-5 chắc vượt) · re-carry 2 ý D rơi (vá-GỐC-Phase-3.5-refresh {mới-nêu} CÒN SỐNG + khuôn-5-đợt→sổ bền {gần-chốt}) · STATUS:6 bổ arc dry-run S160 (mù có lý do — land 15:27 trước pipeline) · hạ nhãn DR/WF-3-trạm đã-định-đoạt · E đếm 7 sub-file (không 6) · mở lại slot-45 trần `_mind` (tiền-đề lật: file SỐNG 83,2%) · quyết bookkeeping OVERDUE:light (cặp H24 chạy @S159 bookend mà `last_audit` đứng 27)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# Reviewer Agent — Persistent Memory
|
# Reviewer Agent — Persistent Memory
|
||||||
|
|
||||||
|
- **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".
|
- **[→ 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]`
|
- **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]`
|
||||||
|
|||||||
@ -11,9 +11,11 @@
|
|||||||
| 1 | Đợt-1a BE: mega-file `ContractSigningPlanFeatures.cs` (Create+auto-Lines+MaKeHoach@CREATE · UpdateDraft · Get/List/pendingMe+inbox-precompute · Dossier UPSERT/DELETE · Attachments · DELETE allow-list · picker PE-chờ-lập-KH) + `ContractSigningPlansController` authz 2-tầng per-action | hmw `implementer-backend` | [x] `wf_6f0aad26-229` lane-0 chết #53 sau §7 NHƯNG sub-md **16.5KB ghi-từng-mục TRỌN** (tự mở 5 anchor + grep-cùng-lớp 3 site comment + 15 endpoint policy 15/15 + **4 quyết D1-D4 khai-lead-phân-xử → lead DUYỆT cả 4**) · build lead 0W/0E |
|
| 1 | Đợt-1a BE: mega-file `ContractSigningPlanFeatures.cs` (Create+auto-Lines+MaKeHoach@CREATE · UpdateDraft · Get/List/pendingMe+inbox-precompute · Dossier UPSERT/DELETE · Attachments · DELETE allow-list · picker PE-chờ-lập-KH) + `ContractSigningPlansController` authz 2-tầng per-action | hmw `implementer-backend` | [x] `wf_6f0aad26-229` lane-0 chết #53 sau §7 NHƯNG sub-md **16.5KB ghi-từng-mục TRỌN** (tự mở 5 anchor + grep-cùng-lớp 3 site comment + 15 endpoint policy 15/15 + **4 quyết D1-D4 khai-lead-phân-xử → lead DUYỆT cả 4**) · build lead 0W/0E |
|
||||||
| 2 | Đợt-1b FE: `pages/khkk/` 3 page + types ×2 app + 4-place mirror (routes + staticMap re-point 6 leaf khỏi coming-soon + menuKeys) + build ×2 + SHA-pair | hmw `implementer-frontend` | [x] lane-1 chết #53 sau MUC-1+cp (SHA-pair 4/4) → **lead solo đuôi**: Layout ×2 re-point 6 leaf (WfView 2 app 2 đường: user=alias matrix?type=10 whitelist+10 · admin=Designer deep-link) + fe-admin App routes + root KeHoachKyKet→/khkk/list; menuKeys place-3 SKIP-có-khai (0 consumer). Build ×2 PASS + coming-soon **0/0** |
|
| 2 | Đợt-1b FE: `pages/khkk/` 3 page + types ×2 app + 4-place mirror (routes + staticMap re-point 6 leaf khỏi coming-soon + menuKeys) + build ×2 + SHA-pair | hmw `implementer-frontend` | [x] lane-1 chết #53 sau MUC-1+cp (SHA-pair 4/4) → **lead solo đuôi**: Layout ×2 re-point 6 leaf (WfView 2 app 2 đường: user=alias matrix?type=10 whitelist+10 · admin=Designer deep-link) + fe-admin App routes + root KeHoachKyKet→/khkk/list; menuKeys place-3 SKIP-có-khai (0 consumer). Build ×2 PASS + coming-soon **0/0** |
|
||||||
| 3 | Đợt-2: test-specialist (≥2 test PIN: `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` + `CreateContractSigningPlan_FromNonApprovedPe_Throws`) | hmw `test-specialist` | [!] `wf_7490179a-70b` ĐANG CHẠY nền |
|
| 3 | Đợt-2: test-specialist (≥2 test PIN: `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` + `CreateContractSigningPlan_FromNonApprovedPe_Throws`) | hmw `test-specialist` | [!] `wf_7490179a-70b` ĐANG CHẠY nền |
|
||||||
| 4 | Lead: build ×2 + suite + acceptance §③-B sweep | lead | [ ] |
|
| 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` | [ ] |
|
| 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` | [ ] |
|
| 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
|
## 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).
|
- 🔴 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).
|
||||||
|
|||||||
@ -0,0 +1,215 @@
|
|||||||
|
# sub-reviewer-3 — W2 KHKK adversarial review (TRƯỚC COMMIT)
|
||||||
|
|
||||||
|
> Run: `2026-07-29-S161-khkk-w2-crud` · vai: reviewer · lượt 3
|
||||||
|
> Luật: ghi TỪNG TRỤC ngay khi đo xong (W1 tôi garble rồi resume — lần này ghi từ trục đầu).
|
||||||
|
> Diff scope (git status lúc bắt đầu): 9 M + 8 ?? (ngoài `.claude/`).
|
||||||
|
|
||||||
|
VERDICT (đang dựng — cập nhật ở cuối file)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §0 — Diff scope đo được
|
||||||
|
|
||||||
|
```
|
||||||
|
M fe-admin/src/App.tsx +9
|
||||||
|
M fe-admin/src/components/Layout.tsx 17 +/-
|
||||||
|
M fe-user/src/App.tsx +12
|
||||||
|
M fe-user/src/components/Layout.tsx 20 +/-
|
||||||
|
M fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx 3 +/-
|
||||||
|
M src/.../ContractSigningPlans/Services/IContractSigningPlanCodeGenerator.cs 4
|
||||||
|
M src/.../Domain/ContractSigningPlans/ContractSigningPlan.cs 7
|
||||||
|
M src/.../Configurations/ContractSigningPlanConfiguration.cs 6
|
||||||
|
M tests/.../Api/AuthorizePolicyRegressionTests.cs +118
|
||||||
|
?? fe-admin/src/pages/khkk/ · fe-admin/src/types/khkk.ts
|
||||||
|
?? fe-user/src/pages/khkk/ · fe-user/src/types/khkk.ts
|
||||||
|
?? src/.../Api/Controllers/ContractSigningPlansController.cs
|
||||||
|
?? src/.../Application/ContractSigningPlans/ContractSigningPlanFeatures.cs
|
||||||
|
?? tests/.../Application/ContractSigningPlanCrudTests.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 — TRỤC 1: HỢP-ĐỒNG FE↔BE (lớp đứt-2-bờ) — 🔴 VỠ NẶNG, 6 điểm đứt
|
||||||
|
|
||||||
|
Đo: `fe-user/src/types/khkk.ts` + 3 page `pages/khkk/*.tsx` (SHA identical fe-admin) **vs**
|
||||||
|
`ContractSigningPlansController.cs` (route thật) + `ContractSigningPlanFeatures.cs` (record DTO/Command thật).
|
||||||
|
|
||||||
|
### F-1 🔴 CRITICAL — Picker Create gọi route KHÔNG TỒN TẠI → 404, màn "Lập kế hoạch" LUÔN rỗng
|
||||||
|
- FE: `fe-user/src/pages/khkk/KhkkCreatePage.tsx:50` → `api.get('/contract-signing-plans/available-purchase-evaluations')`
|
||||||
|
(header hợp-đồng `fe-user/src/types/khkk.ts:16` khai đúng chữ đó).
|
||||||
|
- BE: `ContractSigningPlansController.cs:65` → `[HttpGet("approved-pe-awaiting-plan")]`.
|
||||||
|
- ⇒ 404. TanStack nuốt lỗi (không render `candidates.isError`), `list = candidates.data ?? []` (`:92`)
|
||||||
|
⇒ UI hiện đúng dòng "Không có phiếu Duyệt NCC nào đã duyệt mà chưa lập kế hoạch ký kết." (`:129`)
|
||||||
|
= **lỗi trông y hệt trạng-thái-rỗng-hợp-lệ**. Không tạo được phiếu nào. tsc/build/574-test đều xanh.
|
||||||
|
- Fix 1 dòng: đổi 1 trong 2 cho khớp (đề nghị BE giữ route, FE sửa `:50` + `types/khkk.ts:16`).
|
||||||
|
|
||||||
|
### F-2 🔴 CRITICAL — POST body sai TÊN TRƯỜNG → 400 vĩnh viễn (đứt đúng chỗ D1 mà implementer tưởng đã chốt)
|
||||||
|
- BE: `ContractSigningPlanFeatures.cs:275-278` `CreateContractSigningPlanCommand(Guid PeId, ...)`
|
||||||
|
→ JSON nhận `peId`. Validator `:284` `RuleFor(x => x.PeId).NotEmpty()`.
|
||||||
|
- FE: `types/khkk.ts:193-197` `CreateKhkkInput { purchaseEvaluationId, ... }`; gửi ở `KhkkCreatePage.tsx:72-77`.
|
||||||
|
- ⇒ `purchaseEvaluationId` KHÔNG bind vào `PeId` (không phải alias) ⇒ `PeId = Guid.Empty` ⇒
|
||||||
|
ValidationException 400 **mọi lần bấm Lưu**. Nút tạo phiếu chết.
|
||||||
|
- 🔴 Ghi chú adversarial: `sub-implementer-backend-0.md:80` D1 lập luận "lane FE dựng body từ CÙNG spec ⇒ giữ `PeId`
|
||||||
|
cho khỏi gãy". **Lane FE đã KHÔNG làm thế** — nó dùng tên đầy đủ theo convention repo. Lời-khai "bên kia sẽ khớp"
|
||||||
|
là giả định, không phải phép đo (lặp đúng lớp F-7/W1).
|
||||||
|
- Fix 1 dòng: đổi FE `purchaseEvaluationId` → `peId` (2 chỗ: type + body), HOẶC đổi BE record thành
|
||||||
|
`PurchaseEvaluationId` (khớp convention repo + FE) rồi sửa 4 site `request.PeId`.
|
||||||
|
|
||||||
|
### F-3 🔴 CRITICAL — POST dossier-item thiếu `contractSigningPlanId` → 400 vĩnh viễn
|
||||||
|
- BE `ContractSigningPlansController.cs:114-115`: `if (id != cmd.ContractSigningPlanId) return BadRequest(...)`.
|
||||||
|
- FE `KhkkDetailPage.tsx:153-158`: body = `{...itemForm, name, tvgsName, note}` — `UpsertKhkkDossierItemInput`
|
||||||
|
(`types/khkk.ts:205-213`) **KHÔNG có** `contractSigningPlanId`.
|
||||||
|
- ⇒ `cmd.ContractSigningPlanId = Guid.Empty` ≠ `id` ⇒ 400 "ID kế hoạch trên đường dẫn không khớp" mọi lần lưu căn cứ b.8-9.
|
||||||
|
- Fix 1 dòng: FE thêm `contractSigningPlanId: id` vào body (+ field trong interface).
|
||||||
|
|
||||||
|
### F-4 🔴 MAJOR — Sửa căn cứ b.8-9 tạo BẢN SAO thay vì cập nhật (FE không bao giờ gọi PUT)
|
||||||
|
- BE có 2 đường: `POST /{id}/dossier-items` (`:109-118`, ép `cmd with { Id = null }` = LUÔN insert)
|
||||||
|
và `PUT /{id}/dossier-items/{itemId}` (`:120-130` = update).
|
||||||
|
- FE chỉ gọi POST (`KhkkDetailPage.tsx:153`) cho CẢ 2 nhánh; `itemForm` mang `id` khi bấm Sửa
|
||||||
|
(`types/khkk.ts:206` `id?: string | null`) nhưng BE **ném đi**.
|
||||||
|
- ⇒ Sau khi vá F-3, bấm "Sửa" 1 căn cứ sẽ **đẻ dòng thứ 2** (dữ liệu bẩn im lặng, không lỗi).
|
||||||
|
Header hợp-đồng `types/khkk.ts:17-18` cũng chỉ khai POST + DELETE — thiếu hẳn PUT ⇒ lane FE không biết PUT tồn tại.
|
||||||
|
- Fix 1 dòng: FE `body.id ? api.put(.../dossier-items/${body.id}, ...) : api.post(...)`.
|
||||||
|
|
||||||
|
### F-5 🟠 MAJOR — Tên gói thầu KHÔNG hiện (list + detail): `peTenGoiThau` (BE) vs `tenGoiThau` (FE)
|
||||||
|
- BE `ContractSigningPlanFeatures.cs:39` `string? PeTenGoiThau` (cả List `:39` lẫn Detail `:124`).
|
||||||
|
- FE `types/khkk.ts:108` `tenGoiThau`; đọc ở `KhkkListPage.tsx:223` (cột chính hiện `—`) và
|
||||||
|
`KhkkDetailPage.tsx:281` (tiêu đề trang tụt về chuỗi mặc định 'Kế hoạch ký kết HĐ').
|
||||||
|
- Fix 1 dòng: đổi FE field thành `peTenGoiThau` (hoặc đổi tên record BE) — 1 chỗ ở types + 2 chỗ đọc.
|
||||||
|
|
||||||
|
### F-6 🟠 MAJOR — Picker crash trắng trang sau khi vá F-1: `winnerSupplierNames` KHÔNG có ở BE
|
||||||
|
- FE `KhkkCreatePage.tsx:160,163` gọi `pe.winnerSupplierNames.length` / `.map(...)`.
|
||||||
|
- BE `ApprovedPeAwaitingPlanDto` (`ContractSigningPlanFeatures.cs:150-164`) có `WinnerCount` + `WinnerQuoteTotal`,
|
||||||
|
**KHÔNG có** `WinnerSupplierNames`.
|
||||||
|
- ⇒ `undefined.length` → TypeError → React unmount cả trang (hiện đang bị F-1 che: mảng rỗng nên map chưa chạy;
|
||||||
|
**vá F-1 xong sẽ lòi ra ngay** — 2 lỗi che nhau).
|
||||||
|
- Fix 1 dòng: BE thêm `List<string> WinnerSupplierNames` vào DTO (append CUỐI), hoặc FE đổi sang render `winnerCount`.
|
||||||
|
|
||||||
|
### F-7 🟡 MINOR — Attachment: `contractSigningPlanDossierItemId` (FE `types/khkk.ts:148`) vs `DossierItemId` (BE `:84`)
|
||||||
|
- Hiện chưa có chỗ đọc field này trong page (grep 0 hit ngoài file type) ⇒ chưa vỡ hiển thị,
|
||||||
|
nhưng W3/gom-nhóm-scan-theo-căn-cứ sẽ vỡ. Fix: đổi FE thành `dossierItemId`.
|
||||||
|
|
||||||
|
### Đối chiếu path (12 path FE khai vs route thật)
|
||||||
|
| FE khai (`types/khkk.ts:10-21`) | BE thật | Khớp |
|
||||||
|
|---|---|---|
|
||||||
|
| GET `/contract-signing-plans` | `:30` | ✔ |
|
||||||
|
| GET `/deleted` | `:53` | ✔ |
|
||||||
|
| GET `/{id}` | `:71` | ✔ |
|
||||||
|
| POST `/` | `:78` | ✔ route, ✘ body (F-2) |
|
||||||
|
| PUT `/{id}` | `:87` | ✔ (body 3 field khớp `UpdateContractSigningPlanDraftBody:191`) |
|
||||||
|
| DELETE `/{id}` | `:97` | ✔ |
|
||||||
|
| GET `/available-purchase-evaluations` | ✘ KHÔNG CÓ (thật: `/approved-pe-awaiting-plan` `:65`) | ✘ **F-1** |
|
||||||
|
| POST `/{id}/dossier-items` | `:109` | ✔ route, ✘ body (F-3) |
|
||||||
|
| DELETE `/{id}/dossier-items/{itemId}` | `:132` | ✔ |
|
||||||
|
| POST `/{id}/attachments` | `:142` | ✔ (form `file`+`purpose`; FE không gửi `dossierItemId` — optional, OK) |
|
||||||
|
| GET `/{id}/attachments/{attId}/download` | `:162` | ✔ |
|
||||||
|
| DELETE `/{id}/attachments/{attId}` | `:180` | ✔ |
|
||||||
|
| *(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) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 — TRỤC 2: IDOR scope D3 (mã thật, không đọc lời khai)
|
||||||
|
|
||||||
|
Đo `ContractSigningPlanFeatures.cs` toàn bộ 1187 dòng.
|
||||||
|
|
||||||
|
### Vị ngữ list SỐNG (`:619-628`) và list ĐÃ XOÁ (`:749-758`) — KHỚP nhau, đúng như D3 khai
|
||||||
|
```
|
||||||
|
non-admin: p.DrafterUserId == userId
|
||||||
|
|| (p.Phase != DangSoanThao && p.ApprovalWorkflowId != null && userWfIds.Contains(...))
|
||||||
|
```
|
||||||
|
`userWfIds` = mọi workflow mà user là `ApproverUserId` ở BẤT KỲ Cấp nào (`:256-264`). ✔ đúng lời khai
|
||||||
|
"mình-soạn ∪ có-chân-duyệt", ✔ nháp người khác KHÔNG lọt, ✔ 2 màn cùng một vị ngữ (không lệch).
|
||||||
|
Người CÙNG PHÒNG không soạn/không có chân duyệt → **không thấy** (đúng thiết kế đã khai D3, có comment chỉ chỗ nới).
|
||||||
|
|
||||||
|
### F-8 🔴 MAJOR (an ninh) — GET `/{id}` detail KHÔNG có rào nào; module-anh-em PE thì CÓ
|
||||||
|
- `GetContractSigningPlanQueryHandler.Handle` (`ContractSigningPlanFeatures.cs:472-486`) chỉ
|
||||||
|
`FirstOrDefaultAsync(x => x.Id == request.Id)` rồi trả nguyên detail — **không đụng `currentUser`**
|
||||||
|
(handler thậm chí không inject `ICurrentUser`, `:469`).
|
||||||
|
- ⇒ Bất kỳ user nào có `KeHoachKyKet.Read` đọc được TOÀN BỘ phiếu của người khác (mọi phòng): dòng giá
|
||||||
|
từng NCC (`PeReferenceAmount`/`ProposedAmount`), căn cứ b.8-9, danh sách file, tên người soạn — **kể cả phiếu NHÁP**.
|
||||||
|
- 🔴 Không phải "khuôn repo vốn thế": `PurchaseEvaluationFeatures.cs:877-899` (module mà lane này khai là
|
||||||
|
mirror) CÓ guard detail đầy đủ — `isDrafter ∥ eligiblePhases ∥ isV2Approver`, cộng carve-out
|
||||||
|
"[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 |
|
||||||
|
| Whitelist MIME + size | ✔ validator `:916-942` (8 type, 20 MB) + `[RequestSizeLimit(25_000_000)]` controller `:144` |
|
||||||
|
| Xoá row mềm / file cứng | ✔ có chủ đích, mirror PE `:181-182`, best-effort try-catch |
|
||||||
|
|
||||||
|
### F-11 🟡 MINOR — `/view` tự ghép header `Content-Disposition` với FileName THÔ (chưa mã hoá)
|
||||||
|
- `ContractSigningPlansController.cs:176`: `Response.Headers["Content-Disposition"] = $"inline; filename=\"{f.FileName}\""`.
|
||||||
|
- `f.FileName` = `att.FileName` = **tên gốc client gửi** (`Features:979` lưu `request.FileName`, KHÔNG lưu bản đã sanitize).
|
||||||
|
- ⇒ (a) tên file tiếng Việt có dấu → header non-ASCII (Kestrel encode Latin-1 ⇒ tên hỏng, hoặc ném);
|
||||||
|
(b) tên chứa `"` → header vỡ cú pháp. Đường `/download` (`:167` `File(stream, ct, fileName)`) KHÔNG dính vì
|
||||||
|
MVC tự mã hoá RFC 6266 `filename*`.
|
||||||
|
- Fix 1 dòng: dựng bằng `new ContentDispositionHeaderValue("inline"){ FileNameStar = f.FileName }` thay chuỗi nội suy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 — TRỤC 4: Picker predicate ⟂ 2 rào Create
|
||||||
|
|
||||||
|
Create rào (`:312-331`): (i) `Phase == DaDuyet` · (ii) `Any(IsWinner)` · (iii) `!Any(plan.Phase != TuChoi)` · (iv) workflow type.
|
||||||
|
Picker (`:1165-1168`): `e.Phase == DaDuyet` **∧** `Any(sw.IsWinner)` **∧** `!Any(pl.Phase != TuChoi)`.
|
||||||
|
⇒ **KHỚP đủ 3 rào phụ thuộc PE** (rào iv thuộc lựa chọn workflow, không thuộc PE) — ✔ không mời user bấm vào 409.
|
||||||
|
Soft-delete: cả 2 phía đọc `db.ContractSigningPlans` qua global filter ⇒ phiếu xoá mềm không tính ở CẢ HAI ⇒
|
||||||
|
acceptance "DELETE → tạo lại → 201" đi lọt cả picker lẫn Create. ✔
|
||||||
|
|
||||||
|
### F-12 🟡 MINOR — Picker HẸP hơn Create do INNER JOIN Projects (silent-vanish, bài S147)
|
||||||
|
- `:1160` `join p in db.Projects on e.ProjectId equals p.Id` là INNER JOIN, mà `ProjectConfiguration.cs:24`
|
||||||
|
có `HasQueryFilter(!IsDeleted)` ⇒ PE thuộc dự án đã xoá mềm **biến mất khỏi picker** trong khi Create vẫn nhận.
|
||||||
|
- Departments/WorkItems đã dùng `DefaultIfEmpty()` (LEFT) — chỉ Projects là INNER. Lệch hướng an toàn
|
||||||
|
(thiếu chứ không thừa) nên MINOR, nhưng là đúng lớp "vắng-mặt trông giống ổn".
|
||||||
|
- Fix 1 dòng: LEFT-join Projects (`into pj from p in pj.DefaultIfEmpty()`) + `p != null ? p.Name : null`.
|
||||||
|
|
||||||
|
(đang đo tiếp — trục 5 test)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5-§7 + VERDICT — [LEAD điền on-behalf @S161: vai chết #53 sau Trục-4; 3 trục còn lại lead tự đo + toàn bộ fix đã áp]
|
||||||
|
|
||||||
|
**Trục-5 (test-claim): ĐẠT** — `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` = reflection ĐỘNG (`GetActionsWithVerb`, 0 hardcode danh sách endpoint) + neo chống-vacuous `>= 15 action` + write-verbs derive. KHÔNG phải bẫy hardcode F-C5/W1.
|
||||||
|
**Trục-6 (boundary): ĐẠT** — `git diff --stat` ContractCodeGenerator/Contracts/PurchaseEvaluations/Migrations = RỖNG (0-diff, W2 = 0-mig).
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### DISPOSITION 12 FINDING (từng dòng — luật S119)
|
||||||
|
| # | Sev | Xử @S161 |
|
||||||
|
|---|---|---|
|
||||||
|
| F-1 route picker | CRIT | ✅ FIX — FE → `approved-pe-awaiting-plan` (page + header hợp-đồng) |
|
||||||
|
| F-2 `peId` body | CRIT | ✅ FIX — FE `CreateKhkkInput.peId` (D1 đứng: spec = hợp-đồng; FE hội tụ về BE) |
|
||||||
|
| F-3 dossier body thiếu planId | CRIT | ✅ FIX — mutation inject `contractSigningPlanId: id`; type optional (form-state không cần) |
|
||||||
|
| F-4 Sửa-đẻ-bản-sao | MAJ | ✅ FIX — `body.id ? PUT : POST` + header hợp-đồng +PUT |
|
||||||
|
| F-5 `peTenGoiThau` | MAJ | ✅ FIX — types + 2 reader (List :223 · Detail :281) |
|
||||||
|
| F-6 `winnerSupplierNames` | MAJ | ✅ FIX — BE DTO append-cuối + subquery names khuôn PE; FE giữ render chip |
|
||||||
|
| F-7 `dossierItemId` | MIN | ✅ FIX — types (0 reader hiện tại, W3 khỏi vỡ) |
|
||||||
|
| F-8 detail 0 rào | MAJ-security | ✅ FIX — `EnsureCanViewAsync` (owner ∥ Admin ∥ [rời-nháp ∧ wf-approver]; nháp riêng-tư S89) áp Get |
|
||||||
|
| 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-11 Content-Disposition thô | MIN | ✅ FIX — `ContentDispositionHeaderValue.SetHttpFileName` RFC 6266 |
|
||||||
|
| 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