---
name: feedback_cardinality_change_grep_consumers
description: Đổi field single→multi (cardinality) = grep MỌI consumer của field cũ — impl agent sửa write-site nhưng sót dependent read-site (vỡ chính feature vừa thêm)
metadata: 
  node_type: memory
  type: feedback
  originSessionId: 1f21f760-be7e-4f90-8c3d-337edd5a4a9b
---

Khi đổi 1 field từ single→multi (cardinality), implementer agent sửa đúng chỗ HIỂN NHIÊN (picker/setter/write-site) nhưng SÓT các read-site phụ-thuộc field cũ — và đó là nơi feature MỚI vỡ.

**Ca thật (S87 Mig 58 PE multi-winner):** winner đổi từ scalar `PurchaseEvaluation.SelectedSupplierId` → tập `PurchaseEvaluationSupplier.IsWinner` per-row. implementer-frontend làm xong picker (tick nhiều ô + tổng) nhưng SÓT **8 chỗ FE** vẫn keyed `selectedSupplierId`: `missingForApproval` (submit-guard), checklist, `computeGiaChaoThau`, `canCreateContract`, 2 display, `winnerSupplierRowId`. Vì `SelectedSupplierId` = **null khi liên-danh ≥2** → multi-winner phiếu bị chặn submit (FE nói "chưa chọn NCC" dù đã tick 3 ông) = vỡ ĐÚNG feature vừa thêm. em-main self-review `grep -rn selectedSupplierId` mới bắt → đổi hết sang `ev.suppliers.some(s => s.isWinner)`.

**Why:** cardinality change đổi SEMANTIC field cũ (single→có-thể-null/đổi-nghĩa khi multi). Mọi nơi đọc field cũ với giả-định "luôn có đúng 1 giá trị" sẽ sai. Implementer thấy write-path (1 chỗ), không thấy read-path rải-rác khắp codebase. Reviewer/build cũng PASS (TS compile + tests xanh) vì lỗi là LOGIC-runtime cho case multi, không phải type/compile.

**How to apply:** sau khi đổi cardinality 1 field → `grep -rn "<oldField>"` TOÀN BỘ FE+BE, kiểm TỪNG site: site này giả-định single? → đổi sang nguồn-sự-thật mới. Self-gate BẮT BUỘC dù impl + reviewer + build PASS — đây là blind-spot điển hình. Mở rộng cho mọi "1→N": single→list, optional→required, enum-thêm-value ([[feedback_root_cause_over_symptom]] họ gotcha #71 proxy-predicate). **Trục thứ 2 — thêm ATTRIBUTE/FLAG mới rendered nhiều surface (S89):** thêm 1 per-level flag `AllowApproverFinalize` (Mig 58) vào Workflow Designer nhưng SÓT surface song-song `WorkflowMatrixViewPage` (build S24 hard-code 7 `<FlagRow>`) → anh báo "matrix chưa hiển thị chính xác". Quy tắc: thêm field vào flag-set/attribute-set của 1 entity → `grep` MỌI nơi render set đó (Designer + matrix + flow-panel + chip + DTO projection), không chỉ surface đang sửa. BE thường đã gửi sẵn field; FE-các-surface mới là chỗ sót. Liên quan [[feedback_agent_kill_recovery]] (#53 recover-disk + self-gate) + [[feedback_workflow_fanout_reliability]] (workflow review-lane verify disk độc-lập, trust PASS dù impl-lane return NULL).

**Trục thứ 3 — DERIVED-INVARIANT ở MUTATION-PATH, enumeration-writer KHÔNG đáng tin (S114 gotcha #81):** đổi winner single→multi per-hạng-mục, `Supplier.IsWinner` thành DERIVED (== `Any(Quote.IsSelected)`). Spec (invest+review) liệt kê writer maintain-invariant "SelectWinner + UpsertQuote(ins+upd) + seed + backfill" NHƯNG SÓT **DeleteQuote + DeleteDetail (cascade)** → xóa quote-selected CUỐI của 1 NCC để IsWinner kẹt stale (Any=false) → `CreateContract Where(IsWinner)` gom NCC phantom → sinh **HĐ giaTri=0**. invest/review/implement ĐỀU sót vì cùng theo enumeration; chỉ **verify adversarial-refute** (lane "thử tìm bug, grep độc-lập MỌI `.Remove(`") bắt. **Quy tắc mở rộng:** cardinality/derived-flag change → grep KHÔNG chỉ read-site + write/select mà MỌI **mutation đổi tập-nguồn: thêm (`.Add`) / sửa (`= field`) / XÓA (`.Remove`) / cascade-delete**. Field-nguồn (`Quote.IsSelected`) → mọi path chạm TẬP quote phải re-establish flag phái-sinh. Read-site-miss ≡ writer-miss ≡ delete-writer-miss — cùng 1 class "consumer/producer bị bỏ sót khỏi enumeration". Enumeration của người KHÔNG đáng tin → grep máy-móc + adversarial-verify.
