24 KiB
sub-reviewer (lane 3) — LĂNG KÍNH: LOGIC WAVE + ACCEPTANCE — S160
Run:
2026-07-29-S160-khkk-dryrun-plan· Vai: reviewer (adversarial, READ-only) · Ngày: 2026-07-29 Đích soi sâu:plan-cha-dry-run-toan-trinh-29-07-2026.md+spec-wave-w1-schema-designer+spec-wave-w3-duyet-3-tram; liếc đối chiếu đồ thị: w2/w5/w6/w7/w8 +sub-investigator-codebase-1.md§F-10. NOTE tên file: TASK lượt-1 chỉ định tên này; boilerplate RUN-TRACE ghisub-reviewer-3.md— ghi theo TASK, khai lệch trong return.
VERDICT (dòng-1): SUA-TRUOC-THI-CONG — 4 HIGH (2 chặn E2E im lặng: W1 grant no-op trên prod · W7 danh sách GĐ4 rỗng với đúng vai; 2 rơi bước: workflow HĐ 3 trạm không ai dựng · b.14-17 rơi khỏi bảng default). W2/W3/W5/W6 = GO-WITH-FIXES. Bộ plan grounding tốt bất thường: 26/28 neo tôi chấm lại trên đĩa là ĐÚNG.
0. Nhật ký đo (ghi TRONG lúc làm — chống #53)
- [t1] scaffold file này.
- [t2] Đọc: plan-cha (92 dòng) · w1 (39) · w3 (37) · w5 (30) · w6 (32) · w2 (35) · w7 (35) · w8 (33) · sub-investigator-codebase-1 (215).
- [t3] Grep
ApproveV2Asynctoànsrc/Backend⇒ CÓContractWorkflowService.cs:217. Nghi-vấn "W3 copy sai file" (CLAUDE.md §S158 ghi ApproveV2Async ởContractFeatures.cs): BÁC BỎ — CLAUDE.md mới là chỗ mô-tả lệch, spec đúng. - [t4-t8] Chấm 28 neo trên đĩa (bảng §6). Bắt 4 HIGH + 5 MED + 5 LOW.
- [t9] Viết file này TRƯỚC khi return.
1. Đồ thị phụ thuộc (plan-cha §2 :30-39)
Cạnh ĐÚNG (verify được):
W1→W2→W3— W2 §②-2 sinhMaKeHoachbằng CodeGen của W1, W3 §③-A neo bảngApprovals/Changelogscủa W1 ⇒ thứ tự bắt buộc, đúng.W3→W5— W5 §②-2a đọcplan Phase==DaDuyet; trạng thái đó chỉ tồn tại sau W3. Đúng.W6 trước W5(về VẬN HÀNH) = ĐÚNG và có căn cứ đĩa: không có W6 thì HĐ pin V2 vàoChoDuyet=10là vô hình —GetEligiblePhases(ContractFeatures.cs:334-351) 0 entry cho ChoDuyet lẫn DaPhatHanh,PhaseActorRoles(:421-430) 100% phase legacy. Đồng thời W5 KHÔNG bị W6 chặn về mặt acceptance: người trình là Drafter tự tạo ⇒ qua guard:73-78, vàGET /{id}qua được nhờ nhánhisDrafter(:485-489). Tức plan gọi đây là ràng buộc vận hành chứ không phải biên dịch — mô tả CHÍNH XÁC.W7 build song song, VERIFY sau W5+W6— đúng về nguyên tắc, nhưng thiếu 2 cạnh (dưới).
CẠNH THIẾU (F-01, F-02, F-03 — chi tiết §7):
W7 → (view-guard cho vai KHÔNG-approver): W7 liệt HĐphase=9. Bộ lọc IDOR ởContractFeatures.cs:302=DrafterUserId==userId || eligiblePhases.Contains(Phase);GetEligiblePhaseskhông có DaPhatHanh(9) ⇒ HRA (đóng dấu b.20) và PB/BCH (phát hành b.21) thấy danh sách rỗng,GET /{id}403. W6 chỉ mở cho approver V2 của chính HĐ đó (W6 §②-3) — HRA/BCH thường KHÔNG nằm trong workflow ⇒ không wave nào cấp. Cạnh này KHÔNG có trong đồ thị.W5/W6 → (dựng ApprovalWorkflow V2 type=3 ba trạm): E2E:39hứa "3 trạm HĐ". Đĩa chỉ có seedQT-HD-V2-001= 1 Bước × 1 Cấp (DbInitializer.cs:239-265, approver duy nhấtbinh.le@CCM), và seeder tự tắt nếu đã tồn tại workflow type-3 (:226-228). Không wave nào, không mục nào của §4 "chiến lược data" giao việc dựng workflow HĐ 3 trạm PMH/PRO→CCM→CEO. Đây là cạnh chuẩn-bị-dữ-liệu bắt buộc.W8 → W7(thứ tự nguy hiểm): W8 gắnContracts.Updatecho mọi HttpPost/Put/Delete củaContractsController— bao gồmPOST {id}/attachmentsmà W7 dùng để upload 3 purpose. Danh sách role được grant của W8 (§②-3) = Drafter/ProjectManager/DeptManager/Procurement/CostControl/Director/Admin — KHÔNG có HrAdmin (HRA), chính là người làm b.20 đóng dấu (F-10:123). W8 chạy trước W7-UAT ⇒ HRA 403 khi upload.
Thứ tự đề nghị (giữ tinh thần plan, thêm 2 nút): W1 → W2 → W3 ∥ W6 → [dựng WF V2 type=3 3 trạm] → W5 → W7(+view-guard vai cứng) → W8(+HrAdmin vào grant).
2. Mỗi wave có THẬT deploy-độc-lập không? ("người dùng thấy gì" đứng vững?)
| Wave | Người dùng thấy gì khi đẩy riêng | Đứng vững? |
|---|---|---|
| W1 | 0 thay đổi cho user thường (menu giữ, leaf vẫn /coming-soon); admin dựng được quy trình type-10 |
✅ đứng — nhưng xem F-04 (grant có thể no-op ⇒ "thấy" mà không "gọi" được ở W2) |
| W2 | 6 leaf Khkk_* hết ComingSoon → trang thật, tạo/sửa/xoá phiếu nháp |
✅ đứng |
| W3 | Phiếu trình được, 3 trạm duyệt, chốt giá | ✅ đứng (phụ thuộc W2, khai rõ) |
| W5 | Nút "Tạo Hợp đồng" trên phiếu KHKK DaDuyet | ✅ đứng |
| W6 | Approver V2 thấy + mở + duyệt HĐ đang chờ mình | ✅ đứng — và tôi xác nhận đường tạo HĐ-tay để test W6 trước W5 là KHẢ THI thật: ContractCreatePage.tsx:340 lấy bucket applicableType===3, ContractFeatures.cs:109 nhận ApprovalWorkflowId ⇒ pin V2 được từ UI. (Điểm cộng cho spec.) |
| W7 | 7 leaf Hdc_* thành trang theo dõi bản cứng |
⚠️ KHÔNG đứng vững cho đúng vai — F-01 (list rỗng/403 với HRA, BCH, CCM) + F-05 (filter type không tồn tại ⇒ 7 leaf hiển thị y hệt nhau) |
| W8 | Thu hẹp ai bấm được | ✅ đứng (acceptance prod 201/403 là phép đo thật) — trừ F-03 (thiếu HrAdmin) |
3. E2E tới GĐ4 có ĐỦ qua W1+W2+W3+W6+W5+W7? — đối chiếu bảng b.13→21 (sub-investigator-codebase-1.md:114-124)
| b | Việc (QT) | Wave phủ | Verdict |
|---|---|---|---|
| 13 | Lựa chọn NTP/NCC | GĐ1 PE + W2 (winners→Lines) | phủ gián tiếp; plan không khai ánh xạ — LOW |
| 14 | Soạn thảo HĐ (7 ngày) | W5 tạo HĐ nháp; soạn nội dung = PUT /contracts/{id} sẵn có |
⚠️ RƠI khỏi bảng default (F-06) |
| 15 | Góp ý 6 phòng (7 ngày) | không wave nào; F-19 :202 đề "comment thread" |
⚠️ RƠI (F-06) |
| 16 | Đàm phán (7 ngày) | không có máy | ⚠️ RƠI (F-06) |
| 17 | In HĐ + số HĐ + ký nháy + cover khối ĐỀ XUẤT | số HĐ: default #10 ✓ (verify ContractFeatures.cs:113-118 gen tại create — ĐÚNG); ký nháy + cover: chỉ được HIỂN THỊ ở W7, KHÔNG có đường UPLOAD nào trong GĐ3 |
⚠️ RƠI một nửa (F-07) |
| 18 | CCM kiểm tra + ký nháy + cover khối CCM | trạm V2 — nhưng workflow 3 trạm chưa ai dựng (F-02) | ⚠️ phụ thuộc F-02 |
| 19 | BOD/NĐUQ ký duyệt | W7 (upload ScannedSigned) |
✅ — nhưng TRÙNG trạm CEO của V2 (F-08 semantics) |
| 20 | Đóng dấu HRA/ISO | W7 khai gộp vào b.21 (spec-w7:13) |
✅-with-note (khai thật, chấp nhận) — nhưng F-03 chặn HRA upload nếu W8 chạy trước |
| 21 | Phát hành + lưu (scan, 01 bản gốc CCM, Filing) | W7 (SealedCopy + note) |
⚠️ scan cover FO-002.01 không có purpose (F-09) |
Kết luận (3): chuỗi wave đủ để máy chạy từ PE→KHKK→HĐ→bản-cứng, nhưng 4 bước nghiệp vụ (b.14/15/16 + nửa b.17) không có ô nào trong bảng default §5 và không wave nào nhận, còn b.18-21 vướng F-02/F-03/F-09. Nói cách khác: E2E hiện đo được ở tầng phase-machine, KHÔNG đo được ở tầng "9 bước ISO".
4. Defer W4/W9 có phá E2E?
- W9 defer: KHÔNG phá ✅ (đúng như F-19
:200). - W4 defer: KHÔNG phá luồng, nhưng nhãn default sai một nửa (F-10): plan §2
:23ghi W4-defer ⇒ dry-run chạy "comment tự do + cảnh báo mềm, 0 dev". F-18:184chỉ chứng minh comment tự do = 0 dev; "cảnh báo mềm khi thiếu căn cứ TvgsDuyet" (default #4, §5:66) không wave nào code — W3 §② không có bước nào đọcDossierItemslúc trình. Vậy hành vi thật = không kiểm gì cả, không phải "cảnh báo mềm". Default đang tự khen là có lưới trong khi lưới rỗng. - Hệ quả kèm: luật (c) FO-002.01 (cover phải lưu kèm bộ gốc) mà F-18.4
:187đề thành acceptance GĐ4 — W4 defer + F-09 ⇒ không chỗ nào giữ.
5. Acceptance: cái nào 0-bit / không đo được / literal tự-lão-hoá
| # | Chỗ | Loại | Bằng chứng |
|---|---|---|---|
| A1 | W1 :33 "dotnet test → AuthorizePolicyRegressionTests XANH (policy KeHoachKyKet.* hợp lệ — All đã chứa key)" |
0-BIT | Test tại AuthorizePolicyRegressionTests.cs:120-143 chỉ soi ApprovalWorkflowsV2Controller (+ HrmConfigs/Employees ở dưới). Ở W1 chưa có controller nào dùng policy KeHoachKyKet.* (controller ra ở W2) ⇒ test XANH bất kể có thêm key vào All hay không. Phép đo không thể trượt ⇒ 0 bit. |
| A2 | W1 :35 "Admin tạo quy trình type-10 …; xoá 1 Level CHƯA chữ ký → OK" |
0-bit với rủi ro đã nêu | Chính W1 §②-10 cảnh báo 3 site ApprovalWorkflowV2AdminFeatures.cs (:911/:929, :971/:992, :1044/:1056) — cả 3 chỉ chạy khi Level CÓ chữ ký. Ca duy nhất acceptance kiểm là ca KHÔNG chạm 3 site đó. Ở W1 chưa thể có chữ ký type-10 ⇒ rủi ro tự khai chỉ lộ ở W3. |
| A3 | W7 :32 "GET /api/contracts?type=1&phase=9 → 200, mọi item phase==9" |
0-BIT trên trục type |
ListContractsQuery = (Phase, SupplierId, ProjectId) — KHÔNG có Type (ContractFeatures.cs:282-285); controller List :16-24 cũng không nhận type. Query-param lạ bị bỏ im lặng ⇒ assertion "mọi item phase==9" PASS dù filter loại chết. |
| A4 | W1 :37 "sqlcmd đếm Permission rows key KeHoachKyKet = số role đích × đúng cờ" |
không đo được | không có số kỳ vọng, không có tên cờ ⇒ mọi kết quả đều "khớp". So sánh: W8 :30 ("user đã-cấp POST→201; chưa-cấp→403") là phép đo THẬT — cùng bộ spec mà 2 chuẩn khác nhau. |
| A5 | W1 :32 "sys.tables 89 → 96" · :38 "menu KHÔNG đổi (142 giữ)" · W7 :23 "142 giữ" |
literal tự-lão-hoá | plan §7 :82 tự đặt luật "neo TÍNH-CHẤT, CẤM literal" cho test-count nhưng lại literal cho bảng/menu. Rủi ro thấp (1 mig) nhưng nên viết "= số-đo-ngay-trước + 7" / "= số-đo-ngay-trước". |
| A6 | Mã phiếu KHKK/{YYYY}/{Seq:D3} (default #6, W1 §②-9) |
không có acceptance nào | W1 §③ không có dòng nào chạm CodeGen; W2 §③ chỉ "POST → 201". Một quyết-định owner-visible (format mã in trên phiếu) đi qua 2 wave mà 0 phép đo. |
| A7 | W3 :30 "Lines WHERE PlanId=@id AND ApprovedAmount IS NULL = 0" |
✅ TỐT | có AND PlanId=@id (chống bẫy S134 đếm-toàn-bảng) — giữ nguyên. |
| A8 | W3 :28 OR-of-N 4 vế (2 phiếu song sinh + ca ÂM + 1 row + không-đòi-cả-hai) |
✅ TỐT NHẤT bộ | đúng lưới cho rủi ro "copy nhầm ProposalFeatures.cs:427-439 mất OR-of-N âm thầm". |
6. Gate 5-anchor: đủ 5? có thật? (chấm lại 28 neo trên đĩa)
| Wave | Neo | Trên đĩa | Verdict |
|---|---|---|---|
| W1 | ApprovalWorkflow.cs:53-67 slot 10 trống |
enum dừng TravelRequest = 9 (:66), } :67 |
✅ ĐÚNG |
| W1 | MenuKeys.cs:41 const + comment tái dùng |
:41 public const string KeHoachKyKet + comment :37-40 "Wave-1 GĐ2 … phải TÁI DÙNG" |
✅ ĐÚNG |
| W1 | MenuKeys.cs:163+ All chưa có KeHoachKyKet |
All :163-181, không có |
✅ ĐÚNG |
| W1 | AuthorizePolicyRegressionTests.cs:138-139 |
MenuKeys.All.Should().Contain(parts[0], …) đúng 2 dòng đó |
✅ ĐÚNG (nhưng xem A1) |
| W1 | DbInitializer.cs:1779-1786 Khkk_* 7 key |
:1779 Khkk_G1 + :1781-1786 6 leaf |
✅ ĐÚNG |
| W2 | ProposalFeatures.cs:427-439 bẫy flatten · :258-267 guard type · CreateContractFromEvaluationFeatures.cs:53-62 |
(2 cái Proposal không chấm — ngoài ngân sách lượt; cái thứ 3 ✅ :53-62 đúng guard+SUM) |
⚪ 1/3 chấm |
| W2 | DbInitializer.cs:2207 "khu staticMap comment Khkk_*" |
:2207 là comment trong khối grant CanRead (reviewKeys :2202-2208), KHÔNG phải staticMap. staticMap thật ở fe-user/src/components/Layout.tsx:59-62,79-80 |
⚠️ NHÃN SAI (F-11) |
| W3 | ContractWorkflowService.cs:217-394 nguồn copy |
ApproveV2Async :217, kết thúc :394 |
✅ ĐÚNG (kể cả cận trên) |
| W3 | :259-260 OR-of-N · :292-316 UPSERT · (:246 GroupBy, :320-352 skipToFinal) |
:259 allowedUserIds :260 Contains ✓ · :292-316 UPSERT ContractLevelOpinions ✓ · :246 GroupBy ✓ · :320 if(skipToFinal) … :352 ✓ |
✅ 4/4 ĐÚNG (chính xác đến dòng) |
| W3 | ApprovalWorkflow.cs:95 class Level + :116-125 4 return-mode |
class :95 ✓; :116/119/122/125 = 4 cờ ✓ (cảnh báo "class nằm TRONG ApprovalWorkflow.cs, không có file riêng" ĐÚNG) |
✅ ĐÚNG |
| W3 | PurchaseEvaluationWorkflowService.cs:1008 khuôn RULE choke-point — spec mô tả "comment nêu LUẬT + lệnh grep, CẤM số-đếm site" |
:1008 = // RULE: MỌI nhánh set Phase=DaDuyet PHẢI gọi helper này ngay TRƯỚC assignment (**reviewer grep-check 4 site**). — không có lệnh grep và CÓ số-đếm "4 site" |
⚠️ MÔ TẢ NGƯỢC (F-12) |
| W5 | :53-60 guard · :68-71 pin V1 · :88-90 SUM · :145 idempotency-write · ContractWorkflowService.cs:115-116 Conflict |
5/5 khớp từng dòng | ✅ 5/5 |
| W6 | ContractFeatures.cs:334-351 · :421-430 · :488-489 · ContractWorkflowService.cs:73-78 · ContractsController.cs:38-40 |
5/5 khớp (inbox endpoint đúng :38-40) |
✅ 5/5 |
| W7 | ContractAttachment.cs:5-11 enum · DbInitializer.cs:1862-1867 loop 7 leaf · Layout.tsx:63-69 7 dòng Hdc_ · MyContractsPage.tsx:29-46 |
4/4 khớp (Layout :63-69 đúng CHÍNH XÁC 7 dòng) |
✅ 4/4 |
| W7 | ContractsController.cs:16-24 "list params type/phase" |
:16-24 có phase/supplierId/projectId, KHÔNG có type; type chỉ ở /deleted :31 |
❌ SAI (F-05) |
| W8 | ContractsController.cs:13 class-trần · :28-29 tiền lệ policy · MenuKeys.cs:163-181 Contracts∈All (:168) · ContractWorkflowService.cs:49-66 Reject |
4/4 khớp (SuppliersController.cs:67 không chấm) |
✅ 4/4 chấm |
Đếm: 7/7 wave có đủ 5 mục anchor về HÌNH THỨC. Về THỰC CHẤT: 26 đúng / 2 sai-hoặc-nhãn-sai (W7 type, W2 "staticMap") + 1 mô tả ngược (W3 khuôn :1008).
Khiếm khuyết cấu trúc (F-13, LOW): W3 đếm 5 neo nhưng 3 trong số đó lồng nhau (:259-260 ⊂ :292-316 ⊂ :217-394) ⇒ chỉ 3 sự-kiện độc lập; W1 có 2 neo cùng file MenuKeys.cs; W6 3/5 cùng ContractFeatures.cs. "5 anchor" đang đếm KHOẢNG-DÒNG chứ không đếm mệnh-đề độc lập.
7. FLAG (F-01 → F-16) + SEV
HIGH
F-04 (W1) — grant KeHoachKyKet 4-action nhiều khả năng NO-OP trên prod (403 im lặng, gotcha #44 + #84).
Trên đĩa, key này đã có row Permission cho mọi role qua nhánh read-only: DbInitializer.cs:2204-2206 .Concat(new[] { MenuKeys.KeHoachKyKet, MenuKeys.HopDongCung }) (comment: "CanRead-only mọi role … Prod đã INSERT trực tiếp"). Ngay dưới, :2211-2212 cảnh báo nguyên văn: "nhánh grant key-thường là skip-existing ⇒ trên prod row false KHÔNG được nâng ở đây". ⇒ W1 §②-8(a) thêm Create/Update/Delete theo đường seeder thường sẽ bị bỏ qua vì row đã tồn tại; W2/W3 deploy xong thì user dry-run POST → 403 im lặng, và acceptance A4 (đếm ROW, không đếm CỜ) không bắt được.
Acceptance đề nghị: dùng đúng khuôn W8 :30 — sau deploy, user role đích gọi POST /api/contract-signing-plans phải 201 và user không-cấp phải 403; cộng thêm phép đọc CỜ (CanCreate/CanUpdate/CanDelete) chứ không đếm row.
F-01 (W7, kèm W6) — GĐ4 không vận hành được cho đúng vai: danh sách rỗng + 403.
ContractFeatures.cs:302 lọc DrafterUserId==userId || eligiblePhases.Contains(Phase); GetEligiblePhases :342-349 không chứa DaPhatHanh(9) (cũng không chứa ChoDuyet(10)). ⇒ HRA (b.20), PB/BCH + CCM (b.21), BOD (b.19) nếu không phải Drafter/Admin sẽ thấy 0 dòng ở mọi leaf Hdc_* và 403 khi mở chi tiết. W6 §②-3 chỉ mở cho approver V2 của chính HĐ đó, và W6 acceptance chỉ đo inbox + GET /{id} — không wave nào đo LIST. W7 §③ prod-check lại không nói ai gọi (admin gọi thì luôn xanh vì :298).
Acceptance đề nghị: thêm ca "user vai HRA/BCH KHÔNG-admin KHÔNG-drafter gọi GET /api/contracts?phase=9 ≥1 dòng" + ca ÂM giữ 403 cho người ngoài.
F-02 (plan §4 + W5/W6) — không ai dựng ApprovalWorkflow V2 type=3 ba trạm; seed chỉ có 1 trạm.
DbInitializer.cs:226-228 bỏ qua nếu đã có workflow type-3; :239-265 seed QT-HD-V2-001 = 1 Bước (CCM) × 1 Cấp, approver duy nhất binh.le@solutions.com.vn. E2E :39 hứa "3 trạm HĐ"; W6 §① hứa "3 trạm PRO→CCM→CEO". §4 "chiến lược data" chỉ liệt ZZTEST + NCC + PE — thiếu hẳn mục dựng workflow HĐ. Nếu chạy bằng seed: GĐ3 chỉ 1 trạm CCM, b.19 (BOD ký) không có trạm web, và default #1 "CEO ký thật" không thể xảy ra.
Acceptance đề nghị: thêm bước-0 của W5/W6 "admin tạo WF type-3 ≥3 Bước, IsUserSelectable=true, approver = user dry-run thật" + acceptance đếm Steps>=3 trước khi bấm E2E.
F-03 (W8×W7) — grant W8 thiếu HrAdmin ⇒ khoá đúng người làm b.20.
W8 §②-2 gắn policy cho MỌI HttpPost/Put/Delete của ContractsController (bao gồm POST {id}/attachments); §②-3 grant cho Drafter/ProjectManager/DeptManager/Procurement/CostControl/Director/Admin — không có HrAdmin, trong khi b.20 đóng dấu là HRA/ISO (sub-investigator-codebase-1.md:123) và W7 yêu cầu HRA upload bằng chứng. Cùng lớp với F-04 (403 im lặng giết chính dry-run — W8 tự nêu rủi ro này ở §③-C mà lại rơi đúng vào nó).
MEDIUM
F-05 (W7) — type filter không tồn tại ở endpoint list ⇒ 7 leaf hiển thị TRÙNG nhau; anchor sai; acceptance 0-bit.
ContractsController.cs:16-24 + ListContractsQuery :282-285 không có Type. Khuôn W7 chọn (MyContractsPage.tsx:41-46,:60) lách bằng lọc client-side sau khi lấy pageSize:100 ⇒ nếu copy nguyên, trang bản-cứng phân trang SAI (server phân trang trước khi lọc loại) và trần 100. Cần hoặc thêm Type vào query BE (nhỏ, 0-mig) hoặc khai rõ lọc client + bỏ phân trang.
F-06 (plan §5 + W5) — b.14/15/16 rơi khỏi bảng 12 default.
sub-investigator-codebase-1.md:202 có default rõ ("Nháp + comment thread + update draft; muốn trạm thật thì thêm Bước trong workflow, 0 code") nhưng plan-cha KHÔNG chép vào §5 và không wave nào nhận. E2E :39 nhảy thẳng "HĐ per-winner pin V2 → 3 trạm HĐ" ⇒ 21 ngày SLA nghiệp vụ (7+7+7) biến mất khỏi kế hoạch mà không có dòng khai.
F-07 (W7 §① + F-11 lane-1) — ký nháy/cover b.17-18 chỉ có đường ĐỌC, không có đường GHI.
W7 khai "readonly từ GĐ3 … hiển thị từ attachments (InitialSigned mới)" nhưng chính W7 mới là chỗ thêm enum InitialSigned, và panel upload của W7 nằm ở trang GĐ4 (sau DaPhatHanh). ⇒ hoặc GĐ3 không có nút upload ký-nháy (dữ liệu luôn rỗng, khối readonly vô nghĩa), hoặc phải upload ở GĐ4 rồi gọi là "của b.17" (nghịch thời gian). Cần chốt 1 trong 2 và khai.
F-09 (W7) — scan cover FO-002.01 không có purpose để gắn.
AttachmentPurpose = {DraftExport=1, ScannedSigned=2, SealedCopy=3, Other=99} (ContractAttachment.cs:5-11); W7 chỉ thêm InitialSigned=4. §① lại đòi "upload scan cover FO-002.01 (luật (c))" và §③ chỉ đo "3 purpose". ⇒ cover rơi vào Other hoặc rơi hẳn; luật (c) (bắt buộc lưu kèm bộ gốc) không có phép đo nào.
F-10 (plan §2 :23 + §5 #4) — default "cảnh báo mềm" không có chủ.
W4 deferred = 0 dev, W3 §② không có bước kiểm căn cứ ⇒ hành vi thật là không kiểm gì. Nhãn "cảnh báo mềm" đang mô tả một lưới không tồn tại (lớp Goodhart: default trông như đã che rủi ro).
LOW
- F-08 (semantics, cross-wave): terminal V2 của HĐ =
DaPhatHanh=9"Đã duyệt (gen mã + phát hành) — terminal thành công" (ContractPhase.cs:24, set tạiContractWorkflowService.cs:379). Nhưng "phát hành" theo QT là b.21, tức trạng thái hệ thống nói "đã phát hành" trước khi GĐ4 làm b.19/20/21. W7 §① có ý thức ("GĐ4 nằm SAU terminal") nhưng plan-cha không khai mâu thuẫn NHÃN. Rẻ: đổi nhãn hiển thị GĐ3-terminal thành "Đã duyệt HĐ" ở UI HĐ. - F-11 (W2 anchor):
DbInitializer.cs:2207bị gọi là "khu staticMap" — thực tế là khối grant CanRead; site staticMap thật (fe-user Layout.tsx:59-62,79-80+ mirror fe-admin) không nằm trong gate-5 của W2 dù W2 phải sửa đúng chỗ đó. - F-12 (W3): spec mô tả khuôn
PurchaseEvaluationWorkflowService.cs:1008là "LUẬT + lệnh grep, CẤM số-đếm" — đĩa cho thấy ngược: có "(reviewer grep-check 4 site)", không có lệnh grep. Worker copy khuôn sẽ copy đúng cái con-số mà spec đang cấm. - F-13: gate-5-anchor đếm khoảng-dòng lồng nhau (W3:
:259-260⊂:292-316⊂:217-394) ⇒ "5" phóng đại độ phủ; đề nghị đếm mệnh-đề độc lập. - F-14 (W2 vs W5): tên field FK lệch giữa 2 spec — W2
p.PeId(spec-w2:13) vs W5Plans.Where(PurchaseEvaluationId==peId)(spec-w5:13); W1 §②-2 không chốt tên cột. Chốt 1 tên ở W1 để W5 không phải sửa lại. - F-15 (W1): thêm
KeHoachKyKetvàoAllbuộc cập nhật 2 số canonical — chính mã dặn:MenuKeys.cs:150-151"Thêm vàoAllsẽ đổi 2 số canonicalMenu keys/Policiesởdocs/STATUS.md(Policies = |All| × |Actions|, DERIVED)". Checklist W1 không có dòng cập nhậtdocs/STATUS.md(B1 drift). - F-16 (plan §4.4, xác nhận ĐÚNG): "HĐ ≥ phase 5 không xoá được" —
ContractFeatures.cs:632-633Phase >= DangInKy✓; đường thoát admin-overrideContractWorkflowService.cs:191-198✓ tồn tại thật. Lưu ý phụ:ChoDuyet=10/TraLai=98/TuChoi=99đều ≥5 ⇒ mọi phiếu HĐ đã trình đều không xoá được bằng API — plan chỉ nói "≥ phase 5", đúng nhưng dễ đọc nhầm là chỉ HĐ cuối luồng.
8. Điểm ĐỨNG VỮNG trước soi (ghi để không chỉ kể lỗi)
- 26/28 neo chấm lại đúng đến dòng, kể cả các cận trên khó (
:217-394,Layout.tsx:63-69đúng 7 dòng,DbInitializer.cs:1779-1786đúng 7 key). - W3 §③-B (OR-of-N 4 vế + ca ÂM +
AND PlanId=@id) là bộ acceptance mạnh nhất bộ plan — đủ răng để bắt đúng rủi ro tự khai ("copy nhầm Proposal mất OR-of-N âm thầm"). - W6 §③ có ca ÂM tường minh ("user ngoài workflow vẫn 403") — đúng bài chống mở-toang.
- Đường tạo HĐ-tay để test W6 trước W5 là khả thi thật (
ContractCreatePage.tsx:340+ContractFeatures.cs:109) — không phải hứa suông. - Nghi vấn nặng nhất tôi mang vào (W3 copy sai file vì CLAUDE.md nói
ApproveV2AsyncởContractFeatures.cs) đã bị đĩa BÁC BỎ: spec đúng, CLAUDE.md lệch.