<!-- task=m-milestone-work-item-creation-sync/10+09_plane_draft_authoring plan=0 tag=API -->

# Plane Draft Authoring Contract

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

`CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 구현 후 검증 명령의 실제 stdout/stderr를 붙이고 active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 종결, log rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret, 범위 충돌이 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 근거, 실행 명령/출력, 재개 조건을 적고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 재실행/증거 수집으로 닫을 수 있는 검증 공백은 사용자 리뷰 요청이 아니다.

## 배경

Plane-origin authoring route는 IOP Edge HTTP bridge까지 연결되어 있지만, workspace agent에게 1상위 티켓=1마일스톤, provider identity block, 기존 Milestone 갱신 우선, duplicate 금지 같은 초안 작성 계약을 충분히 전달하지 않는다. `roadmapsync.ParseMilestoneIdentity`와 identity map은 후단 매칭/재시도 기준을 갖고 있으므로, authoring 입력이 그 기준을 안정적으로 생산하도록 보강해야 한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active `CODE_REVIEW-local-G06.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자에게 묻지 않으며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
- Task ids:
  - `plane-draft`: Plane ticket body to idempotent Milestone draft authoring
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
- `agent-ops/rules/project/domain/workspace-ops/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/update-roadmap/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/workspace-ops-smoke.md`
- `services/core/internal/model/model.go`
- `services/core/internal/adapters/openai/client.go`
- `services/core/internal/adapters/openai/client_test.go`
- `services/core/internal/workitem/provider.go`
- `services/core/internal/workitem/provider_test.go`
- `services/core/internal/workitempipeline/service.go`
- `services/core/internal/workitempipeline/service_test.go`
- `services/core/internal/authoring/request.go`
- `services/core/internal/authoring/request_test.go`
- `services/core/internal/scheduler/jobs.go`
- `services/core/internal/scheduler/jobs_test.go`
- `services/core/internal/roadmapsync/identity.go`
- `services/core/internal/roadmapsync/identity_test.go`
- `services/core/internal/roadmapsync/milestone_parser.go`
- `services/core/internal/roadmapsync/milestone_parser_test.go`
- `services/core/internal/roadmapsync/develop_match.go`
- `services/core/internal/roadmapsync/develop_match_test.go`
- `services/core/internal/roadmapsync/retry.go`
- `services/core/internal/roadmapsync/retry_test.go`

### 테스트 환경 규칙

`test_env=local`. `agent-test/local/rules.md`를 읽었고 core 변경은 `agent-test/local/core-smoke.md`, roadmap/task artifact 변경은 `agent-test/local/workspace-ops-smoke.md`를 적용한다. 필수 검증은 focused `go test -count=1`과 `git diff --check`다. local 규칙의 remote runner 표준은 남아 있으나 이 계획은 repo-local 명령으로 검증 계약을 고정한다.

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

- Authoring instructions include title/body, provider identity, existing Milestone update first, duplicate prevention: 기존 테스트는 workspace path/source branch와 generic fallback만 확인한다. 새 assertion 필요.
- Tenant/project/external URL identity context in authoring input: 기존 `providerIdentity`는 provider/work item id만 반환한다. 새 테스트 필요.
- Provider identity parser and develop match: 기존 `roadmapsync` 테스트가 있음. 이 plan에서는 parser 자체 변경이 필요 없으면 기존 테스트로 회귀 확인한다.

### 심볼 참조

rename/remove 없음. 수정 후보 symbols: `authoring.pipelinePayload`, `authoring.providerIdentity`, `authoring.BuildAuthoringGenerateInput`, `authoring.buildAuthoringInstructions`, `model.WorkspaceMetadata`는 필드 추가가 필요한 경우에만 수정한다.

### 분할 판단

split decision policy를 먼저 평가했다. shared task group은 `m-milestone-work-item-creation-sync`다.

- `09_creation_trigger_gate`: predecessor. 현재 active/archive `complete.log`가 없으므로 이 plan implementation은 시작하지 않는다.
- `10+09_plane_draft_authoring`: 이 plan. 디렉터리명의 `+09`가 runtime 의존성 source of truth다.

의존 관계 및 구현 순서: `agent-task/m-milestone-work-item-creation-sync/09_creation_trigger_gate/complete.log` 또는 matching archive path가 생긴 뒤 이 plan을 구현한다.

### 범위 결정 근거

실제 git push executor, remote develop scanner, Plane Todo projection, Todo 이후 In Progress lifecycle은 기존/다른 Task 소유다. 이번 변경은 workspace agent에게 Milestone 초안 산출 계약을 명시하고, authoring input이 후단 `roadmapsync` identity/match 기준을 생산하도록 만드는 데 한정한다. IOP Edge wire payload는 기존 `metadata.workspace` 호환을 깨지 않는다. provider/work item 세부는 instructions 또는 client-side metadata로 추가한다.

### 빌드 등급

`local-G06`: authoring/model boundary와 scheduler 테스트를 건드릴 수 있지만 핵심은 deterministic prompt contract와 unit tests이며, 후단 identity/match 테스트로 회귀를 닫을 수 있다.

## 구현 체크리스트

- [ ] `09_creation_trigger_gate` predecessor complete.log가 active 또는 archive 경로에 있는지 확인하고, 없으면 구현하지 않고 review stub의 사용자 리뷰 요청이 아니라 plan dependency 미충족으로 보고한다.
- [ ] Plane work item identity context를 authoring input에서 구조적으로 보존한다. 검증: provider, tenant, project, work item id가 instructions 또는 client-side metadata에 포함된다.
- [ ] fallback authoring instructions에 1상위 티켓=1마일스톤, 기존 Provider identity 검색 후 갱신, `## Provider identity` block 작성, `develop` commit/push, duplicate 금지 조건을 명시한다.
- [ ] focused Go tests와 `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

1. `09_creation_trigger_gate`가 PASS되어 `complete.log`를 남긴 뒤 시작한다.
2. Authoring input/context 보강을 먼저 적용한다.
3. Instruction contract와 tests를 갱신한다.
4. focused tests와 `git diff --check`를 실행한다.

### [API-1] Preserve Full Work Item Identity For Authoring

문제: `authoring.providerIdentity`는 [request.go](/config/workspace/nomadcode/services/core/internal/authoring/request.go:49)에서 provider/work item id만 반환한다. `model.WorkspaceMetadata`도 [model.go](/config/workspace/nomadcode/services/core/internal/model/model.go:16)에서 provider/work item id만 담고 tenant/project가 없다. 그런데 `roadmapsync.Identity`의 strict retry key는 [identity.go](/config/workspace/nomadcode/services/core/internal/roadmapsync/identity.go:105)에서 tenant/project까지 비교한다.

Before:

```go
// services/core/internal/authoring/request.go:49
func providerIdentity(task storage.Task, payload pipelinePayload) (provider, workItemID string) {
	provider = task.Source
	workItemID = task.ID
```

해결 방법: `providerIdentity`를 `workItemIdentity` helper로 바꾸고 provider, tenant, project, work item id, external URL, state id를 payload `work_item`과 task external fields에서 추출한다. `model.WorkspaceMetadata`는 wire 호환을 깨지 않는 범위에서 tenant/project fields를 추가하거나, fields를 추가하지 않는다면 instructions에 identity를 반드시 넣는다. `openai.buildRequestMetadata`는 기존 `metadata.workspace` string behavior를 유지한다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/authoring/request.go`: identity struct/helper 추가, `BuildAuthoringGenerateInput`에서 identity context 보존.
- [ ] `services/core/internal/authoring/request_test.go`: tenant/project/work item id/external URL identity assertion.
- [ ] `services/core/internal/model/model.go`: 필요한 경우 `WorkspaceMetadata` additive fields 추가.
- [ ] `services/core/internal/adapters/openai/client.go`: 수정이 필요하다면 `metadata.workspace` 기존 shape 회귀 테스트 유지.

테스트 작성: `TestBuildAuthoringGenerateInputCarriesProviderIdentityContext`를 추가한다. OpenAI adapter를 수정하면 `TestGenerateWorkspaceMetadataUsesFlatWorkspacePath`를 갱신해 기존 flat workspace path가 유지되는지 확인한다.

중간 검증:

```bash
cd services/core && go test -count=1 ./internal/authoring ./internal/adapters/openai
```

### [API-2] Idempotent Milestone Draft Instructions

문제: fallback instructions는 [request.go](/config/workspace/nomadcode/services/core/internal/authoring/request.go:122)에서 active Phase 아래 Milestone을 작성/갱신하고 develop에 push하라고만 말한다. `roadmapsync.ParseMilestoneIdentity`는 [milestone_parser.go](/config/workspace/nomadcode/services/core/internal/roadmapsync/milestone_parser.go:39)에서 strict Provider identity block을 요구하지만, authoring prompt는 그 block 작성, 기존 block 검색, duplicate 금지를 명시하지 않는다.

Before:

```go
// services/core/internal/authoring/request.go:124
b.WriteString("Your task is to author or update the agent-roadmap Milestone file for the following Plane work item.\n\n")
b.WriteString("Use the roadmap skill to create or update the Milestone under the active Phase, then commit and push to the develop branch.\n\n")
```

해결 방법: `buildAuthoringInstructions`가 identity context를 받아 다음 계약을 명시한다.

```text
- One Plane top-level work item maps to exactly one agent-roadmap Milestone.
- First search active Milestones for a Provider identity block matching provider/tenant/project/work item id; update that file if found.
- If none exists, create one Milestone under the active Phase and include a `## Provider identity` block.
- The block must include provider, tenant, project, work item id, and milestone id/roadmap item id when known.
- Commit and push the Milestone file change to develop from the slot checkout.
- Do not create duplicate Milestones or Plane child work items in this slice.
```

수정 파일 및 체크리스트:

- [ ] `services/core/internal/authoring/request.go`: fallback instructions 보강.
- [ ] `services/core/internal/authoring/request_test.go`: instructions에 provider identity block, duplicate prevention, active Milestone search, develop push가 포함되는지 assertion.
- [ ] `services/core/internal/roadmapsync/milestone_parser_test.go`: parser compatibility 회귀를 focused command로 실행.

테스트 작성: `TestBuildAuthoringGenerateInputFallbackInstructionsRequireIdentityBlockAndNoDuplicate`를 추가한다.

중간 검증:

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

### [API-3] Scheduler Authoring Contract Regression

문제: scheduler는 Plane-origin task를 IOP model bridge로 라우팅하지만 [jobs.go](/config/workspace/nomadcode/services/core/internal/scheduler/jobs.go:126) 이후에는 model response를 별도 draft artifact로 해석하지 않는다. 이 plan은 별도 변환 응답을 만들지 않는 기존 경계를 유지하면서 authoring request contract만 강화해야 한다.

해결 방법: scheduler behavior는 유지한다. `TestRunTaskRoutesPlaneOriginToAuthoringBridge`, `TestRunTaskAuthoringPreemptsA2AClient`, `TestRunTaskAuthoringSuccessRecordsState`가 계속 통과해야 한다. instruction/input 변경으로 captured `model.GenerateInput` assertions가 필요하면 scheduler test에 최소 assertion만 추가한다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/scheduler/jobs_test.go`: 필요 시 captured input에 identity contract가 전달되는지 regression assertion 추가.
- [ ] `services/core/internal/scheduler/jobs.go`: behavior 변경은 피한다. 불가피하면 계획 대비 변경 사항에 이유를 기록한다.

테스트 작성: scheduler behavior가 바뀌지 않으면 기존 tests만 사용한다. `GenerateInput` assertions를 보강하면 `jobs_test.go`에 추가한다.

중간 검증:

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

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/authoring/request.go` | API-1, API-2 |
| `services/core/internal/authoring/request_test.go` | API-1, API-2 |
| `services/core/internal/model/model.go` | API-1 |
| `services/core/internal/adapters/openai/client.go` | API-1 |
| `services/core/internal/adapters/openai/client_test.go` | API-1 |
| `services/core/internal/roadmapsync/milestone_parser_test.go` | API-2 |
| `services/core/internal/scheduler/jobs.go` | API-3 |
| `services/core/internal/scheduler/jobs_test.go` | API-3 |

## 최종 검증

```bash
cd services/core && go test -count=1 ./internal/authoring ./internal/roadmapsync ./internal/adapters/openai ./internal/scheduler
git diff --check
```

기대 결과: 모든 package가 `ok`로 종료하고 `git diff --check` 출력이 없다. 캐시 출력은 허용하지 않으며 `-count=1`로 fresh 실행한다.

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