<!-- task=m-milestone-work-item-creation-sync/02+01_workspace_slot_store plan=1 tag=API -->

# Implementation Plan - API

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

구현 완료는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 검증을 실행하고, 실제 변경/출력을 기록하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 종료 처리, log rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret 준비, scope 충돌이 있으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 적고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`를 만들지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다.

## 배경

`projectsync`에는 `available`, `in_use`, `dirty`, `error` 상태와 3자리 slot index 모델이 있다. 하지만 Core DB에는 slot 상태가 저장되지 않고, 동시에 들어온 요청이 같은 slot을 잡지 않도록 원자적으로 `in_use`로 바꾸는 persistence 경계가 없다. 이 plan은 `workspace-slot-state` Task의 DB 저장과 예약 기준만 완료 대상으로 한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.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:
  - `workspace-slot-state`: Workspace slot의 사용중 상태 모델을 정의한다.
- 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-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.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/core/rules.md`
- `agent-ops/rules/project/domain/workspace-ops/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/workspace-ops-smoke.md`
- `.gitignore`
- `services/core/internal/projectsync/config.go`
- `services/core/internal/projectsync/config_test.go`
- `services/core/internal/storage/store.go`
- `services/core/queries/tasks.sql`
- `services/core/migrations/00003_add_task_metadata.sql`
- `services/core/sqlc.yaml`

### 테스트 환경 규칙

- 선택한 `test_env`: `local`.
- `agent-test/local/rules.md`를 읽었다. core 변경은 `agent-test/local/core-smoke.md` 기준으로 최소 `cd services/core && go test ./...`가 필수다.
- workspace-ops는 plan/review artifact 작성에만 닿으므로 `agent-test/local/workspace-ops-smoke.md` 기준 `git diff --check`를 최종 확인에 포함한다.
- `<확인 필요>` 값은 core-smoke의 기준 출력 예시에만 있으며, 이 plan의 명령에는 미해결 환경값이 없다.

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

- Slot state enum과 path/index helper는 `services/core/internal/projectsync/config_test.go`에서 일부 검증된다.
- DB 원자 예약은 신규 동작이라 기존 테스트가 없다. 구현 시 `available` slot만 예약되고, 이미 `in_use`인 slot은 예약되지 않는 경계를 테스트한다.
- 실제 동시 transaction race는 local unit test에서 DB 없이 완전 검증하기 어렵다. SQL `UPDATE ... WHERE state='available' RETURNING` 형태와 storage-level behavior를 검증하고 남은 DB integration 공백을 기록한다.

### 심볼 참조

- none. 기존 symbol rename/remove는 없다.

### 분할 판단

- split decision policy를 평가했다. 공유 task group은 `agent-task/m-milestone-work-item-creation-sync/`다.
- `01_project_sync_store`: predecessor. Project sync 설정 DB 저장소를 만든다.
- `02+01_workspace_slot_store`: 현재 plan. predecessor `01`: missing active/archive `complete.log`. 구현은 `01_project_sync_store` PASS 후 시작한다.
- `03+01,02_project_binding_checkout`: 후속. 설정 조회와 slot 예약이 모두 있어야 binding/checkout을 연결한다.

### 범위 결정 근거

- Project sync 설정 저장소 생성은 predecessor `01` 범위다. 이 plan은 해당 설정 row를 참조하는 workspace slot 상태 저장과 원자 예약만 다룬다.
- 실제 git checkout, workspace 디렉터리 생성, Plane-origin authoring 실행은 제외한다.
- slot 상태 후보는 `projectsync.SlotState`를 source of truth로 유지하고 중복 enum을 만들지 않는다.

### 빌드 등급

- `local-G06`: DB update 조건과 상태 전이가 핵심이지만 범위가 storage 내부로 한정되고 focused tests plus full Go test로 검증 가능하다.

## 구현 체크리스트

- [ ] `workspace_slots` migration과 SQLC queries를 추가한다. 검증: 각 slot은 `available`, `in_use`, `dirty`, `error` 상태 후보를 갖는다.
- [ ] `available` slot만 원자적으로 `in_use`로 예약하는 storage method를 추가한다. 검증: 동시에 들어온 요청이 같은 slot을 잡지 않도록 SQL 조건이 있다.
- [ ] Slot state persistence 테스트를 작성한다.
- [ ] `cd services/core && go test -count=1 ./...`와 `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 subtask 디렉터리 이름은 `02+01_workspace_slot_store`이므로 predecessor는 `01_project_sync_store` 하나다.
- 구현 시작 전 같은 task group의 `01_project_sync_store/complete.log`가 active 또는 archive 경로에 있어야 한다. 현재 확인 시 predecessor completion은 missing이다.

### [API-1] Workspace slot DB schema and queries

- 문제: `projectsync.SlotState`는 코드 모델로 존재하지만(`services/core/internal/projectsync/config.go:36`), DB에는 slot 상태를 저장할 table이 없다. milestone은 상태 후보와 DB 원자 전환 기준을 요구한다(`agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md:55`).
- 해결 방법: `services/core/migrations/00005_create_workspace_slots.sql`을 추가한다. `project_sync_setting_id`, `slot_index`, `state`, `path`, timestamps를 포함하고 `(project_sync_setting_id, slot_index)` unique를 둔다. `state` check constraint는 `available`, `in_use`, `dirty`, `error`만 허용한다. SQLC queries에는 list by project, upsert, reserve next available, update state를 둔다.
- 수정 파일 및 체크리스트:
  - [ ] `services/core/migrations/00005_create_workspace_slots.sql`
  - [ ] `services/core/queries/workspace_slots.sql`
  - [ ] `services/core/internal/db/*.go` generated SQLC output
- 테스트 작성: storage-level tests에서 query behavior를 검증한다. SQL parser-only test는 작성하지 않는다.
- 중간 검증: `cd services/core && go test -count=1 ./internal/db`.

### [API-2] Atomic slot reservation storage API

- 문제: `storage.Store`는 task CRUD만 제공한다(`services/core/internal/storage/store.go:24`). 새 작업이 `available` slot만 선택하고 같은 slot을 동시에 잡지 않도록 하는 API가 없다.
- 해결 방법: storage에 `ReserveWorkspaceSlot(ctx, projectSyncSettingID string)`와 상태 update method를 추가한다. SQL은 한 statement에서 `state='available'` 조건과 `ORDER BY slot_index LIMIT 1 FOR UPDATE SKIP LOCKED` 또는 동등한 원자 update pattern을 사용한다. 반환 타입은 `projectsync.SlotIndex`, `projectsync.SlotState`, path를 보존한다.
- 수정 파일 및 체크리스트:
  - [ ] `services/core/internal/storage/store.go` 또는 `workspace_slots.go`
  - [ ] `services/core/internal/projectsync/config.go` helper가 필요하면 최소 수정
- 테스트 작성: `services/core/internal/storage/workspace_slots_test.go`에 available 예약, in_use 제외, dirty/error 제외, no available error를 작성한다.
- 중간 검증: `cd services/core && go test -count=1 ./internal/storage ./internal/projectsync`.

### [API-3] Slot state regression coverage

- 문제: 모델 test는 `SlotStateAvailable.Allocatable()`만 확인한다(`services/core/internal/projectsync/config_test.go`). Persistence의 상태 전이는 아직 검증되지 않는다.
- 해결 방법: storage test에서 상태 후보와 예약 전후 state change를 검증한다. 실제 DB가 필요한 부분은 테스트 fixture를 명시하고, DB 없는 환경이면 query compile과 converter test로 대체한 사유를 review stub에 기록한다.
- 수정 파일 및 체크리스트:
  - [ ] `services/core/internal/storage/workspace_slots_test.go`
  - [ ] 필요한 경우 `services/core/internal/projectsync/config_test.go`
- 테스트 작성: 위 파일에 작성한다.
- 중간 검증: `cd services/core && go test -count=1 ./internal/storage ./internal/projectsync`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/migrations/00005_create_workspace_slots.sql` | API-1 |
| `services/core/queries/workspace_slots.sql` | API-1 |
| `services/core/internal/db/*.go` | API-1 |
| `services/core/internal/storage/workspace_slots.go` 또는 `services/core/internal/storage/store.go` | API-2 |
| `services/core/internal/storage/workspace_slots_test.go` | API-3 |
| `services/core/internal/projectsync/config.go` | API-2 |
| `services/core/internal/projectsync/config_test.go` | API-3 |

## 최종 검증

```bash
cd services/core && go test -count=1 ./...
git diff --check
```

기대 결과: 두 명령 모두 exit code 0. Go test cache output은 허용하지 않으므로 `-count=1`을 유지한다.

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