<!-- task=m-stream-evidence-gate-core/22+21_filter_observation_runtime plan=1 tag=REVIEW_FILTER_OBSERVATION_RUNTIME -->

# Plan - Filter observation runtime 검증 완결

## 배경

직전 구현은 observation hook과 request-local sequence를 연결했지만, 계획이 요구한 다중 filter 완료 상관관계와 lifecycle의 정확한 순서·횟수를 테스트로 고정하지 못했다. production 동작은 유지하고, ready/non-ready epoch를 함께 사용하는 결정론적 fixture로 SDD S08/S12의 검증 공백만 닫는다.

## Archive Evidence Snapshot

- 직전 계획 증거: `agent-task/m-stream-evidence-gate-core/22+21_filter_observation_runtime/plan_local_G08_0.log`
- 직전 리뷰 증거: `agent-task/m-stream-evidence-gate-core/22+21_filter_observation_runtime/code_review_cloud_G08_0.log`
- 판정: FAIL (`Required=1`, `Suggested=0`, `Nit=0`)
- Required finding: 단일-filter kind 존재 검사는 parallel completion correlation, lifecycle의 정확한 순서·횟수, all-complete 이후 arbitration을 보장하지 못하며, `Applies=false` fixture는 epoch의 unsubscribed/trigger-unready suppression 경로를 직접 실행하지 않는다.
- 영향 파일: `packages/go/streamgate/filter_observation_test.go`
- fresh 검증: `go test -count=1 ./packages/go/streamgate`와 `go test -race -count=1 ./packages/go/streamgate`는 통과했고 `git diff --check`는 clean이었으나, 위 테스트 완결성 공백은 남아 있다.
- 로드맵 계승: `Stream Evidence Gate Core` Milestone은 진행 중이고 SDD는 승인·잠금 해제 상태다. 본 후속은 S08 filter-observation timeline과 S12 parallel all-complete 증거만 보강하며 roadmap checkbox를 직접 변경하지 않는다.

## 분석 결과

### 읽은 파일과 근거

- 직전 active pair와 위 두 archive evidence를 확인했다.
- `packages/go/streamgate/parallel_evaluation.go`, `runtime.go`, `filter_observation.go`, `filter_observation_test.go`, `parallel_evaluation_test.go`, `runtime_test.go`, `evaluation_contract.go`, `filter_registry.go`를 확인했다.
- `agent-roadmap/milestones/stream-evidence-gate-core.md`와 해당 SDD의 S08/S12/S16 기준을 확인했다.
- 선행 task 21의 `agent-task/archive/2026/07/m-stream-evidence-gate-core/21+14,19_filter_observation_contract/complete.log`에서 완료 의존성을 확인했다.

### SDD 기준

- S08: 단일 request-local sequence에서 multi-consumer/filter observation의 lifecycle, attribution, epoch 상관관계를 재생 가능한 형태로 검증해야 한다.
- S12: ready filter의 parallel evaluation은 전체 outcome이 모인 뒤에만 arbitration으로 진행하고, deferred/not-applicable filter는 evaluation observation을 만들지 않아야 한다.
- S16 production vertical slice는 이미 연결되어 있으므로 이번 후속은 해당 동작을 고정하는 test evidence만 소유한다.

### 테스트 환경과 검증 정책

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 적용한다.
- proto/wire 변경이 없으므로 proto setup과 Edge/Node transport 검증은 적용 대상이 아니다.
- Go toolchain provenance, focused test, package unit/race, gofmt, go vet, diff 검증을 fresh 실행한다. 캐시 결과는 증거로 사용하지 않는다.

### 리뷰 공백

- `filter_observation_test.go:1275-1367`은 sequence와 correlation을 확인하지만 lifecycle kind는 존재 여부만 검사해 순서 변경이나 중복 emit을 놓친다.
- `filter_observation_test.go:1376-1385`의 `Applies=false` filter는 request resolve 단계에서 제거되어 `GateCoordinator.Evaluate`의 non-ready `EpochFilter` 경로를 증명하지 않는다.
- 단일 filter fixture는 completion 순서를 제어할 수 없어 parallel all-complete barrier와 observation correlation의 결합을 증명하지 않는다.

### 심볼·분할·범위 판단

- rename/remove된 public symbol은 없다.
- 정확한 timeline과 non-ready suppression은 같은 observation evidence invariant이므로 하나의 compact 후속으로 유지한다.
- production `.go` 변경, recovery observation(task 23), Edge sink/task 24·25는 범위에서 제외한다.

### 최종 라우팅

- 최종 라우팅 스크립트를 한 번 실행했다.
- `build_route_basis=risk-boundary`, `build_lane=cloud`, `build_grade=G05`, `build_filename=PLAN-cloud-G05.md`.
- `review_route_basis=official-review`, `review_lane=cloud`, `review_grade=G05`, `review_filename=CODE_REVIEW-cloud-G05.md`.
- `review_adapter=codex`, `review_model=gpt-5.6-sol`, `review_reasoning_effort=xhigh`.
- positive signatures: `temporal_state`, `concurrent_consistency`, `boundary_contract`; `large_indivisible_context=false`.
- lifecycle/test/routing closures는 모두 충족했고 capability gap은 없다.

## 구현 체크리스트

- [x] REVIEW_FILTER_OBSERVATION_RUNTIME-1 parallel completion lifecycle 순서·횟수와 all-complete correlation fixture를 추가한다.
- [x] REVIEW_FILTER_OBSERVATION_RUNTIME-2 unsubscribed/trigger-unready `EpochFilter`의 evaluation observation suppression fixture를 추가한다.
- [x] REVIEW_FILTER_OBSERVATION_RUNTIME-3 focused/unit/race/gofmt/vet/diff 검증을 수행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_FILTER_OBSERVATION_RUNTIME-1] parallel completion과 정확한 lifecycle

- 수정 파일: `packages/go/streamgate/filter_observation_test.go`
- Before (`filter_observation_test.go:1335-1352`):

```go
// 2. Verify lifecycle kinds presence.
var kinds []ObservationKind
for _, obs := range obss {
	kinds = append(kinds, obs.Kind())
}

hasKind := func(k ObservationKind) bool {
	for _, kind := range kinds {
		if kind == k {
			return true
		}
	}
	return false
}

if !hasKind(ObservationKindResponseStaged) {
	t.Errorf("missing response_staged observation in %v", kinds)
}
```

- 해결 형태:
  - ready filter를 2개 이상 구성하고 독립 channel로 완료 순서를 제어한다.
  - 두 ready outcome이 모두 완료되기 전 arbiter가 호출되지 않으며, 완료 뒤 정확히 한 번 호출되는지 단언한다.
  - observation을 expected row(`kind`, `filterID`, `epochID`, correlation fields)와 위치별로 비교해 start/evaluated/lifecycle 순서와 종류별 정확한 횟수를 고정한다.
  - `response_staged`, `arbitration_decided`, `release_committed`, `terminal_committed`가 action당 한 번인지 명시적으로 단언한다.
- 완료 기준: kind 존재 검사만으로 통과할 수 없고, 순서 변경·중복·누락·상관관계 오염 중 하나라도 생기면 테스트가 실패한다.

### [REVIEW_FILTER_OBSERVATION_RUNTIME-2] non-ready epoch suppression

- 수정 파일: `packages/go/streamgate/filter_observation_test.go`
- Before (`filter_observation_test.go:1376-1385`):

```go
fEvaluated := &customMockFilter{id: "filter.evaluated"}
fUnready := &customMockFilter{
	id: "filter.unready",
	appliesFn: func(c FilterContext) bool {
		return false // not applicable for request/attempt context
	},
}

reg1, _ := NewFilterRegistration(fEvaluated, "cap.alpha", true, FilterEnforcementBlocking, 5*time.Second, 10)
reg2, _ := NewFilterRegistration(fUnready, "cap.alpha", true, FilterEnforcementBlocking, 5*time.Second, 20)
```

- 해결 형태:
  - `subscribed=false`와 `triggerReady=false`인 `EpochFilter`를 ready filter와 함께 `GateCoordinator.Evaluate`에 직접 전달한다.
  - complete `EvaluationSet`에는 not-applicable/deferred outcome이 남고, observation에는 ready filter의 start/evaluated만 존재함을 단언한다.
  - non-ready filter ID의 `filter_evaluation_started`/`filter_evaluated`가 하나도 없음을 exact count로 확인한다.
- 완료 기준: request registration exclusion이 아니라 실제 non-ready epoch normalization 경로가 실행되고 suppression을 증명한다.

### [REVIEW_FILTER_OBSERVATION_RUNTIME-3] 검증 증거

- production 코드나 unrelated test는 수정하지 않는다.
- 모든 명령은 구현 완료 뒤 fresh 실행하고 stdout/stderr를 review stub에 기록한다.
- `go test -race` 실패나 flaky timing 의존성이 있으면 완료로 표시하지 않는다.

## 의존 관계 및 구현 순서

선행 task 21 완료 → REVIEW_FILTER_OBSERVATION_RUNTIME-1/2 fixture 보강 → REVIEW_FILTER_OBSERVATION_RUNTIME-3 fresh 검증 → review stub evidence 작성.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/streamgate/filter_observation_test.go` | REVIEW_FILTER_OBSERVATION_RUNTIME-1, REVIEW_FILTER_OBSERVATION_RUNTIME-2 |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT`
- `go test -count=1 ./packages/go/streamgate -run '^(TestRuntimeObservation_LifecycleOrderAndCorrelation|TestRuntimeObservation_DeltaSuppression|TestGateCoordinatorObservation_ParallelCompletionAndDeltaSuppression)$'`
- `go test -count=1 ./packages/go/streamgate`
- `go test -race -count=1 ./packages/go/streamgate`
- `test -z "$(gofmt -l packages/go/streamgate/filter_observation_test.go)"`
- `go vet ./packages/go/streamgate/`
- `git diff --check`

기대 결과는 모든 Go 명령 exit 0, gofmt 대상 없음, diff check clean이다.

## 리뷰어를 위한 체크포인트

- observation row의 정확한 순서와 종류별 횟수가 단언되어 lifecycle 중복/재정렬을 검출하는가.
- 완료 순서를 제어한 ready filter들이 모두 끝나기 전에 arbitration이 발생하지 않으며 correlation/epoch attribution이 보존되는가.
- unsubscribed와 trigger-unready epoch filter는 outcome set에는 남지만 start/evaluated observation에는 나타나지 않는가.

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

구현, 테스트, evidence 작성만 소유한다. active plan/review pair의 archive, `complete.log`, roadmap 상태 변경과 최종 판정은 공식 리뷰어/runtime 소유다. 체크리스트 항목과 review stub의 구현 에이전트 소유 섹션을 모두 채운 뒤 active pair를 유지하고 멈춘다.
