# T732 — atomic product match repositories

> Status: `done`  
> Milestone: `M3` · Phase C  
> Kind: `feat` · Est. change: `~200 lines`

## Goal

repository 在多 worker 競態下仍只保存一筆 Opportunity 與一筆同產品 ProductMatch。

## Depends on

- T701、T730、T731

## Inputs

- `apps/backend/internal/module/radar/repository/opportunity_{memory,mongo}.go`
- `../spec.md` §3.3、§6、§8

## Outputs

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/module/radar/domain/repository.go` | edit | merge match、set primary、product filters methods |cl3
| `apps/backend/internal/module/radar/repository/opportunity_memory.go` | edit | 同語意實作 |
| `apps/backend/internal/module/radar/repository/opportunity_mongo.go` | edit | atomic upsert／guarded primary／stable list |
| repository tests | edit/add | duplicate race、watch／term set merge |

## Behavior

- existing external ID 不再只 merge terms；新 Product 增 match，同 Product 只 merge sources。
- 人工 primary override 不被自動 merge 改寫。
- 新 match 不覆寫舊 match snapshot。

## Out of scope

- 決定 match 分數（T742/T743）
- HTTP logic

## Acceptance

- [x] memory concurrency test has no duplicate ProductMatch; Mongo uses guarded insert and source-set merge.

```bash
cd apps/backend && go test ./internal/module/radar/repository/... -race -count=1
```

Implementation result (2026-08-10): added repository merge/set-primary methods,
owner guards, stable product filters, guarded Mongo array insertion, and memory
race tests preserving snapshots and human overrides.
