<!-- task=m-branch-event-protosocket-interop-readiness plan=1 tag=REVIEW_TEST -->

# Follow-up Plan - REVIEW_TEST

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

`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션을 반드시 채워야 한다. 체크리스트의 마지막 항목(`CODE_REVIEW-local-G05.md` 작성)이 완료되기 전에는 구현이 완료된 것이 아니다.

구현이 사용자 결정, 사용자 소유 외부 환경, 또는 범위 충돌로 막히면 `CODE_REVIEW-local-G05.md`의 `사용자 리뷰 요청` 섹션에 근거를 채우고 active 파일을 그대로 둔다. 사용자에게 직접 질문하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/branch-event-protosocket-interop-readiness.md`
- Task ids:
  - `producer-fixture`: 실제 proto-socket Go client 또는 동등한 binary fixture로 `event.subscribe`를 보내고 `branch.updated`를 수신하는 producer-side 테스트 추가
  - `webhook-smoke`: Forgejo push webhook, watched branch listener, event outbox/fanout을 이어 `branch.updated`가 발행되는 smoke 절차
- Completion mode: check-on-pass

## 배경

첫 리뷰에서 `TestBinaryProtoSocketBranchUpdated`의 binary proto-socket 수신 경로와 필수 검증 명령은 통과했다. 다만 원 계획과 구현 체크리스트가 요구한 `changed_files[0].path` 검증이 구현되지 않았다. 현재 테스트는 `changed_files`가 비어 있지 않은지만 확인하므로, payload에 다른 파일 경로가 들어가도 테스트가 통과할 수 있다.

## 범위 결정 근거

- 변경 범위는 `services/core/internal/controlplane/binary_interop_test.go`의 assertion 보강으로 제한한다.
- production 코드, 계약 문서, 로드맵 문서는 수정하지 않는다.
- 기존 binary proto-socket E2E 흐름은 유지하고, 계획 누락분인 payload 필드 검증만 추가한다.

## 구현 체크리스트

- [ ] `services/core/internal/controlplane/binary_interop_test.go`에서 `changed_files[0].path == "docs/README.md"`와 `changed_files[0].change_type == "modified"`를 검증한다.
- [ ] `cd services/core && go test -count=1 -run TestBinaryProtoSocketBranchUpdated ./internal/controlplane/ -v` 통과 확인
- [ ] `cd services/core && go test -count=1 ./...` 전체 패키지 회귀 확인
- [ ] `CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_TEST-1] changed_files 첫 항목의 path/change_type 단언 추가

### 문제

`services/core/internal/controlplane/binary_interop_test.go:127` - 기존 계획은 `changed_files[0].path` 검증을 요구했지만, 현재 테스트는 `len(files) == 0`만 검사한다. 이 상태에서는 Forgejo payload의 변경 파일 경로가 binary proto-socket envelope payload로 올바르게 전달되는지 계약 수준으로 보장하지 못한다.

### 해결 방법

`files[0]`를 `map[string]any`로 확인한 뒤 `path`와 `change_type` 값을 직접 비교한다. 타입 변환에 실패하면 `t.Fatalf`로 실패시켜 payload shape 변화도 잡는다.

Before:

```go
files, _ := env.Payload["changed_files"].([]any)
if len(files) == 0 {
	t.Error("expected changed_files, got none")
}
```

After:

```go
files, _ := env.Payload["changed_files"].([]any)
if len(files) == 0 {
	t.Fatal("expected changed_files, got none")
}
firstFile, ok := files[0].(map[string]any)
if !ok {
	t.Fatalf("changed_files[0]: got %T", files[0])
}
if firstFile["path"] != "docs/README.md" {
	t.Errorf("changed_files[0].path: got %v", firstFile["path"])
}
if firstFile["change_type"] != "modified" {
	t.Errorf("changed_files[0].change_type: got %v", firstFile["change_type"])
}
```

### 테스트 결정

기존 검증 계약을 그대로 실행한다.

### 중간 검증

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

### 최종 검증

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

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/internal/controlplane/binary_interop_test.go` | REVIEW_TEST-1 |
