[CLAUDE] Docs: S153 closeout — adopt TRỌN Ctx soft-memory + OWNER-VETO 3-vai + RENAME hub-form (squash 22 wal:)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m39s

Pipeline anh lệnh: /check-email + /adap-apply -> /fable-real invest (spec 9-task/24-checklist)
-> hmw Opus-5-MAX 2 đợt + lead-solo T6-T8 -> checklist verify.

Thư ① 9c909007 (model-default Opus 5 MAX): TRỌN — filing 2-tuyến + probe-sau-restart
2-nguồn claude-opus-5[1m] + thư hub d432f5eab5cd + caveat (e) đóng.

Thư ② 0df10df4 (pause-tiep Ctx soft-memory, sàn 7-khoản): EXECUTED
- máy mind-check: scripts/session_ctx.py +542 dòng (7 phép + 2 phụ, 4 mức, draft/closed/--root/--session PIN, exit 0/1/3)
- template .claude/templates/session-mind-template.md + wire 4 lệnh (pause C-bis+2bis+§1-carve-out, tiep §2.6+§3-ter, session-start Phase 3.5, session-end §L.b(l)+6.3-bis)
- memory-budget: mind_ctx_kb=32 + tiep_reload + ĐÓNG ghost-wire session_ctx_kb (H18 class-2 sống S146->S153)
- fault-inject 18/18 + 2 fix hậu-T4 (FI 6/6): GAP-#9 closed-mode E-0-con-trỏ CỜ->TRƯỢT (chặn gradient
  thưởng xoá-con-trỏ-trung-thực, họ bẫy F-1 của chính thư hub) · secrets-sweep +--root parity
- số ĐO THẬT pause +117ms thay ước lệch 2.2-5.4x

OWNER 2 quyết trong-phiên:
- (38) VETO: 1-vai -> hub-form 3-vai (ctx-curator @pause + carve-out pause §1 · ctx-verifier @tiep · ctx-audit @2-bookend)
- RENAME theo chuẩn hub/sisters để audit chéo: ctx-pause-check->ctx-curator, ctx-reload->ctx-verifier
  (đo đĩa 3 nhà; hub≠sister => SE theo HUB-form, tiền lệ S149). Roster 20->23, VALID_ROLES 23.
  Gap còn lại: ring5-audit (V5 truth-floor) hub CÓ, SE+VIPIX chưa -> slot (41).

Bookend 2 đầu (12 spawn) + trio auto 1 vòng:
- @open: H1 7f · H2 4f · H24 5+3 FLAG · ring1 28Đ/4T · ring2 8/8Đ + M-1 MISS thật · trio eval-MIXED-14/refine-3A-7B-4N/audit-25Đ-3T (re-chạy 7 instrument, 24 số byte-exact)
- @close: H1 6f · H2 8f (baseline S154 = 16/10, tự sửa bản ~23/11) · H24 4+2 FLAG · ring1 47Đ/5T/1KC/53 · ring2 6/6Đ + 21 phép phụ
- lead xử 11 mục: 9 FLAG H24 + 3 RESIDUAL ring2 + F-r2 (plugin 3 hard-site) + M-1 cả 2 persona + M9 14 sổ diary + F-3/F-5/F-6 + nit Sàn-5 + BÁC F-4 (fan-out không vào _index)

#53 x14-cận-dưới, 0 mất dữ liệu (WRITE-lane derive-from-body · Agent-resume · SendMessage-resume x6).
Datum: trio return-only hết miễn nhiễm (3/3 CLEAN -> 1/3). 4-A suýt xảy ra thật (bản committed của 2 file H24 là bản cắt 24%/48%).
completeness-gate: 5 vòng đủ · phép ĐẠT 4 / TRƯỢT 0 / vacuous 0.
State: Mig 67 · 89 bảng · 532 test · gotcha 83 · Sub-agents 23 · counter 27 · bundle FROZEN · governance-only 0 prod-code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-26 16:10:54 +07:00
parent c2d01f0dce
commit 11db5efdf8
88 changed files with 5750 additions and 64 deletions

View File

@ -6,6 +6,15 @@
- activity-s51-s52.md · 2026-06-08 (S52 P11-D Master gotcha #57 EXT) [test-before · 3 RED LIVE]: · substring:"- **2026-06-08 (S52 P11-D Master gotcha #57 EXT) [test-before · 3 RED LIVE]:"
- activity-s51-s52.md · 2026-06-08 (S52 P11-E + P11-F WorkflowApps/Attendance test-after · substring:"- **2026-06-08 (S52 P11-E + P11-F WorkflowApps/Attendance test-after"
- activity-s51-s52.md · 2026-06-08 (S51 P11-C HMW Wave2 filtered-unique gotcha #57 · substring:"- **2026-06-08 (S51 P11-C HMW Wave2 filtered-unique gotcha #57"
- baseline-history.md · S118b — ReportsController company-wide-report authz REGRESSION (test-after SECURITY), +4 -> 509 · substring:"S118b — ReportsController company-wide-report authz REGRESSION (test-a"
- baseline-history.md · S118 — Procurement master-access seeder SCOPE-ISOLATION regression (test-after SECURITY), +4 -> 505 · substring:"S118 — Procurement master-access seeder SCOPE-ISOLATION regression (te"
- baseline-history.md · S117 — PE ProMinMax duyet-theo-KHOANG range-price (test-before-merge FINANCIAL), +5 -> 500 · substring:"S117 — PE ProMinMax duyet-theo-KHOANG range-price (test-before-merge F"
- baseline-history.md · S116 — PE bao gia SO AM regression-lock (test-before guard R2-revised), +9 -> 495 · substring:"S116 — PE bao gia SO AM regression-lock (test-before guard R2-revised)"
- baseline-history.md · S113 — Supplier import v2 dedup + publish-guard (test-after critical-algo + security), +19 -> 477 · substring:"S113 — Supplier import v2 dedup + publish-guard (test-after critical-a"
- baseline-history.md · D4 — PE sign-off A1 + EDGE-5 + opinion-history (test-before-merge critical-algo), +8 -> 458 · substring:"D4 — PE sign-off A1 + EDGE-5 + opinion-history (test-before-merge crit"
- baseline-history.md · D3 — Supplier Excel-import BE Phase B (test-before-merge case-collation bug-class), +10 -> 450 · substring:"D3 — Supplier Excel-import BE Phase B (test-before-merge case-collatio"
- baseline-history.md · S97 — PE EndsBeforeCeo CONFIG->RUNTIME (spec-change test), +0 (extend in-place) · substring:"S97 — PE EndsBeforeCeo CONFIG->RUNTIME (spec-change test), +0 (extend "
- baseline-history.md · S92 — admin-only-modules revoke (test-before-merge SECURITY), +3 -> 434 · substring:"S92 — admin-only-modules revoke (test-before-merge SECURITY), +3 -> 43"
- baseline-history.md · Count progression (one-line chain) · substring:"Count progression (one-line chain)"
- baseline-history.md · D2 — PE Mig 58 multi-winner (test-after + EF translation-smoke), +6 → 419 · substring:"D2 — PE Mig 58 multi-winner (test-after + EF translation-smoke), +6 →"
- baseline-history.md · S87 — PE Mig 59 CCM-budget-period setter (test-after authz), +7 · substring:"S87 — PE Mig 59 CCM-budget-period setter (test-after authz), +7"

View File

@ -12,6 +12,33 @@ metadata:
## Count progression (one-line chain)
254 (S60) → 263 (S61 +22 PeWorkItemBudget, 14 BudgetPolicy Domain drop) → 286 (S67 +23 HRM) → 292 (S69 +6 Office) → 306 (S69b +14 PE) → 334 (S72 +28 PE Mig54) → 339 (S74 +5 CcmNote) → 344 (S76 +5 PRO-split) → 351 (S77 +7 suggested-price NOTE) → 354 (S77b symmetric, superseded) → 366 (S78-S84, MEMORY chưa ghi từng delta) → 371 (S85 +5 urgent drafter-unset) → 374 (S85b +3 UpdateDraft WF-preserve) → 377-395 (S86-S86d) → 402 (S87 +7 CCM-budget-period) → **419 (D2 +6 PE Mig58 multi-winner + translation-smoke)**.
## S118b — ReportsController company-wide-report authz REGRESSION (test-after SECURITY), +4 -> 509
**S118b (2026-07-14) ReportsController company-wide-report authz REGRESSION (test-after SECURITY — reviewer catch) — 505→509 PASS (+4 Infra), no-red, NO prod bug:** EXTEND `Api/AuthorizePolicyRegressionTests.cs` (Pattern 10 reflection, +4 [Fact] → file 15→19). Fix S118: 2 endpoint company-wide `Dashboard`+`ExportContracts` (lộ tài chính HĐ toàn cty: tổng giá trị, top NCC/dự án, xu-hướng-12-tháng, export-Excel) NAY gate `[Authorize(Policy="Reports.Read")]` (chỉ Admin, khớp S92); `MyDashboard` CỐ Ý giữ mở (chỉ class-level `[Authorize]` trần, scope currentUser). Trước: cả controller class-level `[Authorize]` trần → mọi user đăng nhập gọi được = lộ tài chính. **T1** class-level `[Authorize]` Policy=null+Roles=null (bare — nếu hardcode Policy sẽ khóa my-dashboard). **T2/T3** Dashboard+ExportContracts action-Policy==`Reports.Read` (gỡ→NotBeNull-fail = tái-lộ toàn cty). **T4** MyDashboard `(attr?.Policy).Should().BeNull()` — cho phép bare `[Authorize]` vô-hại NHƯNG chặn mọi Policy (thêm policy→khóa dashboard cá-nhân role không-Admin). csproj ĐÃ ref Api (line 25, từ S45) — KHÔNG vướng reference. KHÔNG touch prod.
## S118 — Procurement master-access seeder SCOPE-ISOLATION regression (test-after SECURITY), +4 -> 505
**S118 (2026-07-14) Procurement master-access seeder SCOPE-ISOLATION regression (test-after SECURITY — anh Kiệt FDC) — 501→505 PASS (+4 Infra), no-red, NO prod bug:** new `ProcurementMasterAccessSeedTests.cs` (4 [Fact]) guard seeder MỚI `SeedProcurementMasterAccessAsync` (private static, DbInitializer) — NGOẠI-LỆ role Procurement mở lại Danh mục SAU S92 revoke ("chỉ Admin thấy"). Chain thật: Revoke→SeedProcurement. **T1 upgrade-path** Procurement sau revoke→seed đúng ma trận (Suppliers R+C+U D=false + 8 read-only chỉ R). **T2 ⭐SCOPE-ISOLATION (core)** DeptManager đứng RIÊNG (KHÔNG kèm Procurement): pre-grant→revoke→seed → 9 key VẪN all-false (row tồn tại giữ vết) = grant KHÔNG rò role khác; +CONTRAST assert Procurement THẬT được nâng (chống vacuous-pass). **T3 insert-path** DB mới no-revoke → INSERT 9 row, Delete=false + count==9. **T4 idempotent** seed ×2 → bất biến + count==9 no-dup + role khác vẫn ẩn. Reflection harness MIRROR `AdminOnlyModulesRevokeTests` (NonPublic|Static, sig (ApplicationDbContext,RoleManager<Role>,ILogger), FK seed MenuItem→Role→Permission). No user cần (perm role-level). **Reflection KHÔNG vướng — KHÔNG cần đổi visibility.** KHÔNG touch prod.
## S117 — PE ProMinMax duyet-theo-KHOANG range-price (test-before-merge FINANCIAL), +5 -> 500
**S117 (2026-07-14) PE "duyệt theo KHOẢNG" ProMinMax range-price TEST-BEFORE-MERGE (financial-critical) — 495→500 PASS (+5 Infra, file 10→15), no-red, NO prod bug:** feature anh Kiệt: PRO có cả Min&Max → người duyệt cấp cuối chọn CẢ HAI (source="ProMinMax") → `ApprovedPriceAmount=Min`, `ApprovedPriceMaxAmount=Max`. Bất-biến `Max!=null ⟺ Source=="ProMinMax"`. Prod đã land: `ApplyApprovedPriceOnFinalize(...,decimal? maxAmount=null)` +`ValidApprovedPriceSources`+"ProMinMax"; `TransitionAsync`/`ApproveV2Async` +`approvedPriceMaxAmount` (SAU source). 5 new [Fact] qua reachable terminal path (mirror case-1 SeedSingleApproverWorkflow, KHÔNG gọi private): (a) happy Min100/Max130 set cả 2 · (a-boundary) **min==max OK** lock guard `>` vs `>=` (revert→RED) · (b) thiếu Max→Conflict "*phải có cả giá Min và giá Max*" not-finalized · (c) min>max→Conflict "*không được lớn hơn giá Max*" · (d) nguồn ĐƠN+Max→Conflict "*kèm giá Max*" wire-bug-guard. +regression assert `Max==null` vào Theory 3b (4 nguồn đơn). **⚠️ Gotcha (self-inflicted-avoided):** prod method 5-param nay → `InvokeApply` reflection helper phải pass 5 args (`{...,maxAmount}`); optional param KHÔNG auto-bind qua `MethodInfo.Invoke` → nếu giữ 4 args = `TargetParameterCountException` phá unit-test 4/4b/4c. Đã fix helper. KHÔNG touch prod.
## S116 — PE bao gia SO AM regression-lock (test-before guard R2-revised), +9 -> 495
**S116 (2026-07-13) PE báo giá SỐ ÂM regression-lock (test-before cho guard R2-revised — critical-algo/financial) — 486→495 PASS (+9 Infra), no-red, NO prod bug:** feature anh Kiệt cho ThanhTien ÂM (phát-sinh-giảm/hoàn tiền NCC = giá THẬT). BE ĐÃ đổi submit-guard `:202-208` `Sum()<=0``Count==0`(chưa chọn)/`else All(x==0)`(chưa có giá). 4 nhóm test khớp hành-vi MỚI: **T1** `PeWorkItemBudgetTests` (+1) quote ÂM 20tr KHÔNG chạm PeWorkItemBudget record (FullAmount ngân sách bất biến) NHƯNG CHẢY vào CurrentProposalTotal=20tr (chứng tách-biệt cột đề-xuất vs ngân-sách, "0 code" kiến-trúc). **T2** `PeMultiWinnerTests` (+2) winnerQuoteTotal=SUM CÓ DẤU khác-winner(+50,20=30) + cùng-winner-2-dòng(80,30=50). **T3** `PeSubmitGuardAndBypassTests` (+4, +2 helper `SeedWinnerWithQuotesAsync` params + `BuildV2PeReadyToSubmitWithQuotesAsync`): all-zero[0,0]→BLOCKED "chưa có giá"; pure-neg[500k]→ChoDuyet; mixed net>0→ChoDuyet; 🔒 **mixed net==0 từng-dòng≠0 [+500k,500k]→ChoDuyet** = ranh giới phân-biệt All(==0) vs Sum()<=0 (revert→RED). **T4** `PeCcmThresholdFinalizeTests` (+2, +helper 2-signed-quote): NET threshold — [+1.2tỷ,0.3tỷ]=0.9tỷ<ngưỡngCCM finalize no-throw + CONTRAST [+1.2tỷ] alone=1.2tỷ≥ngưỡng→Conflict (dòng âm LOAD-BEARING, R-CEO giữ NET). KHÔNG touch prod.
## S113 — Supplier import v2 dedup + publish-guard (test-after critical-algo + security), +19 -> 477
**S113 (2026-07-12) Supplier import v2 dedup + publish-guard TEST-AFTER (critical-algo + security) 458477 PASS (+19 Infra), no-red, 🔴 1 BUG-FOUND (REPORTED em-main, NOT fixed):** 2 new files `Services/SupplierImportV2DedupTests.cs` (13) + `Application/SupplierPublishAndListTests.cs` (6). Existing S112 file UNTOUCHED (re-baked ExpectedHeaderTokens=30 \n-token real tokens khớp Case9 tự flip LayoutValid=true). **Dedup precedence** existing-MSTexisting-Codebatch-MSTbatch-CodeNew: strip-ws-normalize MST-match · 2-blank-MST=2-insert (blank không index) · same-MST-batch=1-insert+fill-null-skip · **Code-backstop CI** (S112: dedup SERVICE OrdinalIgnoreCase, SQLite BINARY KHÔNG bắt "backstop"≈"BACKSTOP" xanh=chứng không dựa DB) · **MST→A/Code→B = MST-precedence WINS, no silent-clobber** (FillNulls skip Code A.Code giữ, B untouched). **Publish** `PublishSupplierCommand` (KHÔNG UpdateSupplier #73): gate D2 Publish+blank-CodeConflict "Cần NCC" · unique-exactConflict "đã tồn tại" (⚠ `x.Code==newCode` DB-collation, SQLite bắt exact-case only prod SQL CI bắt CI-dup; asymmetry vs import, observation không bug) · unpublish always. ListSuppliers Published t/f/null · GetSupplier DTO.IsPublic. **🔴 BUG:** `NewSupplier` `Code=r.Code!.Trim()` NRE khi row.Code==NULL (path R4 nháp: parser sinh null ô rỗng + preview classify New no-error confirm NRE 500 CẢ batch dbCount=0). Empty ""=OK (T8/T14). Characterization test CHỐT NRE fix null-safe thì flip NotThrow+draft.
## D4 — PE sign-off A1 + EDGE-5 + opinion-history (test-before-merge critical-algo), +8 -> 458
**D4 (2026-07-12) PE sign-off A1 + EDGE-5 + opinion-history TEST-BEFORE-MERGE (critical-algo bug-fix + security) 450458 PASS (+8 Infra, 4 updated in-place), no-red, NO prod bug:** BE đổi `ApplyDrafterBypassOnSubmitAsync` sang **A1** trong `PeSubmitGuardAndBypassTests.cs`. **4 updated + RENAMED** (tên misleading): drafter bypass CHỈ cấp NV dưới THUẦN (Order<minOwn); pointer DỪNG tại minOwn (người soạn TAY qua ApproveV2Async); KHÔNG auto-sign opinion chính chủ / KHÔNG auto-terminal-on-submit / KHÔNG auto nhảy Bước. minOwn=1 (drafter Cấp1 hoặc non-contiguous Cấp1+3) bypass RỖNG (opinion=0, AutoApprove=0). **8 new:** A1 happy-path (drafter Cấp2 comment THẬT canned "duyệt tự động" đã GỠ khỏi prod, grep 0-match) · non-contiguous full-manual-chain đúng thứ tự · EDGE-5 ×4 (non-admin Reject từ DaDuyet/TuChoi Conflict "trạng thái kết thúc"; **Admin + system[actorUserId=null+AutoApprove] MIỄN** fall-through "không hỗ trợ" chứng minh qua-guard) · opinion-history ×2 (re-sign đè opinion non-empty đúng 1 Changelog Workflow/Update EntityId=level.Id ContextNote chứa comment ; first-sign / đè-rỗng 0 row, filter theo EntityId==levelId loại được Trả-lại-changelog EntityId=null). Helpers: `ApproveAsync` (approvedPrice khi cấp CUỐI terminal ApplyApprovedPriceOnFinalize Conflict nếu thiếu, isSystem miễn) + `ReturnToDrafterAsync` (AllowReturnToDrafter default TRUE trên Level). Empty-opinion chỉ reachable qua seed-trực-tiếp (normalizedComment placeholder-hoá whitespace).
## D3 — Supplier Excel-import BE Phase B (test-before-merge case-collation bug-class), +10 -> 450
**D3 (2026-07-12) Supplier Excel-import BE (Phase B) TEST-BEFORE-MERGE (CRITICAL-ALGO + case-collation bug-class) 440450 PASS (+10 Infra), no-red, NO prod bug:** new `Services/SupplierExcelImportServiceTests.cs` (10 [Fact]). **Case-collation (THE bug):** ConfirmAsync lowercase `truonggiang` vs DB `TRUONGGIANG` Inserted=0/Updated=1/count=1 (dedup SERVICE `OrdinalIgnoreCase`, KHÔNG dựa DB SQLite fixture BINARY collation KHÔNG bắt CI-dup, prod SQL Server CI thì nếu Ordinal = 500 batch); Preview classify Update. **Fill-nulls SAFE** (non-null giữ / null điền). **All-or-nothing** (1 hard-errorCommitted=false + DB unchanged). MapType unknownNhaCungCap+warning · emoji-strip status DangHoatDong · valid-layout absolute-index parse (LinkGuq RAW backslash) · no-column-drift (col9 emptycol12 vẫn TaxCode) · #REF! literalnull. **Pattern (reuse):** header xlsx dựng từ `ExpectedHeaderTokens` qua REFLECTION (`GetField(NonPublic|Static)`) valid-layout test tự đồng bộ khi tokens sửa; ClosedXML in-mem `XLWorkbook.AddWorksheet`+`ws.Cell(r,c).Value=string` GIỮ TEXT (0.105 no numeric-coercion leading-zero safe). **Spec-drift FLAGGED (không phải bug):** `ExpectedHeaderTokens` = BEST-GUESS 30 real row-4 token Case 9 dynamic `LayoutValid==(NormalizeJoin(real)==NormalizeJoin(reflected))` robust 2-chiều, hiện LayoutValid=false; close-review sẽ sửa tokens test tự flip true.
## S97 — PE EndsBeforeCeo CONFIG->RUNTIME (spec-change test), +0 (extend in-place)
**S97 (2026-07-01) PE EndsBeforeCeo CONFIGRUNTIME (bug anh Kiệt FDC, spec-change test) 440 PASS no-red, NET +0 [Fact] (extend in-place):** em-main BE thêm `PurchaseEvaluation.EndedByLevelFinalize` (Mig 60) set true DUY NHẤT nhánh level-finalize `ApproveV2Async`. ListApproved (DaDuyet-only) đọc THẲNG field (`CreateContractFromEvaluationFeatures.cs:196`); List/Inbox ternary `Phase==DaDuyet ? field : subquery`. **Spec-change RED handled:** `PeFinalizeProjectionTests` seed-helper +param `endedByLevelFinalize` case `peFin` set true; **REPRO case mới `peFinButWentCeo`**: CÙNG wfFin config-có-finalize NHƯNG field=FALSE EndsBeforeCeo=FALSE (bug: config-true nhưng lên CEO thật). FinalizeStep/LevelName VẪN từ config-subquery (nhãn cấp, không phải cờ). **EF-translate OK** (ListApproved handler no-throw). Service `PeApproverFinalizeTests` +3 assert: #1 finalizetrue / #2 advance-CEOfalse / #6 opt-outfalse (khớp projection repro). No prod bug.
## S92 — admin-only-modules revoke (test-before-merge SECURITY), +3 -> 434
**S92 (2026-06-29) +3 admin-only-modules revoke (test-before-merge SECURITY) 431434 PASS, no red:** anh chốt 5 nhóm menu (NHÂN SỰ/VĂN PHÒNG SỐ/ NHÂN/HỢP ĐỒNG/DANH MỤC) CHỈ Admin thấy. Em-main mở rộng `RevokeTemporarilyHiddenModulesAsync` predicate +Contracts/Ct_*/Master/Suppliers/Projects/Departments/Catalog* (cộng dồn S58 Hrm*/Off*/Personal) + TẮT 2 re-grant S65/S69 + narrow `InReviewScope→Pe_*`. `AdminOnlyModulesRevokeTests.cs` (3): NonAdmin-S92Modules-4cờ-false LOAD-BEARING + Admin-untouched + Pe-module-NOT-revoked invariant (root `PurchaseEvaluations` + `Pe_DuyetNcc` không match predicate giữ user). Reflection harness mirror Office/Hrm seed test. **Gotcha:** OfficeModulePermissionSeedTests + HrmProfilePermissionSeedTests VẪN PASS prod đã TẮT 2 call grant test invoke method-CÔ-LẬP qua reflection (private static giữ cho reversibility), KHÔNG chạy SeedAsync chain; thêm 1 dòng header note mỗi file. (ChangePassword S91 10-test ĐÃ merge trong baseline 431.)
## D2 — PE Mig 58 multi-winner (test-after + EF translation-smoke), +6 → 419
`CreateContractFromEvaluationMultiWinnerTests.cs` (Application ns, 5) cho `CreateContractFromEvaluationCommand` singlemulti-winner trả `List<Guid>` (1 per đơn vị IsWinner). Handler 4-dep `(db, ICurrentUser, IContractWorkflowService, IContractCodeGenerator)` wire THẬT (mirror CreateContractCommandApplicableTypeTests S33): ContractCodeGenerator(db,clock)+ContractWorkflowService BeginTransactionAsync SERIALIZABLE chạy SẠCH SQLite, KHÔNG try/skip. IdentityFixture. 5 case: (a)≥2 winner2 ids·2 Contract·Phase=DangSoanThao·SupplierId khớp winner·**GiaTri per-winner = SUM ThanhTien báo giá của ĐÚNG winner (q.PESupplierId==winnerRow.Id)** 140/260·2 DISTINCT·pe.ContractId==ids[0] / (b)single→**GiaTri = SUM details.ThanhTienNganSach (LEGACY 800, NOT quote-total 140)**·1 ·TenHopDong nullfallback TenGoiThau / (c)idempotencyConflictException`*đã tạo HĐ rồi*`+count 2 / (d)0 winnerConflict`*chưa chọn NCC/TP trúng thầu*`+0 / (e)not-DaDuyetConflict`*DaDuyet*`+0 . **SEED-FK: PurchaseEvaluationQuote HARD FK PESupplierIdSupplier Restrict (cfg:135)** mỗi Quote ref 1 PESupplier-row thật (random GuidSQLite 19); Contract.SupplierId/ProjectId no FK vật nhưng handler load Project(NotFound)+supplierMapseed real. `PeListWinnerNamesProjectionTests.cs` (1) TRANSLATION-SMOKE cho EF subquery `WinnerSupplierNames` collection-projection **EF TRANSLATE OK no InvalidOperationException trên SQLite**: 2/1/0-winner. SqliteDbFixture read-only. No prod bug.