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

# Implementation Plan - REVIEW_AUTHORING_RUNTIME

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

이 plan은 code-review FAIL 후속 루프다. 아래 `Archive Evidence Snapshot`에 명시된 이전 plan/review log만 좁게 참고하고, `agent-task/archive/**`는 읽지 않는다. 구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G07.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

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/plan_cloud_G07_0.log`
- Current archived review: `agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/code_review_cloud_G07_0.log`
- Verdict: FAIL
- Required summary:
  - `services/core/internal/adapters/openai/client.go:170` parses streaming `data:` payloads only as `responsesResponse`, so real Responses semantic events such as `response.output_text.delta` do not contribute text and `response.completed` does not contribute final response/usage.
  - `services/core/internal/adapters/openai/client.go:133` treats any streaming `400 Bad Request` as `stream_unsupported`, which can misclassify ordinary validation/model errors and retry incorrectly.
  - `services/core/internal/adapters/openai/client_test.go:228` uses pseudo full-response chunks instead of semantic Responses SSE event fixtures, so the broken stream path still passes.
- Affected files:
  - `services/core/internal/adapters/openai/client.go`
  - `services/core/internal/adapters/openai/client_test.go`
  - `services/core/README.md` if wording changes are needed after behavior is narrowed.
- Verification evidence from failed loop:
  - `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...` passed locally.
  - `cd services/core && go test -count=1 ./...` passed locally.
  - `git diff --check` passed locally.
- Roadmap/spec carryover:
  - Roadmap Task `iop-progress` remains claimed only when this follow-up passes.
  - SDD scenario `S03` remains claimed only when semantic stream support and explicit unsupported fallback evidence are both present.
- Narrow reread allowed:
  - `agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/plan_cloud_G07_0.log`
  - `agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/code_review_cloud_G07_0.log`

## 범위 결정 근거

- 포함: OpenAI-compatible Responses SSE semantic event parsing, explicit stream unsupported fallback classification, regression tests and minimal docs alignment.
- 제외: scheduler stale threshold policy, live Plane full-cycle evidence, push-only flow, identity/projection sync.
- Lane/grade: `cloud-G07`. 후속 범위는 좁지만 외부 Responses streaming semantic event 계약을 정확히 반영해야 하고, 이전 루프의 unit tests가 실제 계약을 대표하지 못한 evidence-trust 실패가 있었다.

## 구현 체크리스트

- [ ] `REVIEW_AUTHORING_RUNTIME-1` Responses streaming parser가 semantic SSE event의 `response.output_text.delta`, `response.completed`, `error`를 처리하고 final text/usage/id/model을 보존한다.
- [ ] `REVIEW_AUTHORING_RUNTIME-2` stream fallback은 명시적인 stream unsupported error에서만 발생하고, 일반 400/validation error는 `stream_unsupported` metadata 없이 원래 error로 반환된다.
- [ ] `REVIEW_AUTHORING_RUNTIME-3` streaming tests가 pseudo `output_text` chunk 대신 semantic SSE fixture와 일반 400 negative case를 검증한다.
- [ ] `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...`, `cd services/core && go test -count=1 ./...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Runtime dependency from directory name remains `02+01_iop_progress`.
- 추가 hidden dependency를 만들지 않는다.
- 이전 loop log는 위 `Archive Evidence Snapshot`에 명시된 두 파일만 필요한 경우 좁게 읽는다.

### [REVIEW_AUTHORING_RUNTIME-1] Semantic Responses SSE parser

문제:

현재 streaming parser는 `data:` payload를 `responsesResponse`로 unmarshal한 뒤 `output_text`/`output`만 읽는다. 실제 Responses stream은 semantic event이며 text delta는 `type=response.output_text.delta`와 `delta` 필드로 온다. final usage와 response id/model은 `response.completed`의 nested `response`에서 올 수 있다.

해결 방법:

- streaming 전용 event DTO를 추가한다.
  - fields: `Type`, `Delta`, `Response`, `Error`.
  - `response.output_text.delta`: `Delta`를 text builder에 append하고 progress callback을 호출한다.
  - `response.completed`: nested `Response`를 final response에 merge하고 usage/id/model을 반영한다.
  - `error`: 명시 error를 반환한다.
- 기존 pseudo full-response chunk 호환이 필요하면 fallback branch로 유지하되, semantic event 처리가 우선이어야 한다.
- JSON parse 실패를 무조건 삼키지 말고 malformed data가 final output을 만들 수 없으면 error evidence가 남도록 한다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/adapters/openai/client.go`: semantic stream event DTO와 merge 로직 추가.
- [ ] `services/core/internal/adapters/openai/client_test.go`: semantic delta/completed fixture 기반 테스트로 검증.

중간 검증:

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

### [REVIEW_AUTHORING_RUNTIME-2] Explicit unsupported fallback only

문제:

stream 요청에서 모든 `400 Bad Request`를 `stream_unsupported`로 분류한다. 이러면 invalid model/input 같은 일반 validation error도 stream 미지원으로 metadata가 남고 non-streaming 재시도가 발생할 수 있다.

해결 방법:

- fallback predicate를 별도 helper로 만든다.
- status code만으로 fallback하지 않는다.
- error message/type/code에 `stream`, `streaming`, `unsupported`, `not supported`, `unsupported_parameter` 같은 stream unsupported 신호가 있을 때만 fallback한다.
- 일반 400은 원래 `responseError`를 반환하고 progress callback을 호출하지 않는다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/adapters/openai/client.go`: fallback 판별 helper 추가.
- [ ] `services/core/internal/adapters/openai/client_test.go`: stream unsupported는 fallback, 일반 400은 no fallback/no progress로 검증.

중간 검증:

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

### [REVIEW_AUTHORING_RUNTIME-3] Regression evidence and docs alignment

문제:

기존 streaming test는 `output_text`를 가진 pseudo chunk만 검증해서 실제 semantic event parser 결함을 잡지 못했다.

해결 방법:

- `TestGenerateStreamingSuccess`를 semantic SSE fixture로 바꾸거나 새 테스트를 추가한다.
- fixture는 `event: response.output_text.delta` line과 `data: {"type":"response.output_text.delta","delta":"..."}` line을 함께 포함한다.
- final usage는 `data: {"type":"response.completed","response":{...}}`에서 검증한다.
- README 표현이 “400이면 fallback”처럼 너무 넓으면 명시 stream unsupported error일 때만 fallback한다고 좁힌다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/adapters/openai/client_test.go`: semantic SSE fixture와 negative 400 test 추가/갱신.
- [ ] `services/core/README.md`: fallback 조건 문구를 구현과 일치시킨다.

중간 검증:

```sh
cd services/core && go test -count=1 ./internal/adapters/openai/...
git diff --check
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/adapters/openai/client.go` | REVIEW_AUTHORING_RUNTIME-1, REVIEW_AUTHORING_RUNTIME-2 |
| `services/core/internal/adapters/openai/client_test.go` | REVIEW_AUTHORING_RUNTIME-1, REVIEW_AUTHORING_RUNTIME-2, REVIEW_AUTHORING_RUNTIME-3 |
| `services/core/README.md` | REVIEW_AUTHORING_RUNTIME-3 |

## 최종 검증

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

Expected: pass with semantic stream and explicit unsupported fallback tests.

```sh
cd services/core && go test -count=1 ./...
```

Expected: pass for full core regression coverage.

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

Expected: no output.

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