Files
solution-erp/.claude/agent-memory/implementer-backend/MEMORY.md
pqhuy1987 f36aab8934
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m52s
[CLAUDE] Docs: adopt Harness-9 — L2 archive dark-matter recovery (4 sub) + adap 2-workflow mandate (S70)
3-stage Workflow run-id evidence: investigate wf_be952f3c-97f / implement wf_a58e0d15-beb / audit wf_9520d8cd-4fe.

PART 1 (L2 recovery): 4 over-cap sub (cicd-monitor/investigator-codebase/reviewer/implementer-backend)
curated L1->L2 byte-exact + archive/_INDEX.md (substring sha-keyed pointers, no line-hints)
+ <period>.gist.md (4-field distill, distill-gen:1, verbatim frozen). All 4 MEMORY.md now < 25KB
auto-inject cap (closes P1 curate-debt). ~240KB archive no longer RAG-dark. 0-byte-loss git+sha
verified (Stage C audit + em-main self-gate on 2 reviewer StructuredOutput no-returns). Read-side
gap fixed (MEMORY.md L5 header -> _INDEX). + memory-budget.json (seed-by-measure) +
scripts/measure-agent-memory.ps1 + .ragignore guard.

PART 2/3 (process mandate): every adap = 2 separate workflows (implement + review) + report with
run-id; short-but-needs-confirm still requires review. Codified in .claude/commands/adap-apply.md
+ agents/README.md (Upgrade S70) + session-start.md (§2.1.2 budget-audit, pending-restart).

adap-report + email-back to AI_INFRA (body-hash 7c07b716e775).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 23:52:51 +07:00

17 KiB
Raw Blame History

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/.

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).

"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-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, OffDanhBaOff, 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 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/treeList<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 (NO mig, 3 file edit, em-main spec deterministic 100% → ACCEPT Case 1): #3 LeaveBalance lost-update LeaveOtApprovalFeatures.cs terminal DaDuyet branch → atomic-executeupdate-tx (spec chosen, KHÔNG RowVersion/Mig). Replaced in-mem bal.UsedDays += NumDays với: set p.Status/Updated* → (DbContext)db cast + BeginTransactionAsync(ct) (plain, NO IsolationLevel — READ COMMITTED đủ vì increment atomic) → STEP1 ensure-row (FirstOrDefault, auto-create UsedDays=0 via tracker) + SaveChanges (opinion+status+insert trong tx) → STEP2 db.LeaveBalances.Where(...).ExecuteUpdateAsync(s=>s.SetProperty(b=>b.UsedDays, b=>b.UsedDays+p.NumDays), ct) server-side row-lock race-free → tx.CommitAsync + return; (skip trailing shared SaveChanges). STALE-TRACKED caveat (load-bearing): ExecuteUpdate bypass tracker → tracked bal giữ pre-increment value; SAFE vì không đọc lại + handler return ngay; KHÔNG thêm bal.UsedDays += (double-count). using System.Data + EF Core đã import. #5 AssignItTicketHandler existence-oracle WorkflowAppsFeatures.cs:493 → moved Admin-OR-dept-IT Forbidden guard (itDeptId+isAdmin+myDeptId resolve) TRƯỚC ticket NotFound lookup → fail-closed (non-IT nhận Forbidden cho MỌI ticketId). assignee-must-be-IT Conflict + reassign giữ nguyên. #6 DocxRenderer.cs:30,40 CS8602 → hoist mainPart = doc.MainDocumentPart ?? throw InvalidOperationException + document = mainPart.Document ?? throw (Document cũng nullable — KEY: 1st hoist chỉ fix part, vẫn còn 1 warn ở .Document.Body); deref qua local non-null. Build SolutionErp.slnx 0 err 0 warn (DocxRenderer warn CLEARED — thực tế 1 warn không phải 2 như MEMORY ghi). Test 58 Domain PASS + 154/158 Infra: 4 FAIL LeaveBalanceTests (Approve_LastLevel_DeductsLeave.../AccumulatesExisting.../OverEntitled.../MultiLevel_NoDeductAtIntermediate) = EXPECTED #3 stale-tracked (re-query trả tracked instance pre-increment, DB row đúng) → tests_to_update cho test-specialist (add AsNoTracking/ChangeTracker.Clear). ItTicket authz tests #5 PASS (Case5 đã expect Forbidden, NotFound case dùng Admin caller). KHÔNG touch tests/FE/commit. #4 (Travel/Vehicle smoke test) = test-specialist next stage. Tag [s56, golive-harden, executeupdate-atomic, fail-closed-authz, cs8602, no-mig]. ↳ [em main post-review S56] Tx bumped → IsolationLevel.Serializable (shipped code LeaveOtApprovalFeatures.cs:369) per database-agent review — convention-align (codegen/Proposal/TravelVehicle) + serialize auto-create-row race. '(plain, NO IsolationLevel — READ COMMITTED đủ)' ở entry = pre-review reasoning, superseded. Test 228 green.


🔄 Curate trigger

  • ~30KB → archive recent → L2 archive/<period>.md. Stale >3mo → remove.

  • Last curate: 2026-05-29 S40 em main proxy (30.9→~18KB): dedup split — removed FE patterns (5/6/13/14/15/16-bis → implementer-frontend) + test patterns (10/11/12 → test-specialist), condensed Pattern 12-bis/12-ter, refreshed stale (104/111→130 test, Opus 4.7→4.8 model). BE patterns 1-4/7-9/12-bis/12-ter foundation preserved. Prev: S34 q3 · S32 q2 · S22 q1.