Files
solution-erp/.claude/agent-memory/investigator-codebase/archive/2026-06.md
pqhuy1987 447082fb03 [CLAUDE] Docs: S80 curate L1 over-cap reviewer/inv-codebase/cicd -> L2 (archive-gate keep-floor manual, A7 217/217)
- 3 over-cap sub L1 -> L2 archive byte-exact: reviewer 45->10KB, investigator-codebase 40->10KB, cicd-monitor 39->12KB
- 31 entries moved (sed, +N -0 additive, 0 byte-loss) + 31 _INDEX substring pointers; A7 GATE PASS 217/217 resolve
- stale foundation counts flushed: 130/263->354 test, 55->71 gotcha, Mig 40/55->57, 84->88 table, bundle->#330
- 0 production code, state unchanged (Mig 57 / 88 tables / 354 test / gotcha 71)
- WATCH (A6 strike-1, no-action): frontend-designer 26KB + test-specialist 28KB
- lesson: _INDEX substring MUST quote-free (A7 quote-parser caught escaped-quote PURO pointer that self-grep missed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 11:29:11 +07:00

65 KiB
Raw Blame History

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ẠIDomain/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-20987 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 URLPurchaseEvaluationAttachmentFeatures.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:2055roleManager.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].

  • 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, 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 waverun, 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/"). 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:nullrun=(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 scaffoldedruns//sub-md/, today FLAT); (3) :106-120writeGuard 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** (:111subMdPath→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-treeDepartment.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].