<!-- task=m-provider-adapter-foundation/02+01_webhook_adapter_wiring plan=1 tag=REVIEW_API -->

# Provider Webhook Adapter Wiring Follow-up Plan

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

이 계획은 직전 리뷰의 Required 이슈만 고친다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실구현을 차단할 때만 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/provider-change-request-integration/milestones/provider-adapter-foundation.md`
- Task ids:
  - `verify`: webhook signature 검증 hook을 adapter별로 꽂을 수 있게 한다.
  - `normalize`: provider payload를 provider-neutral event 후보로 변환한다.
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-provider-adapter-foundation/02+01_webhook_adapter_wiring/plan_local_G06_0.log`
- Current archived review: `agent-task/m-provider-adapter-foundation/02+01_webhook_adapter_wiring/code_review_local_G06_0.log`
- Verdict: FAIL
- Required summary:
  - `services/core/internal/provider/forgejo/push.go:46` treats empty `EventType` as unsupported, but `X-Forgejo-Event` is only recommended and the old router accepted missing event headers as push payloads.
  - `services/core/internal/provider/forgejo/push.go:51` converts malformed payload parse errors into empty candidates, so `router.go:281` returns `202 matched=false` instead of contract-required `400`.
  - `services/core/internal/provider/forgejo/push.go:63` loses the `repository.name` fallback and empty repo id error that `NormalizePush` and the contract require.
- Affected files:
  - `services/core/internal/provider/forgejo/push.go`
  - `services/core/internal/provider/forgejo/push_test.go`
  - `services/core/internal/controlplane/router.go`
  - `services/core/internal/controlplane/router_test.go`
- Verification evidence from review:
  - `cd services/core && go test -count=1 ./internal/provider/forgejo ./internal/controlplane` passed.
  - `cd services/core && go test ./...` passed.
  - `git diff --check` passed.
- Split dependency evidence:
  - Predecessor complete log exists at `agent-task/archive/2026/06/m-provider-adapter-foundation/01_webhook_adapter_contract/complete.log`.
- Narrow archive reread allowed if needed:
  - `agent-task/m-provider-adapter-foundation/02+01_webhook_adapter_wiring/code_review_local_G06_0.log`
  - `agent-task/m-provider-adapter-foundation/02+01_webhook_adapter_wiring/plan_local_G06_0.log`
  - `agent-task/archive/2026/06/m-provider-adapter-foundation/01_webhook_adapter_contract/complete.log`

## 범위 결정 근거

- 범위는 Forgejo webhook adapter normalization regression과 그 router contract tests로 제한한다.
- Provider contract type 자체, generic provider receipt route, durable store/idempotency, outbound webhook delivery는 수정하지 않는다.
- 선행 `01_webhook_adapter_contract`는 archive complete log로 충족되어 있으므로 source change 대상이 아니다.

## 구현 체크리스트

- [ ] `X-Forgejo-Event`가 비어 있을 때 Forgejo push payload를 정상 normalize하도록 `services/core/internal/provider/forgejo/push.go`를 수정하고 adapter/router 테스트를 추가한다.
- [ ] malformed Forgejo push payload가 normalizer error를 통해 `/callbacks/forgejo/push`에서 `400`으로 유지되도록 수정하고 router 테스트를 추가한다.
- [ ] adapter repo id fallback이 `repo_id` query, `repository.full_name`, `repository.name` 순서를 보존하고 repo id가 없으면 error가 되도록 수정하고 adapter 테스트를 추가한다.
- [ ] `cd services/core && go test -count=1 ./internal/provider/forgejo ./internal/controlplane`와 `cd services/core && go test ./...`를 실행한다.
- [ ] CODE_REVIEW-*-G04.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_API-1] Missing Event Header Regression

### 문제

[services/core/internal/provider/forgejo/push.go](/config/workspace/gito/services/core/internal/provider/forgejo/push.go:46)가 `EventType == ""`를 unsupported로 처리한다. 기존 router는 event header가 비어 있으면 payload를 parse했고, contract에서 `X-Forgejo-Event`는 권장 header다.

### 해결 방법

- `eventType != "" && eventType != "push"`일 때만 empty candidate를 반환한다.
- empty event type이면 push payload normalization을 계속 수행한다.
- adapter unit test와 router behavior test 중 최소 하나는 missing event header가 watched branch event를 만드는지 확인한다.

### 테스트 결정

- 작성/수정: `TestWebhookAdapterTreatsEmptyEventAsPush` 또는 동등한 이름.
- 작성/수정: `TestForgejoPushTreatsMissingEventHeaderAsPush` 또는 동등한 router test.

### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/provider/forgejo ./internal/controlplane -run 'TestWebhookAdapter|TestForgejoPush'
```

## [REVIEW_API-2] Malformed Payload Status Regression

### 문제

[services/core/internal/provider/forgejo/push.go](/config/workspace/gito/services/core/internal/provider/forgejo/push.go:51)가 parse error를 empty candidates로 바꾼다. 그 결과 [services/core/internal/controlplane/router.go](/config/workspace/gito/services/core/internal/controlplane/router.go:281)가 malformed webhook payload에도 `202 matched=false`를 반환한다.

### 해결 방법

- `ParsePushPayload` error는 `NormalizeWebhook` error로 반환한다.
- router는 normalizer error를 기존 contract에 맞춰 `400`으로 반환한다.
- unsupported event와 non-branch ref만 side-effect 없는 empty candidates로 유지한다.

### 테스트 결정

- 작성/수정: `TestForgejoPushRejectsMalformedPayload` 또는 동등한 router test.
- 필요하면 adapter test에서 malformed payload가 error를 반환하는지 확인한다.

### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/controlplane -run 'TestForgejoPushRejectsMalformedPayload'
```

## [REVIEW_API-3] Repo ID Fallback Regression

### 문제

[services/core/internal/provider/forgejo/push.go](/config/workspace/gito/services/core/internal/provider/forgejo/push.go:63)가 repo id를 query와 `repository.full_name`에서만 정한다. 기존 `NormalizePush`와 contract는 query `repo_id`, `repository.full_name`, `repository.name` 순서를 요구하고, 모두 없으면 invalid payload로 처리해야 한다.

### 해결 방법

- 가능하면 `NormalizeWebhook`에서 `NormalizePush(payload, queryRepoID, req.ReceivedAt)`를 재사용해 기존 fallback/error semantics를 보존한다.
- 직접 구현한다면 `repository.name` fallback과 empty repo id error를 반드시 포함한다.
- `ReceivedAt`이 zero인 경우 기존 `NormalizePush`의 fallback도 유지한다.

### 테스트 결정

- 작성/수정: `TestWebhookAdapterFallsBackToRepositoryName` 또는 동등한 adapter test.
- 작성/수정: repo id source가 모두 비면 error가 나는 adapter test.

### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/provider/forgejo -run 'TestWebhookAdapter'
```

## 최종 검증

```bash
cd services/core && go test -count=1 ./internal/provider/forgejo ./internal/controlplane
cd services/core && go test ./...
```

기대 결과: 두 명령 모두 exit 0. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G04.md`의 구현 에이전트 소유 섹션을 채운다.
