19 KiB
REVIEW-1 (Fable, adversarial ĐÚNG-SAI) — S182 khkk-awf-mirror-ncc
Vai:
/fable-real reviewer#1/2 — lens SAI/SÓT/MÂU-THUẪN trên spec DRAFT + 3 invest. Trạng thái: ĐANG ĐỔ RUỘT (skeleton @spawn, Edit liên tục).
VERDICT (điền cuối)
- (pending)
Checklist việc (8 mục từ lệnh)
- 1. W1a deactivate-per-Code — đọc
ApprovalWorkflowV2AdminFeatures.cs:339-343+ grep consumerIsActive - 2. W2b synthetic slice —
ApprovalWorkflowsV2Page.tsx:286-353+ DTOAwTypeSummaryDto:199-213 - 3. W2c lockedCode — PUT đổi Code 409
:539-550+ đường rơi khỏi slice - 4. W1b group-match Submit — phiếu cũ pin lệch
- 5. W1d DELETE 409 — đếm module pin từ ModelSnapshot
- 6. D3 chấm A/B/C — bảng 9-flag, #4 Trả-về-Drafter
- 7. Mâu thuẫn 3 lens
- 8. Sót chung (notification / invalidate keys / optimistic)
GHI NHẬN SAU KHI ĐỌC 4 PIN (trước khi mở code)
- Spec DRAFT 68 dòng, 3 invest đọc đủ. Nghi vấn dựng sẵn để đo:
- (i) W2b: BE
HistorytrongAwTypeSummaryDtochứa TẤT CẢ version hay LOẠI active? Nếu loại →versions.find(v=>v.isActive)chết → active=null giả. PHẢI đọc:199-213. - (ii) W1a P1: scope
w.Code == request.CodeCÓ kèmApplicableType == typeEnumkhông? UNIQUE(Code,Version) là GLOBAL — Code trùng xuyên-type khả thi về schema. - (iii) FE-invest D1 đề xuất scope theo REGEX
^KHKK-N[1-8]$, BE-invest P1 theo typeEnum==10 — 2 công thức KHÁC NHAU (codeQT-KHKK-V2-001rơi vào nhánh nào?). Spec chọn P1 mà không khai lệch lens. - (iv) W1b: rào (v) "code NGOÀI họ → CHO QUA" — extract giữ nguyên lỗ này? Phiếu ChoDuyet đã pin lệch TRƯỚC vá không bị đụng — cần SQL audit prod, spec chưa có.
- (v) W1d: module pin loose-Guid (không FK) → hard-delete THÀNH CÔNG vật lý → dangling pin. Danh sách FK-Restrict của DB-invest chỉ có 3 (PE/Contract/KHKK) — WorkflowApps pins = FK hay loose? PHẢI đếm ModelSnapshot.
- (vi) D3=C: ô #4 Trả-về-Drafter hành-vi = LUÔN-CHO dù cờ tắt → mờ kiểu "chưa áp dụng" là NÓI DỐI CHIỀU NGƯỢC; phải hiển thị forced-true.
- (vii) Acceptance #3 (tạo v02 N1) chạy Ở ĐÂU? Prod = mutate config thật, local = workflow rỗng người. Không có activate-endpoint để rollback IsActive.
- (i) W2b: BE
ĐO TRỰC TIẾP (đợt 1 — AdminFeatures:80-420 + FE page:150-364)
- HELD (i):
AwTypeSummaryDto.History= FULL list mọi version (BE:206-211—versionsdùng cho CẢActivelẫnHistory) ⇒ synthetic slice W2bhistory.filter(code)+.find(isActive)ĐÚNG shape. Ordering: queryOrderByDescending(Version)⇒ slice giữ Version DESC per code — đủ cho render. - HELD: deactivate
:340-343đúng như invest tả:Where(ApplicableType == typeEnum && IsActive). Comment dòng 339 tự khai "only ONE active per type". - Đo thêm:
nextVersion:334-337đã tính theo Code (per-Code sẵn) — chỉ deactivate là per-type ⇒ vá P1 là nhất quán hoá scope với chính hàm. - Đo FE: queryKey
['approval-workflow-v2-overview', selectedTypeInt](:292) — 8 kind nhóm sẽ share key với panel ContractSigningPlan (cùng 10) = cache chung, invalidate:348prefix-match toàn key ⇒ mutation từ panel nhóm tự làm tươi panel type-10 và ngược lại. KHÔNG cần key mới. - Đo FE:
DEFAULT_CODE_BY_TYPE[10]='QT-KHKK-V2-001'(:179) — nút "Tạo quy trình mới" từ panel ContractSigningPlan (không lockedCode) vẫn đề xuất code NGOÀI-họ type-10. Sau P1 (scope theo typeEnum==10 ⇒ per-Code) code lạ này KHÔNG tắt 8 nhóm. Nếu executor lỡ implement theo công-thức FE-invest D1 (regex^KHKK-N[1-8]$) thì code lạ rơi nhánh per-type ⇒ NUKE cả 8 nhóm — 2 công thức KHÁC HÀNH VI, spec phải khoá công thức.
ĐO TRỰC TIẾP (đợt 2 — guards/delete/FK/middleware/scope/fe-user)
- HELD: PUT T2 Code-echo 409 (
:539-541) · CEO-echo (:547-550) · F5/F2-echo (:598-603) · ô Mã FEdisabled={isEdit}(:927) · CEO boxdisabled={isEdit}only (:954— CREATE-mode mở cho MỌI type kể cả 10). - HELD: DELETE handler
:1196-1204= load+Remove+Save, 0 guard; comment:1187-1189tự khai nợ. Middleware chỉ map 5 app-exception (GlobalExceptionMiddleware.cs:32-40),_→ 500. - 🔴 MỚI — pin FK thật: grep
HasForeignKey("ApprovalWorkflowId")snapshot = 4 hit (Steps-Cascade + KHKK/Contract/PE-Restrict). Domain có 8 cột pin (thêm Proposal/Leave/Ot/Vehicle/Travel — ItTicket KHÔNG có) ⇒ 5 module Office pin = LOOSE 0-FK ⇒ DELETE hôm nay với office-pin THÀNH CÔNG vật lý → dangling pin. DB-invest M3 "data KHÔNG mất (DB chặn được)" = SAI cho 5 module này. W1d usage-check là guard DUY NHẤT cho office. - 🔴 MỚI — query filter: PE (
:84) + Contract (:44) + KHKK (:55) đềuHasQueryFilter(!IsDeleted)⇒ đếm-pin naive sẽ BỎ SÓT phiếu xoá-mềm (FK vẫn chặn 547) ⇒ W1d PHẢIIgnoreQueryFilters()cho 3 module này, không thì đúng ca "đã xoá phiếu rồi mà vẫn 500" tồn tại tiếp. - HELD: 8 bảng
*LevelOpinion(8 FKApprovalWorkflowLevelId) khớp claim "CẢ 8". - 🔴 MỚI — W1b unbuildable như viết:
ContractSigningPlanScope=internal static(ContractSigningPlanFeatures.cs:263);InternalsVisibleToCHỈ cóSolutionErp.Infrastructure.Tests(csproj:25). Submit KHÔNG có Application-handler (comment:19— controller gọi thẳng service Infrastructure) ⇒ helper internal KHÔNG gọi được từSubmitAsync. Executor phải chọn: public-guard-class mới (Application) / inline-duplicate / InternalsVisibleTo-Infrastructure. Spec chưa khai. - HELD:
ContractSigningPlanEntityType.Workflow = 4tồn tại + changelog entity đủ cột ⇒ W1c 0-enum-extend. Phase filter {DaDuyet=3, TuChoi=99} đúng tên enum. - HELD: rào (v)
:479-486cho-qua code ngoài-họ LÀ CHỦ ĐÍCH (comment :477-478). UpdateDraft:603-607chỉ EnsureWorkflowTypeAsync. Submit:163-213chỉ check Lines↔group. - 🔴 MỚI — IsActive server-side cho KHKK-create = KHÔNG CÓ: bridge KHKK→HĐ đã vá đòi
IsActive ∧ IsUserSelectable(vá-3,CreateContractFromSigningPlanFeatures.cs:141-147— chính comment nêu "khuôn ContractFeatures thiếu 2 vế ⇒ forge POST pin được quy trình retired"); Create/UpdateDraft phiếu KHKK thì CHỈ check type ⇒ pin bản archived = 200. fe-user KhkkCreatePage:104filterisUserSelectableKHÔNG lọcisActive,:114find(code)ăn theo ordering Version-DESC. Hệ quả SAU W1a: (a) tạo v02 xong, v01 vẫn selectable=true (POST không đụng cờ này) — auto-pin trúng v02 chỉ NHỜ ordering; (b) "Bỏ ghim v02" ⇒ rơi về v01 archived im lặng, KHÔNG chặn tạo như spec mô tả răng nút Ghim. - Grep IsActive consumer (W1a regression): Office features 0 lọc IsActive trên ApprovalWorkflows (mọi hit IsActive = Users/MeetingRooms/OtPolicies/Catalog) · V1
WorkflowDefinitions.IsActivekhông liên quan · consumer THẬT của IsActive-V2: OverviewActive(display) + bridge type-3 vá-3 + FE dropdowns (PE/Contract create lọc client-side). KHÔNG AI resolve "active-per-type-10" server-side ⇒ P1 đổi scope type-10 an toàn, P2 không cần.
ĐO TRỰC TIẾP (đợt 3 — service/seeder/status/FE-designer)
- HELD: service KHKK comment
:36-65tự khai (c) 4 return-mode CỐ Ý chưa đọc + "mở thêm mode là đổi hành vi duyệt, thuộc quyền lead" · finalize:276-289= #8 sống thật · Submit là entry-point controller-gọi-thẳng (:60-62— xác nhận KHÔNG có MediatR handler cho Submit). - HELD: seeder
DbInitializer.cs:539-625đúng như invest (idempotent per-Code:610, roster email prod,IsActive=true + IsUserSelectable=true:622-623, thiếu user → bỏ slot + warn). - HELD (control): grep
AwV2_trongSolutionErp.Api= 0 hit — khớp claim "32 policy 0-consumer BE". - Đo FE Designer: "Tạo quy trình mới" (
TypePanel:413) setcloneFrom = type.active⇒ ở panel nhóm (synthetic slice) code state tự prefillKHKK-N{g}từ card active — lockedCode là RÀO chống sửa tay + chống case active=null (rơi vềDEFAULT_CODE_BY_TYPE[10]='QT-KHKK-V2-001':650). Archived card cũng đủ 4 nút (:426-443) — slice pass-through by-Id OK. - Đo STATUS.md: canonical dòng 8 = "644 test PASS (45D+599I — CI #444 KHỚP local)" @S168; mốc sống S179 không ghi đổi test. Spec W4 ghi "baseline 645" — lệch 1, không rõ nguồn.
FINDINGS
Ký hiệu:
[C]phải sửa spec trước khi execute ·[M]lỗ/claim-sai phải vá trong spec ·[m]khai/polish. Mỗi finding: chỗ sai (spec/invest) → bằng chứng file:line → cách vá.
F1 [C] — W1b không biên dịch được như viết (helper internal ⟂ Submit nằm Infrastructure).
Spec W1b: "extract rào (v) → EnsureWorkflowGroupMatchAsync(db, awId, group); gọi ở Create + UpdateDraft + Submit". Đo: ContractSigningPlanScope = internal static (ContractSigningPlanFeatures.cs:263); InternalsVisibleTo duy nhất = SolutionErp.Infrastructure.Tests (SolutionErp.Application.csproj:25); Submit KHÔNG có Application-handler — controller gọi thẳng service Infrastructure (ContractSigningPlanFeatures.cs:19 + ContractSigningPlanWorkflowService.cs:60-62) ⇒ helper để trong Scope thì site thứ 3 KHÔNG gọi được, executor sẽ tự chế đường khác ngoài review.
Vá: tạo public static class ContractSigningPlanWorkflowGuards (Application/ContractSigningPlans, file cạnh Scope) chứa helper; Create/UpdateDraft (Application) + SubmitAsync (Infrastructure — đã reference Application) cùng gọi. KHÔNG dùng InternalsVisibleTo(Infrastructure) (mở toàn bộ internal surface 2 assembly) · KHÔNG inline-duplicate (drift 3 bản regex).
F2 [C] — W1d claim "hết cảnh 547 → 500" là claim-mạnh-hơn-fix: thiếu IgnoreQueryFilters().
PE + Contract + KHKK đều có HasQueryFilter(!IsDeleted) (PurchaseEvaluationConfiguration.cs:84 · ContractConfiguration.cs:44 · ContractSigningPlanConfiguration.cs:55). Đếm-pin naive sẽ KHÔNG THẤY phiếu xoá-mềm, nhưng FK vật lý vẫn chặn (row còn) ⇒ đúng ca PHỔ BIẾN NHẤT (drafter xoá phiếu nháp/TraLai — allow-list KHKK S177 — rồi admin xoá version "không ai dùng") vẫn 547→500. Test trong spec ("DELETE version có pin ⇒ 409") PASS mà bug ship — acceptance đo hình-dạng không đo hành-vi (@S179 class).
Vá: mọi count pin/opinion trong guard dùng IgnoreQueryFilters() (3 module có filter; 5 office không filter — đếm thẳng); opinions đếm CẢ row phiếu-đã-xoá (FK không quan tâm IsDeleted). Test bổ sung: phiếu KHKK đã xoá-mềm pin version → DELETE 409.
F3 [M] — DB-invest M3 "data KHÔNG mất (DB chặn được)" SAI cho 5 module Office — pin LOOSE 0-FK; W1d là guard DUY NHẤT.
Đo snapshot: HasForeignKey("ApprovalWorkflowId") = 4 hit (Steps-Cascade :6231 + KHKK :6247 + Contract :6329 + PE :6757). Domain có 8 cột pin (Proposal/LeaveRequest/OtRequest/VehicleBooking/TravelRequest thêm vào 3 module trên; ItTicket KHÔNG có — "8/9" của BE-invest ĐÚNG). ⇒ DELETE hôm nay với phiếu Office pin (0 opinions) thành công vật lý → dangling pin → phiếu treo chết lúc load workflow. Spec W1d đếm đủ 8 module (đúng), nhưng rationale kế thừa claim sai của DB-invest — executor đọc M3 có thể thu hẹp còn 3 module FK "vì DB chặn rồi".
Vá spec: sửa rationale W1d thành 2 vế: (a) 3 module FK = nâng UX 547→409; (b) 5 module Office = chống mất-toàn-vẹn dữ liệu (không có FK đỡ). Test BẮT BUỘC ca office: Leave/Proposal phiếu pin → DELETE 409 (hôm nay = 200-xoá-êm).
F4 [M] — W1a: 2 công thức scope KHÁC HÀNH VI giữa 2 invest, spec phải KHOÁ công thức + thiếu 1 test + thiếu belt.
BE-invest P1 = typeEnum == ContractSigningPlan ⇒ scope Code; FE-invest D1 = "per-Code khi code khớp ^KHKK-N[1-8]$". Khác nhau ở code type-10 NGOÀI-họ (QT-KHKK-V2-001 — chính là DEFAULT_CODE_BY_TYPE[10] :179 mà panel ContractSigningPlan đề xuất): công thức BE → per-Code (an toàn); công thức FE-regex → rơi nhánh per-type → NUKE cả 8 nhóm. Spec chọn đúng (typeEnum) nhưng khai "3 lens hội tụ" — che lệch; executor đọc FE-invest có thể implement regex.
Vá: (a) KHOÁ công thức trong spec: w.Code == request.Code && w.ApplicableType == typeEnum && w.IsActive (vế ApplicableType = belt: UNIQUE(Code,Version) là GLOBAL nên 1 Code có thể span 2 type qua version khác nhau — hiếm nhưng rẻ để chặn); (b) thêm test thứ 3: POST type-10 code-lạ QT-KHKK-V2-001 ⇒ IsActive 8 nhóm bất biến; (c) ghi chú P1 nhất quán với chính hàm — nextVersion :334-337 đã per-Code sẵn.
F5 [M] — KHKK-create KHÔNG check IsActive server-side ⇒ sau wave này, nút Ghim mất răng như spec mô tả + pin-bản-archived = 200.
Đo: Create/UpdateDraft phiếu KHKK chỉ EnsureWorkflowTypeAsync (type) + rào (v) — KHÔNG đòi IsActive/IsUserSelectable; đối chứng bridge KHKK→HĐ ĐÃ vá đúng lỗ này cho type-3 (vá-3, CreateContractFromSigningPlanFeatures.cs:141-147, comment tự khai "forge POST pin được quy trình retired"). FE KhkkCreatePage fe-user :104 filter isUserSelectable KHÔNG lọc isActive, :114-116 find(code) ăn may theo ordering Version-DESC của BE. Hệ quả CHỈ LỘ SAU W2 (mở đường tạo v02): (a) v01 sau deactivate vẫn IsUserSelectable=true (POST :343 không đụng cờ này) ⇒ "Bỏ ghim v02" làm auto-pin rơi về v01 archived im lặng thay vì CHẶN tạo — câu regression của spec ("Bỏ ghim ⇒ CHẶN TẠO PHIẾU nhóm đó") chỉ đúng khi nhóm có đúng 1 version; (b) forge POST pin bản archived.
Vá (khuyến nghị làm trong W1): Create + UpdateDraft-khi-đổi-pin thêm check IsActive ∧ IsUserSelectable 2 message tách (mirror vá-3); UpdateDraft giữ-nguyên-pin không đụng (guard chỉ chạy awId != plan.ApprovalWorkflowId :603 — phiếu cũ pin bản archived vẫn sửa GhiChu bình thường). FE ×2 app: find(w.isActive && code-match) (fe-user :114 + fe-admin mirror :113-116). Nếu anh muốn giữ nguyên semantics cũ thì tối thiểu PHẢI sửa câu răng-nút-Ghim trong spec thành "chặn khi bỏ ghim TẤT CẢ version của nhóm".
F6 [M] — D3=C như spec viết sẽ NÓI DỐI CHIỀU NGƯỢC ở ô #4 Trả-về-Drafter; danh sách ô mờ phải CHÍNH XÁC.
Bảng BE §3 dòng #4: KHKK BỎ QUA cờ nhưng hành-vi = LUÔN-CHO (return về Drafter cho mọi NV đúng lượt kể cả cờ tắt — ReturnOrRejectAsync không đọc cờ). Mờ ô #4 kiểu "chưa áp dụng cho KHKK" = bảo tính năng KHÔNG CÓ trong khi nó LUÔN BẬT.
Vá spec-C (danh sách chốt, gate applicableType === 10 — áp cả panel ContractSigningPlan cho nhất quán):
- Mờ-false-disabled + tooltip "chưa áp dụng cho KHKK": #1
allowReturnOneLevel· #2allowReturnOneStep· #3allowReturnToAssignee(:1235-1266vùng render) · #5allowApproverEditDetails:1268-1276· #6allowApproverEditBudget:1277-1285· #9allowApproverDelete:1320-1328— 6 ô. - Forced-true-disabled + tooltip "KHKK luôn trả về người soạn khi Trả lại — cờ không tắt được": #4
allowReturnToDrafter:1256-1267. - Giữ nguyên: #7 đã disabled sẵn (
:1292-1302) · #8 sống (:1307-1315). - Payload: ô mờ gửi giá-trị-đang-lưu (echo, không ép) để không vấp guard PUT.
F7 [M] — Ô "Ngưỡng giá trị gói CEO" phải TÁCH KHỎI D3 — disable cho type-10 VÔ ĐIỀU KIỆN.
:944-961 chỉ disabled={isEdit} ⇒ create-mode NHẬP ĐƯỢC cho type-10, trong khi CeoApprovalThreshold là quyết-đã-chốt-vĩnh-viễn KHÔNG port (OG-3 NĐUQ human-gate — ContractSigningPlanWorkflowService.cs:46-47). Lý lẽ của phương án A ("cờ nằm chờ port") KHÔNG áp dụng cho ô này — nó là #78 vĩnh viễn, không phải tạm thời. Spec hiện bó nó vào C ⇒ anh chọn A hoặc B là lỗ này sống tiếp.
Vá: W2 thêm mục riêng (mọi phương án D3): type-10 ⇒ ô CEO disabled + tooltip "KHKK không dùng ngưỡng máy — NĐUQ quyết" + POST gửi null; tiền lệ cùng-file :1292.
F8 [m] — Mâu-thuẫn 3 lens spec chưa khai (mục 7 của đề): (a) công-thức W1a lệch BE⟂FE (đã gộp F4); (b) D3: FE-invest §3 "mặc định nghiêng GIỮ (A)" ⟂ BE-invest §4.3 "KHÔNG port thì FE PHẢI ẩn/disable" (= C) — spec trình trung lập mà không khai 2 lens ngược nhau; (c) DB-invest W-DB-2 (thêm chiều code= phía BE) bị DROP im lặng — spec chọn client-side slice mà không ghi disposition. Vá: 3 dòng khai trong MỤC 3.
F9 [m] — Baseline test hardcode "645" ⟂ STATUS canonical "644" (@S168 CI #444). Một trong hai stale. Vá: W0/W4 đo tươi + ghi vào run.md, spec không chép số (B1).
F10 [m] — Acceptance #3 chưa chỉ định môi trường; prod-create v02 là mutate config THẬT và KHÔNG có đường re-activate (controller 5 action — không có activate; PUT cấm đổi IsActive; seeder idempotent per-Code không re-set). Vá: chứng chính = integration test W1a; acceptance UI #3 chạy LOCAL sau W0 (chấp nhận workflow rỗng người — đo cấu-trúc slice/badge); nếu muốn prod thì v02 = clone-y-hệt và khai với anh "v02 thành bản sống, v01 archived" TRƯỚC khi bấm.
F11 [m] — Thiếu data-audit phiếu ĐÃ pin lệch trước vá. Guard W1b chỉ chặn Create/UpdateDraft/Submit TỪ NAY; phiếu ChoDuyet pin lệch từ trước chạy tiếp đội hình lệch im lặng. Vá W5: thêm query SELECT p.MaKeHoach, p.ApprovalGroup, w.Code FROM ContractSigningPlans p JOIN ApprovalWorkflows w ON w.Id = p.ApprovalWorkflowId WHERE w.Code LIKE 'KHKK-N%' AND w.Code <> CONCAT('KHKK-N', p.ApprovalGroup) — >0 row thì báo anh xử tay. Cùng W5: nếu query 8-nhóm cho thấy nhóm nào 0-active (bom đã nổ trước vá) → recovery = "Tạo phiên bản mới" từ panel nhóm (lockedCode tự điền mã đúng).
F12 [m] — Sót nhỏ (mục 8): (a) hint ô Mã :929-932 khi lockedCode nên đổi chữ ("Mã khoá theo nhóm — version auto-tăng"); (b) sửa-workflow-tại-chỗ KHÔNG notify approver phiếu treo — parity PE (PE cũng không notify), KHÔNG làm wave này, khai vào Nợ; (c) invalidate keys ĐÃ ĐO ĐỦ: key ['approval-workflow-v2-overview', selectedTypeInt] + mutations invalidate prefix ⇒ 8 panel nhóm và panel type-10 dùng chung cache tự tươi, 0 việc; fe-user cache là app khác — không cross-invalidate được, chấp nhận (hành vi sẵn có); (d) comment BE :339 "only ONE active per type" phải sửa CÙNG COMMIT với W1a kẻo comment nói dối.
TỔNG HỢP WAVE FINAL ĐỀ XUẤT
- (điền cuối)