<!-- task=m-milestone-work-item-creation-sync/02+01_iop_authoring_bridge plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API IOP Authoring Bridge Fixes

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

이 plan은 `code_review_cloud_G07_0.log`의 Required 이슈만 해결한다. 구현 중 사용자에게 직접 질문하거나 `request_user_input`을 호출하지 않는다. 사용자 전용 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청`에 근거를 기록하고 active 파일을 유지한 채 멈춘다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
- Task ids:
  - `cycle-iop-http-ready`: IOP Edge OpenAI-compatible HTTP(`/v1/responses`) workspace agent 실행 통로를 선행 조건으로 확인한다.
  - `cycle-authoring-run`: Plane 티켓 제목/본문과 provider work item identity를 입력으로 `slots/<index>` workspace에서 1회 authoring run을 실행한다.
- Completion mode: check-on-pass

## 리뷰 근거

- 이전 리뷰: `agent-task/m-milestone-work-item-creation-sync/02+01_iop_authoring_bridge/code_review_cloud_G07_0.log`
- 판정: FAIL
- Required 이슈 수: 3

## 범위 결정 근거

- Plane Todo projection, identity map persistence, idempotency retry는 계속 후속 subtask 범위로 둔다.
- 이 plan은 이미 추가된 OpenAI metadata object support를 유지하면서 실제 Plane-origin authoring request shape, 실행 routing, 상태 metadata만 보정한다.

## 구현 체크리스트

- [ ] [REVIEW_API-1] Authoring builder가 실제 work item pipeline payload(`message`, `work_item`)와 task external fields에서 Plane 티켓 본문 및 provider identity를 보존한다.
- [ ] [REVIEW_API-2] Plane-origin authoring task는 A2A client가 설정되어 있어도 IOP OpenAI-compatible HTTP bridge를 우선 호출한다.
- [ ] [REVIEW_API-3] authoring run 시작 시 `authoring_run_state=in_progress`와 `authoring_run_updated_at`을 저장하고 stale 판단 가능한 metadata 경계를 검증한다.
- [ ] `cd services/core && go test ./internal/authoring ./internal/scheduler ./internal/workitempipeline ./internal/workflow`와 `cd services/core && go test ./...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_API-1] 실제 Plane payload와 provider identity 보존

### 문제

`services/core/internal/authoring/request.go`는 payload의 `prompt`/`instructions`와 `metadata.external`만 본다. 실제 work item pipeline은 `services/core/internal/workitem/provider.go`에서 payload에 `message`와 `work_item`을 넣고, 외부 식별자는 task의 `ExternalProvider`/`ExternalID` 컬럼으로 저장한다. 따라서 현재 bridge는 Plane 티켓 본문을 입력하지 못하고 `metadata.workspace.work_item_id`가 내부 task id로 떨어질 수 있다.

### 해결 방법

- `authoring` payload parser가 `prompt`, `message`, `input`, `instructions`, `system`, `work_item`을 읽도록 확장한다.
- `metadata.workspace.provider`/`work_item_id`는 우선 `task.ExternalProvider`/`task.ExternalID`, 다음 `payload.work_item.provider`/`id`, 마지막 기존 fallback을 사용한다.
- prompt가 별도로 없으면 title과 Plane body/message가 모두 모델 input에 포함되게 구성한다. 빈 body만 있을 때는 기존 title fallback을 유지한다.
- `metadata.workspace` object에는 workspace path/source branch와 provider identity가 실제 Plane ref로 들어가야 한다.

### 수정 파일 및 체크리스트

- [ ] `services/core/internal/authoring/request.go`
- [ ] `services/core/internal/authoring/request_test.go`
- [ ] 필요 시 `services/core/internal/scheduler/jobs_test.go`

### 테스트 작성

- `workitem.BuildCreateTaskInput` 또는 동일한 payload shape(`message`, `work_item`)를 사용한 authoring builder 테스트를 추가한다.
- `task.ExternalProvider="plane"`, `task.ExternalID="work-1"`인 실제 `storage.Task`에서 `WorkspaceMetadata.WorkItemID=="work-1"`인지 검증한다.
- 기존 `prompt` payload 테스트는 backward-compatible 경로로 유지한다.

### 중간 검증

```bash
cd services/core && go test ./internal/authoring ./internal/workitempipeline
```

## [REVIEW_API-2] Authoring task의 HTTP bridge 우선 routing

### 문제

`services/core/internal/scheduler/jobs.go`는 `w.Agent != nil`이면 바로 A2A 경로로 실행한다. 서버 wiring은 A2A client와 model client를 동시에 주입할 수 있으므로, A2A가 설정된 환경에서는 Plane-origin authoring task가 `/v1/responses` bridge까지 도달하지 않는다.

### 해결 방법

- `TaskWorker.runTask`에서 authoring task 판별을 A2A 우선 분기보다 앞에 둔다.
- authoring task이면 `w.Model.Generate`를 사용하고, `w.Model == nil`이면 명확한 error를 반환한다.
- non-authoring task의 기존 A2A 우선 동작과 generic model fallback은 유지한다.

### 수정 파일 및 체크리스트

- [ ] `services/core/internal/scheduler/jobs.go`
- [ ] `services/core/internal/scheduler/jobs_test.go`

### 테스트 작성

- worker에 `Agent`와 `Model`을 둘 다 넣고 Plane-origin checkout metadata task를 실행했을 때 fake model이 호출되고 fake agent는 호출되지 않음을 검증한다.
- manual/non-plane task는 기존처럼 A2A가 우선 호출됨을 검증한다.
- authoring task인데 model client가 nil이면 명확한 error를 반환하는지 검증한다.

### 중간 검증

```bash
cd services/core && go test ./internal/scheduler
```

## [REVIEW_API-3] Authoring in-progress 상태 metadata

### 문제

`authoring_run_state` 값 목록에는 `in_progress`가 있지만 실제 저장은 성공/실패 경로뿐이다. 실행 중인 authoring task에 `authoring_run_updated_at`이 없으면 stale 판단 기준이 없다.

### 해결 방법

- Plane-origin authoring task가 running으로 전환될 때 `authoring_run_state=in_progress`와 `authoring_run_updated_at=<RFC3339 UTC>`를 task metadata에 저장한다.
- 이 저장 경계는 기존 lifecycle 단일 metadata write 원칙을 유지해야 한다. 구현 선택지는 `Lifecycle.StartTask`에서 source/checkout metadata를 판별하거나 scheduler가 lifecycle에 명시적인 in-progress metadata를 전달하도록 interface를 확장하는 방식 중 기존 구조와 더 잘 맞는 쪽을 택한다.
- 성공/실패 시 기존 `succeeded`/`failed` overwrite가 유지되는지 확인한다.

### 수정 파일 및 체크리스트

- [ ] `services/core/internal/workflow/lifecycle.go`
- [ ] `services/core/internal/workflow/model.go`
- [ ] `services/core/internal/workflow/service_test.go`
- [ ] 필요 시 `services/core/internal/scheduler/jobs.go`
- [ ] 필요 시 `services/core/internal/scheduler/jobs_test.go`

### 테스트 작성

- authoring checkout metadata를 가진 Plane task가 `StartTask`/worker start 경계에서 `authoring_run_state=in_progress`와 timestamp를 저장하는지 검증한다.
- 성공 completion은 `succeeded`, failure는 `failed`로 덮어쓰는지 검증한다.
- non-authoring task에는 authoring metadata가 붙지 않는지 검증한다.

### 중간 검증

```bash
cd services/core && go test ./internal/workflow ./internal/scheduler
```

## 최종 검증

```bash
cd services/core && go test ./internal/authoring ./internal/scheduler ./internal/workitempipeline ./internal/workflow
cd services/core && go test ./...
git diff --check
```

Go test cache output is acceptable for unchanged packages; changed packages must appear in output or be rerun directly as 중간 검증.
