Files
solution-erp/.claude/agent-memory/implementer-backend/MEMORY.md
pqhuy1987 6bfe3db5e2 [CLAUDE] Docs: S88 closeout — PE stability audit + D1/D2 prod-verified
STATUS/HANDOFF S88 prepend (State THAT: Mig 59 · 88 bang · 419 test (45D+374I) ·
gotcha 73 · bundle admin BQq2eTP6/user dHa64I59) + session log + agent-memory
harvest (6 sub: cicd/impl-backend/impl-frontend/inv-codebase/reviewer/test-spec).

D1/D2 production already shipped: 73cce1f (Run #347 PASS, +11 test, the 6 fixes) +
1aa3bdb (Run success, +6 test, create-contract 1->N + list winner names). Both
prod-verified (bundle rotation + Gitea run=success; cicd-monitor verdict truncated
#53 -> curl-self-verify).

Curate-debt: test-specialist L1 34.8KB OVER-SOFT (root-caused #53 x3 this session)
DEFERRED next session as #1 NEXT-em (proper byte-exact L1->L2 + _INDEX, not rushed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:58:15 +07:00

96 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementer-Backend Agent — Persistent Memory
> **Persistent diary cross-session.** Auto-injected first ~200 lines at spawn (L1 HOT).
> Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 `archive/` on-demand · L3 RAG `search_memory` just-in-time. Keep entry ≤ 1.5K chars (gotcha #53).
> Full verbatim history pre-S40 → git `d2f52ba` + `archive/2026-05-q1..q4.md` + `archive/2026-06.md`. 🗺️ **Lookup map (Harness-9 S70): `archive/_INDEX.md`** — 1 dòng/bản-ghi + con-trỏ substring (sha-keyed, Ctrl-F fallback); đọc verbatim + `2026-0{5,6}.gist.md` (nén 4-field) theo nhu cầu.
> **Renamed S39:** implementer → implementer-backend (.NET half). FE patterns → `implementer-frontend` MEMORY. Test patterns → `test-specialist` MEMORY.
---
## 🎯 Role baseline
WRITE specialist .NET backend SOLUTION_ERP (`Domain+Application+Infrastructure+Api`). Case 1+2+3+5 only. Tools: Read, Edit, Write, Bash, Skill, Grep, Glob + 5 RAG MCP. Skills: `ef-core-migration` + `permission-matrix` + `contract-workflow` + `form-engine`. Output: commits + verify report.
## 🚫 Split boundary (S39) + auto-refuse
- ✅ MINE: `src/Backend/SolutionErp.{Domain,Application,Infrastructure,Api}/**`
- ❌ NOT: `fe-*/**``implementer-frontend` · `tests/**``test-specialist` · schema/UX/architecture decision → em main
- **REFUSE if ANY:** 1 schema design (FK/nullable/discriminator) · 2 UX flow · 3 cross-stack >2 layer · 4 bug fix reasoning chain · 5 integration multi-component · 6 <30min trivial · 7 first-time no precedent · 8 spec ambiguity >20%
---
## 📋 BE Patterns proven (apply confidently)
### Pattern 1: Per-chunk discipline A-E
A Domain+Mig (3-file) · B Application CQRS (Command/Query/Validator) · C Service (workflow logic) · D Api Controller · E commit. Build+test pass mỗi chunk. Commit `[CLAUDE] <scope>: Chunk X — ...` + Co-Authored-By Claude Opus 4.8 (1M context).
### Pattern 2: EF migration 3-file rule (gotcha #17 — BẮT BUỘC commit đủ)
`{TS}_{Name}.cs` + `.Designer.cs` + `ApplicationDbContextModelSnapshot.cs`. Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`.
```bash
dotnet ef migrations add <Name> --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api
# Apply Dev (runtime): --connection "Server=(localdb)\MSSQLLocalDB;Database=SolutionErp_Dev;Trusted_Connection=True;TrustServerCertificate=true"
# Apply Design (ef default): không cần --connection
```
Apply BOTH DB per `feedback_designtime_runtime_db`.
### Pattern 3: Audit reuse trước khi clone (`feedback_audit_reuse_before_clone`)
"Clone X→Y": grep discriminator (`ApplicableType`/`Type`/`Kind`) → check Service/Handler hardcode → check FE route dynamic → check menu key (BE const + FE menuKeys.ts thường thiếu) → default reuse 80%, chỉ thêm menu key + sample seed.
### Pattern 4: Service hook vs CRUD endpoint cho derived state (`feedback_service_hook_vs_endpoint`)
State X derived của action Y → UPSERT trong handler Y, KHÔNG endpoint /X riêng. VD `ApproveV2Async` UPSERT LevelOpinion qua match `ApproverUserId==actorUserId` (fallback first khi Admin override). 0 endpoint mới.
### Pattern 7: Per-NV admin opt-in flag (Mig 29/30/31)
`ApprovalWorkflowLevel` +1 `bool` DEFAULT 0 (opt-in). EF `HasDefaultValue(false)`. DTO extend. FE Designer checkbox inline. Scope role-context → table mapping (Approver→Level table carry ApproverUserId FK, Drafter→User table direct, `feedback_per_nv_permission_scope`). Reusable F5/F6.
### Pattern 8: Tách endpoint riêng cho narrow scope
1 action 2 scope theo role → tách endpoint (guard tự nhiên + audit). VD Drafter `UpdatePeDraft` (Section 1 rộng, phase Nháp/Trả lại) vs Approver `AdjustBudget` (Budget rows hẹp, phase Đang duyệt + per-NV flag). KHÔNG default expand Drafter scope cho Approver.
### Pattern 9: Defense-in-depth FE+BE guard pair
UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT inline handler) — tránh forge qua DevTools. Bất kỳ action sensitive (approve/reject/adjust).
### Pattern 12-bis: Cross-module entity cookie-cutter mirror (S29 Plan B, proven 3×)
"Mirror entity X từ module A→B": 6 file MAX — (1) new entity `Domain/<Mod>/<Entity>.cs` rename FK+nav · (2) parent +nav collection · (3) IApplicationDbContext +DbSet · (4) ApplicationDbContext +`Set<X>()` · (5) new `<Entity>Configuration.cs` (separate file mirror PE, NOT inline) · (6) `dotnet ef migrations add` 3-file. AuditableEntity inherit. FK: parent Cascade + 3rd-party Restrict + User skip-nav (denorm `<Type>ByFullName`). Apply 2 DB. ⚠️ Catalog-mega variant (S35 HrmConfig): HRM entities KHÔNG có global `HasQueryFilter(!IsDeleted)` (vs Master) → list query MUST `.Where(!IsDeleted)` thủ công (verify `Grep HasQueryFilter` Configurations FIRST). Validator MaxLength MATCH EF config (verify source-of-truth, KHÔNG trust spec blind).
### Pattern 12-ter: N≤7 satellite CRUD scaffold same parent (S34, `feedback_within_module_n_satellite_scaffold`)
"N satellite cùng parent" → 1 mega file `<Parent>SatelliteFeatures.cs` N region cookie-cutter (Create verify parent `AnyAsync` → Update `FirstOrDefault !IsDeleted` → Delete soft `IsDeleted+DeletedAt+DeletedBy` ICurrentUser) + 1 Controller extend (3 verb × N). Endpoint verify `parentId==cmd.ParentId` BadRequest mismatch. Per-action policy override class-level Read.
### Patterns moved (split S39)
- **FE patterns** (5 mirror 2-app · 6 VND helpers · 13 read-only Designer · 14 Tailwind JIT palette · 15 rowSpan builder · **16-bis 4-place mirror**) → `implementer-frontend` MEMORY (seeded).
- **Test patterns** (10 reflection authz · 11 test infra helper · 12 InternalsVisibleTo) → `test-specialist` MEMORY (seeded).
---
## ⚠️ Anti-patterns (DO NOT)
1. ❌ Skip MEMORY · 2. ❌ `--no-verify` · 3. ❌ `git add -A`/`git add .` (specific files) · 4. ❌ Touch outside spec scope · 5. ❌ Push remote autonomous (em main pushes) · 6. ❌ Modify `SolutionErp.slnx` autonomous · 7. ❌ Lower bar match em main (Smart Friend) · 8. ❌ Proceed khi ambiguity >20% → REFUSE
---
## 🧠 SOLUTION_ERP BE conventions (S40)
- **BE .NET 10:** PascalCase entities + DTO records + command names. CQRS+MediatR+FluentValidation+AutoMapper. Repository qua `IApplicationDbContext`. `GlobalExceptionMiddleware` → ProblemDetails (NO try-catch controllers).
- **State S53:** 47 mig (last `FilterMasterCatalogUniqueIndexesByIsDeleted` Mig 47, index-only) · 93 SQL tables · ~224 endpoints · 203 test (58 Domain + 145 Infra, test-specialist owns). Phase 9 UAT skip per chunk (`feedback_uat_skip_verify`).
- **Build:** `dotnet build SolutionErp.slnx` clean 0 err. Commit `[CLAUDE] <scope>: <msg>` + Co-Authored-By Claude Opus 4.8 (1M context).
- **Pin (KHÔNG `*`/latest):** MediatR `12.4.1` (14 fail DI) · Swashbuckle `6.9.0` · Node CI `20.x` · LibreOffice `25.8.6` · @microsoft/signalr `8.0.7`.
---
## 📅 Recent activity (FIFO — older → archive/git)
- **2026-06-25 (S88 PE stability cleanup — FROZEN-module mechanical relocation + 1 stale-comment fix BE — NO migration, 3 edit/2 file, em-main spec deterministic 100% file/line/boundary given → ACCEPT Case 1):** Pure mechanical move ZERO behavior-change. **T1 misplaced-command relocate:** `SetPeCcmBudgetPeriodCommand`+Validator+Handler (BUDGET setter writes `pe.CcmBudgetPeriodAmount` Section B "NS kỳ này" CCM, Mig 59) sống NHẦM trong suggested-PRICE file → move về budget file. (a) REMOVE `PeSuggestedPriceFeatures.cs` lines 144-199 (blank + `===== CCM NS kỳ này =====` header → `SetPeCcmBudgetPeriodCommandHandler` closing `}`); file end clean tại line 143 (`UpdatePeSuggestedPriceCcmCommandHandler` `}`), suggested-price PRO/CCM trên giữ nguyên. (b) APPEND `PeWorkItemBudgetFeatures.cs` END sau line 200 (`UpdatePeBudgetCcmCommandHandler` `}`) → cạnh budget setters `UpdatePeBudgetPro/Ccm`. Code BYTE-IDENTICAL (logic/authz Admin\|CostControl fail-closed/validation GTE(0).When(HasValue)/absolute-set null=clear/changelog UNCHANGED); CHỈ prepend 1 comment line `// [S88] Chuyển từ PeSuggestedPriceFeatures.cs về đây (đúng nhóm budget setter).`. Usings 8-header IDENTICAL 2 file → KHÔNG add duplicate. Namespace `SolutionErp.Application.PurchaseEvaluations` SAME → MediatR type-resolution transparent, controller `:125` KHÔNG đụng. **T2 stale-comment fix:** `Dtos/PurchaseEvaluationDtos.cs` ~250-254 comment trên `decimal WinnerQuoteTotal` ghi SUM "của SelectedSupplierId" (OLD single-winner) → verified actual compute `PurchaseEvaluationFeatures.cs:1093-1099` = `Suppliers.Where(s=>s.IsWinner)` (multi, Mig 58) → sửa CHỈ comment-text "MỌI đơn vị IsWinner (multi-winner Mig 58)". Field/code UNCHANGED. **LESSON:** frozen-relocation = read BOTH file FIRST exact boundary + verify usings-superset target + verify stale-comment claim vs real compute-site TRƯỚC sửa (KHÔNG trust comment blind, mirror S35 EF-source-of-truth). Build `SolutionErp.Application.csproj -v q` **0 warn 0 err** (4s). KHÔNG test (test-specialist)/FE/mig/commit. Tag `[s88, frozen-relocation, byte-identical-move, mediatr-type-resolve-transparent, stale-comment-verify-compute-site, no-behavior-change]`.
- **2026-06-24 (PE Section B 3-cột THỰC HIỆN — CCM "NS kỳ này" cột riêng BE — Mig 59 `AddCcmBudgetPeriodToPurchaseEvaluation` 3-file, 5 edit/0 new file, em-main CHỐT spec+API-contract 100% → ACCEPT Case 1):** Section B tách 3 cột Dự án|PRO|CCM giống Section A. PRO=`BudgetPeriodAmount` sẵn có; CCM nhập cột riêng độc-lập. COOKIE-CUTTER mirror S77 suggested-price-Ccm + S65b HoSoLink one-column. (1) `PurchaseEvaluation.cs` +`decimal? CcmBudgetPeriodAmount` sau ExpectedRemainingAmount. (2) `PurchaseEvaluationConfiguration.cs` +`HasColumnType("decimal(18,2)")` sau ExpectedRemaining (spec asked literal decimal(18,2); siblings dùng HasPrecision(18,2) NHƯNG emit identical column — tôi follow spec verbatim HasColumnType). (3) Mig: Up=1 AddColumn decimal(18,2) nullable NO table/index, Down=1 DropColumn, snapshot verified — clean mirror Mig 52. (4) DTO `PurchaseEvaluationDetailBundleDto` +`decimal? CcmBudgetPeriodAmount` sau ExpectedRemainingAmount TRƯỚC BudgetSummary + projection PEFeatures.cs:1110 positional-insert đúng order (positional record → projection order BẮT BUỘC khít). (5) Setter `SetPeCcmBudgetPeriodCommand(Guid Id, decimal? CcmBudgetPeriodAmount):IRequest` append `PeSuggestedPriceFeatures.cs` (same concern PE role-gate setters) + Validator `GreaterThanOrEqualTo(0).When(HasValue)` (budget=0 hợp lệ) + Handler fail-closed Forbidden Admin|CostControl TRƯỚC side-effect (S56 #5) + absolute-set null=clear (gotcha #73) + changelog Header (sibling-consistency; spec min=SaveChanges). (6) Controller `[HttpPatch("{id:guid}/budget/ccm-period")]` + `CcmBudgetPeriodBody` (spec EXPLICIT PATCH dù siblings PUT — follow spec). **Call-site verify:** `new PurchaseEvaluationDetailBundleDto(` repo-wide → EXACTLY 1 (PEFeatures:1100, đã sửa) → positional-safe. Submit-guard `BudgetPeriodAmount` PRO KHÔNG đụng. Build SolutionErp.slnx (6 proj incl 2 test, gotcha #65) **0 warn 0 err**. KHÔNG apply DB/FE/test/commit. Route FE/test mirror: DTO field `ccmBudgetPeriodAmount` camelCase + PATCH `/api/purchase-evaluations/{id}/budget/ccm-period` body `{ ccmBudgetPeriodAmount }`. Tag `[pe-section-b-3col, ccm-budget-period, mig59, one-column-no-table, absolute-set-fail-closed, positional-dto-order, patch-per-spec]`.
- **2026-06-19 (PE chuông báo approver BE — NO migration, 1 edit/1 file, em-main spec deterministic 100% → ACCEPT Case 1):** Tra Sol (Zalo) "không thấy chuông — việc có hồ sơ cần duyệt": approver Cấp hiện tại KHÔNG nhận notify khi phiếu ENTER/ADVANCE tới ChoDuyet (chỉ drafter báo terminal). FIX = +1 block trong `LogTransitionAsync` (PurchaseEvaluationWorkflowService.cs ~line 1058) NGAY SAU drafter-notify, KHÔNG endpoint mới (Pattern 4 — notify = side-effect của Submit/Approve-advance, cả 2 đã gọi LogTransitionAsync + end ChoDuyet). **Resolution mirror EXACT canonical** `EnsureActorInLevel` (line ~301): `db.ApprovalWorkflows.AsNoTracking().Include(Steps).ThenInclude(Levels)``stepsOrdered[CurrentWorkflowStepIndex]` (bounds-check) → `.Levels.Where(Order==CurrentApprovalLevelOrder && ApproverUserId!=Guid.Empty && !=actorUserId).Select(ApproverUserId).Distinct()``NotifyManyAsync(ids, Generic, "Phiếu cần bạn duyệt: {MaPhieu??TenGoiThau}", "Có phiếu Duyệt NCC đang chờ bạn duyệt.", /purchase-evaluations/{Id}, refId:Id, ct)`. **Recon:** (1) `ApprovalWorkflowLevel.ApproverUserId` = non-null `Guid` (NOT Guid?) → "exclude null" = `!=Guid.Empty` defensive (OR-of-N rows mỗi 1 NV). (2) `NotifyManyAsync` sig verified = `(IEnumerable<Guid>, NotificationType, title, string? desc, string? href, Guid? refId, CT)` khớp urgent-feature ref. (3) actor-exclude qua `!= actorUserId` (Guid != Guid? lift OK). **Guard V2-only:** `ApprovalWorkflowId is Guid && CurrentWorkflowStepIndex is int && CurrentApprovalLevelOrder is int` → V1/no-workflow skip. **Best-effort try/catch** nuốt lỗi (phiếu đã SaveChanges; notify fail KHÔNG rollback). Block đọc pointer SAU set → Submit (idx0/lvl1) + Approve-advance (pointer advance rồi LogTransition) đều fire đúng. Build SolutionErp.slnx (2 test project, gotcha #65) **0 warn 0 err**. KHÔNG test/FE/mig/commit. Tag `[pe-approver-bell, notify-many, log-transition-hook, no-mig, v2-only, best-effort-trycatch, pattern4-side-effect]`.
- **2026-06-19 (S77 PE +2 ghi-chú giá đề xuất BE — Mig 57 `AddPeSuggestedPriceNotes` 3-file, 6 edit/0 new file, COOKIE-CUTTER S73 suggested-price + S74 CcmNote, em-main CONTRACT-locked names 100% → ACCEPT Case 1):** PRO dải Min/Max + CCM 1 giá (S73) +ô GIẢI THÍCH vì-sao min vs max (anh Kiệt FDC + Tra Sol). 2 cột nullable, no table/index/backfill. (1) `PurchaseEvaluation.cs` +`string? ProSuggestedPriceNote`/`CcmSuggestedPriceNote` ngay sau CcmSuggestedPrice. (2) `PurchaseEvaluationConfiguration.cs` +`HasMaxLength(1000)` ×2 sau ApprovedPriceSource (KHÔNG index — free-text). (3) Mig: Up=2 AddColumn nvarchar(1000) nullable, Down=2 DropColumn, snapshot ×2 verified — clean mirror Mig 55. (4a) `UpdatePeSuggestedPriceProCommand` +trailing `string? Note=null` + validator `MaximumLength(1000)` MATCH-EF (S35) + handler **absolute-set** `pe.ProSuggestedPriceNote=request.Note` (null=clear, gia-đình text-field) + changelog part khi đổi. (4b) `UpdatePeSuggestedPriceCcmCommand` mirror + changelog suffix "(kèm ghi chú)". Role-gate KHÔNG đụng (Note gated y giá: Forbidden fail-closed TRƯỚC side-effect đã có). (5) Controller `SuggestedPriceProBody`/`CcmBody` +`string? Note` + pass `body.Note` vào cmd (4th/3rd positional). (6) `PurchaseEvaluationDetailBundleDto` +`ProSuggestedPriceNote`/`CcmSuggestedPriceNote` sau CcmSuggestedPrice + projection PEFeatures.cs positional-insert ĐÚNG order (DTO positional → order khít projection BẮT BUỘC). **Call-site grep verify (S65b lesson):** `new UpdatePeSuggestedPrice(Pro|Ccm)Command\(` repo-wide → 2 controller (đã sửa) + 18 test named-arg/positional-dừng-trước-Note → trailing-optional fully backward-compat, 0 test edit. FE 0 ref (đúng — implementer-frontend next). Build SolutionErp.slnx (2 test project, gotcha #65) **0 warn 0 err**. KHÔNG apply DB/FE/test/commit. Route: `note` camelCase qua PUT `/suggested-price/{pro,ccm}` body + GET detail bundle. Tag `[s77, pe-suggested-price-note, mig57, two-column-no-table, absolute-set, trailing-optional-callsite-safe, positional-dto-order]`.
- **2026-06-17 (S? Off_Dashboard menu leaf BE — NO migration, 3 edit/2 file, idempotent seed mirror S53/S54-TaskD, em-main spec deterministic 100% → ACCEPT Case 1):** +1 menu key `Off_Dashboard` ("Bảng điều khiển Văn phòng số"), pattern = S53 Off_AttendanceReport EXACT. 3 insert: (1) `MenuKeys.cs` const `OffDashboard = "Off_Dashboard"` ngay sau root `Off:99` · (2) `MenuKeys.cs` All[] line `Off, OffDanhBa``Off, OffDashboard, OffDanhBa` · (3) `DbInitializer.cs` SeedMenuTreeAsync tuple `(OffDashboard, "Bảng điều khiển Văn phòng số", Off, **0**, "LayoutDashboard")` trước OffDanhBa=1 (Order 0 = landing đầu nhóm, KHÔNG renumber children 1-7 hiện có). **KEY recon — Off_* leaves ARE IN All[] (NOT factory-excluded):** task hint "leaf may be excluded+granted-via-factory" KHÔNG áp Off (chỉ Pe_* leaf sinh động). Off_AttendanceReport :160 in All → tôi follow SAME = +All. Admin auto 2-point verified: `SeedAdminPermissionsAsync:2001` + `Program.cs:78` both iterate `MenuKeys.All` → +All = 4 policy {Read/Create/Update/Delete} + Admin Permission row auto, NO manual grant. **Revoke verified KHÔNG sửa:** `RevokeTemporarilyHiddenModulesAsync:2170` `p.MenuKey.StartsWith("Off")` → Off_Dashboard tự nằm trong scope ẩn-non-Admin. `InReviewScope:2070` chỉ match Catalog*/Master-keys/Pe_* → Off_Dashboard KHÔNG re-grant non-admin. Idempotent: upsert loop :1909 `existingItems.TryGetValue(key)` miss→Add / hit→chỉ reconcile Order (prod DB cũ nhận leaf next boot, re-run no-op). Build SolutionErp.slnx (gồm 2 test project, gotcha #65) **0 warn 0 err**. KHÔNG touch FE (menuKeys.ts/Layout=implementer-frontend)/test/mig/commit. Tag `[s?, off-dashboard, menu-leaf, no-mig, admin-perm-via-all, order-0-landing]`.
- **2026-06-16 (S65b PE +HoSoLink BE — Mig 51 `AddHoSoLinkToPurchaseEvaluation` 3-file, 6 file edit + 0 new file, em-main CHỐT spec 100% → ACCEPT Case 1):** Phiếu PE +1 cột `HoSoLink` = 1 hyperlink tới thư mục hồ sơ NAS (anh Kiệt paste link, FE render bấm-mở). KHÔNG entity con/bảng mới — 1 cột nullable. (1) `PurchaseEvaluation.cs` +`string? HoSoLink` sau MoTa. (2) `PurchaseEvaluationConfiguration.cs` +`HasMaxLength(1000)` (KHÔNG index — hyperlink free-text, không filter/join). (3) Mig via `dotnet ef migrations add` → Up=1 `AddColumn<string> nvarchar(1000) maxLength:1000 nullable` NO table NO index, Down=1 DropColumn; snapshot HoSoLink nvarchar(1000) verified. (4a) `CreatePurchaseEvaluationCommand` +trailing `string? HoSoLink = null` (sau WorkItemId — optional-param-after-required rule) + validator `MaximumLength(1000)` MATCH EF (S35 lesson) + handler `HoSoLink = request.HoSoLink`. (4b) `UpdatePurchaseEvaluationDraftCommand` +trailing `string? HoSoLink = null` + validator 1000 + handler **absolute-set** `entity.HoSoLink = request.HoSoLink` (Section-1 text-field family MoTa/DiaDiem pattern → null=clear, KHÔNG null-safe-keep như WorkItemId picker; deliberate: hyperlink user cần clear được). (5) `PurchaseEvaluationDetailBundleDto` +`string? HoSoLink` sau MoTa + projection `e.HoSoLink` positional-insert đúng vị trí. **RANG-CUNG grep verify (bài học CreateDepartmentCommand CS7036):** `Grep CreatePurchaseEvaluationCommand|UpdatePurchaseEvaluationDraftCommand` repo-wide gồm tests → 0 manual `new ...Command(...)` call-site (controller bind `[FromBody]` model-binding, KHÔNG manual ctor; tests dùng NAMED-ARG dừng ở WorkItemId) → trailing-optional-default fully backward-compat, KHÔNG sửa call-site nào. List DTO KHÔNG đụng (spec chỉ Detail/Get). `.slnx` KHÔNG update (chỉ 2 mig-file trong project có sẵn). KHÔNG apply DB/FE/test/commit. Build SolutionErp.slnx (gồm 2 test project) **0 warn 0 err** (DocxRenderer warn cleared). Route: `hoSoLink` camelCase qua POST/PUT body + GET detail. Tag `[s65b, pe-hosolink, mig51, one-column-no-table, trailing-optional-param, named-arg-callsite-safe]`.
- **2026-06-16 (S65 Department hierarchy BE — Mig `AddDepartmentParentId` 3-file, 4 file edit + 0 new file, em-main schema CHỐT → ACCEPT Case 1):** Cây tổ chức nền trang Hồ sơ Nhân sự. (1) `Department.cs` +`Guid? ParentId` loose-Guid KHÔNG physical FK (convention PE.ProjectId/WorkItemId/SelectedSupplierId). (2) `DepartmentConfiguration.cs` +`HasIndex(x=>x.ParentId)` only — **KHÔNG `HasOne` self-FK** (em main chốt loose). (3) `DepartmentFeatures.cs` +`GetDepartmentTreeQuery`+`DepartmentTreeNodeDto`+Handler (append existing file, NO new .cs → `.slnx` KHÔNG cần update; slnx lists projects-not-files). (4) `DepartmentsController.cs` +`[HttpGet("tree")]`. **KEY recon finding (spec asked verify):** `EmployeeProfile` has **NO `DepartmentId`** — links via `UserId`; org-chart dept field nằm trên **`User.DepartmentId`** (Mig 11) → GROUP BY `db.Users.Where(DepartmentId!=null && IsActive).GroupBy(DepartmentId).ToDictionary` = DirectEmployeeCount (recon NOT schema-decision). Tree ráp in-mem: roots=ParentId-null OR orphan-parent (safe-root); TotalEmployeeCount=Direct+Σ(Children.Total) đệ quy rollup via `record with{}`; **cycle-guard HashSet<Guid> visited** (node đã thăm→return null cắt vòng); Children sort `OrderBy(Code, StringComparer.Ordinal)` ổn định. **Authz copy-từ-đâu:** `[HttpGet]` List = CHỈ class-level `[Authorize]` (no per-action attr) → `/tree` cũng vậy (verified read controller). Mig diff CLEAN: AddColumn ParentId nullable + CreateIndex IX_Departments_ParentId, NO new table, Down DropIndex→DropColumn (SQL 5074 order). KHÔNG apply (prod/CI). Build SolutionErp.slnx 0/0. KHÔNG touch FE/test/seed-parent/commit (em main). Route `GET /api/departments/tree``List<DepartmentTreeNodeDto>`. Tag `[s65, dept-hierarchy, loose-guid-no-fk, in-mem-tree-rollup, cycle-guard, user-departmentid-source]`.
- **2026-06-11 (S57bis PE WorkItemId BE slice — PARTIAL, on-behalf em main ghi hộ, H2-proposed):** Return-truncated #53 TRƯỚC Mig 49 + projection-3 → em main solo hoàn tất (fix CS7036 + CS8019, Mig 49 `AddWorkItemToPurchaseEvaluation` 3-file, projection ListItemDto ×3 LEFT-join WorkItems, UpdateDraft null-safe `if (request.WorkItemId is not null)` chống null-hóa bug-class S42). LEARNED: FK-guard loose-Guid `AnyAsync(w.Id==x && w.IsActive)`→Conflict (mirror S43); validator `NotEmpty` create-only, DB nullable backward-compat 4 phiếu cũ; `NotEmpty()` trên `Guid?` KHÔNG chặn `Guid.Empty` → handler guard bắt (defense-in-depth). SURPRISE: truncate 2 session liên tiếp (S55, S57bis) ở slice lớn cross-layer → cắt stage nhỏ hơn (entity+config / mig / projection tách spawn). Tag `[s57bis, truncated-53, on-behalf]`.
- **2026-06-10 (S57-resume spawn-test H4.8 — Harness-4 two-tier):** Mình bị DEMOTE pin `model: claude-opus-4-8` (deterministic-scaffold class, double-gate reviewer+test+cicd sau lưng). Spawn-test echo model NGAY sau edit → self-report `claude-fable-5[1m]` = SE env (CCD harness) KHÔNG fresh-read frontmatter → pin ăn SAU restart CLI. Post-restart mình chạy Opus 4.8 (effort Max giữ env-wide); task hệ-trọng giao mình qua hmw có thể override `tier:'fable'`. Tag [h4-demote, spawn-test, pending-restart].
_(S56 GOLIVE-HARDEN 3 BE fix — ExecuteUpdate-atomic LeaveBalance + fail-closed AssignItTicket authz + DocxRenderer CS8602 → FIFO'd S77; verbatim git/archive. em-main post-review bumped tx → `IsolationLevel.Serializable` per database-agent. Test 228 green.)_
---
## 🔄 Curate trigger
- >~30KB → archive recent → L2 `archive/<period>.md`. Stale >3mo → remove.
- **Last curate: 2026-06-17 S70 Harness-9 (em-main + Stage-B workflow)** (33.2→17.4KB): L2 dark-matter recovery — 14 Recent-activity entries (S55→S35) → NEW `archive/2026-06.md` + `_INDEX.md` (substring sha-keyed) + `2026-0{5,6}.gist.md` (distill-gen:1). 0-byte-loss md5 byte-exact (Stage C audit CONCERN → read-side-gap MEMORY-L5→_INDEX fixed). _(cosmetic: 2 curate-meta lines carry `S?` worker-label.)_ Prev: S40 (30.9→~18KB dedup-split BE/FE/test) · S34 q3 · S22 q1.