<!-- task=m-iop-agent-run-bridge/04+03_revision_scan_event plan=0 tag=API -->

# Plan - API

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

이 계획은 구현 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채우는 것까지가 작업이다. 구현 에이전트는 검증을 실행하고 active 파일을 유지한 채 리뷰 준비 상태를 보고한다. 사용자만 결정할 수 있는 정책, 외부 secret, scope 충돌로 막히면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 근거, 실행 명령, 재개 조건을 남기고 멈춘다. 구현 중 채팅으로 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다. 최종 archive, `complete.log`, roadmap 갱신은 code-review/runtime 책임이다.

## 배경

`Runtime.ScanBranchRevision`은 revision cursor와 `branch.updated` event 발행 흐름을 이미 제공한다. 하지만 agent run의 policy-allowed push 이후 이 scan이 연결되어 있지 않아 Git 결과가 operation event와 revision event로 완전히 수렴하지 않는다. 이 계획은 `03_commit_push_policy`가 만든 push 성공 경계를 전제로, push 이후 revision scan을 호출하고 결과를 agent run completion payload에 남기는 작업이다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지이며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-shell-iop-execution/milestones/iop-agent-run-bridge.md`
- Task ids:
  - `revision-event`: push 이후 revision scan event를 발행한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/agent-shell-iop-execution/PHASE.md`
- `agent-roadmap/phase/agent-shell-iop-execution/milestones/iop-agent-run-bridge.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `agent-ops/rules/project/domain/workspace-ops/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/workspace-ops-smoke.md`
- `agent-contract/index.md`
- `services/core/internal/worker/runner.go`
- `services/core/internal/worker/runner_test.go`
- `services/core/internal/gitengine/command.go`
- `services/core/internal/storage/storage.go`
- `services/core/internal/core/model.go`
- `services/core/internal/events/events.go`
- `services/core/internal/controlplane/runtime.go`
- `services/core/internal/controlplane/runtime_test.go`
- `packages/contracts/notes/gito-control-plane.md`

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md` 있음, 읽음.
- 적용 profile: `agent-test/local/core-smoke.md`, `agent-test/local/contracts-smoke.md`, `agent-test/local/workspace-ops-smoke.md`.
- core 필수 명령: `cd services/core && go test ./...`.
- 계약 노트를 수정하면 필수 smoke: `rg --sort path -n "proto-socket|REST|gRPC|ChangeRequest|Operation|Event|agent_shell|agent-shell|iop" packages/contracts README.md docs`.
- task artifact만 수정하는 workspace-ops 검증은 `rg --sort path -n "Gito|agent-shell|agent_shell|iop|gito-shell|control plane" README.md docs packages/contracts agent-roadmap -g '!agent-roadmap/archive/**'`.
- 구조적 공백 없음. `<확인 필요>` 값 없음.

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

- `controlplane.Runtime.ScanBranchRevision` 자체 테스트는 있다. agent run push 이후 scan을 호출하는 worker-level 테스트가 없다.
- worker가 controlplane package에 직접 의존하면 ownership 경계가 흐려질 수 있다. interface/adapter 테스트가 필요하다.
- real remote push 후 scan integration은 secret/remote 준비가 필요하므로 이 계획 범위에서는 fake scanner unit test와 existing runtime tests로 제한한다.

### 심볼 참조

- none. 기존 심볼 rename/remove 계획 없음.

### 분할 판단

- split decision policy를 적용했다.
- 공유 task group: `m-iop-agent-run-bridge`.
- `03_commit_push_policy`: predecessor 없음. 이 계획의 필수 선행 작업이다.
- `04+03_revision_scan_event`: predecessor `03`; 현재 시점에는 active sibling `agent-task/m-iop-agent-run-bridge/03_commit_push_policy/complete.log`가 아직 없으므로 구현 시작 전 완료되어야 한다.
- push 성공 후 revision scan event가 의미 있으므로 이 task는 `03` 완료 전 구현하지 않는다.

### 범위 결정 근거

- commit/push policy 구현은 `03_commit_push_policy`에 있다.
- revision scan cursor/event 생성은 기존 `controlplane.Runtime.ScanBranchRevision`을 재사용하거나 얇은 adapter로 연결한다. 새 provider webhook 경로는 만들지 않는다.
- IOP CLI, secret injection, provider PR/MR, Flutter UI, external remote integration smoke는 제외한다.
- Runtime/store migration은 기존 `revision_cursors`, `branch_watches`, `provider_deliveries` 구조로 충분하면 건드리지 않는다.

### 빌드 등급

- build=`local-G06`, review=`local-G06`. worker와 runtime 경계가 걸리지만 interface injection과 existing fake tests로 검증 가능한 범위다.

## 구현 체크리스트

- [ ] 구현 시작 전 `03_commit_push_policy`의 `complete.log`가 active 또는 archive에 있는지 확인한다.
- [ ] worker가 push 성공 후 revision scan을 요청할 수 있는 interface 또는 adapter 경계를 추가한다.
- [ ] scan 요청에는 provider, repo_id, branch, workdir, runner, observed_at을 넘기고 default provider는 기존 scan 기본값과 충돌하지 않게 둔다.
- [ ] revision scan 결과를 agent run completion payload에 `revision_scan_attempted`, `revision_event_matched`, `revision_event_id` 형태로 남긴다.
- [ ] scanner 실패 시 operation failed로 전환할지, push 성공은 유지하고 scan failure만 payload화할지 계획의 범위 안에서 명시적으로 결정하고 테스트한다.
- [ ] worker unit test로 push 성공 후 scan 호출, scan matched payload, scan failure 경로를 검증한다.
- [ ] `cd services/core && go test ./internal/worker ./internal/controlplane`를 실행한다.
- [ ] `cd services/core && go test ./...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 subtask 디렉터리 이름은 `04+03_revision_scan_event`이며 predecessor는 같은 task group의 `03_commit_push_policy`뿐이다.
- 구현 전 다음 중 하나가 있어야 한다.
  - active: `agent-task/m-iop-agent-run-bridge/03_commit_push_policy/complete.log`
  - archive: `agent-task/archive/*/*/m-iop-agent-run-bridge/03_commit_push_policy/complete.log`
- predecessor가 없으면 구현하지 말고 active review stub의 `사용자 리뷰 요청`이 아니라 "predecessor pending" 상태로 보고한다.

### [API-1] Revision scan interface after push

#### 문제

[services/core/internal/controlplane/runtime.go](/config/workspace/gito/services/core/internal/controlplane/runtime.go:324)의 `ScanBranchRevision`은 revision cursor와 event 발행을 제공하지만, [services/core/internal/worker/runner.go](/config/workspace/gito/services/core/internal/worker/runner.go:218)의 agent run terminal flow는 이를 호출할 경계가 없다. worker가 controlplane concrete type에 직접 붙으면 package boundary가 커진다.

#### 해결 방법

worker package에 small interface를 두고, controlplane/runtime 쪽 또는 cmd wiring에서 adapter를 주입한다. interface는 worker가 필요한 최소 DTO만 알게 한다.

```go
// before: runner.go has no revision scanner dependency.
type RunnerDependencies struct {
    OperationStore      storage.OperationStore
    OperationEventStore storage.OperationEventStore
    AgentRunInputLoader AgentRunInputLoader
    AgentRunInvoker     AgentRunInvoker
    GitRunner           gitengine.CommandRunner
}
```

```go
// after
type RevisionScanner interface {
    ScanAgentRunRevision(ctx context.Context, req RevisionScanRequest) (RevisionScanResult, error)
}

type RunnerDependencies struct {
    OperationStore      storage.OperationStore
    OperationEventStore storage.OperationEventStore
    AgentRunInputLoader AgentRunInputLoader
    AgentRunInvoker     AgentRunInvoker
    GitRunner           gitengine.CommandRunner
    RevisionScanner     RevisionScanner
}
```

Keep request/result structs free of provider-specific secrets.

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

- [ ] `services/core/internal/worker/runner.go`: `RevisionScanner` interface와 dependency 추가.
- [ ] `services/core/internal/worker/runner_test.go`: fake scanner 추가.
- [ ] 연결 adapter가 필요하면 `services/core/internal/controlplane/` 또는 `services/core/cmd/worker/` 중 기존 wiring에 맞춰 최소 추가.

#### 테스트 작성

작성한다. fake scanner가 request fields를 캡처하고 push 성공 후 정확히 한 번 호출되는지 검증한다.

#### 중간 검증

```bash
cd services/core && go test ./internal/worker ./internal/controlplane
```

기대: worker/controlplane 테스트가 0 exit로 통과한다.

### [API-2] Revision scan payload and failure behavior

#### 문제

[services/core/internal/controlplane/runtime.go](/config/workspace/gito/services/core/internal/controlplane/runtime.go:388)는 matched event record를 반환하지만, agent run completion payload에는 scan 시도/결과를 남기는 필드가 없다. [packages/contracts/notes/gito-control-plane.md](/config/workspace/gito/packages/contracts/notes/gito-control-plane.md:93)는 Git workspace와 이후 commit/push result를 source of truth로 둔다고만 설명한다.

#### 해결 방법

`03_commit_push_policy`의 push success signal이 있을 때만 scanner를 호출한다. payload에는 최소 필드를 추가한다.

```go
completionExtra["revision_scan_attempted"] = true
completionExtra["revision_event_matched"] = matched
if record.ID != "" {
    completionExtra["revision_event_id"] = record.ID
}
```

Failure behavior는 구현 전에 코드상 한 가지로 고정한다. 추천 기본값은 "push succeeded but scan failed => operation failed"이다. 이유는 `[revision-event]` Task의 성공 기준이 push 이후 event convergence라서 scan 실패를 숨기면 roadmap target을 닫을 수 없다.

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

- [ ] `services/core/internal/worker/runner.go`: scan 호출과 payload fields 추가.
- [ ] `services/core/internal/worker/runner_test.go`: matched/unmatched/failure 경로 테스트.
- [ ] `packages/contracts/notes/gito-control-plane.md`: payload 의미를 문서화할 필요가 있으면 transport-independent wording으로 추가.

#### 테스트 작성

작성한다. fake scanner가 matched record를 반환하면 completion payload에 `revision_event_id`가 들어가는지 검증한다. scanner error는 operation failed와 sanitized message를 검증한다.

#### 중간 검증

```bash
cd services/core && go test ./internal/worker ./internal/controlplane
```

기대: worker/controlplane 테스트가 0 exit로 통과한다.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/internal/worker/runner.go` | API-1, API-2 |
| `services/core/internal/worker/runner_test.go` | API-1, API-2 |
| `services/core/internal/controlplane/runtime.go` | API-1 |
| `services/core/cmd/worker/main.go` | API-1 |
| `packages/contracts/notes/gito-control-plane.md` | API-2 |

## 최종 검증

```bash
cd services/core && go test ./internal/worker ./internal/controlplane
```

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

계약 노트를 수정했다면 추가 실행:

```bash
rg --sort path -n "proto-socket|REST|gRPC|ChangeRequest|Operation|Event|agent_shell|agent-shell|iop" packages/contracts README.md docs
```

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