Files
solution-erp/.claude/workflows/runs/2026-07-29-S160-khkk-dryrun-plan/sub-reviewer-wave-logic-s160.md
2026-07-29 16:54:56 +07:00

24 KiB
Raw Blame History

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 ghi sub-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 ApproveV2Async toàn src/Backend 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 sinh MaKeHoach bằng CodeGen của W1, W3 §③-A neo bảng Approvals/Changelogs của W1 ⇒ thứ tự bắt buộc, đúng.
  • W3→W5 — W5 §②-2a đọc plan 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ào ChoDuyet=10vô hìnhGetEligiblePhases (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ánh isDrafter (: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):

  1. 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); GetEligiblePhases khô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ị.
  2. W5/W6 → (dựng ApprovalWorkflow V2 type=3 ba trạm): E2E :39 hứa "3 trạm HĐ". Đĩa chỉ có seed QT-HD-V2-001 = 1 Bước × 1 Cấp (DbInitializer.cs:239-265, approver duy nhất binh.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.
  3. W8 → W7 (thứ tự nguy hiểm): W8 gắn Contracts.Update cho mọi HttpPost/Put/Delete của ContractsController — bao gồm POST {id}/attachments mà 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 → W3W6[dựng WF V2 type=3 3 trạm]W5W7(+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 :23 ghi W4-defer ⇒ dry-run chạy "comment tự do + cảnh báo mềm, 0 dev". F-18 :184 chỉ 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 đọc DossierItems lú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 testAuthorizePolicyRegressionTests 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 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 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 grepCÓ 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-24phase/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ại ContractWorkflowService.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:2207 bị 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:1008 là "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 W5 Plans.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 KeHoachKyKet vào All buộc cập nhật 2 số canonical — chính mã dặn: MenuKeys.cs:150-151 "Thêm vào All sẽ đổi 2 số canonical Menu keys/Policiesdocs/STATUS.md (Policies = |All| × |Actions|, DERIVED)". Checklist W1 không có dòng cập nhật docs/STATUS.md (B1 drift).
  • F-16 (plan §4.4, xác nhận ĐÚNG): "HĐ ≥ phase 5 không xoá được" — ContractFeatures.cs:632-633 Phase >= DangInKy ✓; đường thoát admin-override ContractWorkflowService.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 ApproveV2AsyncContractFeatures.cs) đã bị đĩa BÁC BỎ: spec đúng, CLAUDE.md lệch.