<!-- task=m-provider-adapter-foundation/02+01_postgres_provider_store plan=0 tag=API -->

# Plan - API

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

이 계획의 구현이 끝나도 task는 완료가 아니다. 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 명령, stdout/stderr로 채우고 active 파일을 그대로 둔 뒤 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다. 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 막히면 review stub의 `사용자 리뷰 요청` 섹션에 연결 대상과 근거를 채우고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service 차단, 일반 범위 변경, 검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과나 후속 계획으로 기록한다.

## 배경

`provider-store`는 provider config를 저장/조회할 durable store까지 갖춰야 완료로 볼 수 있다. `01_provider_store_boundary`가 storage 계약을 만든 뒤, 이 subtask는 migration, PgStore wiring, upsert/get/list 구현, PostgreSQL smoke coverage를 추가한다. Credential raw value는 저장하지 않고 `credential_ref`만 저장한다.

## 사용자 리뷰 요청 흐름

구현 중 직접 사용자 프롬프트는 금지된다. 선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목만 active review stub의 `사용자 리뷰 요청` 섹션에 기록하며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성을 담당한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/provider-change-request-integration/milestones/provider-adapter-foundation.md`
- Task ids:
  - `provider-store`: provider config를 저장하고 조회하는 store boundary를 만든다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `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/provider-change-request-integration/PHASE.md`
- `agent-roadmap/phase/provider-change-request-integration/milestones/provider-adapter-foundation.md`
- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `services/core/go.mod`
- `services/core/internal/provider/provider.go`
- `services/core/internal/provider/provider_test.go`
- `services/core/internal/provider/forgejo/push.go`
- `services/core/internal/provider/forgejo/push_test.go`
- `services/core/internal/core/model.go`
- `services/core/internal/core/model_test.go`
- `services/core/internal/storage/storage.go`
- `services/core/internal/storage/storage_test.go`
- `services/core/internal/storage/postgres.go`
- `services/core/internal/storage/postgres_internal_test.go`
- `services/core/internal/storage/postgres_repo_test.go`
- `services/core/internal/storage/postgres_test.go`
- `services/core/migrations/00001_initial.sql`

### 테스트 환경 규칙

- test_env: `local`
- `agent-test/local/rules.md` present/read. Routing matched `core / smoke`.
- `agent-test/local/core-smoke.md` present/read. Required command: `cd services/core && go test ./...`.
- PostgreSQL-dependent tests may skip when `GITO_TEST_DATABASE_URL` is unset; record exact output and residual risk if skipped.
- Focused command: `cd services/core && go test -count=1 ./internal/storage`.

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

- Existing Postgres tests cover repos, leases, operations, branch watch/delivery, operation events, agent inputs, webhook deliveries, but not provider config persistence.
- Add migration string assertions for `provider_configs` and a PgStore integration test for save/get/list. If DB env is absent, command output must show skip and residual risk.
- Boundary contract is covered by predecessor `01_provider_store_boundary`.

### 심볼 참조

- none. This subtask adds PgStore implementation and migration table; it should not rename/remove symbols.

### 분할 판단

- Split policy was evaluated before choosing files.
- Shared task group: `agent-task/m-provider-adapter-foundation`.
- Current subtask: `02+01_postgres_provider_store`.
- Predecessor index `01`: missing active `complete.log` at planning time. Implementation must wait until `agent-task/m-provider-adapter-foundation/01_provider_store_boundary/complete.log` exists or the matching archived predecessor `complete.log` exists.

### 범위 결정 근거

- Includes only provider config persistence and storage wiring.
- Excludes provider read/write client methods, callback REST endpoints, webhook signature hooks, event normalization, and external API calls.
- Stores only `credential_ref`, never raw credential values.
- Uses current bitmask `provider.Capability`; no new dependency is required.

### 빌드 등급

- `local-G05`: bounded Go persistence change with migration, PgStore wiring, and integration-test risk from optional PostgreSQL env.

## 구현 체크리스트

- [ ] `01_provider_store_boundary` predecessor의 `complete.log`를 확인한 뒤 구현을 시작한다.
- [ ] `provider_configs` migration table과 migration assertion을 추가한다.
- [ ] `PgStore`에 provider config store 필드/constructor/accessor를 연결한다.
- [ ] `pgProviderConfigStore`의 save/get/list 구현과 validation/scan helper를 추가한다.
- [ ] PostgreSQL integration test로 config 저장, 업데이트, 조회, listing, not-found, invalid input을 검증한다.
- [ ] `cd services/core && go test -count=1 ./internal/storage` 및 `cd services/core && go test ./...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 디렉터리 이름의 `02+01_...`가 runtime dependency source다. 구현 전 `01_provider_store_boundary`가 PASS되어 `complete.log`를 남겼는지 확인한다. 추가 의존성은 없다.

### [API-1] provider_configs migration 추가

문제: [00001_initial.sql](/config/workspace/gito/services/core/migrations/00001_initial.sql:77)는 provider delivery dedupe table만 만들고 provider config registry table은 없다.

현재:

```sql
-- services/core/migrations/00001_initial.sql:77
CREATE TABLE IF NOT EXISTS provider_deliveries (
    id           TEXT PRIMARY KEY,
    provider     TEXT NOT NULL,
    delivery_id  TEXT NOT NULL,
    dedupe_key   TEXT NOT NULL,
    event_id     TEXT NOT NULL,
    repo_id      TEXT NOT NULL,
    branch       TEXT NOT NULL,
    revision     TEXT NOT NULL,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (provider, dedupe_key)
);
```

해결 방법: `provider_deliveries` 근처에 `provider_configs` table을 추가한다. 권장 columns: `id TEXT PRIMARY KEY`, `endpoint TEXT NOT NULL CHECK (btrim(endpoint) <> '')`, `credential_ref TEXT`, `capabilities BIGINT NOT NULL CHECK (capabilities >= 0)`, `created_at`, `updated_at`. Down block에는 dependent table보다 먼저/적절히 `DROP TABLE IF EXISTS provider_configs;`를 추가한다.

수정 파일 및 체크리스트:

- [ ] `services/core/migrations/00001_initial.sql`: provider config table과 down drop 추가.
- [ ] `services/core/internal/storage/storage_test.go`: `TestInitialMigrationSupportsStoreBoundary` snippets에 table/columns/checks 추가.

테스트 작성: 기존 migration snippet test에 provider_configs assertions를 추가한다.

중간 검증:

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

기대 결과: storage tests pass; DB env가 없으면 PG integration tests may skip but migration snippet test must run.

### [API-2] PgStore wiring과 provider config CRUD 구현

문제: [postgres.go](/config/workspace/gito/services/core/internal/storage/postgres.go:21)의 `PgStore`는 provider delivery/webhook delivery stores를 갖지만 provider config store accessor가 없다.

현재:

```go
// services/core/internal/storage/postgres.go:21
type PgStore struct {
	pool              *pgxpool.Pool
	repos             *pgRepoStore
	leases            *pgWorkspaceLeaseStore
	ops               *pgOperationStore
	opEvents          *pgOperationEventStore
	watches           *pgBranchWatchStore
	cursors           *pgRevisionCursorStore
	deliveries        *pgProviderDeliveryStore
	agentRunInputs    *pgAgentRunInputStore
	webhookDeliveries *pgWebhookDeliveryStore
}
```

해결 방법: predecessor가 만든 `ProviderConfigStore` 계약을 구현하는 `pgProviderConfigStore`를 추가하고 `NewPgStore`에서 초기화한다. `provider.Config.Normalized()`를 사용해 ID/endpoint/credential_ref를 trim/lowercase 처리하고, empty id/endpoint 또는 zero capabilities는 `ErrInvalidProviderConfig`로 거부한다. Capabilities는 current bitmask를 `int64`로 저장/조회한다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/storage/postgres.go`: provider import 추가.
- [ ] `services/core/internal/storage/postgres.go`: `PgStore` field, constructor init, accessor 추가.
- [ ] `services/core/internal/storage/postgres.go`: `pgProviderConfigStore.SaveProviderConfig`, `GetProviderConfig`, `ListProviderConfigs` 구현.
- [ ] `services/core/internal/storage/postgres.go`: `normalizeProviderConfig`, `scanProviderConfig` helper 추가.

테스트 작성: `services/core/internal/storage/postgres_test.go`에 `TestPostgresProviderConfigStoreSavesGetsListsAndValidates`를 추가한다. assertions: first save inserts, second save updates endpoint/credential/capabilities, get returns updated config, list is ordered by id, missing get returns `ok=false`, blank id/endpoint or zero capabilities returns `ErrInvalidProviderConfig`, raw credential value is not introduced.

중간 검증:

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

기대 결과: exit 0. If `GITO_TEST_DATABASE_URL` is unset, record skip output and remaining DB coverage risk.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/migrations/00001_initial.sql` | API-1 |
| `services/core/internal/storage/storage_test.go` | API-1 |
| `services/core/internal/storage/postgres.go` | API-2 |
| `services/core/internal/storage/postgres_test.go` | API-2 |

## 최종 검증

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

기대 결과: 두 명령 모두 exit 0. `go test ./...`의 cached 출력은 허용된다. `GITO_TEST_DATABASE_URL` 부재로 PostgreSQL integration test가 skip되면 실제 출력과 남은 위험을 review stub에 남긴다.

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