<!-- task=m-external-integration/01_iop_responses_surface plan=1 tag=REVIEW_IOP_RESP -->

# Plan: Review Follow-up - IOP Responses Flat Metadata Contract

## 작업 범위

이 follow-up은 `code_review_cloud_G07_0.log`의 Required 이슈만 닫는다. sibling IOP repo의 `/v1/responses` metadata parser가 NomadCode Core의 실제 flat `metadata` request shape를 받아들이도록 고치고, unit/smoke 검증이 그 shape를 직접 덮도록 보강한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/external-integration/milestones/external-integration.md`
- 완료 대상 Task id: 없음. 이 subtask는 선행 IOP 표면 구현이며, 최종 `[iop-responses]` 체크는 `02+01_nomadcode_core_iop_smoke`에서 원격 Core enqueue smoke까지 통과한 뒤 주장한다.

## 범위 결정 근거

- NomadCode Core는 `services/core/internal/adapters/openai/client.go`에서 `Metadata map[string]string`를 그대로 Responses `metadata` field로 보낸다.
- Scheduler는 현재 `task_id`, `source` flat keys를 채운다.
- IOP 구현은 structured `metadata.nomadcode.task_id/source`만 읽고 있어, NomadCode가 보내는 metadata가 service boundary에서 누락된다.
- Core request shape를 다시 바꾸기보다, IOP가 기존 structured metadata와 NomadCode flat alias를 함께 수용하는 것이 cross-project 의존성을 작게 푼다.

## 구현 계획

1. `/config/workspace/iop`의 작업트리를 먼저 확인하고 unrelated 변경은 건드리지 않는다.
2. IOP `apps/edge/internal/openai`의 Responses metadata parsing을 수정한다.
3. `metadata.task_id`와 `metadata.source`가 string으로 들어오면 각각 `nomadcode.task_id`, `nomadcode.source` run metadata로 매핑한다.
4. 기존 structured `metadata.nomadcode.task_id/source`는 계속 지원한다. flat과 structured가 동시에 있으면 structured value가 우선하고 flat value는 fallback으로만 사용한다.
5. IOP-owned `source=openai-responses` key는 유지한다. NomadCode task source는 `nomadcode.source`로만 전달해 key 충돌을 피한다.
6. IOP server test에 flat metadata payload가 `SubmitRunRequest.Metadata`까지 전달되는 regression test를 추가한다.
7. `scripts/e2e-openai-ollama.sh` 또는 동등 smoke의 Responses payload가 flat NomadCode metadata shape를 최소 1회 사용하도록 보강한다.
8. 필요하면 `apps/edge/README.md`의 Responses metadata 설명에 flat compatibility alias를 짧게 반영한다.

## 구현 체크리스트

- [ ] IOP metadata parser accepts flat `metadata.task_id` and `metadata.source` as NomadCode compatibility aliases without requiring NomadCode Core changes.
- [ ] Structured `metadata.nomadcode.task_id` and `metadata.nomadcode.source` remain supported, with structured values taking precedence over flat aliases.
- [ ] IOP server tests assert flat metadata reaches `SubmitRunRequest.Metadata` as `nomadcode.task_id` and `nomadcode.source` while IOP-owned `source=openai-responses` remains unchanged.
- [ ] Responses smoke path or equivalent listener smoke uses flat NomadCode metadata at least once.
- [ ] Existing IOP Responses/chat/model tests and NomadCode client/scheduler tests are rerun from the correct module roots with actual stdout/stderr recorded.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 검증 계획

- `cd /config/workspace/iop && go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service`
- `cd /config/workspace/iop && go test -count=1 ./apps/edge/...`
- `cd /config/workspace/iop && ./scripts/e2e-openai-ollama.sh`
- `cd /config/workspace/nomadcode/services/core && go test -count=1 ./internal/adapters/openai ./internal/scheduler`

## 위험과 주의점

- 이 follow-up은 IOP surface contract 수정만 다룬다. NomadCode Core remote enqueue smoke는 dependent subtask에서 진행한다.
- `metadata.source`를 그대로 `source` run metadata로 넣으면 IOP-owned `source=openai-responses`와 충돌한다. task source는 `nomadcode.source`로 분리한다.
- top-level `metadata.cli` 거절 정책은 유지한다.
- secret/token 원문은 테스트 payload, 로그, docs에 남기지 않는다.
