<!-- task=m-stream-evidence-gate-core/19+18_core_runtime_loop plan=12 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP -->

# Plan - Stale replacement range fail-closed

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

`CODE_REVIEW-cloud-G03.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 필수 마지막 단계다. 아래 검증을 실행해 실제 내용과 stdout/stderr를 기록하고 active 파일을 그대로 둔 채 리뷰 준비 완료를 보고한다. finalization, 로그 rename, `complete.log`, archive 이동은 code-review 전용이다. 차단되면 구현 소유 evidence 필드에 정확한 blocker, 시도한 명령/출력, 재개 조건만 기록한다. 사용자에게 질문하거나 user-input 도구/control-plane stop 파일을 만들거나 다음 상태를 분류하거나 로그를 archive하지 않는다.

## 배경

epoch/channel-bound replacement settlement는 다채널 state와 실제 sink progress를 보존하도록 개선됐지만, captured non-empty range가 newer overlapping release에 의해 이미 소비된 경우를 정상 empty snapshot과 구분하지 못한다. 현재 prepare/confirm은 range 안에 남은 entry가 0이면 prefix 검증을 생략하므로 stale epoch replacement가 이미 downstream에 반영된 원본을 다시 대체할 수 있다. 이 후속은 captured range width와 현재 pending prefix의 exact liveness를 검사하고 legitimate empty trigger range는 유지한다.

## Archive Evidence Snapshot

- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G08_11.log`
- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G08_11.log`
- 판정: `FAIL`
- 문제 수: Required 1, Suggested 0, Nit 0
- 문제 요약: `evidence_tail.go:2023-2025,2110-2112`가 captured range에 현재 남은 entry 수만 검사하고 0이면 검증을 생략해, newer release가 이미 소비한 non-empty old range의 replacement prepare를 허용한다.
- 영향 파일: `packages/go/streamgate/evidence_tail.go`, `packages/go/streamgate/evidence_tail_test.go`.
- 실제 검증 evidence:
  - `/config/.local/bin/go` → `/config/opt/go/bin/go`, Go `1.26.2`, `GOROOT=/config/opt/go`였다.
  - `make proto`, package build, focused/contract/package unit·race, gofmt, addition guard, import boundary, `git diff --check`는 fresh 실행에서 통과했다.
  - old epoch와 overlapping newer epoch를 만든 뒤 newer release를 전부 confirm한 임시 package probe는 `prepareReplacement accepted an epoch whose captured range was already consumed`로 실패했다. probe 파일은 제거됐다.
- 필요한 경우 위 두 log만 좁게 읽는다. `agent-task/archive/**`를 탐색하지 않는다.
- Roadmap carryover: 없음. 이 split subtask PASS만으로 Milestone 기능 Task 완료를 주장하지 않으므로 `Roadmap Targets`를 두지 않는다.

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/evidence_tail.go`
- `packages/go/streamgate/evidence_tail_test.go`
- `packages/go/streamgate/stream_release.go`
- `packages/go/streamgate/stream_release_test.go`
- `packages/go/streamgate/runtime.go`
- `packages/go/streamgate/runtime_test.go`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-roadmap/current.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/index.md`
- `agent-contract/index.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`, 상태 `[승인됨]`, SDD 잠금 `해제`.
- S01 → Milestone Task `evidence-tail`: pending range와 committed look-behind/cursor는 실제 downstream release만 반영해야 한다. 이미 소비된 old range가 replacement capability로 재사용되지 않는 assertion을 적용한다.
- S07 → Milestone Task `policy-hook`: channel-bound single buffer 상태에서 old/new epoch가 같은 pending entry를 중복 소유하지 않아야 한다. exact range liveness를 같은 channel 격리 회귀로 반영한다.
- S13 → Milestone Task `decision-arbiter`: 한 epoch의 replacement single action은 아직 pending인 captured original만 대체할 수 있다. stale action은 sink mutation 전에 fail-closed해야 한다.
- 이 plan은 해당 Milestone Task 전체 완료를 주장하지 않는다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다.
- `packages/go/**` 변경에 매칭되는 `agent-test/local/platform-common-smoke.md`를 적용해 host Go identity, `make proto`, fresh package unit/race, `git diff --check`를 사용한다.
- Edge/Node 사용자 실행 경로, wire schema, external service를 바꾸지 않으므로 별도 full-cycle/live profile은 적용하지 않는다.
- 실제 checkout/workdir `/config/workspace/iop-s0`에서 실행하고 모든 Go test에 `-count=1`을 사용한다.

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

- 기존 replacement regression은 invalid epoch/channel, zero/partial/full progress와 foreign channel을 다루지만, newer overlapping release가 old captured non-empty range를 먼저 소비한 상태를 만들지 않는다.
- exact-count 검사만 추가하면 원래부터 pending이 없던 terminal/provider-error trigger의 `[n,n)` replacement를 잘못 막을 수 있으므로 valid empty-range acceptance도 함께 고정해야 한다.
- package unit/race와 기존 multi-channel/progress/runtime tests는 일반 회귀로 유지한다.

### 심볼 참조

- rename/remove 없음.
- `prepareReplacement`, `confirmReplacement`, `rangeEntryCount`, `pendingPrefixMatchesRange`는 모두 `packages/go/streamgate` package-private다.
- public API, runtime/releaser call signature, import chain은 바꾸지 않는다.

### 분할 판단

- 기존 `19+18_core_runtime_loop` split subtask 안의 리뷰 후속이며 predecessor `18`은 이전 active plan이 명시한 `agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`로 충족됐다.
- 한 package-private invariant와 같은 direct test fixture만 수정한다. source/test를 나누면 독립 동작·검증 단위가 되지 않으므로 추가 split을 만들지 않는다.

### 범위 결정 근거

- `StreamReleaser`, `RequestRuntime`, `CommitBoundary`, recovery coordinator와 다른 channel settlement는 변경하지 않는다. fresh 검증에서 이전 다채널/progress 수정은 통과했고 finding은 EvidenceTail exact-range 검증으로 좁혀졌다.
- epoch invalidation 정책 전체를 재설계하지 않고 prepare/confirm 양쪽에서 captured range width와 prefix sequence의 현재 일치만 fail-closed 검증한다.
- `agent-roadmap`, `agent-spec`, `agent-contract`, `agent-ops`와 dispatcher-owned `WORK_LOG.md`는 수정하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh`; finalizer_mode: `pair`
- build closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`.
- build route_basis: `local-fit`; capability_gap: `none`; grade scores: `scope_coupling=0`, `state_concurrency=1`, `blast_irreversibility=0`, `evidence_diagnosis=1`, `verification_complexity=1`; lane/grade/file: `local`, `G03`, `PLAN-local-G03.md`; floor: `none`.
- review closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`.
- review route_basis: `official-review`; capability_gap: `none`; grade scores: `scope_coupling=0`, `state_concurrency=1`, `blast_irreversibility=0`, `evidence_diagnosis=1`, `verification_complexity=1`; lane/grade/file: `cloud`, `G03`, `CODE_REVIEW-cloud-G03.md`; adapter/model/effort: `codex`, `gpt-5.6-sol`, `xhigh`; floor: `none`.
- build loop-risk observations:
  - ordered_transitions: state_count=3 (`captured pending`, `overlapping newer release consumed range`, `replacement prepare rejected`), adverse_paths=`stale|duplicate|legitimate empty range`; evidence=`evidence_tail.go:2000-2025,2083-2123`과 failing probe.
  - concurrent_consistency: actor_count=1 (`serializer owner`), constraints=`package-private serialized prepare/confirm`; evidence=`stream_release.go` serializer path.
  - boundary_contract: component_count=2 (`EvidenceTail`, `StreamReleaser caller`), consumer_count=1, constraints=`exact range, fail-before-sink, valid empty trigger`.
  - structured_interpretation: mechanisms=0, hazards=[]; variant_product: independent_axis_count=2 (`empty/non-empty captured range`, `intact/consumed liveness`), combination_verification_required=true.
  - matched signatures: `temporal_state`, `variant_product`; triggered=true.
- missing_evidence: `[]`; blocked_reason: `null`.

## 구현 체크리스트

- [ ] replacement prepare/confirm이 captured sequence range의 exact width와 현재 pending prefix sequence를 모두 요구해 stale consumed non-empty range를 sink mutation 전에 거부하되 legitimate empty trigger range를 허용한다.
- [ ] EvidenceTail replacement regression에 newer overlapping full release 뒤 old epoch rejection과 valid empty-range acceptance를 추가한다.
- [ ] fresh focused/contract/package unit·race, format, guard, import boundary와 diff 검증을 통과시킨다.
- [ ] `CODE_REVIEW-cloud-G03.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1] Captured replacement range exact liveness

#### 문제

`prepareReplacement`와 `confirmReplacement`는 현재 range 안에 남은 entry 수가 0이면 prefix 검증을 생략한다.

```go
targetCount := cs.rangeEntryCount(record.snapStartSeq, record.snapEndSeq)
if targetCount > 0 && !cs.pendingPrefixMatchesRange(targetCount, record.snapStartSeq) {
    return preparedReplacement{}, errPreparedSnapshotMismatch
}
```

따라서 captured width가 1 이상이어도 newer overlapping release가 그 entry를 먼저 소비하면 `targetCount == 0`으로 stale old epoch가 통과한다. 이후 replacement payload가 내려가면 이미 downstream에 확정된 original range를 다시 대체해 중복 의미가 생긴다.

#### 해결 방법

prepare와 confirm에서 `expectedCount := snapEndSeq - snapStartSeq`를 계산하고 `rangeEntryCount`가 정확히 expected count인지 먼저 검사한다. 그 다음 `pendingPrefixMatchesRange(expectedCount, snapStartSeq)`로 prefix sequence가 연속·동일한지 확인한다. expected count가 0인 `[n,n)`은 prefix helper의 zero-length semantics로 계속 허용하므로 정상 terminal/provider-error trigger를 보존한다. 모든 검증은 replacement reservation, pending/cursor/look-behind mutation과 sink 호출 전에 끝나야 한다.

#### 수정 파일 및 체크리스트

- [ ] `packages/go/streamgate/evidence_tail.go`: prepare/confirm 양쪽에 exact captured-range liveness 검사를 적용한다.
- [ ] `packages/go/streamgate/evidence_tail_test.go`: consumed non-empty old range 거부와 legitimate empty range 허용을 검증하고 실패 시 pending/cursor/token state가 변하지 않음을 assertion한다.

#### 테스트 작성

- 확장: `TestEvidenceTailReplacementSettlement`에 old epoch를 만든 뒤 same-channel overlapping newer epoch를 full prepare/confirm해 원본을 소비하고, old `prepareReplacement`가 `errPreparedSnapshotMismatch`로 실패하며 replacement token/reservation을 만들지 않는 subtest를 추가한다.
- 확장: 처음부터 captured range가 `[n,n)`인 terminal 또는 provider-error trigger가 non-empty replacement payload를 prepare/confirm할 수 있는 subtest를 추가한다.

#### 중간 검증

- `gofmt -d packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go`: 출력 없음.
- `go test -count=1 -v ./packages/go/streamgate -run '^TestEvidenceTailReplacementSettlement$'`: 새 regression 전부 PASS.
- `go test -race -count=1 ./packages/go/streamgate -run '^TestEvidenceTailReplacementSettlement$'`: race 없이 PASS.

## 의존 관계 및 구현 순서

1. EvidenceTail prepare/confirm exact-range invariant를 같은 helper 또는 동일 조건으로 적용한다.
2. stale-consumed와 legitimate-empty 조합 회귀를 추가한다.
3. focused/contract/package unit·race와 정적 검증을 실행하고 review evidence를 채운다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/evidence_tail.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 |
| `packages/go/streamgate/evidence_tail_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT`: host Go path/version/GOROOT 기록.
- `make proto && go build ./packages/go/streamgate`: proto generation 및 package build PASS.
- `gofmt -d packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go`: 출력 없음.
- `go test -count=1 -v ./packages/go/streamgate -run '^(TestEvidenceTailReplacementSettlement|TestStreamReleaserReplacementSettlement|TestRequestRuntimeReplacementProposalRelease)$'`: fresh focused PASS.
- `go test -race -count=1 ./packages/go/streamgate -run '^(TestEvidenceTailReplacementSettlement|TestStreamReleaserReplacementSettlement|TestRequestRuntimeReplacementProposalRelease)$'`: focused race PASS.
- `go test -count=1 -v ./packages/go/streamgate -run '^(TestEvidenceTailFragmentCompletionPreservesOtherChannels|TestStreamReleaserConfirmsExactlySinkProgress|TestRequestRuntimeReplaceAttemptStagedStartIsolation|TestRequestRuntimeLifecycleMatrix)$'`: 기존 channel isolation/progress/runtime regression PASS.
- `go test -count=1 ./packages/go/streamgate`: fresh package PASS; cache 출력 불허.
- `go test -race -count=1 ./packages/go/streamgate`: fresh package race PASS.
- `! git diff -U0 -- packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go | rg '^\+' | rg 'DEBUG|, _ := New|_ = New|desc, _ :=|cause, _ :=|causes, _ :='`: 출력 없음.
- `! rg -n --sort path 'iop/apps/' packages/go/streamgate/evidence_tail.go`: 출력 없음.
- `git diff --check`: 출력 없음.

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