<!-- task=m-stream-evidence-gate-core/04+01,03_codec_contract_fixtures plan=0 tag=TEST -->
<!-- plan-model=GPT-5 -->


# Stream Gate Codec Contract Fixture 구현 계획

## 구현 에이전트 규칙

- Runtime이 encoded predecessor를 확인한 뒤에만 이 pair를 전달한다. 전달받은 Worker는 `agent-task/**`나 archive를 검색하지 않는다.
- 아래 두 test 파일만 수정하고 구현 체크리스트와 검증 명령을 따른다.
- review stub의 구현 소유 필드와 명령 출력만 채운다.
- `review-ready`를 보고하고 종료한다. code-review, archive, terminal file 작성, 다음 agent 시작은 금지한다.

## 배경

production contract만으로는 OpenAI provider tunnel과 agent-family runtime event가 동일 lifecycle table로 수렴하는지, host가 bounded cause를 공개 오류 하나로 평탄화하는지 증명되지 않는다. 승인된 SDD S09/S21의 closure evidence를 fake consumers와 import-boundary test로 고정해야 `event-contract` Task를 완료할 수 있다. 이 sibling은 production source를 수정하지 않는 검증 closure다.

## 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, immutable evidence/filter decision, bounded failure cause와 단일 terminal result 계약
- Completion mode: check-on-pass

## 구현 범위

- Write set:
  - `packages/go/streamgate/consumer_contract_test.go`
  - `packages/go/streamgate/package_boundary_test.go`
- `NormalizedEvent`, `BaseEventDisposition`, `FailureCauseChain`, `TerminalResult`, `ReleaseSink`를 test-only consumer에서 사용해 OpenAI/agent-family lifecycle 수렴과 single external terminal을 검증한다.
- 참고 경로: `proto/iop/runtime.proto`, `apps/node/internal/runtime/types.go`, `apps/node/internal/adapters/cli/emitter_profile_json.go`, `apps/edge/internal/openai/provider_tunnel.go`, `apps/edge/internal/openai/chat_stream_session.go`, `apps/edge/internal/openai/common_types.go`.
- SDD `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`의 `S09`, `S21`과 Milestone Task `event-contract` evidence를 닫는다.
- `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 따르며 Go 검증은 `-count=1`로 실행한다.
- Production source, Edge/Node codec, proto, config, contract 문서는 수정하지 않는다. Fake codec는 test-local adapter로만 두고 public error에는 cause, stack, trace, provider body를 추가하지 않는다.

## 구현 체크리스트

- [ ] `TEST-1` OpenAI/agent-family fake codec event/base-disposition matrix와 bounded cause를 외부 오류 하나로 commit하는 fake host fixture를 작성한다.
- [ ] `TEST-2` `packages/go/streamgate` production source의 앱 internal import 금지 테스트와 deterministic static search를 추가한다.
- [ ] 계획에 명시한 local closure 검증 명령을 fresh cache 조건으로 실행하고 stdout/stderr를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채우고 review-ready를 보고한 뒤 현재 실행을 종료한다. code-review를 실행하거나 다음 에이전트를 시작하지 않는다.

### [TEST-1] Codec lifecycle와 단일 terminal fixture

- 문제:
  - `apps/edge/internal/openai/provider_tunnel.go:189-268`과 `chat_stream_session.go:249-272`가 서로 다른 switch를 사용하지만 공통 contract로의 수렴 증거가 없다.
  - `apps/edge/internal/openai/server.go:207-209`와 `sse_writer.go:61-65`는 error를 한 번 쓰지만 bounded cause 입력을 public body에서 제외하는 fixture가 없다.
- 해결 방법:
  - test-local `fakeOpenAICodec`와 `fakeAgentCodec`를 만들고 각 raw fixture를 production predecessor의 public constructors로 변환한다.
  - response-start, text, reasoning, tool fragment, success terminal, provider/runtime error를 table-driven으로 비교한다.
  - 모든 case에서 event kind와 base disposition이 같고 provider/runtime error는 unmatched 상태에서도 terminal-error candidate인지 확인한다.
  - `fakeHostReleaseSink`가 `TerminalResult`를 한 번만 받아 현재 OpenAI-compatible `{error:{type,message}}` shape로 직렬화하고 second terminal을 거부하게 한다. cause code는 내부에 남되 public JSON에 `causes|stack|trace|provider body`가 없는지 확인한다.
- Before:

```text
packages/go/streamgate/consumer_contract_test.go does not exist
```

- After (test inventory):

```go
func TestOpenAIAndAgentCodecContractMatrix(t *testing.T)
func TestProviderErrorRetainsTerminalBaseDisposition(t *testing.T)
func TestReleaseSinkCommitsSingleExternalTerminal(t *testing.T)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/consumer_contract_test.go`: 두 fake codec, event table, fake host sink, single external error assertions
- 테스트 작성: 위 3개 fixture를 반드시 작성한다. production parser/import는 추가하지 않는다.

### [TEST-2] 공통 package import boundary

- 문제:
  - platform-common rule은 `packages/go`의 `apps/*/internal` import를 금지하지만 새 package에 이를 자동 검출하는 테스트가 없다.
- 해결 방법:
  - 표준 라이브러리 `go/parser`, `go/token`, `strconv`로 `packages/go/streamgate`의 non-test production `.go` imports를 읽고 `iop/apps/` 또는 `/internal/` 앱 경로를 거부한다.
  - proto wire 타입 import도 거부해 Core가 raw wire contract 대신 자기 typed contract만 소유하게 한다.
  - test file 자체는 fake raw shape를 선언하고 앱 internal package를 import하지 않는다.
- Before:

```text
packages/go/streamgate/package_boundary_test.go does not exist
```

- After:

```go
func TestStreamgateProductionImportsStayTransportAgnostic(t *testing.T)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/package_boundary_test.go`: production import AST 검사
- 테스트 작성: 위 static boundary test를 반드시 작성한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/consumer_contract_test.go` | TEST-1 |
| `packages/go/streamgate/package_boundary_test.go` | TEST-2 |

## 최종 검증

1. `gofmt -d packages/go/streamgate/consumer_contract_test.go packages/go/streamgate/package_boundary_test.go`
   - 기대 결과: stdout 없음
2. `go test -count=1 ./packages/go/streamgate -run 'Test(OpenAIAndAgentCodecContractMatrix|ProviderErrorRetainsTerminalBaseDisposition|ReleaseSinkCommitsSingleExternalTerminal|StreamgateProductionImportsStayTransportAgnostic)$'`
   - 기대 결과: PASS
3. `make proto`
   - 기대 결과: protobuf 생성 성공. toolchain이 없으면 차단 evidence로 기록
4. `go test -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS
5. `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. `git diff --check -- packages/go/streamgate proto/gen/iop`
   - 기대 결과: stdout 없음

검증 결과를 review stub에 기록하고 review-ready를 보고한 뒤 현재 실행을 종료한다. code-review를 실행하거나 다음 에이전트를 시작하지 않는다.
