# invest-synthesis — S114 multi-NCC per hạng-mục

> 🔄 **partial-honest — CHUẨN-HOÁ VỊ-TRÍ @S122 (2026-07-15), KHÔNG PHẢI VIẾT MỚI.**
>
> Thân dưới = **trích từ `run.md:28-36`** (`## Synthesis` + `## Spec-file`), đã tồn-tại từ **2026-07-12**.
> 🔴 **Bản gốc trong `run.md` GIỮ NGUYÊN, và bản gốc mới là CHUẨN** — file này là bản-sao chuẩn-hoá vị-trí. Không cắt, không rewrite history (tinh-thần C8 `workflows/README.md:19`).
>
> **Vì sao file này ra đời:** orphan-authority (`workflows/README.md:20`) đo **TÊN FILE**, không đo **nội-dung**. Đo @S122: `naive 13 / dual-accept 8 / C8 cứu 5` — **0/7 là việc mất**. Anh chủ-dự-án chốt @S122: **chuẩn-hoá + tri-accept**.
>
> 🔸 **Ca đáng chú ý:** lúc synthesis được viết, nó ghi *"D1-D5 **CHỜ anh Kiệt**"* ⇒ tại thời-điểm đó việc **CHƯA xong**. Việc land **SAU** đó cùng ngày (`d436af2`, 22:22). Tức folder này **không** phải "quên harvest" — nó **đúng là đang-chờ**, rồi chạy tiếp mà không ai quay lại đóng sổ.

---

## [TRÍCH `run.md:28-36` — bản gốc là chuẩn]

### Synthesis (4-lane hội tụ)

- **3 trục winner:** A `PE.SelectedSupplierId` (single legacy) · B `Supplier.IsWinner` (Mig 58 liên-danh cả gói) · **C `Quote.IsSelected`** (per hạng-mục×NCC, Mig 12 DORMANT). **Feature = trục C**, KHÁC Mig 58 (trục B).
- **Hook `Quote.IsSelected` sẵn từ Mig 12** — persist + echo DTO + seed, mỗi cell có ThanhTien riêng, nhưng 0 consumer tài-chính → storage ≈ 0 migration, chỉ wire + re-key aggregation.
- **Cardinality-change lần 3** (S87 single→multi sót 2 read-site, S88 bắt). 6 BE read-site tài-chính + ~8 FE key off `IsWinner` → grep MỌI consumer.
- 4 lane token: schema 117.9K · be-consumer 130.9K · fe-consumer 100.6K · product-history 148.7K. Tổng 498K, 0 empty/error. Model `claude-opus-4-8` ×4.
- Engine stamp: `[engine: fable-clone-ensemble · Opus-S114]` (em-main harvest @closeout).

### Spec-file

`spec-multi-ncc-per-hangmuc-12-07-2026.md` — Bước 1 kết-tinh (3-mục: đề-bài+ground-truth · cách-implement decision-gated · checklist C1-C6). D1-D5 CHỜ anh Kiệt.

---

## Verify độc-lập @S122 (lead — KHÔNG tin thẳng return sub, và KHÔNG tin nhãn `CLAUDE.md`)

**VERDICT: LANDED** — neo bằng vật-chứng, **không** dựa vào `CLAUDE.md` (doc dẫn-xuất).

| Vật-chứng | Neo |
|---|---|
| **Commit code** | **`d436af2971fbc25b962929ae40f0348df447bccb`** (`d436af2`) · Sun **Jul 12 22:22:09 2026 +0700** · *"[CLAUDE] PurchaseEvaluation: multi-NCC per hạng mục…"* — **18 file, +7272/−425** |
| **Mig 65 file** | `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260712142652_BackfillPeQuoteIsSelectedAndWinnerInvariant.cs` |
| 🟢 **Ordinal = 65 ĐO ĐƯỢC** | sorted non-Designer listing → vị-trí **65/66** (64 = `AddSupplierPublishState`, 66 = `AddPeApprovedPriceRange`) — **KHÔNG chỉ tin nhãn doc** |
| **Nội-dung Mig 65** | `…IsSelectedAndWinnerInvariant.cs:23-27` CHIỀU-1 `UPDATE q SET IsSelected=1 … WHERE s.IsWinner=1 AND q.IsSelected=0` · `:32-38` CHIỀU-2 `UPDATE s SET IsWinner=1 … EXISTS(… IsSelected=1)` · `:42-47` Down **no-op** (mirror Mig 61) — khớp `spec:29` + A2/A6 `spec:69/84` |
| **gotcha #81** | **`docs/gotchas.md:1343`** — *"Derived-flag invariant re-derive ở write/select NHƯNG SÓT DELETE writer → … phantom (Session 114)"*; fix `PeWinnerInvariant.ReSyncAfterQuoteRemovalAsync` `:1347`; bài-học `:1349`. Tổng gotcha = **82** |
| **Closeout** | **`16a199f`** Jul 12 **23:45:08** — *"[CLAUDE] Docs: S114 … closeout (STATUS/HANDOFF/gotcha #81/Mig 65/session-log)"*; body ghi *"run-trace fold"* + *"cicd on-behalf record (garble #53 ×5 → em-main-solo deploy verify)"* |
| **Session log** | `docs/changelog/sessions/2026-07-12-S114-multi-ncc-per-hangmuc.md` (32 dòng) — B1→B6 đủ; `:11` *"B5 deploy commit d436af2 push → CI"* |

> 🔴 **Cảnh-báo của synthesis đã thành hiện-thực.** Synthesis ghi *"**Cardinality-change lần 3** (S87 single→multi sót 2 read-site, S88 bắt) → grep MỌI consumer"*. Kết-quả: S114 **vẫn sinh gotcha #81** — cùng họ (derived-flag re-derive **sót DELETE writer** → phantom). Tức lời cảnh-báo đúng hướng nhưng **quét chưa đủ rộng**: nó nhắc grep *read-site*, mà lỗ nằm ở *write-site (DELETE)*. Khớp memory `feedback_cardinality_change_grep_consumers` — và là **bằng-chứng bài học đó chưa đủ mạnh**, cần mở rộng sang delete-path.
>
> ⚠️ **`16a199f` body tự khai `garble #53 ×5`** → cicd-lane chết ×5 ⇒ em-main-solo deploy verify. Khớp memory `feedback_agent_return_garble_recover` (*"cicd tái-phạm dai"* — S114 là một data-point của chuỗi đó).

### Gap KHÔNG chứng minh được (khai thẳng)

1. **D1-D5** — synthesis ghi *"CHỜ anh Kiệt"*. Việc land 22:22 cùng ngày ⇒ **chắc chắn đã có chốt**, nhưng S122 **KHÔNG tìm verbatim quyết-định của anh** trong run-folder ⇒ **KHÔNG XÁC ĐỊNH ĐƯỢC** từng D chốt ra sao. (Chỉ suy được là *đã* chốt, từ việc code land.)
2. **Prod deploy** — `session-log:11` ghi *"B5 deploy commit d436af2 push → CI"*, nhưng CI-result **do em-main ghi on-behalf** (cicd-lane garble ×5) ⇒ nấc bằng-chứng **cấp-2**, không phải probe trực-tiếp. S122 cũng không probe.
3. **6 BE read-site + ~8 FE key off `IsWinner`** — synthesis nói phải grep hết. S122 **KHÔNG re-grep** để xác nhận đã phủ đủ ⇒ **KHÔNG XÁC ĐỊNH ĐƯỢC**. 🔸 Riêng điểm này đáng soi lại thật, vì gotcha #81 chứng minh **lần quét đó đã sót ít nhất 1 chỗ**.

### Lệch convention của chính folder này (ghi để không lặp)

- **0 sub-MD** dù 4 lane chạy (498K token, 0 empty/error) ⇒ **run-trace thủng** — 498K công-sức điều-tra chỉ còn lại 8 dòng gom. Run này **có trước** sàn H22 S111 ép RUN-TRACE ≥3 task.
- Synthesis ghi vào **thân `run.md`** ⇒ **dạng-3**, nguyên-nhân gốc false-orphan.

---
*Chuẩn-hoá vị-trí @S122 `/tiep` · run `2026-07-15-S122-retro-harvest-7-orphan` · wf `wf_5ce007c4-980` lane `retro:S114-multi-ncc-per-hangmuc` · lead Opus 4.8 · sub read-only, lead single-writer (B6)*
