<!-- task=m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke plan=2 tag=REVIEW_REVIEW_TEST -->

# Plan - REVIEW_REVIEW_TEST

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

이 follow-up은 `code_review_cloud_G07_1.log`의 Required 이슈만 해결한다. 사용자에게 직접 묻지 말고, 사용자 결정이 필요하다고 판단되면 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 남긴다. 구현의 마지막 단계는 active `CODE_REVIEW-cloud-G07.md` 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채우는 것이다.

## 배경

이전 루프는 remote runner 최신화, webhook task 생성, authoring enqueue, Plane Todo/title/body projection, 두 번째 smoke slot reuse evidence를 수집했다. 리뷰 중 DB/Plane/API 재조회로 live evidence는 보강 가능했지만, `RoadmapCreationSyncWorker`가 idempotent duplicate/already-complete sync에서도 오래된 task checkout metadata로 slot을 다시 `available` 처리할 수 있는 correctness 문제가 발견되어 FAIL됐다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
- Task ids:
  - `live-webhook-task`: 원격 dev Plane에서 `Backlog + AGENT assignee` 상위 티켓을 만들거나 갱신하면 webhook만으로 Core task가 생성되고 checkout metadata가 남는다.
  - `webhook-auto-enqueue`: webhook-dispatched creation task가 별도 수동 API 호출 없이 authoring queue로 이어진다.
  - `live-creation-projection`: matched develop scan/job 입력이 `roadmapsyncpipeline.Service`와 `RoadmapCreationSyncWorker`를 통해 실제 Plane 원문 댓글 보존, 본문/제목 갱신, `Todo` 이동으로 수렴한다.
- Completion mode: check-on-pass

## 분석 결과

### 리뷰 이슈

- Required source: `agent-task/m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke/code_review_cloud_G07_1.log`
- Problem locus:
  - `services/core/internal/scheduler/roadmap_sync_jobs.go`
  - `services/core/internal/workflow/service.go`
  - `services/core/internal/db/workspace_slots.sql.go`

### 원인

`SyncActionComplete`는 creation sync ledger가 이미 모두 기록되어 이번 호출에서 provider projection을 하지 않았다는 idempotent 결과다. 그런데 현재 worker는 `projected`와 `complete` 모두에서 `CompleteTaskByExternalRef`를 호출하고, 반환된 task의 checkout metadata로 slot을 무조건 `available` 처리한다. `CompleteTaskByExternalRef`는 external task가 이미 `completed`인 경우에도 기존 task를 정상 반환하므로, duplicate/late sync job이 과거 task의 slot id를 새 task가 사용하는 동안 다시 `available`로 덮을 수 있다.

### 범위 결정 근거

- 원격 runner dirty state나 secret 상태를 사용자 blocker로 되돌리지 않는다.
- 이미 확보된 live evidence를 재수집하는 것이 목적이 아니다. 이번 follow-up은 idempotent terminal finalization correctness와 회귀 테스트가 목적이다.
- `workspace_slots` 스키마 확장이나 owner lease 도입은 범위 밖이다. 현재 인터페이스 안에서 “이번 호출이 실제 task completion transition을 수행했는지”를 구분해 오래된 task의 slot release를 막는다.

## 구현 체크리스트

- [ ] `CompleteTaskByExternalRef` 계열 finalizer가 task와 함께 `completed` transition 발생 여부를 반환하도록 API를 조정하거나 동등한 명시 결과 타입을 도입한다.
- [ ] `RoadmapCreationSyncWorker`가 task가 이번 호출에서 실제로 완료 전이된 경우에만 slot terminal state를 `available`로 적용하게 수정한다. 이미 완료된 duplicate/already-complete sync는 slot update를 하지 않는다.
- [ ] `SyncActionComplete` crash-recovery path는 유지한다. 즉 ledger는 complete지만 task가 아직 running이면 task를 completed로 전이하고 slot을 release해야 한다.
- [ ] duplicate/already-completed external task가 old checkout slot metadata를 반환해도 `WorkspaceSlotStateUpdater`가 호출되지 않는 단위 테스트를 추가한다.
- [ ] ledger complete + running task crash-recovery path에서는 finalizer transition과 slot release가 모두 일어나는 단위 테스트를 추가하거나 기존 테스트를 명확히 확장한다.
- [ ] `go test -count=1 ./internal/scheduler/... ./internal/workflow/... ./internal/roadmapsyncpipeline/...`를 실행한다.
- [ ] `go test ./...`와 `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_TEST-1] Idempotent slot terminal finalization

문제: `RoadmapCreationSyncWorker`가 already-complete duplicate sync에서도 이전 task checkout metadata로 slot을 무조건 release할 수 있다.

해결 방법:

Before:

```text
SyncActionProjected or SyncActionComplete
  -> CompleteTaskByExternalRef(...)
  -> applyTaskSlotTerminalState(..., available)

CompleteTaskByExternalRef:
  if task.Status == completed:
    return existing completed task, nil
```

After:

```text
SyncActionProjected or SyncActionComplete
  -> CompleteTaskByExternalRef(...) returns task + transitioned/completedNow flag
  -> applyTaskSlotTerminalState(..., available) only when completedNow == true

CompleteTaskByExternalRef:
  if task.Status == completed:
    return existing completed task, completedNow=false, nil
  else:
    transition to completed and return completedNow=true
```

수정 파일 및 체크리스트:

- [ ] `services/core/internal/workflow/service.go`
- [ ] `services/core/internal/workflow/service_test.go`
- [ ] `services/core/internal/scheduler/roadmap_sync_jobs.go`
- [ ] `services/core/internal/scheduler/river_test.go`
- [ ] 필요 시 `services/core/internal/scheduler/river.go`와 fake finalizer call sites

테스트 작성:

- already completed duplicate external task: finalizer returns completed task with old checkout metadata and `completedNow=false`; worker must not call slot updater.
- crash-recovery complete path: finalizer transitions running task to completed with `completedNow=true`; worker must call slot updater once with `available`.
- projected path: normal projection transition still releases slot once.

중간 검증:

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

## 의존 관계 및 구현 순서

1. Adjust finalizer return contract and update compile call sites.
2. Add/adjust scheduler and workflow unit tests for transitioned vs already-completed outcomes.
3. Run focused and full tests.
4. Fill `CODE_REVIEW-cloud-G07.md`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/workflow/service.go` | REVIEW_REVIEW_TEST-1 |
| `services/core/internal/workflow/service_test.go` | REVIEW_REVIEW_TEST-1 |
| `services/core/internal/scheduler/roadmap_sync_jobs.go` | REVIEW_REVIEW_TEST-1 |
| `services/core/internal/scheduler/river_test.go` | REVIEW_REVIEW_TEST-1 |
| `agent-task/m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_TEST-1 |

## 최종 검증

- `cd services/core && go test -count=1 ./internal/scheduler/... ./internal/workflow/... ./internal/roadmapsyncpipeline/...`
- `cd services/core && go test ./...`
- `git diff --check`

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 채운다.
