13 KiB
c1-l2 đủ-vs-spec
Lane 2/3 — review cụm-1 (sub-invest-fable-b2-cum1.md) đối chiếu spec-v2 (spec-4gd-khkk-tong-quat-31-07-2026.md).
Lens: ĐỦ-vs-SPEC (nghĩa vụ nào của spec-v2 KHÔNG được cụm-1 gánh) — mọi claim đo lại trên đĩa.
VERDICT: FAIL (cổng tiền-implement) — 17 finding / 3 HIGH. Nền vững (anchor ~95% chính xác, 0 trôi số canonical); thủng ở acceptance mù: cả 3 HIGH đều thuộc lớp "impl SAI vẫn PASS 100% checkbox". Vá bằng chỉnh acceptance + bổ sweep, KHÔNG cần làm lại invest.
A. Bảng phủ 9 quyết định §② (Q1)
| QĐ | Nghĩa vụ | Cụm-1 gánh ở | Kết |
|---|---|---|---|
| ②.1 OG-1 gộp | schema GỘP-capable | PRE :79 ghi tên; việc THẬT = swap UNIQUE :86 nhưng không dán nhãn OG-1; 0 acceptance riêng |
⚠️ 0-bit gate |
| ②.2 per-nhóm + line hạng mục | cột + UNIQUE + rào + Σ + 16-consumer | :84-87 · :111-112 · :132 · :136-153 (17 dòng) |
✅ (trừ M1, M6) |
| ②.3 8 Code + K2-chủ-cột | seed 8 + validator + "K3 chỉ đọc" | :185-192 · :210 · :130 |
✅ |
| ②.4 vai 2 tầng | "khai limitation trong doc K1" | chỉ 2 chú-thích cột :21,:40 — 0 deliverable doc trong commit K1 :240, 0 acceptance :69-75 |
❌ RƠI |
| ②.5 NĐUQ tham khảo | cột text, seed null | :23 |
✅ |
| ②.6 backfill=1 (+42-leaf→K4a) | default 1 + COUNT==Σ8 | :83 + acceptance :168 |
✅ |
| ②.7 admin ẩn | cụm-1 chỉ cần CHỪA subtree AwV2 | :214 |
✅ (đúng phạm vi) |
| ②.8 nguồn + OG-6 | chặn seed-land | :7 · :241 · :256 = 3 chỗ |
✅ |
| ②.9 freeze | freeze tại finalize + né #81-EXT | K2.b :90-107 |
⚠️ H2 + M4 |
PRE-gate (2 câu hỏi đích danh): OG-6 chặn seed-land K1 = CÓ, 3 chỗ (:7/:241/:256). OG-2 verify-LIVE trước seed K3 = CÓ, 3 chỗ (:176/:229/:243) — và khả thi thật: ApprovalWorkflowsV2Controller.cs:17-21 GET api/approval-workflows-v2 [Authorize] trần, DTO có IsActive (ApprovalWorkflowV2AdminFeatures.cs:176) ⇒ phân biệt được LIVE/archived.
14 vá b1b thuộc cụm-1 (8 mục): #2 ✅ · #3 ✅ · #4 ✅ · #5 ✅BE/❌UI (M1) · #6 ✅ · #7 ✅ · #11 ✅ · #13 ✅.
Findings
HIGH
H1 — leaf ContractCatalog NGOÀI All + "grant qua SeedAdminPermissionsAsync loop" = tự mâu thuẫn ⇒ menu KHÔNG BAO GIỜ hiện.
Cụm-1 :57. Đo: DbInitializer.cs:2058 foreach (var menuKey in MenuKeys.All) ⇒ key ngoài All không sinh Permission row. GetMyMenuTreeQuery.cs build cây THUẦN từ db.Permissions + filter HasAccess = CanRead || child — không có admin-bypass; inherit-root chỉ 4 (Contracts/Workflows/PurchaseEvaluations/PeWorkflows), Catalogs KHÔNG có. ⇒ 0 row ⇒ leaf bị filter, kể cả Admin.
Tiền lệ dẫn SAI: Pe_* sống được CHÍNH VÌ PurchaseEvaluations là inherit-root; HopDongCung thì 0 menu-row. Khuôn THẬT của leaf catalog: MenuKeys.cs:168 Catalogs, CatalogUnits, CatalogMaterials, CatalogServices, CatalogWorkItems — cả 5 đều TRONG All.
Lý do (iv) "0 policy/menu-key mới = 0 drift STATUS row Policies" (:47) = Goodhart: giữ số doc đứng yên bằng cách làm menu chết. Acceptance K1.f :69-75 không có mục nào đo "leaf hiện trong sidebar" ⇒ lỗi ship với 7/7 xanh.
Đề nghị: chọn 1 trong 2 — (a) vào All và KHAI drift Menu keys 55→56 + Policies 220→224 (2 row cùng lúc); (b) grant tường minh kiểu SeedKeHoachKyKetAccessAsync. Thêm acceptance "login Admin thấy leaf ở nhóm Danh mục" + 1 test seed-grant.
H2 — K3.⚠️ liệt 3 việc code, SÓT ≥3 site "ép false" ⇒ tính năng Chương-tick chết ở lần admin lưu lại quy trình đầu tiên, mà mọi acceptance K3 vẫn PASS.
Cụm-1 :181 liệt (a) nhánh service (b) cột runtime (c) gỡ khoá FE :151-152. Sót:
ApprovalWorkflowV2AdminFeatures.cs:380-381— CREATE ép false server-side:AllowApproverSkipToFinal/Finalize = typeEnum != ApprovalWorkflowApplicableType.ContractSigningPlan && l.…(comment:377-379"ép false server-side (FE đã ép ở levelFlagsPayload — đây chặn curl bypass)").:591-594PUT sửa-tại-chỗ: echo lệch →ConflictException"không được bật/tắt cờ …".:605-610dòng MỚI mang cờ →ConflictException.- FE
ApprovalWorkflowsV2Page.tsx:247-258levelFlagsPayload(e, lockEarlyFinalize)⇒allowApproverFinalize: lockEarlyFinalize ? false : …, bật bởi:638 earlyFinalizeLocked = applicableType === TYPE_INT_CONTRACT_SIGNING_PLAN. Gỡ note:151-152mà không gỡ 2 chỗ này ⇒ UI mở nhưng payload vẫn false. Vì sao acceptance mù: K3.d test 2 & 5 đo trên workflow SEED (ghi thẳng DbContext, không qua command) ⇒ xanh. Đây đúng lớp #81-EXT mà chính doc viện dẫn — doc quét kỹPhase =nhưng không quétAllowApproverFinalize. Đề nghị: bổ sweepgrep -rn "AllowApproverFinalize\|AllowApproverSkipToFinal"vào K3, liệt đủ site vào PR + 1 test "tạo version mới qua CreateCommand cho type-10 GIỮ được cờ".
H3 — CreateContractSigningPlanCommand +ApprovalGroup required, nhưng KhkkCreatePage bị dispose "KHÔNG-ĐỔI" ⇒ tạo phiếu vỡ (hoặc feature vô hình); acceptance curl-only không bắt.
Cụm-1 :111 (+ApprovalGroup int, validator InclusiveBetween(1,8)) ⟂ :151 K2.f row 16-17 KhkkCreatePage.tsx:8 = "KHÔNG-ĐỔI đợt này" ⟂ :112 lại đòi sửa KhkkCreatePage ×2 app.
Đo: fe-admin/src/pages/khkk/KhkkCreatePage.tsx:72-77 body = { peId: purchaseEvaluationId, approvalWorkflowId, … } → api.post('/contract-signing-plans', body) — không có approvalGroup. Header :3 khai "File MIRROR SHA256 identical với fe-admin" ⇒ phải sửa cả 2 app. Không sửa: approvalGroup=0 → 400 mọi lần tạo. Nếu impl né bằng default=1: mọi phiếu FE tạo ra đều nhóm 1 ⇒ feature vô hình.
K2.h :169 đo bằng curl ⇒ bỏ qua FE hoàn toàn. Đề nghị: 1 dòng acceptance "tạo phiếu nhóm 2 từ UI ×2 app".
MEDIUM
M1 — nửa-UI của OG-7 có trong văn, 0 acceptance / 0 test / 0 dòng commit. :112 mô-tả nút "Tạo thêm cho nhóm khác" + existingGroups: int[]; K2.g :157-163 không có test; K2.h :167-172 không có mục; commit-K2 :242 chỉ ghi "DTO + FE types". Impl SAI vẫn PASS: BE mở rào, UI giữ 409 cũ ⇒ đúng thứ owner nhìn thấy thì vắng.
M2 — nhãn đo-lường SAI: "hit IgnoreQueryFilters đầu tiên trong src/Backend — S155 đo 0-hit" (:38). Đo lại: 29 hit trong src/Backend (trừ Migrations) — 18 ApprovalWorkflowV2AdminFeatures.cs · 4 PeSoftDeleteFeatures.cs · 3 ContractSigningPlanFeatures.cs (chính module đang spec) · 3 ContractFeatures.cs · 1 PurchaseEvaluationsController.cs. Số ĐÚNG & chịu lực = 0 hit trong DbInitializer.cs (đo: 0). Đơn thuốc vẫn ĐÚNG (chứng DbInitializer.cs:2970 db.Projects.Select(p => p.Code) thiếu IgnoreQueryFilters — verified). Sửa nhãn hoặc bỏ nhãn.
M3 — K3.⚠️(a) mở lại quyết định owner đã ĐÓNG bằng chữ "PHÁ VỠ" @S155, doc không nhắc một chữ. 5 nơi ghi: ContractSigningPlanWorkflowService.cs:44-46 · ApprovalWorkflowV2AdminFeatures.cs:377 · fe-{admin,user}/src/pages/khkk/KhkkWorkflowPanel.tsx:8 · docs/HANDOFF.md:77 (vẫn nằm ở mục "🔸 Không chặn W1-W2" = CHƯA chốt). Spec v2 §④ OG-3 có câu owner @S164 nghe như tái-cho-phép và lead đã dán "🔄 SỬA @B2c1", nhưng câu đó nói về NĐUQ/ngưỡng CEO; đảo một phán quyết mang chữ "PHÁ VỠ" phải hỏi owner TƯỜNG MINH. → trả lời Q4.
M4 — freeze tuyên "xong" ở cụm-1 nhưng lỗ duy nhất bịt bằng việc cụm-2. :107 tự khai: "xoá mềm line sau DaDuyet làm Σ :711 trôi ÂM THẦM — chặn bằng phase-guard", mà phase-guard nằm ở K4b (cụm-2). Đo: Features.cs:709-711 Σ tính LIVE từ ContractSigningPlanLines ⇒ soft-delete 1 line đổi tổng phiếu ĐÃ DUYỆT. K2.g test 5 chỉ đo ApprovedAmount != null.
M5 — neo chéo gãy: "Mig 71 item 6" không tồn tại. :181 ("gắn vào Mig 71 item 6 (K2.a)") + :242 ("Mig 71 gồm cả item 6") ⟂ K2.a :83-88 chỉ có item 1-5. Impl đọc K2.a rời → Mig 71 thiếu EndedByLevelFinalize → K3 kẹt hoặc phải đẻ Mig 72 trái QUYẾT :242.
M6 — CatalogEntryId bắt-buộc hay không: 3 câu không thể cùng đúng. :87 "line MỚI/SỬA bắt buộc có CatalogEntryId" ⟂ :113 auto-gen create "mồi CatalogEntryId = null" (mọi phiếu tạo sau Mig 71 đều sinh line null) ⟂ :126 "null → CHO QUA". Cách đoán quyết định bẫy NULL-unique (:87) có nổ hay không.
LOW
- L1 —
:105gắn cờ 🔴 "PHẢI XỬ" choWorkflowService.cs:342do đọc nhầm chú thích:39. Đo:ContractSigningPlanWorkflowService.cs:36-42đã KHAI RÕ service có ĐÚNG MỘT nhánh set DaDuyet, và:39là trích nguồn-copyContractWorkflowService.cs:190-193như thứ CỐ Ý không chép.:342nằm trongReturnOrRejectAsync(:310-318,targetPhase ∈ {TraLai,TuChoi}; guard:320-323đòiPhase == ChoDuyet). Phương án (i) "gọiApplyApprovedAmountsngay trước:342" = thuốc SAI. Phương án (ii) test-khoá-hành-vi hợp lệ, bổ choContractSigningPlanApprovalTests.cs:523-533(T3 hiện chỉ khoá write-path Admin-approve). - L2 —
:11đặt fileDomain/Master/ContractCatalogEntry.cs"(cạnh WorkItem)";WorkItem.csthật ởsrc/Backend/SolutionErp.Domain/Master/Catalogs/WorkItem.cs. 2 vế không cùng đúng. - L3 — GroupCode dòng CEO:
:19liệt giá trịCEO⟂:40bảo lấy heading "(A1..C3, 5.2)" ⇒ ra"5.2". Heading nguồn:danh-muc-sp002-transcribe.md:131"## 5.2 — CEO". - L4 —
ContractSigningPlanFeatures.cs:32tự khai luật "positional record, field append-CUỐI (đừng chèn giữa — vỡ FE mapping)"; K2.e:131liệt 3 DTO cần +field nhưng không nói vị trí. - L5 —
AddColumn … defaultValue: 1(:83) để lại DEFAULT constraint vĩnh viễn ⇒ insert quên setApprovalGroupIM LẶNG thành nhóm 1 thay vì fail (cùng gốc M6/H3). - L6 — đếm QUYẾT: lead hỏi "3 QUYẾT của lane", đo được 5 (
:44+:47policy ·:84loose-Guid ·:90+:96freeze-reuse ·:210convention-parse ·:242gộp-Mig-71). - L7 —
:86trỏ "vùng:24-26" để lấy tên index EF-sinh; index thật ởContractSigningPlanLineConfiguration.cs:22-24. - L8 — acceptance
:167(SUM(PeReferenceAmount)trước == sau migrate) gần 0-bit: Mig 71 không chạm cột tiền ⇒ không impl-sai nào làm nó TRƯỢT. Răng thật ở test:163(mồi 0 khi tách dòng) — nên nêu vế đó.
Kháng được soi (đo lại, ĐÚNG)
- P1 — bảng #81-EXT
:100-105:grep -rnE "Phase\s*=[^=]"→ đúng 4 site (Features.cs:373·WorkflowService.cs:174/:278/:342); "DbInitializer 0 site KHKK" đo được 0. Cảnh báo "đừng grep\.Phase" xác nhận đúng. - P2 — 13/13 email+tên K3.a khớp
DbInitializer.cs:1613-1632từng dòng; bẫylong.nguyen@(HRA:1629) có thật; ô ⚠️ Trưởng/Trình có thật (:1625Lê Tự Đăng Trình ⟂:1626Lê Trần Đăng Trường). - P3 — "KHKK không đọc bảng NS live": đo lại chỉ 1 hit source (
Features.cs:162nhãn DTO) ⇒ lý do bỏ 11 cột PE Mig 67 đứng vững. - P4 — khuôn authz catalog:
CatalogsController.cs:13-14+ đúng 12[Authorize(Roles = "Admin")]+ policy-per-action 0 hit. Số khớp tuyệt đối. - P5 — 86 & phân bố 5/10/5/23/14/15/13/1 tự-kiểm từ transcribe: A1=5 · A2 1+A3 9=10 · A4=5 · B1=23 · B2=14 · B3=15 · C1 6+C2 3+C3 4=13 · CEO=1 ⇒ Σ=86 ✓; EN có mặt 86/86; 8 cột map đủ (NduqNote seed null, khớp ⚠️3).
- P6 — 0 trôi số canonical: STATUS
Tests 590(:472) ·SQL tables 96(:466) ·Migrations 69(:465) ⇒ "Mig 70/71" · "96→97" · "baseline ~590 +17" đều đúng. - P7 — OG-2 verify-LIVE khả thi đúng như mô tả (route +
IsActiveđều có). - P8 — anchor
Features.cs:34 · :342-347 · :350 · :389 · :458-462 · :570-574 · :622-625 · :711 · :1201-1202 vàLine.cs:15/:17-20/:23/:25·LineConfig:22-26/:29·Layout.tsx:166-172— đo lại khớp hết.
Verdict
FAIL (cổng tiền-implement) — 17 finding: 3 HIGH · 6 MEDIUM · 8 LOW, 8 điểm kháng-soi. Chặn: H1 (menu chết), H2 (feature owner-yêu-cầu chết im lặng), H3 (tạo phiếu vỡ). Cả 3 cùng một lớp: acceptance không có phép đo nào TRƯỢT khi impl sai. Cần owner gật: M3 (đảo phán quyết "PHÁ VỠ" @S155).