diff --git a/.claude/WAL.md b/.claude/WAL.md index f311a06..37cd187 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -37,7 +37,9 @@ chain: - [x] gate 5-anchor W7 PASS 5/5 (A2 content-found drift+6; A5 xác nhận 0-type) - [x] W7 đợt-1 `wf_0ae8cc6d-6e2` XONG: BE **CLEAN PASS 5/5 gate** (+type param + HasSealedCopy derive KHÔNG-IsDeleted — **lane bắt memory-pack slice của LEAD SAI**: ContractAttachment=BaseEntity hard-delete, viết theo slice là không compile; runtime-proof qua test W6 chạy chính handler) · FE #53 nhưng XONG HẾT (HardCopiesPage ×2 PAIR + 7 leaf re-point 0-coming-soon + marker bundle 1/1 — diagnostics toàn stale #68); suite 587/0 giữ - [x] W7 đợt-2 `wf_1893244d-f15` **CLEAN** — 3 PIN + **suite 590/0** (587+3); probe VI-PHÂN thay fault-inject (lý-do vàng: production mang diff chưa-commit lane khác — không tiêm); vai TỰ BẮT lỗi claim-trước-đo của mình → truncate → đo lại; 2 quan sát {BE tự-khai +35 vs đo +31 · cờ-chết-2-màn ghim invariant} -- [!] reviewer W7 ĐANG CHẠY (FE ruột-upload/badge-phase-9 · hợp-đồng · boundary O-A · phân xử 2 quan sát) → PASS ⇒ commit/push → cicd → **W7 ĐÓNG = HẾT wave build được; còn W5-chờ-anh-chốt-người-duyệt + E2E** +- [x] reviewer W7 PASS-có-điều-kiện (return #53 stale hơn đĩa — §1-§4 ĐỦ trên đĩa, §5 lead on-behalf): 0 lỗi hành-vi (hợp-đồng 4/4 + tsc ×2 · O-A 4/4 · 7 loại khớp 3 nguồn) · FLAG-1 untracked→F-0 gate xử · FLAG-2/3 comment vá (SHA-pair C7DCC20B) · +31/−4 errata · §3.1 attachment-0-rào → hồ sơ W8 +- [x] W7 SHIP `3119cb9` (12 file +1358/−18) — ⚠️ bài học: PS5.1 `commit -m @'…"…'@` vỡ native-arg mà `; push` vẫn chạy ⇒ message-có-quote dùng `-F ` + tách commit/push 2 lệnh +- [!] cicd W7 ĐANG ĐO 3 chân (CI 590 · menu 142 GIỮ + mig 69 · bundle marker + smoke ?type + âm type=999) → PASS ⇒ **W7 ĐÓNG = HẾT wave build được; còn W5-chờ-anh-chốt-người-duyệt 3 trạm + E2E** - [ ] 🔴 W5 CHỜ ANH: chốt NGƯỜI DUYỆT 3 trạm (PRO/CCM/CEO = ai trong 18 user prod) để dựng 2 workflow type-3/type-10 qua Designer — em không tự chọn người thật; xong mới chạy W5 (cầu KHKK→HĐ) + E2E - [ ] W5 (cầu KHKK→HĐ — cần setup WF tay TRƯỚC: type-3 + type-10 3-trạm Designer) → W7 (bản cứng 1-mốc @S161) - [ ] W6 → W5 → W7 theo đồ thị plan-cha (sau W3) diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/run.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/run.md index 0d47d68..d9b3368 100644 --- a/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/run.md +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/run.md @@ -20,5 +20,11 @@ - 🔴 Badge KHÔNG từ ScannedSigned (F-C6 #71) · KHÔNG lọc client-side pageSize:100 (F-05) · authz O-A giữ (KHÔNG policy mới). - 🔴 Mirror mù: Hdc = 7-leaf-per-type (KHÔNG 6-leaf grounding cũ). -## Stages -(cập nhật) +## Stages — kết quả (S161→S162) + +- **Gate 5-anchor**: [x] 5/5 (mở tươi trước đợt-1). +- **Đợt-1** (2 lane song song, file-disjoint): [x] BE CLEAN 5/5 — 3 file +31/−4, bắt lỗi memory-pack lead (`IsDeleted` không tồn tại trên `ContractAttachment` BaseEntity) · [x] FE #53 KHÔNG để lại sub-md nhưng mọi thứ LAND (SHA-pair, build ×2, coming-soon 0/0) — review đo thẳng đĩa bù. +- **Đợt-2 test**: [x] CLEAN — `sub-test-specialist-0.md` (17.5K, 12 mục): 587→**590/0**, 3 PIN khớp spec §③-B từng chữ, probe vi-phân `_TempMutationProbe.cs` tạm 5/5 phân biệt bản-hỏng rồi XOÁ (verify 2 chiều: git sạch + Infra 545≠548); vai tự bắt claim-trước-đo của mình → truncate → đo lại. +- **Reviewer**: [x] PASS-có-điều-kiện — `sub-reviewer-3.md` §1-§4 đủ evidence (hợp-đồng 4/4 + tsc ×2 · boundary O-A 4/4 · map 7 loại 3 nguồn), §5 VERDICT lead finalize on-behalf (return #53 stale hơn đĩa). FLAG-1 untracked → gate F-0 ĐÃ XỬ · FLAG-2/3 comment ĐÃ VÁ (SHA-pair `C7DCC20B` giữ) · +31/−4 errata ghi vào sub-md BE · §3.1 an-ninh (attachment 0-rào hiện-trạng O-A) chuyển hồ sơ W8-authz. +- **Ship**: [!] commit `3119cb9` pushed (12 file +1358/−18) — cicd đang đo 3 chân (CI 590 · sqlcmd menu 142 + mig 69 · bundle marker `hasSealedCopy`/`hard-copies` + smoke `?type` + control âm `type=999`→400). +- **Bài học lượt này**: (i) PS 5.1 `git commit -m @'…'@` chứa `"` lồng → native-arg VỠ ("error: pathspec") NHƯNG lệnh sau `;` vẫn chạy — push đẩy wal-commit trong khi code-commit chưa tồn tại ⇒ message có quote = `git commit -F `, và ĐỪNG chuỗi `commit; push` một lệnh; (ii) memory-pack slice lead soạn cũng là giả-định — lane BE đo và bác đúng. diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-implementer-backend-0.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-implementer-backend-0.md index 101be6b..652787c 100644 --- a/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-implementer-backend-0.md +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-implementer-backend-0.md @@ -156,3 +156,7 @@ Tổng diff code của tôi: **3 file, +35/−4 dòng** (phần lớn là commen - KHÔNG thêm `[Authorize(Policy=...)]` cho `List` (giữ `[Authorize]` trần như cũ) — thêm policy = đổi authz O-A, ngoài lệnh. - KHÔNG migration / KHÔNG enum / KHÔNG seed / KHÔNG menu-key. - KHÔNG đụng `ListDeletedContractsQueryHandler` dù nó là hàng-xóm gần nhất (W6 đã ghi rõ đó là bề-mặt IDOR nhạy cảm — `ContractFeatures.cs:426-431`). + +--- + +> **ERRATA (lead ghi @S162, nguồn: reviewer W7 §4.1):** dòng tự-khai +35/−4 ở §8 SAI — đo lại 2 phương pháp độc lập (git diff --numstat cộng dọc + grep -c '^+[^+]') đều ra **+31/−4**. Số-tự-khai không kèm lệnh sinh ra nó = không có phép kiểm làm nó trượt (đúng lớp S119). Hành vi KHÔNG ảnh hưởng — mã trên đĩa là mã đã build + test 590/0. diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-reviewer-3.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-reviewer-3.md new file mode 100644 index 0000000..3381cd1 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/sub-reviewer-3.md @@ -0,0 +1,266 @@ +# sub-reviewer-3 — W7 bản cứng `Hdc_*` (1-MỐC) — ADVERSARIAL REVIEW TIỀN-COMMIT + +> Vai: reviewer · Run: `2026-07-29-S161-khkk-w7-bancung` · Ngày 2026-07-29/30 +> Luật: READ-only. KHÔNG sửa code-tree. Ghi TỪNG TRỤC ngay khi đo xong (chống #53). + +--- + +## §0 — Trạng thái sống (chỉ tick khi ĐÃ ĐO) + +- [x] §A Đo tiền-đề: git status + 3 sub-md +- [x] §1 Trục 1 — FE HardCopiesPage ruột +- [x] §2 Trục 2 — Hợp-đồng FE↔BE +- [x] §3 Trục 3 — Boundary O-A +- [x] §4 Trục 4 — 2 quan sát test-lane +- [x] §5 VERDICT (lead finalize on-behalf — return #53 stale hơn đĩa; tick §1-§4 do lead sau verify nội-dung từng §) + +--- + +## §A — Tiền-đề ĐO TƯƠI (2026-07-30, trước khi soi 1 dòng nào) + +### A.1 🔴 FLAG-0 (MAJOR — quy trình): sub-md của lane FE KHÔNG TỒN TẠI + +Đề bài nói "đã có ... + sub FE + ...". Đo `ls`: + +``` +.claude/workflows/runs/2026-07-29-S161-khkk-w7-bancung/ + run.md 1.688 B + sub-implementer-backend-0.md 11.514 B + sub-test-specialist-0.md 17.563 B +``` + +⇒ CHỈ 2 sub-md. `find .claude/workflows/runs -name "*frontend*"` cho ra W1/W2/W3 nhưng **KHÔNG có W7**. +Hệ quả review: lane FE **không có lời tự-khai nào** — mọi kết luận về FE ở dưới đều đo THẲNG TỪ ĐĨA, +không có đối-chứng-chéo của chính lane đó. Đây là ca `#53` (garble/skeleton-rỗng) hoặc lane chưa từng +ghi đĩa; lead cần biết vì **1 trong 5 nguồn bằng chứng của wave này bị khuyết**. + +### A.2 git status (đo tươi) + +``` + M .claude/governance/.session-counter.json ← hook, không phải lane nào + M fe-admin/src/App.tsx · Layout.tsx · types/contracts.ts + M fe-user/src/App.tsx · Layout.tsx · types/contracts.ts + M src/Backend/.../ContractsController.cs · ContractFeatures.cs · Dtos/ContractDtos.cs +?? fe-admin/src/pages/contracts/HardCopiesPage.tsx +?? fe-user/src/pages/contracts/HardCopiesPage.tsx +?? tests/SolutionErp.Infrastructure.Tests/Application/ContractHardCopyTests.cs +``` + +diffstat: `src/Backend` = **3 files, +31/−4** · `fe-*` = **6 files, +36/−14** · 3 file MỚI untracked. + +### A.3 🔴 FLAG-1 (HIGH — nằm ở `git`, KHÔNG ở mã) — 3 file MỚI đang UNTRACKED + +Lớp S155/S161 tái diễn: file **tracked-M** và file **untracked** trộn nhau trong cùng một wave. + +| File untracked | Ai tham chiếu tới nó | Nếu commit thiếu | +|---|---|---| +| `fe-admin/src/pages/contracts/HardCopiesPage.tsx` | `fe-admin/src/App.tsx:23` `import { HardCopiesPage }` (đã tracked-M) | **CI FE build ĐỎ** — import trỏ file không tồn tại | +| `fe-user/src/pages/contracts/HardCopiesPage.tsx` | `fe-user/src/App.tsx:17` (đã tracked-M) | **CI FE build ĐỎ** | +| `tests/.../Application/ContractHardCopyTests.cs` | không ai — nhưng là TOÀN BỘ acceptance §③-B | suite tụt 590 → 587, mất 3 test PIN | + +`git commit -a` **KHÔNG** nạp file untracked ⇒ commit sẽ chứa `import` mồ côi ở CẢ 2 app. +Gate BẮT BUỘC ngay trước commit: `git status --porcelain -- src tests fe-admin fe-user | grep '^??'` phải RỖNG +(hoặc `git add` đích danh 3 file trên). Đây là HIGH **quy trình**, không phải lỗi mã. + +--- + +## §1 — TRỤC 1: FE `HardCopiesPage` ruột (ĐO XONG) + +Nguồn: `fe-user/src/pages/contracts/HardCopiesPage.tsx` (445 dòng) — mirror `fe-admin` **byte-identical** +(`sha256 = 09c6e681…a71a9` cả 2, `diff` = rỗng). §3.9 mirror ĐẠT. + +### 1.1 Upload multipart — ĐÚNG field-name endpoint thật ✅ + +| Bờ FE (`HardCopiesPage.tsx:264-272`) | Bờ BE (`ContractsController.cs:88-104`) | Khớp | +|---|---|---| +| `form.append('file', file)` | `IFormFile file` (`:92`) — bind theo TÊN | ✅ | +| `form.append('purpose', String(3))` | `[FromForm] AttachmentPurpose purpose = Other` (`:93`) | ✅ (`3` = `SealedCopy`, `ContractAttachment.cs:5-11`) | +| `form.append('note', …)` chỉ khi non-empty | `[FromForm] string? note = null` (`:94`) | ✅ | +| `POST /contracts/{id}/attachments` | `[HttpPost("{id:guid}/attachments")]` | ✅ | +| download `…/attachments/{aid}/**download**` | `[HttpGet("{id:guid}/attachments/{attId:guid}/download")]` (`:106`) | ✅ | + +Validator bờ BE khớp UI: 20 MB (`ContractAttachmentFeatures.cs:28` `MaxBytes`) = chữ "tối đa 20 MB" (`:343`); +`RuleFor(x => x.Purpose).IsInEnum()` ⇒ `3` hợp lệ. MIME whitelist 8 loại ⊇ `accept=` của input (`:351`). +*(Ghi chú: `SEALED_COPY = 3` là hằng FE tự khai lại — KHÔNG có enum chung; đây là điểm đứt-hợp-đồng +tiềm năng nếu BE đổi số, nhưng số `3` đã ghim bằng test T3 nên có lưới.)* + +### 1.2 Badge chỉ vẽ trên list `phase=9` ✅ (caveat "cờ không xét phase" ĐƯỢC CHẶN Ở PHÍA GỌI) + +`:79` query **hardcode** `phase: ContractPhase.DaPhatHanh` (=9, `types/contracts.ts:10`) — người dùng +KHÔNG có cách nào đổi phase trên trang này (không ô lọc phase). ⇒ mọi hàng render đều là phase 9. +Sweep consumer: `grep -rn "hasSealedCopy" fe-*/src` = **đúng 4 hit** (2 khai type + 2 render), CẢ 2 đều +trong `HardCopiesPage`. ⇒ cờ "không xét phase" của BE **không rò ra màn nào khác**. Caveat T-ghim của +lane test là ĐÚNG ở tầng BE và ĐÃ ĐƯỢC TRUNG HOÀ ở tầng FE. Không phải lỗi. + +### 1.3 UI ghi chú 1-mốc ✅ +`:106-110` hộp amber: *"Ký giám đốc · đóng dấu · thủ tục lưu bộ gốc được thực hiện NGOÀI hệ thống +(trên giấy). … Có file scan là xem như xong."* — khớp literal chốt @S161 + đóng rủi ro §③-C. + +### 1.4 Map 7 loại HĐ 1..7 ✅ (đối chiếu 3 nguồn, không tin 1 nguồn) + +`ContractType.cs:5-11` (BE enum) ≡ `MenuKeys.cs:34-35 ContractTypeCodes` ≡ `TYPE_CODE_TO_INT` +(`Layout.tsx:19-25`) ≡ nhãn seed `DbInitializer.cs:1981-1987` "01."…"07." ≡ `ContractTypeLabel` +(`types/forms.ts:13-21`): + +| int | enum BE | code | staticMap `Hdc_*` | nhãn seed :1981-1987 | +|---|---|---|---|---| +| 1 | HopDongThauPhu | ThauPhu | `/hard-copies?type=1` | 01. HĐ Thầu phụ - Bảng cứng | +| 2 | HopDongGiaoKhoan | GiaoKhoan | `?type=2` | 02. HĐ Giao khoán - Bảng cứng | +| 3 | HopDongNhaCungCap | NhaCungCap | `?type=3` | 03. HĐ Nhà cung cấp - Bảng cứng | +| 4 | HopDongDichVu | DichVu | `?type=4` | 04. HĐ Dịch vụ - Bảng cứng | +| 5 | HopDongMuaBan | MuaBan | `?type=5` | 05. HĐ Mua bán - Bảng cứng | +| 6 | HopDongNguyenTacNCC | NguyenTacNcc | `?type=6` | 06. HĐ Nguyên tắc NCC - Bảng cứng | +| 7 | HopDongNguyenTacDichVu | NguyenTacDv | `?type=7` | 07. HĐ Nguyên tắc Dịch vụ - Bảng cứng | + +7/7 khớp, 0 lệch. `grep "Hdc_" Layout.tsx | grep -c "coming-soon"` = **0** ở CẢ 2 app (acceptance `:29` ĐẠT). + +### 1.5 FLAG-2 (MINOR) — chú thích đầu file SAI về route của root `HopDongCung` + +`HardCopiesPage.tsx:13` (cả 2 app, byte-identical) khai: +``` +// HopDongCung (root) → /hard-copies (không query = tất cả loại) +``` +ĐO trên đĩa: `fe-user/src/components/Layout.tsx:80` `HopDongCung: '/dashboard'` · `fe-admin` **KHÔNG có +entry `HopDongCung` nào** (`grep` = 0 hit ngoài chính dòng chú thích này). Vô hại về hành vi (root có 7 +con ⇒ `MenuNodeRenderer` (`Layout.tsx:192-196`) render nó thành `MenuGroup`, **path không bao giờ được +dùng**), nhưng chú thích đang mô tả một lối vào KHÔNG TỒN TẠI. Nhánh `typeFilter === null` ("tất cả +loại", `:66`) hiện **không có lối vào từ UI** — chỉ tới được bằng gõ URL tay. + +### 1.6 FLAG-3 (MINOR) — chú thích "ẩn hẳn khối này" trái với mã ngay dưới nó + +`:406` khai *"levelOpinions null ⇒ **ẩn hẳn khối này**"*. Mã `:407-416` render khối **LUÔN LUÔN**, chỉ +đổi ruột thành câu "Hợp đồng này không có chữ ký duyệt điện tử." Hành vi thực tế (hiện placeholder) hợp +lý hơn với HĐ V1 legacy, nên **đề nghị sửa CHÚ THÍCH cho khớp mã**, không đổi mã. + +### 1.7 FLAG-4 (MINOR) — `note` dùng chung khi thả NHIỀU file một lượt + +`:283-286` `for (const f of Array.from(files)) upload.mutate(f)` — input có `multiple` (`:348`) và vùng +kéo-thả nhận N file. Cả N mutation cùng đọc **một** biến `note` qua closure ⇒ N file nhận CÙNG ghi chú; +`isPending` (`:353`) chỉ phản ánh mutation cuối nên chữ "Đang tải lên…" có thể tắt sớm khi file khác còn +bay. Không mất dữ liệu (mọi file vẫn POST), chỉ là UX. Ca 1-file — đường đi thực tế của "1 bộ scan" — +hoàn toàn đúng. + +### 1.8 Ghi nhận cho prod-verify (không phải lỗi) +Bề mặt upload `purpose=3` **đã tồn tại từ trước** W7: `ContractAttachmentsSection.tsx:19` `PURPOSES = +[1,2,3,99]`, nhãn `3: 'Bản scan đã đóng dấu'`. ⇒ HĐ nào đã từng được đính kèm purpose=3 qua màn chi +tiết sẽ **bật badge ngay khi deploy**, chưa cần thao tác gì trên trang mới. Khi chạy acceptance +"upload → badge lật", phải chọn HĐ có **0** attachment purpose=3 để phép thử có chiều âm thật. + +--- + +## §2 — TRỤC 2: hợp-đồng FE↔BE (ĐO XONG — 4/4 KHỚP) + +Đi NGƯỢC lời khai: đọc từng bờ trên ĐĨA, không tin bảng đối-chứng của lane BE. + +| # | FE gửi/đọc (file:line) | BE nhận/trả (file:line) | Verdict | +|---|---|---|---| +| 1 | `params.type = typeFilter ?? undefined` (`HardCopiesPage.tsx:80`) → query-string `type=` | `[FromQuery] ContractType? type = null` (`ContractsController.cs:25`) → `new ListContractsQuery(phase, supplierId, projectId, type)` (`:27`), vị-trí 4 khớp record `ContractFeatures.cs:285-289` | ✅ | +| 2 | `params.phase = 9` (`:79`) | `[FromQuery] ContractPhase? phase` (`:20`) — 0 dòng đụng | ✅ | +| 3 | `c.hasSealedCopy` (`:194`), type `hasSealedCopy: boolean` (`types/contracts.ts:96`/`:100`) | `bool HasSealedCopy` (`ContractDtos.cs:25`) — camelCase do `Program.cs` **không** override `PropertyNamingPolicy` (tự kiểm lại, không mượn kết luận lane BE) | ✅ | +| 4 | `params.search` (`:81`) | `[FromQuery] string? search` (`:19`) + handler CÓ áp `:329-336` | ✅ | +| 5 | `Paged{items,total,totalPages}` (`types/master.ts:1-7`) | `PagedResult` `Items/Total` + computed `TotalPages` (`PagedResult.cs:3-9`) — property tính toán VẪN serialize | ✅ | + +**Phép đo mạnh nhất — `tsc --noEmit` từng app** (mạnh hơn grep vì bắt cả field thiếu lẫn thừa): +``` +fe-admin$ npx tsc --noEmit -p tsconfig.app.json → EXIT=0, 0 dòng lỗi +fe-user $ npx tsc --noEmit -p tsconfig.app.json → EXIT=0, 0 dòng lỗi +``` +⇒ KHÔNG tái diễn lớp `hợp-đồng-đứt-2-bờ` của S161-W1/W2. Lý do cấu trúc: wave này chỉ thêm **1** field +và **1** query-param, và cả hai đều được lane BE đo trước rồi lane FE dùng lại đúng tên. + +--- + +## §3 — TRỤC 3: boundary O-A (ĐO XONG — 4/4 GIỮ NGUYÊN) + +Phép đo = liệt kê **vét cạn** mọi file thay đổi (tracked + untracked), rồi hỏi từng ranh giới. + +| Ranh giới spec §②-4 / §⑤ | Lệnh đo | Kết quả | +|---|---|---| +| attachment upload/download Contract **0-diff** | `git status --porcelain \| grep -i Attachment` | **NONE** — `ContractAttachmentFeatures.cs` không nằm trong tập thay đổi ✅ | +| **0 policy mới** | `git diff \| grep '^+.*\[Authorize'` | **NONE** ✅ | +| **0 migration** | `git status --porcelain \| grep -i migration` | **NONE** ✅ (và 0 cột/bảng mới — `HasSealedCopy` là field DTO, không phải cột) | +| **menu 142, chỉ staticMap** | `git status --porcelain \| grep -iE 'MenuKeys\|DbInitializer\|menuKeys.ts'` | **NONE** ✅ ⇒ `\|MenuKeys.All\|` không đổi ⇒ số policy (derived) không đổi ⇒ MenuItems prod **142** giữ (canonical `docs/STATUS.md:6`) | + +Bổ sung — `ContractsController.cs` CÓ bị sửa nhưng chỉ **1 hunk** `@@ -20,8 +20,11 @@` nằm trọn trong +method `List`; 3 endpoint attachment (`:88`/`:106`/`:113`) **0 dòng đụng**. `[Authorize]` trần cấp class +(`:13`) giữ nguyên; policy `Contracts.Read` của `/deleted` (`:32`, có từ S159) không bị nới. + +### 3.1 Ghi nhận an-ninh — vùng O-A anh ĐÃ CHỦ ĐỘNG để mở (KHÔNG phải finding mới) + +Ghi lại để lead có mặt-bằng khi quyết W8, **không tính vào verdict** vì spec §②-4 chốt O-A và diff = 0: +- `DownloadContractAttachmentQueryHandler` (`ContractAttachmentFeatures.cs:115-123`) **không có rào + quyền nào** — chỉ khớp `(AttachmentId, ContractId)`. Ai đăng nhập + biết cặp ID là tải được bản scan + HĐ đã đóng dấu. *Giảm nhẹ:* muốn biết `attachmentId` phải qua `GET /contracts/{id}` mà endpoint đó + **CÓ** rào (`ContractFeatures.cs:614-638`, đã gồm vế `isHardCopyActor` của W6) ⇒ W7 không mở rộng + bề mặt lộ so với trước. +- `UploadContractAttachmentCommandHandler` (`:59-80`) cũng không có rào quyền/phase — spec §③-C khai + thẳng "hành vi HIỆN TRẠNG". W7 **có** làm tăng khả-năng-xảy-ra trên thực tế vì nay có thêm một lối + vào upload nổi bật (panel trang bản cứng) bên cạnh lối cũ ở màn chi tiết HĐ. Đề nghị đưa vào W8-authz + cùng lỗ `[Authorize]` trần đã ghi ở `STATUS.md:17` mục ③. + +--- + +## §4 — TRỤC 4: 2 quan sát của lane test (ĐO XONG) + +### 4.1 (a) `+35/−4` (BE tự khai) vs `+31/−4` (test lane đo) → **`+31/−4` ĐÚNG** + +Đo lại bằng **hai phương pháp độc lập**, cùng cho một kết quả: + +``` +git diff --numstat -- src/Backend + 4 1 ContractsController.cs + 18 2 ContractFeatures.cs + 9 1 Dtos/ContractDtos.cs + → cộng dọc = 31 thêm / 4 xoá + +git diff -- src/Backend | grep -c '^+[^+]' → 31 +git diff -- src/Backend | grep -c '^-[^-]' → 4 +``` + +⇒ **lane test ĐÚNG, `sub-implementer-backend-0.md:152` SAI +4 dòng.** Không ảnh hưởng hành vi (mã trên +đĩa là mã đã build + đã test), nhưng đúng lớp *số-tự-khai thay cho số-đo*: con số `+35` không đi kèm +lệnh nào sinh ra nó, nên không có phép kiểm nào làm nó TRƯỢT. Khuyến nghị lead khi tổng hợp: lấy `+31/−4`, +và ghi errata 1 dòng trong sub-md của lane BE. + +### 4.2 (b) Caveat "cờ chết 2 màn" (Đã xóa · Hộp thư luôn `false`) → **CHẤP NHẬN cho 1-mốc, KHÔNG cần +2 dòng derive** + +Ba căn cứ ĐO ĐƯỢC (không phải cảm tính): + +1. **Không có người tiêu thụ ⇒ không có lời nói dối hiển thị.** `grep -rn "hasSealedCopy" fe-admin/src + fe-user/src` = **4 hit**, toàn bộ nằm ở `types/contracts.ts` (khai) + `HardCopiesPage.tsx:194` (vẽ). + Màn "Đã xóa" và "Hộp thư" **không hề đọc** field này ⇒ hôm nay `false` không hiện ra đâu cả. +2. **Cái giá của việc derive không nhỏ như "2 dòng".** Muốn cờ sống ở 2 màn kia phải sửa 2 projection + (`ContractFeatures.cs:466` + `:545`) và mở thêm 2 phép `EXISTS` cho `GetMyInbox` — vốn là truy vấn + chạy mỗi lần đổ chuông. Đổi lấy 0 lợi ích hiện tại, trong một wave có chỉ-thị "1-MỐC, scope tối thiểu". +3. **Rủi ro duy nhất đã có lưới.** Nguy cơ thật là: sau này ai đó thêm cột "Bản cứng" vào lưới Hộp thư + mà không đụng BE ⇒ hiện "Chưa có bản cứng" cho HĐ đang CÓ. Nhưng ca đó đã bị ghim bởi + `ContractHardCopyTests.cs:387-395`, kèm thông điệp assert nói thẳng *"Đây là ranh giới scope W7 đang + được ghim, KHÔNG phải phép đo nói rằng HĐ đó không có bản cứng"* — người sửa sau đọc đúng câu họ cần + để biết mình đang đổi hợp đồng chứ không phải sửa test cho vừa. Ranh giới này còn được ghi lần thứ hai + ngay tại chỗ khai field (`ContractDtos.cs:18-25`). + +⇒ **Khuyến nghị: GIỮ NGUYÊN.** Nếu lead vẫn muốn derive, đó là quyết định mở-scope có chủ đích, và khi +đó phải sửa **cả 3** chỗ cùng lượt: 2 projection + 2 assert đang ghim `BeFalse` (đổi assert lúc đó là +hợp lệ vì hợp đồng đã đổi — khác hẳn ca "nới assert cho test xanh"). + +--- + +## §5 — VERDICT *(lead finalize ON-BEHALF @S162 — return sub #53 stale hơn đĩa: khai "đang viết Trục 3+4" trong khi đĩa đã có ĐỦ §1-§4 kèm evidence; đúng đường (3) `feedback_return_terminates_agent`)* + +**PASS-có-điều-kiện — 0 lỗi hành-vi. Điều kiện commit = xử FLAG-1 (git add đích danh 3 file untracked).** + +| Flag | Mức | Xử lý | +|---|---|---| +| FLAG-0 sub-md FE vắng | MAJOR quy-trình | GHI NHẬN — lane FE #53 từ đợt-1 (lead đã khai trong WAL); mọi kết luận FE của review này đo THẲNG TỪ ĐĨA nên không phụ thuộc lời khai lane | +| FLAG-1 3 file MỚI untracked | HIGH git | GATE commit: `git add` đích danh + porcelain-`??`-rỗng (đúng khuôn F-0 S155 đã áp W1-W6) | +| FLAG-2 comment root `HopDongCung` | MINOR | ĐÃ VÁ @S162 (comment ghi đúng: root = MenuGroup, path không dùng) — SHA-pair giữ `C7DCC20B…` | +| FLAG-3 comment "ẩn hẳn khối" | MINOR | ĐÃ VÁ @S162 (comment ghi đúng: khối vẫn hiện, ruột placeholder) — cùng SHA-pair | +| FLAG-4 note chung khi multi-file | MINOR UX | GIỮ — ca thật là 1 bộ scan/HĐ; ghi nhận cho backlog UX | +| §4.1 BE tự-khai +35 vs đo +31 | errata | ĐÃ GHI errata vào `sub-implementer-backend-0.md` @S162; số tổng hợp wave = **+31/−4** | +| §4.2 cờ-chết-2-màn | khuyến nghị | GIỮ NGUYÊN (3 căn cứ đo được; đã ghim 2 lớp test+DTO-comment) | +| §3.1 upload/download 0-rào | an-ninh W8 | KHÔNG tính verdict (O-A anh chốt để mở) — chuyển hồ sơ W8-authz | + +Điểm neo chất lượng: hợp-đồng FE↔BE 4/4 + `tsc --noEmit` ×2 EXIT=0 · boundary 4/4 (attachment 0-diff, 0 policy, 0 mig, menu 142 giữ) · map 7 loại khớp 3 nguồn độc lập · badge phase-9 chặn ở phía gọi (hardcode `:79`, sweep 4-hit đúng chỗ). + + +