# cicd-monitor/2026-05-runs — gist
distill-gen: 1
source-verbatim: .claude/agent-memory/cicd-monitor/archive/2026-05-runs.md (6 tóm-tắt + 20 Run + 8 key-pattern → 10 cụm)
pointer-style: substring

## TỔNG-HỢP (3-5 ý)
1. **Chuỗi per-NV lookup discrimination (Plan K→P, Mig 29/30/31):** bug `FirstOrDefault` không lọc actor sống 2-3 ngày prod, "em main + Reviewer + Implementer all missed across 3 plan", checklist phình 6→8→9→10 điểm — với schema OR-of-N, mọi `FirstOrDefault(Order==)` là điểm mù (quét TOÀN kho, không chỉ handler lộ bệnh); record body Controller phải soi-gương param với Command (thiếu field ⇒ ASP.NET `default` im-lặng).
2. **Saga Discovery #3 chốt sau 5 lần quan-sát:** Gitea xét `paths-ignore` trên TRỌN push-range — muốn loại một biến phải tìm ca VẮNG biến mà hiện-tượng vẫn còn (#202 then-chốt); kết-luận là "lỗi" nhưng "Anomaly is BENEFICIAL".
3. **Chuỗi audit-trail S25 (#215 FAIL → #221):** phát hiện "field X không set ở N chỗ" thì vá cả N chỗ một lượt (9 site UserName); dữ-liệu cũ hỏng cứu ở FE bằng `userMap` từ dữ-liệu nhúng sẵn; Run #217 `a734bf2` = nguồn duy nhất Discovery #5 + pattern "capture pre-call mutation state".
4. **Plan T tắt DemoSeed:** cờ chống re-seed chỉ tin sau restart THẬT (`Restart-WebAppPool` + sqlcmd đếm 0); `appsettings.Production.json` bị `.gitignore` ⇒ default phải đặt ở `appsettings.json`; chính cờ này về sau nuốt seed hạ-tầng Plan B (gotcha #51 — xem gist q2).
5. **Lệnh đo sai trả về IM-LẶNG** (7 phát-hiện công-cụ: `/actions/tasks`≠runs · `updated_at` trễ ~2 phút · 429 ~5 req/phút → cache token · sqlcmd 4-backslash Discovery #5 · enum cần số Discovery #4 · 503 recycle lành · route `/api/auth/login` + field `accessToken`) — mỗi phép đo cần ca đối-chứng dương. MEMORY tự phình 35→72KB qua 8 nấc cảnh-báo leo thang không tự dừng ⇒ cần cổng ngoài. 🔐 dòng 48 verbatim: password DB prod ĐÃ redact bởi lead — ROTATE REQUIRED.

## Entries

> **Ghi-chú hợp-nhất nội-file:** phần (a) tóm-tắt ∩ phần (b) đầy-đủ của verbatim chồng 4 Run (#202 · #203 · #210 · #219) — cụm dưới đây chọn bản DÀY; riêng **#215 + #217 CHỈ có ở phần (a)** — ĐÃ GIỮ khi hợp-nhất (KHÔNG mất Run #217).

**[cao] D1 — Chuỗi per-NV lookup discrimination (Plan K → P), sinh checklist 10 điểm**
- **VIỆC:** wire cờ opt-in per-NV (Allow* theo từng slot Approver) xuyên Mig 29/30/31, rồi vá 5 lượt liên-tiếp khi lộ ra thiếu điểm nối.
- **KẾT-LUẬN:** #194 `098baa6` **PARTIAL** — schema + FE checkbox thứ 7 đã ship nhưng `AwLevelDto` (`ApprovalWorkflowV2AdminFeatures.cs:23-38`) **thiếu hẳn param** `AllowApproverSkipToFinal` (chỉ tồn-tại trong **comment dòng 60**) + `ToDto:156-158` thiếu ⇒ checkbox đọc/ghi vào hư-vô. #195 `0062fcb` vá 15 LOC ⇒ 13 key round-trip PASS, đúc **checklist 8 điểm**. #201 `fb3c22c` (Plan N) vá `PurchaseEvaluationFeatures.cs:765` thêm `&& l.ApproverUserId == currentUser.UserId` + fallback admin ⇒ **điểm 9**. #202 `a1c8386` (Plan O) quét ra **4 site cùng khuôn** (`...WorkflowService.cs:204` + `:258-260`, `...DetailFeatures.cs:75-76`, `...Features.cs:314-315`) + 3 test hồi-quy (111/111). #203 `1727bd5` (Plan P) vá Controller `TransitionPeBody` từ 3 → **7 param** ⇒ **điểm 10**.
- **BÀI-HỌC:** với schema **OR-of-N** (nhiều NV cùng một Cấp), mọi `FirstOrDefault(... Order == ...)` là **điểm mù**: phải quét TOÀN kho (`grep -n "FirstOrDefault.*Order.*=="`) chứ không chỉ sửa handler lộ bệnh. Và **record body của Controller phải soi-gương số param với Command** — thiếu field thì ASP.NET **không báo lỗi**, chỉ lặng-lẽ `default`.
- **BẤT-NGỜ:** bug sống trên prod **2-3 ngày** và **3 lượt refactor liên-tiếp (Mig 29/30/31) đều trượt** — verbatim tự khai "em main + Reviewer + Implementer **all missed across 3 plan**"; đồng-thời checklist phình **6 → 8 → 9 → 10 điểm**, tức mỗi lần "đã đủ" đều sai.
→ substring:"098baa6"

**[cao] D2 — Saga Discovery #3 (`paths-ignore` xét trọn push-range)**
*(trùng liên-file "Verify S22 chốt": bản dày = file này; cicd-monitor--2026-05-q3 B2 giữ bản mỏng — đóng-kín per-file, không xoá bên nào)*
- **VIỆC:** truy vì sao commit docs-only vẫn kích CI (ngược gotcha #41), qua 5 lần quan-sát.
- **KẾT-LUẬN:** giả-thuyết đầu (Discovery #2, Run #188): "`.claude/agent-memory/**` không nằm trong `paths-ignore` nên kích CI" → **BỊ BÁC** ở S22-chốt vì `**/*.md` khớp mọi độ sâu (`cc8a7d3` SKIP đúng). Chốt sau 3 lần củng-cố (#200 `f4055a1`, #201 `fb3c22c`, #202 `a1c8386` — lần 3 **0 file agent-memory** nên loại hẳn biến này): **Gitea xét paths-ignore trên TRỌN push-range**, chỉ cần 1 commit trung-gian có file không-bị-ignore là cả range chạy. #210 mixed-diff nên **không tính** là bằng-chứng.
- **BÀI-HỌC:** muốn loại một biến thì phải tìm ca **VẮNG biến đó mà hiện-tượng vẫn còn** (#202 là ca then-chốt) — thêm ca có-biến chỉ củng-cố giả-thuyết sai. Ghi rõ ca nào **không** tính là bằng-chứng (#210) cũng quan-trọng ngang ca tính.
- **BẤT-NGỜ:** kết-luận cuối là "lỗi" nhưng verbatim đánh giá **"Anomaly is BENEFICIAL"** — CI chạy dư lại cứu được gate verify trên commit docs; và gotcha #47 tuy bị bác vẫn **giữ giá-trị phòng-ngừa** cho file non-`.md` tương-lai.
→ substring:"f4055a1"

**[cao] D3 — Chuỗi audit-trail S25 (Plan AB → AF): 5 khuôn phục-hồi lịch-sử**
*(trùng liên-file #215: bản dày hơn ở cicd-monitor--2026-05-q3 B1; #215+#217 trong verbatim file này chỉ có ở phần tóm-tắt (a) — Run #217 = nguồn duy nhất Discovery #5 + pattern "capture pre-call mutation state", ĐÃ GIỮ)*
- **VIỆC:** làm "Lịch sử duyệt" hiển-thị đúng + cứu dữ-liệu lịch-sử đã ghi thiếu.
- **KẾT-LUẬN:** #215 **FAIL** (tie-break, xem q3) → #217 `a734bf2` (AC) thêm Approval-row nhánh Reject `Service:75-103` + prefix `[Duyệt vượt cấp tới Cấp cuối]` `:472`, xác-minh 6/6 approval có đủ 8 key → #218 `25837b6` (AC2) FE dựng **synthetic Reject row** từ Changelog `entityType=5` (20 entry, 8 Workflow-entry, khớp keyword "Trả về") → #219 `0aaf2df` (AD) **bỏ badge from/to** + `extractNextTargetHint()` parse `\[Bước (\d+) — Cấp (\d+)\]` → #220 `9ea62be` (AE) vá **9 site** `UserName = currentUser.FullName ?? currentUser.Email` (4 Features + 5 DetailFeatures) → #221 `506cada` (AF) FE `userMap` useMemo dựng từ 5 nguồn nhúng sẵn (drafter/approvals/levelOpinions/departmentOpinions/approvalFlow.steps), 3 nấc: tin `entry.userName` → tra map → "Hệ thống". Cumulative 7 commit, 1 catch + 6 PASS, 0 regression.
- **BÀI-HỌC:** khi phát-hiện "field X không set ở N chỗ giống nhau", **vá cả N chỗ một lượt** thay vì chỗ báo lỗi (AE: 1 lộ + 8 phòng-xa). Dữ-liệu cũ đã hỏng thì **cứu ở FE bằng dữ-liệu đã nhúng sẵn trong bundle** — không cần đổi hợp-đồng API, né luôn ràng-buộc `/api/users` chỉ-admin.
- **BẤT-NGỜ:** bằng-chứng chốt Bug 1 là **một dòng dữ-liệu cũ còn sót**: entry Budget Adjust `02:31:39` `entityType=1` có `userName=""` — tức prod **tự lưu giữ vật-chứng tiền-sửa** để đối-chứng sau deploy; và AD **bỏ bớt** thông-tin (badge from/to) lại làm UI đúng hơn vì phần lớn là `ChoDuyet→ChoDuyet` tự-lặp.
→ substring:"a734bf2"

**[cao] D5 — Plan T tắt DemoSeed + chứng bằng restart THẬT**
- **VIỆC:** chặn seed demo tự mọc lại trên prod (#207 `0b97840`, #208 `7b7b28f`).
- **KẾT-LUẬN:** bọc 5 method demo trong `if (!demoSeedDisabled)` (V1 workflow ×2, demo Contract, demo PE, sample AW V2); **GIỮ** 8 nhóm seed hạ-tầng (Roles/Admin/30 user UAT/master/template/catalog/backfill). T5 xoá 4 PE + 1 V2 + 2 V1 + cascade; **T6 ép `Restart-WebAppPool SolutionErp-Api`** rồi sqlcmd: PE=0, V2=0, V1=0, master còn nguyên (Users 33 · Suppliers 19 · Projects 9 · Contracts 7). Tổng R+S+T ≈ **720 dòng** bị xoá, cờ tồn-tại vĩnh-viễn.
- **BÀI-HỌC:** cờ chống re-seed chỉ được tin sau khi **khởi-động lại thật** rồi đếm lại — không tin vào việc đọc code. Và vì `appsettings.Production.json` bị `.gitignore`, giá-trị mặc-định phải đặt ở `appsettings.json` (thứ đi qua git) mới tới được prod.
- **BẤT-NGỜ:** chính cái cờ chứng-minh-tốt này **về sau nuốt luôn seed hạ-tầng** của Plan B (Run #231/#232, gotcha #51) — biện-pháp đúng ở lớp này thành bẫy ở lớp khác.
→ substring:"Restart-WebAppPool"

**[cao] D6 — 7 phát-hiện về CÔNG-CỤ ĐO (dễ mất nhất khi nén)**
- **VIỆC:** gom các bài học về chính phép đo, rải rác trong 6 Run.
- **KẾT-LUẬN:** (1) API liệt-kê Run của Gitea là `/api/v1/repos/.../actions/**tasks**`, `/actions/runs` trả **404**; đọc **không cần auth**. (2) `updated_at` trong bảng task **trễ ~2 phút** — phải đối-chiếu `LastWriteTime` của `.dll/.html` trên VPS. (3) đăng-nhập **~5 request/phút là 429** ⇒ cache token ra `%TEMP%\*_token.txt`, 2 login + 8 lệnh dùng lại (Run #188 dính 429, S22-chốt hết dính). (4) **Discovery #5:** sqlcmd Windows-auth qua ssh cần escape **4 dấu `\\\\SQLEXPRESS`** — 2 dấu ra **rỗng KHÔNG báo lỗi**. (5) **Discovery #4:** ASP.NET Core 10 record + enum đòi **số**, không có `JsonStringEnumConverter` (grep `Program.cs` = 0 hit) ⇒ payload mẫu `"Drafter"` trong brief là **sai, fail 400**. (6) 503 giữa lúc IIS recycle (~5-15s) là **bình-thường, tự khỏi**. (7) route thật là `/api/auth/login` (không `/api/v1/...`), field token là `accessToken` (không `token`); `/api/me` + `/api/menu-keys` **404 vì không tồn-tại**, không phải regression.
- **BÀI-HỌC:** **lệnh đo sai trả về IM-LẶNG** (2-backslash ra rỗng; enum string ra 400; endpoint sai ra 404) — mỗi phép đo phải có ca đối-chứng dương, và phải phân-biệt "0 kết-quả" với "đo hỏng".
- **BẤT-NGỜ:** brief do người ra lệnh viết lại chính là nguồn sai (payload `"Drafter"`) — và verbatim ghi thẳng "brief example payload was misleading", không đổ cho hệ-thống.
→ substring:"JsonStringEnumConverter"

**[cao] D7 — MEMORY phình 35→72KB: 8 lần cảnh-báo leo thang mà không ai dừng**
- **VIỆC:** vai tự theo-dõi kích-thước diary của chính mình sau mỗi entry.
- **KẾT-LUẬN:** chuỗi tự khai: ~35KB ("approaching") → ~40 → ~43 ("over 25KB trigger, under 50KB hard") → ~50 ("at hard threshold") → ~58 ("**REQUIRED** ARCHIVE ... BEFORE next entry") → ~60 ("**MANDATORY**") → ~62 ("**DEDICATED CURATION SESSION PRIORITY NEXT**") → ~66→69 ("priority MAX") → ~72 ("**priority MAX REINFORCED**" — chữ nguồn viết `priority` thường). Chốt chỉ xảy ra khi có **phiên curate riêng** (chính file archive này), kéo về mục-tiêu ~25-30KB.
- **BÀI-HỌC:** cảnh-báo do **chính kẻ đang phình** phát ra thì không tự dừng được — cần **cổng ngoài** (ngưỡng cứng chặn ghi) hoặc **việc riêng có chủ**; và ngưỡng "mềm 25KB / cứng 50KB" bị vượt **gần 3 lần** vẫn chỉ sinh thêm chữ in-đậm.
- **BẤT-NGỜ:** mức độ khẩn-cấp trong câu chữ leo thang **8 nấc** (approaching → REQUIRED → MANDATORY → priority MAX → REINFORCED) — bản thân nó là dữ-liệu: leo-thang ngôn-từ **không** thay cho cơ-chế.
→ substring:"priority MAX REINFORCED"

**[cao] D10 — Tự khai tác-dụng-phụ và giới-hạn phép đo**
- **VIỆC:** ghi lại thứ phép kiểm-chứng làm hỏng + thứ nó KHÔNG chứng được.
- **KẾT-LUẬN:** kiểm-chứng bằng POST thật đã **đổi state prod**: PE/2026/A/025 phase 10→98 (admin bypass qua `if (isAdmin) return` **dòng 186**), khai rõ "admin bypass là thiết-kế, không phải regression", đề bro tự chọn revert hay giữ; PE/2026/A/021 trả 409 ⇒ **không đổi state (atomic)** — có kiểm lại chứ không đoán. Giới-hạn tự khai: NV Test **không đóng vai approver** (đã quét 20 PE) nên biến-thể actor **không khả-thi**; #194 dùng chữ **"presumed" 4 lần** cho phần không đo được; #219 **bỏ qua sqlcmd** vì thiếu `PROD_DB_PASSWORD`, chấp-nhận verify gián-tiếp và nói rõ là gián-tiếp.
- **BÀI-HỌC:** báo-cáo verify phải tách 3 mức: **đo trực-tiếp** / **suy ra** (`inferred`, `presumed`) / **không đo được** — và ghi luôn phép đo đã làm bẩn dữ-liệu.
- **BẤT-NGỜ:** ca 409 lại là **bằng-chứng dương mạnh nhất** cho Plan O: lỗi 409 "mode mismatch" chứng-tỏ đã qua được cửa actor-check (403 cũ), tức **thất-bại đúng chỗ = thành-công**.
→ substring:"no state change (atomic)"

**[vừa] D4 — Chuỗi Mig 28→31 + backfill có kiểm số**
- **VIỆC:** 5 lượt deploy schema per-NV/per-Level.
- **KẾT-LUẬN:** Mig 28 `AddAdvancedOptionsToApprovalWorkflows` (#186, 6 cờ Allow* mức workflow, `allowReturnToDrafter=true` mặc-định + 5 false) → Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (#187): Levels **+5** Allow*, Users **+1**, ApprovalWorkflows **−6**; backfill **48/48** Levels=1 và **0/13** Users=1 (đúng vì nguồn `false`) → Plan D/C/E (#188): `/api/users` +`allowDrafterSkipToFinal`, PATCH admin 204 / nv.test **403**, scope V2 chặt (nv.test 8 PE < admin 17) → Mig 30 `AddAllowApproverEditBudgetToLevels` (S22-chốt): `currentLevelOptions` 6 key, `AwLevelDto` 12 key → Mig 31 `RefactorSkipToFinalToApproverLevel` (#194): Levels +1, Users −1, endpoint zombie `/allow-skip-final` trả **404** đúng ý.
- **BÀI-HỌC:** backfill phải kiểm **cả hai chiều** — "48/48 = 1" và "**0/13** = 1" đều là kết-quả ĐÚNG cần xác-nhận; số 0 ở đây là bằng-chứng, không phải thiếu-sót. Dọn field cũ thì phải kiểm **cả endpoint lẫn key trong DTO đã biến mất**.
- **BẤT-NGỜ:** cùng một tính-năng bị dời chỗ 3 lần (workflow → user → level) trong ~2 ngày; test-gate suy-ra từ "deploy chạy được" chứ không đọc trực-tiếp (`test gate inferred PASS`).
→ substring:"AddAdvancedOptionsToApprovalWorkflows"

**[thấp] D8 — Đánh-bóng UX + 1 ca revert trong phiên**
- **VIỆC:** 4 Run FE thuần: #204 banner `mx-5`→`mb-3`; #209 sidebar `truncate` (`min-w-0 flex-1` + `shrink-0` + `title=` tooltip); #210 widen `w-60 → w-72 xl:w-80` **kèm revert Plan U**; #214 wrap nhãn dài.
- **KẾT-LUẬN:** 4/4 PASS, bundle rotate đúng kỳ vọng mỗi lần; menu `Pe_DuyetNcc_WfView` Order=2 + họ `Pe_DuyetNccPhuongAn` Order 6-10, tổng **1-10 liền mạch không hở/không chồng** (seed idempotent).
- **BÀI-HỌC:** đổi FE-thuần vẫn verify được gián-tiếp bằng **bundle-hash làm proxy**; nhưng khi một chunk bị revert trong cùng phiên thì hash phản-ánh **trạng-thái tích-luỹ**, không phải diff của chunk ⇒ đừng đọc hash như chữ-ký của một thay-đổi đơn lẻ.
- **BẤT-NGỜ:** phép đo bundle của Plan P **bắt nhầm** thay-đổi của Run #204 đang bay cùng lúc (`QZIPWD-g`/`DaLTMGcx`) — vẫn kết-luận đúng nhờ đối-chiếu **diff có 0 file FE**, tức dùng nguồn thứ hai để cứu phép đo bị nhiễu.
→ substring:"ac2c859"

**[cao] D9 — "Key patterns extracted S21-S25" (giữ gần như nguyên)**
- **VIỆC:** phần (c) đã là bản chưng-cất 8 mục do chính vai viết.
- **KẾT-LUẬN:** 8 mục = Discovery #3 · Discovery #4 · Discovery #5 · **checklist 10 điểm per-NV** (chép ĐỦ 10, nguyên-văn rút gọn — vá P2a-MAJOR-2 @S170): **(1)** Domain entity field · **(2)** EF config `HasDefaultValue(false)` · **(3)** Migration 3-file rule (Up + Designer + Snapshot) · **(4)** Service handler read field · **(5)** Domain DTO + Application DTO mirror · **(6)** Designer FE checkbox inline · **(7)** Admin overview `AwLevelDto` record + `ToDto` ctor · **(8)** `CreateAwLevelInput` record + Update mutation handler · **(9)** Lookup discrimination in handler (`FirstOrDefault` ADD `ApproverUserId == actorId` filter, fallback admin) · **(10)** Controller body record mirror count check (`[FromBody]` record param count = Command record param count) — kèm gotcha #48 + cách vá · "CI test-gate bắt được lỗi mà UAT-skip-test bỏ sót" · FE merge synthetic + userMap · capture pre-call mutation state.
- **BÀI-HỌC:** khi nguồn đã có lớp chưng-cất, việc nén nên **giữ lớp đó gần như nguyên văn** và cắt ở lớp Run bên dưới — nén lại cái đã nén là chỗ dễ mất mã-số (#48, điểm 9/10) nhất. *(Chính draft này lượt đầu đã dính đúng bẫy đó: tuyên "bản duy nhất đủ 10" rồi chỉ giữ ~6 — reviewer P2a bắt, vá bằng chép đủ 10 ở trên.)*
- **BẤT-NGỜ:** checklist 10 điểm ở verbatim là **bản duy nhất liệt-kê đủ 10** trong toàn batch A — mọi chỗ khác chỉ nhắc số "8"/"9"/"10 surface points" mà không kèm nội-dung (đủ 10 mục gốc: verbatim vùng "Key patterns extracted" mục 4).
→ substring:"Key patterns extracted"

## Phụ-lục token (coverage)

- **gotcha#:** `#41` (path filter — dẫn ≥6 lần) · `#44` (silent 403 — "fix confirmed" ở #210) · `#45` (fix Trả lại, Run #186) · `#47` (agent-memory — **disproven**, giữ vai PREVENTIVE) · `#48` (**NEW** multi-`Changelog.Add` + SQLite frozen-clock tie-break)
- **Discovery#:** `#1` rate-limit 429 ~5 req/phút · `#2` `.claude/agent-memory/**` không nằm trong paths-ignore (**về sau bị bác**) · `#3` Gitea xét TRỌN push-range (số lần củng-cố: NGUỒN TỰ MÂU-THUẪN — tiêu-đề verbatim ghi "RE-CONFIRMED 4th time" nhưng thân ngay dưới ghi #210 "does NOT reinforce" ⇒ 3 lần củng-cố THẬT #200/#201/#202 + 1 lần #210 không-tính; thân-draft dùng số 3 là theo vế thân) · `#4` ASP.NET Core 10 record+enum cần **số** · `#5` sqlcmd cần `\\\\SQLEXPRESS` **4 backslash** · *(không đánh số)* `/actions/tasks` ≠ `/actions/runs` (#186) · `updated_at` trễ ~2 phút (#187) · "Discovery 1" 503 giữa deploy + "Discovery 2" `/api/auth/login` + field `accessToken` (#221)
- **surprise:** `CRITICAL FAIL — K3 DTO mirror INCOMPLETE` · "`AllowApproverSkipToFinal` mentioned **only in comment line 60**, NOT as actual record param" · "**em main + Reviewer + Implementer all missed across 3 plan**" · "Bug present **2 days prod**" / "**3-day prod bug latency**" · "Spec hypothesis ... **disproven for this commit**" · "**Anomaly is BENEFICIAL** — catches verify gate even on docs-only tip" · "PROVES ACTOR VALIDATION SUCCESS ... 409 mode mismatch NOT 403" · "First attempt with **string enum** `\"Drafter\"` → HTTP 400 ... Brief example payload was **misleading**" · "Test 2 HTTP 409 = handler threw exception → **no state change (atomic)**" · "NV Test actor variant **not feasible** (scanned 20 PEs…)" *(trích cắt — nguồn còn "— NV Test = Drafter not approver")* · "the Budget Adjust entry ... has `userName=\"\"` empty — this is the **EXACT pre-fix Bug 1 evidence**" · "**Side effect logged:** Admin transition on PE/2026/A/025 mutated phase 10→98" · "Run #190 ... **CANCELLED** by concurrency ... normal not a fail" · "`/api/me` + `/api/menu-keys` returned 404 (route names different) — **Not regression**" · "Memory size ... **priority MAX REINFORCED**" · "8 surface points checklist (**NOT 6**)" · "prophylactic codebase scan is needed"
- **guard:** `if (isAdmin) return` (**line 186**, admin bypass CỐ Ý) · `if (!isAdmin && currentLevel is not null)` (**line 264**) · `&& l.ApproverUserId == actorId` + `throw if null` (4 site Plan O) · fallback `?? curStep?.Levels.FirstOrDefault(...)` cho admin/non-approver (Plan N) · `[Authorize]` class-level + `GetWorkflowsForUserAsync` auth filter (fix #44) · `if (!demoSeedDisabled)` bọc 5 seed demo (Plan T) · `.Where(c => c.Summary!.Contains("Chuyển phase"))` (vá #48) · `HasDefaultValue(false)` (điểm 2 checklist) · **Migration 3-file rule** (Up + Designer + Snapshot — điểm 3) · `[FromBody]` record param count = Command param count (điểm 10) · seed Order shift **idempotent** (1-10 liền mạch) · token-cache `%TEMP%\*_token.txt` chống 429 · test-gate CI chặn trước deploy
- **root-cause:** `FirstOrDefault(l => l.Order == curLevelOrder)` **không lọc actor** ⇒ luôn lấy `Levels[0]` theo thứ-tự DB ⇒ cờ per-NV admin tick bị bỏ qua (4 site cùng bệnh) · Controller `TransitionPeBody` 3 field ⟂ Command 7 field ⇒ ASP.NET `default` **im-lặng** 3 field ⇒ F1/F2 không tới handler suốt 2 ngày · `AwLevelDto` thiếu param + `ToDto` thiếu ctor-arg ⇒ cột DB có mà API không trả · nhiều `Changelog.Add()` cùng `SaveChangesAsync` + SQLite frozen-clock ⇒ `OrderByDescending(CreatedAt)` tie-break bất-định · Gitea xét paths-ignore trên trọn push-range ⇒ tip docs-only vẫn chạy · thiếu `JsonStringEnumConverter` ⇒ enum dạng chuỗi fail 400 · `\\SQLEXPRESS` (2 backslash) ⇒ **0 output, im-lặng** · `UserName` không set ở 9 site `Changelog.Add()` ⇒ cột người-dùng rỗng
- **file:line / định-danh (BE):** `ApprovalWorkflowV2AdminFeatures.cs` **:23-38** (`AwLevelDto` record), **comment :60**, `ToDto` **:156-158**, vùng **:184-238** · `PurchaseEvaluationFeatures.cs` **:765** (per-NV lookup), **:314-315** (AdjustBudget) · `PurchaseEvaluationWorkflowService.cs` **:204** (`EnsureCanRejectV2Async`), **:258-260** (`ApplyReturnModeAsync`), **:264** (guard), **:186** (admin bypass), **:75-103** (nhánh Reject Plan AC), **:472** (prefix `[Duyệt vượt cấp tới Cấp cuối]`), **:287-333** (F1 edge Bước 1), **:268-275** (F1.Drafter giữ TraLai) · `PurchaseEvaluationDetailFeatures.cs` **:75-76** (`EnsureEditableForDetailsAsync`) · Controller `TransitionPeBody` **:267** (3 field) → **:280-286** (7 param), `mediator.Send` **:76-78**, marker Plan P **:71-75** · `TransitionPurchaseEvaluationCommand` **:395** · `Program.cs` (0 hit `JsonStringEnumConverter`/`AddJsonOptions`) · `DbInitializer.cs` · `MenuKeys.cs` · `appsettings.json` / `appsettings.Development.json` / `appsettings.Production.json` (**bị `.gitignore`**)
- **file:line / định-danh (test + FE):** test `ApplyReturnMode_OneStep_AtStep1` **line 350** · `ApplyReturnMode_OneLevel_AtStep1Level1` **line 308** · `GetPurchaseEvaluationCurrentLevelOptionsTests.cs` (2 method) · `PurchaseEvaluationPerNvLookupRegressionTests.cs` (3 test) · `PeDetailTabs.tsx` (fe-user + fe-admin, mirror **§3.9**) · `PeWorkflowPanel.tsx` · `types/purchaseEvaluation.ts` (`WorkflowReturnMode = { OneLevel:1, OneStep:2, Assignee:3, Drafter:4 }`) · `Layout.tsx` (`w-60 → w-72 xl:w-80`; `min-w-0 flex-1` + `shrink-0` + `truncate` + `title=`) · `WorkflowMatrixViewPage.tsx` · `approvalWorkflowV2.ts` · `App.tsx` · helper `extractNextTargetHint(comment, decision)` · regex `\[Bước (\d+) — Cấp (\d+)\]` · `resolveActorName`/`resolveUserName` + `userMap` useMemo
- **Run / sha / bundle:** #186 `eea86fd` (id=300) · #187 `c0af9e0` (301) · #188 `a74e671` (302) · #189 `40f64c6` · #190 `8185070` **CANCELLED** · #191 `0e70789` · #192 `30d51c8` · #193 `b04a11a` (307) · #194 `098baa6` (308) **PARTIAL** · #195 `0062fcb` (309) · #199 `da30e27` · #200 `f4055a1` (314) · #201 `fb3c22c` (315) · #202 `a1c8386` (316) · #203 `1727bd5` (317) · #204 `108268a` · #207 `0b97840` · #208 `7b7b28f` · #209 `86d8806` · #210 `ac2c859` (324) · #214 `ee0902a` (328) · #217 `a734bf2` · #218 `25837b6` (332) · #219 `0aaf2df` (333) · #220 `9ea62be` (334) · #221 `506cada` (335) · commit lẻ: `c2042ef`/`4dd6f9c`/`508b17a` (Plan M) · `0326458` (N1+N2) · `ae01ca5` (O1-O5) · `ee776d5`/`c667802` (AA) · `b079b27` (BE Mig 30) · `cc8a7d3` (SKIP) · `3d725c4..cc8a7d3` · `e23f51c..506cada` · bundle-chain admin `CzesdXLh→D5l49-70→Cclc8Uwu→CpI5OL8n→CRsX6cFo→D_JENTBi→QZIPWD-g→Dmk--X6w→CZdXQ2eo→CrHpBYFM→B5iZMa7g→CDVnRDe6→DR95zKWg→C8TvDy7r`; user `DP-gH4LW→B6N5hq3d→d064StNa→X7qb4Zl4→COJhbRxy→DaLTMGcx→Bd4gh3Tp→DCwhhey2→C3bCWZ90→CHJDH3M2→gAFN3NVx→BAj_Yaj5→BvcWrq2z`
- **Mig / dữ-liệu prod:** Mig 28 `20260513114505_AddAdvancedOptionsToApprovalWorkflows` · Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (backfill **48/48** Levels · **0/13** Users) · Mig 30 `20260513160703_AddAllowApproverEditBudgetToLevels` · Mig 31 `20260514160124_RefactorSkipToFinalToApproverLevel` · test-count chuỗi **84 → 103 → 104 → 106 → 108 → 111** · Users **33** · Suppliers 19 · Projects 9 · Contracts 7 · PE-ID `3248f2f9-c6e9-43ff-a4ca-067ffecf9f36` (PE/2026/A/032) · `98736f06-...` (A/025) · `59176ad5-...` (A/027) · `6148ba7a-...` (A/026) · `f9476dad-...` (A/021) · workflow `bf31f120` · token len admin **468** / nv.test **477** · `~720 rows wiped`
- **🔐 secret (đã xử-lý):** dòng **48** verbatim — `appsettings.Production.json` tại `C:\inetpub\solution-erp\api\`, `user=vrapp`, password prod **ĐÃ ĐƯỢC LEAD REDACT** thành nhãn `pwd=[đã che 71b7740a — ROTATE REQUIRED]` — gist chép NHÃN CHE này, TUYỆT ĐỐI không tra/chép giá-trị gốc từ git history; **ROTATE REQUIRED** vẫn treo (mật-khẩu từng nằm trong lịch-sử git)
- **meta / size-trend:** `~35KB → ~40 → ~43 → ~50 → ~58 → ~60 → ~62 → ~66 → ~69 → ~72KB` · ngưỡng mềm **25KB** / cứng **50KB** · FIFO `~8 → ~9 → ~10 → ~11 → ~12 → ~13 entry` · token-cost mỗi lượt `~10k-28k` · `feedback_md_compact_narrative.md` (§6.5) · `feedback_uat_skip_verify` · `feedback_per_nv_permission_scope.md`

### Bổ-sung coverage @P3-gate S170 — ⚠️ LƯỚI GREP THUẦN, KHÔNG PHẢI DỮ KIỆN (đoạn trích sau mỗi token là phần ĐẦU DÒNG verbatim chứa token — dòng archive là mega-line nên đoạn trích thường KHÔNG liên-quan token; CHỈ dùng để coverage-diff grep trúng, đọc nội-dung thì tra verbatim theo token — reviewer P3 @S170)

- `#216` — > **Target slim MEMORY.md after archive:** ~25-30KB (~5 entries kept: Run #215 fail lesson + Run #216 fix + Run #222-#227 S26 + setup baseline).…
- `#222` — > **Target slim MEMORY.md after archive:** ~25-30KB (~5 entries kept: Run #215 fail lesson + Run #216 fix + Run #222-#227 S26 + setup baseline).…
- `#227` — > **Target slim MEMORY.md after archive:** ~25-30KB (~5 entries kept: Run #215 fail lesson + Run #216 fix + Run #222-#227 S26 + setup baseline).…
- `83c9f7b` — (S23 t3 Plan M push range `83c9f7b..f4055a1` 4 commits: Chunk M1 BE Service F1.OneLevel/OneStep edge case Bước 1 → reset (0,1) giữ ChoDuyet + a…
- `8c05947` — **PASS** verdict — fix Bug 3 "Lịch sử duyệt" panel show Trả lại + Duyệt vượt cấp. Push `8c05947..a734bf2` 1 commit Plan AC. Duratio…
- `Mig 26` — (S25 t4 Plan AC2 — FE merge view recover historical Reject events PE cũ — push `a734bf2..25837b6` 1 commit Plan AC2 FE-only). Duration 3m23s base…
- `Mig 27` — (S23 t11 Plan U FE sidebar truncate). Commit 2 file FE × 2 app mirror `Layout.tsx` — MenuNodeRenderer button (accordion toggle) + MenuLeaf NavLink …
- `PurchaseEvaluationDetailFeatures.cs:75` — (S23 t5 Plan O HOTFIX cascade 4 lookup sites — push range `fb3c22c..a1c8386` 2 commits: `ae01ca5` Chunk O1-O5 atomic BE fix 4 lookup sites cùng pat…
- `PurchaseEvaluationFeatures.cs:314` — (S23 t5 Plan O HOTFIX cascade 4 lookup sites — push range `fb3c22c..a1c8386` 2 commits: `ae01ca5` Chunk O1-O5 atomic BE fix 4 lookup sites cùng pat…
- `PurchaseEvaluationWorkflowService.cs:204` — - `PurchaseEvaluationWorkflowService.cs:204` `step.Levels.FirstOrDefault(l => l.Order == curLvl && l.ApproverUserId == actorId)` + throw if null…
- `PurchaseEvaluationWorkflowService.cs:258` — - `PurchaseEvaluationWorkflowService.cs:258-260` `step.Levels.FirstOrDefault(l => l.Order == evaluation.CurrentApprovalLevelOrder && l.ApproverUserId …
- `Run #187` — ### 2026-05-13 20:12-20:15 — Run #187 id=301 sha=c0af9e0 VERDICT=PASS…
- `Run #193` — (S22 chốt — em main spawn cumulative verify cuối S22). Latest CI run #193 id=307 sha=`b04a11a` (S22+5 Chunk B FE) success at 23:16. **Tip commit…
- `Run #194` — ### 2026-05-14 ~16:30 — Run #194 id=308 sha=`098baa6` VERDICT=PARTIAL…
- `Run #195` — ### 2026-05-14/15 — Run #195 id=309 sha=`0062fcb` VERDICT=PASS…
- `Run #199` — (S23 t3 Plan M push range `83c9f7b..f4055a1` 4 commits: Chunk M1 BE Service F1.OneLevel/OneStep edge case Bước 1 → reset (0,1) giữ ChoDuyet + a…
- `Run #200` — ### 2026-05-15 ~11:21-11:25 — Run #200 id=314 sha=`f4055a1` VERDICT=PASS…
- `Run #201` — ### 2026-05-15 12:42-12:45 — Run #201 id=315 sha=`fb3c22c` VERDICT=PASS…
- `Run #202` — ### 2026-05-15 13:18 — added S23 t5 Plan O HOTFIX Run #202 entry…
- `Run #203` — ### 2026-05-15 13:45 — added S23 t6 Plan P HOTFIX Run #203 entry…
- `Run #207` — ### 2026-05-15 ~14:15 — Run #207 sha=`0b97840` VERDICT=PASS…
- `Run #208` — ### 2026-05-15 ~14:30 — Run #208 sha=`7b7b28f` VERDICT=PASS…
- `Run #209` — ### 2026-05-15 ~14:50 — Run #209 sha=`86d8806` VERDICT=PASS…
- `Run #210` — ### 2026-05-15 15:25 — added S24 t1 Plan AA Run #210 entry…
- `Run #214` — id=328 sha=`ee0902a` **VERDICT=PASS** (bundle baseline admin `CZdXQ2eo` + user `DCwhhey2`, Mig 31 TOP 1 unchanged, smoke 5/5 200) — *dòng này @P3-mức-3 thay đoạn trích đầu-dòng cũ vốn dính thân entry Run #215 FAIL ⇒ gán verdict ngược (reviewer P3-B1)*
- `Run #215` — > **Target slim MEMORY.md after archive:** ~25-30KB (~5 entries kept: Run #215 fail lesson + Run #216 fix + Run #222-#227 S26 + setup baseline).…
- `Run #216` — > **Target slim MEMORY.md after archive:** ~25-30KB (~5 entries kept: Run #215 fail lesson + Run #216 fix + Run #222-#227 S26 + setup baseline).…
- `Run #218` — ### 2026-05-19 11:00-11:03 — Run #218 id=332 sha=`25837b6` VERDICT=PASS…
- `Run #219` — ### 2026-05-19 11:25 — added S25 t5 Plan AD Run #219 entry…
- `Run #220` — ### 2026-05-19 12:33-12:36 — Run #220 id=334 sha=`9ea62be` VERDICT=PASS…
- `Run #221` — ### 2026-05-19 13:05-13:08 — Run #221 id=335 sha=`506cada` VERDICT=PASS…
- `Run #222` — > **Target slim MEMORY.md after archive:** ~25-30KB (~5 entries kept: Run #215 fail lesson + Run #216 fix + Run #222-#227 S26 + setup baseline).…
- `a1a910f` — (S24 t1 Plan AA — IsUserSelectable filter + WfView menu + sidebar widen). Push range `a1a910f..ac2c859` 3 commits: `ee776d5` Chunk A BE+Layout (6 fi…
- `eb106f2` — (Plan K 8 commits S23 t1 Mig 31 F2 refactor sang per-Approver-slot — push range `eb106f2..098baa6`). Run completed `status=success`. **Mig 31 prod T…
