<!-- task=m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress plan=0 tag=AUTHORING_RUNTIME -->

# Code Review Reference - AUTHORING_RUNTIME

> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Confirm predecessor `01_iop_long_run` has `complete.log` before implementation starts.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
> Finalization is review-agent-only.

## 개요

date=2026-06-20
task=m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress, plan=0, tag=AUTHORING_RUNTIME

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
- Task ids:
  - `iop-progress`: `/v1/responses` streaming 지원 여부를 dev IOP 계약으로 확인하고, stream 미지원 환경에서는 `authoring_run_updated_at`과 task metadata 기반 stale monitor로 대체한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`
- Acceptance scenarios:
  - `S03`: task=`iop-progress`; evidence=`IOP streaming capability check and fallback stale-monitor evidence`
- Completion mode: spec-check-on-pass

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [AUTHORING_RUNTIME-1] Responses progress contract and fallback metadata | [x] |

## 구현 체크리스트

- [x] `iop-progress` 기준으로 `/v1/responses` stream 지원/미지원이 task metadata에 명시되고, 진행 갱신이 있을 때 `authoring_run_updated_at`이 갱신된다. 검증: stream 지원 path와 stream 미지원 fallback path unit tests가 모두 통과한다.
- [x] stream 미지원은 silent failure가 아니라 `authoring_progress_mode`/`authoring_progress_reason` 같은 명시 metadata로 남긴다.
- [x] `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...`와 `git diff --check`를 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] active review/plan files를 `.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block을 확인한다.
- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [ ] PASS이면 runtime completion metadata를 보고하고 roadmap 직접 수정은 하지 않는다.
- [x] WARN/FAIL이면 후속 plan 또는 USER_REVIEW 필요 여부를 판단한다.

## 계획 대비 변경 사항

계획과 정확히 동일하게 구현되었습니다. `MODEL_RESPONSES_STREAM` 설정을 추가하고 이를 기본값 `false`로 지정하였으며, 스트리밍 호출 시의 SSE 파싱 및 fallback non-streaming 메커니즘을 온전히 보장하였습니다.

## 주요 설계 결정

- **스트리밍 활성화 및 Fallback 구현**: `MODEL_RESPONSES_STREAM`이 활성화된 경우 `stream: true`를 전달하여 SSE 파싱을 시작합니다. 만약 API endpoint에서 스트리밍 미지원 에러를 반환하는 경우(예: 400 Bad Request와 함께 stream 미지원 관련 메시지 수신), `stream_unsupported` 상태 메타데이터를 저장한 후 non-streaming으로 1회 자동 재시도하도록 `executeGenerate`를 재귀 호출하였습니다.
- **진행 상황 메타데이터 실시간 병합**: `model.GenerateInput`에 `OnProgress` 콜백 필드를 도입하여 스트리밍 chunk 수신 시마다 `authoring_progress_mode=streaming`, `authoring_progress_reason=receiving stream chunks`, `authoring_run_updated_at` 메타데이터가 DB의 태스크 정보와 실시간으로 병합(`MergeTaskMetadata`)되도록 설계하여 stale monitor가 상태 중단을 파악할 수 있도록 도왔습니다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- `stream:false` fallback still works.
- Stream unsupported state is explicit metadata, not a swallowed error.
- Scheduler progress callback cannot overwrite secret or raw payload data into task metadata.

## 검증 결과

### AUTHORING_RUNTIME-1 중간 검증

```sh
$ cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...
?   	github.com/nomadcode/nomadcode-core/internal/model	[no test files]
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/openai	0.007s
ok  	github.com/nomadcode/nomadcode-core/internal/scheduler	2.037s
ok  	github.com/nomadcode/nomadcode-core/internal/config	0.002s
```

### 최종 검증

```sh
$ cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...
?   	github.com/nomadcode/nomadcode-core/internal/model	[no test files]
ok  	github.com/nomadcode/nomadcode-core/internal/adapters/openai	0.007s
ok  	github.com/nomadcode/nomadcode-core/internal/scheduler	2.037s
ok  	github.com/nomadcode/nomadcode-core/internal/config	0.002s
```

```sh
$ git diff --check
(출력 없음 - 성공)
```

---

> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Fail
  - code quality: Pass
  - plan deviation: Warn
  - verification trust: Fail
  - spec conformance: Fail
- 발견된 문제:
  - Required: `services/core/internal/adapters/openai/client.go:170`의 streaming parser가 `data:` payload를 `responsesResponse`로만 해석하므로 실제 Responses API semantic event인 `response.output_text.delta`의 `delta` 필드와 `response.completed`의 nested `response`를 수집하지 못한다. 그 결과 stream 지원 path에서 진행 콜백은 호출될 수 있어도 최종 `GenerateResult.Text`가 비거나 usage/id/model이 누락되어 SDD S03의 stream 지원 evidence가 성립하지 않는다. 수정: SSE event envelope(`type`, `delta`, `response`, `error`)를 별도 타입으로 파싱하고, `response.output_text.delta`는 text builder에 append하며 `response.completed`는 final response/usage를 반영하는 테스트를 추가한다.
  - Required: `services/core/internal/adapters/openai/client.go:133`이 stream 요청의 모든 `400 Bad Request`를 `stream_unsupported`로 분류해 non-streaming으로 재시도한다. invalid model, invalid input, auth/validation성 400도 stream 미지원 metadata로 오분류될 수 있어 silent fallback 방지 요구와 맞지 않는다. 수정: 명시적인 stream unsupported error type/code/message일 때만 fallback하고, 일반 400은 원래 error를 반환하는 negative test를 추가한다.
  - Required: `services/core/internal/adapters/openai/client_test.go:228`의 streaming success test가 실제 semantic event shape가 아니라 `output_text`가 있는 pseudo full-response chunk만 검증한다. 수정: `event: response.output_text.delta` / `data: {"type":"response.output_text.delta","delta":"..."}` 및 `response.completed` fixture를 사용해 텍스트/usage/progress를 검증한다.
- 다음 단계: FAIL 후속 plan/review를 작성해 streaming event parser와 fallback 판별을 좁게 수정한다.
