<!-- task=m-iop-agent-run-bridge/05_production_worker_bridge plan=1 tag=REVIEW_REVIEW_API -->

# PLAN - REVIEW_REVIEW_API Production Worker Bridge Follow-Up

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 필수 단계다. 구현과 검증을 끝낸 뒤 실제 변경 내용, 계획 대비 차이, 주요 결정, stdout/stderr를 review stub에 기록하고 active 파일을 유지한 채 리뷰 준비를 보고한다. 사용자 전용 결정, 사용자 소유 외부 환경/secret/service 준비, 또는 범위 충돌로 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 말고, 후속 에이전트가 재실행/증거 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청으로 보지 않는다.

## 배경

첫 production worker bridge 구현은 typed picker, durable input, HTTP invoker, explicit revision scan을 추가했지만 code-review에서 네 가지 Required 문제가 발견됐다. explicit revision scan은 changed files를 계산하지 않고, input loader failure는 operation을 `running`에 남길 수 있으며, durable input validation은 빈 instruction을 허용한다. 또한 repository root 아래에 generated worker binary가 남아 workspace rule을 위반한다. 이 follow-up은 기존 구현을 되돌리지 않고 해당 Required만 좁게 닫는다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 전용 결정은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트는 chat으로 직접 묻지 않고 active 파일을 유지한 채 code-review가 `USER_REVIEW.md` 작성 필요성을 판정하게 한다.

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-iop-agent-run-bridge/05_production_worker_bridge/plan_cloud_G07_0.log`.
- Current archived review: `agent-task/m-iop-agent-run-bridge/05_production_worker_bridge/code_review_cloud_G07_0.log`.
- Verdict: FAIL.
- Required summary:
  - `services/core/internal/controlplane/runtime.go`: explicit before/after revision scan must compute `changed_files` from `WorkDir`/`Runner`.
  - `services/core/internal/worker/runner.go`: input loader failure must fail the already-picked operation instead of leaving it `running`.
  - `services/core/internal/storage/postgres.go` and `services/core/migrations/00001_initial.sql`: durable input must reject blank `instruction`.
  - `services/core/worker`: generated binary must not remain in the repository.
- Review recovery evidence:
  - `GITO_TEST_DATABASE_URL="$DATABASE_URL" go test -run 'TestPostgres(OperationStorePickQueuedAgentRunOperationFiltersType|AgentRunInputStoreCreateAndGet)' -v ./internal/storage` PASS.
  - `go test -count=1 ./internal/worker ./internal/controlplane ./internal/storage` PASS.
  - `go test ./...` PASS.
  - contracts/workspace keyword scans and `git diff --check` PASS.
- Roadmap carryover: this task still targets `production-worker` and `revision-scan-seed`.
- Additional archive reread is not required by default. If exact prior wording is needed, read only the two log paths above.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-shell-iop-execution/milestones/iop-agent-run-bridge.md`
- Task ids:
  - `production-worker`: production worker가 durable agent_run input source에서 실행 입력을 로드하고, agent_run만 안전하게 pick해 IOP adapter/result path를 실제 runtime 의존성으로 연결한다.
  - `revision-scan-seed`: cursor 없는 agent_run push도 known before/after revision으로 `branch.updated` 이벤트를 emit/retry한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-task/m-iop-agent-run-bridge/05_production_worker_bridge/plan_cloud_G07_0.log`
- `agent-task/m-iop-agent-run-bridge/05_production_worker_bridge/code_review_cloud_G07_0.log`
- `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-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`
- `agent-contract/provided/gito-forgejo-branch-events-v1.md`
- `services/core/internal/controlplane/runtime.go`
- `services/core/internal/controlplane/runtime_test.go`
- `services/core/internal/worker/runner.go`
- `services/core/internal/worker/runner_test.go`
- `services/core/internal/storage/postgres.go`
- `services/core/internal/storage/postgres_test.go`
- `services/core/internal/storage/storage.go`
- `services/core/migrations/00001_initial.sql`
- `services/core/cmd/worker/main.go`
- `services/core/internal/worker/http_invoker.go`
- `services/core/internal/worker/http_invoker_test.go`
- `packages/contracts/notes/gito-control-plane.md`

### 테스트 환경 규칙

- test_env: local.
- `agent-test/local/rules.md`를 읽었고, matched profile로 `core-smoke.md`, `contracts-smoke.md`, `workspace-ops-smoke.md`를 읽었다.
- 적용 명령:
  - core: `cd services/core && go test ./...`
  - focused fresh Go tests: `cd services/core && go test -count=1 ./internal/worker ./internal/controlplane ./internal/storage`
  - Postgres focused SQL evidence: `cd services/core && GITO_TEST_DATABASE_URL="$DATABASE_URL" go test -run 'TestPostgres(OperationStorePickQueuedAgentRunOperationFiltersType|AgentRunInputStoreCreateAndGet)' -v ./internal/storage`
  - contracts keyword scan: `rg --sort path -n "proto-socket|REST|gRPC|ChangeRequest|Operation|Event|agent_shell|agent-shell|iop" packages/contracts README.md docs`
  - workspace keyword scan: `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/**'`
  - diff hygiene: `git diff --check`
- `<확인 필요>` 값은 발견하지 못했다.
- `GITO_TEST_DATABASE_URL`은 직접 출력하지 않는다. 현재 환경에서는 `DATABASE_URL`이 set이고 `GITO_TEST_DATABASE_URL="$DATABASE_URL"`로 focused Postgres tests가 PASS했다.

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

- explicit before/after scan changed files: 기존 새 테스트는 event emit과 cursor만 확인하고 changed_files, `WorkDir`, `Runner` 사용을 확인하지 않는다.
- input loader failure terminal state: 기존 새 테스트는 error return만 확인하고 operation failed transition/event를 확인하지 않는다.
- durable input instruction validation: empty whole input은 test하지만 blank instruction만 비어 있는 boundary를 확인하지 않는다.
- generated binary cleanup: `git status --short`에서 `?? services/core/worker`가 사라지는지 확인이 필요하다.

### 심볼 참조

- `worker.RevisionScanRequest`: `runtime.go`, `runtime_test.go`, `runner.go`, `runner_test.go` call site를 유지하되 explicit pair path에서 `WorkDir`/`Runner`를 실제 사용한다.
- `storage.AgentRunInputStore`: `postgres.go`, `postgres_test.go`, `cmd/worker/main.go` validation behavior를 맞춘다.
- renamed/removed symbol은 없다.

### 분할 판단

split policy를 검토했다. Required 네 개는 같은 production bridge closure에서 발견됐지만 모두 현재 subtask의 보완이며, 별도 split을 만들면 active loop 상태가 불필요하게 늘어난다. 변경 범위는 `runtime`, `runner`, `storage/migration`, workspace cleanup으로 분명하고 검증 명령도 하나의 focused set으로 닫힌다. 따라서 같은 split task 디렉터리에서 단일 follow-up plan으로 처리한다.

### 범위 결정 근거

- IOP callback async protocol, credential injection, cancellation/log redaction, provider adapter 확장은 다루지 않는다.
- 기존 HTTP invoker success/error shape는 Required가 아니므로 필요한 경우 테스트 영향 범위 안에서만 수정한다.
- Roadmap 문서는 직접 갱신하지 않는다. PASS completion metadata는 runtime/update-roadmap 책임이다.
- `agent-task/archive/**`를 넓게 읽지 않는다. 이 계획에 필요한 archive evidence는 같은 active task directory의 두 log 파일뿐이다.

### 빌드 등급

cloud-G07. storage/migration, worker lifecycle, branch event contract, verification evidence가 함께 걸린 follow-up이며 현재 lane이 적절하다.

## 구현 체크리스트

- [ ] explicit before/after revision scan이 `WorkDir`/`Runner`로 changed files를 계산해 `branch.updated` 이벤트에 포함하도록 고친다. 검증: `cd services/core && go test -count=1 ./internal/controlplane ./internal/worker`
- [ ] input loader failure가 이미 picked된 `agent_run` operation을 sanitized failed terminal path로 수렴시키도록 고친다. 검증: `cd services/core && go test -count=1 ./internal/worker`
- [ ] durable agent_run input에서 blank `instruction`을 store/schema/loader boundary에서 거부하고 regression test를 추가한다. 검증: `cd services/core && GITO_TEST_DATABASE_URL="$DATABASE_URL" go test -run 'TestPostgresAgentRunInputStoreCreateAndGet' -v ./internal/storage`
- [ ] generated worker binary `services/core/worker`를 제거하고 source/task artifact 외 불필요 산출물이 남지 않게 한다. 검증: `git status --short`와 `git check-ignore -v services/core/worker` 확인
- [ ] focused/full Go tests, contracts/workspace keyword scans, diff hygiene를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

1. revision scan changed_files와 loader failure terminal path를 먼저 고친다.
2. durable input validation과 migration CHECK를 보강한다.
3. generated binary를 제거한다.
4. focused tests, Postgres focused tests, full smoke, scans, diff hygiene를 실행한다.

### [REVIEW_REVIEW_API-1] Explicit revision scan changed_files

#### 문제

- `services/core/internal/controlplane/runtime.go:490`은 explicit before/after pair를 받은 뒤 `core.RevisionEvent`를 직접 만들며 `ChangedFiles`를 채우지 않는다.
- `services/core/internal/worker/runner.go:70-81`의 request는 `WorkDir`와 `Runner`를 유지하지만 explicit pair path는 이를 무시한다.
- `agent-contract/provided/gito-forgejo-branch-events-v1.md`의 `branch.updated` base payload는 `changed_files`를 consumer 판단 정보로 둔다.

#### 해결 방법

- `scanExplicitRevisionPair` 또는 그 caller가 `workdir`과 `runner`를 인자로 받아 `gitengine.ChangedFilesWithStatus(runner, workdir, before, after)`를 실행한다.
- empty `workdir`은 explicit pair에서도 error로 처리한다. runner가 nil이면 `gitengine.CLI{}`를 쓴다.
- `revisionEventFromScan(repoID, branch, before, after, files, observedAt)`으로 이벤트를 만들고 기존 cursor/publish failure semantics를 유지한다.
- `TestRuntimeScanAgentRunRevisionEmitsWithoutExistingCursor`는 실제 git repo fixture 또는 fake runner를 사용해 changed_files가 포함되는지 assert한다.

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

- [ ] `services/core/internal/controlplane/runtime.go`: explicit pair path에서 changed files 계산과 workdir validation을 추가한다.
- [ ] `services/core/internal/controlplane/runtime_test.go`: no cursor explicit pair test가 changed_files와 runner/workdir 사용을 확인하게 보강한다.
- [ ] `services/core/internal/worker/runner_test.go`: scanner request before/after test가 기존대로 통과하는지 확인한다.

#### 테스트 작성

- 작성한다.
- `TestRuntimeScanAgentRunRevisionEmitsWithoutExistingCursor`: event `ChangedFiles`에 modified/added 파일이 포함되는지 assert.
- `TestRuntimeScanAgentRunRevisionPublishFailureNoCursorAdvance`: changed_files 계산 후 publish failure에도 cursor 미전진을 유지하는지 assert.

#### 중간 검증

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

예상: fresh 실행 PASS.

### [REVIEW_REVIEW_API-2] Loader failure terminal state

#### 문제

- `services/core/internal/worker/runner.go:245`에서 loader failure가 발생하면 `runAgentRunOperation`이 즉시 error return한다.
- operation은 typed picker에서 이미 `running`으로 전이됐으므로, missing/corrupt durable input 하나가 production operation을 terminal state 없이 고착시킨다.

#### 해결 방법

- started events를 남긴 뒤 input load failure가 발생하면 `terminalState=failed`, `completionMessage="agent run input load failed"` 같은 stable sanitized message로 completion path에 진입한다.
- 원래 error는 caller에 raw로 노출하지 않는다. 필요한 경우 logger에 sanitized context만 남긴다.
- completion event payload에는 raw DB error나 raw input payload를 넣지 않는다.

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

- [ ] `services/core/internal/worker/runner.go`: loader failure를 failed terminal completion path로 수렴시킨다.
- [ ] `services/core/internal/worker/runner_test.go`: loader failure test가 `FailOperation` 호출, failed operation event, invoker not called를 assert하도록 보강한다.

#### 테스트 작성

- 작성한다.
- `TestRunnerRunOnceAgentRunInputLoaderFailureFails`: `opStore.failedCalls == 1`, final state `failed`, completion events present, invoker calls 0.

#### 중간 검증

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

예상: fresh 실행 PASS.

### [REVIEW_REVIEW_API-3] Durable input instruction validation

#### 문제

- `services/core/internal/storage/postgres.go:1091-1115`은 operation/repo/branch/workspace/expected revision만 required로 검사하고 `instruction`은 비어 있어도 허용한다.
- `services/core/migrations/00001_initial.sql:95`도 `instruction TEXT NOT NULL`만 있어 empty string을 막지 않는다.
- `services/core/cmd/worker/main.go:63` loader validation도 instruction을 확인하지 않는다.

#### 해결 방법

- store create path에서 `strings.TrimSpace(input.Instruction) == ""`이면 `ErrInvalidAgentRunInput`을 반환한다.
- migration schema를 `instruction TEXT NOT NULL CHECK (btrim(instruction) <> '')`로 맞춘다.
- production loader도 durable input의 `Instruction` blank를 방어적으로 거부한다.

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

- [ ] `services/core/internal/storage/postgres.go`: `Instruction` trim/required validation을 추가한다.
- [ ] `services/core/migrations/00001_initial.sql`: `instruction` CHECK constraint를 추가한다.
- [ ] `services/core/cmd/worker/main.go`: loader required-field check에 `instruction`을 포함한다.
- [ ] `services/core/internal/storage/postgres_test.go`: blank instruction boundary test를 추가한다.

#### 테스트 작성

- 작성한다.
- `TestPostgresAgentRunInputStoreCreateAndGet`: valid input은 유지하고, blank `Instruction`만 있는 input이 `ErrInvalidAgentRunInput`을 반환하는 assertion을 추가한다.

#### 중간 검증

```bash
cd services/core && GITO_TEST_DATABASE_URL="$DATABASE_URL" go test -run 'TestPostgresAgentRunInputStoreCreateAndGet' -v ./internal/storage
```

예상: actual Postgres path PASS. DSN 값은 출력하지 않는다.

### [REVIEW_REVIEW_API-4] Generated worker binary cleanup

#### 문제

- `services/core/worker:1`에 19MB generated worker binary가 untracked 상태로 남아 있고 `.gitignore`에도 걸리지 않는다.
- workspace-ops rule은 generated build outputs를 tracked workspace에 남기지 말라고 한다.

#### 해결 방법

- `services/core/worker`를 제거한다.
- 반복 생성 가능성이 있으면 기존 Go ignore 정책과 충돌하지 않는 최소 ignore entry를 검토한다. 이 follow-up에서는 불필요하면 `.gitignore`를 수정하지 않는다.
- `git status --short`에서 `?? services/core/worker`가 사라졌는지 확인한다.

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

- [ ] `services/core/worker`: generated binary를 제거한다.
- [ ] `.gitignore`: 필요할 때만 최소 ignore entry를 추가한다.

#### 테스트 작성

- 별도 코드 테스트는 작성하지 않는다. `git status --short`와 `git diff --check`로 산출물 정리를 검증한다.

#### 중간 검증

```bash
git status --short
git check-ignore -v services/core/worker
```

예상: `services/core/worker`가 status에 없고, 파일이 제거된 경우 check-ignore는 파일 없음 또는 ignore 미적용을 명확히 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/internal/controlplane/runtime.go` | REVIEW_REVIEW_API-1 |
| `services/core/internal/controlplane/runtime_test.go` | REVIEW_REVIEW_API-1 |
| `services/core/internal/worker/runner.go` | REVIEW_REVIEW_API-2 |
| `services/core/internal/worker/runner_test.go` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2 |
| `services/core/internal/storage/postgres.go` | REVIEW_REVIEW_API-3 |
| `services/core/internal/storage/postgres_test.go` | REVIEW_REVIEW_API-3 |
| `services/core/migrations/00001_initial.sql` | REVIEW_REVIEW_API-3 |
| `services/core/cmd/worker/main.go` | REVIEW_REVIEW_API-3 |
| `services/core/worker` | REVIEW_REVIEW_API-4 |
| `.gitignore` | REVIEW_REVIEW_API-4, only if needed |

## 최종 검증

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

```bash
cd services/core && GITO_TEST_DATABASE_URL="$DATABASE_URL" go test -run 'TestPostgres(OperationStorePickQueuedAgentRunOperationFiltersType|AgentRunInputStoreCreateAndGet)' -v ./internal/storage
```

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

```bash
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/**'
```

```bash
git diff --check
```

```bash
git status --short
```

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