# SPEC — PE sign-off IMPLEMENT (6 decision chốt) + close-review — 12-07-2026

> `/fable-clone review` (H21 LỆNH-B) anh-directed S112: "review kỹ lại cách làm + điều chỉnh" TRƯỚC khi fan-out implement + deploy. Opus 4.8 MAX. Base = investigation `wf_fd8f2bfe-9f6` (PASS_WITH_CONCERNS).

## ① Đề-bài — 6 decision anh Kiệt ĐÃ CHỐT (lock)
1. **A1** — sửa `ApplyDrafterBypassOnSubmitAsync` (SVC:571-681): bypass CHỈ NV *cấp dưới* creator (order < drafterSlots.Min); creator KÝ TAY ô cấp mình (comment thật, SignedByUserId=creator). Giữ skip-NV-dưới của S60. Creator Cấp-1 → 0 subordinate → pointer đứng (0,1) creator tự ký.
   - 🔴 **anh Kiệt CONFIRM S112 (sau giải-thích-kỹ):** đúng hướng, **"BÁM ĐÚNG QUY TRÌNH ĐÃ ĂN"** = thay-đổi TỐI-THIỂU, **KHÔNG chế skip-logic mới**, không refactor engine — chỉ gate cái bypass. Cấp KHÔNG-liền-nhau (vd creator Cấp-1 + Cấp-3, Cấp-2 người khác) → creator ký ô mình TỪNG cấp, người-khác cấp-giữa ký **BÌNH THƯỜNG theo workflow** (không skip). Non-contiguous open-Q = **RESOLVED** (follow-workflow, chỉ bypass thuần NV order < creator-min-level).
2. **Không guard cứng self-approve** — "cấp chỉ 1 người → tạo và duyệt luôn được". Creator duyệt cấp single-person của mình hợp lệ (KHÔNG thêm Forbidden guard B2).
3. **Fix EDGE-5 (hard-lock)** — DaDuyet (+ TuChoi) = terminal khóa cứng: guard `fromPhase∈{DaDuyet,TuChoi}→throw` ở reject-branch (SVC:92) + `EnsureCanRejectV2Async` early-return `:321` + handler `FEAT:510-539` + controller. "Đã duyệt rồi là cứng không trả lại hay thao tác gì được."
4. **1 nút** (save=advance) — giữ nguyên, không tách.
5. **Opinion history** — giữ ô ý-kiến + cho sửa/cập-nhật + **LƯU HISTORY bản cũ** (hiện UPSERT overwrite = mất bản cũ). Cần cơ-chế mới: version-stamp col / bảng `PurchaseEvaluationLevelOpinionHistory` / log vào `PurchaseEvaluationChangelog`.
6. **Chỉ phiếu MỚI** — không migrate phiếu cũ (anh Kiệt: "phiếu cũ user đã chỉnh rồi").

## ② Cách implement (từ investigation, verify lại)
- **B1 (A1):** `SVC:571-681` đổi `k`/`bypassedOrders` → chỉ bypass `order < drafterSlots.Min`; set pointer = creator min-own-order (thay advance-qua `:647-680`). Update test `PeSubmitGuardAndBypassTests.cs` (spec-change §7).
- **B2 (câu 2):** KHÔNG thêm hard guard. ⚠️ review xác-nhận: interaction với `AllowApproverFinalize`/`SkipToFinal` (SVC:805/:867) — creator single-person-level bật finalize → tự lên DaDuyet bỏ CEO. anh Kiệt "tạo và duyệt luôn được" = chấp-nhận? hay CHỈ áp cho cấp không-phải-terminal? (review chốt: no-guard có tạo CEO-skip NGOÀI Ý không).
- **B3 (EDGE-5):** guard terminal-lock. Xác-nhận ĐỦ kín: 4 return-mode + controller `:130-143` + handler fromPhase.
- **B5 (history):** review CHỌN cơ-chế (a) bảng history append-only, (b) version/round col trên LevelOpinion, (c) log Changelog — + Mig tối-thiểu. 1-nút mỗi lần ký lại → log bản trước.
- **FE:** happy-path ~0 (banner+nút Duyệt đúng khi pointer đứng creator); F1 cosmetic label. Mirror 2-app byte-identical (903 dòng).
- **Test:** bypass-test update + EDGE-5-lock test + history test.

## ③ Checklist review (mỗi lane 1 lăng-kính, đối kháng)
- [ ] **State-machine:** A1 pointer đúng (subordinate-only, creator ký tay, 0 kẹt phiếu, 5-state nguyên); reject-return sau A1 không vỡ.
- [ ] **Security/governance:** EDGE-5 fix ĐỦ KÍN mọi đường tới terminal (4 mode + controller + handler); câu-2 no-guard KHÔNG tạo CEO-skip ngoài ý (finalize-flag interaction) — nếu có → cảnh-báo owner.
- [ ] **Schema/history:** chọn cơ-chế history (a/b/c) tối-thiểu-schema + reversible; 1-nút+history log đúng mỗi lần ký lại; câu-6 no-migrate an-toàn (phiếu cũ có canned-comment).
- [ ] Output: bảng file:line cần đổi + test cần update + **risk DEPLOY** (đây là prod ERP live UAT anh Kiệt) + GO/NO-GO cho fan-out.
