# Archive 2026-06 — investigator-codebase FIFO overflow

> Moved from MEMORY.md L1 (S57bis curate 2026-06-11). Verbatim.

- **2026-06-08 (S50 P11-C Vehicle+Driver — HrmConfigs add-kind pattern VERIFIED on-disk, RAG down):** ⭐ **HrmConfigs KHÔNG có "kind enum/registry" backend** — 4 entity RIÊNG (LeaveType/Holiday/ShiftPattern/OtPolicy), NOT discriminated table. "kind" chỉ FE: `HrmConfigKind` union `fe-admin/src/types/hrm-config.ts:4` + route param. **Add 1 kind = mirror FULL entity stack 11 chỗ:** BE (1) Domain `Hrm/{X}.cs` AuditableEntity soft-delete (2) `Configurations/{X}Configuration.cs` `.ToTable+.HasIndex(Code).IsUnique()` (3) `ApplicationDbContext.cs:95-98` DbSet (4) `IApplicationDbContext.cs:102-105` DbSet (5) `HrmConfigFeatures.cs` +Region N (DTO+List/Create/Update/Delete handler+validator, mega 4-region :30/125/222/328) (6) `HrmConfigsController.cs` +4 route hardcode `[HttpGet/Post/Put/Delete("{kind}")]` (Post/Put/Del `[Authorize(Roles="Admin")]`, Get chỉ `[Authorize]`) (7) `DbInitializer.cs:2329 SeedHrmConfigsAsync` +if-block + skip-guard :2331 phải +`&& OtPoliciesNew.AnyAsync()` (8) `MenuKeys.cs:88-92` +const + `:149 All[]` (Admin auto-grant `SeedAdminPermissionsAsync` loop idempotent). FE (9) `HrmConfigsPage.tsx:45 KIND_CONFIG` +entry + `:114 KINDS[]` + `:379 renderCells` branch + `:166 smart-defaults` + types/hrm-config.ts DTO (10) `App.tsx:90` route `/hrm/configs/:kind` SẴN catch-all → KHÔNG cần sửa, chỉ +menuKeys (11) `menuKeys.ts:38-42` + `Layout.tsx:60-63 staticMap`. **gotcha #57 CONFIRMED còn trần:** `LeaveTypeConfiguration.cs:19` + `ShiftPatternConfiguration.cs:19` + `OtPolicyConfiguration.cs:22` `.IsUnique()` CHƯA `.HasFilter("[IsDeleted]=0")` (chỉ `HolidayConfiguration.cs:18` đã fix Mig 43). → Vehicle/Driver Code UNIQUE PHẢI add filter ngay từ đầu. **Mig 44 BẮT BUỘC CREATE TABLE** (mỗi kind = bảng riêng, NOT discriminated → +2 bảng Vehicles+Drivers, không phải seed-only). **VehicleBooking** (`Office/VehicleBooking.cs:13-19`) pure free-text `VehicleLicense/VehicleName/DriverName` string, NO `VehicleId/DriverId` FK (grep empty) → P11-C catalog-only, FK link defer Mig sau. Latest Mig=43 `FilterHolidayUniqueIndexByIsDeleted` (`20260601064128`), next=44. Tag `[p11-c, hrmconfig-add-kind, gotcha57, on-disk-verify]`.

- **2026-06-07 (S50 wave `h2-verify` — B6 guardrail audit, read-only) [em main scribe from findings + H2 harvest]:** Verified B6 wave-isolation **3/3 PASS**. **B6 = TWO complementary rules:** (a) transient `wave-*/` + `agent-teams/` gitignored (`.gitignore:93-94`) → audit-noise=0; (b) canonical `agent-memory/**/MEMORY.md` TRACKED → rogue sub-write surfaces in `git status`. `git check-ignore -v` = ground-truth verifier BOTH directions (matched rule:line for ignored; empty for tracked). ⚠️ **Ordering gotcha:** wave/team patterns MUST sit AFTER `!.claude/**` (`.gitignore:82-83`) to win via last-match (`:91` documents intent) — else `!.claude/**` un-ignores everything. All 10 MEMORY.md tracked (roster 8→10). **Surprise (cross-cutting, both wave subs):** Bash tool = `/usr/bin/bash` NOT PowerShell despite env=PowerShell → `Get-ChildItem`/`Select-String`/`Test-Path` fail (exit 2/127); read-only Bash-only subs MUST use POSIX (`git ls-files`/`grep`/`ls`). Tag [wave-h2, b6-isolation, posix-not-pwsh].


- **2026-06-08 (S51 gotcha #57 EXTENSION reachability audit — 6 candidate, RAG down, on-disk only):** ⭐ Bug class = soft-delete + bare `.IsUnique()` on Code → recreate-after-delete throws DbUpdateException 500. Verdict 6 cand: **FIX 3 (Master)** Department/Supplier/Project (`Department/Supplier/ProjectConfiguration.cs:18/24/19` bare unique). ALL = AuditableEntity + **GLOBAL `HasQueryFilter(!IsDeleted)`** + Delete via `.Remove()` → `AuditingInterceptor.cs` (State Deleted→Modified, IsDeleted=true) + Create `AnyAsync(x=>x.Code==req.Code)` NO `!IsDeleted` BUT global filter auto-hides soft-deleted → check passes → unfiltered index 500. **CONFIRMED-reachable** (`DepartmentFeatures.cs:76+125`, `ProjectFeatures.cs:87+147`, `CreateSupplierCommand.cs:45`+`DeleteSupplierCommand.cs:20`). **SKIP 3:** (a) **ContractClause** (`ContractClauseConfiguration.cs:18`) — NO Create/Update/Delete handler ANYWHERE (only `IApplicationDbContext.cs:32` DbSet; FormsController = templates only) → not CRUD-reachable. (b) **MeetingRoom** (`MeetingRoomConfiguration.cs:20`) — Delete sets `IsActive=false` NOT IsDeleted (`MeetingFeatures.cs:178`, comment :175 "FK Restrict → NOT soft delete") → index never gets soft-deleted row; Create also checks `&& !IsDeleted` :113. (c) **EmployeeProfile** (`EmployeeProfileConfiguration.cs:24/26` EmployeeCode+UserId) — Delete soft (`EmployeeFeatures.cs:437`) BUT Create BLOCKS reuse by design: UserId check `AsNoTracking().FirstOrDefault(UserId==)` (no HRM global filter) sees soft-deleted → throws ConflictException "Cần khôi phục" :160-163; EmployeeCode auto-gen atomic (never user-supplied/reused) → no collision. **Completeness (grep ALL `.IsUnique()`):** beyond 3 Master + 6 HRM-fixed (LeaveType/Holiday/Shift/OtPolicy/Vehicle/Driver all `.HasFilter([IsDeleted]=0)`), every OTHER bare-unique is either composite junction (Permission RoleId+MenuKey, *LevelOpinion, MeetingBookingAttendee, LeaveBalance, Attendance UserId+Date), nullable-code already filtered (`[Ma*] IS NOT NULL`: Contract/PE/Proposal/Budget/WorkflowApps), or no-soft-delete (WorkflowDefinition/ApprovalWorkflow Code+Version, ContractTemplate FormCode, WorkflowTypeAssignment, DepartmentApprovals). **Mig 46 = exactly 3 indexes (Departments/Suppliers/Projects Code).** Surprise: Master GLOBAL query filter MAKES the bug (auto-hides soft-deleted from check) — opposite of HRM where bug needs manual `!IsDeleted`; either way unfiltered index = 500. Tag `[gotcha57-ext, reachability-audit, master-global-filter, s51]`.

- **2026-06-01 (MONTHLY DRIFT AUDIT):** Ground truth code: **migrations=42** (last `AddLeaveBalances`, path `.../Persistence/Migrations/*.cs`) · **gotchas highest=#56** (file header NO self-count → drift chỉ ở file *reference* gotchas.md) · tests=154 (58 Domain+96 Infra, em main verified) · tables≈91 (45 config class nhưng Catalogs=4+ContractDetails=7+7 Identity untracked → khớp STATUS 91, KHÔNG cheap-exact). **Biggest drift: ef-core-migration SKILL** (frontmatter:3 "31 migration"→42, :19 history "31"→42 + thiếu rows Mig 27-42, :50 "59 bảng"→91, :80 "111 test"→154, :258/:267 "59 bảng"). dependency-audit SKILL:153 "49 bẫy"→56. CLAUDE.md:53 "40 mig→84 bảng"→42/91, :66 "130 test"→154, :133 "52 bẫy"→56. docs/CLAUDE.md:65 "52"→56. **schema-diagram GAP:** migration TABLE dừng Mig 16 (line 487); detail § cuối =§15=Mig 26 (§16=Related KHÔNG phải mig) → thiếu § cho **Mig 27-42** (16 mig). database-guide:4 "47 bảng/13 mig"→91/42. STATUS:97 backlog "Curate 4 agent MEMORY 35.7/35.3/30.9/28.4" STALE (đã curate S40 `78c9de3`, all ≤16KB) → REMOVE. NO-CHANGE: contract-workflow (historical counts OK), form-engine, iis-deploy (no count), HANDOFF (S43 current), PROJECT-MAP (no count). Tag `[drift-audit, monthly, 2026-06]`.

- **2026-06-01 (P11-C Vehicle+Driver catalog pre-flight):** Mig 44 next (latest=Mig 43 `FilterHolidayUniqueIndexByIsDeleted` S45). **NO Vehicle/Driver master exists** — chỉ `Office/VehicleBooking.cs` (request, Mig 39) dùng FREE-TEXT (`VehicleLicense`/`VehicleName`/`DriverName?` strings, :13-19 comment "defer catalog Phase 11"). **RECOMMEND home = extend HrmConfigs** (NOT new module): `Application/Hrm/HrmConfigFeatures.cs` mega 4-region + `HrmConfigsController` (`[Authorize]` read / `[Authorize(Roles="Admin")]` write) — add Region 5 Vehicle + 6 Driver (kind `vehicles`/`drivers`), pattern proven 12-bis. ⚠️ HRM entities KHÔNG global HasQueryFilter → manual `.Where(!IsDeleted)` + UNIQUE soft-delete cần `.HasFilter("[IsDeleted]=0")` (Holiday Mig 43 lesson, LeaveType/Shift UNIQUE Code chưa có filter → nếu Vehicle BienSo UNIQUE phải add filter). **FE cheap:** `HrmConfigsPage.tsx` declarative KIND_CONFIG Record — add 2 entry vào KIND_CONFIG + KINDS[] + `renderCells` branch + smart-defaults; NO new page. **Menu+perm:** add 6 const `MenuKeys.cs` (+`Hrm_Config_Vehicles/Drivers`), thêm vào `All[]` (:140) → Admin auto-grant qua `SeedAdminPermissionsAsync` loop (:1909 idempotent), +2 MenuItem `DbInitializer` :1757, +2 `menuKeys.ts` mirror. Hrm_Config KHÔNG inherit-root (4 root=Contracts/Workflows/Pe/PeWf only) → leaf cần row riêng (loop lo). **Fields (NamGroup XeCong DROPPED Mig 2026-05-15, ref response shape only):** Vehicle{Code/BienSo UNIQUE, Hang, MauXe, SoCho int, TrangThai, GhiChu}; Driver{Code/Hoten, SDT, GPLX, Hang bằng, TrangThai}. FK link defer: P11-C = catalog only, optional FK `VehicleBooking.VehicleId?/DriverId?` giữ free-text back-compat (Mig sau). Tag `[pre-flight, p11-c, vehicle-driver-catalog]`.

- **2026-06-07 (Harness 1/2/3 adap-apply recon — 3 slice, HMW wave):** Governance recon AI_INFRA broadcast harness-1/2/3. **H1/H2 (Harness 1):** roster 8→10 — CREATE 2 sub TÁCH BIỆT `tooling-auditor` (H1 freshness 4-mặt skill/sub-role/plugin/docs) + `harvest-curator` (H2 integrity 5-trục). H2 PARTIAL sẵn: `session-end.md` Phase 1.5 §L.b(d) spawn-record 4-field + (f) double-check moved-not-cut + (c) 0-byte AS-8 = Coverage+Completeness+Corruption (3/5); THIẾU Fidelity-escalate + Placement. RE-REPORT @session-start = 0 (chỉ generic Phase 2.7). 2 sub mirror inv-codebase read-set + store_memory strip + NO Write/Edit; color brown+teal (8 màu cũ hết). **H2 wave (Harness 2):** SE `hmw.js` = OLD pre-wave (no subMdPath/writeGuard/wave-block); AI_INFRA `hmw.js` = canonical template. ⭐ `git check-ignore -v` = ground-truth B6: `.claude/workflows/wave-test/wave.md` HIỆN match `.gitignore:83 !.claude/**` = TRACKED → wave pattern PHẢI đặt AFTER `!.claude/**` (last-match-wins, mẫu `hmw-mode.on` :87). Read-only sub (4)=inv-cb/inv-api/reviewer/cicd; Write sub (4)=impl×2/test/fe-designer. B5 depends H2 harvest-curator. **H3 email (Harness 3):** broadcasts/ absent; id authoritative = `se` (NOT solution_erp), 6 others short `{ai_infra,vipix,dyd,namgroup,ashico,bvaau}` từ `AI_INFRA/broadcasts/sister-commands/send-email.md:13-22` (folder name = 2nd source-truth); `adap-apply.md:14` base-path STALE flat → `outbox/all/*.md` (latent bug). broadcasts/ ở root → commit OK (no gitignore rule). **Containment post-P2:** git-diff bắt 1 file-write (inv-api self-MEMORY), chunk-count 2414=2414 (0 RAG-write) = defense-in-depth proven. Tag [harness-recon, governance, hmw-wave, 2026-06-07].

- **2026-06-09 (S56 pre-golive verify — 4 logic streams, all PASS):** Audited P11-B/D/E/F + ApproveV2 + catalogs + S55 master-wiring. **LeaveBalance** deduction exactly-once (terminal DaDuyet, guard `Status!=DaGuiDuyet` :296 blocks re-approve), FK guard Create+UpdateDraft→Conflict; **AttendanceReport** classify day-type IN-MEMORY (Holiday DateOnly HashSet), OtPolicy multiplier; **MaTicket** gen-on-Create Serializable IT/2026/NNN. Tests cover (LeaveBalance 9 + AttReport 2 + codegen 3 = 29 green). ApproveV2 4-module flatten Steps→Levels correct; **Travel/Vehicle ApproveV2 = 0 test** (cookie-cutter of tested Leave/OT — add 2 smoke post-golive). master-data **idempotency PROVEN** (DbInitializer re-run → counts identical, per-code guard :2310/:2404/:2422). **⚠️ PROD FACT (corrects stale S52 mem):** dept "IT"/Phòng CNTT DOES exist (Id 65CC6307…) but has **0 active users** on prod → ItTicket auto-assign no-ops, reassign dropdown empty, SLA job no notify-target. Pre-golive ops fix: assign ≥1 real user to dept IT (1 UPDATE, no code). Tag [s56, pre-golive-verify, logic-pass, dept-IT-empty-prod, travel-vehicle-untested].

- **2026-06-09 (S56 Phase 2 FE-redesign RECON — 25 page audit, on-disk):** ⭐ **NOT a rewrite** — S55 already redesigned ui-primitives + DataTable + shell → any page importing `ui/{Button,Input}`+`DataTable` AUTO-inherits density. **Hover-hidden quick-win nearly absent:** repo-wide grep `opacity-0`×`group-hover` = **only 1 real site** `ContractCreatePage.tsx:196` (EXCLUDED scope) + DataTable.tsx:15 is a comment forbidding it (good). In-scope = **0 hover-hidden fixes**. **DataTable adoption split:** only 5/25 use `<DataTable>` (Suppliers/Projects/Departments/Users/Forms); 12 pages roll RAW `<table>` (MeetingRooms/Catalogs/HrmConfigs/EmployeesList/Proposals/WorkflowApps/Attendance×2/MenuVisibility/Roles) → custom density pass needed. **Drawer ≥8-field candidates = 3:** Suppliers (9 fld, Dialog), Projects (10 fld, Dialog), Users-CREATE (~8 fld w/ roles multiselect, 4 Dialogs total but only create is big). All currently big-Dialog → convert. **NO `Drawer.tsx` exists** (`ui/` = Button/Dialog/Input/Label/Select/Textarea only) → build first. **Bậc-thang reference ALREADY EXISTS:** `EmployeesListPage.tsx` (1200L) = canonical inline add/edit-row for 5 satellites (`setEditing{X}Id` + `addingX` mutex, :256-356) → extract `InlineEditRow` pattern from here, reuse for Catalogs/HrmConfigs/MeetingRooms (these 3 currently edit via Dialog :251/:316/:232, ≤7 cols → bậc-thang candidates). **Modal-detail = NONE:** ProposalDetail:275 + WorkflowAppDetail:424 Dialogs are tiny action-confirm (just `<Textarea>` ý kiến), detail body already inline 2-col grid (:181) → NOT convert. **fe-user mirrors** office/master/hrm (SHA256-identical per comments) but NO system/forms/reports mirror. **Custom-layout heavy:** InternalDirectory (card-grid :124 not table), MeetingCalendar (693L FullCalendar), EmployeesList (2-panel), HrmConfigs (declarative KIND_CONFIG :45). Effort: Master 3×Drawer=M, Catalogs/HrmConfigs/MeetingRooms bậc-thang=M, Users Drawer=M, rest S (auto-inherit polish). Surprise: hover-hidden NAMGROUP-win essentially pre-solved (team already avoids opacity-0 pattern, DataTable comment enforces) → quick-win section nearly empty. Tag `[fe-redesign-p2, recon, drawer-3, basc-thang-ref-exists, s56]`.

- **2026-06-09 (S55 master-data Excel-import recon — 3 master + seed mechanism, on-disk):** ⭐ **"Hạng mục"/WorkItem master TỒN TẠI** — `Domain/Master/Catalogs/WorkItem.cs:6-14` (Code(50)UNIQUE-filtered/Name(200)/Category(100,idx)/DefaultUnit(50)/Description/IsActive), config `CatalogsConfiguration.cs:60-74`, full CRUD `CatalogsFeatures.cs:260-324` → group(VẬT TƯ/THẦU PHỤ/MEP)→Category, "1 Mat"→Code, item→Name. KHÔNG cần table/migration mới. **PE detail = pure free-text** (`PurchaseEvaluationDetail.cs` GroupCode/GroupName/ItemCode/NoiDung strings, NO FK→WorkItem) → load WorkItems non-breaking. **Project** (`Project.cs:5-14`, cfg `:14-21`): Code(50,UNIQUE `[IsDeleted]=0` Mig47)+Name(200) REQUIRED, StartDate/EndDate/BudgetTotal(18,2)/Note(1000)/ManagerUserId optional. ❌ **THIẾU Year/Investor/Location/Package** — chỉ Note free-text catch-all. Create cmd `ProjectFeatures.cs:67` dup-check `:87 AnyAsync(Code==)`. **Supplier** (`Supplier.cs:5-16`, cfg `:14-27`): Code/Name req + Type enum + TaxCode(20)/Phone/Email/Address/ContactPerson/Note. `SupplierType.cs`: NhaCungCap=1/NhaThauPhu=2/ToDoi=3/DonViDichVu=4/ChuDauTu=5. ❌ **THIẾU Status/TinhTrang (KHÔNG có field/enum nào)** + bank-acct + legal-rep (≠ContactPerson) + quality-score; "Cả hai" PHÂN LOẠI unmappable (Type single-valued). Create `CreateSupplierCommand.cs:10` dup `:45`. **Seed = idempotent `existingCodes.Contains→skip`** (`DbInitializer.SeedDemoMasterDataAsync:2149`, today 18 supplier `:2155` + 8 project `:2222`; WorkItems 15 rows tuple-loop `SeedCatalogsAsync:576-599`). **NO bulk import** — Master chỉ single CRUD; Import/Upload hits = Forms/PE/Employees attachment only; POST one-at-a-time. **Seed→prod:** `DbInitializer.InitializeAsync` chạy MỌI startup (`Program.cs:197` unless `--no-db-init`) → `MigrateAsync` THEN seed; demo gated `config.GetValue<bool>("DemoSeed:Disabled")` (`:80`) NHƯNG SeedDemoMasterData+SeedCatalogs chạy BẤT KỂ flag (ngoài if-block :108/:115) → seed method mới auto-reach prod next deploy. Rec: idempotent DbInitializer mirror (NOT API loop). Surprise: real+demo data sẽ trộn chung Suppliers/Projects/WorkItems (18/8/15 demo rows) → cân nhắc gate demo off prod. Tag `[master-import, workitem-exists, seed-idempotent, s55]`.

- **2026-06-10 (S57 perm-broaden blocks A/B/E/F — on-disk):** ⭐ **BE AUTHZ SPLIT (decision-critical for E):** Config controllers gate WRITE behind `[Authorize(Roles="Admin")]`, READ open to any-authed: `HrmConfigsController.cs:15` class `[Authorize]`+GET open `:19-21`, all POST/PUT/DEL `Roles="Admin"`; `CatalogsController.cs:14` same (write `:23+` Admin); `MeetingRoomsController.cs:15` same (comment `:9-10` explicit). → granting FE read on Hrm_Config/Catalogs/MeetingRooms = pure UI-visibility, BE already correct. **BUT Master 3 controllers = class `[Authorize]` ONLY, no per-action role:** `SuppliersController.cs:17`, `ProjectsController.cs:11`, `DepartmentsController.cs:11` — ANY authed user can POST/PUT/DELETE via API (FE menu perm is only gate, not BE-enforced). Flag em-main: making Suppliers/Projects/Departments visible-to-all ⇒ staff can write master incl. S55 prod data unless add `[Authorize(Roles="Admin,CatalogManager")]` or per-action policy. **S55 PROD DATA location:** `SeedRealMasterDataAsync` `:2267-2460` writes to **Projects**(62, `:2270`), **WorkItems**(71=CatalogWorkItems key, `:2430-2438`), **Suppliers**(3, `:2440-2456`) — all ungated idempotent per-code. **10 departments now** (`SeedDepartmentsAsync:2104`): 9 orig + IT (`:2115`). **31 demo users** (`SeedDemoUsersAsync:1553-1609`): dept spread BOD4/PM2/CCM9/PRO6/QS2/FIN2/ACT1/EQU1/HRA2; roles = mostly Drafter/CostControl/Procurement + 1 CatalogManager (`catalog.manager@`, dept PRO, `:1608`). NO user has zero-role; every demo user authenticatable. **Inherit-root display:** `GetMyMenuTreeQuery.cs:96 HasAccess = CanRead OR Children.Any(HasAccess)` → a non-inherit root (Hrm/Off/Master) auto-shows if ANY child has CanRead, so granting leaves is enough to reveal the root node (root row itself optional for display, but grant it too for cleanliness). Inherit-roots (Contracts/Pe/Wf/PeWf) cascade root→child so root-row-only suffices there. Menu already S57-edited: `Personal` group + `Off_ChamCong` re-parent Off→Personal via `parentBackfill:1908`. Tag `[s57-perm, be-authz-split, master-write-open, s55-data, 31user]`.

- **2026-06-10 (S57 perm-broaden RECON blocks C/D — RAG down, on-disk):** ⭐ **SEED MODEL:** `SeedAdminPermissionsAsync` `DbInitializer.cs:1939-1977` Admin loops `MenuKeys.All` CRUD=true skip-existing dedup (`:1950/:1952`). Calls 2 sub: (a) `SeedPurchaseEvaluationPermissionDefaultsAsync` `:2036-2098` → **7 roles** {Drafter,DeptManager,Procurement,CostControl,ProjectManager,Director,AuthorizedSigner} Read+Update on PE keys only; (b) `SeedCatalogManagerPermissionsAsync` `:1984-2029` → role **CatalogManager** full-CRUD 9 master keys. **NO generic per-employee Read seed** — plain Drafter user sees ONLY PE keys; DOESN'T see Off_*/Hrm_*/Master/Contracts. **Most non-admin staff today see ~nothing but PE.** GetMyMenuTree `:96` filters CanRead=true. **Permission entity** `Permission.cs:3-15`: RoleId/MenuKey/CanRead/Create/Update/Delete. Dedup=app-level skip-existing per(RoleId,MenuKey), NO DB upsert. **13 AppRoles** `AppRoles.cs:23`: Admin(system)+12 employee. **4 inherit-roots** `GetMyMenuTreeQuery.cs:56-84`: Contracts/Workflows/PurchaseEvaluations/PeWorkflows — root grant auto-cascades to child IF child no own row (`:66`). Hrm/Off/Master NOT inherit → each leaf needs own row or add to switch. **GRANT-ALL pattern (block D):** mirror CatalogManager seeder but loop `roleManager.Roles` (all 13) × chosen key-set, CanRead=true only, insertion AFTER SeedCatalogManagerPermissionsAsync `:1976`. Tag `[s57-perm-recon, seed-model, no-employee-default, inherit-4root]`.

- **2026-06-10 (menu-order cross-repo recon SE↔NAMGROUP, RAG down, on-disk):** ⭐ **SE menu seed = `SeedMenusAsync` `DbInitializer.cs` tuple-list** (NOT partial). Văn phòng số root `Off` (Order=29) `:1769-1792`; HR root `Hrm` "Nhân sự" (Order=28) `:1754-1767` (+`Hrm_Dashboard` appended out-of-order `:1791`). ⚠️ **HR SCATTERED 2 roots:** `Hrm` holds only Hồ sơ+Cấu hình HRM(6 leaf)+Dashboard; transactional HR (Nghỉ phép/OT/Công tác/Đặt xe/Chấm công/Báo cáo CC) live under `Off` as `Off_DonTu_*`/`Off_DatXe`/`Off_ChamCong`/`Off_AttendanceReport`. **SEED = UPSERT that RE-SETS Order** (`:1845-1871` `if(existing.Order!=o){existing.Order=o}`) → reorder in code propagates to Dev/prod next deploy, NO migration. BUT Label/ParentKey/Icon NOT touched on existing rows (`:1855` comment) — rename needs separate `labelBackfill` dict `:1874`. **Order = BE-only:** `GetMyMenuTreeQuery.cs:35 OrderBy(m.Order)`; both FE `Layout.tsx` render `useAuth().menu` as-is, `staticMap`+`menuKeys.ts` = key→route ONLY (no sort). FE needs NO edit for pure reorder. **NAMGROUP "Puro" = hardcoded FE array (NOT DB seed),** index=order: client `InternalLayout.tsx:83-118` (Nhân sự 3-item / Văn phòng số 6-item FLAT / Chấm công under separate "Cá nhân" group); admin `AdminLayout.tsx:87-119` splits by function not HR/office. NAMGROUP `Đơn từ`/`Phòng họp`/`Đề xuất` = flat single links (no sub-children) vs SE deep-nested. Tag `[menu-order, se-namgroup, seed-upsert-order, fe-be-driven, s57]`.

- **2026-06-11 (S59 recon — prod test-data wipe + PE tree Hạng mục, prod+on-disk):** ⭐ **Prod:** PE=10 active (1 Nháp + 1 DaDuyet(7) + 8 ChoDuyet(10), MaPhieu A/031-040, ALL WorkItemId NULL) + child 20/10/20/28/138/18/18 (Sup/Det/Quote/Appr/Chg/Att/LvlOp); Contracts=7 ALL `[DEMO]` 05-08 pin V1 (AwId NULL) + Appr15 + details15; Budgets/WorkflowApps/Proposals/Attendances/Meetings ALL 0; Notifications 64. Seq: PE/2026/A=40 B=1; CT=7 demo prefix LastSeq=1. **FK:** PE child CASCADE trừ `Quotes→PE NO_ACTION` (multi-path; Plan R S23 proved single `DELETE FROM PurchaseEvaluations` OK — NO_ACTION check end-of-statement sau cascade Details→Quotes). Contract child ALL CASCADE. PE.ApprovalWorkflowId Restrict → wipe PE trước khi xóa AW QT-DN-V2-001 v1 (inactive, còn 1 PE pin). AW V2=8: 7 ghim KEEP. **Uploads orphan:** purchase-evaluations/ 19 folder vs 10 PE → ~10 orphan từ S23 (file không xóa); contracts/ 1. **Demo gate OK:** SeedDemoContracts/PE TRONG `DemoSeed:Disabled` (DbInitializer:80,131-132) → wipe không resurrect. **Surprise:** Users 55 total / 21 active — 20 user THẬT batch 2026-06-11 06:01 (S58 seed fix ăn; thanh.lethanh NOW EXISTS — stale S57bis mem; chuong.phan typo-domain VẪN active song song twin). **FE tree:** `pe/PurchaseEvaluationsListPage.tsx:138-179` Project>Year(createdAt :150)>Supplier; SHA256 identical 2 app; PeListItem ĐÃ có workItemId/Name (types :116-118, BE Features :514/570/644) → đổi tree FE-only. Tag `[s59-recon, prod-wipe, pe-tree-workitem]`.

- **2026-06-11 (S57bis lock no-op — prod user census, on-disk+prod):** ⭐ `LockDemoSampleUsersAsync` (DbInitializer.cs:1552, chạy CUỐI :98) hardcode 14 named-person email (bod.huynh/pm.nguyen/fin.do/qs.hoang…) = population CHỈ CÓ TRÊN DEV. **Prod 34 user ALL-active:** 20 UAT-matrix placeholder hand-created batch 2026-05-13 15:04-05, scheme `{act,equ,fin,hra,pm,qs}.{nv,pp,tp}@` + `bod.{1,2}@` (FullName tự khai "ACT NV - Drafter+Accounting", "[Bypass]"/"[SkipFinal]" = test Mig 29-31 flags) + 9 real staff hand-created 05-04→05-12 + `binh.lethanh@` (người thật Lê Thanh Bình — seed dùng `thanh.lethanh@` KHÔNG tồn tại prod) + `chuong.phan@solution.com.vn` TYPO-domain dup (twin đúng tạo 05-12) + admin/catalog.manager/nv.test. **ROOT CAUSE seed-user never-on-prod:** prod `Identity:Password:RequiredLength=12` (appsettings.Production.json) vs `DemoUserPassword="User@123456"`=11 chars → CreateAsync silent-fail MỌI startup từ prod-init 04-21 (code comment :1675-79 đã biết); Dev fallback 8 (DependencyInjection.cs:67 `?? 8`, Development.json no Identity section) → Dev đủ 33 user named-person. `bod.1@` NEVER in git pickaxe = tạo tay qua admin UI, không phải seed. Surprise: _Dev hiện CŨNG chưa khóa (Locked=0; LockoutEnd=MaxValue sẽ persist qua reconcile re-activate :1714 nếu từng chạy) → lock chưa từng execute against _Dev runtime. Fix cần 20 email prod-thật; GIỮ binh.lethanh + 9 real + admin/catalog.manager; `nv.test@` = creds smoke-verify (khóa = vỡ cicd smoke). Tag `[s58, s57bis-lock-noop-recon, prod-user-census, pwd-policy-env-divergence]`.

- **2026-06-11 (S57bis PE recon — 4 đầu việc sếp, on-disk):** ⭐ PE entity NO Year, NO WorkItem link (`PurchaseEvaluation.cs:15` ProjectId req; Detail free-text `PurchaseEvaluationDetail.cs:10-13`). Create cmd `PurchaseEvaluationFeatures.cs:19-30`; MaPhieu gen-AT-CREATE `:114-116` format `PE/{YYYY}/{A|B}/{Seq:D3}` (`PurchaseEvaluationCodeGenerator.cs:23`). Main create UI = `PeWorkspaceCreateView.tsx` (:151 workflow-select isUserSelectable ĐẦU TIÊN → tenGoiThau → projectId → DiaDiem → MoTa → PaymentTerms → budget; canSubmit :129 = wf+project+ten). PE controller class-`[Authorize]` ONLY no policy → mở menu là đủ, no silent-403. Pe_* leaves NOT in `MenuKeys.All` (chỉ root :156); PE defaults 7 role × 11 key (root + 2type×{group,WfView,List,Create,Pending}) `DbInitializer.cs:2098-2160`. S57 `SeedAllRolesReviewReadPermissionsAsync:1993-2001` InReviewScope EXCLUDES Pe; extend đúng = `key == MenuKeys.PurchaseEvaluations` EXACT (prefix "Pe" sẽ dính PeWorkflows admin!) — root inherit cascade (`GetMyMenuTreeQuery.cs:49-82`). Demo gate: prod `appsettings.json:35 DemoSeed:Disabled=true` → 7 `[DEMO]` HĐ + 4 `[DEMO]` PE (MaPhieu `[DEMO]-A-001`) KHÔNG lên prod; UNGATED trên prod = 31 users + 18 demo NCC + 8 demo project (:2244-2315) + real 62/71/3 (:2329-2522). ⚠️ Clear-demo gotcha: seed re-add per-code idempotent MỖI startup → xóa DB-only sẽ resurrect, phải gỡ khỏi DbInitializer code. WorkItem write Admin-only (`CatalogsController:113-130`) — CatalogManager có menu-perm nhưng API write bị chặn. Tag `[s57bis, pe-recon, demo-inventory]`.

- **2026-06-16 (S66 recon — mirror Hồ sơ NS fe-user→fe-admin, on-disk):** ⭐ **VERDICT (B): vá `fe-admin/src/index.css` TRƯỚC rồi cookie-cutter SẠCH.** Copy page thuần = VỠ MÀU. **fe-admin index.css = 86 dòng (chốt `7feb53e`, TRƯỚC redesign S58 `e959f72`/`c98030f`) → THIẾU:** 4 accent palette `teal/amberx/violet/greenx` (mỗi cái 50/100/500/600/700) + 3 utility `.icon-chip`/`.app-gradient-brand`/`.card-accent`/`.stat-value`. **CÓ SẴN:** `--color-brand-50..900` (hex y hệt fe-user, incl brand-800 #175685 :15), `.label-eyebrow` :54, font Be Vietnam Pro :22. ⚠️ heading-weight CẦN CHECK (fe-user S66 `h1-h4 font-weight:700 color:#0b1220`; fe-admin có thể còn 600). **Page fe-user phụ thuộc THẬT:** text-brand-800 ×9, teal/amberx/violet/greenx-50/500/700 ×4 mỗi, icon-chip ×3, app-gradient-brand ×1. **Wiring fe-admin ĐỦ SẴN (0 đụng):** route `/employees`+`/new` `App.tsx:82-83` · `EmployeeCreatePage.tsx` **identical** (diff rỗng) · menu `Hrm_HoSo` `menuKeys.ts:33`+staticMap `Layout.tsx:53`. **Lib parity ✅:** ui/{Input,Select,Textarea,Button} prop-sig **identical** (HTMLAttributes passthrough, content khác chỉ className=chủ ý non-breaking) · EmptyState/cn/api/apiError ✅ · `types/employee.ts` **identical** · Paged `types/master` ✅ · `DepartmentTreeNode` định nghĩa INLINE trong page (:65 mirror BE DepartmentTreeNodeDto, đi theo copy — không cần type file). **KHÔNG khác chủ ý admin/user** — mirror y hệt như S35 (`9616ae2`/`c3cd343` cùng commit 2 app); write Admin-gated ở BE controller. **Cấu trúc:** admin=1200 dòng (S35 cũ, 2-panel + 5 `<details>` + 5 satellite mutex); user=1602 dòng (redesign: cây "SOLUTION COMPANY" đệ quy + list cột trái dọc · detail phải 5 tab accent + avatar gradient). **Scope: 3 file** = index.css (chèn ~40 dòng token+class block từ fe-user :29-51+:100-160) + overwrite EmployeesListPage.tsx (import path KHÔNG chỉnh, đều `@/`) + (tùy chọn heading-700). KHÔNG đụng route/menu/types/primitives/CreatePage. Tag `[s66, mirror-employee-page, accent-token-missing-fe-admin, verdict-B, cookie-cutter-after-css]`.

- **2026-06-16 (S65ter recon — Mục E "Link hồ sơ" phiếu PE, on-disk):** ⭐ Anh Kiệt: chèn mục E "Link hồ sơ" NGAY DƯỚI mục D "Bản so sánh". **Render 4 file** (SHA256-identical 2 app): `components/pe/PeDetailTabs.tsx` (detail+edit, 2770 LOC) + `PeWorkspaceCreateView.tsx` (create) × {fe-user,fe-admin}. KHÔNG tabs — 5 `<Section>` dọc, tiêu đề "1./2./3./4." + sub-item chữ thường "a./b./c./d." (label cột trái w-44). **Mục D ∈ Section "3. Đơn vị NCC/TP" = `ChonNccSection`** (`PeDetailTabs.tsx:1302-1375`): a.NCC(:1321) · b.Tổng hợp NS trình ký(:1324 `PeBudgetSummaryTable` — S61 thay Budget) · c.Giá chào thầu(:1326 auto) · **d.Bản so sánh(:1337-1348)** = `GeneralAttachmentsSection`(:2613) upload N FILE filter `supplierId===null` purpose=ComparisonTable(4). **INSERT E: `PeDetailTabs.tsx:1348`** (sau `</div>` mục D, trước paymentTerms :1350); mẫu = block :1337-1348. Create: `PeWorkspaceCreateView.tsx:277` (sau FormRow d). **BE: `PurchaseEvaluation.cs`(:1-72) KHÔNG có field URL** — DiaDiem/MoTa/PaymentTerms semantic khác. 1 link → `string? HoSoLink`(1000)+Mig AddColumn+cmd+DTO+validator; nhiều link → entity con `PurchaseEvaluationLink`+CREATE TABLE+CRUD (nặng). **Attachment KHÔNG reuse URL** — `PurchaseEvaluationAttachmentFeatures.cs:18-55` IFormFile thuần (FileSize>0+ContentType whitelist+IFileStorage). Mục D multi-row → E nên multi-row đối xứng. ⚠️ Surprise: comment :1314 nói "purpose=ComparisonTable hoặc supplier-row null" SAI — filter thực :1315-17 CHỈ `supplierId===null`. Tag `[s65ter, pe-section-e-link, attachment-file-only, insert-1348]`.

- **2026-06-16 (S65 recon — public HRM module for all-role, on-disk):** ⭐ **Mục 6 CRITICAL (gotcha #44 family) RESOLVED-FAVORABLE:** `EmployeesController.cs:23-25` = class `[Authorize(Policy="Hrm_HoSo.Read")]` (NOT `Roles="Admin"`) + per-action `Hrm_HoSo.{Create/Update/Delete}` (:45/:54). Policy resolves THROUGH permission matrix (`MenuPermissionHandler.cs:40-52` baseQuery role×menuKey CanRead; Admin-bypass :27) → seed CanRead row = API ALSO unlocked, NO 403. `HrDashboardController.cs:8-11` = `[Authorize]` any-auth only (`/api/hr/dashboard`). GET list = `/api/employees` (:28). ⇒ **seed BE permission ĐỦ, không cần đụng controller.** **Menu keys dưới `Hrm` (prefix THẬT = `Hrm_`):** root `Hrm`="Nhân sự" parent=null Order=28 (`DbInitializer.cs:1805`); `Hrm_Dashboard`="Dashboard NS" parent=Hrm Order=1 (:1850); `Hrm_HoSo`="Hồ sơ Nhân sự" parent=Hrm Order=2 (:1806). Hrm_Config* (6 leaf: LeaveTypes/Holidays/Shifts/OtPolicies/Vehicles/Drivers) parent=**Master** Order=25 (S57 re-parent :1812 — KHÔNG dưới Hrm). **Revoke (Mục 2):** `RevokeTemporarilyHiddenModulesAsync` :2151 — match `StartsWith("Hrm")||StartsWith("Off")||==Personal` AND role!=Admin AND any-flag-true (:2162-67) → set 4 cờ CRUD=false. ⚠️ **THỨ TỰ: gọi CUỐI CÙNG `:2040` trong SeedAsync, SAU grant `:2033`** → revoke THẮNG mọi grant trước nó. Mở Hrm = phải (a) sửa revoke loại trừ Hrm_HoSo/Hrm_Dashboard HOẶC (b) thêm grant SAU :2040. **Pe pattern (Mục 3):** `SeedAllRolesReviewReadPermissionsAsync:2055` — `roleManager.Roles.ToListAsync():2090` loop ALL role × reviewKeys, upsert CanRead (+CanCreate cho Pe_*), additive idempotent (skip-existing non-Pe :2115). **Seed entity (Mục 4):** `Permission`(RoleId,MenuKey,4 CRUD); idempotent = app-level skip per (RoleId,MenuKey); **13 role** `AppRoles.All` (Admin/Drafter/DeptManager/ProjectManager/Procurement/CostControl/Finance/Accounting/Equipment/Director/AuthorizedSigner/HrAdmin/CatalogManager). `Hrm_HoSo`+`Hrm_Dashboard` ĐỀU ∈ `MenuKeys.All:153,160` (khác Pe_* leaf NOT in All). **FE (Mục 5):** menu-tree-API-driven via `GetMyMenuTreeQuery.cs` (`/api/menus/me`); Hrm NOT inherit-root (chỉ 4: Contracts/Workflows/Pe/PeWf :51-59) → MỖI leaf cần CanRead row riêng, NHƯNG root `Hrm` auto-hiện nếu child có access (`HasAccess:96` CanRead OR child). `Layout.tsx:145 USER_HIDDEN_KEYS`={System,Users,Roles,Permissions,Forms,Reports} — KHÔNG chứa Hrm → fe-user auto-render; `staticMap Hrm_HoSo→/employees :75`, `Hrm_Dashboard→/hr/dashboard :104`. NO PermissionGuard per-route fe-user. ⇒ **chỉ seed BE, FE tự hiện.** Tag `[s65-recon, public-hrm, policy-based-authz-not-roles, revoke-runs-last]`.


<!-- ===== Moved from L1 at S80 (2026-06-20) curate — verbatim, additive ===== -->

- **2026-06-20 (governance-landing map for RC-sig + User-Mark H12/13 + objective-criteria, on-disk):** ⭐ **WHERE-to-land 3 AI_INFRA gov broadcasts.** Key files: `docs/governance/harness-11-engine.md` = CANONICAL engine (PHẦN A/B/C/D + CAVEAT; line5 "doc khác TRỎ về đây KHÔNG copy luật"; D5/D6/D7/D8 safety-tier line62-72; D7 OWNER-APPROVE line69; D9 single-writer line73; D10 "Bash residual chưa block cứng" line74; CAVEAT no-OS-hook line80). `error-ledger.md` = §L.a action-sig table AS-1..AS-13 + §L.b 7-step + Active-Guards 2-strike; **3-ledger triad** README.md:6 (error/comms/summary by FUNCTION). adap-report FORMAT = `adap-reports/YYYY-MM-DD-<Topic>.md` frontmatter(id/from/applied_by/nac/project_fit/source_content_sha256)+VERDICT/Nấc-table/Tailoring/Honest-caveats/Reverse-findings/Evidence(run-id) — richest template = `2026-06-18-Governance-harness-11.md`. rules.md §6=Docs/gov-discipline (§6.4 audit-cadence/§6.5 consolidate-KEEP-vs-CUT) → objective-criteria → NEW §6.6. session-start §2.1.3 (line83-88 H11-detector) = EXACT precedent for per-session gov-surface → mark-list-display START = NEW §2.1.4. session-end §L.b(c) (line48 archive-gate+sleep) → mark-list END. **RECs:** RC-sig+4-tier → NEW section in engine.md (REUSE file, after PHẦN D); decision-mark ledger → **NEW `docs/governance/decision-marks.md`** sibling error-ledger (forward-registry ≠ reactive-RCA). **CONFLICT: report-before-stamp ⊂ D7 already** (owner-approve exists) — extend D7 not duplicate (else C3 vocab-fork). 2-channel enforce already empirical: E-006/AS-10 + CAVEAT "hook fails-open, permission-config strip = real gate". Tag `[gov-landing-map, rc-sig, user-mark-h12-13, decision-marks-new-file, report-before-stamp-subset-d7, objective-criteria-rules-6-6]`.

- **2026-06-19 (S76 P2+P3 — budget-edit-role BADGE insert-point map, designer+fe-user-flow, on-disk):** ⭐ **Display-only "✎ NS PRO/CCM" badge per approver — BE change = SMALL both DTOs.** **(A) Designer fe-admin `ApprovalWorkflowsV2Page.tsx`:** read-only render `DefinitionCard:446-454` (level group → approver `{approverUserName}` + `({approverEmail})`); DTO `LevelDto:37-54` (approverUserId/userName/email + 7 Allow* flag, **NO role/dept field**). Feed = `GetAwAdminOverview` (`/approval-workflows-v2`). **Insert badge → `:447-452`** cạnh `approverUserName`. **(B) fe-user `PeDetailTabs.tsx`:** approvalFlow render `LevelOpinionsSectionV2:588` (signed-only) — но live flow tree = `currentApproval.approvers` :131 + Panel3 separate. `PeApprovalFlow` DTO `purchaseEvaluation.ts` + BE `PurchaseEvaluationApprovalLevelApproverDto` (`PurchaseEvaluationDtos.cs:129-132` = UserId/FullName/Email, **NO role**). **(C) Role-resolve for LIST userId:** codebase uses `userManager.GetRolesAsync(u)` (per-user, N+1 risk) OR `GetUsersInRoleAsync(role)` (reverse, `PeUrgentFeatures.cs:74`). `IApplicationDbContext` exposes `DbSet<Role> Roles` :29 but **NO UserRoles join-table DbSet** → efficient batch = either (a) `userManager.GetUsersInRoleAsync(Procurement/CostControl)`→2 HashSet<Guid>, mark approver if id∈set (NO N+1, 2 queries total); or (b) add `DbSet<IdentityUserRole<Guid>>` to interface for join. **BE build site `PurchaseEvaluationFeatures.cs:964-972`** already batches `approverInfos` via `userManager.Users.Where(Contains(allApproverIds))` — extend SELECT or post-join 2 role-sets here; handler has both `db`+`userManager` :750-751. **(D) Change size = SMALL:** +2 bool field (canEditProBudget/canEditCcmBudget) per approver DTO + 2 GetUsersInRoleAsync calls. Designer side: `GetAwAdminOverview` query needs same 2-set lookup (admin-only, cheap). Gate semantics ALREADY proven `:800-801` (canEditPro=Admin||Procurement, canEditCcm=Admin||CostControl). **(E) REC:** minimal = compute 2 HashSet once (proFans/ccmFans via GetUsersInRoleAsync), pass into approver-DTO map both sites; badge = pure display `id∈proFans→"✎ NS PRO"` `id∈ccmFans→"✎ NS CCM"`. RISK low (display-only, no authz touch) — only watch: a user can hold BOTH roles → show both badges; Admin holds neither role explicitly unless seeded → may need OR Admin note. Tag `[s76, budget-role-badge, designer+pe-flow, getusersinrole-batch-no-n1, approver-dto-add-2bool, display-only]`.

- **2026-06-19 (PE Block-A budget editable-gate audit — submission-count lock NEXISTS, on-disk):** ⭐ **Gate = PURE ROLE, KHÔNG phase, KHÔNG số-lần-trình.** BE `PurchaseEvaluationFeatures.cs:800-801` `canEditPro=isAdmin||Procurement` · `canEditCcm=isAdmin||CostControl` (DTO arg :856). Handler `PeWorkItemBudgetFeatures.cs`: PRO `:86-91` CCM `:152-157` fail-closed ForbiddenException role-only TRƯỚC side-effect; comment `:18-20` ghi RÕ "KHÔNG ràng Phase (bảng NS = tài-liệu-sống chỉnh bất-kỳ-lúc-nào như Excel)". Validator chỉ `>=0` (Initial :136, Adjustment cho-ÂM :138), absolute-set null=clear. **FE `PeDetailTabs.tsx:1060 PeBudgetSummaryTable`:** ô "Ban hành lần đầu" :1173 + ô "hiệu chỉnh V0" :1188 dùng **CÙNG biến `bs.canEditCcm`** — ZERO phân-biệt 2 ô, ZERO lock-after-first. `drafterEditable:1066`=`!readOnly&&isEditablePhase` chỉ áp row3/row8 (drafter NS-kỳ-này), KHÔNG áp Block-A. **(b) submission-count lock = KHÔNG TỒN TẠI:** grep `submitCount|lanTrinh|firstSubmit|lockInitial|hasSubmitted|soLanTrinh` toàn `src/Backend`=0 + FE=0. Entity `PeWorkItemBudget.cs` 6 field plain, KHÔNG cờ `IsInitialLocked`/`SubmitCount`; record per-cặp(Project×WorkItem) share mọi phiếu KHÔNG track lần-trình. **Kết luận: yêu-cầu chị Trà/anh Kiệt (khóa Initial sau lần-trình-đầu, mở Adjustment) = FEATURE MỚI — cần field track first-submit-done + TÁCH gate 2 ô (Initial vs Adjustment), HIỆN cùng `canEditCcm` không tách được.** Tag `[pe-block-a-gate, role-only-no-phase, submission-count-lock-NEXISTS, initial-vs-adjustment-same-gate, fdc-feature-new]`.

- **2026-06-20 (Harness-14 Eval/Budget/Outcome adoption-readiness audit, on-disk):** ⭐ H-14 rule = time/age/recency-decay KHÔNG được làm căn-cứ cắt feature (cùng họ lỗi team-size). **(1) BUDGET ALIGNED:** `memory-budget.json` grep decay|recency|retention|age|TTL|expire=**0 hit**. Params = `autoinject_cap 25600`/`soft_cap 30720` (L1) · `archive_gate{low_watermark_ratio 0.85, keep_floor_entries 5, strike_threshold 2}`. `keep_floor=5` = **newest-entry-protection** (gate-script `:144` `entryCount - keepFloor` drains OLDEST keeps newest N — KHÔNG age-window). Cap = **seed-by-MEASURE** (`_note:2` "SEEDED BY MEASUREMENT NOT imagined headroom" + `scripts/measure-agent-memory.ps1` real-bytes), bump-not-cut khi curate drops markers. Hysteresis drain-to-BELOW-low-water (`gate:33`). ✅ fully H-14-aligned, zero forbidden knob. **(2) BASELINE-DRIFT ALIGNED:** `governance-detectors.ps1` staleness = **CANONICAL-ANCHOR vs docs/STATUS.md** (`Get-StatusValue:133` parse `| label | **N** |`) + **disk cross-check** (`:164-194` mig=count .cs · gotcha=max `### N.` anchor; flag if canonical-itself-stale) — ZERO age-window. `memory-archive-gate.ps1` over-cap = byte-MEASURE+2-strike-hysteresis (`:106 bytes>cap`), A7 = substring-pointer-resolve. Neither uses time. **(3) EVAL = GENUINE (not a gap!):** SE HAS RAG golden-set harness — `eval/golden-set-solution_erp.jsonl` (14 q: 11 pos + 3 neg) + `eval/evaluator.md` (Spec-A strict recall@5 gate 0.7, rerank≥0.7) + `eval/trial-state-lock.json` (baseline recall@5=1.0, chunk-drift 5% threshold) + `eval/runs/*.json`. BUT: weekly-Friday manual (`evaluator.md:88`), no scripts/ automation, RAG re-index AI_INFRA-owned. Honest nuance: harness EXISTS (richer than expected) but NOT auto-run. **(4) OUTCOME PARTIAL:** anti-downgrade rule EXISTS as Harness-8 "all-inherit, chất-lượng-trên-chi-phí, 'nhanh'=parallelism KHÔNG hạ-model" (`agents/README.md:12` + adap-report `2026-06-16-...harness-8:18,42,65`). BUT phrased as model-tier policy, NOT a generic "downgrade-to-save-tokens=forbidden" rule; `docs/rules.md` has NO such rule (grep 0). em-main must author H-14-specific knobs only if wanting explicit "age≠cut-basis" doctrine — mechanisms already structurally compliant. Tag `[harness-14, budget-no-decay-knob, canonical-anchor-not-age, eval-genuine-richer, anti-downgrade-h8-partial]`.

- **2026-06-18 (PE price-model recon FDC "Giá chào thầu" PRO-Min/Max + CCM-proposed, on-disk):** ⭐ **"Giá chào thầu" mục c = DERIVED, KHÔNG stored column** = `WinnerQuoteTotal` = SUM(Quote.ThanhTien WHERE supplierRows==SelectedSupplierId). Computed 3 nơi đồng-predicate: submit-guard `PurchaseEvaluationWorkflowService.cs:188-192` · detail-GET `PurchaseEvaluationFeatures.cs:818-826`(→`CurrentProposalTotal`) · CEO-threshold `:833`. DTO `WinnerQuoteTotal` `PurchaseEvaluationDtos.cs:244`. **ALL money fields:** Quote(NCC) `BgVat/ChuaVat/ThanhTien` decimal non-null `PurchaseEvaluationQuote.cs:12-14` · PE-header `BudgetPeriodAmount`(row3 drafter)/`ExpectedRemainingAmount`(row8) decimal? `PurchaseEvaluation.cs:40-41` · PeWorkItemBudget(per cặp Project×WorkItem) PRO `ProEstimateAmount:27` + CCM `InitialAmount`/`AdjustmentAmount`(ÂM-OK) `:29-30` decimal? · Detail dự-toán `KhoiLuong/DonGia/ThanhTienNganSach` `PurchaseEvaluationDetail.cs:15-18`. **PRO-min/max + CCM-proposed = KHÔNG tồn-tại** (grep Min|Max|Proposed|Suggest|BidPrice|GiaChaoThau PE-entities=0) → field MỚI. **Role-gate mirror-được (`PeWorkItemBudgetFeatures.cs`):** 2 cmd tách `UpdatePeBudgetProCommand:61`+`UpdatePeBudgetCcmCommand:126`; handler fail-closed `ForbiddenException` TRƯỚC side-effect — PRO `:86-91`(`Admin||Procurement`) CCM `:150-155`(`Admin||CostControl`); capability-flag BE-computed `canEditPro/canEditCcm` `PurchaseEvaluationFeatures.cs:783-784`→DTO `PeBudgetSummaryDto:290-291`; auto-create race-safe `PeWorkItemBudgetEnsurer.EnsureTrackedAsync:34`; KHÔNG ràng Phase. NO AutoMapper (DTO project tay). **FE (fe-user `src/`; fe-admin PeDetailTabs.tsx = SHA-identical `diff -q`):** mục-c `components/pe/PeDetailTabs.tsx:1406-1417`(helper `computeGiaChaoThau` def:71 call:1393) · budget-table `PeBudgetSummaryTable:1062-`(rows:1110-1128, host `ChonNccSection:1383`) · giá-gói+CEO-threshold `:311-313` · create `PeWorkspaceCreateView.tsx` · header `PeHeaderForm.tsx`. FE type `types/purchaseEvaluation.ts` `PeBudgetSummary:292-307`+`winnerQuoteTotal:445`. ⚠️ fe-admin types DIFFER (sync cả 2). **Surprise:** PRO-Min/Max-chốt + CCM-proposed = semantic MỚI (giá-người-duyệt ≠ giá-NCC-báo); gắn PeWorkItemBudget(per-cặp role-gate-sẵn) vs column-PE(per-phiếu) = em-main quyết. Mig 53 CeoApprovalThreshold+cờ-gấp đã có khung CEO-duyệt-theo-ngưỡng. Tag `[pe-price-model, gia-chao-thau-derived, pro-minmax-ccm-proposed-NEW, role-gate-mirror, fdc]`.

- **2026-06-18 (S71 PART-C audit — run-trace vs checklist-v2 FLAT + detector-refine, on-disk):** ⭐ **2 GAP THẬT (trung-thực, không inflate):** (1) **C1/C2/C8 = SUBFOLDER, canonical-v2 = FLAT → migration NEEDED, chưa làm.** `find runs/` cho thấy MỖI run-folder có `sub-md/`+`harvest/` SUBDIR (5 run: h10-{invest,implement,review}+h910-{finalize,curate}) — đúng cấu-trúc CŨ broadcast-delta phát-bỏ. ZERO flat-awareness: grep `phẳng|flat|cùng cấp` trong `.claude/workflows/`+`.claude/commands/`=0 hit. SE-adoption-commit `8c47bd0`(06-18) TRƯỚC broadcast-flat cùng-ngày → SE chưa biết. README/hmw.js/session-end đều mô-tả subfolder. C8 dual-form-acceptance close-gate cũng chưa. (2) **REFINE(b) detector = MISSING HOÀN-TOÀN.** `find .claude -name *.js/*.ps1`=CHỈ `hmw.js`(=engine ≠ detector). `.claude/hooks`+`.claude/scripts` KHÔNG tồn-tại. Repo-wide grep `bypass|scan.*runs` script=0. SE KHÔNG có bộ-dò chống-lách-engine → 3-function (whitelist/path-variants/launch-key-anchor)+relation-acceptance = n-a. **MET (đừng nhạ oan):** C3 committed THẬT — `git check-ignore runs`=exit1(NOT-ignored)+`git ls-files runs`=22 file (cả hai nấc). C4 per-turn real (`invest-synthesis.md` 43-dòng). C5 3-layer wired: L1 README:51(convention em-main) · L2 `session-start.md:71` orphan-scan `runs/*/` closed=⏳+harvest-rỗng · L3 `session-end.md:51` close-gate idempotent 5-trục. C6 ledger 2-beat (`_ledger.md:7`, 5 run đều CLOSE-beat+wf_). C7 caveat present (README §69-73 no-overclaim/fragile/G-015 TRACKED≠enforced). ⚠️ sub-md/ chỉ `.gitkeep` (read-only sub→em-main scribe, design KHÔNG phải miss). Tag `[s71, part-c-audit, subfolder-not-flat, detector-MISSING, c3-committed-real]`.

- **2026-06-18 (S71 Harness-10 ref-sweep — wave-*/agent-teams/harvest migration map, on-disk):** ⭐ **2 RULE-MECHANISM (cơ-chế, sửa CẨN THẬN ≠ text-swap):** (1) **`.gitignore:93-94`** `.claude/workflows/wave-*/` + `.claude/agent-teams/` AFTER `!.claude/**:83` (last-match-wins) — Harness-10 LẬT containment: runs/ TRACKED nên KHÔNG gitignore (đã có `runs/_ledger.md:4` "tracked-change NGOÀI run-folder = vi-phạm" thay B6 "mọi tracked = vi-phạm"). agent-teams = n-a Windows in-process → giữ-hay-bỏ tùy. (2) **`hmw.js`** wave-mechanism: meta.description:9 (2-MODE) · args:19 `wave:{name,dir}` · SCHEMA subMdPath:52 · WAVE-MODE block:87-91 (`const wave = A.wave&&A.wave.dir`) · log:94 · subMd path:102 · writeGuard TOOL-AWARE 2-nhánh:106-120 (wave→sub-MD-isolated / default→return-delta) · prompt subMdPath:131. → run-trace = đổi `wave`→`run`, dir `wave-<tên>`→`runs/<run-id>`, +harvest/ path. **TEXT-ONLY (đổi chữ, KHÔNG cơ-chế):** `.claude/workflows/README.md` TOÀN BỘ (48 dòng, đầu-đề + table 2-MODE + structure + B1-B6 + agent-team §) · `session-end.md:32,49,51` (§L.b(d)(f) GATE + B5 wave-gom) · `session-start.md:71` (H2 báo wave-folder tồn-đọng) · `agents/README.md:111` (decision-tree wave-gom B5) + :22-28,52 harvest-curator.md (B5 scan path `wave-<tên>/sub-*.md` + agent-team) · `harvest-curator/MEMORY.md:20` (wave-folder gitignored). **DOC/HISTORY (immutable evidence — KHÔNG sửa):** `broadcasts/**` (handshake:17 + inbox/README:15 "khác wave-folder gitignored") · `docs/governance/adap-reports/2026-06-07-Agent-harness-2.md` (toàn bộ B1-B6 spec) · `docs/changelog/sessions/*` · `error-ledger.md:86` (wave-folder-leak=0 evidence). **ĐÃ SCAFFOLD Harness-10 (S71 đang chạy):** `runs/_ledger.md` (2-beat OPEN/CLOSE) + `runs/2026-06-18-h10-invest/run.md` + `harvest/`. ⚠️ **Note .gitignore:92 comment** `git check-ignore -v .claude/workflows/wave-x/wave.md` = verify-cmd cũ, đổi theo. Tag `[s71, harness-10-refsweep, wave-to-runtrace-migration, gitignore-mechanism, hmw-wave-mechanism]`.

- **2026-06-18 (S71 Harness-10 STAGE-C harvest-flow recon — per-turn + 3-layer wire points, on-disk):** ⭐ **CURRENT harvest = SINGLE-POINT @session-end (B5), KHÔNG per-turn.** Driver = `harvest-curator` H2 (`agents/harvest-curator.md:22` "sau workflow-dài/cuối-session quét `wave-<tên>/sub-*.md`→gom→APPEND agent-memory/<role>"). Wired ONLY `session-end.md §L.b(f):51` (5-trục GATE + wave-folder gom B5). `session-start.md:71` = REPORT-only (báo wave tồn-đọng, KHÔNG gom). ZERO per-turn hook — `hmw.js` JS-sandbox no-fs (`hmw.js:5`), harvest deferred-to-close. **C4 per-turn-primary wire (3 chỗ):** (a) `hmw.js:122-134` prompt-builder — sub return findings; (b) NEW em-main step: ghi `runs/<id>/harvest/` SAU MỖI fan-out turn (KHÔNG đợi close); (c) `session-end.md §L.b(f):51` đổi "gom @end" → "VERIFY per-turn harvest đủ". **C5 3-layer anti-miss wire:** L1 in-run-reminder = `hmw.js` prompt + `run.md` checklist (run trước chưa-harvest → flag); L2 post-exec-rescan = `session-start.md:71` (mở rộng orphan-scan `runs/*/` tìm ledger-OPEN-no-harvest, hiện chỉ báo wave); L3 close-gate = `session-end.md §L.b(f):51` (GATE đã có, repoint wave→runs). **EVIDENCE tracked:** `git check-ignore runs/.../run.md`→matched `!.claude/**` (.gitignore:83 negation)=NOT-ignored ✓ vs `wave-*/` still gitignored (:93). Run-folder ĐÃ scaffold S71: `runs/2026-06-18-h10-invest/`{run.md·sub-md/.gitkeep·harvest/.gitkeep}+`runs/_ledger.md` (2-beat OPEN/CLOSE `:3`, orphan=OPEN-no-CLOSE). **G-015 shift:** Harness-2 "mọi tracked=vi-phạm" (wave gitignored→diff mù) → Harness-10 "tracked NGOÀI run-folder+code-disjoint=vi-phạm" (`_ledger.md:4`) → containment MẠNH hơn (run-folder in git-diff thấy sub-MD writes). Tag `[s71, h10-harvest-flow, per-turn-C4, 3-layer-C5, single-point-end-current]`.

- **2026-06-18 (S71 Harness-10 task-A — hmw.js EXACT edit-list wave→run-trace, on-disk):** ⭐ Refines sibling ref-sweep with precise diffs. **3 LOGIC edits:** (1) `:90` `const wave=(A.wave&&A.wave.dir)?A.wave:null` → `run=(A.run&&A.run.dir)?A.run:null`; (2) `:102` subMd `\`${wave.dir}/sub-${role||'task'}-${i}.md\`` → `\`${run.dir}/sub-md/sub-${role||'task'}-${i}.md\`` (⚠️ +`/sub-md/` SUBDIR — matches scaffolded `runs/<id>/sub-md/`, today FLAT); (3) `:106-120` writeGuard 2-branch keep TOOL-AWARE, reword. **CONTAINMENT-FLIP 2 strings:** `:112` "wave-folder gitignored nên KHÔNG hiện trong diff = sạch" → "run-folder TRACKED; tracked-change NGOÀI run-folder(+code-disjoint)=vi-phạm" (model = `runs/_ledger.md:4`); `:114` "file NGOÀI repo/wave-folder"→run-folder. **TEXT reword:** `:5,9(+drop stale two-tier H4.5→H8),19(args wave→run),52,55,88-91,94,108,113,131`. **VERDICT: pure mechanical** — fan-out/SCHEMA/resolveModel/parallel/checkpoint-gate ALL unchanged; only rename + path-subdir + 2 string-flips. **Read-only sub flow same** (`:111` subMdPath→em-main-scribe @P3, no Write tool). **C2/C4 stay em-main** (hmw.js no-fs `:1-5`). Tag `[s71, h10-task-a, hmw-exact-difflist, subdir-sub-md, mechanical-rename]`.

- **2026-06-17 (PE-workflow recon for FDC feature-plan — urgent flag + value-threshold routing, on-disk):** ⭐ **PE VALUE: NO stored "giá trị gói thầu" column.** Best-fit = winner-quote-total `SUM(Quote.ThanhTien WHERE supplier==SelectedSupplierId)` — COMPUTED (submit-guard `PurchaseEvaluationWorkflowService.cs:188-190` + `CurrentProposalTotal` in `PeBudgetSummaryDto`). Other amounts: `PE.BudgetPeriodAmount`(:40 drafter NS kỳ này)/`ExpectedRemainingAmount`(:41)/`PeWorkItemBudget.FullAmount`=(Initial??0)+(Adjustment??0) (`PeWorkItemBudget.cs:29-30`) — all budgets, not deal-value. **ROLES PRO/CCM/CEO = domain shorthand NOT constants** (`AppRoles.cs` has Procurement/CostControl/Director; PRO=Procurement CCM=CostControl CEO=Director). **V2 routing IGNORES roles** — approvers = specific `ApproverUserId` (`ApprovalWorkflow.cs:80`), OR-of-N = N Level rows same `Order` (GroupBy :687). "Phòng CCM" = seed Step NAME + non-strict DeptId hint only (`:67`). **CEO = positional (last level/last step), NOT conditional.** **ROUTING 100% LINEAR** (level→step, `DaDuyet` when `nextIdx>=steps.Count`). ZERO value/threshold/conditional config anywhere (grep 0 on AW/Step/Level/PEType). ⭐ **HOOK B (value-threshold) = `ApproveV2Async` advance block lines 816-845** (`:817` levelOrder++ / `:828-837` terminal DaDuyet / `:838-845` next step). Precedent: `skipToFinal :773-814` already "jump pointer to last step+level" — reuse mechanic conditioned on value. **HOOK A (urgent):** add `IsUrgent bit`/`PePriority` enum (mirror `ItTicketPriority{Low,Medium,High,Urgent}` `Office/Enums.cs:48-54`) AddColumn no-new-table; notify `INotificationService.NotifyAsync(userId,type,title,desc?,href?,refId?)` (`INotificationService.cs:10`)+SignalR interceptor; LogTransition notifies DRAFTER-only on terminal (`:960-980`), NO approver-notify yet. Badge DTOs: `PurchaseEvaluationListItemDto`(`PurchaseEvaluationDtos.cs:6`)+`DetailBundleDto`(:201). Type A/B (`PurchaseEvaluationType.cs:6-10`) constrains pinnable ApplicableType only — ZERO type-conditional routing. ⚠️ "Từ chối" REMOVED S60 hard-guard `:80-85` (throws even Admin; only Duyệt/Trả lại). ⚠️ drafter-in-chain bypass `:543` auto-approves drafter's own step-1 levels on submit (interacts w/ value-finalize). Tag `[pe-workflow-recon, value-threshold-hook, urgent-flag, fdc-feature-plan]`.

- **2026-06-17 (S69 recon — Office-module inventory + Hồ sơ-NS CSS-contract, on-disk):** ⭐ **PART A Office:** 21 `Off_*` keys (`MenuKeys.cs:99-121`): root `Off` + DanhBa(card-grid), `Off_PhongHop`{View=cal/Manage=room-CRUD-admin/Book}, `Off_DeXuat`{List/Create/Inbox=Proposal-V2}, `Off_DonTu`{Leave/Ot/Travel}, `Off_DatXe`, `Off_ItTicket`, `Off_ChamCong`(re-parent→Personal S57), `Off_AttendanceReport`(admin). 10 office pages `{fe-admin,fe-user}/src/pages/office/` ALL SHA256-MIRROR except **MyAttendancePage DIFFERS** + AttendanceReportPage ADMIN-ONLY. Routes `App.tsx` user:70-80/admin:88-100; staticMap `Layout.tsx:87-103` (workflow-apps :kind `/workflow-apps/{leave,ot,travel,vehicle}`); menuKeys.ts:45-63. **HIDE-FLAG** `RevokeTemporarilyHiddenModulesAsync` (`DbInitializer.cs:2157-2190` called :2040 LAST) wipes CRUD on `MenuKey.StartsWith("Off")||"Hrm"||==Personal` non-Admin, idempotent. **Golive flip:** remove :2040 call (+ re-add prefix InReviewScope grant). Office already S55-shell polished NOT bare. **PART B Hồ sơ-NS CSS:** layout=3-col flex (`EmployeesListPage.tsx` SHA256-identical x2, 1597 LOC): cây-tổ-chức TRÁI(:178) + NV-list MID(:244) + detail PHẢI = avatar-header `app-gradient-brand`(:643)+`text-white!`(:653)+initials chip bg-white/15 → 5-TAB(:507 Tổng quan/Thân nhân/Trình độ/Kinh nghiệm/Hợp đồng) → `Card`(:1526 left-rail+icon-chip) w/ `Field`(:1572 label uppercase accent-tint + value `font-medium text-brand-800`, empty=`text-slate-300 —`). `ACCENT` map :497-503 Record<5,{chipBg/chipFg/head/rail/labelText}> accent∈{brand,teal,violet,amberx,greenx}, palettes stops 50/100/500/600/700 only no-800→headings -700 (brand -800 OK). Tokens `index.css`: brand-600=#1f7dc1 brand-800=#175685 @theme:5-55, font Be-Vietnam-Pro:53; classes `.app-gradient-brand`(:105 120deg b600→700→800),`.card-accent`(:112),`.icon-chip`(:128 --chip-bg/--chip-fg),`.stat-value`(:140),`.label-eyebrow`(:89). ⚠️ **GOTCHA #66 = `index.css:79-83` `h1,h2,h3,h4{color:#0b1220;font-weight:700}` OUTSIDE @layer** → TW-v4 unlayered wins → heading-tag inside gradient MUST `text-white!`. ⚠️ **CROSS-APP DRIFT:** fe-user=S68 (h1-4 #0b1220/700, label-eyebrow brand-600, 175L); **fe-admin STILL OLD** (h1-4 #0f172a/600, label-eyebrow #64748b slate, 167L) — fe-admin NOT synced S66-68 heading bump → mirror Office to fe-admin needs index.css sync. Tag `[s69, office-inventory, hoso-css-contract, gotcha66, fe-admin-css-drift]`.

- **2026-06-18 (S71 Harness-8/hmw/pending/sleep audit — fidelity ground-truth, on-disk):** ⭐ **H8 all-inherit = FULLY ADOPTED both layers.** (1) Frontmatter: ALL 12 `.claude/agents/*.md` = `model: inherit` (11 sub + README; grep-count 12/12, zero demoted). (2) `hmw.js resolveModel:36-44` = all-inherit (`:43` role-with-frontmatter→`undefined`=inherit; `:41` invalid-role→fail-UP inherit; `:42` role-less→inherit). **Per-task escape-hatch PRESENT** `:37` `if(tier==='fable'||'opus')return tier` (H8.1 "ngoại lệ per-task" satisfied). **hmw run-trace = args.run + legacy args.wave alias** `:91` `(A.run&&A.run.dir)?A.run:((A.wave&&A.wave.dir)?A.wave:null)` ✓. ⚠️ **GAP-1 (the one real gap): hmw FLAT-vs-SUBFOLDER.** hmw STILL emits SUBFOLDER: `:103` `subMd=${dir}/sub-md/${role}-${i}.md` + desc/`:113` reference `run.md+sub-md/+harvest/`. The 2026-06-18 `h10-flat-detector-refine` (supersedes-part-of checklist-9-10 §C1/C2/C8) mandates FLAT files-one-level distinguished BY FILENAME, no subdirs. Disk confirms subfolder: `runs/2026-06-18-h10-invest/` has `harvest/`+`sub-md/` dirs (not flat). **GAP-2: SE adopted checklist v1 NOT v2** (`broadcasts/outbox/ai_infra/2026-06-18-...checklist-adopted.md:16` cites `2026-06-16-Governance-checklist-harness-9-10` = v1, no `-v2`). **UNADOPTED broadcasts (2 confirmed PENDING):** `2026-06-18-h10-flat-detector-refine` + `2026-06-18-checklist-harness-9-10-v2`. NO other newer (last outbox/all = these two; SE outbox last report = 2026-06-18 v1). **C3 two-level VERIFIED:** `git check-ignore runs/` exit=1 (NOT-ignored, neg `!.claude/**` :83) + `git ls-files runs/` = 22 files committed. `wave-x/wave.md` check-ignore exit=0 (still gitignored :93). **sleep-recovery-memory-l2 = AI_INFRA-internal** (`AI_INFRA/.claude/commands/sleep-recovery-memory-l2.md` + `docs/architecture/MEMORY-SLEEP-RECOVERY-L2-DESIGN-v3.md:scope` "CHỈ L2, KHÔNG sister"; NOT in outbox/all — only its Harness-9 broadcast form is, already adopted S70). NOT a sister obligation. **SE already does equivalent FULLY** (not ad-hoc): `memory-budget.json` (caps seeded-by-measure `scripts/measure-agent-memory.ps1`) + 4× `archive/_INDEX.md` + 4× `*.gist.md` (`distill-gen:2` counter) + `.ragignore` (exclude index/gist). Tag `[s71, harness8-audit, hmw-flat-gap, checklist-v1-not-v2, sleep-recovery-ainfra-internal, gist-additive-done]`.
- **[→ git pre-S60]** S60 recon#2 V2-engine-map (ApprovalWorkflow.cs Step/Level Order 1-based per-step; OR-of-N=N rows cùng Order service GroupBy:475; ApproveV2Async:446-634 guard+UPSERT+advance; notify DRAFTER-only:748; skipToFinal F2:561-602 = precedent advance-không-ghi-opinion) · S60 PE Section-3 submit-guard (submit path POST/pe/{id}/transitions→TransitionAsync:38 ROLE-only guard NO data-check; Section-3 mục a/b/c/d map — SUPERSEDED bởi S65ter post-Mig50 Budget-drop; test mirror PurchaseEvaluationWorkflowServiceGuardTests). Full text git.

- **2026-06-16 (S65bis recon — Employee profile master-detail vs NamGroup, on-disk):** ⭐ **STALE-PREMISE CORRECTION:** fe-user `/employees` KHÔNG list-only — `hrm/EmployeesListPage.tsx` (1201 LOC) ĐÃ master-detail 2-panel (filter sidebar :117 + list table :197 + inline detail :234) với **6 collapsible section** (`<details>` :1157, KHÔNG tab) + 5 satellite inline CRUD (WorkHistory/Education/FamilyRelation/Skill/Document, `setEditing{X}Id`+`adding{X}` mutex pattern 12-ter S35). **fe-admin == fe-user `diff -q` IDENTICAL** (SHA256 same). **Entity gần đủ screenshot:** `Domain/Hrm/EmployeeProfile.cs` (137 LOC) CÓ: DOB/Gender/Ethnicity/Religion/Nationality/Height/Weight(:98-99)/IdCard(số+ngàycấp+nơicấp :52-54)/permanent+temporary addr/phone/personalEmail/code/hireDate/qualification/salary(Base+Total)/bank/4×leave-days. **THIẾU vs screenshot:** (a) BloodType CÓ nhưng "sức khỏe loại" (health-grade A/B/C) KHÔNG; (b) thâm niên = DERIVED từ HireDate (no column); (c) chức danh = `User.Position`/`PositionLevel` (Identity, KHÔNG ở EmployeeProfile) — list/detail JOIN Users (`EmployeeFeatures.cs:467`); (d) "lương BHXH/phụ cấp" tách riêng KHÔNG có (chỉ Base+Total); đơn vị=DepartmentName JOIN. **5 satellite entity + 15 endpoint FULL** (`EmployeesController.cs:75-233` 5 region×Create/Update/Delete; GET detail Include cả 5 :455-459). Skill polymorphic gộp 3 NamGroup table (Computer/Language/Other Kind :69). **GAP THẬT:** (1) **NO org-tree** — `Department.cs` FLAT (Code/Name/ManagerUserId/Note, KHÔNG ParentId), `DepartmentsController` chỉ GET list+byId (NO /tree), KHÔNG endpoint count-per-dept → cây trái + badge phải build CLIENT-side group-by departmentId từ list; (2) **5-tab layout** screenshot = 6-section `<details>` hiện tại (re-skin UI, data đủ); (3) "Hợp đồng lao động" tab = chỉ có `EmployeeDocument` type=LaborContract(5), KHÔNG entity HĐLĐ riêng (3 HĐLĐ table DEFER Plan H2 per `EmployeeProfile.cs:10`). **NamGroup source:** `D:\...\NAMGROUP\SOURCECODE_CÔNG_TY\` find .tsx/.razor = 0 hit (KHÔNG phải React/archived) — RAG `proj_namgroup_main` 0 component; tham khảo layout = screenshot anh gửi, KHÔNG có code mirror trực tiếp. ⇒ **Wire-lại-là-xong:** data + API + satellite CRUD 100% sẵn. **Build mới:** Department.ParentId migration + /tree + count endpoint (nếu muốn org-tree thật thay client-group). **Re-skin:** 6-section→5-tab + avatar header. **No new field bắt buộc** trừ health-grade nếu anh cần. Tag `[s65bis, employee-profile, master-detail-EXISTS, dept-flat-no-tree, stale-list-only-corrected]`.

- **2026-06-17 (S69 recon — NamGroup "PURO" digital-office layout, CROSS-REPO `D:\...\NAMGROUP\`):** ⭐ **PURO = UI design-language/skin (ref ERP demo.purocorp.vn), KHÔNG phải app riêng** — NamGroup mirror sidebar/typography của nó (comments `InternalLayout.tsx:33,74,109,200,332` "PURO exact spec"). Digital-office sống trong `namgroup.client/` (app NV; admin = config-only). **Shell = `components/layout/InternalLayout.tsx`** (724L): sidebar trái fixed h-screen + `<main flex-1 overflow-auto p-2.5..lg:p-4>` :609 chứa `<Outlet/>`. Sidebar = **`navTree` hardcoded array :76-122** (KHÔNG DB), flat 2-tier group→leaf, 4 group: **"Văn phòng số" :90-100 = 6 leaf** {Danh bạ `/danhba` · Phòng họp `/phonghop` · Đề xuất `/dexuat` · Đơn từ `/dontu` · Đặt xe công `/xecong` · Ticket CNTT `/ticket`}; + Nhân sự(3) + Cá nhân{Chấm công}(1) + Hệ thống(5). Routing = `App.tsx:81-140` flat `<Route element={InternalLayout}>` > `<RouteGuard>` (perm) > index=HomePage. **Landing `/` = `internal/HomePage.tsx`** (296L): grid 2-col (LEFT 2/3 stack 4 WidgetCard: Đề xuất/Nghỉ phép/Bình luận/Truyền-thông · RIGHT 1/3 Công-việc-của-tôi); WidgetCard = gradient-blue header + inline stat-chips + body/EmptyState (shared comp tại :219). **Layout pattern mỗi feature (KHÔNG dùng tab — dùng KpiCard-row + view-toggle):** (a) `<PageHeader>` shared (icon-badge accent + breadcrumb + actions slot, `components/shared/PageHeader.tsx`); (b) **KPI stat-cards clickable filter** (DeXuat :1643 6-card / Ticket :197 5-card — "PURO KPI cards" comment); (c) body = **list-table** (DeXuat/Ticket: `<table>` master + right detail panel grid-cols-3) HOẶC **list↔calendar ViewToggle** (DonTu :683 + XeCong + PhongHop: custom month-grid Sun-Sat, NO FullCalendar — comment :PhongHop "saves install friction"); DanhBa = dept-tree trái + card-grid phải. **Shared comp tái dùng:** PageHeader · DataTable · KpiCard · CrudToolbar · ActionLogList · DatePickerVN · MasterDataPage · DonutChart/MiniBarChart (`components/shared/` 16 file). **Top files mirror:** InternalLayout.tsx(shell+nav) · HomePage.tsx(dashboard) · PageHeader.tsx · DeXuatPage.tsx(1676 KPI+table+detail) · DonTuPage.tsx(1269 +DonTuCalendar.tsx toggle) · XeCongPage/PhongHopPage(month-grid) · TicketPage.tsx(595) · DanhBaPage.tsx(756 tree+grid) · ChamCongPage.tsx(809). ⚠️ Style/màu lấy chỗ khác per task — chỉ structure. SE đã có analog (fe-user `InternalLayout`/office pages) nhưng layout khác (deep-nested vs PURO flat). Tag `[s69, namgroup-puro-recon, digital-office-layout, hardcoded-navtree, kpicard-not-tab, cross-repo]`.
- **[→ archive/2026-06.md + git]** S52 P11-D/E/F 6-gap recon (IT-pool absent → S56 corrected: dept IT exists 0 user · SlaExpiryJob HostedService DI:46 pattern · OtPolicy 3-multiplier · ClosedXML exporter reuse · Attendance API cá nhân-only · FE skeleton state — full text git pre-S60) · S50 P11-C HrmConfigs add-kind 11-chỗ pattern · S50 wave h2-verify B6 gitignore ordering + POSIX-not-pwsh (curated S57bis) · S51 gotcha #57 EXT reachability 3-Master-fix/3-skip global-filter-makes-bug (curated S59).

- **Archived S29-S37 → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** S36 G-O2 Phòng họp clean-room + FullCalendar v6 MIT eval · S36 startup MEMORY-size audit · S35 G-H2 HRM clean-room verdict · S33 G-H1 NamGroup TblNhanVien 10-bảng (105 cols main) · S33 startup RAG verify · S32 Plan G 11-module backlog · S29 Plan CA+B pre-flight (3 patterns: 9-menu terrain, V1+V2 coexist, reference-template-paths cite line-range ROI). KEY absorbed: **clean-room > NamGroup port verified 4×** · Pattern 12-bis cross-module mirror · FK+freetext dual-write.

- **2026-06-18 (S71 audit — Harness-9 PART B adap-2workflow trung-thuc, on-disk):** ⭐ **VERDICT B substantially-LANDED, ZERO nac-inflation (reports UNDER-state via honest hedge).** B1/B2 ok-runtime: 2 adap cycle ran SEPARATE workflows distinct run-id — S70 impl `wf_a58e0d15-beb`≠audit `wf_9520d8cd-4fe`; S71 impl `wf_e4e46725-231`≠review `wf_636bc95b-939` (review caught real C5-L1 over-claim impl-self-check missed). B2.5 ok: reverse-findings non-empty both reports (3+4 items)+both emails. B3 ok: 2 emails `broadcasts/outbox/ai_infra/{2026-06-17,2026-06-18}-se-to-ai_infra-*.md` carry true-nac+findings+BOTH run-ids. B4 **partial/convention-met**: rule codified `adap-apply.md:38` (short-but-confirm→review) BUT no runtime instance (no short-decision task arose S70/S71) → pure-convention, lead-discipline. ⚠️ **3 PRECISION-flaws (not protocol-gap):** (1) reviewer-agent StructuredOutput unreliable both sessions → em-main self-gate git/sha (disclosed, valid-branch per feedback memory). (2) **PATH-TRAP:** S71 report/email self-verify cited bare `git ls-files runs/`=14 — WRONG; actual = `.claude/workflows/runs/` (22 tracked files, 5 runs: invest/implement/review + h910-finalize `wf_73de399d-753` + h910-curate `wf_f32987b8-03f`). Auditor running bare `runs/` from repo-root gets 0 → false "not-committed". Folder genuinely committed `8c47bd0` + NOT-ignored at correct path. (3) hmw.js RUN-TRACE runtime honest-flagged pending-restart. adap-apply.md:33-36 codify 2-workflow mandate (auto-loaded). Tag `[s71-audit, harness-9-partB, adap-2workflow, path-trap-runs-folder, b4-convention-gap, no-nac-inflation]`.

- **2026-06-25 PE FROZEN patchwork audit AREA-2 (budget matrix + Section B 3-col, Mig 56/59):** ⭐ **BAND-AID CONFIRMED** `SetPeCcmBudgetPeriodCommand+Validator+Handler` (`PeSuggestedPriceFeatures.cs:153-199`) is a BUDGET setter (writes `pe.CcmBudgetPeriodAmount`) MISPLACED in a price-feature file; SHOULD live in `PeWorkItemBudgetFeatures.cs` (where UpdatePeBudgetPro/Ccm sit). Test+comments already self-flag (PeCcmBudgetPeriodSetterAuthzTests.cs:14). ⚠️ **Role-gate ASYMMETRY (intentional, NOT a bug — but inconsistent surface):** CCM budget setters = flat role-gate (CostControl|Admin fail-closed): UpdatePeBudgetCcm `PeWorkItemBudgetFeatures.cs:160-165` + SetPeCcmBudgetPeriod `PeSuggestedPriceFeatures.cs:176-181`. BUT the PRO sibling `/budget-adjust`→`AdjustPurchaseEvaluationBudgetCommandHandler` `PurchaseEvaluationFeatures.cs:349-394` = **phase/drafter/workflow-slot gate** (Drafter when Nháp/TraLai; Approver+AllowApproverEditBudget F4 when ChoDuyet) NOT role-PRO. So "PRO col" & "CCM col" of same Section-B row 3 have DIFFERENT authz models (PRO=drafter-flow on `pe.BudgetPeriodAmount`; CCM=role-flow on `pe.CcmBudgetPeriodAmount`). Both per-PE fields (NOT PeWorkItemBudget per-pair — that's the matrix rows Initial/Adjustment). Section-B **"Dự án" col = intentional FE-only "—"** hardcoded (`PeDetailTabs.tsx:1520,1542,1596,1606`; Domain `PeWorkItemBudget.cs:19` "chưa wire BE — sau mới có người dự án nhập") — documented, NOT a stub-bug. Controller class-level `[Authorize]` only (`PurchaseEvaluationsController.cs:15`) → handler gates ARE the boundary. Tests solid (PeWorkItemBudgetTests 30+ cases, PeCcmBudgetPeriodSetterAuthzTests 7). → _INDEX.

- **2026-06-24 PE 3-change recon (Lưu Trần/anh Kiệt CCM — multi-winner + budget=0-valid + per-step terminal):** ⭐ (A1 winner) cardinality=**SINGLE** scalar `PE.SelectedSupplierId Guid?` (`PurchaseEvaluation.cs:28`); cmd `SelectPurchaseEvaluationWinnerCommand(PEId,Guid?)` `PurchaseEvaluationDetailFeatures.cs:392` handler `:398-423` (null=clear S85-C5); ctrl `/select-winner` `PurchaseEvaluationsController.cs:167-172`. winnerQuoteTotal=**DERIVED** SUM `Quote.ThanhTien WHERE SupplierId==Selected` 3 sites: read `PurchaseEvaluationFeatures.cs:1089` + submit-guard `PurchaseEvaluationWorkflowService.cs:191` + ccm-delegate `:868`. ⚠️ Quote ALREADY has `IsSelected bool` (`PurchaseEvaluationQuote.cs:15` per-item winner, UNUSED by total). Multi-winner = NEW junction OR reuse IsSelected + change predicate `==Selected`→`IN selectedSet`. FE single `<Select>` `PeDetailTabs.tsx:961-991` (NccSelectorRow). (A2 budget=0) BLOCKED 2 layers: validator `.GreaterThan(0).When(HasValue)` create `:55` + UpdateDraft `:233` + budget-adjust `:339`; submit-guard `effectiveBudget<=0`→missing `PurchaseEvaluationWorkflowService.cs:213-216` (effective=`BudgetPeriodAmount??ProInitialAmount` S85-D3). FE `effBudget<=0` `PeDetailTabs.tsx:211,237` canSubmit `:242`. To allow 0: relax `>0`→`>=0` validator + submit `<0` + FE. winnerQuoteTotal-gate SEPARATE `:194`. (A3 terminal) `ApprovalWorkflowLevel` already has 6 per-slot bool flags (`ApprovalWorkflow.cs:104-131`: AllowReturnOneLevel/OneStep/ToAssignee/ToDrafter/EditDetails/EditBudget/**SkipToFinal**); `CeoApprovalThreshold decimal?` on WORKFLOW `:43`. Finalize branches `ApproveV2Async`: terminal `nextIdx>=steps.Count` `:905-916` + ccm-delegate opt-in `:854-891` + skipToFinal `:803-844`. "Approve-ends-here" = NEW per-Level bool mirror SkipToFinal but set Phase=DaDuyet (like ccm-delegate but no threshold). Designer FE = `fe-admin/ApprovalWorkflowsV2Page.tsx`. **schema-decision forks→em-main:** (1) multi-winner storage: reuse Quote.IsSelected vs new PE↔Supplier winner-junction (2) budget=0 vs budget-null distinction post-relax (3) terminal flag scope per-Level vs per-Step + interaction w/ CeoApprovalThreshold (both finalize → ordering). → _INDEX.

- **2026-06-25 PE AREA-4 UpdatePeDraft edit-preserve audit (gotcha #73-class hunt, FROZEN Mig 59):** ⭐ **LATENT #73-CLASS BUG FOUND:** `UpdatePeDraftHandler` absolute-sets `entity.HoSoLink = request.HoSoLink` (`PurchaseEvaluationFeatures.cs:280`) BUT `PeHeaderForm.tsx` PUT payload (admin+user `:112-120`) OMITS hoSoLink → editing header CLEARS HoSoLink (Mig 52) to null. HoSoLink set separately in `PeDetailTabs.tsx:2012-2020` HoSoLinkRow (re-sends all absolute fields but only IT edits link). Two PUT call-sites to SAME /purchase-evaluations/{id} endpoint are mutually destructive. PeHeaderForm reachable as EDIT (`PurchaseEvaluationCreatePage.tsx:13,26` editId from URL `?id=`, deep-link "Sửa header"). Field table: TenGoiThau/DiaDiem/MoTa/PaymentTerms absolute-set BUT PeHeaderForm re-sends all (safe); HoSoLink absolute-set + OMITTED (BUG); ApprovalWorkflowId/Budget/ExpectedRemaining/WorkItemId all null-safe `if(x is not null)` `:285-297` (S85 fix). ExpectedRemaining absolute-set in budget-adjust `:423-424` (FE sends both, OK). Require-workflow NOT duplicated: CREATE-validator `:48` + submit-guard `PurchaseEvaluationWorkflowService.cs:237` = different lifecycle (coherent defense-in-depth); UPDATE-validator correctly omits. Tests SOLID: PeUpdateDraftWorkflowPreserveTests (3 null-safe cases incl ApprovalWorkflowId+Budget) + PeSubmitGuardAndBypassTests (14) + PurchaseEvaluationDraftGuardTests (8). NO HoSoLink-preserve test (test-gap for the bug). → _INDEX.
- **2026-06-25 PE AREA-3 finalize/price audit (FROZEN Mig 59 Run#346 — patchwork hunt):** ⭐ TWO finalize mechanisms COEXIST + BOTH LIVE (NOT dead): (1) `AllowApproverFinalize` per-Level AUTO config (Mig 58) `PurchaseEvaluationWorkflowService.cs:861-877` + (2) `finalizeByCcmDelegation` OPT-IN param threshold-gated (Mig 54) `:879-915`. AUTO runs FIRST + returns → if both eligible, OPT-IN flag silently ignored (coherent, no double). `finalizeByCcmDelegation` FULLY wired E2E (ctrl `:141`+`:351` · Feature `:474,:526` · IPe…Service:32 · FE both `PeWorkflowPanel.tsx:184,584` ô-tích) — NOT orphan. ALL 3 DaDuyet paths bind giá chốt via `ApplyApprovedPriceOnFinalize` (private static `:956-971`, allow-list `{Ncc,ProMin,ProMax,Ccm}` `:955`): terminal `:933` + AllowApproverFinalize `:863` + ccm-delegate `:901`. isSystem-exempt branch (`:961-964`) = DEAD via public ApproveV2Async (needs decision=AutoApprove but APPROVE-STEP gates Approve; PE has NO SLA-job — only Contract) — tested-only-via-reflection (PeApprovedPriceFinalizeTests:238-295, header DISCLOSES "defensive/dead"). **MISPLACED-CODE CONFIRMED (known band-aid):** `SetPeCcmBudgetPeriodCommand` (Mig 59 Section-B BUDGET setter) lives in `PeSuggestedPriceFeatures.cs:153-199` (a SUGGESTED-PRICE file) — wrong file, own header admits Section-B. Has test `PeCcmBudgetPeriodSetterAuthzTests.cs` (NOT test-gap). DTO `currentLevelOptions` per-NV-discriminated `PurchaseEvaluationFeatures.cs:950-963` = same source as service `matchingLevel` (coherent). 4 finalize test files all present + green-design. → _INDEX.
- **2026-06-25 PE AREA-5 urgent/notify/TZ audit (FROZEN Mig 59 — gotcha #72):** ⭐ (a-TZ) `UtcDateTimeJsonConverter` registered ONLY on `AddControllers().AddJsonOptions` `Program.cs:33-34` — covers REST. ⚠️ **SignalR push BYPASSES it** (`AddSignalR()` no `.AddJsonProtocol`, separate STJ): `NotificationPushInterceptor.cs:77` pushes `createdAt=n.CreatedAt` over `SendAsync`. BENIGN at runtime: `n.CreatedAt=clock.UtcNow`=Kind.Utc (DateTimeService.cs:7) → default STJ writes 'Z' for Utc-kind, FE `new Date(iso)` NotificationBell.tsx:23 OK. Latent: any future SignalR push of an EF-READ (Unspecified) DateTime would skew. NOT a bug now, inconsistency-low. Converter handles DateTime? auto (non-null) — no double-convert. (b-authz) Urgent SET/UNSET branches `PurchaseEvaluationUrgentFeatures.cs:54-73` SYMMETRIC + fail-CLOSED both (`if(!allowed)throw` BEFORE mutate); 14 tests `PeUrgentToggleAuthzTests` cover plain/TP/Admin/drafter×set/unset. No fail-open. Controller class-`[Authorize]` only `:91` → handler IS boundary. (c-notify) Drafter excluded 2 sites: advance `:1138` (`!=DrafterUserId`) + terminal-result KEPT `:1085`; tests `PeApproverNotifyExcludesDrafterTests` RED-on-remove. ⚠️ **POSSIBLE double-notify-then-skip:** submit `:252` LogTransition notifies Cấp-1 approvers (excl drafter+actor) THEN `:260` ApplyDrafterBypass advances pointer + `:650/660` re-notifies new level — if drafter=Cấp1 w/ co-approver, co-approver pinged for a level immediately bypassed. Low-sev (extra ping, not wrong recipient); no de-dup across the 2 LogTransition calls. → _INDEX.
- **2026-06-23 PE UAT recon D2/D4/D5 (anh Kiệt FDC go-live):** ⭐ (D2-urgent authz) `PurchaseEvaluationUrgentFeatures.cs:49-50` BẤT-ĐỐI-XỨNG `isPro=IsUrgent?hasPro:(hasPro&&isDeptManager)` — GẮN=role-only / GỠ=DeptManager-only. NO DrafterUserId match (handler chỉ load entity, never reads DrafterUserId). Để drafter gỡ cờ mình: sửa :49-50 OR `actor.Id==entity.DrafterUserId`. Endpoint `PUT /purchase-evaluations/{id}/urgent` (controller:91). ⚠️ cờ KHÔNG split-by-drafter (PRO/CCM = role, drafter ≠ tất nhiên PRO/CCM). (D5) FE PE-create dropdown=`PeWorkspaceCreateView.tsx:94-104` GET `/approval-workflows-v2?applicableType={1|2}` filter `isUserSelectable`. **Dev DB: PE type1/type2 selectable=0** (6 selectable đều type3-9 Contract/Proposal/Leave/OT/Xe/Ticket NOT PE) → prod UNVERIFIED (SSH-paren-quote-trap). Validator REQUIRE workflow CẢ 2 tầng: `PurchaseEvaluationFeatures.cs:48` create NotEmpty (S83) + submit-guard `PurchaseEvaluationWorkflowService.cs:225` ConflictException. (D4 required) create-validator `:35-57`: Type/TenGoiThau≤500/ProjectId/WorkItemId/ApprovalWorkflowId NotEmpty + BudgetPeriodAmount>0-when-present. Submit-guard `:174-228`: SelectedSupplierId + winnerQuoteTotal>0 + BudgetPeriodAmount>0 + Bảng-so-sánh-attachment + ApprovalWorkflowId. → _INDEX.

- **2026-06-25 PE S89 AllowApproverFinalize NOTE feasibility (anh Kiệt FDC — "phiếu kết thúc cấp X không CEO" 3 surface):** ⭐ **GAP CONFIRMED — BE field MỚI cần.** `AllowApproverFinalize` (Mig 58 per-Level bool `ApprovalWorkflow.cs:142`) HIỆN CHỈ expose qua `currentLevelOptions` (CURRENT level only) `PurchaseEvaluationFeatures.cs:981` + DTO `PurchaseEvaluationDtos.cs:123` + FE type `purchaseEvaluation.ts:412`. **`approvalFlow` DTO KHÔNG carry flag**: flow-level record `PurchaseEvaluationApprovalFlowLevelDto` (`Dtos.cs:154-158`) chỉ {Order,Name,Approvers,Status}; build loop `PurchaseEvaluationFeatures.cs:1050-1065` KHÔNG project `l.AllowApproverFinalize`. **LIST DTO KHÔNG có** (`PurchaseEvaluationListItemDto:6-45`, 0 finalize field). **DASHBOARD = reuse list** → cũng thiếu. ⇒ FE detail CHỈ biết finalize khi actor LÀ approver cấp finalize (current). Để render "phiếu này kết thúc ở Bước Y/Cấp X — không CEO" cho MỌI người (kể cả non-approver, list, dashboard) → BE PHẢI expose field mới: bundle `bool EndsBeforeCeo`+`string? FinalizeStepName/FinalizeLevelName` (compute = first Step/Level có AllowApproverFinalize=true trong aw.Steps loop đã load `:985`) + mirror lên list-item + dashboard. BE finalize logic `PurchaseEvaluationWorkflowService.cs:861` (AUTO khi matchingLevel.AllowApproverFinalize → DaDuyet bỏ CEO, NO threshold/role-gate). **FE detail placement (cả 2 app SHA-IDENTICAL** sha `e477c939` PeWorkflowPanel + `49b39404` PeDetailTabs): note nên đặt đầu `PeWorkflowPanel.tsx` flow `<ol>` (sau h3 "Quy trình duyệt" :245, trước :256) — banner emerald mirror style :599 existing approverFinalize callout (chỉ trong dialog hiện). Detail đã có precedent: TraLai banner `PeDetailTabs.tsx:370-378`, V2 current-level banner `PeWorkflowPanel.tsx:346-365`. Edge: phiếu **DaDuyet** → đổi copy "đã kết thúc tại <X>, không qua CEO" (past-tense); copy đề xuất "⚑ Quy trình này duyệt tới <StepName/Cấp X> là KẾT THÚC — không trình CEO". MIRROR 2-app bắt buộc. → _INDEX.
- **2026-06-25 PE FROZEN patchwork audit AREA-6 (FE consumers BOTH apps — multi-winner + mirror-drift + dead-code + #70):** ⭐ Post-Mig58 multi-winner (`isWinner` bool) NOW implemented (supersedes 06-24 "SINGLE" recon). **All 6 PE components + 3 PE pages SHA-IDENTICAL fe-admin↔fe-user** (PeDetailTabs/PeHeaderForm/PeUrgentChips/PeListPanel/PeWorkspaceCreateView/PeWorkflowPanel). type files DIFFER but ONLY badge-tint (-700→-800 ink) + comments (intentional per-app UX, struct fields identical incl ccmBudgetPeriodAmount/isWinner/8 Allow flags). **RESIDUAL single-winner BUG (low):** `PeListPanel.tsx:168` renders `p.selectedSupplierName` (list-card winner label) → BLANK when ≥2 joint winners (BE list-DTO `PurchaseEvaluationFeatures.cs:615,706` projects from `SelectedSupplierId` which BE sets null when count≠1 `PurchaseEvaluationDetailFeatures.cs:431`). List DTO has NO isWinner/winner-list field; detail-view multi-winner correct everywhere (8 detail consumers all converted: computeGiaChaoThau:78, missingForApproval:195, submitChecklist:229, ThongTinChonThau:1949/1970, Section-B row4:1578, CreateContractDialog:2171 fallback `||selectedSupplierName`, HangMucCard toggle:2560). **#70 stale-echo ASYMMETRY:** budget Section-B CCM-period editor gates `ccmPeriodMut.isPending||peFetching` (`PeDetailTabs.tsx:1563`) but PRO sibling row3/row8 `adjustMut` gates `.isPending` ONLY (`:1548,:1649`, NO peFetching) — rapid PRO row3→row8 absolute-set pair can echo stale ev.* pre-refetch (same #70 class CCM guards). 3 peFetching sites exist (945/1268/1736). **Stale comment** PeDetailTabs:2507 references `AddItemDialog` "giữ(dead)" but function DELETED (grep 0 def) — misleading comment only, not dead code. PeWorkflowsPage admin-only = by-design (workflow config not mirrored). → _INDEX.
