# S97 — PE finalize: đổi chữ "không trình tới" + đảo default checkbox (2026-07-01)

> UAT anh Kiệt FDC (tiếp buổi live S96). 2 fix nối tiếp tính năng "Cấp KẾT THÚC quy trình" (S96 `e657faa`). **em-main-led + HMW-workflow** (implement + review). FE-only, prod-verified **Run #481**.

## Bối cảnh
Anh Kiệt gửi 2 ảnh chat Zalo về phiếu "8 MEP Sub Hệ thống solar":
1. Badge trên sơ đồ duyệt ghi "không trình tới" — sai ngữ cảnh khi phiếu lên CEO. Chốt (Huy đề xuất → Kiệt "Yeb"): đổi thành **"Kết thúc tại CCM"**.
2. Checkbox "Cấp này KẾT THÚC quy trình" đang default = tick (opt-out, S96). Anh Kiệt: *"option mặc định là không chọn; khi nào chọn mới không đẩy lên"* → đảo thành **opt-in**.

## Thay đổi
FILE (mirror 2 app, SHA-identical): `fe-{admin,user}/src/components/pe/PeWorkflowPanel.tsx`

**Fix 1 — badge wording (dòng ~329-331):**
- text `"không trình tới"` → `Kết thúc tại {evaluation.finalizeLevelName ?? evaluation.finalizeStepName ?? 'cấp trên'}` (render động → "Kết thúc tại CCM" cho workflow của họ)
- tooltip title → template-literal tương ứng (`… kết thúc tại ${finalizeLevelName …} … không cần trình tới`)
- `finalizeLevelName`/`finalizeStepName` đã có sẵn trong scope (banner dòng ~280) — no new prop/DTO

**Fix 2 — đảo default checkbox opt-out→opt-in:**
- `const [applyLevelFinalize] = useState(true)` → `useState(false)` (dòng 57)
- reset onSuccess `setApplyLevelFinalize(true)` → `(false)` (dòng 221)
- helper text ternary reword (default = "TRÌNH TIẾP lên CEO", tick = "KẾT THÚC tại cấp này")
- comment S96 "default checked" → "OPT-IN" (drift cleanup, em-main — landmine)
- Logic gửi BE (dòng 200 `approverFinalizeEligible ? applyLevelFinalize : true`) + price-picker gate (dòng 496) tự đúng theo state mới — no change

**KHÔNG đụng:** BE (service dòng 867 `if (matchingLevel.AllowApproverFinalize && applyLevelFinalize)` **tôn trọng cờ FE gửi lên**) · migration · test · file FE khác. BE param default `applyLevelFinalize=true` **giữ nguyên** (fallback caller non-FE; FE luôn POST giá trị tường minh) → zero test-churn, zero prod-risk.

## Quy trình (HMW-mode ON — anh consent "ultra-on hmw")
Workflow `wf_0e01fdba-209` (implement → review pipeline):
- 🟧 **implementer-frontend**: 8 hunk (4×2 app), SHA256 identical `bcf812fd`, build admin PASS 22s + user PASS 8s, 0 TS err. Ghi chú: MEMORY.md hit PostToolUse-hook compaction giữa-spawn (23.8→11.5KB) → archive S76-79 sang `archive/2026-H1.md` (file mới; verify nội dung không mất).
- 🟥 **reviewer**: PASS 0 issue — 4 edit đúng verbatim 2 app, comment L121 + handler L664 (`e.target.checked`) giữ nguyên (đúng), 0 BE/test/mig leak, logic non-break (default-false → không ép chọn giá chốt), TS-safe (`finalizeStepName/LevelName: string|null`).

em-main: comment-drift cleanup (2 app) + verify SHA mirror (1 hash) + commit `1c8fb6a` + push.

## Deploy verify — cicd-monitor Run #481 (byte-level #77)
- **PASS** ~5m10s (18:05:56→18:11:06). Test gate 440 green.
- JS rotate (real FE proof): admin `COYAX-yQ`→`Cf0o8dx8` · user `0MU-kb4N`→`x8rppRAK`. CSS frozen `Bbtesa6B`/`BmuPz_Dy` (#69-REFINE JSX-text+bool-only). Real-size 1.64/1.54MB (anti-SPA-trap 919b).
- **Byte-level (#77) 2 app CLEAN:** NEW `Kết thúc tại` PRESENT + helper `Mặc định: duyệt xong phiếu TRÌNH TIẾP` PRESENT · OLD `không trình tới` ABSENT + `Đã BỎ tick` ABSENT → **fix shipped byte-level; UI cũ = cache (incognito), KHÔNG phải deploy-fail.**
- Mig 59 / 88 tables frozen · smoke api+admin+eoffice 200×3 + PE-list 200 items[] valid.
- Caveat: badge-text/checkbox-default không curl-verify được (cần login + phiếu thật → anh Kiệt UAT).

## Lessons
- **#53 return-garble tái diễn LẦN-4** — cicd-monitor trả prose curate-memory ("18.7KB still above 17.1KB target…") thay deploy-verdict, DÙ instruction tường minh "return verdict NOT memory-curation prose". Recover verdict từ diary (`agent-memory/cicd-monitor/MEMORY.md` S97 #481 entry) **KHÔNG re-spawn** — theo `feedback_agent_return_garble_recover`. → instruction-fix "return-verdict" **giảm-không-100%**; recover-from-diary là đường robust.
- **BE-honors-explicit-flag** → đảo default checkbox chỉ cần sửa FE `useState` (BE param default = fallback cho caller non-FE, FE luôn POST tường minh dòng 200) → zero BE/test churn, zero prod-risk. 1 UAT-flip "tưởng to" = 4 dòng × 2 app.
- **Comment-drift** — đổi behavior phải sửa comment mô tả (S96 "default checked" nằm ngay trên `useState(false)` = landmine cho reader sau). em-main cleanup trước commit.

## State (GIỮ NGUYÊN trừ bundle)
Mig 59 · 88 bảng · 440 test (45D+395I) · gotcha 77 · menu 54 · **bundle admin `Cf0o8dx8`/user `x8rppRAK` (Run #481)** · prod 21 NCC / 17 user.

## NEXT
- 🔴 **anh:** UAT **incognito** phiếu "8 MEP Sub Hệ thống solar" — xác nhận badge "Kết thúc tại CCM" + checkbox default unchecked. (+ vụ cũ "lỗi phân quyền vào phiếu": cho **số-phiếu + user** để em soi config approver.)
- **em:** đã closeout S97. Monthly re-tier STATUS/HANDOFF mega-line vẫn pending (giờ 5-blurb S97→S93).

---

## Part 2 (cùng ngày S97) — endsBeforeCeo CONFIG→RUNTIME (bug "CEO duyệt mà bị mờ")

**Bối cảnh:** anh Kiệt hỏi phiếu PE/2026/A/010 "8 MEP Sub Hệ thống solar" — CEO (Bước 3, Nguyễn Văn Trường) đã duyệt thật (16:47:30, Lịch sử duyệt) nhưng sơ đồ quy trình báo "Phiếu đã kết thúc tại CCM Cấp 2 — không qua CEO" + làm mờ Bước 3. "CEO duyệt mà ko thấy gì?"

**Root-cause:** `endsBeforeCeo` (banner + làm-mờ bước-sau) tính 100% CONFIG (`steps.Any(l => l.AllowApproverFinalize)` = workflow CÓ cấp finalize), 0% runtime. Phan Văn Chương (Bước 2 Cấp 2, cờ "Duyệt thay CEO") chọn "Trình duyệt" (opt-out) → phiếu lên CEO thật, nhưng display suy config → nói dối. `ComputeLevelStatus` blanket "Done" mọi cấp khi DaDuyet. **Nặng lên sau fix-2 (đảo default opt-in):** mặc định giờ trình tiếp CEO → nhiều phiếu lên CEO → bug phổ biến (fix = follow-up trực-tiếp của chính fix-2). → gotcha #78.

**Fix (anh chọn "fix trọn", Mig 60 `8037fa0`):**
- Field runtime `PurchaseEvaluation.EndedByLevelFinalize` (bool default false) — set `true` CHỈ ở nhánh level-finalize `ApproveV2Async` (`PurchaseEvaluationWorkflowService.cs:871`; reviewer confirm 1/5 nhánh DaDuyet, ccm-delegation/full/drafter/V1 giữ false).
- 4 projection `EndsBeforeCeo`: phiếu DaDuyet đọc runtime field; ChoDuyet giữ config heads-up. Detail (`:1153`) + List/Inbox (ternary `Phase==DaDuyet?field:subquery`, `:647/:767`) + ListApproved (đọc thẳng field `:196`, DaDuyet-only).
- FE gate làm-mờ `finalizeFlowStep` theo `endsBeforeCeo` (2 app SHA-mirror `b7abbc2f`). Banner + PeFinalizeChip đã gate sẵn.
- Migration Mig 60 + backfill Sql (dò marker "Duyệt KẾT THÚC tại" set phiếu finalize-sớm cũ; ccm-delegation comment không match → giữ false).

**Quy trình:** em-main BE core solo (schema + 6 edit + migration — tight-coupling/prod-migration) → build slnx PASS → spawn test-specialist (test-before) + implementer-frontend (FE gate mirror) song song → reviewer double-check (anh yêu cầu) → commit → cicd.

**Verify:**
- test-specialist **440 PASS** (repro `peFinButWentCeo`: cùng config-finalize NHƯNG EndedByLevelFinalize=false → EndsBeforeCeo=false) + service #6 opt-out→CEO assert false. EF-ternary translate OK.
- reviewer **PASS 0-issue** (6 category + 6 điểm nghi vấn CONFIRMED sạch): build slnx 0w/0e · test 440 · tsc×2 exit-0 · SHA mirror `b7abbc2f` · migration 3-file + backfill marker khớp exact · EF-translate confirmed (PeDraftVisibilityTests hit ternary).
- **cicd #482 PASS — bug fixed LIVE:** Mig 60 applied prod (col EXISTS default 0, backfill=0 UAT-no-marker, sys.tables **89** pre==post no-new-table), **EF-ternary SQL-Server 200-NOT-500**, PE-detail `31faef0d` **endsBeforeCeo=False phase=7** (runtime-path chạy đúng), bundle admin `Cf0o8dx8`→`DR9q2VUy`/user `x8rppRAK`→`Bgz8u4E5`, byte-#77 `endsBeforeCeo` PRESENT.

**Lessons (thêm):**
- **gotcha #78** — display/status flag phải suy RUNTIME ("instance NÀY ĐÃ làm-gì") KHÔNG CONFIG ("workflow CÓ THỂ"); đổi default opt-in/opt-out làm lệch phổ biến → soi lại mọi config-driven display.
- **#53 return-garble lần-5** — cicd-monitor #482 lại trả memory-curation ("18.1KB need under 17.1KB, trim S79/S93 stub") thay verdict. **Root-cause structural (mới):** cicd MEMORY tự-target ~17.1KB → curate-at-return → thinking-step curate = message cuối = return. Recover từ diary (#482 entry đủ Mig60+EF-200+bundle). Fix bền = giảm áp lực curate cicd (archive sớm), KHÔNG instruction. cicd = 4/5 lần garble = kẻ tái phạm dai.
- **Fix-trọn cross-stack + migration** = em-main BE core solo (reliability) + delegate FE/tests/reviewer/cicd (parallelism) — không fan-out toàn bộ vì logic tight-coupling.

**State sau Part 2:** Mig 60 · 89 bảng · 440 test · gotcha 78 · menu 54 · bundle admin `DR9q2VUy`/user `Bgz8u4E5` (Run #482) · prod 21 NCC/17 user.

---

## Part 3 (cùng ngày S97) — badge "✅ Đã được CEO duyệt" (đối xứng fix-3)

**Yêu cầu (anh Kiệt):** "những cái nào đã đc CEO duyệt thì hiện luôn là đã đc CEO duyệt" — sau khi fix-3 làm sơ đồ hiện đúng CEO đã duyệt, anh muốn badge ở DANH SÁCH để nắm ngay không cần mở phiếu.

**Fix (FE-only, `9e12e80` #483):** mở rộng `PeFinalizeChip.tsx` (single-source-of-truth pill dùng ở mọi danh sách) — thêm prop `phase?: number` + nhánh: `!endsBeforeCeo && phase===DaDuyet` → emerald badge "✅ Đã được CEO duyệt"; giữ violet "⚑ Kết thúc · X" cho `endsBeforeCeo=true`. Wire `phase={p.phase}` ở 6 call-site (PeListPanel + PurchaseEvaluationsListPage ×2 app + InboxPage + UserDashboardPage user). Mirror 2-app SHA `da9c3ed9`.

**"Miễn phí" về dữ liệu:** badge = cách đọc khác của cùng cờ `endsBeforeCeo` đã fix runtime-đúng ở fix-3 → 0 BE/migration/field mới. Lợi tức của fix root-cause đúng.

**Verify:** implementer-frontend build ×2 PASS 0-TS-err (bắt `PeListItem.phase: number` → prop `phase?: number` không narrow-union, gotcha type-plumbing) + SHA mirror. cicd #483 PASS: bundle admin `CeTA7Z82`/user `CGaAyXen`, **byte-#77 "Đã được CEO duyệt" PRESENT cả 2 app** + prev-badge additive, Mig 60 frozen, smoke 200×3 + PE-list 200.

**State sau Part 3:** Mig 60 · 89 bảng · 440 test · gotcha 78 · bundle `CeTA7Z82`/`CGaAyXen` (Run #483).

---

## Part 4 (cùng ngày S97) — check CEO-duyệt A/005 → phát hiện Mig 60 backfill WRONG-TABLE (gotcha #79)

**Anh Kiệt:** PE/2026/A/005 "7 Mat Nắp gang" — CEO đã duyệt chưa, nếu rồi hiển thị lịch sử đàng hoàng.

**Điều tra (API + sqlcmd prod):** A/005+A/008 Phase=7, endsBeforeCeo=False, con-trỏ NULL (terminal ran), lịch-sử 4 record dừng ở "[Bước 2 — Cấp 2]" (Phan Văn Chương), KHÔNG có record CEO. Đối chứng A/010: 9 record có "[Bước 3 — Cấp 1]" CEO. Verify Changelogs: A/005/A/008 fin_marker=1 (terminal Changelog "...không trình CEO]"), A/010=0.

**Chẩn đoán:** A/005/A/008 **KHÔNG qua CEO** — finalize tại CCM Cấp 2 (Phan Văn Chương duyệt-thay-CEO, auto-finalize code cũ). Lịch sử 4 record ĐÚNG. Sai = badge "Đã được CEO duyệt" (fix-4) do `EndedByLevelFinalize=0`.

**Root-cause (gotcha #79):** Mig 60 backfill query `PurchaseEvaluationApprovals.Comment` cho marker "[Duyệt KẾT THÚC tại]", nhưng marker do `LogTransitionAsync:1073` ghi vào `PurchaseEvaluationChangelogs.ContextNote` (2 bảng tách vai: Approvals=per-approver :745 · Changelogs=transition+marker) → match 0 → phiếu finalize pre-Mig60 giữ flag=0.

**Fix (Mig 61 `fb2dfa9`):** backfill ĐÚNG `Changelogs.ContextNote LIKE N'%Duyệt KẾT THÚC tại%'` + PhaseAtChange=7, idempotent, SQL-Server-only. database-agent điều tra prod + em-main verify (marker-count prod) + viết migration + reviewer-skip (đã prod-verify signal).

**Verify (cicd #484 prod):** Mig 61 applied · **backfill: A/005=1, A/008=1 (0→1) · A/010=0 (control)** · **endsBeforeCeo FLIP: A/005 True (trước False), A/010 False** · smoke 200×3 · NO-FE (data-fix). → A/005/A/008 giờ đúng "⚑ Kết thúc · CCM — không qua CEO".

**Lessons:** **#79** backfill/query transition-marker phải xác định ĐÚNG bảng writer (PE Approvals≠Changelogs) + verify marker-count prod trước · **#53 lần-6** cicd garble → recover diary #484 (cicd = 5/6 offender, structural curate-at-return).

**State sau Part 4:** Mig 61 · 89 bảng · 440 test · gotcha 79 · bundle `CeTA7Z82`/`CGaAyXen`(#483). **5 fix PE.**

---

## Part 5 (cùng ngày S97) — Deep review-before-session-end (anh yêu cầu, LIVE PROD rigor)

**Yêu cầu:** review thật kỹ S96+S97 (test trên prod THẬT) trước khi đóng. Workflow `wf_4951c347-247` 4-lane parallel.

**Kết quả:**
- 🔵 **prod-data-integrity (database-agent): CLEAN** — contingency flag-vs-marker 3 phiếu DaDuyet: A/005/A/008 flag=1+marker (finalize-CCM), A/010 flag=0+no-marker (CEO thật). **0 miss, 0 over-reach.** Mig 60/61 applied, backfill complete (paranoid alt-marker scan 0 sót). NCC=21 (DONGDUONG có), 0 price/urgent anomaly. DB11: Phase=7 pointer NULL đúng, 0 orphan changelog, 0 flag-on-non-Phase7. **#78/#79 fix xác nhận đã ăn prod.**
- 🟩 **prod-deploy-health (cicd-monitor): CLEAN** — 11 commit #478-#484 đều deploy success. Mig 61 applied (history-top). Health 4×200, SignalR wired (#25 clear), auth OK, PE-list 200. 0 regression, w3wp fresh. Bundle #484 rebuild #69-benign → live `mEGqlNWc`/`Dnrp4PU1`.
- 🟥 **code-review (reviewer): fail-return-schema → recover** — reviewer diary session-review = **PASS 0-issue** (endsBeforeCeo+Mig60: field set CHỈ nhánh finalize :873 grep-all 5-nhánh, 4 projection nhất quán, EF-translate confirmed via PeDraftVisibilityTests). em-main grep bổ-sung: **KHÔNG wrong-table pattern nào khác** (chỉ Mig 60 buggy đã sửa bởi Mig 61).
- 🟦 **feedback-capture (investigator): fail-return-schema → recover** — em-main verify: gotchas #77/#78/#79 đủ (count 79), session-log Part 1-5, feedback #53 updated ×6.

**§L.a bug-production RCA (Mig 60 wrong-table):** em ship Mig 60 với backfill query nhầm bảng (Approvals thay Changelogs) → prod badge A/005/A/008 sai. **2-fix:** code (Mig 61 đúng bảng) + guard (gotcha #79 + verify-marker-count-prod-TRƯỚC-backfill). Không escape lâu (cùng-ngày phát-hiện→fix→prod-verify). 5-why: viết backfill mà không đọc code writer (LogTransition:1073 ghi Changelog) → giả định marker ở Approvals → không verify-count trước → cicd #482 backfill=0 (đáng-lẽ cờ đỏ nhưng bỏ qua vì tưởng "no pre-Mig60 phiếu").

**Lessons:** **schema-param KHÔNG chặn return-fail** (2/4 lane fail StructuredOutput dù schema → lane quan-trọng luôn cần fallback) · **#53 lần-6** (cicd 5/6) · **backfill 0-dòng = cờ-đỏ** (đừng cho là "no data", verify bảng).

**Verdict review: PROD AN TOÀN + mọi fix đúng + learnings captured.**

**State cuối S97:** Mig 61 · 89 bảng · 440 test · gotcha 79 · menu 54 · bundle admin `mEGqlNWc`/user `Dnrp4PU1` (Run #484, #69-rebuild) · prod 21 NCC/17 user. **5 fix PE** (wording + default-flip + endsBeforeCeo-runtime + CEO-badge + backfill-wrong-table) + deep-review-verified.
