<!-- task=m-stream-evidence-gate-core/13+11,12_recovery_plan_contract plan=2 tag=REVIEW_MANAGED_ALLOWANCE_BINDING -->

# Plan - Post-rebuild allowance와 dispatch request binding

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

이 plan의 구현·테스트·evidence만 작성한다. 모든 검증을 실행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 채운 뒤 active pair를 유지하고 리뷰 준비 완료를 보고한다. 막히면 정확한 blocker, 시도한 명령/출력, 재개 조건만 구현 소유 evidence에 기록한다. 사용자에게 질문하거나 user-input 도구·control-plane stop 파일을 사용하거나 다음 상태를 분류하지 않으며, log archive·`complete.log`·최종 판정은 code-review 전용이다.

## 배경

recovery contract는 sibling snapshot 타입 없이 격리 컴파일되고 실제 rebuilt prompt 측정 뒤 managed allowance를 계산한다. 그러나 확정 allowance는 최종 `RecoveryPlan`에만 남고 `AttemptDispatcher`에는 이미 조립된 `RebuiltRequest`만 전달되어, 실제 outbound request가 같은 allowance를 적용했는지 Core 계약이 보장하지 못한다. 같은 task path에서 measured draft와 dispatchable request를 분리해 SDD S24의 post-rebuild 순서를 닫는다.

## Archive Evidence Snapshot

- `agent-task/m-stream-evidence-gate-core/13+11,12_recovery_plan_contract/plan_local_G08_1.log`: recovery-owned snapshot seam과 managed provisional/final plan을 도입한 직전 계획.
- `agent-task/m-stream-evidence-gate-core/13+11,12_recovery_plan_contract/code_review_cloud_G08_1.log`: FAIL. Required 1, Suggested 0, Nit 0.
  - `WithManagedRebuiltRequest`가 allowance를 확정한 뒤에도 dispatcher에는 최종 plan/allowance와 결합되지 않은 기존 `RebuiltRequest`만 전달되어 실제 outbound cap 적용을 보장하지 못한다.
- 첫 리뷰의 sibling `IngressSnapshot`/`ErrIngressSnapshotLimitExceeded` 의존성 Required는 recovery-owned `RecoveryRequestSnapshotRef`/`ErrRecoverySnapshotLimitExceeded`와 격리 checkout package PASS로 해소됐다.
- 영향 파일: `packages/go/streamgate/recovery_plan.go`, `packages/go/streamgate/recovery_plan_test.go`.
- 실제 검증: focused/package/race/gofmt/symbol/diff와 HEAD+contract-only 격리 `GOWORK=off go test -count=1 ./packages/go/streamgate`는 PASS했다. `rg`에서는 `managedAllowance`가 `RecoveryPlan` 내부에만 있고 `AttemptDispatcher.DispatchAttempt`가 `RebuiltRequest` 하나만 받는 것으로 확인됐다.
- 선행 완료: `agent-task/archive/2026/07/m-stream-evidence-gate-core/11+09,10_policy_hook_contract/complete.log`, `agent-task/archive/2026/07/m-stream-evidence-gate-core/12+11_config_policy/complete.log`.

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/recovery_plan.go` — recovery budget, plan lifecycle, rebuilt request와 host seam 전체.
- `packages/go/streamgate/recovery_plan_test.go` — managed finalization, preparation, host seam fixture 전체.
- `agent-task/m-stream-evidence-gate-core/13+11,12_recovery_plan_contract/plan_local_G08_1.log` — 직전 구현 계획과 검증 계약.
- `agent-task/m-stream-evidence-gate-core/13+11,12_recovery_plan_contract/code_review_cloud_G08_1.log` — 현재 FAIL finding과 재현 근거.
- `agent-task/m-stream-evidence-gate-core/13+11,12_recovery_plan_contract/plan_cloud_G09_0.log`, `code_review_cloud_G09_0.log` — 최초 contract 범위와 이전 Required 원문.
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md` — Phase 경계, Milestone 범위와 잠금.
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md` — 승인된 recovery lifecycle와 Evidence Map.
- `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md` — local Go identity와 platform-common 검증 기준.
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/11+09,10_policy_hook_contract/complete.log`, `agent-task/archive/2026/07/m-stream-evidence-gate-core/12+11_config_policy/complete.log` — split 선행 11/12 완료 근거.

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`, 상태 `[승인됨]`, SDD 잠금 `해제`.
- S23 / `recovery-coordinator`: preparation 성공 뒤에만 rebuild/dispatch 가능한 plan을 만든다. Evidence Map은 success-only rebuild와 request-local lifecycle을 요구한다.
- S24 / `recovery-coordinator`: Rebuilder 조립 뒤 실제 `rebuilt_prompt_tokens`로 allowance를 계산하고 같은 downstream continuation dispatch에 적용한다. Evidence Map은 attempt/caller/context allowance, fault ledger 분리와 단일 final terminal을 요구한다.
- 이 follow-up은 S23/S24의 중간 public contract만 닫고 Milestone `recovery-coordinator` Task 전체 완료를 주장하지 않으므로 `Roadmap Targets`는 두지 않는다. 구현 체크리스트와 검증은 measured draft → allowance finalization → dispatchable request 순서와 exhaustion no-dispatch 증거에서 역산했다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다.
- 호스트 `PATH`의 Go/GOROOT identity를 먼저 기록하고 fresh `go test -count=1`, package race, gofmt, deterministic symbol search와 `git diff --check`를 적용한다.
- proto/config/wire는 수정하지 않으므로 profile의 `make proto`, Edge/Node transport와 full-cycle은 적용하지 않는다. task-specific fallback은 `packages/go/streamgate` focused/package/race suite다.
- 외부 runner, provider, secret, port, artifact가 필요하지 않아 비-local 프리플라이트는 적용하지 않는다. 테스트 룰 유지보수도 필요 없다.

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

- 기존 `TestManagedTrajectoryPostRebuildFinalization`은 최종 plan의 allowance와 readiness만 확인하고 dispatcher가 같은 allowance가 결합된 request를 받는지 확인하지 않는다.
- 기존 fake dispatcher는 `RebuiltRequest` 인자를 무시하므로 provisional/draft request가 그대로 dispatch되는 회귀를 탐지하지 못한다.
- preparation-required plan, identity mismatch, caller/context exhaustion은 일부 검증하지만 measured draft와 dispatchable request의 타입·상태 경계 및 no-dispatch까지 연결되지 않았다.

### 심볼 참조

- 제거/대체 대상 `WithManagedRebuiltRequest`: production 정의는 `recovery_plan.go:915`, test 호출은 `recovery_plan_test.go:383,426,443,466,485`뿐이다.
- 계약 변경 대상 `RebuiltRequest`, `RequestRebuilder`, `AttemptDispatcher`: package 밖 production caller는 없고 `recovery_plan.go`와 `recovery_plan_test.go` fixture에만 있다.
- `IngressSnapshot`/`ErrIngressSnapshotLimitExceeded` 직접 참조는 두 recovery contract 파일에 없다.

### 분할 판단

- split 정책을 재평가했으며 기존 `m-stream-evidence-gate-core/13+11,12_recovery_plan_contract`를 유지한다. index 11은 `11+09,10_policy_hook_contract/complete.log`, index 12는 `12+11_config_policy/complete.log`로 충족됐다.
- 공개 draft/final request seam과 그 회귀는 같은 타입 전이 하나이며 동일 두 파일과 같은 focused tests를 공유한다. API와 call-site rollout 경계, 다른 도메인, 외부 검증, 독립 완료 가능한 slice가 없어 추가 split은 인위적인 중간 불완전 계약을 만든다.

### 범위 결정 근거

- 수정 포함: `recovery_plan.go`, `recovery_plan_test.go`의 rebuilt draft/final request, managed allowance binding, dispatcher fixture.
- 수정 제외: `ingress_snapshot.go`, config/proto, 실제 Edge/OpenAI rebuilder, 아직 미구현 RecoveryCoordinator, roadmap/SDD/spec/contract 문서. 이 follow-up은 transport-agnostic public seam만 보정하고 host rollout이나 Milestone Task 완료를 주장하지 않는다.
- second rebuild, host-local duplicate allowance 계산, mutable request registry side channel은 허용하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`; finalizer: `finalize-task-policy.sh`; finalizer_mode: `pair`.
- build closures: scope/context/verification/evidence/ownership/decision 모두 true. 두 파일의 네 public seam과 S23/S24 순서, fresh package/race 및 격리 compile, no production caller, Milestone 결정 필요 없음으로 닫혔다.
- build route_basis: `local-fit`; capability_gap: none; scores=`scope_coupling:2,state_concurrency:1,blast_irreversibility:1,evidence_diagnosis:2,verification_complexity:1`; lane=`local`; grade=`G07`; filename=`PLAN-local-G07.md`.
- build loop-risk: ordered transitions 4개(provisional/prepared/measured/dispatchable), adverse path는 unprepared/stale identity/exhaustion/no-dispatch다. concurrency actor는 1개이며 공유-state 제약은 없다. boundary component 4개(Plan/Rebuilder/Draft/Dispatcher), consumer 2개, identity·immutability·allowance binding 제약이 있다. structured interpretation은 없고, preparation×allowance limiter×identity variant 조합 검증이 필요하다. matched=`temporal_state,boundary_contract,variant_product`; triggered=true; G floor=none.
- review closures: 모두 true. passing tests와 누락된 dispatch binding을 SDD/public seam에서 독립 재검증할 수 있다.
- review route_basis: `official-review`; capability_gap: none; scores=`2/1/1/2/1`; lane=`cloud`; grade=`G07`; filename=`CODE_REVIEW-cloud-G07.md`; official target=`Codex gpt-5.6-sol xhigh`; G floor=none.

## 구현 체크리스트

- [ ] REVIEW_MANAGED_ALLOWANCE_BINDING-1 measured rebuilt draft와 allowance-bound dispatchable request를 분리한다.
- [ ] REVIEW_MANAGED_ALLOWANCE_BINDING-2 preparation/identity/allowance/exhaustion/dispatcher 회귀를 추가한다.
- [ ] REVIEW_MANAGED_ALLOWANCE_BINDING-3 focused/package/race/gofmt/symbol/diff 검증을 수행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_MANAGED_ALLOWANCE_BINDING-1] Two-phase rebuilt request contract

- 문제: `recovery_plan.go:915-959`는 실제 prompt 측정 뒤 plan allowance를 확정하지만 `recovery_plan.go:1262-1264`의 dispatcher는 `recovery_plan.go:1284-1285`가 이미 반환한 동일 `RebuiltRequest`만 받는다. final allowance가 outbound request에 bind되지 않는다.
- 해결 방법: Rebuilder 반환을 raw-free `RebuiltRequestDraft`로 분리하고, plan/idempotency identity와 retained/peak/max, prompt 측정값, required capability를 보존한다. `RecoveryPlan.FinalizeRebuiltRequest(draft)`가 preparation readiness와 identity를 검증하고 managed allowance를 한 번 계산해 immutable final `RecoveryPlan`과 allowance-bound `RebuiltRequest`를 함께 반환한다. fault plan도 같은 finalizer를 거쳐 dispatchable request를 만들되 managed allowance는 0이다. `AttemptDispatcher`는 final `RebuiltRequest`만 받아 draft를 타입상 dispatch할 수 없게 한다. second rebuild나 host-side 공식 중복 계산은 두지 않는다.

Before (`recovery_plan.go:915-959`, `recovery_plan.go:1262-1285`):

```go
rebuilt, _ := rebuilder.RebuildRequest(ctx, snapshot, provisional)
finalPlan, _ := provisional.WithManagedRebuiltRequest(rebuilt)
binding, _ := dispatcher.DispatchAttempt(ctx, rebuilt)
```

After:

```go
draft, _ := rebuilder.RebuildRequest(ctx, snapshot, provisional)
finalPlan, request, _ := provisional.FinalizeRebuiltRequest(draft)
binding, _ := dispatcher.DispatchAttempt(ctx, request)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/recovery_plan.go`: draft/final value types, one-shot finalizer, managed allowance accessor/validation, Rebuilder/Dispatcher signatures.
  - [ ] `packages/go/streamgate/recovery_plan_test.go`: 기존 constructors, fake rebuilder/dispatcher와 모든 finalization call site 갱신.
- 테스트 작성: 작성. 기존 `TestManagedTrajectoryPostRebuildFinalization`을 dispatcher-observed allowance까지 확장하고 `TestRebuiltRequestDraftLifecycleAndIdentity`를 추가한다.
- 중간 검증: `go test -count=1 ./packages/go/streamgate -run 'Test(ManagedTrajectoryPostRebuildFinalization|RebuiltRequestDraftLifecycleAndIdentity|RecoveryHostSeamsAndRawFreeReferences)'`가 PASS한다.

### [REVIEW_MANAGED_ALLOWANCE_BINDING-2] Dispatch binding regression

- 문제: `recovery_plan_test.go:341-491`은 plan readiness만 보고 `recovery_plan_test.go:600-603`의 dispatcher는 request를 관찰하지 않아 outbound cap drift를 놓친다.
- 해결 방법: recording dispatcher가 final request의 plan/idempotency identity와 `ManagedAllowance()`를 캡처하도록 한다. attempt/caller/context 최소값, preparer-required 순서, plan/idempotency mismatch, original draft/plan immutability, zero caller/context exhaustion에서 final request 미생성·dispatcher 미호출, managed dispatch의 fault ledger 비소비를 표로 검증한다. fault request는 allowance 0이지만 정상 dispatchable임을 함께 고정한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/recovery_plan_test.go`: allowance variant table, draft/final lifecycle, recording dispatcher/no-dispatch assertions.
  - [ ] `packages/go/streamgate/recovery_plan.go`: test가 관찰할 raw-free final request accessors와 validation.
- 테스트 작성: 작성. `TestManagedTrajectoryPostRebuildFinalization`, `TestRebuiltRequestDraftLifecycleAndIdentity`, `TestRecoveryHostSeamsAndRawFreeReferences`가 회귀 fixture다.
- 중간 검증: `go test -count=1 ./packages/go/streamgate -run 'Test(ManagedTrajectoryPostRebuildFinalization|RebuiltRequestDraftLifecycleAndIdentity|RecoveryPlanDefensiveCopiesAndPreparationContract|RecoveryHostSeamsAndRawFreeReferences)'`가 PASS한다.

### [REVIEW_MANAGED_ALLOWANCE_BINDING-3] Verification closure

- 문제: 현재 package/race PASS는 최종 plan 숫자만 검증해 실제 dispatch binding에 대한 verification trust를 닫지 못한다.
- 해결 방법: host Go identity, gofmt, focused lifecycle tests, fresh package/race, stale finalizer와 sibling concrete symbol search, diff check를 실행하고 실제 stdout/stderr를 review stub에 기록한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/recovery_plan.go`, `packages/go/streamgate/recovery_plan_test.go`: gofmt와 stale symbol 없음.
  - [ ] `CODE_REVIEW-*-G??.md`: 모든 명령의 실제 출력을 기록한다.
- 테스트 작성: 별도 테스트 파일은 만들지 않는다. CONTRACT-1/2의 회귀를 기존 contract test 파일에 둔다.
- 중간 검증: 아래 최종 검증 전체가 PASS한다.

## 의존 관계 및 구현 순서

선행 index 11/12 complete 유지 → BINDING-1 draft/final API → BINDING-2 dispatcher fixture → BINDING-3. 디렉터리명에 없는 추가 runtime 의존성은 없다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/streamgate/recovery_plan.go` | REVIEW_MANAGED_ALLOWANCE_BINDING-1/2/3 |
| `packages/go/streamgate/recovery_plan_test.go` | REVIEW_MANAGED_ALLOWANCE_BINDING-1/2/3 |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT` — 호스트 Go identity가 local rule과 일치한다.
- `gofmt -l packages/go/streamgate/recovery_plan.go packages/go/streamgate/recovery_plan_test.go` — 출력 없음.
- `go test -count=1 ./packages/go/streamgate -run 'Test(ManagedTrajectoryPostRebuildFinalization|RebuiltRequestDraftLifecycleAndIdentity|RecoveryPlanDefensiveCopiesAndPreparationContract|RecoveryRequestSnapshotRefBounds|RecoveryHostSeamsAndRawFreeReferences)'` — PASS.
- `go test -count=1 ./packages/go/streamgate` — fresh package PASS; test cache를 사용하지 않는다.
- `go test -race -count=1 ./packages/go/streamgate` — fresh race PASS.
- `if rg --sort path -n '\bWithManagedRebuiltRequest\b' packages/go/streamgate/recovery_plan.go packages/go/streamgate/recovery_plan_test.go; then exit 1; fi` — 출력 없이 exit 0.
- `if rg --sort path -n '\b(IngressSnapshot|ErrIngressSnapshotLimitExceeded)\b' packages/go/streamgate/recovery_plan.go packages/go/streamgate/recovery_plan_test.go; then exit 1; fi` — 출력 없이 exit 0.
- `git diff --check` — 출력 없음.

proto/config/wire, repo Edge-Node 진단, 보조 E2E와 외부 provider는 적용하지 않는다. 이 follow-up은 아직 production caller가 없는 transport-agnostic contract와 unit fixture만 변경한다.

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