<!-- task=m-gito-protosocket-consumer-wire-readiness/03+01_failure_ops plan=0 tag=REFACTOR -->

# Plan - REFACTOR

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수입니다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고하세요. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용입니다.

구현 중 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret, 또는 scope conflict가 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 채우고 멈추세요. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않으며, `USER_REVIEW.md`, archive log, `complete.log`를 만들지 않습니다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰가 아닙니다.

## 배경

Gito consumer는 장기 실행 subscription입니다. malformed frame, not-ready revision, duplicate revision은 이미 일부 단위 테스트가 있지만, binary transport 교체 후 reconnect/normal shutdown/logging 기준이 명확하지 않으면 core server shutdown이나 중복 Plane mutation으로 이어질 수 있습니다. 이 계획은 선행 binary transport 위에서 장애 입력이 safe drop/retry/no-op으로 수렴하는 운영 기준을 정리합니다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 구현 에이전트의 직접 사용자 질문은 금지되며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정합니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/gito-protosocket-consumer-wire-readiness.md`
- Task ids:
  - `failure-ops`: reconnect, not-ready revision, duplicate event, malformed frame 처리와 로그 기준 정리
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/gito-protosocket-consumer-wire-readiness.md`
- `/config/workspace/gito/agent-contract/index.md`
- `/config/workspace/gito/agent-contract/provided/gito-forgejo-branch-events-v1.md`
- `agent-test/local/rules.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/workspace-ops-smoke.md`
- `services/core/internal/gitoevents/client.go`
- `services/core/internal/gitoevents/client_test.go`
- `services/core/internal/gitoevents/events.go`
- `services/core/internal/gitoevents/events_test.go`
- `services/core/internal/gitosync/runner.go`
- `services/core/internal/gitosync/bridge.go`
- `services/core/internal/gitosync/bridge_test.go`
- `services/core/internal/gitosync/scanner.go`
- `services/core/internal/gitosync/scanner_test.go`
- `services/core/cmd/server/main.go`
- `services/core/internal/config/config.go`
- `services/core/internal/config/config_test.go`
- `/config/go/pkg/mod/git.toki-labs.com/toki/proto-socket/go@v0.0.0-20260530100557-fed0da37fa1a/ws_client.go`
- `/config/go/pkg/mod/git.toki-labs.com/toki/proto-socket/go@v0.0.0-20260530100557-fed0da37fa1a/communicator.go`

### 테스트 환경 규칙

`test_env=local`입니다. `agent-test/local/rules.md`, `agent-test/local/core-smoke.md`, `agent-test/local/workspace-ops-smoke.md`를 읽었습니다. 적용 명령은 `cd services/core && go test ./...`와 `git diff --check`입니다. failure/reconnect tests should use `-count=1` for fresh async behavior evidence.

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

`Bridge.Handle` duplicate/not-ready behavior는 [bridge_test.go](/config/workspace/nomadcode/services/core/internal/gitosync/bridge_test.go:209)와 [bridge_test.go](/config/workspace/nomadcode/services/core/internal/gitosync/bridge_test.go:226)에 있습니다. `gitoevents.Client` drops malformed/off-target envelopes in [client_test.go](/config/workspace/nomadcode/services/core/internal/gitoevents/client_test.go:113). Missing coverage: binary transport disconnect/reconnect behavior, normal shutdown error classification, malformed binary frame logging after `01_wire_transport_decode`.

### 심볼 참조

none expected. If implementing reconnect introduces new unexported types, no exported call-site update is needed. If `NewRunner` signature changes, grep all references before editing.

### 분할 판단

split decision policy를 평가했습니다. 이 plan은 `03+01_failure_ops`라서 predecessor index `01`에 의존합니다. 현재 predecessor는 `agent-task/archive/2026/06/m-gito-protosocket-consumer-wire-readiness/01_wire_transport_decode/complete.log`로 PASS 완료 근거가 있습니다. `bridge-e2e`와는 독립적으로, binary transport가 생긴 뒤 운영 실패 기준을 보강할 수 있습니다.

### 범위 결정 근거

이 plan은 reconnect/shutdown/malformed/drop/log criteria만 다룹니다. Producer-side Gito dedupe, durable listener persistence, Plane lifecycle state transition은 마일스톤 범위 제외 또는 후속 범위입니다. Full external dev smoke는 `bridge-e2e`와 별도입니다.

### 빌드 등급

`cloud-G06`입니다. 비동기 transport lifecycle과 log/error classification이 있지만 scope는 `gitoevents`/`gitosync`로 제한됩니다.

## 구현 체크리스트

- [ ] 선행 `01_wire_transport_decode`의 active 또는 archived `complete.log`를 확인한 뒤 구현을 시작한다.
- [ ] runner transport disconnect/normal shutdown/malformed binary frame behavior를 분류하고, core server shutdown을 error spam으로 만들지 않는 기준을 구현한다.
- [ ] reconnect 또는 retry policy를 최소 MVP로 정한다. 자동 reconnect를 넣는다면 context cancel과 backoff 상한을 테스트한다. 넣지 않는다면 runner stop/error reporting 기준을 명시하고 test로 고정한다.
- [ ] 기존 not-ready revision과 duplicate event tests가 binary path 변경 뒤에도 중복 Plane mutation을 막는지 보강한다.
- [ ] `git diff --check`와 `cd services/core && go test ./...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 subtask directory의 `+01`이 runtime dependency source of truth입니다. `01_wire_transport_decode`는 `agent-task/archive/2026/06/m-gito-protosocket-consumer-wire-readiness/01_wire_transport_decode/complete.log`에서 PASS 완료 확인되었습니다.

### [REFACTOR-1] Define runner shutdown and reconnect behavior

문제: [main.go](/config/workspace/nomadcode/services/core/cmd/server/main.go:129)는 runner를 goroutine으로 시작하고 returned error를 로그합니다. [gitoevents/client.go](/config/workspace/nomadcode/services/core/internal/gitoevents/client.go:96)는 context cancel이면 nil을 반환하지만 transport disconnect와 malformed binary frame은 runner error로 전파될 수 있습니다. 기준이 없으면 정상 shutdown과 장애가 같은 로그로 보일 수 있습니다.

해결 방법: context cancel/normal close는 nil 또는 non-error log로 정리합니다. unexpected disconnect는 either bounded reconnect loop or explicit retryable error log로 고정합니다. 자동 reconnect를 넣으면 `NewRunner` 내부가 아니라 runner wrapper/helper로 분리해 testable하게 둡니다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/gitosync/runner.go`에서 close/disconnect error classification을 정리한다.
- [ ] `services/core/internal/gitosync/runner_test.go`에 normal shutdown과 unexpected close test를 추가한다.
- [ ] 필요 시 `cmd/server/main.go` log message가 secret/payload 없이 운영 필드만 남기는지 확인한다.

테스트 작성: 필수. 테스트명 예: `TestRunnerStopsCleanlyOnContextCancel`와 reconnect를 구현한다면 `TestRunnerReconnectsAfterUnexpectedDisconnect`.

중간 검증:

```bash
cd services/core && go test ./internal/gitosync -run 'TestRunner(StopsCleanly|Reconnects)' -count=1
```

### [REFACTOR-2] Pin malformed frame and duplicate/no-ready side-effect behavior

문제: [client_test.go](/config/workspace/nomadcode/services/core/internal/gitoevents/client_test.go:113)는 logical malformed envelope을 drop합니다. binary decode가 들어오면 malformed frame은 transport/decode boundary에서 발생하므로 별도 regression이 필요합니다.

해결 방법: malformed binary frame, wrong channel/action, missing payload, unknown metadata/custom field가 handler/enqueuer를 호출하지 않는 tests를 둡니다. 기존 duplicate/not-ready tests는 유지하고, binary path와 결합되는 최소 regression을 추가합니다.

수정 파일 및 체크리스트:

- [ ] `services/core/internal/gitosync/runner_test.go`에 malformed binary frame no-side-effect test를 추가한다.
- [ ] `services/core/internal/gitosync/bridge_test.go` 기존 duplicate/not-ready tests를 유지하고 필요한 assertion만 보강한다.
- [ ] logs에 payload 원문이나 secret이 남지 않도록 assert 가능한 부분만 확인한다.

테스트 작성: 필수. 테스트명 예: `TestRunnerDropsMalformedBinaryFrameWithoutEnqueue`.

중간 검증:

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

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `services/core/internal/gitosync/runner.go` | REFACTOR-1, REFACTOR-2 |
| `services/core/internal/gitosync/runner_test.go` | REFACTOR-1, REFACTOR-2 |
| `services/core/internal/gitosync/bridge_test.go` | REFACTOR-2 if duplicate/not-ready assertions need expansion |
| `services/core/cmd/server/main.go` | REFACTOR-1 if runner error log needs classification |

## 최종 검증

```bash
git diff --check
cd services/core && go test ./...
```

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