<!-- task=05_event_bus_replay plan=0 tag=EBR -->

# 05 Event Bus Replay Plan - EBR

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

**필수: 구현 마지막에는 반드시 `CODE_REVIEW-*-G??.md`의 모든 섹션을 실제 구현 내용과 명령 출력으로 채운다.**
이 task는 event bus의 유실 관찰성과 bounded replay만 다룬다.

## 배경

Ops console과 HTTP/SSE가 같이 event bus를 볼 경우 이벤트 유실이 조용히 발생하면 장애 분석이 어렵다. 현재 bus는 subscriber channel이 꽉 차면 아무 기록 없이 drop한다.

## 분석 결과

### 읽은 파일

- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `apps/edge/internal/events/bus.go`
- `apps/edge/internal/events/bus_test.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/cmd/edge/console.go`
- `proto/iop/runtime.proto`

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

- drop count 테스트가 없다.
- recent event replay 테스트가 없다.
- subscriber buffer overflow 동작이 명시되지 않았다.

### 심볼 참조

- `offerRun`: `bus.go:140`.
- `offerNode`: `bus.go:147`.
- `SubscribeRun`: `bus.go:28`, calls `service.go:97`.
- `SubscribeAllRuns`: `bus.go:78`, calls `console.go:53`.

### 범위 결정 근거

- DB persistence는 제외한다.
- HTTP/SSE implementation은 제외한다.
- event schema 변경은 제외한다.

### 빌드 등급

- Build `cloud-G08`: concurrency/event fanout policy 변경.
- Review `cloud-G08`: race/loss semantics와 tests 확인 필요.

### [EBR-1] drop stats 추가

#### 문제

`offerRun`/`offerNode`가 default branch에서 조용히 event를 버린다.

#### 해결 방법

- `BusStats`를 추가한다.
- run/node/all subscriber별 또는 총합 drop count를 기록한다.
- `Stats()`는 lock-safe snapshot을 반환한다.

#### 수정 파일 및 체크리스트

- [ ] `apps/edge/internal/events/bus.go`
- [ ] `apps/edge/internal/events/bus_test.go`

#### 테스트 작성

- `TestBusCountsDroppedRunEvents`
- `TestBusCountsDroppedNodeEvents`

#### 중간 검증

```bash
go test -count=1 ./apps/edge/internal/events
```

### [EBR-2] bounded replay 추가

#### 문제

늦게 붙은 ops/debug surface가 최근 lifecycle/run event를 확인할 방법이 없다.

#### 해결 방법

- Bus에 bounded ring buffer를 둔다.
- `ReplayRun(runID)`와 `ReplayNode(nodeID)` 또는 `Subscribe...WithReplay`를 제공한다.
- 기본 보관 개수는 작고 명시적인 상수로 둔다.

#### 수정 파일 및 체크리스트

- [ ] `apps/edge/internal/events/bus.go`
- [ ] `apps/edge/internal/events/bus_test.go`
- [ ] `apps/edge/internal/service/service.go` if replay is exposed through service

#### 테스트 작성

- `TestBusReplaysRecentRunEvents`
- `TestBusReplayIsBounded`

#### 중간 검증

```bash
go test -count=1 ./apps/edge/internal/events ./apps/edge/internal/service
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `apps/edge/internal/events/bus.go` | EBR-1, EBR-2 |
| `apps/edge/internal/events/bus_test.go` | EBR-1, EBR-2 |
| `apps/edge/internal/service/service.go` | EBR-2 |

## 최종 검증

```bash
go test -race -count=1 ./apps/edge/internal/events
go test -count=1 ./apps/edge/internal/events ./apps/edge/internal/service ./...
```

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