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

# Plan - Ingress snapshot allocation fail-closed follow-up

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

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

## 배경

첫 공식 리뷰에서 기존 unit/race suite는 통과했지만 SDD S22의 hard-limit과 retained-object release 경계를 직접 실행한 focused reproducer 두 케이스가 실패했다. pre-peak 산술 wraparound와 post-overflow/normal-completion ownership release를 같은 snapshot/guard lifecycle 안에서 닫고 결정적 회귀로 고정한다.

## 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_G08_0.log`
- 이전 review: `agent-task/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation/code_review_cloud_G08_0.log`
- 판정: FAIL
- Required 2건: `int64` pre-peak wraparound로 초과 reservation 허용, post-peak overflow/normal completion에서 request-owned snapshot과 guard reference release 미완료.
- Suggested/Nit: 없음. 대상 Go 파일의 gofmt drift는 리뷰 중 정리됐다.
- 실제 검증: fresh unit/race와 `git diff --check`는 PASS. focused reproducer는 `ReserveRebuild(math.MaxInt64)` guard 발급과 overflow 뒤 `closed=false retained=60`을 확인해 FAIL.
- 영향 파일: `packages/go/streamgate/ingress_snapshot.go`, `packages/go/streamgate/ingress_snapshot_test.go`.
- Roadmap carryover: 승인된 SDD S22와 `request-rebuilder` retained-byte/release 계약을 유지하되 이 subtask PASS만으로 Milestone Task 완료를 직접 체크하지 않는다.

## 분석 결과

### 읽은 파일

- 소스: `packages/go/streamgate/ingress_snapshot.go` 전체.
- 테스트: `packages/go/streamgate/ingress_snapshot_test.go` 전체.
- 규칙/환경: `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`, `go.mod`.
- 설계/현재 상태: `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`, `agent-spec/input/openai-compatible-surface.md`, `agent-contract/outer/openai-compatible-api.md`.
- 선행 근거: `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는 pre/post-allocation peak overflow, no dispatch, request-owned canonical/temporary release, configured hard bound를 요구한다.
- 이 기준에서 checked arithmetic, overflow terminal release, sibling guard invalidation, normal completion guard-reference drop과 fresh unit/race 검증을 역산했다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다.
- PATH의 host Go, resolved binary, version, GOROOT를 먼저 기록하고 같은 toolchain으로 fresh package/race test를 실행한다.
- proto를 바꾸지 않지만 platform-common profile의 setup 정합성 확인으로 `make proto && git diff --exit-code -- proto/gen`을 실행한다.
- 외부 서비스, Docker, credential, port는 필요 없다. `<확인 필요>` 값은 없다.

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

- 기존 pre-peak test는 작은 정상 정수의 초과만 다뤄 `int64` wraparound를 놓쳤다.
- 기존 post-peak test는 guard handle metadata만 확인해 parent snapshot backing이 남는 문제를 놓쳤다.
- 기존 success/cancel/race test는 committed guard `Close`의 hidden `rebuiltSnapshot` 참조 drop과 sibling guard terminal invalidation을 확인하지 않았다.

### 심볼 참조

- rename/remove 예정 심볼은 없다.
- `IngressSnapshot`, `IngressSnapshotRebuildGuard`, `ReserveRebuild`, `CommitTyped`, `ErrIngressSnapshotRebuildOverflow`의 현재 Go 참조는 `ingress_snapshot.go`와 `ingress_snapshot_test.go`에만 있다.

### 분할 판단

- split policy를 재평가했다. 두 Required는 같은 `IngressSnapshot` mutex/byte ledger와 `IngressSnapshotRebuildGuard.releaseLocked` state machine을 함께 바꾸며 동일 race fixture로 검증해야 하므로 별도 subtask로 나누면 lifecycle invariant가 분산된다. 기존 `16+15_ingress_snapshot_allocation` 한 pair를 유지한다.
- predecessor `15`는 `agent-task/archive/2026/07/m-stream-evidence-gate-core/15+12_ingress_snapshot_ledger/complete.log`로 충족됐다.

### 범위 결정 근거

- 포함: ingress snapshot/guard peak arithmetic, terminal release helper, committed guard reference release, 직접 회귀와 race fixture.
- 제외: HTTP body limit+1 host wiring, Chat/Responses serializer, Recovery Coordinator/dispatcher, config/contract 변경. 각각 후속 sibling `17+13,16`, `18+14,16`, `20+17,19`의 소유이며 현재 Required를 닫는 데 필요하지 않다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`; finalizer: `finalize-task-policy.sh`; finalizer_mode: `pair`.
- build closures: scope/context/verification/evidence/ownership/decision 모두 true. 근거는 두 파일로 닫힌 public lifecycle, focused failure output, fresh unit/race 명령, 선행 15 완료, Milestone 결정 필요 없음이다.
- build route_basis: `local-fit`; grade scores: scope=1, state=2, blast=1, evidence=1, verification=1; 합계 G06; capability_gap=`none`; none floor=`none`; lane/file=`local`, `PLAN-local-G06.md`.
- loop risk: ordered transitions state_count=5(reserved/committed/cancelled/overflow/released), adverse paths=pre/post overflow·commit·cancel·close·parent close. concurrent actors=parent snapshot와 복수 guard/close goroutine, constraints=lock order·exactly-once·race-free. boundary components=2, consumers=0. structured mechanisms=없음. variant axes=overflow phase와 lifecycle transition, 조합 검증 필요=true. matched signatures=`temporal_state`, `concurrent_consistency`, `variant_product`; triggered=true.
- review closures: 모두 true. route_basis=`official-review`; grade scores: scope=1, state=2, blast=1, evidence=1, verification=1; 합계 G06; capability_gap=`none`; none floor=`none`; lane/file=`cloud`, `CODE_REVIEW-cloud-G06.md`; target=`gpt-5.6-sol` xhigh.

## 구현 체크리스트

- [x] REVIEW_INGRESS_SNAPSHOT_ALLOCATION-1 pre/post peak 계산을 overflow-safe capacity 검사로 바꾸고 최대 정수·exact-bound 회귀를 추가한다.
- [x] REVIEW_INGRESS_SNAPSHOT_ALLOCATION-2 post-peak overflow가 parent snapshot backing과 모든 guard-owned reference를 terminal하게 해제하고 sibling/normal completion이 exactly-once stable state로 끝나도록 구현·검증한다.
- [x] REVIEW_INGRESS_SNAPSHOT_ALLOCATION-3 host Go/proto 정합성, focused unit, 전체 package unit/race, gofmt/diff 검증을 fresh 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_INGRESS_SNAPSHOT_ALLOCATION-1] Overflow-safe peak capacity

- 문제: `packages/go/streamgate/ingress_snapshot.go:404-410`의 `retained + reservedTempBytes + expectedTempBytes`와 `:458-463`의 post-peak 덧셈은 합이 `int64`를 넘으면 음수로 wrap되어 max 검사를 우회한다.
- 해결 방법: 덧셈 결과를 비교하지 말고 각 값의 비음수/invariant를 확인한 뒤 `additional <= max-retained-reserved` 형태의 subtraction 기반 helper 하나로 pre/post 경로를 검사한다.

Before (`packages/go/streamgate/ingress_snapshot.go:404`):

```go
retained := s.retainedBytes()
currentPeak := retained + s.reservedTempBytes + expectedTempBytes
if currentPeak > s.maxBytes {
    return nil, ErrIngressSnapshotRebuildOverflow
}
```

After:

```go
if !fitsIngressSnapshotPeak(s.maxBytes, s.retainedBytes(), s.reservedTempBytes, expectedTempBytes) {
    return nil, ErrIngressSnapshotRebuildOverflow
}
```

- 수정 파일 및 체크리스트:
  - [ ] `ingress_snapshot.go`: 공용 pre/post checked-capacity helper와 stable overflow 반환.
  - [ ] `ingress_snapshot_test.go`: `math.MaxInt64`, exact limit, 다른 active reservation이 있는 post-peak 표.
- 테스트 작성: 작성. `TestIngressSnapshot_RebuildPeakArithmeticOverflow` table이 pre/post wraparound에서 guard/dispatch object가 없고 reservation이 0으로 복구되는지 확인한다.
- 중간 검증: `go test -count=1 ./packages/go/streamgate -run '^TestIngressSnapshot_Rebuild(PeakArithmeticOverflow|PrePeakOverflow|PostPeakOverflow)$' -v`가 PASS한다.

### [REVIEW_INGRESS_SNAPSHOT_ALLOCATION-2] Terminal ownership release

- 문제: `packages/go/streamgate/ingress_snapshot.go:463-466`은 overflow guard metadata만 비우며 parent `backings`를 유지한다. `:547-573`은 `rebuiltSnapshot`을 비우지 않아 committed guard `Close` 뒤에도 guard-owned reference가 남고, 복수 guard에서 terminal overflow가 다른 guard를 일관되게 무효화하지 않는다.
- 해결 방법: `IngressSnapshot.Close`와 overflow가 공유하는 lock-held terminal release helper로 backing/reservation을 한 번에 해제한다. overflow guard state는 `overflow`로 보존하고 sibling guard의 commit/dispatch는 closed/invalid stable error로 끝내며, 각 guard release가 예약값을 음수로 만들지 않게 한다. committed guard `Close`는 외부 caller가 가진 rebuilt snapshot을 닫지 않되 guard 내부 `rebuiltSnapshot`/temp/canonical/typed 참조는 모두 nil/zero로 만든다.

Before (`packages/go/streamgate/ingress_snapshot.go:463`):

```go
if postPeak > g.snap.maxBytes {
    g.releaseLocked(RebuildStateOverflow)
    return nil, ErrIngressSnapshotRebuildOverflow
}
```

After:

```go
if !fitsIngressSnapshotPeak(...) {
    g.releaseLocked(RebuildStateOverflow)
    g.snap.closeLocked()
    return nil, ErrIngressSnapshotRebuildOverflow
}
```

- 수정 파일 및 체크리스트:
  - [ ] `ingress_snapshot.go`: parent terminal release helper, sibling invalidation, committed guard reference drop, nonnegative exactly-once reservation accounting.
  - [ ] `ingress_snapshot_test.go`: overflow parent close/zero-retained, sibling guard stable error, committed Close reference drop, concurrent overflow/cancel/close.
- 테스트 작성: 작성. `TestIngressSnapshot_RebuildOverflowReleasesOwnedSnapshot`, `TestIngressSnapshot_RebuildOverflowInvalidatesSiblingGuard`, `TestIngressSnapshot_RebuildCommittedCloseDropsGuardReferences`, `TestIngressSnapshot_RebuildConcurrentOverflowCloseCancelRace`를 추가하거나 기존 named tests에 동등한 deterministic assertion을 통합한다.
- 중간 검증: `go test -race -count=1 ./packages/go/streamgate -run '^TestIngressSnapshot_Rebuild(OverflowReleasesOwnedSnapshot|OverflowInvalidatesSiblingGuard|CommittedCloseDropsGuardReferences|ConcurrentOverflowCloseCancelRace)$' -v`가 PASS한다.

### [REVIEW_INGRESS_SNAPSHOT_ALLOCATION-3] Fresh regression closure

- 문제: 첫 loop의 전체 suite PASS만으로 focused hard-limit/release 실패를 탐지하지 못했다.
- 해결 방법: 새 named regression, 전체 package fresh unit/race, proto no-drift, gofmt/diff를 모두 evidence로 남긴다.
- 수정 파일 및 체크리스트:
  - [ ] `ingress_snapshot.go`, `ingress_snapshot_test.go`: gofmt clean.
  - [ ] fresh unit/race stdout과 proto/diff 결과를 review stub에 기록.
- 테스트 작성: 별도 테스트 파일은 만들지 않는다. REVIEW-1/2의 named tests가 회귀를 소유한다.
- 중간 검증: `gofmt -d packages/go/streamgate/ingress_snapshot.go packages/go/streamgate/ingress_snapshot_test.go`와 `git diff --check`가 출력 없이 PASS한다.

## 의존 관계 및 구현 순서

선행 `15` 완료 근거를 유지하고 REVIEW-1 capacity helper → REVIEW-2 terminal lifecycle → REVIEW-3 fresh closure 순서로 수행한다. 다른 sibling 구현을 선행 조건으로 추가하지 않는다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/streamgate/ingress_snapshot.go` | REVIEW-1, REVIEW-2, REVIEW-3 |
| `packages/go/streamgate/ingress_snapshot_test.go` | REVIEW-1, REVIEW-2, REVIEW-3 |

## 최종 검증

- `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_Rebuild(PeakArithmeticOverflow|PrePeakOverflow|PostPeakOverflow|OverflowReleasesOwnedSnapshot|OverflowInvalidatesSiblingGuard|CommittedCloseDropsGuardReferences|CancelAndReleaseSequence|UseAfterReleaseReturnsStableError)$' -v`
- `go test -count=1 ./packages/go/streamgate`
- `go test -race -count=1 ./packages/go/streamgate`
- `gofmt -d packages/go/streamgate/ingress_snapshot.go packages/go/streamgate/ingress_snapshot_test.go`
- `git diff --check`

모든 명령은 cache 우회를 위해 `-count=1`을 유지하고, gofmt/diff/proto no-drift는 출력 없음/exit 0을 기대한다.

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