Files
solution-erp/.claude/workflows/runs/2026-08-08-S182-khkk-awf-mirror-ncc/sub-invest-be-fable.md
2026-08-08 01:01:12 +07:00

21 KiB
Raw Blame History

INVEST-BE — KHKK 8 nhóm workflow admin mirror DuyetNcc (S182, Fable deep-pass)

Trạng thái: ĐANG ĐO — đổ ruột liên tục. Mỗi claim kèm file:line.

§1 Endpoint map trang approval-workflows-v2 (admin)

Controller duy nhất: src/Backend/SolutionErp.Api/Controllers/ApprovalWorkflowsV2Controller.cs — route api/approval-workflows-v2 (:17), class-level [Authorize] trần (:18).

Action Verb+Route Authz file:line
Overview (list all types + versions + history) GET ?applicableType=&isUserSelectable= authenticated-only, 0 policy (CỐ Ý — Drafter cần list để pick lúc create, comment :10-15) :21-26
Tạo version mới POST Workflows.Create :28-34
Sửa-tại-chỗ (giữ Id/Version/IsActive) PUT {id} Workflows.Create :45-51
Ghim/Bỏ ghim (IsUserSelectable) PATCH {id}/user-selectable Workflows.Create :56-62
Xoá version DELETE {id} Workflows.Create :64-70

Tham số phân loại = ApplicableType (int enum), KHÔNG code-prefix. Enum ApprovalWorkflowApplicableType @ Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs:53-71: DuyetNcc=1 · DuyetNccPhuongAn=2 · Contract=3 · Proposal/Leave/Ot/Vehicle/ItTicket/Travel=4-9 · ContractSigningPlan=10 (:70). Trang DuyetNcc = ?applicableType=1; "NCC và Giải pháp" = 2; KHKK = 10.

Handler Overview: Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs — filter generic d.ApplicableType == typeEnum (:123-127) + IsUserSelectable (:129-132); khi applicableType=null group theo Enum.GetValues<>() toàn bộ (:199-213) ⇒ GENERIC, 0 hardcode chặn type-10. Label map AwLabels.Type :90-98 CÓ entry type-10 "Kế hoạch ký kết HĐ" (:97) — nhưng label này là 1-per-type, KHÔNG phân biệt 8 nhóm N1-N8 (8 nhóm cùng type=10 sẽ gộp chung 1 AwTypeSummaryDto với 1 Active duy nhất — xem §2 hệ quả).

9 checkbox = 9 cột bool per-Level row trên ApprovalWorkflowLevel (ApprovalWorkflow.cs:99-176) — schema DÙNG CHUNG mọi ApplicableType:

# Checkbox UI Flag line
1 Trả về 1 Cấp trước AllowReturnOneLevel (F1m1) :120
2 Trả về 1 Bước trước AllowReturnOneStep (F1m2) :123
3 Trả về Người chỉ định AllowReturnToAssignee (F1m3) :126
4 Trả về Drafter (default) AllowReturnToDrafter (F1m4, default TRUE) :129
5 Sửa Section 2 lúc duyệt AllowApproverEditDetails (F3) :133
6 Sửa Section ngân sách AllowApproverEditBudget (F4) :139
7 Duyệt thẳng Cấp cuối AllowApproverSkipToFinal (F2) :147
8 Duyệt là KẾT THÚC (không CEO) AllowApproverFinalize (F5) :158
9 NV XÓA phiếu khi đến lượt AllowApproverDelete (F6, Mig 68) :173

🔴 Create handler ĐÃ CÓ nhánh type-10 đích danh (ApprovalWorkflowV2AdminFeatures.cs:387): AllowApproverSkipToFinal = typeEnum != ContractSigningPlan && l.AllowApproverSkipToFinalép-false F2 CHO RIÊNG type-10 (K3-test O-1: KHKK service 0 lần đọc cờ này, echo true = config-lie #78). AllowApproverFinalize từng ép-false @S161 nhưng ĐÃ GỠ @S165 (service KHKK port level-finalize) — comment :376-386.

PUT sửa-tại-chỗ (S155 F-1): T1 khung-không-đổi (tập Step.Order + Level.Order bất biến, :552-559 + :572-574 + :593-595 + :636-648) + T2 luật-chơi-không-đổi (Code/ApplicableType/CeoApprovalThreshold/F5/F2 = ECHO-only, lệch ⇒ 409, :539-550 + :598-603). Guard chữ-ký 2 tầng (:665-724): CountLiveOpinionsAsync + ListRetainedOpinionsAsync — cần kiểm 2 helper này có đọc ContractSigningPlanLevelOpinions không (xem §2/§4). Transaction rào @ :744-745.

Deactivate-per-type khi POST: :339-343 — deactivate MỌI workflow ApplicableType == typeEnum && IsActiveonly-ONE-active-per-TYPE. Với 8 nhóm KHKK cùng type=10: tạo version mới cho N2 sẽ TẮT IsActive của N1..N8 còn lại ⇒ đây là site chặn #1 (xem §2).

§1-bis Phần sau của AdminFeatures (line 800-1205)

  • Guard chữ-ký 3-site ĐÃ phủ KHKK 3/3: CountLiveOpinionsAsync :961-965 + ListRetainedOpinionsAsync :1033-1038 + PurgeOrphanOpinionsAsync :1099-1102 đều có nhánh ContractSigningPlanLevelOpinions (site liệt-kê-thủ-công, comment :957-960 tự khai luật "module mới PHẢI vào CẢ 3"). DetachTrackedOpinionsOn :1122-1136 quét theo TÊN CỘT metadata ⇒ tự phủ (comment :1120 nói "7 bảng" — stale nhẹ, thực tế 8).
  • 🔴 GAP changelog PUT sửa-tại-chỗ: :863-893 chỉ ghi vết vào PurchaseEvaluationChangelogs khi ApplicableType ∈ {DuyetNcc, DuyetNccPhuongAn}KHKK type-10 bị SKIP (comment :857-862 tự khai: ContractSigningPlanChangelogs là changelog PHIẾU, "wire nếu cần ở W2/W3" — CHƯA wire). Sửa workflow nhóm KHKK đang có phiếu treo ⇒ phiếu 0 vết, chỉ có def.UpdatedAt (:851).
  • 🔴 GAP DELETE version 0 usage-check: DeleteAwDefinitionCommandHandler :1193-1205 = load + Remove + Save, KHÔNG kiểm phiếu đang pin (comment :1187-1189 tự khai "Sau UAT ... cần check usage trước khi delete" — nợ từ Mig 22 era). Hard-delete; hành vi thực tế phụ thuộc FK của cột pin từng module (cần đo FK ContractSigningPlans.ApprovalWorkflowId — xem §2).
  • SetUserSelectable :1173-1185 generic by-Id ⇒ type-10 dùng được 0-đổi.

§2 Seed 8 workflow nhóm KHKK + phân loại

Seed = DbInitializer.SeedKhkkGroupWorkflowsAsync (Infrastructure/Persistence/DbInitializer.cs:549-687, K3 nhịp-1 S164/S165) — KHÔNG phải Mig 71 (Mig 71 chỉ AddColumn ApprovalGroup lên phiếu + backfill=1; workflow ROWS do DbInitializer đẻ lúc boot, INFRASTRUCTURE seed not-gated DemoSeed, comment :539).

  • Phân loại 8 nhóm = Code string KHKK-N{n} (:609), n=1..8 (:607). TẤT CẢ 8 cùng ApplicableType=ContractSigningPlan(10) (:616). Name "Duyệt KHKK — Nhóm {n}" (:617). Version=1, IsActive=true + IsUserSelectable=true cả 8 (:615,:622-623).
  • Idempotent PER-CODE AnyAsync(w.Code == code) (:610) — comment :540-542 tự khai vì sao KHÔNG dùng khuôn per-type của 6 seeder mẫu: 8 workflow dùng chung 1 type, check type sẽ chặn 7 nhóm còn lại.
  • Đội hình 3 trạm PRO→CCM→BGĐ sao từ QT-DN-V2-001 prod (roster :554-585): PRO C1 4-người OR-of-N + C2 tra.bui · CCM C1 4-người + C2 chuong.phan AllowApproverFinalize=true (:576-579) · BGĐ C1 truong.nguyen. Resolve theo EMAIL, thiếu user ⇒ bỏ slot + warn không throw (:593-600), cấp rỗng ⇒ nén Order (:645), workflow 0-bước vẫn tạo giữ mã (:673-676).
  • 8 nhóm hiện SỐNG CHUNG IsActive=true cùng type=10 ⇒ bất biến "only-ONE-active-per-type" của POST create (ApprovalWorkflowV2AdminFeatures.cs:339-343) ĐÃ VỠ từ lúc seed (8 active cùng type). Hệ quả: POST tạo version mới cho BẤT KỲ nhóm nào sẽ deactivate CẢ 8 — site chặn đích danh #1.
  • Sidebar 8 menu-row AwV2_KhkkN1..N8 seed @ DbInitializer.cs:1988-1995, parent ApprovalWorkflowsV2; MenuKeys VÀO All (MenuKeys.cs:246-249) ⇒ 32 policy AwV2_KhkkN{n}.{R,C,U,D} tồn tại.

§3 Runtime duyệt KHKK — bảng 9-flag × {PE-runtime, KHKK-runtime}

KHKK runtime = Infrastructure/Services/ContractSigningPlanWorkflowService.cs (W3 S161 + K3 S165). 4 action cố định submit/approve/return/reject (:74-75), KHÔNG có targetPhase param. OR-of-N ResolveActingLevel (:455-469), UPSERT ContractSigningPlanLevelOpinions (:477-513), con-trỏ ĐÔI reset-Cấp-1-khi-sang-Bước (:309-310), submit reset về Bước1/Cấp1 cả 2 lối vào (:198-202).

# Flag PE-runtime KHKK-runtime Evidence KHKK
1 AllowReturnOneLevel TÔN TRỌNG — PurchaseEvaluationWorkflowService.cs:421 (switch WorkflowReturnMode) BỎ QUA — return luôn = TraLai toàn phần ContractSigningPlanWorkflowService.cs:48-50 comment (c) "4 return-mode per-level CỐ Ý chưa đọc"; ReturnOrRejectAsync:354-400 0 đọc flag
2 AllowReturnOneStep TÔN TRỌNG — :422 BỎ QUA — nt nt
3 AllowReturnToAssignee TÔN TRỌNG — :423 BỎ QUA — nt nt
4 AllowReturnToDrafter TÔN TRỌNG — :424 (mode Drafter phải bật cờ) BỎ QUA (hành vi = luôn-cho) — return về Drafter cho MỌI NV đúng lượt, kể cả cờ tắt ReturnOrRejectAsync chỉ guard đúng-lượt (:379-380), không đọc cờ
5 AllowApproverEditDetails (F3) TÔN TRỌNG — PurchaseEvaluationDetailFeatures.cs:84 + PurchaseEvaluationSupplierFeatures.cs:45 BỎ QUA / CHƯA WIRE — KHKK Lines/Dossier edit = drafter-only Nháp/TraLai EnsureDraftEditable ContractSigningPlanFeatures.cs:295-303 (mọi đường ghi W2); 0 hit AllowApproverEditDetails trong ContractSigningPlan*
6 AllowApproverEditBudget (F4) TÔN TRỌNG — PurchaseEvaluationFeatures.cs:411 BỎ QUA / PE-semantics (KHKK không có section NS; giá dòng = ApprovedAmount ??= ProposedAmount chốt tại finalize :345-349) 0 hit trong ContractSigningPlan*
7 AllowApproverSkipToFinal (F2) TÔN TRỌNG — PurchaseEvaluationWorkflowService.cs:810 (+ Contract :335) CHƯA WIRE + BỊ ÉP-FALSE tại POST create cho type-10 service 0 đọc (comment :47 "giữ nguyên không port"); ép-false ApprovalWorkflowV2AdminFeatures.cs:387
8 AllowApproverFinalize (F5) TÔN TRỌNG — :870 (opt-out applyLevelFinalize) TÔN TRỌNG — port @K3 S165, opt-out param mirror PE S96, set EndedByLevelFinalize runtime-flag (Mig 71) ContractSigningPlanWorkflowService.cs:276-289; seed bật cho TP.CCM (DbInitializer.cs:576-579,:655)
9 AllowApproverDelete (F6) TÔN TRỌNG — PeSoftDeleteFeatures.cs:110-114 (3 rào: policy ∧ đúng-lượt ∧ F6) BỎ QUA / CHƯA WIRE — delete KHKK không có nhánh approver-đúng-lượt (xem §3-bis delete handler) 0 hit AllowApproverDelete trong ContractSigningPlan*
  • CeoApprovalThreshold (workflow-level, NGOÀI 9 checkbox): PE đọc; KHKK CỐ Ý KHÔNG port (NĐUQ human-gate vĩnh viễn, OG-3 — comment :46-47).
  • DTO detail KHKK ĐÃ expose AllowApproverFinalize per-level (ContractSigningPlanFeatures.cs:140,:1013,:1030) — các flag khác không expose (PE expose đủ 9 @ PurchaseEvaluationDtos.cs:124-138).

§3-bis Surprises quanh pin/đổi workflow (evidence create/update path)

  • Rào (v) group-match CHỈ chạy lúc CREATE: ContractSigningPlanFeatures.cs:479-486 regex ^KHKK-N([1-8])$ so approvalGroup; workflow code NGOÀI họ (type-10 code khác) → CHO QUA (comment :477-478).
  • 🔴 UpdateDraft re-pin (:603-607) chỉ gọi EnsureWorkflowTypeAsync (type=10, :307-318), KHÔNG re-check group-match ⇒ phiếu nhóm 3 tạo đúng KHKK-N3 rồi PUT draft re-pin KHKK-N5 = 200 OK; SubmitAsync cũng không re-check workflow-code↔group (ContractSigningPlanWorkflowService.cs:169-187 chỉ check Lines↔group) ⇒ bypass rào (v) 2-bước — phiếu nhóm 3 chạy đội hình nhóm 5.
  • FK ContractSigningPlans.ApprovalWorkflowId → ApprovalWorkflows = Restrict vật lý (ContractSigningPlanConfiguration.cs:48-53).

§4 Gap-analysis BE → wave đề xuất

§4.0 Authz đối chiếu 32 policy AwV2_KhkkN{n}.{R,C,U,D} (gotcha #85)

Đo: grep AwV2_ toàn SolutionErp.Api = 0-hit (control-dương cùng thước: KeHoachKyKet cùng path = 29 hit / 1 file ⇒ vắng thật). ⇒ 32 policy KHÔNG controller nào tiêu thụ — chúng chỉ sống ở tầng menu-display (sidebar row + Permission matrix). Đây KHÔNG phải bug mới của KHKK: 2 key cũ AwV2_DuyetNcc/AwV2_DuyetNccPhuongAn (MenuKeys.cs:116-117) cũng 0 consumer BE — trang DuyetNcc hiện hành chạy đúng mô hình này rồi.

  • Key gate THẬT của trang admin (mọi type): GET = authenticated-only · POST/PUT/PATCH/DELETE = Workflows.Create (§1). "Y như trang DuyetNcc" ⇒ 0-đổi-authz: admin nào có Workflows.Create sửa được CẢ 8 nhóm (và cả PE/Contract workflows).
  • Trade-off nếu muốn per-group write-authz (nhóm N1 chỉ ai được cấp N1 sửa): controller phải resolve workflow→Code→group rồi check AwV2_KhkkN{n}.Update — đổi hình authz của controller DÙNG CHUNG 9 type ⇒ blast radius mọi module; và theo #85 endpoint phải gate bằng key CỦA CHÍNH NÓ, không OR key con. Quyết định thuộc owner/lead — không tự quyết; mặc định rẻ + nhất quán = giữ Workflows.Create.
  • 🔸 Menu-row 8 leaf cần CanRead grant per-role (KHKK root CỐ Ý không inherit — GetMyMenuTreeQuery.cs 4-case; seeder grant explicit) — tầng display độc lập tầng API (memory permission_grant_two_layers).

§4.1 Disposition 4+1 site chặn

# Site Hiện trạng Đề xuất wave Rủi ro
(a) only-ONE-active-per-TYPE ApprovalWorkflowV2AdminFeatures.cs:339-343 POST version mới nhóm N2 ⇒ deactivate CẢ 8 (bất biến đã vỡ từ seed: 8 active cùng type-10). Blast thực tế: pick-list KhkkCreatePage lọc isUserSelectable KHÔNG lọc IsActive (fe-admin/src/pages/khkk/KhkkCreatePage.tsx:97) + runtime load by-Id ⇒ thiệt hại = Overview Active badge/summary + mọi chỗ đọc IsActive về sau P1 (khuyên): đổi scope deactivate = w.Code == request.Code khi typeEnum == ContractSigningPlan (đúng semantic "1 active per chuỗi-version", 8 chuỗi độc lập). P2: đổi per-Code cho MỌI type — sạch hơn nhưng đổi hành vi PE/Contract (hiện tạo Code MỚI cùng type tự tắt Code cũ — có thể có chỗ dựa vào) P1: if-đặc-thù type-10 (thêm 1 nhánh rẽ); P2: regression PE/Contract khó lường — cần grep chỗ đọc IsActive trước
(b) Changelog PUT skip type-10 :863-893 Sửa workflow nhóm có phiếu KHKK treo ⇒ phiếu 0 vết (comment :857-862 tự khai defer W2/W3) Wire block mirror PE: query ContractSigningPlans pin def.Id + Phase ∉ {DaDuyet,TuChoi} → add ContractSigningPlanChangelogs (EntityType Workflow, khuôn ContractSigningPlanWorkflowService.cs:546-557) Thấp — bảng phiếu-changelog có sẵn FK Cascade; chỉ thêm 1 nhánh else-if
(c) DELETE version 0 usage-check :1193-1205 FK ContractSigningPlans.ApprovalWorkflowId Restrict (ContractSigningPlanConfiguration.cs:50-53) ⇒ data KHÔNG mất, nhưng DbUpdateException KHÔNG được map (GlobalExceptionMiddleware.cs:32-39 chỉ map 5 app-exception, _500 "Lỗi hệ thống"). Phiếu xoá-MỀM vẫn còn row ⇒ vẫn chặn. Workflow từng dùng rồi unpin: opinions FK Restrict vào Levels cũng chặn cascade ⇒ 500 Guard usage-check trong DeleteAwDefinitionCommandHandler: đếm pin per-module (PE ApprovalWorkflowId, Contract, Proposal, 4 WorkflowApps, KHKK — 8/9 module có cột pin) + đếm LevelOpinions (tái dùng khuôn CountLiveOpinionsAsync NHƯNG theo workflow-id) → 409 đích danh "N phiếu đang pin" Trung bình — liệt-kê-thủ-công 8 module (đúng class lỗi comment :957-960 đã cảnh báo); nợ này của Mig 22 era, sửa 1 lần lợi mọi type
(d) Bypass rào (v) 2-bước ContractSigningPlanFeatures.cs:603-607 UpdateDraft re-pin chỉ check type, không check group-match; Submit không re-check ⇒ phiếu N3 chạy đội hình N5 Extract rào (v) (:479-486) thành ContractSigningPlanScope.EnsureWorkflowGroupMatchAsync(db, awId, group) → gọi ở CẢ Create + UpdateDraft; cân nhắc gọi thêm ở SubmitAsync làm chốt-cuối (khuôn 2-tầng như hạng-mục :179-187) Thấp; chú ý phiếu cũ đã lỡ pin lệch trước vá sẽ kẹt submit — message phải chỉ việc cần làm
(e) Ép-false F2 type-10 :387 ĐÚNG theo chống config-lie #78 (service KHKK 0 đọc F2 — ContractSigningPlanWorkflowService.cs:47) GIỮ cho tới khi có lệnh owner port F2. Nếu port: copy nhánh PE PurchaseEvaluationWorkflowService.cs:797-810 vào service KHKK + gỡ ép-false + gỡ khoá FE CÙNG WAVE (đúng tiền lệ F5 @K3: port + gỡ ép-false + gỡ FE-lock 1 wave, comment :376-386) Mở mà không port = đẻ lại #78; port mà quên gỡ ép-false = cờ chết chiều ngược

§4.2 Việc 0-đổi-BE (đã generic sẵn — wave FE nối thẳng)

  1. GET Overview ?applicableType=10 trả đủ 8 nhóm × versions × 9-flag per-level (§1) — FE lọc client-side theo code == "KHKK-N{n}" (payload nhỏ; muốn sạch hơn có thể thêm query param code — optional, không blocker).
  2. PUT sửa-tại-chỗ (thêm/bớt người, đổi NV, 7 cờ tại-chỗ, đổi tên/phòng) — T1/T2 + guard chữ-ký 3-site ĐÃ phủ KHKK (§1-bis).
  3. PATCH user-selectable + DELETE (khi chưa pin) — generic by-Id.
  4. Lịch sử versions (History list per code) — có sẵn trong Overview DTO.
  5. Runtime approve/return/reject/submit KHKK + F5 finalize + inbox + notify — W3/K3 xong (§3).
  6. Authz: giữ Workflows.Create = y-hệt trang DuyetNcc (§4.0).

§4.3 Việc PHẢI đổi BE (gom wave)

  • Wave-BE-1 (nhỏ, an toàn): (a)-P1 deactivate-per-Code cho type-10 · (d) group-match guard 2-site · (b) changelog type-10 · (c) usage-check DELETE → 409. 4 việc đều trong 2 file ApprovalWorkflowV2AdminFeatures.cs + ContractSigningPlanFeatures.cs (+1 helper Scope), 0 migration.
  • Wave-BE-2 (chỉ khi owner duyệt semantics): port return-modes F1×4 / F3-analog (sửa Lines lúc duyệt) / F6 approver-delete / F2 skip-to-final vào KHKK runtime — mỗi cái là ĐỔI HÀNH VI DUYỆT, thuộc quyền owner (comment (c) service :48-50 đã khai chủ-đích chưa đọc). Nếu KHÔNG port: FE phải ẩn/disable các ô chưa-wire cho type-10 (tiền lệ earlyFinalizeLocked W1 — đã có khuôn), không thì mỗi ô tick = config-lie #78 mới.

§5 Surprises ngoài đề bài

  1. 8 link sidebar KhkkN{n} hiện đổ về trang landing chungLayout.tsx:188 route /system/approval-workflows-v2/KhkkN{n} nhưng ApprovalWorkflowsV2Page.tsx TYPE_CODE_TO_INT (:164-172) KHÔNG có key KhkkN*selectedTypeInt=undefined → rơi về grid landing (:299-301,:323). Menu có, trang chưa phân nhóm — wave FE là phần nặng nhất của đề bài, BE gần xong.
  2. GET Overview 0-policy trả đủ 9-flag + CeoApprovalThreshold + tên/email toàn bộ approver cho MỌI user authenticated (kể cả fe-user token) — cố ý từ Mig 22 cho Drafter pick (:10-15), nhưng payload nay giàu hơn nhiều so 2026-05; đáng để owner ngó lại (information-disclosure mềm).
  3. Roster 8 workflow = SNAPSHOT lúc seed theo email hardcode (DbInitializer.cs:554-585) — QT-DN-V2-001 prod đổi người thì 8 nhóm KHÔNG tự theo; idempotent per-Code nên restart không re-sync (đúng thiết kế, nhưng dễ bị hiểu nhầm "sao đội hình không khớp DuyetNcc nữa").
  4. Bất biến only-1-active-per-type đã vỡ từ seed (8 active type-10) mà Overview Active = versions.FirstOrDefault(IsActive) (:210) chỉ hiện 1 — 7 nhóm còn lại "tàng hình" khỏi ô Active của type-summary; FE per-group phải đọc theo Code, đừng tin ô Active.
  5. DetachTrackedOpinionsOn comment "7 bảng" stale (thực 8 — quét metadata nên vẫn đúng hành vi) ApprovalWorkflowV2AdminFeatures.cs:1120.
  6. KHKK delete phiếu allow-list ĐÃ rộng hơn PE: {Nháp, TuChoi, TraLai} (S177 owner) vs PE {DangSoanThao, TuChoi} + F6-đúng-lượt (ContractSigningPlanFeatures.cs:1733-1738 vs PurchaseEvaluationFeatures.cs:1404-1406 + PeSoftDeleteFeatures.cs:110-114) — mirror "nút XÓA ở màn duyệt" cho KHKK là F6-wire (wave-BE-2), không phải nới allow-list thêm.

VERDICT

BE cho 8 workflow "Duyệt KHKK — Nhóm 1..8" đã generic ~85%: controller + 5 action + PUT sửa-tại-chỗ + guard chữ-ký + ghim/xoá + seed 8 nhóm (Code KHKK-N{n}, cùng ApplicableType=10) + runtime F5 finalize đều SỐNG. 4 site BE phải sửa (wave-BE-1, 2 file, 0-mig): deactivate-per-Code type-10 (:339-343) · group-match guard thêm 2-site (:603-607+Submit) · changelog type-10 (:863-893) · usage-check DELETE→409 (:1193-1205). 6/9 checkbox hiện chỉ PE tôn trọng — tick cho type-10 = cờ nằm im (#78-class): hoặc port runtime (wave-BE-2, cần owner duyệt semantics), hoặc FE khoá ô theo tiền lệ earlyFinalizeLocked. Authz giữ Workflows.Create y trang DuyetNcc; 32 policy AwV2_KhkkN* 0 consumer BE (đo + control-dương). Phần nặng còn lại là FE (8 route đổ landing chung).