<!-- task=m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation plan=4 tag=REVIEW_INGRESS_SNAPSHOT_WINNER_ERROR_PAIRING -->

# Plan - Ingress snapshot concurrent winner/error pairing follow-up

## 이 파일을 읽는 구현 에이전트에게

구현과 테스트, 실제 검증 evidence만 수행한다. 모든 변경 뒤 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 내용과 stdout/stderr를 채우고 active pair를 그대로 둔 채 리뷰 준비 완료를 보고한다. 막히면 정확한 blocker, 시도한 명령/출력, 재개 조건만 구현 소유 evidence에 기록한다. 사용자에게 질문하거나 user-input 도구를 호출하거나 control-plane stop 파일을 만들거나 다음 상태를 분류하지 않는다. 로그 archive, `complete.log`, 최종 판정은 code-review 스킬 소유다.

## 배경

concurrent overflow fixture는 oversized commit, committed sibling, parent close를 실제로 경쟁시키지만 guard B의 상태와 `Commit` 오류를 독립적으로 검사한다. `overflowErr == nil`을 허용하므로 commit이 잘못 성공해도 parent close가 최종 state와 backing을 정리하면 테스트가 false positive로 통과할 수 있다. result channel을 확실히 수신하고 terminal winner state와 반환 오류를 한 분기에서 대조해야 SDD S22의 concurrent overflow evidence가 닫힌다.

## Archive Evidence Snapshot

- 이전 task: `agent-task/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation`
- 이전 plan: `agent-task/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation/plan_local_G04_3.log`
- 이전 review: `agent-task/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation/code_review_cloud_G04_3.log`
- 판정: FAIL
- Required 1건: concurrent fixture가 guard B의 terminal state와 `Commit` 오류를 대응시키지 않고 nil 오류를 허용해, oversized commit의 잘못된 성공을 concurrent parent close가 숨길 수 있다.
- Suggested/Nit: 없음.
- 실제 검증: host Go/proto no-drift, deterministic terminal lifecycle, focused `-race -count=20`, package unit/race, symbol scan, gofmt, `git diff --check`는 PASS했다. reviewer source audit에서 `overflowErr != nil` guard와 독립 state assertion이 nil/state-error mismatch를 거부하지 못함을 확인했다.
- 영향 파일: `packages/go/streamgate/ingress_snapshot_test.go`.
- Roadmap carryover: 승인된 SDD S22의 rebuild overflow no-dispatch/request-owned canonical·temporary release를 유지한다. 이 subtask PASS만으로 Milestone Task를 직접 체크하지 않는다.

## 분석 결과

### 읽은 파일

- 소스: `packages/go/streamgate/ingress_snapshot.go` 전체.
- 테스트: `packages/go/streamgate/ingress_snapshot_test.go` 전체.
- 규칙/환경: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`, `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`.
- 설계: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`.
- 현재 loop: `agent-task/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation/plan_local_G04_3.log`, `agent-task/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation/code_review_cloud_G04_3.log`.
- 선행 근거: `agent-task/archive/2026/07/m-stream-evidence-gate-core/15+12_ingress_snapshot_ledger/complete.log`.

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`; 상태 `[승인됨]`, 잠금 `해제`.
- 대상: Acceptance Scenario S22 / Milestone Task `request-rebuilder`.
- Evidence Map S22는 rebuild pre/post-allocation peak overflow의 no-dispatch, request-owned canonical/temporary release, no raw retention을 요구한다.
- 이번 follow-up은 Milestone Task 완료를 주장하지 않고, oversized commit의 winner별 state/error를 강제해 S22 concurrent terminal fixture의 false-positive 경로만 닫는다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다.
- PATH의 host Go 경로/version/GOROOT를 기록하고 같은 toolchain으로 focused `-race -count=20`, fresh package unit/race를 실행한다.
- proto/config/wire는 바꾸지 않지만 local quick-check 정합성으로 `make proto && git diff --exit-code -- proto/gen`을 유지한다. user execution/wire/config 변경이 없어 Edge/Node full-cycle은 적용하지 않는다.
- 외부 서비스, Docker, credential, port, `<확인 필요>` 값은 없다.

### 테스트 커버리지 공백

- 기존 deterministic sibling invalidation tests는 실제 overflow와 stable overflow error를 덮는다.
- concurrent fixture는 guard B state와 error를 독립적으로 허용하고 nil error를 건너뛰므로, concurrent path에서 oversized commit이 반드시 overflow/closed 오류로 끝났다는 회귀 증거가 없다.
- 수정 후에는 result channels를 직접 수신하고 `Overflow → ErrIngressSnapshotRebuildOverflow`, `Released → ErrIngressSnapshotClosed`를 한 switch에서 강제한다. guard C의 cancel result도 nil인지 확인한다.

### 심볼 참조

- rename/remove/public API 변경은 없다.
- 새 dependency와 `go.mod` 변경은 없다. 대상은 기존 test 함수 한 개와 기존 guard API 호출뿐이다.

### 분할 판단

- split policy를 먼저 재평가했다. 한 test 함수 안의 result collection과 winner assertion을 같은 concurrent state/error 불변조건으로 고치는 test-only 단위다.
- API/production/ownership 경계, 독립 검증 slice, 새 선행 산출물이 없어 추가 분할은 인위적이다. 기존 단일 subtask `16+15_ingress_snapshot_allocation`을 유지한다.
- predecessor `15`는 `agent-task/archive/2026/07/m-stream-evidence-gate-core/15+12_ingress_snapshot_ledger/complete.log`로 충족됐다.

### 범위 결정 근거

- 포함: `packages/go/streamgate/ingress_snapshot_test.go`의 concurrent result receive와 guard B winner state/error pairing, guard C cancel result assertion.
- 제외: `ingress_snapshot.go` production lifecycle, public API, config/proto/contract, host wiring. 강화된 fixture가 production failure를 드러내면 범위를 조용히 확장하지 않고 실제 실패와 재개 조건을 review evidence에 기록한다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`; `finalizer_mode=pair`.
- build closures: scope/context/verification/evidence/ownership/decision 모두 true. 한 test 함수, source로 확정된 nil false-positive, exact focused/package 명령, platform-common 단일 ownership, 사용자 결정 없음으로 닫혔다.
- build route basis: `local-fit`; grade scores: scope=0, state=2, blast=0, evidence=0, verification=1; 합계 G03; capability_gap=`none`; none floor=`none`; lane/file=`local`, `PLAN-local-G03.md`.
- loop risk: ordered transitions state_count=5(reserved/committed/cancelled/overflow/released), adverse paths=oversized commit/parent close/cancel/nil result. concurrent actors=3(commit/cancel/close), constraints=winner state-error pairing/no false positive/race-free. boundary components=2, consumers=0. structured mechanisms=없음. variant axes=2(guard B terminal winner, guard C terminal winner), combination 검증 필요=true. matched signatures=`temporal_state`, `concurrent_consistency`, `variant_product`; triggered=true.
- review closures: scope/context/verification/evidence/ownership/decision 모두 true. exact source assertion과 fresh command output으로 독립 판정할 수 있다.
- review route basis: `official-review`; grade scores: scope=0, state=2, blast=0, evidence=0, verification=1; 합계 G03; capability_gap=`none`; none floor=`none`; lane/file=`cloud`, `CODE_REVIEW-cloud-G03.md`; target=`gpt-5.6-sol` xhigh.

## 구현 체크리스트

- [x] REVIEW_INGRESS_SNAPSHOT_WINNER_ERROR_PAIRING-1 result channels를 직접 수신하고 guard B terminal winner state와 `Commit` error를 같은 switch에서 대조해 nil/mismatched error를 거부하며, guard C cancel result와 fresh focused/unit/race/proto/gofmt/diff 검증을 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_INGRESS_SNAPSHOT_WINNER_ERROR_PAIRING-1] Winner-paired commit result assertion

- 문제: `packages/go/streamgate/ingress_snapshot_test.go:2459-2468`은 완료된 goroutine 결과를 non-blocking select로 읽고, `:2540-2556`은 guard B state와 오류를 독립 검사하며 `overflowErr == nil`을 허용한다. oversized commit이 잘못 성공한 뒤 parent close가 guard와 rebuilt snapshot을 정리하면 fixture가 false positive로 PASS할 수 있다.
- 해결 방법: `wg.Wait()` 뒤 buffered result channel을 직접 수신한다. guard B state switch 안에서 Overflow는 `ErrIngressSnapshotRebuildOverflow`, Released는 `ErrIngressSnapshotClosed`와 반드시 짝지어 nil과 mismatch를 모두 실패시킨다. guard C는 Cancelled/Released만 허용하고 `cancelErr`가 nil인지 확인한다.

Before (`packages/go/streamgate/ingress_snapshot_test.go:2540-2556`):

```go
bState := guardB.State()
if bState != RebuildStateOverflow && bState != RebuildStateReleased {
    t.Errorf("guardB State: want Overflow or Released, got %v", bState)
}
if overflowErr != nil && !errors.Is(overflowErr, ErrIngressSnapshotRebuildOverflow) && !errors.Is(overflowErr, ErrIngressSnapshotClosed) {
    t.Errorf("guardB Commit error: want RebuildOverflow or Closed, got %v", overflowErr)
}
```

After:

```go
overflowErr := <-overflowResult
cancelErr := <-cancelResult
switch bState := guardB.State(); bState {
case RebuildStateOverflow:
    if !errors.Is(overflowErr, ErrIngressSnapshotRebuildOverflow) {
        t.Errorf("guardB Commit in Overflow state: want RebuildOverflow, got %v", overflowErr)
    }
case RebuildStateReleased:
    if !errors.Is(overflowErr, ErrIngressSnapshotClosed) {
        t.Errorf("guardB Commit in Released state: want Closed, got %v", overflowErr)
    }
default:
    t.Errorf("guardB State: want Overflow or Released, got %v", bState)
}
```

- 수정 파일 및 체크리스트:
  - [x] `packages/go/streamgate/ingress_snapshot_test.go`: direct result receive, guard B state/error pairing, guard C cancel result assertion.
- 테스트 작성: 기존 `TestIngressSnapshot_RebuildConcurrentOverflowCloseCancelRace`를 수정한다. 별도 test는 만들지 않고 같은 fixture에서 nil/mismatched commit error, invalid terminal state, non-nil cancel result를 거부한다.
- 중간 검증: `go test -race -count=20 ./packages/go/streamgate -run '^TestIngressSnapshot_RebuildConcurrentOverflowCloseCancelRace$'`가 PASS한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/streamgate/ingress_snapshot_test.go` | REVIEW_INGRESS_SNAPSHOT_WINNER_ERROR_PAIRING-1 |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT`
- `make proto && git diff --exit-code -- proto/gen`
- `go test -count=1 ./packages/go/streamgate -run '^TestIngressSnapshot_(RebuildOverflowInvalidatesCommittedAndReservedSiblings|RebuildOverflowErrorStableOnRepeat|CloseInvalidatesCommittedGuard)$' -v`
- `go test -race -count=20 ./packages/go/streamgate -run '^TestIngressSnapshot_RebuildConcurrentOverflowCloseCancelRace$'`
- `go test -count=1 ./packages/go/streamgate`
- `go test -race -count=1 ./packages/go/streamgate`
- `if rg -n 'overflowErr != nil &&' packages/go/streamgate/ingress_snapshot_test.go; then exit 1; fi; rg --sort path -n 'overflowErr := <-overflowResult|cancelErr := <-cancelResult|switch bState|RebuildStateOverflow|RebuildStateReleased' packages/go/streamgate/ingress_snapshot_test.go`
- `gofmt -d packages/go/streamgate/ingress_snapshot_test.go`
- `git diff --check`

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
