From 416ba9a777e804ae5732719fd767789b850ca864 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 13 Aug 2026 23:12:55 +0700 Subject: [PATCH] wal: flush 20260813T2312 --- .claude/agent-memory/reviewer/MEMORY.md | 3 + .../run.md | 21 +++ .../spec-hd-thauphu-2form-13-08-2026.md | 73 +++++++++ .../sub-reviewer-4.md | 151 +++++++++++++++++- 4 files changed, 241 insertions(+), 7 deletions(-) diff --git a/.claude/agent-memory/reviewer/MEMORY.md b/.claude/agent-memory/reviewer/MEMORY.md index 40a51e99..fe128579 100644 --- a/.claude/agent-memory/reviewer/MEMORY.md +++ b/.claude/agent-memory/reviewer/MEMORY.md @@ -55,6 +55,9 @@ Adversarial pre-commit reviewer SOLUTION_ERP. Read-only verify + live curl prod ## S190 (lead-seed C2 /pause) — YC-029/YC-030 ensemble review GĐ3 - Ensemble 3-lane (B2) + 4-lane style (B4) trả cổ tức THẬT: bắt **2 BLOCKER tiền-đề** mà invest-Fable + lead verify đều lọt vì "nghe xuôi theo khuôn" — "bỏ type=3 trang tự suy" (→trang trắng) · "BE pendingMe sẵn" (0-hit, param nuốt im lặng). Luật: **claim CƠ-CHẾ khuôn ≠ claim chỗ-thiếu — phải MỞ khuôn đọc, grep chỉ bắt cái sau**. W5 anchor "KHKK có nút Xóa panel duyệt" = **CODE CHẾT S175** (nút thật ở thanh đáy detail); mirror sai module (PE ⟂ KHKK) — lời anh "tương tự KHKK" ⇒ approver-delete KHÔNG mở. be2 đo LocalDB **12/13 role CanDelete=0** chứng policy-403-trước-owner-check = khoá oan ⇒ policy dời W8. Gate-W5 reviewer chết-non #53 → lead-gate on-behalf 3 câu sắc (Roles-shape · UserInfo.roles thật · host-404-ổn). Tag `[s190, claim-co-che-phai-mo-khuon, anchor-code-chet, mirror-nham-module]` +## S194 (08-13) lane R4 chấm KẾ HOẠCH YC-032 (lăng-kính "cái gì THIẾU") — `PASS-WITH-FINDINGS 12 (6 nặng)` +🔴 **THƯỚC HỎNG tự bắt:** `antiword` KHÔNG có `-m UTF-8.txt` trả text mà control-dương `'hợp đồng'`=**0** trên cả 3 file `.doc`, và `U+FFFD`=0 nên **"trông như sạch"** — census chạy trên đó ra 0-hit im lặng. Vá cờ ⇒ 56 hit. **Bài:** 0-hit trên file 516 KB mà không có control-dương = đọc thành "file rỗng". · **Chuỗi OR đọc ngược:** spec khai "chỉ 4 role thấy, người soạn KHÔNG có" — thật ra `DrafterUserId==userId` là vế OR **ĐẦU TIÊN**, 4 role là vế **nới thêm** (`ContractFeatures.cs:347-354`) ⇒ đọc vế cuối rồi tưởng là bộ lọc = đảo nghĩa cả tính năng; luôn đọc TRỌN chuỗi OR trước khi phán "hẹp". · **Acceptance 0-bit gen-2:** "upload bản cứng → sửa HĐ ⇒ 409" XANH sẵn vì phase-9 đã khoá sửa (`:167`) ⇒ hỏi "bề mặt nào CÒN mở ở trạng thái đó?" (ở đây = tập đính kèm). · **File nguồn sai ĐUÔI:** seed khai `.docx`, đĩa chỉ có `.doc` + chính mô tả seed tự thú "cần convert" ⇒ việc thiếu là 3 (convert+chép+`UPDATE IsActive=1`), 0 wave nhận. · **Số hiệu điều = auto-number Word** (`numPr`=168/`Heading`=102 ở 002.05 ⟂ `Heading`=0 ở 002.04) ⇒ mọi phép trích `` mất trắng số điều. · 🟢 **3 nghi ngờ tôi tự bác bằng đo** (phase-9 tới được · seed workflow idempotent PER-CODE · mã HĐ sinh lúc TẠO) — khai ra để lượt sau khỏi đo lại; nghi-mà-không-đo thì thành finding bịa. Tag `[s194, thuoc-hong-antiword, chuoi-OR-doc-nguoc, acceptance-0-bit, sai-duoi-file, auto-number-vo-hinh]` + ## 🚨 Recurring bug patterns — CHỈ phần KHÔNG có trong system-prompt > #44 · #43 · #42 · #41 · #39 · #17 và "Wire BE claim" đã nằm trong system-prompt (Category 1+2) — không chép lại ở đây. - **#70 FE absolute-set stale-echo race** — N field cùng-cột share 1 mutation + echo sibling từ server-snapshot + `invalidate()` không await ⇒ lưu 2 ô liên-tiếp đè mất. Window mở **SAU** `isPending=false`, KHÔNG lúc in-flight. Catch: đếm field-cùng-cột share mutation + check invalidate awaited. diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md index eec689e5..798018ba 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md @@ -67,3 +67,24 @@ Mục lục CẢ 2 file khai `PHẦN 2 — ĐIỀU KIỆN CHUNG` gồm **ĐIỀU 1→17**, thân 2 file **không chứa**. Ứng viên duy nhất = `SOL-CCM-FO-002.04.v01 Dieu kien chung hop dong tron goi.docx`. **Lead đo (gộp `run` như `DocxRenderer` làm, vì tách run ra chỉ thấy 1 hit — đúng bẫy repo đã tự khai):** bản repo và bản gốc FORM **byte-identical 192.608 B**; `62 w:p · 0 bảng · 40 đoạn có chữ`; đúng **5 ĐIỀU**: 1 Am hiểu · 2 Nội dung công việc · 3 Tiến độ-chất lượng · **4 = "Xử lý khi có tranh chấp" (mục lục gọi là ĐIỀU 16)** · **5 = "Sửa đổi, bổ sung" (mục lục gọi là ĐIỀU 17)**. ⇒ 🔴 **THIẾU 12 điều: 4 Đơn giá · 5 Tiện ích công trường · 6 Phát sinh · 7 Nghiệm thu · 8 TẠM ỨNG & THANH TOÁN · 9 Quyền-nghĩa vụ · 10 Phạt · 11 Chấm dứt · 12 BẢO HÀNH · 13 Bất khả kháng · 14 Thông tin-thông báo · 15 Quy tắc ứng xử.** Bảng tóm tắt PHẦN 1 lại trỏ sang ĐIỀU 2/3/8/10/12 của PHẦN 2 ⇒ **trỏ vào chỗ trống**. Đây là câu **CHẶN vế (2)**: "toàn bộ cái hợp đồng" hiện **không tồn tại đủ** trong tài liệu đã cấp. + +## Vòng 2 — `/fable-clone review` (`wf_71a58432-f90`) · 4 lane `reviewer` tier-2 + +| lane | verdict | artifact | ghi chú | +|---|---|---|---| +| r1-taxonomy | `PASS-WITH-FINDINGS — 10 finding (3 nặng)` | 22.356 B | A1 GIỮ (2 trụ đỡ HELD) · site thật **17** không phải 13 · W1 bỏ sót trọn tầng chi-tiết per-loại | +| r2-formshape | `PASS-WITH-FINDINGS — 9 finding (6 nặng)` | 26.903 B | **W2⟂W3 tranh cột `DraftData`** · `DraftData` 0 validator/0 RowVersion · **mảnh thứ 4: template rỗng sau tách** | +| r3-prefill | `PASS-WITH-FINDINGS — 9 finding (4 nặng)` | 25.058 B | **chuỗi VAT của spec SAI** · **HĐ tự mâu thuẫn tiền, Dev 2/2 lệch** · **5/8 ô master in trắng** | +| r4-plan | `PASS-WITH-FINDINGS — 12 finding (6 nặng)` | 17.931 B | chạy **LẦN 2** (lần 1 chết chỉ ghi khung 758 B) · bác 2 tiền-đề của spec (S-1/S-2) · 3 việc 0-wave | + +**Tổng 40 finding / 19 nặng.** Vá ghi ở `spec-…§⑤` (APPEND, không sửa đè §①-④ — giữ vết spec-trước ⟂ spec-sau). + +### Sự-cố tầng RETURN vòng 2 (vết thứ 3 cùng phiên) +`r4` lượt 1 mất return **và** chết trước khi đổ ruột ⇒ **file 758 B chỉ có khung**. Đây là ca đầu tiên trong phiên mà **kỷ-luật khung-rỗng KHÔNG cứu được nội dung** — nó cứu được *file*, không cứu được *việc*. Phục hồi = **phóng lại ĐÃ-CẮT-GỌT** (agent lẻ, pin sẵn 3 verdict anh em để khỏi lặp) ⇒ lần 2 ra 12 finding. +🔸 Lượt 2 cũng mất return (`"Updating my diary before returning"`) nhưng **artifact đầy đủ trên đĩa** ⇒ lead thu hoạch bằng đọc đĩa. **3/3 lần garble trong phiên này đều phục hồi trọn** — 2 lần qua đĩa, 1 lần qua resume. + +### Lead tự bắt lỗi của chính mình (ghi làm vết, không giấu) +Spec §①-④ có **4 câu SAI** (bảng `§⑤.a`), trong đó 2 câu **đã lỡ nói với owner** trước khi review chạy xong: +1. *"upload bản cứng xong vẫn sửa được HĐ"* — sai, phase 9 đã khoá sửa từ trước. +2. *"W1 và W2 chạy song song được"* — sai, không cặp wave nào song song thật. +Gốc chung của S-1/S-2: lane invest **đọc bộ lọc của một MÀN HÌNH** (`HardCopiesPage` lọc phase 9 + 4 role) rồi suy ngược ra luật BE — mà **BE rộng hơn FE** (`DrafterUserId == userId` là vế OR đầu). ⇒ **Bài: đọc bộ lọc của một màn hình không bằng đọc câu `Where` của truy vấn.** diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md index 48392217..9548e6f0 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md @@ -145,3 +145,76 @@ Nặng thêm: **bảng tóm tắt PHẦN 1 trỏ sang ĐIỀU 2/3/8/10/12 của 4. **`soffice` trên prod**: lane-2 báo `Test-Path` = True; **máy dev không có** ở đường mặc định ⇒ nhánh IN server-side sẽ chết câm ở local. `Pdf:SofficePath` **0-hit trong mọi `*.json`** = ghost-wire. 5. **`antiword` cắt UTF-8 ở biên wrap dòng** (f02 3 dòng, f03 5 dòng; `U+FFFD` = 0 nên **trông như sạch**, chỉ decode mới lộ) ⇒ 🔴 **CẤM chép thẳng text antiword vào code**; câu chữ chuẩn phải lấy qua Word/LibreOffice. 6. **Số ô THẬT** có thể > 24 — bảng §②.4 là **cận dưới**, chưa gộp ô nằm trong 61/113 dòng bảng. + +--- + +## ⑤ VÁ SAU REVIEW — `/fable-clone review` 4 lane, **40 finding (19 nặng)** + +> Ghi APPEND, **KHÔNG sửa đè** §①-④ ở trên: giữ bản gốc làm vết để đối chiếu spec-trước ⟂ spec-sau. +> Nguồn: `sub-reviewer-1..4.md`. 🔴 **CẤM giao thợ bản §①-④ chưa đọc mục này.** + +### ⑤.a — 4 chỗ spec NÓI SAI (lead tự đo lại, xác nhận) + +| # | Spec §①-④ nói | Sự thật đo được | Neo | +|---|---|---|---| +| S-1 | *"người soạn HĐ không nằm trong 4 role thấy trang bản cứng"* | **SAI** — `DrafterUserId == userId` là **vế OR ĐẦU TIÊN**; 4 role là vế **NỚI THÊM**. ⇒ W5 "nới cửa GĐ4" là việc trên tiền đề sai | `ContractFeatures.cs:347` | +| S-2 | *"upload bản cứng xong vẫn sửa được HĐ"* | **SAI** — bản cứng chỉ tồn tại ở phase 9, mà sửa HĐ **đã khoá từ khi rời `DangSoanThao`** ⇒ acceptance W5 "upload → sửa ⇒ 409" **PASS sẵn, 0-bit** | `ContractFeatures.cs:167-168` | +| S-3 | *"W1 và W2 chạy song song được"* | **SAI** — acceptance W2 không có đường chạy khi chưa có 2 loại (W1); cộng vế tranh `DraftData` ⇒ **không cặp wave nào song song thật** | R4-F06 · R2-F4 | +| S-4 | *"`GiaTri` ← `ThanhTien` = KL × `ChuaVat` ⇒ chưa VAT"* | **SAI** — `ChuaVat` là **cột chết** (UI gửi 0; **12/34 dòng Dev = 0**); `ThanhTien` là số **gõ tay, không khai VAT** ⇒ hỏi owner theo khuôn cũ = rủi ro **cộng VAT 2 lần** | `PeDetailTabs.tsx:2959-2962` | + +🔸 S-1/S-2 cùng một gốc: lane invest đọc **trang FE** (`HardCopiesPage` lọc phase 9 + 4 role) rồi suy ngược ra luật BE, mà **BE rộng hơn FE**. Bài: *đọc bộ lọc của một màn hình không bằng đọc câu `Where` của truy vấn.* + +### ⑤.b — 5 lỗ CHẶN mới (không lỗ nào có wave) + +| # | Lỗ | Neo | +|---|---|---| +| B-1 🔴 | **W2 ⟂ W3 tranh CÙNG cột `DraftData`** — W2 ghi thân HĐ, W3 ghi `pe.PaymentTerms` ⇒ parse nhầm + đè chết. Phải chốt **bao-ngoài + `schemaVersion` TRƯỚC W2** (Dev đang 0 row = thời điểm rẻ nhất) | R2-F4 · `PurchaseEvaluation.cs:29` | +| B-2 🔴 | **HĐ tự mâu thuẫn về tiền**: bảng giá Σ`PeReferenceAmount` ⟂ ô Giá trị Σ`ApprovedAmount`; Dev **2/2 lệch 100tr & 50tr** | R3-F7 · `…FromSigningPlanFeatures.cs:202,223,226-229` | +| B-3 🔴 | **5/8 ô master IN TRẮNG**: TK 83% · người đại diện 83% · chủ đầu tư 93% · địa điểm 93% · MST 25% rỗng. Đổi C4 sang `pe.DiaDiem` (**0% rỗng**) | R3-F5b | +| B-4 🔴 | **Upload/xoá đính kèm: 0 policy · 0 phase-guard · 0 ownership** (chỉ `[Authorize]` trần ở class) ⇒ phương án khoá-mềm của W5 để **bất kỳ ai khoá/mở khoá HĐ người khác** | R4-F02 · `ContractsController.cs:105,:130` | +| B-5 🔴 | **Template rỗng sau khi tách loại**: 2 row seed cứng `ContractType.HopDongThauPhu`; FE+BE lọc template theo type ⇒ tách xong **dropdown Mẫu RỖNG**. Seeder skip-theo-FormCode **không tự sửa** | R2-F7 · `DbInitializer.cs:3833,3861` · `FormFeatures.cs:33` | + +### ⑤.c — 3 việc BẮT BUỘC mà 0 wave nào nhận + +1. **Đưa 2 file mẫu vào hệ**: bản trên đĩa là **`.doc`**, seed khai **`.docx`**, `wwwroot/templates` **không có** ⇒ **convert + chép + `UPDATE IsActive=1`** = 3 việc (R4-F04). +2. **Số hoá 12 điều thiếu** của PHẦN 2 — không wave nào làm, cũng **không có nhánh dự phòng** nếu owner không cấp file (R4-F05). +3. **Sản xuất bản chữ CHUẨN** — §④.5 cấm dùng text `antiword` (cắt UTF-8 ở biên wrap), nhưng **không wave nào sinh ra bản chữ sạch** để dựng TSX (R4-F05). + +### ⑤.d — Acceptance phải viết lại (2/5 của W1 hỏng, W2 hụt 6 lớp ca, W5 0-bit) + +- **W1-A4 PASS VACUOUS**: cùng một hàm ném 409 cho **cả** ca-pin-chéo **lẫn** ca-quên-khai ⇒ xanh ở 2 thế giới. Phải có **ca DƯƠNG** (pin đúng mã loại mới ⇒ 200) + phân biệt **đúng CÂU lỗi**, không chỉ đúng mã HTTP. +- **W1-A5 SAI BY-CONSTRUCTION**: `AwV2DesignerMenuKeyTests` neo cứng `HaveCount(7)`/`HaveCount(17)` ⇒ **không tồn tại đường "thêm 2 loại mà 697 giữ nguyên"**. Viết *"697+k, khai k, cho phép sửa ĐÚNG method nào, cấm xoá/skip"*. +- **W2** hụt 6 lớp ca (ô lặp · bảng N dòng · số/tiền dẫn xuất · ngày 3 mảnh · **xoá về rỗng** · bằng chữ) + **thiếu ca ÂM** (sửa ô header cũ ⇒ `DraftData` KHÔNG được về null). Bỏ tiêu chí "phân biệt bằng mắt" (không đo được). +- **W5-A1 0-bit** (S-2 ở trên) — thay bằng ca thật: **xoá file bản cứng ⇒ KHÔNG mở khoá lại**. +- **W3** thay ngưỡng "≥14 ô" (đặt trên tập chỉ có **13** phần tử) bằng **bảng điểm danh 13 dòng** + cấm bảng-giá-rỗng qua bài. + +### ⑤.e — 3 nhánh CÂM nữa (ngoài `WorkflowPolicy.cs:155` đã biết) + +Lý do sót: spec chỉ grep `case ContractType` ⇒ **bỏ trọn switch-EXPRESSION (`=>`)**. C# có 2 cú pháp switch; grep 1 là đo nửa. +- 🔴 `ContractCodeGenerator.cs:28` `_ => "HĐ"` ⇒ **mã HĐ sai chuẩn RG-001, KHÔNG hồi tố được** (`ContractCodeSequences` đã tăng). +- `ContractDetailsFeatures.cs:41-113` switch **không default** ⇒ bundle RỖNG trả **HTTP 200** (câm). +- `ContractDetailsTab.tsx:583-597` switch không default ⇒ **payload rụng sạch field**. + +### ⑤.f — Đồ thị phụ thuộc ĐÚNG (thay câu "W1 ∥ W2" đã bị bác) + +``` +B-1 chốt lược đồ DraftData ─┐ + ├─→ W2 (khung) ──┐ +W1 (tách 2 loại) ────────────┘ ├─→ W4 (in) ─→ W6 + └─→ W3 (pre-fill)┘ +⑤.c-1 (đưa mẫu vào hệ) ──→ B-5 (vá template type) ──→ W2 +⑤.c-2/3 (12 điều + bản chữ chuẩn) ──→ W2 [CHẶN: chờ owner câu (97)] +B-4 (authz đính kèm) ──→ W5 +``` +⇒ **W1 là wave DUY NHẤT khởi động được ngay**; mọi wave khác có tiền đề. + +### ⑤.g — Bộ câu hỏi owner ĐÃ SỬA + +| Cũ | Phán của review | Mới | +|---|---|---| +| (97) 12 điều thiếu | ✅ giữ — R4 đo lại độc lập, **xác nhận** | giữ nguyên | +| (98) in bằng gì | ❌ **THỪA** — W4 đã chốt Print CSS **trước khi** hỏi | **RÚT** | +| (99) khoá 0-mig/1-mig | ❌ **SAI KHUÔN** — hỏi owner chọn cách cài đặt | → *"bản cứng đã upload có được **xoá/thay** không, và **ai** được làm?"* | +| (100) VAT | ❌ **SAI KHUÔN** (S-4) | → *"giá nhập ở Duyệt NCC là **đã gồm** hay **chưa gồm** VAT?"* | +| (101) đánh số menu | ✅ giữ | giữ nguyên | +| — | 🔴 **QUÊN HỎI** | **(102)** *"**ai** được bấm upload bản cứng?"* — quyết cả authz (B-4) lẫn cửa GĐ4 | diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md index d4f02bf9..9030646b 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md @@ -14,28 +14,165 @@ ## 1. Verify 12 điều thiếu -(đang đo) +**Phương pháp:** `python zipfile` đọc `word/document.xml`, gộp mọi `` trong từng `` (script `scratchpad/dieu_scan.py` · `hunt12.py`); `.doc` dùng `antiword -m UTF-8.txt -w 0`. + +🔴 **THƯỚC HỎNG lần 1 — phải khai:** `antiword` KHÔNG có `-m UTF-8.txt` trả text mà **control-dương `'hợp đồng'` = 0 hit** trên cả 3 file `.doc` ⇒ output không phải UTF-8, mọi số đọc từ đó là RÁC. Thêm `-m UTF-8.txt` ⇒ control lên **56 hit** ở `002.02`. (Bài: 0-hit trên file 516 KB mà không có control-dương thì đọc thành "file không chứa gì".) + +### 1.1 — Claim §②.3 ĐỨNG (verify độc lập, 2 nơi) + +| Đo | Kết quả | +|---|---| +| `002.04` ở `wwwroot/templates` vs `FORM/` | **sha256[:16] = `d83fd7ad832b0ba3` cả hai** · 192.608 B ⇒ byte-identical, khớp spec | +| `002.04` thân | `w:p=62` · nonempty=40 · ``=0 · **đúng 5 heading** `ĐIỀU 1..5` | +| 5 điều đó | 1 AM HIỂU TÀI LIỆU · 2 NỘI DUNG CÔNG VIỆC · 3 TIẾN ĐỘ-CHẤT LƯỢNG · **4 XỬ LÝ KHI CÓ TRANH CHẤP** · **5 SỬA ĐỔI, BỔ SUNG HỢP ĐỒNG** | +| Mục lục `002.02` (`L0042-L0058`) | **ĐIỀU 1→17**, trong đó `16 = XỬ LÝ KHI CÓ TRANH CHẤP`, `17 = SỬA ĐỔI, BỔ SUNG` | +| Mục lục `002.03` (`L0041-L0057`) | **giống hệt 1→17** | + +⇒ Số học khớp: 17 − 5 = **12 điều thiếu**, và ánh xạ đổi-số (16→4, 17→5) đúng như spec mô tả. **Claim §②.3 HELD.** + +### 1.2 — Truy 12 điều ở file khác: KHÔNG CÓ (0-hit có control-dương) + +Quét **7 file** `FORM/` + **4 file** `wwwroot/templates/`, khớp **nguyên văn tiêu đề** 12 điều: + +| File | 12 tiêu đề có mặt | Control-dương (3 tiêu đề thân `002.04`) | +|---|---|---| +| `002.02` `.doc` | 12/12 — **nhưng chỉ trong khối MỤC LỤC `L0042-58`** | c1·c2·c3 ✓ | +| `002.03` `.doc` | 12/12 — **cũng chỉ mục lục** | c1·c2·c3 ✓ | +| `002.04` | **2/12** (`BẢO HÀNH`·`BẤT KHẢ KHÁNG`) — khớp thân-văn rời, KHÔNG phải heading | c1·c2·c3 ✓ | +| `002.05` Giao khoán | 3/12 (`HỦY BỎ HĐ`·`BẢO HÀNH`·`BẤT KHẢ KHÁNG`) — HĐ **loại khác**, thân riêng | c2·c3 ✓ | +| `002.06` Mua bán `.doc` | 1/12 | c3 ✓ | +| `002.01`, `RG-001` | **0/12** | **0 control** ⇒ thước còn sống, file thật sự không liên quan | + +Phép kiểm quyết định: `'TẠM ỨNG VÀ THANH TOÁN'` trong `002.02` = **đúng 1 lần** (dòng mục lục `L0049`) ⇒ **không có thân ĐIỀU 8**. Đối chứng: `'ĐỊNH NGHĨA VÀ DIỄN GIẢI'` = **3 lần** — `L0035` mục lục + `L0130` **heading thân** + 1 lần trong câu ⇒ điều CÓ thân thì đếm ≥2. + +⇒ **12 điều KHÔNG nằm ở bất kỳ file nào trong `FORM/` hay `wwwroot/templates/`.** §②.3 là CHẶN thật, không phải "chưa tìm kỹ". + +### 1.3 — 🔴 PHÁT HIỆN MỚI spec chưa có: số hiệu ĐIỀU là AUTO-NUMBER, không nằm trong text + +- `L0130` thân `002.02` in ra **`ĐỊNH NGHĨA VÀ DIỄN GIẢI` KHÔNG kèm chữ "ĐIỀU 2"** ⇒ số điều ở **thân** 2 file `.doc` do Word tự sinh (list numbering), **không có trong luồng text**. +- `002.05`: `` = **168** · `pStyle="Heading*"` = **102**, mà `'ĐIỀU '` literal chỉ **1 hit** ⇒ cùng bệnh. +- Đối chứng: `002.04` có ``=33 · `pStyle Heading`=**0**, và `ĐIỀU 1..5` **là chữ thật** ⇒ hai file cùng họ dùng **hai cơ chế đánh số khác nhau**. + +**Hệ quả cho kế hoạch:** W2 phải chép tay mục lục + tiêu đề điều, nhưng §④.5 đã cấm chép text `antiword`; đồng thời **mọi phép trích `` sẽ mất trắng số hiệu**. ⇒ Không có nguồn chữ nào đáng tin trên đĩa, mà **không wave nào nhận việc sản xuất bản chữ chuẩn** (xem F-03). + +⚠️ Vết cần Word xác nhận (không tự phán): dòng mục lục **ĐIỀU 14** ở CẢ 2 file đọc ra `"THÔNG TIN VÀ THÔNG BÁOXỬ LÝ KHI CÓ TRANH CHẤP"` — dính 2 tiêu đề, không dấu ngăn. Có thể là lỗi copy-paste trong form gốc, cũng có thể là `antiword` gộp 2 đoạn cùng ô. Ai chép mục lục vào HTML phải mở Word đọc lại ô này. ## 2. Wave thiếu/chồng -(chưa) +### 2.1 — 🔴 Nạp 2 file template: KHÔNG wave nào nhận, và nó KHÔNG chỉ là "thả file" (F-04) + +`DbInitializer.cs:3833-3838` khai tên file cho `002.02` / `002.03` **đuôi `.docx`**, mô tả tự ghi *"(cần convert .doc → .docx qua Word COM)"*. Đĩa thật: + +``` +FORM/ SOL-CCM-FO-002.02.v01 ... .doc 516.608 B (ls -la) +FORM/ SOL-CCM-FO-002.03.v01 ... .doc 591.360 B +wwwroot/templates/ → 002.02/002.03 KHÔNG có mặt (4 file: 002.01, 002.04, 002.05, 002.07, RG-001) +``` + +⇒ việc thật là **3 việc**, spec chỉ nhắc 1 nửa của việc thứ 3, và **0 wave nhận cả ba**: +1. **CONVERT `.doc → .docx`** — chất lượng chưa ai chạy thử (chính §④.3 khai); nếu convert hỏng bảng thì mọi thứ dưới nó hỏng theo. +2. Chép 2 file vào `wwwroot/templates/`. +3. `UPDATE ContractTemplates SET IsActive=1` — vì `DbInitializer.cs:3861 if (existingCodes.Contains(formCode)) continue` chặn mọi lượt boot sau. + +*(Khác ca R2 đã bắt: R2 nói 2 row seed cứng `ContractType.HopDongThauPhu`; đây là ca **file nguồn sai đuôi + không tồn tại**.)* + +### 2.2 — Wave số-hoá 12 ĐIỀU: không tồn tại (F-05) + +W0 chỉ "hỏi owner". Không có nhánh dự phòng cho đáp án *"12 điều nằm ở file anh chưa gửi"* hoặc *"chưa có, tự soạn"*. Khối lượng ước theo số đo: `002.04` dùng **40 đoạn không rỗng cho 5 điều**; 12 điều còn lại (gồm ĐIỀU 8 Tạm ứng-thanh toán và ĐIỀU 10 Phạt — 2 điều dài nhất) ⇒ khối chữ **≳2-3×** cái đang có. W2 chỉ dành cho nó **1 gạch đầu dòng** ("điều khoản chung") trong danh sách 8 mục. + +### 2.3 — Wave "sản xuất bản chữ chuẩn": không tồn tại (F-05b, nối §1.3) + +§④.5 **CẤM** chép text `antiword` vào code; `` thì mất trắng số hiệu điều (auto-number). Vậy chữ đưa vào HTML lấy từ đâu, ai kiểm? Không wave nào nhận, không acceptance nào đo. Đây là đầu vào của W2 — thiếu nó thì W2 không có gì để gõ. ## 3. Phụ thuộc -(chưa) +Vẽ lại từ chính nội dung checklist (không theo câu tuyên bố ở W0): + +``` +W0 (owner) ──┬──► W3 (prefill) ──► W2 (document-view) ──► W4 (in) ──► W6 + └──► W5 (khoá) ──────────────────────────────────────► W6 +W1 (tách loại) ─────────────────► W2 (acceptance) ─────────────────────► W6 +[wave THIẾU: convert+nạp template] ──► W4 (nếu đường in dùng docx) +``` + +- **Spec sai ở câu "W1/W2 chạy song song được".** W2 phải "gỡ `draftData: null` 4/4 site" và ghi `DraftData`; W3 cũng ghi `DraftData` (vá regression `pe.PaymentTerms`) — R2/R3 đã bắt vế tranh-cột. Vế **tôi bổ sung**: acceptance của W2 (*"nhập → Lưu → F5 → đủ ô còn nguyên"*) **không có đường chạy** nếu 2 loại HĐ mới chưa có route/menu — người kiểm không biết mở màn nào. ⇒ W1 là tiền-đề **của acceptance** W2, dù không phải tiền-đề của việc gõ component. +- ⇒ **không cặp wave nào song song thật.** Chuỗi đúng: `W1 → W3 → W2 → W4 → W6`, nhánh `W5` rẽ sau W0. ## 4. Khoá chết vế 4 -(chưa) +### 4.1 — 🔴 Tiền đề của spec SAI: người soạn CÓ thấy trang bản cứng (F-01) + +`ContractFeatures.cs:347-354` là chuỗi **OR 4 vế**, vế **ĐẦU TIÊN** là `x.c.DrafterUserId == userId`: + +```csharp +q = q.Where(x => x.c.DrafterUserId == userId // ← người soạn, KHÔNG lệ thuộc phase + || eligiblePhases.Contains(x.c.Phase) + || (x.c.Phase != DangSoanThao && myWorkflowIds.Contains(x.c.ApprovalWorkflowId)) + || (isHardCopyActor && x.c.Phase == ContractPhase.DaPhatHanh)); // ← 4 role, vế THỨ TƯ +``` + +`HardCopyActorRoles` (`:427-428`, 4 role) là vế **nới thêm** cho người *không* có chân nào, **không phải bộ lọc thu hẹp**. Thêm nữa: `fe-user/src/App.tsx` có **`PermissionGuard` = 0 lần** (`grep -c`), route `/hard-copies` (`:69`) trần ⇒ mọi user đăng nhập mở được trang. + +⇒ Gạch đầu dòng W5 *"Nới cửa GĐ4 nếu owner muốn người soạn thấy"* là **việc trên tiền đề sai** — cửa đã mở sẵn cho drafter. + +### 4.2 — 🔴 Khoá chết THẬT nằm chỗ khác: upload không có cửa nào (F-02) + +| Bề mặt | Đo | Kết quả | +|---|---|---| +| `POST /contracts/{id}/attachments` | `ContractsController.cs:105-121` | **0 `[Authorize(Policy=…)]`** — chỉ `[Authorize]` trần ở class `:13` | +| Handler | `ContractAttachmentFeatures.cs:62-84` | **0 phase-guard · 0 kiểm người soạn/vai · 0 kiểm `Purpose`** — chỉ `FirstOrDefault` HĐ rồi ghi | +| `DELETE …/attachments/{attId}` | `ContractsController.cs:130` + `:143 .Remove(att)` | **0 policy · HARD delete** | + +⇒ nếu W5 chọn **(0-mig) khoá theo `HasSealedCopy`** thì **bất kỳ user đăng nhập nào** biết `contractId` cũng: (a) upload `purpose=SealedCopy` ⇒ **khoá HĐ của người khác**; (b) `DELETE` ⇒ **mở khoá lại**. Owner nói "khoá" = mốc 1 chiều; máy hiện cho 2 chiều **và cho người lạ cầm công tắc**. Vế 4 bắt buộc kèm authz — spec **0 dòng** về chuyện này. + +### 4.3 — 🔴 Acceptance W5 là 0-bit (F-03) + +W5 đo: *"upload bản cứng → sửa HĐ ⇒ 409"*. Nhưng `ContractFeatures.cs:167-168` đã ném 409 cho **mọi** phase ≠ `DangSoanThao`, còn trang bản cứng chỉ nạp phase **9** (`HardCopiesPage.tsx:81`) ⇒ **409 xảy ra dù W5 không viết dòng nào**. Phép đo XANH cả khi tính năng vắng mặt. Thước đúng phải nhắm bề mặt **còn mở** ở phase 9 = tập đính kèm (xoá `SealedCopy` sau khi seal) — đúng cái §②.5 tự nhận là "cờ 2 chiều". + +### 4.4 — Ba nghi ngờ tôi ĐÃ ĐO và bác bỏ (khai để lượt sau khỏi đo lại) + +| Nghi | Đo | Kết luận | +|---|---|---| +| HĐ không bao giờ tới phase 9 | `ContractWorkflowService.cs:576 FinalizeToDaPhatHanhAsync` + `:241` + `ContractPhase.cs` comment | **Tới được** — cấp cuối duyệt ⇒ `DaPhatHanh` (terminal) | +| 2 quy trình `HD-*` mới sẽ không seed trên prod đã có data | `DbInitializer.cs:789 if (await db.ApprovalWorkflows.AnyAsync(w => w.Code == code)) continue` + `:801-802 IsActive/IsUserSelectable = true` | **Idempotent PER-CODE ⇒ +2 member là seed thêm 2**, không chặn | +| In sớm thì chưa có số HĐ | `ContractFeatures.cs:122` · `CreateContractFromSigningPlanFeatures.cs:243` | **Mã sinh ngay lúc TẠO** ở cả 2 đường ⇒ không chặn | ## 5. Rủi ro đánh giá thấp -(chưa) +- **④.2 "prod có HĐ Thầu phụ sống không" — xếp nhầm hạng: đây là TIỀN-ĐỀ của W1, không phải "chưa đo" chung chung** (F-07). Row `Type=1` đang sống sẽ **không thuộc** 2 menu mới sau khi tách ⇒ phải quyết "giữ nguyên hay backfill" TRƯỚC khi seed menu, không phải sau. Rẻ: 1 câu `sqlcmd`, **không cần owner**. +- **④ BỎ SÓT hẳn: số hiệu ĐIỀU là auto-number, vô hình với mọi phép trích chữ** (F-08) — chứng ở §1.3. Ảnh hưởng cả W2 (gõ tay) lẫn nhánh `DocxRenderer` (§②.5). +- **④ BỎ SÓT: authz đính kèm** (F-02) — không mục nào trong 6 mục nói tới, mà nó là bản lề của vế 4. +- **④.5 (antiword cắt UTF-8) xếp ĐÚNG hạng nhưng thiếu một vế:** lượt này đo được thêm — **thiếu `-m UTF-8.txt` thì output KHÔNG phải UTF-8 và `U+FFFD` = 0** ⇒ "trông như sạch" theo đúng nghĩa đen, mọi census chạy trên nó ra 0-hit im lặng. ## 6. Câu hỏi owner -(chưa) +| Câu | Chấm | Lý do (đo được) | +|---|---|---| +| (97) 12 điều thiếu | **ĐÚNG, giữ** | §1 xác nhận 0-hit toàn bộ 11 file có control-dương ⇒ chỉ owner mới có nguồn | +| (98) in bằng gì | **THỪA / tự mâu thuẫn** (F-09) | §③ W4 **đã chốt sẵn** "Print CSS `@media print`", §②.5 đã đo `soffice` ghost-wire ⇒ lead tự quyết rồi mới hỏi. Nếu vẫn hỏi thì hỏi cái owner mới trả lời được: *"in giấy A4 ký tay, hay xuất PDF gửi NCC ký số?"* | +| (99) 0-mig hay 1-mig | **SAI KHUÔN** (F-10) | Hỏi owner chọn *cách cài đặt* — owner không có dữ kiện để chọn. Khuôn đúng: *"sau khi upload bản cứng, có được XOÁ / THAY file đó không, và ai được làm?"* — trả lời đó mới suy ra 0-mig hay 1-mig | +| (100) VAT | (R3 đã chấm — không lặp) | | +| (101) đánh số menu | Được | | +| **THIẾU** | 🔴 **QUÊN HỎI: "ai được bấm upload bản cứng?"** (F-11) | Owner mô tả *"upload → khóa"* nhưng chưa ai hỏi **actor**. F-02 chứng đây không phải chi tiết nhỏ: hiện tại là **bất kỳ ai đăng nhập**. Câu này quyết cả authz (W5) lẫn việc có nới cửa GĐ4 hay không — mà lại **không nằm trong 5 câu đã gửi** | ## 7. VERDICT + finding -(chưa) +**`R4-PLAN: PASS-WITH-FINDINGS — 12 finding (6 nặng)`** +Kế hoạch đúng trục và §②.3 đứng vững khi đo lại độc lập; nhưng **vế 4 dựa trên một tiền đề sai**, acceptance của nó **0-bit**, và **3 việc bắt buộc không wave nào nhận**. + +| # | Mức | Neo | Nội dung | +|---|---|---|---| +| F-01 | NẶNG | `ContractFeatures.cs:347` · `fe-user/src/App.tsx:69` (`PermissionGuard`=0) | Spec §②.5 nói người soạn không thấy trang bản cứng — SAI: `DrafterUserId == userId` là vế OR **đầu tiên**; 4 role là vế **nới thêm**. W5 "nới cửa GĐ4" = việc trên tiền đề sai | +| F-02 | NẶNG | `ContractsController.cs:105`, `:130` · `ContractAttachmentFeatures.cs:62-84`, `:143` | Upload/xoá đính kèm **0 policy · 0 phase-guard · 0 ownership**. Phương án 0-mig của W5 ⇒ bất kỳ ai cũng khoá/mở khoá HĐ người khác | +| F-03 | NẶNG | spec `③ W5` vs `ContractFeatures.cs:167-168` | Acceptance "upload → sửa HĐ ⇒ 409" **PASS sẵn** trước khi viết code (phase 9 đã khoá sửa) ⇒ 0-bit | +| F-04 | NẶNG | `DbInitializer.cs:3833-3838`, `:3861` · `ls FORM/` | 2 template trên đĩa là **`.doc`**, seed khai **`.docx`**, `wwwroot/templates` **không có** ⇒ cần CONVERT + chép + `UPDATE IsActive=1`: **3 việc, 0 wave** | +| F-05 | NẶNG | spec `③ W0/W2` | 12 điều thiếu (đã xác nhận §1) **không có wave số-hoá**, cũng không có nhánh dự phòng; và **không wave nào sản xuất bản chữ chuẩn** dù §④.5 cấm dùng text antiword | +| F-06 | NẶNG | spec `③ W0` | "W1/W2 song song" sai: acceptance W2 không có đường chạy khi chưa có 2 loại (W1); cộng vế tranh `DraftData` R2/R3 ⇒ **không cặp wave nào song song thật** | +| F-07 | vừa | spec `④.2` | "Prod có HĐ Thầu phụ sống không" là **tiền-đề của W1** (row `Type=1` không thuộc 2 menu mới), đo bằng 1 query — không phải rủi ro "chưa đo" | +| F-08 | vừa | `002.02 L0130` · `002.05 numPr=168/Heading=102` vs `002.04 Heading=0` | §④ bỏ sót: số hiệu ĐIỀU ở thân là **auto-number Word**, vô hình với mọi phép trích ``/antiword | +| F-09 | vừa | spec `③ W4` vs câu (98) | Câu owner (98) **thừa** — W4 đã chốt Print CSS trước khi hỏi | +| F-10 | vừa | câu (99) | **Sai khuôn** — hỏi owner chọn cách cài đặt; khuôn đúng hỏi *"có được xoá/thay file bản cứng không, ai được làm"* | +| F-11 | vừa | F-02 | **Quên hỏi**: *"ai được bấm upload bản cứng?"* — không có trong 5 câu, mà nó quyết authz + cửa GĐ4 | +| F-12 | nhẹ | spec giao việc | Xung đột đường dẫn: khối TASK ghi `sub-reviewer-4.md`, footer RUN-TRACE ghi `sub-reviewer-3.md` | + +**Thước hỏng đã tự bắt:** `antiword` thiếu `-m UTF-8.txt` ⇒ control-dương `'hợp đồng'` = **0** trên cả 3 file `.doc` (sau khi vá: 56). Mọi số `.doc` ở §1 đều đo lại sau khi vá thước.