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

# Stream Gate Codec Contract Fixture 신뢰성 보완 계획

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

- 구현과 검증을 마친 뒤 `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는 agent codec이 OpenAI codec 구현을 그대로 재사용해 consumer 수렴을 독립적으로 검증하지 못한다. 외부 오류 검증도 private field만 가진 `ExternalDescriptor`를 직접 JSON marshal해 `{}`를 검사하므로 승인 SDD S09/S21의 closure evidence가 되지 않는다. 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_G04_0.log`
  - `agent-task/m-stream-evidence-gate-core/04+01,03_codec_contract_fixtures/code_review_local_G05_0.log`
- 판정: FAIL
- 문제 수: Required 2, Suggested 0, Nit 0
- 핵심 문제:
  - agent fake가 OpenAI fake converter를 재사용하고 success terminal을 누락해 S09 consumer 수렴 검증이 순환적이다.
  - `ExternalDescriptor` 직접 marshal 결과 `{}`만 검사하며 bounded cause와 Chat/Responses 단일 외부 오류 shape를 검증하지 않는다.
- 영향 파일: `packages/go/streamgate/consumer_contract_test.go`
- 검증 evidence:
  - 기본 Go 1.26.2는 `/config/opt/go` stdlib의 `crypto/internal/fips140cache` 누락으로 Edge/Node transport compile이 차단됐다.
  - `GOTOOLCHAIN=go1.24.12`에서는 focused streamgate, 전체 streamgate, Edge/Node transport 테스트가 모두 PASS했다. 따라서 명령 차단이 아니라 assertion 신뢰성 공백을 보완한다.
- 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-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-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 fixture가 typed normalized event와 host interface를 통해 같은 lifecycle을 통과해야 한다.
- S21 / `event-contract`: sanitized cause chain은 최대 4개로 보존되고 Chat/Responses host는 raw 내부값 없이 외부 오류 하나만 전송해야 한다.
- Evidence Map의 S09 독립 codec table과 S21 endpoint error codec 요구를 각각 `REVIEW_TEST-1`, `REVIEW_TEST-2`와 focused 검증에 직접 연결했다.

### 테스트 환경 규칙

- `test_env`: local
- 적용 규칙: `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`
- 기본 `go version`: `go1.26.2`; `GOROOT=/config/opt/go`
- 기본 toolchain은 stdlib source 불일치로 transport package compile이 실패하므로 성공 evidence로 사용하지 않는다.
- 정상 호환 toolchain `GOTOOLCHAIN=go1.24.12`와 해당 GOROOT를 확인했고 streamgate 및 Edge/Node transport 테스트를 fresh cache로 재현했다.
- 적용 명령: `make proto`, 대상 package fresh test, Edge/Node transport test, deterministic import search, `git diff --check`.
- 외부 runner/service는 사용하지 않으므로 non-local 프리플라이트는 해당 없음이다.

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

- 독립 consumer mapping: 미충족. agent fake가 OpenAI fake에 위임해 잘못된 agent mapping을 검출할 수 없다.
- lifecycle inventory: 미충족. success terminal과 독립 provider/runtime error mapping이 matrix에 없다.
- public error contract: 미충족. 실제 `{error:{type,message}}` envelope를 만들지 않는다.
- bounded internal cause/single external terminal: 미충족. retained cause를 확인하지 않고 endpoint별 output도 없다.
- package import boundary: 기존 `TestStreamgateProductionImportsStayTransportAgnostic`가 충족하며 변경하지 않는다.

### 심볼 참조

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

### 분할 판단

- 기존 split task `04+01,03_codec_contract_fixtures` 안의 FAIL follow-up이며 predecessor `01`, `03`은 각각 아래 `complete.log`로 충족됐다.
  - `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`
- 두 Required는 같은 test-local codec/sink fixture와 한 파일을 공유하고 함께 있어야 S09/S21 closure를 판정할 수 있다. 별도 child로 나누면 동일 파일 동시 편집과 불완전한 중간 evidence만 만들므로 현재 task의 단일 follow-up이 더 안전하다.

### 범위 결정 근거

- 수정 포함: `packages/go/streamgate/consumer_contract_test.go`
- 수정 제외: production source, `package_boundary_test.go`, Edge/Node codec, proto/config/contract/roadmap 문서. 현재 문제는 public API가 아니라 fixture assertion의 비공허성이다.
- 외부 OpenAI contract의 활성 shape는 바꾸지 않고 test-local serializer만 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 1, state concurrency 1, blast irreversibility 0, evidence diagnosis 2, verification complexity 1
- review result: `local`, `G05`, `CODE_REVIEW-local-G05.md`

## 구현 체크리스트

- [ ] `REVIEW_TEST-1` OpenAI provider frame과 agent runtime event를 독립 test-local raw 타입/converter로 정규화하고 response-start/text/reasoning/tool/success terminal/provider·runtime error의 kind, disposition, typed payload를 비교한다.
- [ ] `REVIEW_TEST-2` descriptor accessor 기반 Chat/Responses error codec fixture로 bounded latest-four cause 보존, 정확한 `{error:{type,message}}` 공개 shape, raw field 부재, terminal 단일 emission과 second commit 거부를 검증한다.
- [ ] `GOTOOLCHAIN=go1.24.12`로 focused/full streamgate와 Edge/Node transport 회귀를 fresh cache로 실행하고 proto/import/diff 검증 결과를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_TEST-1] 독립 codec lifecycle fixture

- 문제: `packages/go/streamgate/consumer_contract_test.go:59-64`의 agent fake가 OpenAI fake converter를 호출하고, `:96-106` matrix에 success terminal/error가 없다. 같은 kind를 다시 공통 lookup에 넣는 비교는 producer별 mapping 오류를 검출하지 못한다.
- 해결 방법:

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

```go
func (c *fakeAgentCodec) toNormalizedEvent() (streamgate.NormalizedEvent, error) {
	return c.toOpenAIConverter().toNormalizedEvent()
}
```

After:

```go
type fakeOpenAIProviderFrame struct { /* provider-frame fields */ }
type fakeAgentRuntimeEvent struct { /* runtime-event fields */ }

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

- 두 converter는 공통 fake converter를 호출하지 않고 각 raw enum/payload를 production `streamgate.New*Event` constructor에 독립 매핑한다.
- table은 여섯 event kind를 모두 포함하고 `Kind()`, event 자체의 `Disposition()`, `Validate()`와 kind별 accessor payload를 비교한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/consumer_contract_test.go`: 독립 raw fixture/converter와 전체 lifecycle matrix
- 테스트 작성:
  - `TestOpenAIAndAgentCodecContractMatrix`: 여섯 lifecycle kind와 typed payload 동등성
  - `TestProviderErrorRetainsTerminalBaseDisposition`: 독립 provider/runtime error가 모두 terminal-error candidate이며 `AsProviderError()`가 유효함
- 중간 검증:
  - `GOTOOLCHAIN=go1.24.12 go test -count=1 ./packages/go/streamgate -run 'Test(OpenAIAndAgentCodecContractMatrix|ProviderErrorRetainsTerminalBaseDisposition)$'`
  - 기대 결과: PASS

### [REVIEW_TEST-2] endpoint별 single terminal error fixture

- 문제: `packages/go/streamgate/consumer_contract_test.go:235-246`은 private field만 가진 `ExternalDescriptor`를 직접 marshal해 `{}`를 얻고 forbidden substring만 확인한다. S21의 bounded cause와 Chat/Responses 공개 오류 shape를 실행하지 않는다.
- 해결 방법:

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

```go
jsonBytes, err := json.Marshal(extDesc)
forbidden := []string{"causes", "stack", "trace", "provider"}
```

After:

```go
type fakeErrorEnvelope struct {
	Error struct {
		Type    string `json:"type"`
		Message string `json:"message"`
	} `json:"error"`
}
```

- fake host는 `TerminalResult.ExternalDesc()` accessor로만 Chat SSE와 Responses JSON payload를 만들고 emitted terminal 수를 별도로 기록한다.
- chain은 5개 append 입력에서 latest 4만 남는지와 `repetition_loop_detected`, `recovery_notice_translation_failed` code 보존을 내부에서 확인한다.
- public payload는 정확히 `error.type`/`error.message`만 가지며 `causes`, `stack`, `trace`, provider body/token이 없음을 decoded key 기준으로 확인한다.
- first commit 뒤 output이 하나이고 second commit이 error를 반환해도 output count/body가 바뀌지 않는지 검증한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/consumer_contract_test.go`: `ReleaseSink` compile-time assertion, endpoint codec와 terminal/cause assertion
- 테스트 작성:
  - `TestReleaseSinkCommitsSingleExternalTerminal`: Chat/Responses table, exact envelope, bounded internal causes, duplicate rejection
- 중간 검증:
  - `GOTOOLCHAIN=go1.24.12 go test -count=1 ./packages/go/streamgate -run '^TestReleaseSinkCommitsSingleExternalTerminal$'`
  - 기대 결과: PASS

## 수정 파일 요약

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

## 최종 검증

1. `gofmt -d packages/go/streamgate/consumer_contract_test.go`
   - 기대 결과: stdout 없음
2. `GOTOOLCHAIN=go1.24.12 go test -count=1 ./packages/go/streamgate -run 'Test(OpenAIAndAgentCodecContractMatrix|ProviderErrorRetainsTerminalBaseDisposition|ReleaseSinkCommitsSingleExternalTerminal|StreamgateProductionImportsStayTransportAgnostic)$'`
   - 기대 결과: 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. `git diff --check -- packages/go/streamgate proto/gen/iop`
   - 기대 결과: stdout 없음

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