<!-- task=m-control-plane-operator-actions/01_job_create plan=1 tag=REVIEW_JOB_CREATE -->

# Plan - REVIEW_JOB_CREATE

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

구현의 마지막 단계는 반드시 active `CODE_REVIEW-local-G05.md`에서 구현 에이전트 소유 섹션을 실제 변경 내용, 설계 결정, 검증 출력으로 채우는 것이다. 검증 명령을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log` 작성, archive 이동은 code-review-skill 전용이다.

선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 구현이 막히면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 연결 대상, 증거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service 차단, 일반 범위 조정, 검증 증거 공백처럼 후속 agent가 닫을 수 있는 문제는 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다.

## 배경

이 follow-up은 `JOB_CREATE` 1차 리뷰 FAIL을 닫기 위한 좁은 보완 작업이다. 현재 테스트는 통과하지만 job create form의 fresh 입력 제출 경로와 `OtoClientApp`의 실제 write/refresh 흐름을 충분히 검증하지 못한다. Core API나 proto는 변경하지 않고 Flutter adapter/UI/test만 보완한다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 리뷰가 필요한 경우는 선택된 SDD 결정 또는 선택된 Milestone lock 결정이 실구현을 차단할 때뿐이다. 구현 에이전트는 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 복사된 active review stub의 `사용자 리뷰 요청` 섹션만 채우고 멈춘다. 직접 사용자 프롬프트는 금지되며, code-review가 요청 타당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md`
- Task ids:
  - `job-create`: 기존 `POST /api/v1/jobs` 계약을 사용하는 job 생성 action adapter와 UI 상태를 연결한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md`
- Acceptance scenarios:
  - `S02`: task=`job-create`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, job create adapter/widget tests, related `server_test.go` route coverage reference
- Completion mode: spec-check-on-pass

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-control-plane-operator-actions/01_job_create/plan_local_G06_0.log`
- Prior review: `agent-task/m-control-plane-operator-actions/01_job_create/code_review_local_G06_0.log`
- Verdict: FAIL
- Required summary:
  - `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart:90` computes submit enabled state from controllers without rebuilding on input changes.
  - `apps/client/test/widget_test.dart:500` does not submit the form or verify write/refresh/error/duplicate-submit behavior.
  - `apps/client/test/widget_test.dart:407` omits required 400 and timeout mapping tests.
- Suggested summary:
  - `apps/client/lib/src/app/oto_client_app.dart:72` captures the initial `widget.writeClient` in a `late final`; submit should choose `widget.writeClient ?? _defaultWriteClient`.
- Affected files:
  - `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart`
  - `packages/flutter/oto_console/test/oto_console_test.dart`
  - `apps/client/lib/src/app/oto_client_app.dart`
  - `apps/client/test/widget_test.dart`
- Prior verification evidence:
  - `flutter test` in `apps/client`: passed, but app-level job create assertions were incomplete.
  - `flutter test` in `packages/flutter/oto_console`: passed, but the job create test reused widget state across scenarios.
  - `git diff --check`: passed.
- Roadmap/spec carryover: keep the same `job-create` Roadmap Target and SDD S02 Spec Target.
- Narrow reread allowed if needed: the two prior log files listed above only.

## 분석 결과

### 읽은 파일

- `agent-ops/skills/common/code-review/SKILL.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-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md`
- `agent-task/m-control-plane-operator-actions/01_job_create/PLAN-local-G06.md`
- `agent-task/m-control-plane-operator-actions/01_job_create/CODE_REVIEW-local-G06.md`
- `apps/client/lib/src/app/core_connection_client.dart`
- `apps/client/lib/src/app/oto_client_app.dart`
- `apps/client/test/widget_test.dart`
- `apps/client/pubspec.yaml`
- `packages/flutter/oto_console/lib/src/oto_console_contract.dart`
- `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart`
- `packages/flutter/oto_console/lib/src/oto_console_shell.dart`
- `packages/flutter/oto_console/lib/oto_console.dart`
- `packages/flutter/oto_console/test/oto_console_test.dart`
- `packages/flutter/oto_console/pubspec.yaml`
- `services/core/internal/httpserver/job_handlers.go`
- `services/core/internal/httpserver/dto.go`
- `services/core/internal/httpserver/server_test.go`

### 테스트 환경 규칙

- 선택한 test_env: `local`.
- `agent-test/local/rules.md`가 존재하며 전체를 읽었다.
- matching profile: `apps/client`와 `packages/flutter/oto_console`에 dedicated project smoke 문서는 없었다. local rules의 Monorepo Targets가 `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`를 제공하므로 fallback profile 생성은 하지 않는다.
- 적용 명령: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, `git diff --check`.
- `<확인 필요>` 값은 없었다.

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

- `OtoJobsSurface` fresh form에서 입력 후 submit button 활성화와 callback payload 전달이 신뢰성 있게 검증되지 않는다.
- `OtoClientApp` job create test는 form render만 확인하며 write client 호출, returned job id refresh, error 표시, duplicate submit guard를 검증하지 않는다.
- `OtoHttpCoreWriteClient` job create adapter는 400/timeout mapping test가 없다.

### 심볼 참조

- renamed/removed symbol: none.
- 새 심볼 참조는 `rg --sort path "OtoJobsSurface\\(|OtoJobCreateDraft|OtoActionViewState|OtoCoreWriteClient|OtoHttpCoreWriteClient|createJob" apps packages -g '*.dart'`로 확인했다.

### 분할 판단

- 기존 split subtask `agent-task/m-control-plane-operator-actions/01_job_create`의 follow-up이며 새 subtask로 분리하지 않는다.
- 세 Required 이슈는 같은 job-create action path의 production fix와 regression tests라 한 루프에서 함께 검증하는 편이 안전하다.
- sibling `02+01_execution_actions`, `03+01_runner_actions`는 이 subtask PASS 이후 진행해야 하므로 이 follow-up은 같은 subtask directory에 남긴다.

### 범위 결정 근거

- Core route, proto, persistence schema는 변경하지 않는다. SDD S02는 기존 `POST /api/v1/jobs` 소비만 요구한다.
- execution/runner actions는 sibling plan 범위다.
- UI 문구와 layout은 submit state 버그 수정에 필요한 범위로만 만진다.
- 새 package는 추가하지 않는다.

### 빌드 등급

- 결정: `local-G05`.
- 근거: bounded Flutter UI/test follow-up이며 deterministic widget/unit tests로 검증 가능하지만, app-level async submit/refresh와 action-state regression을 함께 닫아야 한다.

## 구현 체크리스트

- [ ] `OtoJobsSurface` fresh form 입력 후 submit 활성화와 callback payload 전달을 수정하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoJobsSurface renders job create action states"`가 통과하게 한다.
- [ ] `OtoClientApp` job create 실제 제출, success refresh, error 표시, duplicate submit guard와 live `writeClient` 선택을 보강하고 `cd apps/client && flutter test --plain-name "OtoClientApp wires job create action and refresh"`가 통과하게 한다.
- [ ] `OtoHttpCoreWriteClient`의 400/timeout mapping regression tests를 추가하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps job create responses"`가 통과하게 한다.
- [ ] `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_JOB_CREATE-1] Fix Fresh Form Submit State

문제: `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart:90-140`은 `canSubmit`을 controller 값으로 계산하지만 입력 변경 시 parent rebuild가 없다. 기존 test `packages/flutter/oto_console/test/oto_console_test.dart:363-456`은 여러 state를 한 test에서 재-pump해 controller state를 재사용하므로 fresh form disabled 상태를 놓칠 수 있다.

해결 방법: 두 `TextField`에 `onChanged: (_) => setState(() {})`를 추가하거나 controller listener를 `initState`/`dispose`에서 관리해 submit button enabled 상태를 갱신한다. 테스트는 fresh pump에서 빈 값이면 button disabled, id/name 입력 후 button enabled, tap 시 `OtoJobCreateDraft` payload가 전달되는 순서로 검증한다.

수정 파일 및 체크리스트:

- [ ] `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart`: 입력 변경 시 submit enabled 상태 rebuild 추가.
- [ ] `packages/flutter/oto_console/test/oto_console_test.dart`: fresh widget submit callback regression test로 분리하거나 기존 test의 state reuse를 제거.

테스트 작성:

- 작성: `packages/flutter/oto_console/test/oto_console_test.dart`
- 테스트명: `OtoJobsSurface renders job create action states`
- assertion goal: fresh form empty disabled, id/name 입력 후 enabled, tap callback payload, submitting disabled, success/error message text.
- fixture: `OtoJobsSurface(snapshot: OtoSurfaceSnapshot.empty(), jobCreateState: ..., onCreateJob: ...)`.

중간 검증:

```bash
cd packages/flutter/oto_console && flutter test --plain-name "OtoJobsSurface renders job create action states"
```

예상 결과: job create action state widget test가 통과한다.

### [REVIEW_JOB_CREATE-2] Verify App Submit, Refresh, Error, And Guard

문제: `apps/client/test/widget_test.dart:500-555`는 `OtoClientApp wires job create action and refresh`라는 이름과 달리 form render만 확인하고 `createCallCount == 0`을 기대한다. `apps/client/lib/src/app/oto_client_app.dart:72-123`도 submit 시 최초 `widget.writeClient`를 capture한 `_defaultWriteClient`만 사용한다.

해결 방법: `_defaultWriteClient`는 기본 `OtoHttpCoreWriteClient`만 보관하고 `_submitJobCreate`에서 `final writeClient = widget.writeClient ?? _defaultWriteClient;`를 사용한다. app widget test는 Pipelines 탭에서 id/name 입력 후 Create를 tap해 fake write client 호출과 draft payload를 검증하고, success returned id가 `_fetchJobs` 대상으로 들어가 read surface에 나타나는지 확인한다. 별도 assertion으로 failed result message 표시와 pending future 상태에서 두 번째 tap이 write call을 늘리지 않는지 검증한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/src/app/oto_client_app.dart`: live `widget.writeClient ?? _defaultWriteClient` 선택으로 stale injected client 위험 제거.
- [ ] `apps/client/test/widget_test.dart`: job create submit success/refresh/error/duplicate guard assertions 추가.
- [ ] `apps/client/test/widget_test.dart`: 기존 render-only assertions를 실제 behavior assertions로 대체.

테스트 작성:

- 작성: `apps/client/test/widget_test.dart`
- 테스트명: `OtoClientApp wires job create action and refresh`
- assertion goal: fake write client called once with draft, created job id fetched after success, success or job row visible, failed result message visible, submitting duplicate tap ignored.
- fixture: `_FakeCoreWriteClient`, `_FakeCoreReadClient`, controllable `Completer<OtoCoreWriteResult<OtoJobRecord>>`.

중간 검증:

```bash
cd apps/client && flutter test --plain-name "OtoClientApp wires job create action and refresh"
```

예상 결과: app-level job create wiring test가 실제 submit/refresh/error/guard를 검증하며 통과한다.

### [REVIEW_JOB_CREATE-3] Add 400 And Timeout Adapter Tests

문제: `apps/client/test/widget_test.dart:407-492`는 201/409/invalid URL/500만 검증한다. plan과 SDD S02는 `POST /api/v1/jobs`의 201/400/409/timeout mapping을 요구한다.

해결 방법: 같은 test group에 400 response body의 `error`/`message` 추출과 timeout을 추가한다. timeout은 짧은 `timeout` 값과 delayed `MockClient` response로 deterministic하게 검증한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/test/widget_test.dart`: 400 invalid request body mapping test 추가.
- [ ] `apps/client/test/widget_test.dart`: timeout mapping test 추가.
- [ ] 필요하면 `OtoHttpCoreWriteClient` message/status mapping을 테스트 기대에 맞게 최소 수정.

테스트 작성:

- 작성: `apps/client/test/widget_test.dart`
- 테스트명: `OtoHttpCoreWriteClient maps job create responses`
- assertion goal: 400은 `failed` + statusCode 400 + Core error message, timeout은 `failed` + `Timed out`.
- fixture: `MockClient`, short client timeout.

중간 검증:

```bash
cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps job create responses"
```

예상 결과: adapter mapping target test가 통과한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart` | REVIEW_JOB_CREATE-1 |
| `packages/flutter/oto_console/test/oto_console_test.dart` | REVIEW_JOB_CREATE-1 |
| `apps/client/lib/src/app/oto_client_app.dart` | REVIEW_JOB_CREATE-2 |
| `apps/client/test/widget_test.dart` | REVIEW_JOB_CREATE-2, REVIEW_JOB_CREATE-3 |

## 최종 검증

```bash
cd apps/client && flutter test
cd packages/flutter/oto_console && flutter test
git diff --check
```

예상 결과: 모든 명령이 exit 0이다. Widget/unit test는 fresh process로 실행되며 Flutter test cache 여부는 해당 없음이다.

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