<!-- task=m-workflow-core/02+01_transition_ownership plan=0 tag=WFC-OWNERSHIP -->

# Workflow Core Transition Ownership Plan

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

이 계획은 `01_lifecycle_model` PASS 후 진행한다. 구현 후 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채운다. blocker가 사용자 결정이나 외부 환경 준비라면 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 최종 판정과 archive는 code-review 전용이다.

## 배경

현재 enqueue는 workflow service가 직접 `UpdateStatus`와 `FailTask`를 호출하고, worker는 `TaskLifecycle`을 통해 start/complete/fail을 호출한다. 이 구조는 상태 전이 책임이 service와 lifecycle에 나뉘어 있어 실패 metadata와 retry 정책을 붙일 때 중복을 만들 수 있다. 이 작업은 enqueue까지 lifecycle 경계로 모으고 scheduler는 실행 단계만 담당한다는 기준을 코드, 문서, 테스트에 고정한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 USER_REVIEW 작성 여부를 판단한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/workflow-core/milestones/workflow-core.md`
- Task ids:
  - `transition-ownership`: scheduler와 workflow service의 상태 전이 책임 정리
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/workflow-core/PHASE.md`
- `agent-roadmap/phase/workflow-core/milestones/workflow-core.md`
- `services/core/internal/workflow/lifecycle.go`
- `services/core/internal/workflow/service.go`
- `services/core/internal/workflow/service_test.go`
- `services/core/internal/scheduler/jobs.go`
- `services/core/internal/scheduler/jobs_test.go`
- `services/core/internal/scheduler/river.go`
- `services/core/internal/http/handlers.go`
- `services/core/internal/protosocket/tasks.go`
- `services/core/README.md`

### 테스트 환경 규칙

- test_env: `local`
- 읽은 규칙: `agent-test/local/rules.md`, `agent-test/local/core-smoke.md`
- 적용 명령: `cd services/core && go test ./...`
- focused 명령: `cd services/core && go test -count=1 ./internal/workflow ./internal/scheduler ./internal/http ./internal/protosocket`

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

- enqueue conflict mapping은 HTTP/proto-socket test가 있다.
- `Service.EnqueueTask`의 enqueuer nil/failure 후 canonical metadata 처리와 lifecycle-owned queue transition은 직접 테스트가 없다.
- scheduler worker lifecycle ordering은 `jobs_test.go`의 fake lifecycle tests가 있다.

### 심볼 참조

- renamed/removed symbols: none planned.
- call sites: `NewService`는 `cmd/server/main.go`와 tests에서 쓰인다. `TaskLifecycle`은 `scheduler/jobs.go`, `scheduler/river.go`, fake lifecycle tests에서 쓰인다.

### 분할 판단

- task group: `m-workflow-core`
- this subtask: `02+01_transition_ownership`
- predecessor: `01_lifecycle_model`; 현재 active plan 경로는 `agent-task/m-workflow-core/01_lifecycle_model/PLAN-cloud-G07.md`이고 아직 `complete.log`가 없다. 구현은 predecessor PASS 후 시작한다.
- split reason: lifecycle helper foundation과 service/scheduler ownership refactor는 review risk가 다르므로 분리했다.

### 범위 결정 근거

- River queue configuration itself stays in `scheduler/river.go`; retry count policy is `04+03_retry_timeout`.
- Notification payload changes are deferred to `05+02,03,04_notification_model`.
- REST/proto-socket route shapes stay compatible; only service behavior and docs clarify ownership.

### 빌드 등급

- build: `cloud-G07`, review: `cloud-G07`
- 근거: workflow service와 scheduler worker 사이 책임 경계 변경이며 API route behavior에 영향을 줄 수 있다.

## 구현 체크리스트

- [ ] [WFC-OWNERSHIP-1] `Lifecycle.QueueTask`를 추가하고 `Service.EnqueueTask`가 lifecycle을 통해 queued/failure 상태를 기록하게 한다.
- [ ] [WFC-OWNERSHIP-2] scheduler worker는 running 이후 execution terminal transition만 소유한다는 문서와 tests를 추가한다.
- [ ] 중간 및 최종 검증 명령을 실행하고 출력 원문을 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- `01_lifecycle_model`의 `complete.log`가 필요하다.
- Runtime dependency source: directory name `02+01_transition_ownership`.

## [WFC-OWNERSHIP-1] Lifecycle-Owned Queue Transition

### 문제

- [services/core/internal/workflow/service.go](/config/workspace/nomadcode/services/core/internal/workflow/service.go:157)는 enqueue 시 store status를 직접 `queued`로 바꾼다.
- [services/core/internal/workflow/service.go](/config/workspace/nomadcode/services/core/internal/workflow/service.go:166)는 enqueuer 실패 시 store `FailTask`를 직접 호출해 lifecycle metadata rules를 우회한다.

### 해결 방법

Before [services/core/internal/workflow/service.go](/config/workspace/nomadcode/services/core/internal/workflow/service.go:157):

```go
queuedTask, err := s.store.UpdateStatus(ctx, id, string(StatusQueued))
```

After:

```go
queuedTask, err := s.lifecycle.QueueTask(ctx, id)
```

Before [services/core/internal/workflow/service.go](/config/workspace/nomadcode/services/core/internal/workflow/service.go:166):

```go
if _, failErr := s.store.FailTask(ctx, id, err.Error()); failErr != nil && s.logger != nil {
	s.logger.Error("failed to mark task enqueue error", "task_id", id, "error", failErr)
}
```

After:

```go
if _, failErr := s.lifecycle.FailTask(ctx, id, err.Error()); failErr != nil && s.logger != nil {
	s.logger.Error("failed to mark task enqueue error", "task_id", id, "error", failErr)
}
```

Add `lifecycle *Lifecycle` to `Service`, initialized in `NewService(store, enqueuer, logger)` so callers keep the same constructor. `QueueTask` should accept only `pending` and `failed`, set status `queued`, and avoid running metadata updates.

### 수정 파일 및 체크리스트

- [ ] `services/core/internal/workflow/lifecycle.go`: add `QueueTask(ctx,id)`.
- [ ] `services/core/internal/workflow/service.go`: add `lifecycle *Lifecycle` and route enqueue transitions through it.
- [ ] `services/core/internal/workflow/service_test.go`: add service enqueue tests for success, nil enqueuer, and enqueuer failure.

### 테스트 작성

- Required:
  - `TestServiceEnqueueTaskUsesLifecycleQueueTransition`
  - `TestServiceEnqueueFailureMarksTaskFailedThroughLifecycle`

### 중간 검증

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

Expected: workflow service enqueue tests pass.

## [WFC-OWNERSHIP-2] Scheduler Execution Boundary

### 문제

- [services/core/internal/scheduler/jobs.go](/config/workspace/nomadcode/services/core/internal/scheduler/jobs.go:51) starts execution and [services/core/internal/scheduler/jobs.go](/config/workspace/nomadcode/services/core/internal/scheduler/jobs.go:76) completes it, but README does not state that scheduler owns only running/terminal execution transitions.
- Milestone requires ownership to be readable in code/docs/tests.

### 해결 방법

Add short comments near `TaskLifecycle` in `scheduler/jobs.go` and a `services/core/README.md` section explaining:

- workflow service owns create/list/get/enqueue API semantics.
- lifecycle owns all canonical status writes and metadata merge.
- scheduler worker owns invoking `StartTask`, running execution, then invoking `CompleteTask` or `FailTask`.
- provider projection failure must not roll back canonical task state.

### 수정 파일 및 체크리스트

- [ ] `services/core/internal/scheduler/jobs.go`: comment `TaskLifecycle` ownership boundary.
- [ ] `services/core/README.md`: add concise workflow lifecycle ownership section.
- [ ] `services/core/internal/scheduler/jobs_test.go`: keep or extend worker tests to assert `StartTask -> CompleteTask` and `StartTask -> FailTask` ordering.

### 테스트 작성

- Existing `TestWorkCompletesThroughLifecycle` and `TestWorkFailsThroughLifecycle` cover ordering; extend only if QueueTask changes require fake lifecycle alignment.

### 중간 검증

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

Expected: worker lifecycle tests and workflow enqueue tests pass.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/workflow/lifecycle.go` | WFC-OWNERSHIP-1 |
| `services/core/internal/workflow/service.go` | WFC-OWNERSHIP-1 |
| `services/core/internal/workflow/service_test.go` | WFC-OWNERSHIP-1 |
| `services/core/internal/scheduler/jobs.go` | WFC-OWNERSHIP-2 |
| `services/core/internal/scheduler/jobs_test.go` | WFC-OWNERSHIP-2 |
| `services/core/README.md` | WFC-OWNERSHIP-2 |

## 최종 검증

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

Expected: focused and full core tests pass. Fresh `-count=1` is required for focused packages; final full run may use cache.

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