<!-- task=m-stream-evidence-gate-core/02+01_evaluation_contract plan=2 tag=REVIEW_API -->

# Stream Gate Evaluation Failure Disposition 일관성 보완 계획

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

- 아래 구현 체크리스트와 검증 명령을 완료하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 기록한다.
- active PLAN/CODE_REVIEW 파일은 그대로 두고 review-ready만 보고한다. 판정, 로그 rename, `complete.log`, task archive는 code-review 책임이다.
- 선택된 Milestone `구현 잠금 > 결정 필요`가 구현을 막을 때만 review stub의 `사용자 리뷰 요청`에 정확한 연결 근거를 기록하고 중단한다.
- 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`를 만들지 않는다. 환경/secret/서비스, 일반 범위 조정, 후속 에이전트가 닫을 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

후속 리뷰에서 stable evaluator id snapshot과 evaluated wrapper/decision id 일치는 확인됐다. 그러나 `EpochFilterOutcome.Validate()`가 outcome/enforcement와 모순되는 forged failure disposition을 받아들이고, not-applicable/deferred 회귀도 outcome kind를 직접 assert하지 않아 SDD S12의 complete outcome set과 failure policy가 아직 닫히지 않았다. constructor와 defensive validation이 같은 파생 규칙을 사용하도록 국소 보완한다.

## 사용자 리뷰 요청 흐름

선택된 Milestone `구현 잠금 > 결정 필요`만 active review stub의 `사용자 리뷰 요청`에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따르며, 구현 에이전트의 직접 사용자 질문은 금지된다. 요청 검증과 실제 `USER_REVIEW.md` 작성은 code-review가 소유한다.

## Archive Evidence Snapshot

- 이전 plan: `agent-task/m-stream-evidence-gate-core/02+01_evaluation_contract/plan_local_G03_1.log`
- 이전 review: `agent-task/m-stream-evidence-gate-core/02+01_evaluation_contract/code_review_local_G04_1.log`
- Verdict: `FAIL`
- Required:
  - `EpochFilterOutcome.Validate()`가 outcome/enforcement에서 파생될 수 없는 forged failure disposition을 거부하지 않는다.
  - not-applicable/deferred failure-policy case가 실제 kind를 만들지만 outcome kind를 assert하지 않아 이전 Required가 완결되지 않았다.
- 영향 파일: `packages/go/streamgate/evaluation_contract.go`, `packages/go/streamgate/evaluation_contract_test.go`
- 실제 검증: 정상 Go `1.24.12`와 명시적 `GOROOT`에서 focused/package/vet/proto/transport/import/diff 검증은 통과했다. 집중 probe의 `not_applicable_for_epoch + blocking + blocking_fatal` forged entry는 `Validate()`가 `nil`을 반환해 실패했다.
- Roadmap carryover: SDD S12의 complete outcome set과 failure policy 일관성을 보완하되 이 하위 계약 plan 자체는 `parallel-evaluation` 완료를 주장하지 않는다.

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/evaluation_contract.go`
- `packages/go/streamgate/evaluation_contract_test.go`
- `packages/go/streamgate/filter_contract.go`의 직접 연결 `FilterDecision`/`FilterOutcome` 정의
- `packages/go/streamgate/terminal.go`의 `StableToken` 정의
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-spec/index.md`
- `agent-contract/index.md`
- `agent-roadmap/current.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`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
- `agent-task/m-stream-evidence-gate-core/02+01_evaluation_contract/plan_local_G03_1.log`
- `agent-task/m-stream-evidence-gate-core/02+01_evaluation_contract/code_review_local_G04_1.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 `없음`
- 대상 시나리오: S12 / Milestone Task `parallel-evaluation`
- Evidence Map S12는 evaluated/deferred/not-applicable mixed outcomes, complete outcome set, failure/cancel policy를 요구한다.
- 이번 후속은 outcome kind별 failure disposition 파생과 defensive set validation을 같은 불변조건으로 묶는다. fan-out, barrier, race, backpressure, cancel evidence는 dependent `05+02_parallel_evaluation_coordinator`가 소유한다.

### 테스트 환경 규칙

- `test_env`: local
- `agent-test/local/rules.md`: 존재하며 읽었다. 로컬 기본 toolchain은 Go `1.26.2`, module language baseline은 Go `1.24`로 문서화됐다.
- 매칭 profile: `agent-test/local/platform-common-smoke.md`
- 기본 `/config/opt/go`는 binary/source 불일치로 crypto 표준 라이브러리 consumer가 compile되지 않는다.
- 정상 cached toolchain `/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64`에 `GOROOT`와 `GOTOOLCHAIN=local`을 명시하면 streamgate 및 Edge/Node transport 검증이 통과한다.
- 적용 명령: toolchain/version 확인, fresh focused/package 테스트, `go vet`, `make proto`, Edge/Node transport, stdlib-only import 확인, `gofmt -d`, scoped diff/proto status
- 외부 runner, secret, service, `<확인 필요>`는 없다.

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

- constructor failure disposition matrix: 기존 테스트가 pass/violation/fatal/evaluation-error/not-applicable/deferred disposition을 확인한다.
- defensive validation: package-local forged entry의 저장 disposition이 파생값과 다른 경우 `Validate()`와 `NewEvaluationSet` rejection 회귀가 없다.
- not-applicable/deferred: 실제 constructor는 사용하지만 `Outcome().Kind()`를 assert하지 않는다.
- stable evaluator id와 evaluated decision id consistency: 기존 후속 테스트가 커버하며 유지한다.

### 심볼 참조

- rename/remove 없음.
- 새 public symbol과 dependency 없음.
- `EpochFilterOutcome`, `NewEpochFilterOutcome`, `NewEvaluationSet`의 production caller는 아직 없고 dependent coordinator가 소비할 예정이다.

### 분할 판단

- split decision policy를 라우팅 전에 적용했다.
- shared task group: `m-stream-evidence-gate-core`
- 현재 subtask: `02+01_evaluation_contract`
- predecessor `01_event_contract_types`는 `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`로 충족됐다.
- disposition derivation/validation과 그 회귀는 같은 type, 같은 두 파일, 같은 focused test를 공유하는 하나의 contract invariant다. production/test 또는 constructor/Validate를 나누면 중복 파생 로직을 다시 만들 위험이 있어 기존 subtask의 단일 follow-up이 안전하다.
- active sibling `03`, `04`, `05`는 event test/codec/coordinator 범위를 소유하며 현재 두 파일을 수정하지 않는다.

### 범위 결정 근거

- 수정 허용: `packages/go/streamgate/evaluation_contract.go`, `packages/go/streamgate/evaluation_contract_test.go`
- 리뷰 중 바로잡은 `EpochFilterOutcome` API 주석은 유지한다.
- `filter_contract.go`, `terminal.go`, Edge/Node/proto/config/contract/spec/roadmap, local test 문서는 변경하지 않는다.
- failure policy의 제품 의미, Registry, goroutine fan-out, Arbiter, queue/backpressure는 이번 Required와 무관하므로 확장하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - scores: scope coupling 0, state/concurrency 0, blast/irreversibility 0, evidence diagnosis 0, verification complexity 1
  - result: `local`, `G01`, `PLAN-local-G01.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - scores: scope coupling 0, state/concurrency 0, blast/irreversibility 0, evidence diagnosis 0, verification complexity 1
  - result: `local`, `G01`, `CODE_REVIEW-local-G01.md`

## 구현 체크리스트

- [ ] `REVIEW_API-3` failure disposition을 outcome/enforcement에서 한 곳에서 파생하고 constructor와 `Validate()`/`NewEvaluationSet`이 같은 일관성 규칙을 강제하며 not-applicable/deferred kind+disposition 및 forged mismatch를 테스트한다.
- [ ] 계획의 전체 local 검증 명령을 정상 Go 1.24.12와 명시적 `GOROOT`에서 fresh 실행하고 실제 stdout/stderr를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_API-3] Failure disposition derivation and validation

- 문제:
  - `packages/go/streamgate/evaluation_contract.go:281-313`은 constructor 안에서만 disposition을 파생한다.
  - `packages/go/streamgate/evaluation_contract.go:427-461`의 `Validate()`는 disposition enum만 검증해 outcome/enforcement와 모순되는 package-local forged entry를 허용한다.
  - `packages/go/streamgate/evaluation_contract_test.go:376-461`의 not-applicable/deferred case는 disposition만 확인하고 outcome kind를 assert하지 않는다.
- 해결 방법:
  - unexported `deriveEvaluationFailureDisposition(FilterOutcome, FilterEnforcement) (EvaluationFailureDisposition, error)` helper로 현재 switch를 옮긴다.
  - `NewEpochFilterOutcome`은 기존 입력 검증과 evaluated filter-id 일치 검증 뒤 helper 결과를 저장한다.
  - `EpochFilterOutcome.Validate()`는 기존 outcome/enforcement/id 검증 뒤 helper로 기대 disposition을 다시 구하고 저장값과 다르면 raw 값을 포함하지 않는 안정 오류를 반환한다.
  - public API 시그니처와 현재 pass/violation/fatal/evaluation-error 의미는 바꾸지 않는다.
- Before (`evaluation_contract.go:281-313`, `evaluation_contract.go:441-460`):

```go
var disposition EvaluationFailureDisposition
switch outcome.Kind() {
// constructor-only derivation
}

if err := o.failureDisposition.Validate(); err != nil {
	return err
}
// no comparison with the derived disposition
```

- After:

```go
disposition, err := deriveEvaluationFailureDisposition(outcome, enforcement)
if err != nil {
	return EpochFilterOutcome{}, err
}

expected, err := deriveEvaluationFailureDisposition(o.outcome, o.enforcement)
if err != nil {
	return err
}
if o.failureDisposition != expected {
	return errors.New("streamgate: epoch filter outcome failure disposition mismatch")
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/evaluation_contract.go`: shared derivation helper와 constructor/Validate 일관성 gate
  - [ ] `packages/go/streamgate/evaluation_contract_test.go`: outcome kind assertion과 forged Validate/set rejection table
- 테스트 작성:
  - `TestEpochFilterOutcomeValidatesFailurePolicy`: table에 expected `FilterOutcomeKind`를 추가하고 not-applicable/deferred를 포함한 모든 case에서 `Outcome().Kind()`와 disposition을 함께 검증한다.
  - `TestEvaluationSetRejectsForgedFailureDispositionMismatch`: evaluated pass/violation, evaluation-error, not-applicable, deferred family의 대표 inconsistent disposition을 package-local로 forge해 `Validate()`와 `NewEvaluationSet`이 모두 거부하는지 검증한다.
- 중간 검증:
  - `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" test -count=1 ./packages/go/streamgate -run '^Test(EpochFilterOutcomeValidatesFailurePolicy|EvaluationSetRejectsForgedFailureDispositionMismatch)$'`
  - 기대 결과: PASS

## 의존 관계 및 구현 순서

1. predecessor `01_event_contract_types` 완료 근거: `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
2. shared derivation helper를 만든 뒤 constructor와 `Validate()`에서 함께 사용한다.
3. constructor 정상 matrix, forged validation/set rejection, not-applicable/deferred kind assertion을 같은 focused test로 닫는다.
4. 이 subtask가 PASS하고 `complete.log`를 만들기 전에는 `05+02_parallel_evaluation_coordinator` 구현을 시작하지 않는다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/evaluation_contract.go` | REVIEW_API-3 |
| `packages/go/streamgate/evaluation_contract_test.go` | REVIEW_API-3 |

## 최종 검증

1. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" version && env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" env GOROOT`
   - 기대 결과: Go `1.24.12`, `GOROOT`가 `$review_go_root`
2. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" "$review_go_root/bin/gofmt" -d packages/go/streamgate/evaluation_contract.go packages/go/streamgate/evaluation_contract_test.go`
   - 기대 결과: stdout 없음
3. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" test -count=1 ./packages/go/streamgate -run '^Test(EpochFilterOutcomeValidatesFailurePolicy|EvaluationSetRejectsForgedFailureDispositionMismatch|EpochFilterSnapshotsEvaluatorID|EpochFilterOutcomeRejectsDecisionIDMismatch|EvaluationSetRejectsForgedDecisionIDMismatch)$'`
   - 기대 결과: PASS
4. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" test -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS
5. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" vet ./packages/go/streamgate`
   - 기대 결과: PASS, stdout 없음
6. `make proto`
   - 기대 결과: 성공, proto 생성물 추가 diff 없음
7. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`
   - 기대 결과: PASS
8. `review_go_root=/config/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.12.linux-arm64; env GOROOT="$review_go_root" GOTOOLCHAIN=local "$review_go_root/bin/go" list -f '{{range .Imports}}{{println .}}{{end}}' ./packages/go/streamgate | LC_ALL=C sort`
   - 기대 결과: `context`, `errors`, `sort`, `strconv`, `strings`, `time`만 출력
9. `git diff --check -- packages/go/streamgate proto/gen/iop`
   - 기대 결과: stdout 없음
10. `git status --short -- proto/gen/iop`
   - 기대 결과: stdout 없음

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
