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

# Implementation Plan - AUTHORING_RUNTIME

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

이 plan은 split predecessor `01_iop_long_run`의 `complete.log`가 필요하다. 구현 시작 전 `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log` 또는 matching archive `complete.log`가 있는지 확인한다. 구현 완료는 active `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 사용자에게 직접 질문하지 말고, 선택된 SDD/Milestone lock 결정만 review stub의 `사용자 리뷰 요청`에 기록한다.

## 배경

`iop-progress` Task는 dev IOP의 `/v1/responses` streaming 지원 여부를 확인하고, stream 미지원이면 silent failure가 아니라 명시 metadata와 stale monitor 기준으로 대체하라고 요구한다. 현재 OpenAI-compatible adapter는 `stream:false`를 hard-code하고 응답 body를 한 번에 읽는다. Scheduler는 `authoring_run_updated_at`을 시작과 최종 대기 상태에서만 갱신하므로 진행 갱신 중단을 감지할 수 없다.

## 사용자 리뷰 요청 흐름

구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. code-review가 해당 요청을 검증하고 필요 시 `USER_REVIEW.md`를 작성한다.

## 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

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/workspace-ops-smoke.md`
- `agent-test/live-cycle-smoke.md`
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
- `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`
- `services/core/internal/model/model.go`
- `services/core/internal/workflow/model.go`
- `services/core/internal/adapters/openai/client.go`
- `services/core/internal/adapters/openai/client_test.go`
- `services/core/internal/scheduler/jobs.go`
- `services/core/internal/scheduler/jobs_test.go`
- `services/core/internal/config/config.go`
- `services/core/internal/config/config_test.go`
- `services/core/README.md`
- `services/core/go.mod`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md`, `core-smoke`, `workspace-ops-smoke` read.
- focused verification: `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...`.
- `git diff --check` for docs/task artifact whitespace.

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

- `services/core/internal/adapters/openai/client_test.go` covers non-streaming response shape and workspace metadata.
- No test covers `stream:true`, SSE parsing, streaming unsupported fallback, progress callback, or scheduler metadata updates from progress events.

### 심볼 참조

- If `model.Client.Generate` signature remains unchanged, no call-site rename is needed.
- If adding progress callback fields to `model.GenerateInput` or `model.GenerateResult`, update fake clients in `services/core/internal/scheduler/jobs_test.go` and adapter tests.

### 분할 판단

- split decision policy evaluated.
- Directory dependency `02+01_iop_progress` means predecessor `01_iop_long_run` must finish first.
- Predecessor `01`: missing `complete.log` at active `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log`; no archive candidate was read because no candidate path exists for this new split group.
- This plan is separate because streaming/progress changes touch the model adapter contract and can be reviewed independently from timeout policy and push-only flow.

### 범위 결정 근거

- 포함: model progress metadata, OpenAI-compatible Responses stream/fallback behavior, scheduler metadata updates for progress/stale.
- 제외: initial long-run timeout defaults (`01_iop_long_run`).
- 제외: push-only request and remote develop gate (`03+01,02_push_command`).
- Exclude live Plane full-cycle evidence; this plan may document dev IOP stream capability but does not claim `full-cycle`.

### 빌드 등급

- `cloud-G07`: streaming support/fallback can pass unit tests while still breaking live IOP semantics, so cloud lane is required.

## 구현 체크리스트

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

## 의존 관계 및 구현 순서

- Runtime dependency from directory name: `01_iop_long_run` must produce `complete.log` before this subtask starts.
- Do not add hidden dependencies beyond the directory name.

### [AUTHORING_RUNTIME-1] Responses progress contract and fallback metadata

문제:

`services/core/internal/adapters/openai/client.go:66` builds a Responses request with `Stream: false` at `services/core/internal/adapters/openai/client.go:71`, then reads the whole body at `services/core/internal/adapters/openai/client.go:102` through `services/core/internal/adapters/openai/client.go:118`. `services/core/internal/model/model.go:27` has no progress callback/result field. `services/core/internal/scheduler/jobs.go:69` through `services/core/internal/scheduler/jobs.go:79` only record initial `authoring_run_updated_at`, so progress interruption and stream unsupported states are invisible.

Before:

```go
// services/core/internal/adapters/openai/client.go:66
reqBody := responsesRequest{
	Model:           modelName,
	Input:           input.Input,
	Instructions:    input.Instructions,
	Metadata:        buildRequestMetadata(input),
	Stream:          false,
	Temperature:     input.Temperature,
	TopP:            input.TopP,
	MaxOutputTokens: input.MaxOutputTokens,
}
```

해결 방법:

- Add an internal model progress representation without exposing raw stream payloads or secrets. Suggested fields: progress mode (`streaming`, `stream_unsupported`, `non_streaming`), reason, last event time, and optional callback on `GenerateInput`.
- Add config/env for enabling Responses streaming only when dev IOP supports it, defaulting to non-streaming fallback. If the endpoint returns an unsupported stream error, retry once non-streaming and return explicit fallback reason.
- Implement SSE parsing for OpenAI-compatible Responses stream events that update progress and final text. Keep non-streaming behavior compatible.
- In scheduler authoring path, pass a progress callback that merges `authoring_run_updated_at` and progress metadata through `Lifecycle.MergeTaskMetadata`.
- Add tests for stream supported, stream unsupported fallback, and scheduler metadata merge on progress.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/model/model.go`: add progress fields/callback types.
- [ ] `services/core/internal/adapters/openai/client.go`: support stream-enabled path, unsupported fallback, and progress result metadata.
- [ ] `services/core/internal/adapters/openai/client_test.go`: add SSE supported and unsupported fallback tests.
- [ ] `services/core/internal/config/config.go`: add stream enable/config field only if needed.
- [ ] `services/core/internal/config/config_test.go`: assert stream config defaults/overrides if added.
- [ ] `services/core/internal/scheduler/jobs.go`: persist authoring progress metadata safely.
- [ ] `services/core/internal/scheduler/jobs_test.go`: add progress callback metadata regression test.
- [ ] `services/core/README.md`: document stream default/fallback and stale-monitor metadata without secrets.

테스트 작성:

- Required. Add adapter tests for stream support and fallback.
- Required. Add scheduler test for `authoring_run_updated_at` update on progress.

중간 검증:

```sh
cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...
```

Expected: pass with stream/fallback/progress metadata tests.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/model/model.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/adapters/openai/client.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/adapters/openai/client_test.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/config/config.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/config/config_test.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/scheduler/jobs.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/scheduler/jobs_test.go` | AUTHORING_RUNTIME-1 |
| `services/core/README.md` | AUTHORING_RUNTIME-1 |

## 최종 검증

```sh
cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...
```

Expected: pass; Go test cache is not acceptable because stream/progress behavior changed.

```sh
git diff --check
```

Expected: no output.

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