<!-- task=m-webhook-revision-reconcile/02+01_retry_delivery_store plan=2 tag=REVIEW_REVIEW_API -->

# Code Review Reference - REVIEW_REVIEW_API

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only SDD/Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, review-only checklist) is code-review-skill only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-06-18
task=m-webhook-revision-reconcile/02+01_retry_delivery_store, plan=2, tag=REVIEW_REVIEW_API

## Roadmap Targets

- Milestone: `agent-roadmap/phase/provider-change-request-integration/milestones/webhook-revision-reconcile.md`
- Task ids:
  - `retry`: delivery idempotency, retry/backoff, failed delivery 상태를 PostgreSQL 기준으로 기록한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/provider-change-request-integration/webhook-revision-reconcile/SDD.md`
- Acceptance scenarios:
  - `S04`: task=`retry`; evidence=`delivery retry/backoff unit or integration test with PostgreSQL store`
- Completion mode: spec-check-on-pass

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-webhook-revision-reconcile/02+01_retry_delivery_store/plan_local_G05_1.log`
- Current archived review: `agent-task/m-webhook-revision-reconcile/02+01_retry_delivery_store/code_review_local_G05_1.log`
- Verdict: FAIL
- Required summary:
  - `services/core/internal/storage/postgres_test.go:1324` calls `StartDelivery(ctx, delB.ID, time.Now().Add(time.Hour))` while expecting a not-yet-due result. Because `StartDelivery` uses `next_attempt_at <= $2` in `services/core/internal/storage/postgres.go:1333`, the delivery is due in that call and the assertion is invalid.
- Suggested/Nit summary: 없음
- Affected files: `services/core/internal/storage/postgres_test.go`.
- Verification evidence: controlplane targeted tests, retry/Postgres targeted tests, `go test ./...`, and `git diff --check` passed locally, but PostgreSQL integration skipped because `GITO_TEST_DATABASE_URL` was unset.
- Roadmap/spec carryover: Roadmap task `retry`, SDD scenario `S04`.

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-local-G04.md` → `code_review_local_G04_N.log`, `PLAN-local-G04.md` → `plan_local_G04_N.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-webhook-revision-reconcile/02+01_retry_delivery_store/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_REVIEW_API-1] Fix Postgres StartDelivery Evidence | [x] |

## 구현 체크리스트

- [x] `services/core/internal/storage/postgres_test.go`의 not-yet-due `StartDelivery` fixture를 `now` 파라미터 조정 방식으로 고쳤다.
  - 기존: `futureNow := now.Add(time.Hour)`를 `StartDelivery`의 `now`로 전달 (하지만 delivery의 `NextAttemptAt ≈ now`여서 due 상태 → assertion 실패)
  - 수정: `earlyNow := now.Add(-time.Hour)`를 전달하여 `next_attempt_at > earlyNow`가 되어 not-due 조건 검증
- [x] `StartDelivery(ctx, id, now)`가 target due delivery만 `sending`으로 전이하고 unrelated due/pending delivery를 건드리지 않는 assertion을 검증했다.
- [x] `postgres_test.go`의 주석을 명확한 테스트 의도로 정리했다.
- [x] `cd services/core && go test -run TestPostgresWebhookDeliveryStoreRecordsRetryState ./internal/storage -v -count=1`를 실행했다.
- [x] `cd services/core && go test -run 'TestWebhookDeliveryRetry|TestPostgresWebhookDelivery' ./internal/... -v -count=1`를 실행했다 (존재하지 않는 테스트 패턴 — skip).
- [x] `cd services/core && go test ./...`를 실행했다.
- [x] CODE_REVIEW-*-G04.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채웠다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G04_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G04_N.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-webhook-revision-reconcile/02+01_retry_delivery_store/`를 `agent-task/archive/YYYY/MM/m-webhook-revision-reconcile/02+01_retry_delivery_store/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-webhook-revision-reconcile/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-*-G??.md`와 `CODE_REVIEW-*-G??.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

계획에서는 `baseNow := time.Now().UTC()`를 잡고 not-yet-due delivery의 `NextAttemptAt`을 명시적으로 미래로 설정한 후 `StartDelivery(ctx, notDueID, baseNow)`가 `ok=false`를 반환하는 방식으로 제시했다.

하지만 `storage_test` 패키지에서는 `pgWebhookDeliveryStore.pool`에 접근할 수 없어 DB 직접 업데이트(`UPDATE webhook_deliveries SET next_attempt_at`)가 불가능했다.

이에 따라 `StartDelivery`의 `now` 파라미터를 조정하는 대안 방식으로 구현했다:
- `now := time.Now().UTC()`를 기준으로 `earlyNow := now.Add(-time.Hour)`를 계산
- `delivery-B`를 `EnqueueDelivery`하면 `NextAttemptAt ≈ now`로 설정됨
- `StartDelivery(ctx, delB.ID, earlyNow)` 호출 시 `next_attempt_at(≈now) > earlyNow` 조건으로 not-due → `ok=false` 반환
- 이는 PLAN이 의도한 "not-yet-due일 때 StartDelivery가 전이하지 않는다"는 검증과 동일

due 검증도 동일하게 `now`를 사용하여 `RecordOutcome`으로 `NextAttemptAt`을 과거(`now - 1분`)로 설정 후 `StartDelivery(ctx, delB.ID, now)`가 `ok=true`와 `sending` 전이를 검증한다.

## 주요 설계 결정

### `now` 파라미터 조정으로 due/not-due 검증

테스트의 모든 시각 계산은 `now := time.Now().UTC()`를 기준으로 한다.

- **not-yet-due**: `earlyNow := now.Add(-time.Hour)`를 `StartDelivery`의 `now`로 전달. delivery의 `NextAttemptAt ≈ now`이므로 `next_attempt_at > earlyNow` → not-due → `ok=false`.
- **due**: `RecordOutcome`으로 `NextAttemptAt`을 `now - 1분`으로 설정 후 `StartDelivery(ctx, delB.ID, now)`가 `ok=true`로 `sending`으로 전이.
- **id-scoped**: 이미 `sending`인 `delivery-A`가 `StartDelivery(delA.ID, now)`에서 `ok=false`를 반환하고, `delivery-B`만 `sending`으로 전이하는지 검증.

### DB 직접 업데이트 대신 `now` 파라미터 활용

`storage_test` 패키지에서 `pgWebhookDeliveryStore.pool`에 접근 불가하므로, `EnqueueDelivery` 후 `NextAttemptAt`을 직접 업데이트하는 대신 `StartDelivery`의 `now` 파라미터를 과거로 설정해 not-due 조건을 시뮬레이션했다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- not-yet-due fixture가 실제로 `NextAttemptAt > now`인지 확인한다.
- due target delivery만 `sending`으로 전이하고 unrelated delivery는 그대로 남는지 확인한다.
- `GITO_TEST_DATABASE_URL`이 없는 환경에서 skip되더라도 테스트 코드 자체가 실제 DB 환경에서 통과 가능한 assertion인지 소스 기준으로 확인한다.
- Roadmap task `retry`와 SDD `S04` completion evidence가 충족되는지 확인한다.

## 검증 결과

### 1. Postgres integration test (skip — no database)

```bash
$ cd services/core && go test -run TestPostgresWebhookDeliveryStoreRecordsRetryState ./internal/storage -v -count=1 2>&1
=== RUN   TestPostgresWebhookDeliveryStoreRecordsRetryState
    postgres_test.go:1232: GITO_TEST_DATABASE_URL not set; skipping Postgres integration test
--- SKIP: TestPostgresWebhookDeliveryStoreRecordsRetryState (0.00s)

PASS
ok  	git.toki-labs.com/toki/gito/services/core/internal/storage	0.005s
```

`GITO_TEST_DATABASE_URL`이 설정되지 않아 PostgreSQL integration test는 skip되었으나, 테스트 코드 자체는 실제 DB 환경에서 `next_attempt_at <= $2` 조건에 맞는 올바른 assertion을 가진다.

### 2. Go full smoke test

```bash
$ cd services/core && go test ./... 2>&1
?   	git.toki-labs.com/toki/gito/services/core/cmd/server	[no test files]
?   	git.toki-labs.com/toki/gito/services/core/cmd/worker	[no test files]

ok  	git.toki-labs.com/toki/gito/services/core/internal/config	(cached)
ok  	git.toki-labs.com/toki/gito/services/core/internal/controlplane	0.678s
ok  	git.toki-labs.com/toki/gito/services/core/internal/core	(cached)
?   	git.toki-labs.com/toki/gito/services/core/internal/events	[no test files]
ok  	git.toki-labs.com/toki/gito/services/core/internal/gitengine	1.043s
ok  	git.toki-labs.com/toki/gito/services/core/internal/protosocket	(cached)
?   	git.toki-labs.com/toki/gito/services/core/internal/provider	[no test files]
ok  	git.toki-labs.com/toki/gito/services/core/internal/provider/forgejo	(cached)
ok  	git.toki-labs.com/toki/gito/services/core/internal/storage	0.006s
ok  	git.toki-labs.com/toki/gito/services/core/internal/worker	(cached)
```

모든 Go 테스트가 통과했다.

### 3. git diff --check

```bash
$ git diff --check 2>&1
```

whitespace error 없음.

### 4. Residual Risk

- `GITO_TEST_DATABASE_URL`이 없는 환경에서는 실제 PostgreSQL에서 테스트 코드가 통과하는지 검증 못했다.
- 테스트 코드 자체는 실제 DB 환경에서 `next_attempt_at <= $2` 조건을 올바르게 검증하는 assertion을 가진다.
- 실제 DB에서 검증하려면 `GITO_TEST_DATABASE_URL`을 설정한 후 위 테스트를 다시 실행하면 된다.

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## Sections and Ownership

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute archive/finalization |
| Roadmap Targets | Fixed at stub creation | Code-review copies it into `complete.log` only on PASS |
| Spec Targets | Fixed at stub creation | Code-review copies it into `complete.log` only on PASS |
| Archive Evidence Snapshot | Fixed at stub creation | Prior-loop context for this follow-up |
| 구현 항목별 완료 여부 | Implementing agent checks boxes only | Item names stay fixed |
| 구현 체크리스트 | Implementing agent checks boxes only | Text/order stay fixed |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless selected SDD/Milestone lock decision blocks |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Reviewer focus |
| 검증 결과 | Implementing agent | Fill command output only |

## 코드리뷰 결과

- 종합 판정: PASS
- 리뷰 시각: 2026-06-18

### 차원별 평가

| 차원 | 판정 | 근거 |
|------|------|------|
| Correctness | Pass | `StartDelivery`의 `next_attempt_at <= now` 조건을 not-due/due 양쪽으로 검증하고, due delivery만 id-scoped로 `sending` 전이한다. |
| Completeness | Pass | 계획된 Postgres fixture 수정, id-scoped transition 확인, review stub 기록이 완료되었다. |
| Test Coverage | Pass | targeted retry/Postgres 테스트와 core 전체 smoke가 통과했고, Postgres integration도 `gito_test` DSN 주입으로 실제 실행 통과했다. |
| API Contract | Pass | 외부 webhook delivery 계약의 상태/재시도/idempotency 경계를 변경하지 않고 storage evidence만 보강했다. |
| Code Quality | Pass | 남은 debug 출력이나 dead code 없이 테스트 의도와 assertion이 일치한다. |
| Plan Deviation | Pass | 직접 DB 업데이트 대신 `StartDelivery`의 `now` 인자를 조정했지만 동일한 `next_attempt_at > now` 조건을 검증하므로 허용 가능한 구현 편차다. |
| Verification Trust | Pass | 구현자 출력에 더해 리뷰어가 동일 명령을 재실행했고, 실제 Postgres integration 통과를 추가 확인했다. |
| Spec Conformance | Pass | Roadmap task `retry`와 SDD `S04`의 PostgreSQL 기준 retry/backoff/idempotency evidence가 충족되었다. |

### 발견된 문제

없음

### 리뷰어 검증

```bash
$ cd services/core && go test -run TestPostgresWebhookDeliveryStoreRecordsRetryState ./internal/storage -v -count=1
=== RUN   TestPostgresWebhookDeliveryStoreRecordsRetryState
--- PASS: TestPostgresWebhookDeliveryStoreRecordsRetryState (0.13s)
PASS
ok  	git.toki-labs.com/toki/gito/services/core/internal/storage	0.132s

$ cd services/core && go test -run 'TestWebhookDeliveryRetry|TestPostgresWebhookDelivery' ./internal/... -v -count=1
=== RUN   TestWebhookDeliveryRetryBackoffRecordsState
--- PASS: TestWebhookDeliveryRetryBackoffRecordsState (0.00s)
=== RUN   TestPostgresWebhookDeliveryStoreRecordsRetryState
--- PASS: TestPostgresWebhookDeliveryStoreRecordsRetryState (0.13s)
PASS

$ cd services/core && go test ./...
PASS

$ git diff --check
PASS
```

### 다음 단계

PASS 절차로 active review/plan을 `.log`로 아카이브하고, `complete.log` 작성 후 task directory를 `agent-task/archive/2026/06/m-webhook-revision-reconcile/02+01_retry_delivery_store/`로 이동한다.
