<!-- task=m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command plan=0 tag=AUTHORING_RUNTIME -->

# Implementation Plan - AUTHORING_RUNTIME

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

이 plan은 split predecessors `01_iop_long_run`과 `02+01_iop_progress`의 `complete.log`가 필요하다. 구현 시작 전 두 predecessor가 완료되었는지 확인한다. 구현 완료는 active `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 사용자에게 직접 질문하지 말고, 선택된 SDD/Milestone lock 결정만 review stub의 `사용자 리뷰 요청`에 기록한다.

## 배경

`push-command` Task는 첫 authoring 응답 성공만으로 task completion이나 projection으로 넘어가지 않고, 별도 push-only workspace agent 요청과 remote `develop` 확인을 요구한다. 현재 scheduler는 authoring model call을 한 번만 실행하고 결과를 `in_progress`로 두어 develop match를 기다린다. push-only 요청이 없으므로 생성된 milestone이 remote `develop`에 반영되지 않을 수 있다.

## 사용자 리뷰 요청 흐름

구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G08.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. code-review가 해당 요청을 검증하고 필요 시 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
- Task ids:
  - `push-command`: authoring 결과 작성 이후 Core가 workspace agent에 `변경된 내용에 대해 develop 브런치에 푸시해` push-only 요청을 별도 단계로 보낼 수 있다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`
- Acceptance scenarios:
  - `S04`: task=`push-command`; evidence=`scheduler/runtime test or smoke showing push-only request and remote develop verification gate`
- Completion mode: spec-check-on-pass

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/workspace-ops-smoke.md`
- `agent-test/live-cycle-smoke.md`
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
- `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`
- `services/core/internal/model/model.go`
- `services/core/internal/agent/agent.go`
- `services/core/internal/adapters/a2a/client.go`
- `services/core/internal/adapters/a2a/client_test.go`
- `services/core/internal/adapters/openai/client.go`
- `services/core/internal/adapters/openai/client_test.go`
- `services/core/internal/authoring/request.go`
- `services/core/internal/authoring/request_test.go`
- `services/core/internal/authoring/result.go`
- `services/core/internal/authoring/result_test.go`
- `services/core/internal/scheduler/jobs.go`
- `services/core/internal/scheduler/jobs_test.go`
- `services/core/internal/scheduler/river.go`
- `services/core/internal/scheduler/river_test.go`
- `services/core/cmd/server/main.go`
- `services/core/README.md`
- `services/core/go.mod`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md`, `core-smoke`, `workspace-ops-smoke` read.
- focused verification: `cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...`.
- `agent-test/live-cycle-smoke.md` read as runtime-equivalent context; live Plane/secret evidence remains outside this Task's required pass condition unless implementation chooses to run it and records redacted output.

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

- `services/core/internal/scheduler/jobs_test.go:978` proves authoring waits for develop match after model success.
- No test proves a second push-only request is sent.
- No test proves push-only failure becomes `push_failed`/dirty instead of generic `bridge_failed`/error.
- No test proves `wait_type=develop_match` is set only after push-only request succeeds.

### 심볼 참조

- If adding `BuildAuthoringPushInput` or similar helper, update only scheduler/authoring tests.
- If changing `TaskWorker` fields or model/agent interface, update `services/core/internal/scheduler/river.go`, `services/core/cmd/server/main.go`, and test fakes.

### 분할 판단

- split decision policy evaluated.
- Directory dependency `03+01,02_push_command` means predecessors `01_iop_long_run` and `02+01_iop_progress` must finish first.
- Predecessor `01`: missing active/archive `complete.log`.
- Predecessor `02`: missing active/archive `complete.log`.
- This plan is separated because push-only behavior changes authoring completion semantics and failure categories after the timeout/progress foundation is available.

### 범위 결정 근거

- 포함: push-only workspace agent/model request, failure category mapping, scheduler wait gate, unit/runtime-equivalent tests.
- 제외: actual Gito webhook delivery and Plane Todo projection; those are `gito-plane`/`live-gate` Task scopes.
- Exclude provider identity injection/matching; that belongs to `identity-sync`.

### 빌드 등급

- `cloud-G08`: terminal-agent behavior and remote branch verification are central, and a plausible local-only implementation could still fail the runtime roundtrip.

## 구현 체크리스트

- [ ] `push-command` 기준으로 first authoring response success alone does not complete or advance projection; a push-only request is sent with the exact user-facing command and workspace metadata. 검증: scheduler test captures two authoring model/agent calls and the second command text.
- [ ] push-only success records `wait_type=develop_match` and waits for remote `develop` confirmation via the existing Gito/develop match path; push-only failure records `push_failed` and maps slot to `dirty`.
- [ ] `cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...`와 `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Runtime dependency from directory name: `01_iop_long_run` and `02+01_iop_progress` must produce `complete.log` before this subtask starts.
- Do not add hidden dependencies beyond the directory name.

### [AUTHORING_RUNTIME-1] Push-only authoring stage and develop-match gate

문제:

`services/core/internal/scheduler/jobs.go:167` calls `w.Model.Generate` once for Plane-origin authoring. `services/core/internal/scheduler/jobs.go:171` through `services/core/internal/scheduler/jobs.go:191` returns `authoring_run_state=in_progress` immediately after that first response. `services/core/internal/scheduler/jobs.go:105` through `services/core/internal/scheduler/jobs.go:129` keeps the task running for develop match, but no push-only workspace request is ever sent. `services/core/internal/authoring/result.go:47` already has `PushFailed`, but scheduler cannot currently reach it for push-only failure.

Before:

```go
// services/core/internal/scheduler/jobs.go:167
generated, err := w.Model.Generate(ctx, generateInput)
if err != nil {
	return nil, "", err
}
decision := authoring.DecideAuthoringResult(authoring.DecisionInput{
	BridgeSuccess: true,
})
```

해결 방법:

- Add an authoring helper that builds a push-only `model.GenerateInput` from the original task/workspace metadata. The command text must be exactly `변경된 내용에 대해 develop 브런치에 푸시해` unless a local constant is introduced with that exact value.
- Refactor scheduler authoring path into a small `runAuthoringTask` helper: first call authoring generate, then call push-only generate. Do not mark/wait for develop match until push-only returns success.
- Persist distinct metadata keys for first authoring response and push-only response, for example `authoring_response_id`, `push_response_id`, `push_request_state=succeeded`.
- On push-only error, surface an authoring failure that `markFailedWithTask` maps to `authoring_failure_category=push_failed`, `failure_type=execution`, and slot `dirty`.
- Keep remote `develop` confirmation delegated to existing Gito scan/develop match finalization. The scheduler must remain running with `wait_type=develop_match` after push-only succeeds.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/authoring/request.go`: add push-only input helper and constant.
- [ ] `services/core/internal/authoring/request_test.go`: assert command text, workspace metadata, and provider identity are preserved.
- [ ] `services/core/internal/authoring/result.go`: add helper/error mapping if needed so push failures do not become bridge failures.
- [ ] `services/core/internal/authoring/result_test.go`: assert push failure category remains `push_failed`.
- [ ] `services/core/internal/scheduler/jobs.go`: refactor authoring run, perform push-only call, gate develop wait after push success.
- [ ] `services/core/internal/scheduler/jobs_test.go`: add two-call success test, no-complete-before-develop-match assertion, push failure metadata/slot dirty test.
- [ ] `services/core/README.md`: document first authoring response, push-only request, and develop match as separate stages.

테스트 작성:

- Required scheduler unit tests for success and push failure.
- Required authoring helper tests for exact command and metadata preservation.
- No DB migration test required.

중간 검증:

```sh
cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...
```

Expected: pass with push-only success/failure coverage.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/authoring/request.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/authoring/request_test.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/authoring/result.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/authoring/result_test.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/scheduler/jobs.go` | AUTHORING_RUNTIME-1 |
| `services/core/internal/scheduler/jobs_test.go` | AUTHORING_RUNTIME-1 |
| `services/core/README.md` | AUTHORING_RUNTIME-1 |

## 최종 검증

```sh
cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...
```

Expected: pass; Go test cache is not acceptable because scheduler behavior changed.

```sh
git diff --check
```

Expected: no output.

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