<!-- task=m-milestone-work-item-creation-sync/01_project_sync_store plan=1 tag=REVIEW_API -->

# Implementation Plan - REVIEW_API

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

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

## 배경

`project-store` persistence 구현은 컴파일과 전체 Go test는 통과했지만, 첫 리뷰에서 active lookup 오류 경계와 persistence regression coverage가 불충분해 FAIL 판정을 받았다. 이 follow-up은 `project-store` Task 완료를 막는 두 Required 이슈만 수정한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 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:
  - `project-store`: Project sync 설정을 Core DB에 저장하는 persistence 경계를 정한다.
- Completion mode: check-on-pass

## 분석 결과

### 이전 루프

- Archived plan: `agent-task/m-milestone-work-item-creation-sync/01_project_sync_store/plan_local_G06_0.log`
- Archived review: `agent-task/m-milestone-work-item-creation-sync/01_project_sync_store/code_review_local_G06_0.log`
- Verdict: FAIL

### 실패 근거

- `services/core/internal/storage/project_sync_settings.go:27`가 query error를 모두 `ErrProjectSyncNotFound`로 변환해 DB 장애와 설정 부재를 구분할 수 없다.
- `services/core/internal/storage/project_sync_store_test.go:11` 이하 테스트가 실제 storage create/get/deactivate 경계를 호출하지 않아 active lookup, inactive 제외, target param 전달, 일반 DB error 보존을 검증하지 못한다.

### 테스트 환경 규칙

- 선택한 `test_env`: `local`.
- core 변경이므로 최소 `cd services/core && go test -count=1 ./...`를 실행한다.
- task artifact와 문서성 변경이 있으므로 `git diff --check`를 최종 확인에 포함한다.

### 범위 결정 근거

- DB schema 자체를 재설계하지 않는다. partial unique index, SQLC query 이름, `projectsync.Config` 필드 의미는 유지한다.
- 실제 PostgreSQL integration test 환경이 없으면 fake `db.DBTX`/`pgx.Row` 기반 unit test로 generated query 호출, query text, args, scan result, error mapping을 검증한다. DB-backed 검증을 추가하지 못한 사유는 review stub에 남긴다.
- 로드맵 문서나 milestone 상태는 수정하지 않는다. PASS completion event 처리는 runtime 책임이다.

### 빌드 등급

- `local-G07`: 실패 원인은 명시적이고 deterministic하지만, persistence API 오류 경계와 fake DB test evidence를 회복해야 하므로 이전보다 한 단계 높은 local review로 둔다.

## 구현 체크리스트

- [ ] `GetActiveProjectSyncSettingByTarget`가 `pgx.ErrNoRows`만 `ErrProjectSyncNotFound`로 매핑하고 그 외 DB 오류를 보존한다. 검증: fake DBTX/store test가 not found와 일반 DB error를 모두 확인한다.
- [ ] Project sync store regression test가 `Store`의 create/get/deactivate 경계를 호출하고 provider/tenant/project param 전달, active lookup SQL guard, inactive 제외 의미를 검증한다.
- [ ] `cd services/core && go test -count=1 ./internal/storage ./internal/projectsync`, `cd services/core && go test -count=1 ./...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_API-1] Preserve DB error boundary

- 문제: active project sync lookup이 모든 query error를 `ErrProjectSyncNotFound`로 반환한다. 이러면 caller가 "active config 없음"과 "DB 장애"를 같은 상태로 처리한다.
- 해결 방법: `services/core/internal/storage/project_sync_settings.go`에서 `errors.Is(err, pgx.ErrNoRows)`일 때만 `ErrProjectSyncNotFound`를 반환하고, 그 외 오류는 원래 오류를 반환한다.
- 수정 파일 및 체크리스트:
  - [ ] `services/core/internal/storage/project_sync_settings.go`
  - [ ] `services/core/internal/storage/project_sync_store_test.go`
- 테스트 작성: fake `db.DBTX`와 fake `pgx.Row`를 사용해 `pgx.ErrNoRows`는 `ErrProjectSyncNotFound`, sentinel DB error는 원래 error로 유지되는지 검증한다.
- 중간 검증: `cd services/core && go test -count=1 ./internal/storage`.

### [REVIEW_API-2] Meaningful persistence regression coverage

- 문제: 현재 storage tests는 generated struct/param shape만 확인하고 실제 `Store` 메서드를 호출하지 않는다. 계획의 active lookup, inactive 제외, target 분리 검증이 비어 있다.
- 해결 방법: fake DB boundary를 통해 `CreateProjectSyncRecord`, `GetActiveProjectSyncSettingByTarget`, `DeactivateProjectSyncSetting`이 generated query를 호출하고 provider/tenant/project args를 전달하는지 검증한다. fake DBTX가 받은 query text에 `active = TRUE` lookup guard와 `SET active = FALSE` deactivate guard가 포함되는지도 확인한다. 가능하면 migration file의 partial unique index도 가볍게 assert한다.
- 수정 파일 및 체크리스트:
  - [ ] `services/core/internal/storage/project_sync_store_test.go`
  - [ ] 필요한 경우 test helper를 같은 파일 또는 좁은 `_test.go` 파일에 둔다.
- 테스트 작성: 정상 create/get/deactivate, provider/tenant/project param 전달, inactive 제외 의미, 일반 DB error 보존을 assertion으로 둔다. 단순 "package compiles" no-op test는 제거하거나 실제 assertion으로 대체한다.
- 중간 검증: `cd services/core && go test -count=1 ./internal/storage ./internal/projectsync`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/storage/project_sync_settings.go` | REVIEW_API-1 |
| `services/core/internal/storage/project_sync_store_test.go` | REVIEW_API-1, REVIEW_API-2 |
| 필요한 경우 같은 package의 좁은 `_test.go` helper | REVIEW_API-2 |

## 최종 검증

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

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

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