<!-- task=m-stream-evidence-gate-core/23+22_filter_observation_recovery plan=3 tag=REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE -->

# Plan - Recovery observation 최종 결과와 통합 상관관계 폐쇄

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

구현·테스트·증거만 소유한다. 아래 검증을 정확히 실행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경·출력을 채운 뒤 active pair를 유지하고 리뷰 준비 상태를 보고한다. 차단되면 정확한 blocker, 시도한 명령/출력, 재개 조건만 구현 소유 evidence에 기록한다. 사용자 질문, user-input 도구, `USER_REVIEW.md`, 다음 상태 분류, log archive, `complete.log` 작성은 하지 않는다.

## 배경

세 번째 후속 리뷰에서 제출된 명령은 모두 통과했지만 producer의 snapshot release 실패가 최종 result와 observation을 다르게 만들고, result/concurrency/timeline fixture가 계획에 고정된 exact assertion을 충족하지 못했다. named test 존재 여부가 아니라 모든 실패 분기와 old→new attempt lifecycle을 실제 coordinator/runtime 경로에서 결정론적으로 증명해 recovery observation closure를 끝낸다.

## Archive Evidence Snapshot

- 이전 계획: `agent-task/m-stream-evidence-gate-core/23+22_filter_observation_recovery/plan_cloud_G08_2.log`
- 이전 리뷰: `agent-task/m-stream-evidence-gate-core/23+22_filter_observation_recovery/code_review_cloud_G08_2.log`
- 판정: FAIL
- 발견: Required 4, Suggested 0, Nit 0
- 핵심 문제: snapshot release failure가 최종 result에만 append됨, result matrix 분기·exact row·side-effect assertion 부족, concurrency exact order/contiguous sequence/handoff 부족, complete timeline의 attempt-2 evaluation/release correlation 부족
- 영향 파일: `packages/go/streamgate/recovery_coordinator.go`, `packages/go/streamgate/filter_observation_test.go`
- 이전 검증: Go 1.26.2에서 targeted/full package unit·race, gofmt, vet, diff는 통과했다. reviewer의 verbose result-matrix 실행은 snapshot missing, preparer unavailable/failure, rebuild/dispatch failure, success의 6개 subtest만 존재함을 확인했다.
- Roadmap carryover: 이번 follow-up은 하위 recovery observation closure이며 Milestone Task를 직접 완료하지 않으므로 `Roadmap Targets`를 두지 않는다.

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/recovery_coordinator.go`
- `packages/go/streamgate/filter_observation.go`
- `packages/go/streamgate/runtime.go`
- `packages/go/streamgate/filter_observation_test.go`
- `packages/go/streamgate/recovery_coordinator_test.go`
- `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-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-spec/index.md`
- `agent-contract/index.md`
- `agent-task/m-stream-evidence-gate-core/23+22_filter_observation_recovery/plan_cloud_G08_2.log`
- `agent-task/m-stream-evidence-gate-core/23+22_filter_observation_recovery/code_review_cloud_G08_2.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`, 상태 `[승인됨]`, 잠금 해제.
- S08 / `filter-observation`: coordinator failure와 attempt/epoch를 같은 raw-free timeline에 남긴다. 항목 1·2의 final-result/observation equality와 exact matrix를 요구한다.
- S16 / `vertical-slice`: abort→rebuild→dispatch→release→terminal 한 cycle을 실제 runtime에서 증명한다. 항목 3·4의 lifecycle order와 post-dispatch correlation을 요구한다.
- S20 / `recovery-coordinator`: abort가 끝나기 전 다른 cycle/dispatch를 허용하지 않는다. 항목 3의 abort barrier와 single dispatch를 요구한다.
- S23 / `recovery-coordinator`: preparer success/failure/cancel 뒤 snapshot을 release하고 실패는 fallback 없이 terminal로 수렴한다. 항목 1·2의 snapshot release failure outcome을 요구한다.
- Evidence Map의 raw-free one timeline, one-cycle, no-overlap, snapshot lifetime을 targeted unit/race와 full package race로 검증한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다.
- PATH host Go identity를 먼저 기록하고 fresh `-count=1` 및 `-race`를 사용한다.
- protobuf/config/wire를 변경하지 않으므로 profile의 `make proto`와 transport/full-cycle은 적용하지 않는다. repo Edge-Node 진단, 보조 E2E smoke, live provider/full-cycle도 내부 package observation producer/test 범위에는 해당 없음이다.
- 외부 runner/provider/credential/port는 필요 없다. test-rule 유지보수도 필요 없다.

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

- snapshot release가 실패하면 final result cause가 이미 emit된 plan/preparer observation에 반영되지 않는다.
- `ObservationResultMatrix`는 계획의 snapshot unexpected, invalid contributor, mismatch/reentry, deadline/invalid, abort, invalid binding 분기와 exact row/status/side-effect를 검증하지 않는다.
- `ObservationConcurrency`는 actual coordinator를 실행하지만 exact lifecycle order, 1-based contiguous sequence, old→new handoff와 host call order를 검증하지 않는다.
- complete runtime timeline은 dispatch 뒤 attempt-2 evaluation/arbitration/release correlation을 검증하지 않는다.

### 심볼 참조

- rename/제거 없음. 기존 internal helper와 테스트 fixture를 보강하며 public signature를 바꾸지 않는다.

### 분할 판단

- final result, producer observation, coordinator concurrency와 runtime attempt handoff가 하나의 timeline 불변조건을 공유하므로 한 plan으로 유지한다. 분리하면 producer와 evidence가 서로 다른 중간 상태를 PASS할 수 있다.
- subtask `23+22`의 선행 index `22`는 기존 archive evidence로 충족된 상태를 유지한다.

### 범위 결정 근거

- recovery eligibility/budget, rebuilder/dispatcher 의미, `FilterObservation` public carrier, Edge sink/storage, public OpenAI 응답, config/proto, roadmap와 agent-spec은 바꾸지 않는다.
- 기존 synthetic sequencer race는 별도 generic sequencer coverage이므로 제거를 강제하지 않되, actual coordinator evidence 대체물로 사용하지 않는다.
- `agent-spec/index.md`와 `agent-contract/index.md`에는 이 내부 observation closure에 매칭되는 별도 living spec/contract 문서가 없다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- build/review closure: scope/context/verification/evidence/ownership/decision 모두 true. capability gap 없음.
- build scores: scope=2, state/concurrency=2, blast=1, evidence=2, verification=1 → G08. review scores도 2/2/1/2/1 → G08.
- `large_indivisible_context=false`; positive loop risks=`temporal_state`, `concurrent_consistency`, `boundary_contract`, count=3.
- recovery signals: `review_rework_count=3`, `evidence_integrity_failure=true`; build base=`local-fit`, route basis=`recovery-boundary` → `PLAN-cloud-G08.md`.
- review route basis=`official-review` → `CODE_REVIEW-cloud-G08.md`.

## 구현 체크리스트

- [ ] REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-1 snapshot release를 포함한 pre-plan/preparer 최종 failure result와 exact observation을 일치시킨다.
- [ ] REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-2 계획에 고정된 모든 result 분기와 exact row/status/side-effect barrier를 실제 coordinator matrix로 검증한다.
- [ ] REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-3 abort-barrier concurrency에서 exact lifecycle order, contiguous sequence, single dispatch와 attempt handoff를 검증한다.
- [ ] REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-4 complete runtime timeline의 dispatch 이후 evaluation/arbitration/release/terminal을 새 attempt에 exact correlate한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-1] Final failure observation ordering

- 문제: `packages/go/streamgate/recovery_coordinator.go:401-405`는 deferred snapshot release failure를 이미 emit된 pre-plan row 뒤에 result에만 append한다. `packages/go/streamgate/recovery_coordinator.go:585-589`도 prepared success를 먼저 emit한 뒤 release failure를 observation 없이 반환한다.
- 해결 방법: pre-plan rejection과 preparation completion helper가 primary result를 만든 뒤 snapshot release까지 완료하고, release 실패 cause를 append한 최종 result로 observation을 정확히 한 번 emit한다. preparation 성공 뒤 release가 실패하면 prepared success를 emit하지 말고 `status=required`, `deadline_outcome=failed`, cause=`preparation_snapshot_release_failed`인 failure row로 수렴한다. deferred cleanup은 아직 finalize되지 않은 snapshot의 안전망만 담당하며 이미 emit한 result를 사후 변경하지 않는다.

Before (`packages/go/streamgate/recovery_coordinator.go:585`):

```go
emitRecoveryObs(ObservationKindRecoveryPrepared, plan, attemptID, &preparerInfo, result.FailureCauses())
if err := releasePreparationSnapshot(); err != nil {
    return result.withFailure("preparation_snapshot_release_failed"), err
}
```

After:

```go
if err := releasePreparationSnapshot(); err != nil {
    return emitPreparationFailure("preparation_snapshot_release_failed", ObservationDeadlineOutcomeFailed, err)
}
emitRecoveryObs(ObservationKindRecoveryPrepared, plan, attemptID, &preparerInfo, result.FailureCauses())
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/recovery_coordinator.go`: cleanup을 포함한 final-result-first rejection/preparation helper로 emit 순서를 정리한다.
  - [ ] `packages/go/streamgate/filter_observation_test.go`: pre-plan 및 selected-plan snapshot release failure의 exact one-row kind/cause/status와 result equality를 검증한다.
- 테스트 작성: 작성. `TestRecoveryCoordinator_ObservationResultMatrix`에 snapshot release failure variants를 포함한다.
- 중간 검증: `go test -count=1 ./packages/go/streamgate -run '^TestRecoveryCoordinator_ObservationResultMatrix$'`가 PASS해야 한다.

### [REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-2] Exhaustive actual coordinator result matrix

- 문제: `packages/go/streamgate/filter_observation_test.go:1934-1982`는 6개 case만 가지고 `:2021-2040`은 마지막 row/cause와 preparer non-nil만 확인한다. 계획에 고정된 분기, exact kind count, status/deadline, host side-effect barrier가 빠졌다.
- 해결 방법: table을 snapshot missing/unexpected, invalid contributor, unavailable/mismatch/reentry, preparer error/deadline/invalid, snapshot release, abort/rebuild/dispatch failure, invalid binding, success로 확장한다. 각 case는 expected complete lifecycle rows, final cause chain, preparer status/deadline, `PreviousAttemptClosed`, snapshot release count, abort/prepare/rebuild/dispatch call sequence와 usage를 exact assertion한다.

Before (`packages/go/streamgate/filter_observation_test.go:2021`):

```go
obss := obsSink.observations()
obs := obss[len(obss)-1]
if obs.Kind() != tt.wantKind { ... }
```

After:

```go
assertObservationRows(t, obsSink.observations(), tt.wantRows)
if got := recorder.snapshot(); !reflect.DeepEqual(got, tt.wantCalls) { ... }
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_observation_test.go`: required case table과 exact row/outcome/side-effect assertion을 구현한다.
  - [ ] `packages/go/streamgate/recovery_coordinator_test.go`: 기존 helper를 그대로 재사용하며 새 helper가 꼭 필요하지 않으면 수정하지 않는다.
- 테스트 작성: 작성. 실제 `RecoveryCoordinator.Execute`만 사용하고 synthetic emitter로 대체하지 않는다.
- 중간 검증: `go test -count=1 -v ./packages/go/streamgate -run '^TestRecoveryCoordinator_ObservationResultMatrix$'`에서 모든 named subtest가 PASS해야 한다.

### [REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-3] Exact abort-barrier concurrency evidence

- 문제: `packages/go/streamgate/filter_observation_test.go:2253-2282`는 kind count와 duplicate sequence만 검사해 exact interleaving, contiguous sequence, attempt handoff와 abort-before-dispatch 호출 순서를 증명하지 않는다.
- 해결 방법: 첫 cycle을 abort에서 block하고 두 번째 busy rejection을 받은 뒤 release한다. observation을 expected table로 `selected(old) → rejected(old, cycle_in_progress) → aborted(old) → rebuilt(old) → dispatched(new)`에 exact match하고 sequence `1..5`, correlation/config/epoch, single dispatch와 recorder `abort→rebuild→dispatch`를 확인한다.

Before (`packages/go/streamgate/filter_observation_test.go:2253`):

```go
counts := map[ObservationKind]int{}
seen := map[uint64]bool{}
```

After:

```go
want := []observationRow{selectedOld, busyRejectedOld, abortedOld, rebuiltOld, dispatchedNew}
assertObservationRows(t, obss, want)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_observation_test.go`: exact row/sequence/correlation/handoff/host order assertion을 추가한다.
- 테스트 작성: 작성. race detector에서 actual coordinator busy guard와 abort barrier를 실행한다.
- 중간 검증: `go test -race -count=1 ./packages/go/streamgate -run '^TestRecoveryCoordinator_ObservationConcurrency$'`가 PASS해야 한다.

### [REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-4] Complete post-dispatch attempt correlation

- 문제: `packages/go/streamgate/filter_observation_test.go:2146-2150`은 pre-dispatch recovery rows와 dispatched/terminal만 확인하고 dispatch 뒤 filter evaluation, arbitration, release row의 attempt를 확인하지 않는다.
- 해결 방법: complete timeline에서 dispatch 뒤 첫 started/evaluated/arbitration/release/terminal row를 exact 순서로 찾고 모두 `attempt.2`인지 검증한다. dispatch 전 evaluation/arbitration/recovery rows는 `attempt.1`을 유지하며 dispatch boundary를 기준으로 두 attempt의 row가 섞이지 않게 한다.

Before (`packages/go/streamgate/filter_observation_test.go:2149`):

```go
if obss[dispatchedIdx].AttemptID() != "attempt.2" || obss[terminalIdx].AttemptID() != "attempt.2" { ... }
```

After:

```go
for _, idx := range []int{dispatchedIdx, started2Idx, evaluated2Idx, arbitration2Idx, release2Idx, terminalIdx} {
    if obss[idx].AttemptID() != "attempt.2" { ... }
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_observation_test.go`: attempt-1/attempt-2 exact row set과 post-dispatch order를 assertion한다.
- 테스트 작성: 작성. 기존 integrated runtime fixture를 확장하고 별도 synthetic fixture를 만들지 않는다.
- 중간 검증: `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime_CompleteTimelineOrder$'`가 PASS해야 한다.

## 의존 관계 및 구현 순서

final-result producer ordering → exhaustive matrix → concurrency exact table → integrated runtime handoff → fresh package unit/race 순서로 진행한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/streamgate/recovery_coordinator.go` | REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-1 |
| `packages/go/streamgate/filter_observation_test.go` | REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-1, REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-2, REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-3, REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-4 |
| `packages/go/streamgate/recovery_coordinator_test.go` | 기존 helper 재사용 확인만; 수정 필요 시 REVIEW_REVIEW_FILTER_OBSERVATION_CLOSURE-2 |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT`
- `rg --sort path -n '^func TestRecoveryCoordinator_Observation(ResultMatrix|Concurrency)\(' packages/go/streamgate/filter_observation_test.go`
- `go test -count=1 -v ./packages/go/streamgate -run '^TestRecoveryCoordinator_ObservationResultMatrix$'`
- `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime_CompleteTimelineOrder$'`
- `go test -race -count=1 ./packages/go/streamgate -run '^TestRecoveryCoordinator_ObservationConcurrency$'`
- `go test -count=1 ./packages/go/streamgate`
- `go test -race -count=1 ./packages/go/streamgate`
- `test -z "$(gofmt -l packages/go/streamgate/recovery_coordinator.go packages/go/streamgate/filter_observation_test.go packages/go/streamgate/recovery_coordinator_test.go)"`
- `go vet ./packages/go/streamgate/`
- `git diff --check`

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