<!-- task=m-runtime-scaffold-and-contract-baseline plan=1 tag=REVIEW_STORAGE -->

# Plan - REVIEW_STORAGE

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

`CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현자는 아래 검증을 실행하고, 실제 stdout/stderr를 리뷰 스텁에 기록하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 구현이 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret, 또는 scope conflict에 막히면 사용자에게 직접 묻지 말고 리뷰 스텁의 `사용자 리뷰 요청` 섹션에 결정 필요 사항과 근거를 채운 뒤 중단한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 evidence gap은 사용자 리뷰 요청이 아니다.

## 배경

이전 리뷰(`code_review_local_G05_0.log`)에서 storage boundary 구현 자체와 Go 검증은 통과했지만, 계획에 명시된 `New(...)` injection contract 테스트가 충분하지 않았다. 현재 `TestBoundaryContract`는 accessor 반환값이 nil이 아닌지만 확인하므로, constructor가 주입받은 sub-store가 아닌 다른 non-nil store를 반환해도 테스트가 통과할 수 있다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 중 직접 사용자에게 질문하거나 chat 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. code-review가 blocker 정당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md`
- Task ids:
  - `storage-boundary`: Postgres migration과 storage boundary를 실제 store interface 기준으로 연결한다.
- Completion mode: check-on-pass

## 범위 결정 근거

- 포함: `services/core/internal/storage/storage_test.go`의 `TestBoundaryContract` assertion 보강.
- 제외: `storage.go` API 변경, migration 변경, 실제 PostgreSQL CRUD, driver dependency, repo registry API.
- 제외 이유: 실패 원인은 구현 기능이 아니라 계획된 contract assertion 누락이며, deterministic local test로 닫을 수 있다.

## 구현 체크리스트

- [ ] `TestBoundaryContract`에서 `storage.New(...)`에 주입한 각 fake sub-store가 accessor로 동일하게 반환되는지 identity assertion을 추가한다. 검증: `cd services/core && go test -count=1 ./internal/storage` PASS.
- [ ] 전체 core baseline을 재확인한다. 검증: `cd services/core && go test -count=1 ./...`와 `cd services/core && go vet ./...` PASS.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_STORAGE-1] Boundary injection identity assertion 추가

### 문제

`services/core/internal/storage/storage_test.go`의 `TestBoundaryContract`는 `b.Repos() == nil` 같은 nil 여부만 확인한다. 이전 계획은 `New(...)`가 주입받은 store를 그대로 반환하는지도 확인하라고 요구했으므로, 현재 테스트는 storage boundary injection contract를 완전히 고정하지 못한다.

### 해결 방법

`TestBoundaryContract` 안에서 각 fake를 지역 변수로 만든 뒤 `storage.New(repos, leases, ops, opEvents)`를 호출한다. 이후 accessor 반환값이 같은 interface 값인지 직접 비교한다.

예시:

```go
repos := &fakeRepos{}
leases := &fakeLeases{}
ops := &fakeOps{}
opEvents := &fakeOpEvents{}

b := storage.New(repos, leases, ops, opEvents)

if b.Repos() != repos {
	t.Error("Repos() must return the injected repo store")
}
```

같은 방식으로 `WorkspaceLeases`, `Operations`, `OperationEvents`도 확인한다. 기존 nil context와 nil sub-store 테스트는 유지한다.

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

- [ ] `services/core/internal/storage/storage_test.go`: `TestBoundaryContract`가 직접 fake 인스턴스를 만들도록 조정한다.
- [ ] `services/core/internal/storage/storage_test.go`: 각 accessor가 주입받은 정확한 fake 값을 반환하는지 assertion한다.
- [ ] `services/core/internal/storage/storage_test.go`: gofmt를 적용한다.

### 중간 검증

```sh
cd services/core && go test -count=1 ./internal/storage
```

기대 결과: `ok git.toki-labs.com/toki/gito/services/core/internal/storage`.

## 의존 관계 및 구현 순서

1. `TestBoundaryContract`의 setup을 identity assertion이 가능한 지역 변수 형태로 바꾼다.
2. storage package test를 실행한다.
3. 전체 core baseline과 vet을 실행한다.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/internal/storage/storage_test.go` | `REVIEW_STORAGE-1` |

## 최종 검증

```sh
cd services/core && go test -count=1 ./internal/storage
cd services/core && go test -count=1 ./...
cd services/core && go vet ./...
```

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