Closeout feature multi-NCC per hạng mục (ship d436af2):
- STATUS/HANDOFF: S114 blurb + CURRENT STATE canonical Mig 64→65 · test 477→486 · gotcha 80→81 · bundle D51OYyGV/BVdssm5S.
- gotcha #81: derived-invariant re-derive sót DELETE writer (DeleteQuote/DeleteDetail) → IsWinner kẹt stale → HĐ giaTri=0 phantom (Bước 4 fable-clone verify bắt; fix helper PeWinnerInvariant test-before).
- ef-core skill: +Mig 65 BackfillPeQuoteIsSelectedAndWinnerInvariant row.
- session log 2026-07-12-S114 + 2 reviewer diary harvest (verify run) + run-trace fold.
- cicd on-behalf record (garble #53 ×5 → em-main-solo deploy verify).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.0 KiB
S114 — PE multi-NCC per hạng mục (2026-07-12)
Feature ship prod: cho chọn ≥2 NCC-TP trúng thầu vào 1 hạng mục (PurchaseEvaluationDetail), mỗi nhà 1 giá riêng. Anh Kiệt (FDC). Commit d436af2. em-main-led, 6-bước theo plan anh, HMW-ON, lead=Opus 4.8 owner-choice.
Flow 6 bước (fable-clone → hmw → verify)
- B1
/fable-clone invest— 4-lane investigator-codebase ensemble (wf_dd49ddc0, schema/be-consumer/fe-consumer/product-history). Phát hiện 3 trục winner: ASelectedSupplierId(single legacy) · BSupplier.IsWinner(Mig58 liên-danh cả-gói) · CQuote.IsSelected(per Detail×Supplier, hook Mig12 DORMANT — 0 consumer tài-chính). Feature = trục C, KHÁC Mig58. - B2a anh Kiệt 4-decision (AskUserQuestion, all recommended): D1=winnerQuoteTotal SUM(IsSelected) · D2=THAY-THẾ (IsSelected nguồn-sự-thật, IsWinner DERIVED) · D3=giá-chào-gốc Quote.ThanhTien · D4=1HĐ/NCC. → 0 bảng/cột mới.
- B2b
/fable-clone review— 3-lane reviewer ensemble (wf_1e127f4b, consumer-completeness/correctness/checklist-risk). 6 adjustment A1-A6: A1 giaTri bỏ isSingle→detailsSum · A2 derive-invariant MỌI writer + backfill 2-chiều · A3 re-key SPLIT (5 financial→IsSelected, winner-names/existence GIỮ IsWinner-derived) · A4 endpoint per-detail + FE per-cell grid · A5 test-before + threshold-crossing · A6 mig 3-file Down-noop. (lane correctness-edgecase #53-garble → em-main recover, territory phủ bởi 2 lane hội-tụ.) - B3 hmw implement — staged BE→(FE+test) (
wf_e7ee6abestage-1 +wf_410896c4stage-2). BE: command{detailId, supplierIds}set Quote.IsSelected + derive IsWinner (SelectWinner + UpsertQuote + seed); re-key 5 financial-SUM; giaTri bỏ detailsSum; Mig 65 backfill 2-chiều. FE: HangMucCard per-cell winner grid + toggle per-detail; NccSelectorRow hạ-cấp summary; mirror 2-app SHAae3788e9. Test: 7 file update + tests mới. - B4
/fable-clone verify— 2-lane reviewer refute (wf_14850736, CLEAN 0-garble). HỘI TỤ 1 MUST bug invest/review/implement đều sót (gotcha #81, dưới). em-main fix test-before. - B5 deploy commit
d436af2push → CI. B6 session-end (file này).
🎯 MUST bug Bước 4 bắt (gotcha #81)
DeleteQuote + DeleteDetail (cascade) xóa quote-selected KHÔNG re-derive Supplier.IsWinner → xóa quote-selected cuối của NCC để IsWinner kẹt stale (Any(IsSelected)=false) → CreateContract Where(IsWinner) gom NCC phantom → sinh HĐ giaTri=0 + winner-names sai + FE row kẹt. Reachable FE nút "Xóa" quote. 0 test cover. Fix: helper PeWinnerInvariant.ReSyncAfterQuoteRemovalAsync (re-derive từ DB đã-flush) gọi ở 2 delete handler. Test-before: 2 test RED→GREEN + guard (không over-clear) + UpsertQuote-update coverage. Bài học meta: enumeration writer trong spec KHÔNG đáng tin — verify adversarial-refute grep-MỌI-.Remove( bắt (S87/S88 cardinality lên tầng mutation-path).
⚠️ #53 return-garble ×5 phiên này (0 mất việc)
review-lane-2 + BE-lane + FE-lane + test-lane + cicd — tất cả garble RETURN (sub xong việc, không gọi StructuredOutput). Recovery khác theo loại lane:
- READ-only lane (review/verify): garble = mất verdict → recover bằng redundancy (lane sạch phủ territory).
- WRITE lane (BE/FE/test): garble = mất return NHƯNG code trên disk → recover bằng disk-truth + build/test verify (
feedback_agent_kill_recovery). - cicd: garble (trả memory-curation) → em-main-solo verify (curl prod bundle rotate + byte-verify + smoke).
Lesson kỹ-thuật Workflow-tool: stage-1
await agent()TRỰC-TIẾP → 1 garble throw giết cả workflow (FE+test stage-2 không chạy).parallel()= garble→null, workflow SỐNG → verify từ disk. Với WRITE-lane, return chỉ "nice-to-have", disk mới ground-truth. FE-lane garble giữa mirror → chỉ fe-admin xong, em-main mirror fe-user (SHA verify).
State
Mig 65 · 89 bảng · 486 test (45D+441I, +9) · gotcha 81 · menu 54 · bundle admin D51OYyGV/user BVdssm5S · prod 37 NCC/17 user.
Deploy verify (em-main-solo, cicd garble)
bundle admin acprMqfB→D51OYyGV / user Ck3BTDdA→BVdssm5S rotate ×2 · byte-verify detailId+select-winner+hạng mục PRESENT (bundle 1.65MB real) · smoke api/admin/eoffice 3/3 200 · endpoint POST select-winner bogus 401-live · mig applied (API-healthy = migrations-ran-startup).
Carry / follow-up
- UAT anh Kiệt (incognito): giaTri single-winner budget→bid · UX per-cell grid · nit winner-no-quote no-op.
- NEXT em: follow-up nit FE winner-toggle gate quote-tồn-tại (non-blocking) · inv-cb L1 98.6% cap WATCH · watch AI_INFRA 2 email · monthly re-tier 2026-08-01.
- Run-trace:
.claude/workflows/runs/2026-07-12-S114-multi-ncc-per-hangmuc/(run.md + spec-multi-ncc-per-hangmuc-12-07-2026.md).