wal: flush 20260730T0019

This commit is contained in:
pqhuy1987
2026-07-30 00:19:52 +07:00
parent 3119cb9104
commit 1a397d249e
4 changed files with 281 additions and 3 deletions

View File

@ -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 <file>` + 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)

View File

@ -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 <file>`, 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.

View File

@ -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.

View File

@ -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<T>{items,total,totalPages}` (`types/master.ts:1-7`) | `PagedResult<T>` `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
**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ỗ).