<!-- task=m-stream-evidence-gate-core/19+18_core_runtime_loop plan=1 tag=REVIEW_CORE_RUNTIME_LOOP -->

# Plan - Stream Gate owner loop 구현 복구

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

구현과 테스트, 실제 검증 evidence만 소유한다. 아래 체크리스트를 끝낸 뒤 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 stdout/stderr로 채우고 active pair를 그대로 둔 채 리뷰 준비 완료를 보고한다. 차단 시에는 정확한 blocker, 시도한 명령/출력, 재개 조건만 구현 소유 evidence에 기록한다. 사용자에게 결정을 묻거나 user-input 도구·control-plane stop 파일을 만들거나 다음 상태를 분류하지 않으며, log archive·`complete.log`·roadmap·`WORK_LOG.md`는 수정하지 않는다.

## 배경

첫 구현 루프는 `RuntimeOptions`와 `RequestRuntimeSnapshot` 계약만 남기고 request-local owner loop, fake-host fixture, 검증 evidence를 전혀 작성하지 않아 FAIL했다. 이 후속은 기존 transport-agnostic 경계를 유지하면서 initial/recovery attempt binding을 하나의 설치 경로로 직렬화하고, normalized event부터 단일 release/recovery/terminal까지 Core lifecycle을 닫는다. Edge codec·auth·provider admission·HTTP writer 연결은 후속 sibling 책임으로 남긴다.

## Archive Evidence Snapshot

- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G08_0.log`
- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G08_0.log`
- 판정: `FAIL`
- 문제 수: Required 3, Suggested 0, Nit 0
- 문제 요약:
  - `packages/go/streamgate/runtime.go`에 owner loop가 없고 snapshot/context builder에서 끝난다.
  - 계획된 `packages/go/streamgate/runtime_test.go`가 없어 lifecycle fixture가 없다.
  - 구현 체크·설계 결정·실제 검증 출력이 비어 있어 verification trust가 성립하지 않는다.
- 영향 파일: `packages/go/streamgate/runtime.go`, `packages/go/streamgate/runtime_test.go`, active review evidence
- 실제 검증 evidence: Go `1.26.2`, GOROOT `/config/opt/go`; `go test -count=1 ./packages/go/streamgate`, `go test -race -count=1 ./packages/go/streamgate`, `git diff --check`는 기존 계약 기준선에서 PASS했지만 owner lifecycle을 실행하는 테스트는 없었다.
- 기본 재열람은 불필요하다. 세부 판정이 필요할 때만 위 두 archive 파일을 좁게 읽는다.
- Roadmap carryover: 이 subtask 단독 PASS는 Milestone Task id 완료를 주장하지 않으므로 `Roadmap Targets`를 두지 않는다.

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/runtime.go`
- `packages/go/streamgate/runtime_contract_test.go`
- `packages/go/streamgate/commit_boundary.go`
- `packages/go/streamgate/parallel_evaluation.go`
- `packages/go/streamgate/recovery_coordinator.go`
- `packages/go/streamgate/decision_arbiter.go`
- `packages/go/streamgate/evidence_tail.go`
- `packages/go/streamgate/filter_registry.go`
- `packages/go/streamgate/filter_contract.go`
- `packages/go/streamgate/recovery_plan.go`
- `packages/go/streamgate/stream_release.go`
- `packages/go/streamgate/terminal.go`
- `packages/go/streamgate/event.go`
- `apps/edge/internal/openai/stream_gate_dispatcher.go`
- `agent-task/m-stream-evidence-gate-core/17+13,16_openai_request_rebuilder/PLAN-cloud-G09.md`
- `agent-task/m-stream-evidence-gate-core/20+17,19_edge_vertical_slice/PLAN-cloud-G09.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-contract/index.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`
- 이전 active plan/review pair와 현재 git diff/status

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`, 사용자 리뷰 `없음`.
- 대상 시나리오: S03/S05의 staged response와 commit eligibility, S12/S13의 single-flight all-complete arbitration, S14의 cycle당 단일 plan/dispatch 및 cap, S16의 Noop/Injected vertical lifecycle, S18의 continuation cursor/look-behind 보존, S20의 abort 실패 시 no-next-dispatch, S23의 abort 후 one-shot prepare.
- Evidence Map은 eager commit 없음, complete outcome set, one plan/dispatch/terminal, replace reset/continuation preserve, prepare success/failure/no-budget, binding switch/backpressure/cancel/exhausted fixture를 요구한다. 이를 구현 체크리스트와 `runtime_test.go` assertion 및 package race 검증으로 역산했다.
- 이 subtask는 `recovery-coordinator`/`vertical-slice` 전체를 혼자 닫지 않으므로 Roadmap Task id를 체크하지 않는다.

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다.
- 호스트 `go`는 `/config/.local/bin/go` → `/config/opt/go/bin/go`, 버전 `go1.26.2`, GOROOT `/config/opt/go`로 일치한다.
- profile setup인 `make proto`를 실행하되 proto 원본/생성물은 수정 범위가 아니다. profile의 Edge-Node wire unit 명령은 이 transport-agnostic package 동작을 실행하지 않으므로 대상 package fresh unit/race를 직접 검증 근거로 사용한다.
- 외부 runner, Docker, provider, port, credential은 필요 없다. repo-local 도구 산출물은 만들지 않는다.

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

- `runtime_contract_test.go`는 Noop filter, options, immutable snapshot validation만 검증한다.
- owner loop, staged response, event ingestion, attempt-local filter 재해석, recovery rebinding, prepare, backpressure, cancellation, exhausted single terminal을 실행하는 테스트는 없다.
- 새 `runtime_test.go`가 production owner와 같은 공개 진입점을 사용해 이 공백을 모두 닫아야 한다.

### 심볼 참조

- rename/remove 대상은 없다.
- 새 owner API는 현재 call site가 없고 `20+17,19_edge_vertical_slice`가 후속 소비자다. Core에는 `apps/*/internal` import를 추가하지 않는다.

### 분할 판단

- split decision policy를 파일명 선택 전에 평가했다.
- 기존 고정 subtask `19+18_core_runtime_loop` 안의 단일 request owner와 그 fake-host fixture는 같은 state/API를 동시에 검증해야 하므로 분리하면 review 가능한 production invariant가 끊어진다.
- ownership boundary는 `packages/go/streamgate` 하나이고, Edge host adapter·observation sink는 이미 별도 sibling이다. API-vs-call-site split도 후속 `20`에 존재한다.
- 선행 index `18`은 `agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`의 PASS로 충족됐다.

### 범위 결정 근거

- 수정 범위는 `packages/go/streamgate/runtime.go`와 새 `packages/go/streamgate/runtime_test.go`다.
- raw codec/proto, concrete provider selection/auth, OpenAI JSON rebuild, HTTP/SSE serialization, config, observation sink, Edge handler 연결은 제외한다.
- 기존 component contract를 재구현하지 않고 `FilterRegistrySnapshot`, `EvidenceTail`, `GateCoordinator`, `DecisionArbiter`, `RecoveryCoordinator`, `StreamReleaser`를 조립한다.
- roadmap과 다른 active sibling 파일은 수정하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh`
- finalizer_mode: `pair`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`.
  - scope: 승인 SDD와 FAIL finding이 owner loop·fixture 두 파일로 닫힌다.
  - context: request snapshot과 기존 Core component 계약, predecessor PASS, 후속 Edge 소비 경계가 확인됐다.
  - verification/evidence: fresh package unit/race와 deterministic sink/dispatch assertions로 판정 가능하다.
  - ownership/decision: platform-common subtask 단일 소유이고 Milestone 잠금·결정 필요가 없다.
- build route_basis: `local-fit`; capability_gap: `none`; floor: `none`.
- build grade scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=2, verification_complexity=1, 합계 G08.
- build loop risk:
  - temporal_state: request/attempt/staging/holding/evaluating/arbitrating/recovery/release/terminal과 retry/cancel/recovery/exhaustion 경로.
  - concurrent_consistency: owner, Gate worker, filter evaluators 사이 single-flight/backpressure/exactly-once 제약.
  - boundary_contract: registry/tail/gate/arbiter/recovery/releaser/sink와 immutable validation 제약.
  - structured_interpretation: 없음; Core는 typed normalized event만 소비한다.
  - variant_product: commit state × action × resume mode × filter enforcement 조합 검증이 필요하다.
  - matched signatures: `temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product`; triggered=true.
- build output: lane=`local`, grade=`G08`, filename=`PLAN-local-G08.md`.
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`; 복수 component state와 FAIL evidence를 독립 검토할 수 있다.
- review route_basis: `official-review`; capability_gap: `none`; floor: `none`.
- review grade scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=2, verification_complexity=1, 합계 G08.
- review output: lane=`cloud`, grade=`G08`, filename=`CODE_REVIEW-cloud-G08.md`, target=`Codex gpt-5.6-sol xhigh`.

## 구현 체크리스트

- [ ] REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다.
- [ ] REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, exhausted single terminal fixture를 추가한다.
- [ ] REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate unit/race, formatting/import boundary, diff 검증을 수행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_CORE_RUNTIME_LOOP-1] request-local owner lifecycle

- 문제: `packages/go/streamgate/runtime.go:115-297`은 immutable snapshot과 `BuildAttemptFilterContext`만 제공한다. initial binding 설치, event source loop, staged response, filter resolution, tail epoch, arbitration, recovery rebinding, terminal convergence를 소유하는 runtime이 없다.
- 해결 방법:
  - host가 최초 admission으로 만든 `AttemptBinding`과 request의 model group을 받아 validated `RequestRuntime`을 구성한다. 최초 binding과 `RecoveryCoordinator.Execute`가 반환한 binding은 private `installAttempt(binding, resumeMode)` 하나를 통과시킨다.
  - `installAttempt`는 binding actual model/provider/path로 `AttemptTarget`을 만들고 고정 registry generation에서 active filters를 다시 resolve한다. replace는 pending/look-behind/cursor와 uncommitted attempt를 reset/rebind하고, continuation은 committed look-behind/cursor와 열린 downstream commit state를 보존한 채 current attempt identity만 전환한다.
  - owner `Run(ctx)`은 현재 `NormalizedEventSource`를 한 goroutine에서 읽는다. response-start는 `CommitBoundary.StageResponseStart`에만 저장하고, 나머지는 `EvidenceTail.Append` → immutable `EvidenceBatch` → `EvidencePlan.BindEpochFilters` → `GateCoordinator.Submit`으로 보낸다.
  - arbitration action은 한 switch에서 hold/release/terminal/recover/replacement 중 하나만 실행한다. release/terminal은 `StreamReleaser`, recover는 request-local `RecoveryCoordinator`만 호출한다. recovery 성공 뒤 새 binding을 설치하고 cycle을 재개하며, prepare/abort/rebuild/dispatch 실패·cancel·cap exhaustion은 raw-free failure cause를 가진 terminal 하나로 수렴한다.
  - owner 상태는 cycle/terminal guard를 두어 같은 epoch의 plan/dispatch/terminal 중복, stale event/attempt, terminal 이후 sink 호출을 거부한다. Gate coordinator는 모든 종료 경로에서 닫는다.
  - Core에 raw codec, proto, HTTP writer, concrete admission/auth import를 추가하지 않는다.
- Before (`packages/go/streamgate/runtime.go:115`):

```go
type RequestRuntimeSnapshot struct {
    // immutable options, registry snapshot, ingress reference and host seams only
}
```

- After:

```go
type RequestRuntime struct {
    snapshot RequestRuntimeSnapshot
    // request-local binding, filter/tail/gate/recovery/release and terminal state
}

func NewRequestRuntime(snapshot RequestRuntimeSnapshot, modelGroup string, initial AttemptBinding) (*RequestRuntime, error)
func (r *RequestRuntime) Run(ctx context.Context) error
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/runtime.go`: owner constructor/state와 initial binding validation을 추가한다.
  - [ ] `packages/go/streamgate/runtime.go`: shared attempt install, response staging, event/evidence/evaluation action loop를 추가한다.
  - [ ] `packages/go/streamgate/runtime.go`: replace/continuation rebinding, cancel/error/cap terminal convergence와 coordinator close를 추가한다.
- 테스트 작성: 작성. `runtime_test.go`의 pass/recovery/failure matrix가 공개 owner 진입점만 사용해 이 항목을 검증한다.
- 중간 검증: `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime(DisabledAndPass|RecoveryLifecycle|FailureMatrix)$'`가 PASS하고 dispatch/sink count assertion이 모두 만족해야 한다.

### [REVIEW_CORE_RUNTIME_LOOP-2] fake-host lifecycle fixtures

- 문제: `packages/go/streamgate/runtime_test.go`가 없어 baseline package PASS가 owner lifecycle을 한 줄도 실행하지 않는다.
- 해결 방법:
  - deterministic fake event source/controller/dispatcher/rebuilder/preparer/sink와 test-only injected filter를 만든다. fake는 call order와 count, actual binding, released events, commit states, terminal 결과를 thread-safe하게 기록한다.
  - disabled/no active filter와 enabled Noop pass에서 staged response-start가 첫 safe body보다 먼저 한 번만 commit되는지 검증한다.
  - one violation과 simultaneous violation에서 plan/abort/rebuild/dispatch가 cycle당 한 번이고 recovery binding의 model/provider/path가 active filter context에 반영되는지 검증한다.
  - preparer success는 abort 뒤 prepare 뒤 rebuild/dispatch 순서를, failure는 no dispatch/no budget/single terminal을 assertion한다.
  - replace는 pending/look-behind/cursor reset, continuation은 safe prefix/look-behind/cursor 보존과 opening/prefix 중복 억제를 검증한다.
  - blocking/observe-only failure, bounded backpressure, caller cancel, 0/1/3 cap exhaustion을 table-driven subtest로 묶고 terminal sink 호출이 정확히 한 번인지 검증한다.
- 수정 파일 및 체크리스트:
  - [ ] 새 `packages/go/streamgate/runtime_test.go`: fake host/filter/recorder를 추가한다.
  - [ ] `TestRequestRuntimeDisabledAndPass`: disabled와 Noop pass/staging fixture를 추가한다.
  - [ ] `TestRequestRuntimeRecoveryLifecycle`: violation/conflict/prepare/binding/replace/continue fixture를 추가한다.
  - [ ] `TestRequestRuntimeFailureMatrix`: enforcement/backpressure/cancel/exhaustion/single-terminal fixture를 추가한다.
- 테스트 작성: 작성. 정상, 경계, concurrency/ordering을 모두 포함하고 wall-clock sleep 대신 channel/barrier를 사용한다.
- 중간 검증: `go test -race -count=1 ./packages/go/streamgate -run '^TestRequestRuntime'`가 race 없이 PASS해야 한다.

### [REVIEW_CORE_RUNTIME_LOOP-3] 검증과 evidence 복구

- 문제: 이전 review의 구현 체크리스트, 설계 결정, 실제 출력이 비어 있어 기존 baseline PASS와 새 lifecycle PASS를 구분할 수 없다.
- 해결 방법: 아래 명령을 그대로 fresh 실행하고 stdout/stderr를 active review에 붙인다. 명령 변경 시 이유와 대체 명령을 `계획 대비 변경 사항`에 기록한다. repo-local 검증 도구/출력 파일을 만들지 않는다.
- 수정 파일 및 체크리스트:
  - [ ] production/test 파일을 `gofmt`하고 import boundary를 확인한다.
  - [ ] setup/unit/race/diff 명령을 fresh 실행한다.
  - [ ] active review 완료표·체크리스트·설계 결정·검증 실제 출력을 채운다.
- 테스트 작성: 별도 테스트 없음. 이 항목은 REVIEW_CORE_RUNTIME_LOOP-1/2의 실제 검증 trust를 닫는다.
- 중간 검증: `git diff --check` 출력이 없고 active review에 모든 명령 결과가 존재해야 한다.

## 의존 관계 및 구현 순서

`18` PASS → REVIEW_CORE_RUNTIME_LOOP-1 → REVIEW_CORE_RUNTIME_LOOP-2 → REVIEW_CORE_RUNTIME_LOOP-3. 후속 `20`은 이 subtask PASS 이후에만 Core owner를 Edge host에 연결한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/streamgate/runtime.go` | REVIEW_CORE_RUNTIME_LOOP-1 |
| 새 `packages/go/streamgate/runtime_test.go` | REVIEW_CORE_RUNTIME_LOOP-1/2 |
| `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G08.md` | REVIEW_CORE_RUNTIME_LOOP-3 evidence |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT` — host Go/GOROOT 일치.
- `make proto` — setup 성공, 생성 계약 drift 없음.
- `gofmt -d packages/go/streamgate/runtime.go packages/go/streamgate/runtime_test.go` — 출력 없음.
- `go test -count=1 ./packages/go/streamgate` — fresh package PASS.
- `go test -race -count=1 ./packages/go/streamgate` — race detector PASS.
- `rg -n --sort path 'iop/apps/' packages/go/streamgate/runtime.go packages/go/streamgate/runtime_test.go` — 출력 없음.
- `git diff --check` — 출력 없음.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.

