<!-- task=m-stream-evidence-gate-core/04+01,03_codec_contract_fixtures plan=2 tag=REVIEW_REVIEW_TEST -->

# Stream Gate Codec Contract Fixture 독립성·Endpoint 증거 보완 계획

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

- 구현과 검증을 마친 뒤 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 출력으로 채우고, active 파일을 그대로 둔 채 review-ready를 보고한다.
- 종결 판정, log rename, `complete.log`, task archive는 code-review 전용이다.
- 선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 막힌 경우에만 review stub의 `사용자 리뷰 요청`에 정확한 연결 대상과 evidence를 기록하고 멈춘다.
- 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`를 만들지 않는다. 환경/secret/service 차단, 일반 범위 변경, 후속 에이전트가 보완할 수 있는 evidence 공백은 사용자 리뷰 사유가 아니다.

## 배경

현재 fixture는 테스트는 통과하지만 두 consumer가 production event kind를 raw kind로 공유하고 event 자체 disposition을 확인하지 않아 SDD S09의 독립 codec evidence가 되지 않는다. terminal fixture도 필수 cause 하나를 cap에서 제거하고 Chat/Responses가 같은 JSON formatter를 사용해 S21의 endpoint별 단일 외부 오류를 증명하지 못한다. production 계약은 유지하고 test-local fixture와 assertion만 보완한다.

## 사용자 리뷰 요청 흐름

선택된 Milestone lock 결정만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review stub에 기록한다. 구현 중 직접 사용자 prompt는 금지하며, code-review가 요청의 정당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다.

## Archive Evidence Snapshot

- 이전 loop evidence:
  - `agent-task/m-stream-evidence-gate-core/04+01,03_codec_contract_fixtures/plan_local_G05_1.log`
  - `agent-task/m-stream-evidence-gate-core/04+01,03_codec_contract_fixtures/code_review_local_G05_1.log`
- 판정: FAIL
- 문제 수: Required 2, Suggested 0, Nit 0
- 핵심 문제:
  - 두 raw fixture가 production `EventKind`를 공유하고 `BaseDispositionOf(event.Kind())`끼리 비교해 raw mapping, 실제 disposition과 전체 typed payload를 독립 검증하지 않는다.
  - latest-four 입력에서 `repetition_loop_detected`가 제거되고 Chat/Responses case가 같은 JSON formatter를 사용해 endpoint별 공개 오류 framing을 검증하지 않는다.
- 영향 파일: `packages/go/streamgate/consumer_contract_test.go`
- 검증 evidence:
  - Go 1.24.12에서 focused/full streamgate, Edge/Node transport, proto, import boundary, gofmt/diff 검증은 PASS했다.
  - PASS가 assertion 공백 때문에 S09/S21 계약 충족을 증명하지 못하는 상태다.
- Roadmap carryover: `event-contract`의 S09/S21 evidence를 같은 task에서 완결한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
- Task ids:
  - `event-contract`: normalized event/base disposition, bounded failure cause와 endpoint별 single terminal result 계약
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/consumer_contract_test.go`
- `packages/go/streamgate/package_boundary_test.go`
- `packages/go/streamgate/event.go`
- `packages/go/streamgate/terminal.go`
- `packages/go/streamgate/event_test.go`
- `packages/go/streamgate/terminal_test.go`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.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-contract/outer/openai-compatible-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/03+01_event_contract_unit_tests/complete.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`
- S09 / `event-contract`: 서로 다른 OpenAI/agent-family raw event가 typed normalized event와 host interface로 같은 lifecycle을 통과해야 한다.
- S21 / `event-contract`: cause chain은 latest four sanitized code를 보존하고 Chat/Responses host는 raw 내부값 없이 endpoint별 오류 하나만 전송해야 한다.
- Evidence Map의 S09 독립 codec table을 `REVIEW_REVIEW_TEST-1`에, S21 endpoint-specific single-error fixture를 `REVIEW_REVIEW_TEST-2`에 직접 연결한다.

### 테스트 환경 규칙

- `test_env`: local
- 적용 규칙: `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`
- 기본 Go는 `go1.26.2`, `GOROOT=/config/opt/go`지만 stdlib source 불일치가 있으므로 성공 evidence에는 module baseline과 호환되는 `GOTOOLCHAIN=go1.24.12` 및 해당 GOROOT를 사용한다.
- 적용 명령: `make proto`, fresh focused/full package test, Edge/Node transport test, deterministic import search, `go vet`, gofmt/diff check.
- 외부 runner/service/secret은 사용하지 않는다.

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

- distinct raw mapping: 미충족. raw kind가 두 fixture 모두 production `EventKind`다.
- event disposition/typed payload: 미충족. `BaseDispositionOf(kind)`만 비교하며 response-start, terminal, provider-error accessor를 matrix가 보지 않는다.
- bounded cause: 미충족. `repetition_loop_detected`가 cap에서 제거된다.
- endpoint error codec: 미충족. Chat/Responses가 같은 JSON formatter를 사용하고 SSE framing을 만들지 않는다.
- production import boundary와 public constructor 자체의 단위 테스트는 기존 테스트로 충족한다.

### 심볼 참조

- production symbol rename/remove: 없음
- 새 dependency: 없음

### 분할 판단

- 기존 split task `04+01,03_codec_contract_fixtures`의 FAIL follow-up이다.
- predecessor `01`은 `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`, predecessor `03`은 `agent-task/archive/2026/07/m-stream-evidence-gate-core/03+01_event_contract_unit_tests/complete.log`로 충족됐다.
- 두 Required는 같은 test-local consumer/sink fixture와 한 파일을 공유하고 함께 통과해야 `event-contract` S09/S21 closure가 된다. 별도 task로 나누면 동일 파일 동시 편집과 불완전한 중간 evidence를 만들므로 단일 follow-up을 유지한다.

### 범위 결정 근거

- 수정 포함: `packages/go/streamgate/consumer_contract_test.go`
- 수정 제외: production source, `package_boundary_test.go`, Edge/Node codec, proto/config, contract/roadmap/spec 문서. 현재 결함은 public API가 아니라 fixture assertion의 독립성과 endpoint fidelity다.
- 외부 OpenAI-compatible 계약을 바꾸지 않고 test-local serializer만 descriptor accessor 기반으로 모델링한다.

### 최종 라우팅

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

## 구현 체크리스트

- [ ] `REVIEW_REVIEW_TEST-1` OpenAI/agent용 distinct raw kind enum과 독립 converter를 만들고 모든 lifecycle 결과를 expected kind, 실제 `Disposition()`, `Validate()`와 kind별 typed accessor payload에 대조한다.
- [ ] `REVIEW_REVIEW_TEST-2` latest-four에 두 필수 cause를 함께 보존하고 별도 Chat SSE/Responses JSON codec fixture로 정확한 public framing/field set, error emission 1회와 second commit 불변을 검증한다.
- [ ] `GOTOOLCHAIN=go1.24.12`로 focused/full streamgate와 Edge/Node transport 회귀를 fresh 실행하고 proto/import/vet/gofmt/diff 검증 결과를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_TEST-1] Distinct raw codec lifecycle matrix

- 문제: `packages/go/streamgate/consumer_contract_test.go:18-19`와 `:36-37`은 서로 다른 fixture 이름에도 `streamgate.EventKind`를 raw kind로 공유한다. `:295-304`는 event의 저장된 disposition 대신 같은 production lookup 결과를 비교하고 `:307-345`는 세 payload만 본다.
- 해결 방법:

Before (`packages/go/streamgate/consumer_contract_test.go:18`):

```go
type fakeOpenAIProviderFrame struct {
	kind streamgate.EventKind
}
type fakeAgentRuntimeEvent struct {
	kind streamgate.EventKind
}
```

After:

```go
type fakeOpenAIRawKind string
type fakeAgentRawKind string

func normalizeOpenAIFrame(fakeOpenAIProviderFrame) (streamgate.NormalizedEvent, error)
func normalizeAgentEvent(fakeAgentRuntimeEvent) (streamgate.NormalizedEvent, error)
```

- 두 raw enum은 서로와 production enum을 공유하지 않고 converter마다 자기 상수에서 production constructor로 매핑한다.
- table은 raw kind 두 개와 explicit expected normalized kind/disposition을 가지며 각 결과를 expected 값에 독립 대조한다.
- response-start는 status/header, text/reasoning/tool은 payload, success terminal은 `AsTerminal().Success()`, provider/runtime error는 `AsProviderError()`의 descriptor와 terminal-error disposition을 확인한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/consumer_contract_test.go`: distinct raw enum/converter와 전체 typed lifecycle assertion
- 테스트 작성:
  - 기존 `TestOpenAIAndAgentCodecContractMatrix`를 강화하고 `TestProviderErrorRetainsTerminalBaseDisposition`이 event `Disposition()`과 family별 descriptor를 직접 검증하게 한다.
- 중간 검증:
  - `GOTOOLCHAIN=go1.24.12 go test -count=1 -v ./packages/go/streamgate -run '^Test(OpenAIAndAgentCodecContractMatrix|ProviderErrorRetainsTerminalBaseDisposition)$'`
  - 기대 결과: 모든 lifecycle subtest PASS

### [REVIEW_REVIEW_TEST-2] Endpoint-specific single terminal codec

- 문제: `packages/go/streamgate/consumer_contract_test.go:449`은 `repetition_loop_detected`를 가장 오래된 cause로 넣어 cap에서 제거한다. `:489-598`은 unused endpoint metadata만 다르고 두 case 모두 같은 `formatErrorEnvelope`를 사용한다.
- 해결 방법:

Before (`packages/go/streamgate/consumer_contract_test.go:449`):

```go
codes := []string{"repetition_loop_detected", "filter_blocked", "validation_failed", "cache_miss", "recovery_notice_translation_failed"}
```

After:

```go
codes := []string{"discarded_prelude", "repetition_loop_detected", "filter_blocked", "validation_failed", "recovery_notice_translation_failed"}

func encodeChatSSEError(streamgate.TerminalResult) (string, error)
func encodeResponsesJSONError(streamgate.TerminalResult) (string, error)
```

- exact latest-four code 순서를 검사해 `repetition_loop_detected`와 `recovery_notice_translation_failed`가 모두 남는지 확인한다.
- Chat codec은 descriptor accessor에서 만든 envelope를 SSE `data` 한 번과 `[DONE]`으로 framing하고, Responses codec은 JSON envelope 하나를 만든다.
- 각 endpoint는 exact decoded key/value, raw 내부 field 부재, error emission 1회, second commit error와 count/body/state 불변을 검증한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/consumer_contract_test.go`: endpoint codec fixture, cause/order, terminal emission assertions
- 테스트 작성:
  - 기존 `TestReleaseSinkCommitsSingleExternalTerminal`을 Chat SSE/Responses JSON별 exact public output fixture로 강화한다.
- 중간 검증:
  - `GOTOOLCHAIN=go1.24.12 go test -count=1 -v ./packages/go/streamgate -run '^TestReleaseSinkCommitsSingleExternalTerminal$'`
  - 기대 결과: 두 endpoint subtest PASS

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/consumer_contract_test.go` | REVIEW_REVIEW_TEST-1, REVIEW_REVIEW_TEST-2 |

## 최종 검증

1. `gofmt -d packages/go/streamgate/consumer_contract_test.go`
   - 기대 결과: stdout 없음
2. `GOTOOLCHAIN=go1.24.12 go test -count=1 -v ./packages/go/streamgate -run 'Test(OpenAIAndAgentCodecContractMatrix|ProviderErrorRetainsTerminalBaseDisposition|ReleaseSinkCommitsSingleExternalTerminal|StreamgateProductionImportsStayTransportAgnostic)$'`
   - 기대 결과: 모든 대상/subtest PASS
3. `make proto`
   - 기대 결과: protobuf 생성 성공, `proto/gen/iop` 내용 변경 없음
4. `GOTOOLCHAIN=go1.24.12 go test -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS
5. `GOTOOLCHAIN=go1.24.12 go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`
   - 기대 결과: PASS
6. `if rg --sort path -n '"iop/(apps/.*/internal|proto/gen/iop)"' packages/go/streamgate --glob '*.go' --glob '!**/*_test.go'; then exit 1; fi`
   - 기대 결과: stdout 없음, exit 0
7. `GOTOOLCHAIN=go1.24.12 go vet ./packages/go/streamgate`
   - 기대 결과: stdout 없음, exit 0
8. `git diff --check -- packages/go/streamgate proto/gen/iop`
   - 기대 결과: stdout 없음, exit 0

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