# investigator-codebase/2026-05-q2 — gist distill-gen: 1 source-verbatim: .claude/agent-memory/investigator-codebase/archive/2026-05-q2.md (4 entry → 4 cụm, 0 cặp trùng — dedup = nén câu chữ) pointer-style: substring ## TỔNG-HỢP (3-5 ý) - Cùng triệu-chứng "Lịch sử thay đổi trống" nhưng 2 root-cause KHÁC trục: một bug GHI-thiếu (`ApplyReturnModeAsync` ZERO dòng log) ⟂ một bug nghi ĐỌC-thiếu (handler ĐÃ log, nghi FE lọc) — phải tách trục ghi/đọc trước khi đặt giả-thiết chung. - Nối module thứ 2 vào schema duyệt dùng-chung: chi-phí thật nằm ở Service + terminal-state khác nhau (`DaPhatHanh` ≠ `DaDuyet`), KHÔNG ở migration — Contract thừa-hưởng 7 cờ `Allow*` miễn phí; nhưng prod 7 HĐ 100% pin V1 (6 đang giữa quy-trình) ⇒ BẮT BUỘC cùng-tồn V1+V2, KHÔNG drop V1. - Ghost-wire dạng schema lộ 2 lần: `EntityType.Workflow=5` khai trong enum nhưng 0 code ghi suốt 4+ migration; prod 0 row `ApplicableType=3` ⇒ nối xong không seed = dropdown rỗng. - Thiếu component thư-viện KHÔNG chặn việc: fe-user GAP Accordion/Collapsible/Tree/Card/Badge → đường vòng `
/` native; quyết-định kiến-trúc đứng trên số ƯỚC (~50-200 dự-án) phải khai rõ (SSH prod fail auth). - Khuôn hạ-tầng phải quy-chiếu đúng hình-thái người dùng: 1 người 5 dự-án ≠ team 5 dev ⇒ chốt Pattern C (MCP global localhost); Voyage AI 200M token/tháng free làm đổ luôn phép so chi-phí cũ. ## Entries **[cao] B2-A — "Lịch sử thay đổi" trống: 1 bug hiển-thị + 1 bug thiếu-ghi** - VIỆC: 2 báo lỗi UAT gộp 1 lượt audit 5Q: (1) chỉnh ngân-sách 2 lần → lịch-sử không hiện 2 dòng; (2) trả-lại mode "Người chỉ định" → lịch-sử không hiện. - KẾT-LUẬN: Bug 1 KHÔNG phải lỗi ghi: `PurchaseEvaluationFeatures.cs:379-387` ĐÃ log changelog (EntityType=Header + Action=Update + diff) ⇒ nghi FE lọc bỏ Header, hoặc DB UAT cũ. Bug 2 thiếu ghi thật: `PurchaseEvaluationWorkflowService.cs:215-378 ApplyReturnModeAsync` ZERO dòng log — 4 nhánh chỉ đổi con-trỏ rồi trả summary; người gọi `TransitionAsync:100 LogTransitionAsync` chỉ log chuyển-phase. Query handler `ListPurchaseEvaluationChangelogsQueryHandler:1050-1064` không lọc gì ⇒ nếu mất thì mất ở FE. Đường vá: B2 trước (thêm `Changelogs.Add()` mỗi mode, 15-25 LOC 1 file), B1 sau (Option A enum Kind 30 LOC / B mở EntityType 20 LOC / C nới filter FE 10 LOC). - BÀI-HỌC: cùng triệu-chứng nhưng một cái ghi-thiếu, một cái đọc-thiếu — tách hai trục trước khi đặt giả-thiết chung; `EntityType` quá thô (Header/Update dùng chung cho "sửa ngân sách" lẫn "sửa mục 2") nên không phân-biệt được ở tầng hiển-thị. - BẤT-NGỜ: `EntityType.Workflow=5` tồn-tại trong enum nhưng KHÔNG dòng code nào ghi vào suốt 4+ migration — schema khai một khả-năng chưa bao giờ dùng (khai-mà-không-nối, dạng ghost-wire). → substring:"1050-1064" **[cao] B2-B — Plan B Contract V2: 80% schema dùng lại, chốt cùng-tồn V1+V2** - VIỆC: pre-flight 5Q + tái-chia chunk cho việc nối Contract vào bộ máy duyệt V2 (đang chỉ có PE). - KẾT-LUẬN: `Contract.cs:8-58` 25 field, sẵn V1 (`WorkflowDefinitionId:22`, `CurrentWorkflowStepIndex:39`, `Phase:12`, `RejectedAtStepIndex:40`) thiếu `ApprovalWorkflowId` + `CurrentApprovalLevelOrder` ⇒ Mig 32 chỉ ADD COLUMN vì Mig 22 (`20260508053749_AddApprovalWorkflowsV2.cs:14-127`) đã tạo 3 bảng DÙNG CHUNG cho PE lẫn Contract; `ContractPhase.cs:14-28` đã có ChoDuyet=10 / TraLai=98 / TuChoi=99 soi gương PE; `ContractWorkflowService.cs:23-219` 220 LOC chỉ V1, sinh mã HĐ `:148-155` ở bước cuối → `DaPhatHanh` (terminal KHÁC PE `DaDuyet`). Prod: 7 HĐ 100% pin V1, 6 đang giữa quy-trình (Phase 2-8) ⇒ BẮT BUỘC cùng-tồn, KHÔNG drop V1. Mẫu clone = `ApproveV2Async` `PurchaseEvaluationWorkflowService.cs:446-634` (189 LOC: match actor → UPSERT opinion → advance → skipToFinal), điểm rẽ nhánh `:167-171`. Chia 6 chunk A1/A2/B/C/D/E theo tay-nghề (cookie-cutter → implementer, xuyên-tầng → em-main). - BÀI-HỌC: khi schema đã tách sẵn dùng-chung, chi-phí thật của "nối module thứ 2" nằm ở Service + terminal-state, không ở migration — 7 cờ `Allow*` trên `ApprovalWorkflowLevels:86-114` Contract thừa-hưởng miễn phí. - BẤT-NGỜ: (a) prod 0 row `ApplicableType=3` ⇒ nối xong mà không seed thì dropdown rỗng, phải gài seed vào chunk A2 *(chú-thích curator S170, không thuộc verbatim: đúng thứ sau này thành sự-cố Run #231 ở lane cicd)*; (b) `RejectedAtStepIndex` Contract `:40` đã chết mà chưa đánh dấu obsolete, lệch với PE `:50`; (c) 7 `ContractType` đối đầu 1 `ApplicableType=3` chung — có thể phải thêm `ContractTypes int[]` sau. → substring:"V1Pinned" **[vừa] B2-C — Plan AG: PE List "đám rừng" → cây theo dự-án** - VIỆC: anh gửi ảnh chụp phàn nàn danh sách phẳng; audit 5Q để chọn hướng. - KẾT-LUẬN: `PurchaseEvaluationsListPage.tsx:133` 3-panel `lg:grid-cols-[340px_1fr_360px]`, thân phẳng `