<!-- task=m-stream-evidence-gate-core/05+02_parallel_evaluation_coordinator plan=0 tag=API -->

# Stream Gate Parallel Evaluation Coordinator 구현 계획

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

- 아래 구현 체크리스트와 검증 명령을 완료하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 기록한다.
- active PLAN/CODE_REVIEW 파일은 그대로 두고 review-ready만 보고한다. 판정, 로그 rename, `complete.log`, task archive는 code-review 책임이다.
- 선택된 Milestone `구현 잠금 > 결정 필요`가 구현을 막을 때만 review stub의 `사용자 리뷰 요청`에 정확한 연결 근거를 기록하고 중단한다.
- 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`를 만들지 않는다. 환경/secret/서비스, 일반 범위 조정, 후속 에이전트가 닫을 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

Milestone S12는 같은 immutable `EvidenceBatch`의 ready filter를 병렬 실행하되 complete outcome set 전에는 Arbiter를 호출하지 않고, epoch 사이에는 single-flight와 bounded backpressure를 보장하도록 요구한다. 이 동시성 경계가 없으면 느린 filter가 있는 동안 ingress가 무제한 적재되거나 completion order에 따라 action이 달라질 수 있다. 선행 evaluation contract 위에 transport-agnostic Gate Coordinator와 결정적 race/ordering evidence를 추가한다.

## 사용자 리뷰 요청 흐름

선택된 Milestone `구현 잠금 > 결정 필요`만 active review stub의 `사용자 리뷰 요청`에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따르며, 구현 에이전트의 직접 사용자 질문은 금지된다. 요청 검증과 실제 `USER_REVIEW.md` 작성은 code-review가 소유한다.

## Archive Evidence Snapshot

- 기반 task: `m-stream-evidence-gate-core/01_event_contract_types`
- 완료 근거: `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
- Verdict: `PASS`
- Carryover: immutable batch와 네 outcome variant가 완료됐다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
- Task ids:
  - `parallel-evaluation`: immutable batch 병렬 평가, complete outcome barrier, single-flight와 bounded backpressure
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/filter_contract.go`
- `packages/go/streamgate/filter_contract_test.go`
- `packages/go/streamgate/validation_regression_test.go`
- `go.mod`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-spec/index.md`
- `agent-contract/index.md`
- `agent-roadmap/current.md`
- `agent-roadmap/priority-queue.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- `agent-task/m-stream-evidence-gate-core/03+01_event_contract_unit_tests/PLAN-local-G05.md`
- `agent-task/m-stream-evidence-gate-core/04+01,03_codec_contract_fixtures/PLAN-local-G04.md`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 `없음`
- 대상 Acceptance Scenario: S12 / Milestone Task `parallel-evaluation`
- Evidence Map S12: evaluated/deferred/not-applicable mixed outcomes, barrier, reverse completion, race, backpressure, failure/cancel fixtures가 필요하다.
- 이 mapping에 따라 fan-out과 all-complete를 한 항목에, single-flight/queue/cancel/deadline을 다른 항목에 두고, focused fresh test와 package-wide `-race`를 최종 검증에 포함한다.

### 테스트 환경 규칙

- `test_env`: local
- `agent-test/local/rules.md`: 존재하며 전체를 읽었다.
- 매칭 profile: `agent-test/local/platform-common-smoke.md`
- 적용 명령: `make proto`, fresh `go test -count=1` 대상 package 및 Edge/Node transport, `go test -race -count=1`, `go vet`, `gofmt -d`, scoped `git diff --check`
- 현재 runner는 `go1.26.2 linux/arm64`, module 선언은 Go `1.24`다. baseline package, race, vet, transport 테스트가 모두 통과했다.
- 외부 runner/secret/service와 `<확인 필요>`는 없다. host 실제 stream 연결은 vertical-slice/host adoption Task 책임이므로 이 plan에서는 test-local fake evaluator/arbiter로 closure한다.

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

- ready filter 동시 시작과 reverse completion: 구현/테스트 없음.
- not-applicable/deferred를 포함한 complete set 전 Arbiter 호출 금지: 구현/테스트 없음.
- epoch single-flight, FIFO accepted work, bounded pending queue: 구현/테스트 없음.
- caller cancel, host shutdown, per-filter deadline과 blocking/observe failure policy: 구현/테스트 없음.
- `EvidenceBatch` 불변성과 outcome 값 validation은 predecessor 테스트가 커버한다.

### 심볼 참조

- rename/remove 없음.
- 새 third-party dependency 없음. goroutine, channel, context, sync, time 등 Go 표준 라이브러리만 사용한다.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 적용했다.
- shared task group: `m-stream-evidence-gate-core`
- `02+01_evaluation_contract`가 resolved filter binding과 immutable outcome set을 먼저 제공한다.
- 이 plan의 directory `05+02_parallel_evaluation_coordinator`는 predecessor index `02` 하나만 encode한다. `02`는 plan 작성 시 미완료이므로 active/archive `complete.log`가 생기기 전 구현을 시작하지 않는다.
- index `03`과 `04`는 이미 active sibling이 점유하므로 producer `02`보다 큰 최저 collision-free index `05`를 사용했다.
- active `03`/`04`와 source write set이 겹치지 않으며, `04`의 dependency를 이 plan에 숨겨 추가하지 않는다.

### 범위 결정 근거

- 수정 허용: 새 `parallel_evaluation.go`, `parallel_evaluation_test.go`.
- `02`가 만든 실행 계약은 소비만 하고 재정의하지 않는다.
- actual decision priority/terminal/recovery 의미를 계산하는 `decision-arbiter`, Registry resolution, evidence tail, response staging, observation sink, host dispatch는 별도 Milestone Task에 남긴다.
- `EpochArbiter` seam은 complete set을 한 번 전달받았음을 증명하는 경계까지만 소유하며 public OpenAI 응답이나 recovery action을 만들지 않는다.
- Edge/Node host, proto, config, agent-contract, agent-spec, roadmap 문서는 변경하지 않는다.

### 최종 라우팅

- evaluation_mode: `first-pass`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - scores: scope coupling 1, state/concurrency 2, blast/irreversibility 1, evidence diagnosis 0, verification complexity 1
  - result: `local`, `G05`, `PLAN-local-G05.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - scores: scope coupling 1, state/concurrency 2, blast/irreversibility 1, evidence diagnosis 1, verification complexity 1
  - result: `local`, `G06`, `CODE_REVIEW-local-G06.md`

## 구현 체크리스트

- [ ] `API-1` ready filter를 같은 immutable batch에서 동시에 실행하고 non-ready outcome을 함께 모아 stable complete set 이후에만 EpochArbiter를 한 번 호출하도록 구현·테스트한다.
- [ ] `API-2` epoch worker를 single-flight로 유지하고 bounded pending queue, caller cancel, host shutdown, per-filter deadline, blocking-fatal/observe-error 정책을 leak 없이 처리하도록 구현하고 ordering/race 테스트를 fresh 실행한다.
- [ ] 계획의 전체 local 검증 명령을 fresh 및 race cache 조건으로 실행하고 실제 stdout/stderr를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Parallel fan-out와 all-complete Arbiter barrier

- 문제:
  - `packages/go/streamgate/filter_contract.go:836-840`은 outcome이 batch 밖에 유지된다고만 규정하며 ready filter 실행이나 barrier가 없다.
  - completion 순서대로 Arbiter에 전달하면 느린 blocking filter가 끝나기 전에 release/recovery가 결정될 수 있다.
- 해결 방법:
  - `GateCoordinator.Evaluate(ctx, batch, filters)`는 batch와 binding을 먼저 검증하고 ready filter마다 goroutine 하나를 시작한다.
  - result channel은 ready filter 수로 bound해 cancel 경로에서 producer가 영구 block되지 않게 하고, 모든 goroutine 결과를 수집한 뒤 predecessor의 immutable `EvaluationSet`을 만든다.
  - not-applicable/deferred는 evaluator를 호출하지 않고 complete set에 포함한다.
  - `EpochArbiter`는 stable complete set이 준비되고 caller/host context가 살아 있을 때 정확히 한 번 호출한다. concrete arbitration result/action은 `decision-arbiter` Task 범위다.
- Before (`filter_contract.go:836-840`):

```go
// Filter results are
// maintained outside the batch as a separate set of FilterOutcome values.
```

- After (`parallel_evaluation.go`):

```go
type EpochArbiter interface {
	Arbitrate(context.Context, EvidenceBatch, EvaluationSet) error
}

func (c *GateCoordinator) Evaluate(
	ctx context.Context,
	batch EvidenceBatch,
	filters []EpochFilter,
) (EvaluationSet, error)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/parallel_evaluation.go`: evaluator fan-out, buffered collection, complete-set validation, one-shot Arbiter call
  - [ ] `packages/go/streamgate/parallel_evaluation_test.go`: mixed outcomes, simultaneous start, reverse completion, barrier spy, stable order, evaluator call count
- 테스트 작성:
  - `TestGateCoordinatorWaitsForCompleteOutcomeSet`: ready 두 개를 동시에 시작해 역순 완료시키고 deferred/not-applicable을 포함한 네 outcome 전에는 Arbiter가 호출되지 않음을 검증한다.
  - `TestGateCoordinatorPassesSameImmutableBatch`: evaluator accessor mutation이 다른 evaluator와 원본 batch에 영향을 주지 않고 epoch/capturedAt snapshot이 일치함을 검증한다.
- 중간 검증:
  - `go test -count=1 ./packages/go/streamgate -run '^TestGateCoordinator(WaitsForCompleteOutcomeSet|PassesSameImmutableBatch)$'`
  - 기대 결과: PASS

### [API-2] Single-flight, bounded backpressure와 cancel/failure policy

- 문제:
  - SDD S12는 evaluation 중 다음 ingress의 bounded backpressure와 caller cancel/host shutdown no-release를 요구하지만 현재 queue/worker lifecycle이 없다.
  - filter deadline/error를 raw error로 전달하거나 cancel 뒤 Arbiter를 호출하면 fail policy와 no-release 경계가 깨진다.
- 해결 방법:
  - constructor가 host parent context, positive `max_queued_epochs`, `EpochArbiter`를 받아 단일 worker와 bounded jobs channel을 소유한다. 한 epoch의 filter fan-out은 병렬이지만 worker는 다음 epoch를 앞 epoch complete/abort 전 시작하지 않는다.
  - queue가 가득 차면 submit caller를 내부 추가 적재 없이 block하고 caller context 또는 host shutdown으로 해제한다. queued caller cancel은 evaluation 없이 종료한다.
  - ready filter마다 binding timeout을 적용한다. raw error string은 버리고 stable `filter_evaluation_error|filter_evaluation_deadline` code만 outcome에 남긴다. blocking은 fatal disposition, observe-only는 observe-error disposition으로 정규화한다.
  - caller cancel/host shutdown은 child filter context를 cancel하고 complete result/Arbiter 호출 없이 종료한다. `Close`는 idempotent하고 queued waiter를 모두 해제한다. filter는 context 준수 계약을 따라야 한다.
- Before (`filter_contract.go:197-203`):

```go
type FilterOutcome struct {
	kind      FilterOutcomeKind
	decision  *FilterDecision
	errorCode StableToken
}
```

- After (`parallel_evaluation.go`):

```go
type GateCoordinatorOptions struct {
	MaxQueuedEpochs int
}

func NewGateCoordinator(
	hostCtx context.Context,
	options GateCoordinatorOptions,
	arbiter EpochArbiter,
) (*GateCoordinator, error)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/parallel_evaluation.go`: single worker, bounded queue, context composition, deadline/error normalization, idempotent close
  - [ ] `packages/go/streamgate/parallel_evaluation_test.go`: max concurrent epoch, queue saturation, FIFO acceptance, cancel/deadline/host close, blocking/observe policy, goroutine cleanup
- 테스트 작성:
  - `TestGateCoordinatorSingleFlightAndBoundedBackpressure`: active epoch 1개와 queued epoch 1개만 수용하고 추가 submit은 deadline까지 backpressure를 받으며 max concurrent epoch가 1임을 검증한다.
  - `TestGateCoordinatorCancellationSkipsArbiter`: caller cancel과 host close가 evaluator를 취소하고 queued waiter를 해제하며 Arbiter 호출 수를 늘리지 않음을 검증한다.
  - `TestGateCoordinatorNormalizesDeadlineAndFailurePolicy`: blocking/observe evaluator error와 deadline을 stable code/disposition으로 구분하고 raw error가 result에 없음을 검증한다.
- 중간 검증:
  - `go test -race -count=1 ./packages/go/streamgate -run '^TestGateCoordinator(SingleFlightAndBoundedBackpressure|CancellationSkipsArbiter|NormalizesDeadlineAndFailurePolicy)$'`
  - 기대 결과: PASS, race 없음

## 의존 관계 및 구현 순서

1. 직접 predecessor는 `02+01_evaluation_contract`다. runtime은 active 또는 archive `complete.log`를 확인한 뒤 이 plan을 dispatch해야 한다.
2. `API-1`의 fan-out/all-complete 경계를 먼저 구현한다.
3. `API-2`의 worker/queue가 `API-1`을 한 epoch의 유일 실행 함수로 감싼다. directory 이름에 없는 `03`/`04` 의존은 추가하지 않는다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/parallel_evaluation.go` | API-1, API-2 |
| `packages/go/streamgate/parallel_evaluation_test.go` | API-1, API-2 |

## 최종 검증

1. `gofmt -d packages/go/streamgate/parallel_evaluation.go packages/go/streamgate/parallel_evaluation_test.go`
   - 기대 결과: stdout 없음
2. `go test -count=1 ./packages/go/streamgate -run '^TestGateCoordinator(WaitsForCompleteOutcomeSet|PassesSameImmutableBatch|SingleFlightAndBoundedBackpressure|CancellationSkipsArbiter|NormalizesDeadlineAndFailurePolicy)$'`
   - 기대 결과: PASS
3. `go test -race -count=1 ./packages/go/streamgate -run '^TestGateCoordinator'`
   - 기대 결과: PASS, race 없음
4. `go test -race -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS, race 없음
5. `go vet ./packages/go/streamgate`
   - 기대 결과: PASS, stdout 없음
6. `make proto`
   - 기대 결과: 성공, proto 생성물 추가 diff 없음
7. `go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`
   - 기대 결과: PASS
8. `git diff --check -- packages/go/streamgate proto/gen/iop`
   - 기대 결과: stdout 없음

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