<!-- task=m-iop-agent-run-bridge/01_iop_invocation_contract plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API

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

구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. 검증을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 최종 판정, log rename, `complete.log`, task archive 이동은 code-review-skill 전용이다. 구현 중 사용자만 결정할 수 있는 IOP CLI/profile/credential 방식, 외부 secret 준비, 범위 충돌이 필요하면 review stub의 `사용자 리뷰 요청`에 증거와 재개 조건을 기록하고 멈춘다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`/`complete.log`를 만들지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

G05 리뷰에서 `agent_run` 처리 경계가 실제 storage 계약과 맞지 않는 Required 이슈가 확인되었다. `pgOperationStore.PickQueuedOperation`은 queued operation을 이미 `running`으로 전환해 반환하는데, 새 `runAgentRunOperation`이 다시 `StartOperation`을 호출해 실제 Postgres store에서는 `running -> running` invalid transition으로 backend invoke 전에 실패한다. 기존 worker unit test의 fake store가 이 transition rule을 반영하지 않아 제출 검증은 통과했지만 실제 store 계약 회귀를 잡지 못했다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-shell-iop-execution/milestones/iop-agent-run-bridge.md`
- Task ids:
  - `invoke`: Gito가 직접 IOP CLI를 실행하지 않고 IOP backend invocation/result 계약을 호출하거나 수신하는 형식을 정한다.
- Completion mode: check-on-pass

## 이전 리뷰 이슈

- `code_review_local_G05_0.log`: Required - `services/core/internal/worker/runner.go:181`의 `StartOperation` 재호출이 실제 `pgOperationStore.PickQueuedOperation`의 이미-running 계약과 충돌한다. `services/core/internal/worker/runner_test.go:56`의 fake store도 실제 transition rule을 반영하지 못한다.

## 분석 결과

### 읽은 파일

- `agent-task/m-iop-agent-run-bridge/01_iop_invocation_contract/plan_local_G05_0.log`
- `agent-task/m-iop-agent-run-bridge/01_iop_invocation_contract/code_review_local_G05_0.log`
- `services/core/internal/worker/runner.go`
- `services/core/internal/worker/runner_test.go`
- `services/core/internal/storage/storage.go`
- `services/core/internal/storage/postgres.go`
- `services/core/internal/core/model.go`

### 범위 결정 근거

- 이번 루프는 G05 Required 이슈만 수정한다.
- concrete IOP CLI/profile/credential, production `cmd/worker` wiring, operation metadata schema, Git evidence convergence는 범위 밖이다.
- `PickQueuedOperation`의 existing storage contract는 유지한다. worker handler가 picked operation의 `running` 상태를 수용하도록 맞춘다.

### 빌드 등급

- `local-G06`: 실제 store 계약과 fake test fidelity를 함께 조정해야 하지만, 수정 범위는 worker runner와 unit test에 한정되고 deterministic Go tests로 검증 가능하다.

## 구현 체크리스트

- [ ] `PickQueuedOperation`이 이미 `running`으로 전환한 `agent_run`을 `StartOperation`으로 다시 시작하지 않도록 runner 상태 전환 경계를 수정한다.
- [ ] worker test fake를 실제 operation transition rule에 맞게 강화하고, picked `agent_run`에서 `StartOperation`이 호출되지 않아도 backend result가 terminal state/event로 기록되는 테스트를 추가/수정한다. 검증: `cd services/core && go test -count=1 ./internal/worker ./internal/storage ./internal/core`.
- [ ] local core smoke baseline을 실행하고 실제 stdout/stderr를 review stub에 기록한다. 검증: `cd services/core && go test ./...`.
- [ ] `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_API-1] Runner agent_run state transition contract

#### 문제

- `services/core/internal/storage/postgres.go:624`의 `PickQueuedOperation`은 DB에서 queued row를 `running`으로 업데이트한 뒤 operation을 반환한다.
- `services/core/internal/worker/runner.go:181`은 같은 operation에 `StartOperation`을 다시 호출한다.
- `services/core/internal/core/model.go:91`의 transition rule은 동일 상태 전환을 거부하므로 실제 store에서는 backend invoke 전에 invalid transition이 발생한다.
- `services/core/internal/worker/runner_test.go:56` fake store는 이 rule을 반영하지 않아 회귀를 놓쳤다.

#### 해결 방법

- `runAgentRunOperation`에서 picked operation을 이미 running으로 간주하고 중복 `StartOperation` 호출을 제거한다.
- started event append 순서는 유지하되, operation terminal 전환은 `SucceedOperation`/`FailOperation`/`CancelOperation`만 수행한다.
- 테스트 fake는 current state를 추적하거나 `StartOperation` 호출 자체를 실패하도록 구성해, agent_run path가 중복 start를 시도하지 않는지 검증한다.
- 성공 path와 backend error path 모두 terminal operation state와 ordered operation events를 계속 검증한다.

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

- [ ] `services/core/internal/worker/runner.go`: 중복 `StartOperation` 호출 제거 또는 동일 효과의 단일-transition 계약으로 수정한다.
- [ ] `services/core/internal/worker/runner_test.go`: fake store fidelity를 높이고 중복 start 회귀 검증을 추가한다.

#### 테스트 작성

- 수정/보강: `services/core/internal/worker/runner_test.go`.
- assertion 목표:
  - picked `agent_run` operation은 backend invoke 전에 `StartOperation`을 다시 호출하지 않는다.
  - success result는 `SucceedOperation`과 `operation.completed`/`agent.run.completed` event를 남긴다.
  - backend error result는 sanitized message와 `OperationFailed` terminal state/event를 남긴다.

#### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/worker ./internal/storage ./internal/core
```

기대 결과: 세 package 모두 `ok`.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/internal/worker/runner.go` | REVIEW_API-1 |
| `services/core/internal/worker/runner_test.go` | REVIEW_API-1 |

## 최종 검증

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

기대 결과: 모든 core Go package가 통과한다.

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