# SDD: Plane-Origin Authoring Roundtrip Sync

## 위치

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
- Phase: `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
  - 없음

## 문제 / 비목표

- 문제: Plane `Backlog + AGENT assignee`에서 시작한 Milestone authoring은 사용자 prompt, IOP workspace 실행, git push, Gito webhook, provider identity, Plane projection을 한 사이클로 묶기 때문에 source of truth와 terminal 조건이 분산될 수 있다. 또한 Git/Plane 동기화 identity가 path와 provider work item id에 나뉘면 phase/archive 이동 또는 Plane item 생성 전 상태에서 같은 Milestone 판별이 깨질 수 있으므로, Milestone 파일명 slug 하나를 canonical id로 삼아 active roadmap 전체를 reconcile해야 한다.
- 비목표:
  - Plane `Todo -> In Progress` 이후 실행 lifecycle은 다루지 않는다.
  - Milestone Task를 Plane 하위 티켓으로 생성하거나 plan/code-review 루프와 연결하지 않는다.
  - Plane 외 provider(Jira 등)에 active Milestone missing-create를 적용하지 않는다.
  - archive Milestone을 Plane과 계속 실시간 동기화하는 작업은 다루지 않는다.
  - Milestone 파일명 slug rename/reuse를 자동 추론해 병합하는 작업은 다루지 않는다.
  - IOP 내부 모델 라우팅, queue 구현, RAG, output validation은 다루지 않는다.
  - 외부 agent 제어 표면 또는 MCP tool policy는 확정하지 않는다.

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | `agent-roadmap/phase/*/milestones/<milestone-id>.md` | 파일명 slug `<milestone-id>`가 Git/Plane 동기화의 단일 canonical Milestone id다. path는 active/archive 위치 속성이다. |
| Code | `services/core/internal/authoring/`, `services/core/internal/scheduler/`, `services/core/internal/gitosync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`, `services/core/internal/projectsync/`, `services/core/internal/workitempipeline/` | authoring input, state metadata, Gito scan, active Milestone index, slug identity match, Plane creation/projection, slot finalization의 구현 기준이다. |
| Ledger | `roadmap_sync_identities`, `roadmap_sync_steps` | `milestone_id`를 1차 키로 두고 path, provider work item id, revision, step completion은 속성/foreign key로 기록한다. |
| External Provider | Plane, IOP OpenAI-compatible Responses, Forgejo, Gito | Plane work item id/state, IOP response lifecycle, remote `develop` revision, signed Gito delivery가 외부 관찰 기준이다. Plane work item id는 canonical id가 아니라 provider foreign key다. |
| User Decision | 없음 | 현재 Milestone 범위와 실패 처리 기준은 기존 roadmap/domain rule 표준선으로 확정 가능하다. |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `plane_backlog_assigned` | Plane work item이 `Backlog + AGENT assignee` 조건을 만족한다. | `core_task_created`, `ignored` | Plane webhook 또는 intake scan, work item payload |
| `core_task_created` | Core task와 checkout metadata가 생성된다. | `authoring_in_progress`, `authoring_blocked` | `workitempipeline`, task metadata `checkout` |
| `authoring_in_progress` | IOP Responses authoring 요청을 시작한다. | `authoring_generated`, `authoring_waiting`, `authoring_failed` | `authoring_run_state`, `authoring_run_updated_at`, IOP response result |
| `authoring_generated` | authoring 결과가 workspace checkout에 반영된다. | `push_requested`, `authoring_failed` | model response, workspace dirty state |
| `push_requested` | Core/runtime이 push-only workspace agent 요청을 보낸다. | `develop_pushed`, `push_failed`, `authoring_waiting` | push-only task metadata, remote `develop` verification |
| `develop_pushed` | remote `develop` revision이 변경된다. | `gito_delivered`, `scan_waiting` | Forgejo revision, Gito delivery |
| `gito_delivered` | Gito signed HTTP webhook이 Core에 도착한다. | `active_reconcile_requested`, `delivery_duplicate`, `delivery_failed` | signed delivery 202, Core gito webhook log |
| `active_reconcile_requested` | Core가 local develop checkout을 fetch한다. | `active_milestone_indexed`, `scan_waiting`, `scan_failed` | `gitosync` fetch/revision verification |
| `active_milestone_indexed` | Core가 `agent-roadmap/phase/*/milestones/*.md`를 스캔하고 archive를 제외한다. | `milestone_id_matched`, `plane_missing_detected`, `slug_conflict` | active Milestone index, filename slug |
| `plane_missing_detected` | active Milestone slug가 ledger와 Plane에서 발견되지 않는다. | `plane_item_created`, `creation_failed` | `roadmap_sync_identities`, Plane create API |
| `plane_item_created` | Plane work item 생성이 성공한다. | `milestone_id_matched` | Plane work item id, slug ledger |
| `milestone_id_matched` | active Milestone slug가 ledger 또는 생성된 Plane item과 연결된다. | `plane_projected`, `projection_failed` | `roadmap_sync_steps`, slug identity ledger |
| `plane_projected` | Plane 본문/제목/comment/status projection이 완료된다. | `slot_available`, `reconcile_complete` | Plane API/UI, `roadmap_sync_steps` |
| `slot_available` | Plane-origin authoring task의 성공 terminal 상태를 기록한다. | 없음 | workspace slot state `available`, task metadata |
| `authoring_failed` | timeout, queue timeout, bridge failure, identity failure 등 terminal failure가 확정된다. | `slot_error`, `slot_dirty` | failure category, slot finalizer |
| `slot_dirty` | 수동 복구가 필요한 git dirty 또는 push failure 상태다. | 없음 | slot state `dirty`, task metadata |
| `slot_error` | bridge/provider 오류 또는 복구 불가 failure 상태다. | 없음 | slot state `error`, task metadata |

## Interface Contract

- 계약 원문: 없음
- 입력:
  - Active roadmap Milestone: repo-relative path, filename slug `milestone_id`, Markdown body, status, archive 여부.
  - Plane work item: title, description/comment body, assignee, state, provider work item id, external URL.
  - Core task external ref: provider, external id, external metadata, payload `work_item`.
  - checkout metadata: slot id, slot path, source branch, project sync context.
  - IOP Responses request: thin authoring instruction, user-origin message, workspace metadata, optional push-only request.
  - Gito webhook: signed push delivery, repository/ref/revision metadata.
- 출력:
  - workspace changes on local checkout and remote `develop` commit.
  - `roadmap_sync_identities` and `roadmap_sync_steps` records keyed by `milestone_id`, with path, provider work item id, revision, projection, retry/idempotency state as attributes.
  - Plane work item creation for active Milestones that do not have a mapped Plane item.
  - Plane original comment preservation, body/title projection, `Todo` state move.
  - task metadata and workspace slot terminal state.
- 금지:
  - `roadmap_milestone_path`나 Plane work item id를 canonical Milestone identity로 사용하지 않는다.
  - `agent-roadmap/archive/**` 문서를 active sync 생성/갱신 대상으로 삼지 않는다.
  - 같은 filename slug를 자동으로 두 Milestone에 병합하거나, rename/reuse를 추정하지 않는다.
  - Plane/provider identity나 sync 계약 문구를 기본 authoring 사용자 prompt에 섞지 않는다.
  - 첫 authoring 응답 성공만으로 Plane projection 또는 task completion을 단정하지 않는다.
  - Gito webhook 202 또는 remote commit만으로 full-cycle 완료를 단정하지 않는다.
  - identity missing, identity mismatch, milestone id conflict, push failure를 Plane `Todo` projection으로 넘기지 않는다.
  - tracked docs, roadmap, task logs, 최종 응답에 secret 원문을 기록하지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `thin-prompt` | Plane-origin authoring task에 explicit instruction override가 없다. | `BuildAuthoringGenerateInput`이 기본 instruction을 만든다. | instruction은 `다음 메시지를 바탕으로 마일스톤을 작성해.` 수준으로 얇고 Plane/provider 계약 문구를 포함하지 않는다. |
| S02 | `iop-long-run` | IOP Responses authoring이 10분 이상 걸리거나 queue 대기 중이다. | scheduler가 timeout, retry, stale 판단을 수행한다. | 작업은 조기 `failed`로 확정되지 않고 관찰 가능한 in-progress/stale metadata를 남긴다. |
| S03 | `iop-progress` | dev IOP가 streaming을 지원하거나 지원하지 않는다. | Core가 `/v1/responses` 진행 상태를 기록한다. | stream 미지원은 명시 사유로 기록되고, stream 지원 시 진행 갱신 중단을 감지할 수 있다. |
| S04 | `push-command` | authoring 결과 작성은 성공했지만 remote `develop` 확인은 아직 없다. | Core/runtime이 push-only workspace agent 요청을 보낸다. | push-only 단계와 remote `develop` 확인 전에는 다음 projection 단계로 넘어가지 않는다. |
| S05 | `identity-source` | Plane work item identity가 task external ref, payload, checkout metadata, project sync 설정에 있다. | authoring input과 projection input을 만든다. | 사용자 작성 문구에 의존하지 않고 identity를 유지하며, 누락 시 명시 오류로 멈춘다. |
| S06 | `identity-write` | generated Milestone에 provider identity block이 없다. | Gito callback 이후 scan/match가 실행된다. | Core/runtime 주입 또는 sync ledger로 match 가능 상태가 되거나 명시 failure로 수렴한다. |
| S07 | `develop-match` | remote `develop` revision에 Milestone path 변경 후보가 있다. | Core가 provider identity와 원래 Plane 티켓을 비교한다. | path와 identity가 모두 일치할 때만 Plane projection을 실행한다. |
| S08 | `idempotency-retry` | 같은 Plane 티켓, Milestone path, Gito revision이 재처리된다. | creation sync가 재실행된다. | 중복 Milestone/Plane mutation 없이 남은 단계만 재시도하고 충돌은 중단한다. |
| S09 | `canonical-id` | active Milestone 파일 경로가 `agent-roadmap/phase/<phase>/milestones/<milestone-id>.md`다. | Core가 roadmap sync identity를 만든다. | `<milestone-id>` filename slug 하나가 canonical id가 되고 path/provider work item id는 속성으로 기록된다. |
| S10 | `active-index` | remote `develop`에 active와 archive Milestone이 함께 있다. | Gito wakeup 후 Core가 Milestone index를 스캔한다. | `agent-roadmap/phase/*/milestones/*.md`만 sync 후보가 되고 `agent-roadmap/archive/**`는 제외되며, archive 문서는 slug lookup 후보로만 남는다. |
| S11 | `plane-missing-create` | active Milestone slug가 ledger와 Plane에서 발견되지 않는다. | reconcile job이 실행된다. | Plane work item을 생성하고 slug ledger 및 provider identity marker를 기록하며 중복 생성하지 않는다. |
| S12 | `slug-reconcile` | 같은 Milestone slug가 같은 revision 또는 재시도에서 다시 처리된다. | reconcile이 identity ledger와 completed step을 확인한다. | 중복 Plane item 없이 남은 단계만 재개하고, slug 충돌/재사용/rename 의심은 명시 failure로 멈춘다. |
| S13 | `gito-delivery` | Forgejo push가 Gito subscription 대상 branch에 발생한다. | Gito가 Core signed HTTP webhook을 보낸다. | Core는 delivery를 202로 받고 duplicate 처리를 secret 없이 확인할 수 있다. |
| S14 | `scan-enqueue` | Gito wakeup 후 local develop checkout이 fetch된다. | Core가 active Milestone index를 만들고 slug 단위 reconcile job을 enqueue한다. | 변경 파일 힌트와 무관하게 active Milestone 전체가 reconcile 후보가 되고 archive Milestone은 제외된다. |
| S15 | `plane-todo` | slug match 또는 Plane 생성이 완료된 reconcile job이 있다. | Plane projection을 실행한다. | 원문 댓글 보존, 본문 projection, `[milestone-id]` 제목 갱신, `Todo` 이동이 기록된다. |
| S16 | `slot-finalize` | success/failure/timeout/push failed/identity missing 결과가 확정된다. | slot finalizer가 task metadata와 slot state를 갱신한다. | 성공은 `available`, 수동 git 복구는 `dirty`, bridge/provider 오류는 `error`로 수렴한다. |
| S17 | `remote-baseline` | standard remote runner와 dev services 상태가 stale/dirty일 수 있다. | reset/clean/reclone 또는 rebuild로 테스트 기준을 맞춘다. | dirty runner 상태가 차단 사유가 되지 않고 현재 요청 범위와 같은 검증 환경이 된다. |
| S18 | `full-cycle` | 새 Plane Backlog 티켓과 Git에만 존재하는 active Milestone이 있다. | Core task, IOP authoring, push-only, Gito callback, active reconcile, Plane projection까지 실행한다. | Plane `Todo` projection과 active Milestone missing-create evidence가 함께 남는다. |
| S19 | `regression-doc` | 회귀 원인과 완료 오인 조건이 확인된다. | 운영/테스트 문서를 갱신한다. | filename slug canonical id, archive 제외, identity 누락, IOP queue timeout, push-only 누락, delivery-only 성공을 완료로 오인하지 않는 기준이 문서화된다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | `Spec Completion` 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | `services/core/internal/authoring` unit test showing thin default instruction and forbidden provider terms absent | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `thin-prompt`: authoring request test passed and default instruction text/provider-term absence recorded |
| S02 | core config/scheduler test or smoke evidence for long-running timeout/retry/stale metadata behavior | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `iop-long-run`: timeout/retry/stale thresholds and non-premature failure evidence recorded |
| S03 | IOP streaming capability check and fallback stale-monitor evidence | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `iop-progress`: stream supported path or stream unsupported explicit reason plus stale monitor evidence recorded |
| S04 | scheduler/runtime test or smoke showing push-only request and remote `develop` verification gate | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `push-command`: authoring success does not complete without push-only and remote develop check |
| S05 | authoring/workitempipeline test showing workspace metadata identity retention and missing identity failure | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `identity-source`: external ref/payload/checkout/project sync identity source evidence recorded |
| S06 | gitosync/roadmapsync test or smoke showing identity block absence handled by deterministic injection/ledger or explicit failure | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `identity-write`: identity-less generated Milestone does not silently drop |
| S07 | develop match test covering dirty slot, push failure, mismatch, missing path, and successful match | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `develop-match`: only path+identity match triggers Plane projection |
| S08 | retry/idempotency tests for repeated Plane ticket, Milestone path, Gito revision, and partial failure recovery | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `idempotency-retry`: duplicate mutation prevention and retry/abort distinction recorded |
| S09 | storage/parser/matcher tests showing filename slug is canonical and path/provider ids are attributes | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `canonical-id`: filename slug identity and path/provider attribute evidence recorded |
| S10 | active index scan tests covering active Phase Milestones, archive exclusion, and slug lookup candidate behavior | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `active-index`: active-only sync candidate evidence recorded |
| S11 | Plane adapter/reconcile tests or smoke proving missing active Milestone creates one Plane item and records ledger/marker | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `plane-missing-create`: missing active Milestone creation evidence recorded |
| S12 | retry/idempotency tests for same slug reprocess, partial failure resume, and slug conflict/reuse failure | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `slug-reconcile`: duplicate prevention and conflict failure evidence recorded |
| S13 | secret-redacted live evidence for Forgejo -> Gito -> Core signed delivery and duplicate handling | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `gito-delivery`: signed delivery 202, Core log, duplicate result recorded without secret |
| S14 | gitosync/roadmapsyncpipeline test or smoke proving active index scan-to-reconcile enqueue and archive exclusion | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `scan-enqueue`: active reconcile enqueue evidence recorded |
| S15 | Plane UI/API plus `roadmap_sync_steps` evidence for comment/body/title/Todo projection keyed by milestone id | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `plane-todo`: original comment, body update, title update, Todo move recorded |
| S16 | scheduler/projectsync finalizer tests or smoke for `available`, `dirty`, `error` terminal mapping | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `slot-finalize`: terminal task metadata and slot state evidence recorded |
| S17 | standard remote runner setup log with branch/revision and clean/reclone/rebuild actions as needed | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `remote-baseline`: runner baseline evidence recorded |
| S18 | `agent-test/live-cycle-smoke.md` run evidence with secret redaction from Plane Backlog to Plane Todo and active missing-create | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `full-cycle`: authoring roundtrip and active reconcile evidence recorded |
| S19 | doc diff in `agent-test/live-cycle-smoke.md` or core README covering regression prevention criteria | `agent-task/m-plane-origin-authoring-roundtrip-sync/...` | `regression-doc`: regression criteria documented |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 plan/code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.

## 사용자 리뷰 이력

- 없음

## 작업 컨텍스트

- 표준선: Plane 티켓 본문과 authoring prompt는 사용자 요청 원문 중심으로 유지하고, Git/Plane 동기화의 canonical Milestone id는 파일명 slug 하나로 둔다. path와 Plane work item id는 각각 현재 위치와 provider foreign key이며, provider identity, project sync 설정, push 검증, Gito delivery, Plane creation/projection은 Core/runtime의 결정적 책임으로 둔다.
- 후속 SDD: 없음
