<!-- task=m-stream-evidence-gate-core/17+13,16_openai_request_rebuilder plan=4 tag=REVIEW_OPENAI_REBUILDER_LIFECYCLE -->

# Plan - OpenAI rebuilder terminal lifecycle repair

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

구현·테스트·evidence만 수행하고 모든 검증의 실제 출력으로 CODE_REVIEW-*-G??.md 구현 에이전트 소유 섹션을 채운다. active pair를 그대로 두고 ready for review만 보고한다. 막히면 blocker, 시도 명령/출력, 재개 조건만 구현 evidence에 기록한다. 사용자 질문, user-input 도구, stop 파일, 다음 상태 분류, log archive, complete.log 작성은 하지 않는다. 최종화는 code-review skill 전용이다.

## 배경

Close는 시작된 patched rebuild의 reservation/lease release보다 먼저 반환할 수 있고 nil RebuildRequest는 mutex 역참조로 panic한다. 이전 구현 evidence와 실제 source/test도 다르며 race 명령은 현재 checkout에서 no tests to run으로 끝났다. rebuilder 내부 lifecycle과 deterministic regression만 보완하며 외부 OpenAI 계약, admission/auth/path switch는 바꾸지 않는다.

## Archive Evidence Snapshot

- Prior task: agent-task/m-stream-evidence-gate-core/17+13,16_openai_request_rebuilder
- Archived plan: agent-task/m-stream-evidence-gate-core/17+13,16_openai_request_rebuilder/plan_local_G06_3.log
- Archived review: agent-task/m-stream-evidence-gate-core/17+13,16_openai_request_rebuilder/code_review_cloud_G06_3.log
- Verdict: FAIL
- Findings: Required 3, Suggested 0, Nit 0.
- Required summary: in-flight patched release barrier, nil receiver semantics, deterministic regression/evidence가 누락됐다.
- Affected files: apps/edge/internal/openai/openai_request_rebuilder.go, apps/edge/internal/openai/openai_request_rebuilder_test.go.
- Verification evidence: prior targeted race command은 no tests to run, broad package/race/OpenAI smoke는 PASS.
- Roadmap carryover: request-rebuilder는 미완료이며 Milestone/SDD 잠금은 승인·해제 상태다.
- 구현에 prior context가 꼭 필요할 때만 위 두 archived log를 좁게 다시 읽는다.

## 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:
  - request-rebuilder: endpoint별 bounded lossless rebuild와 host re-admission
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- apps/edge/internal/openai/openai_request_rebuilder.go
- apps/edge/internal/openai/openai_request_rebuilder_test.go
- apps/edge/internal/openai/stream_gate_ingress.go
- apps/edge/internal/openai/stream_gate_dispatcher.go
- apps/edge/internal/openai/stream_gate_dispatcher_test.go
- apps/edge/internal/openai/stream_gate_ingress_test.go
- agent-ops/rules/project/domain/edge/rules.md
- agent-ops/rules/project/domain/testing/rules.md
- agent-test/local/rules.md
- agent-test/local/edge-smoke.md
- agent-test/local/testing-smoke.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/outer/openai-compatible-api.md
- agent-spec/input/openai-compatible-surface.md
- agent-task/archive/2026/07/m-stream-evidence-gate-core/13+11,12_recovery_plan_contract/complete.log
- agent-task/archive/2026/07/m-stream-evidence-gate-core/16+15_ingress_snapshot_allocation/complete.log

### SDD 기준

- SDD: agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md, 상태 승인됨, SDD 잠금 해제.
- S15/request-rebuilder는 Chat·Responses rebuilt request의 bounded owned backing/lossless lifecycle을, S22는 종료 경로의 retained object release를 요구한다.
- checklist와 final verification은 Close 반환 전 taken patch/rebuilt lease release, Chat continuation·Responses schema ordering, nil receiver를 직접 단정한다. Evidence Map S15/S22의 request-rebuilder evidence로 사용한다.

### 테스트 환경 규칙

- test_env=local. agent-test/local/rules.md, edge-smoke.md, testing-smoke.md를 읽었다.
- host Go identity를 실제 검증 출력으로 기록하고 fresh Go test에는 count=1을 사용한다.
- 변경은 OpenAI private lifecycle에만 닿고 newOpenAIRequestRebuilder production call site는 아직 없다. deterministic unit/race가 lifecycle 주증거이며 make test-openai-ollama는 Edge/Node OpenAI route 보조 증거다.
- full external provider preflight와 manual console 2-message cycle은 아직 production cycle에 연결되지 않은 rebuilder lifecycle을 검증하지 못하므로 이번 narrow regression PASS 조건이 아니다. 실행 여부와 이유는 evidence에 명시한다.

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

- 현재 CloseRace는 exact rebuild 하나를 Close와 동시에 시작한 뒤 WaitGroup 이후만 검사하므로 taken continuation/schema patch의 Close 반환 순서를 보지 못한다.
- TestOpenAIRequestRebuilderNilReceiver와 planned patched ordering tests는 존재하지 않아 target regex가 zero-test PASS를 허용한다.
- 새 table-driven barrier test와 nil receiver test가 공백을 닫는다.

### 심볼 참조

- rename/remove 없음. openAIRequestRebuilder는 rebuilder/dispatcher test에서만 직접 생성되며 public OpenAI handler call site는 없다.

### 분할 판단

- lifecycle registration, Close barrier, nil guard, deterministic regression은 같은 private state machine과 두 파일의 단일 invariant다.
- production/test 분리, contract/call-site rollout, domain 분리는 독립 완료가 불가능하거나 인위적이므로 single plan이 가장 좁다.
- 현재 17+13,16_openai_request_rebuilder는 archived complete.log인 13+11,12_recovery_plan_contract와 16+15_ingress_snapshot_allocation PASS로 선행 조건을 충족한다.

### 범위 결정 근거

- apps/edge/internal/openai/openai_request_rebuilder.go와 대응 test만 수정한다.
- packages/go/streamgate, dispatcher, ingress admission, OpenAI contract/spec/config/roadmap은 lifecycle barrier의 소유자가 아니므로 변경하지 않는다.
- sibling streamgate/runtime 및 agent-task runtime 변경은 현재 finding과 무관하므로 수정하지 않는다.

### 최종 라우팅

- evaluation_mode=isolated-reassessment, finalizer=finalize-task-policy.sh, finalizer_mode=pair.
- Build closures: scope_closed=true, context_closed=true, verification_closed=true, evidence_trusted=true, ownership_closed=true, decision_closed=true.
- Build route: local-fit; scores scope_coupling=1, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1; local/G06, PLAN-local-G06.md; capability gap=none.
- Review closures: 모두 true. Review route: official-review; scores scope_coupling=1, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=2, verification_complexity=1; cloud/G07, CODE_REVIEW-cloud-G07.md, Codex gpt-5.6-sol xhigh; capability gap=none.
- Build loop-risk: open/in-flight/closed와 close-before/close-during/duplicate-close, Close/Rebuild two actor, rebuilder/patch store/rebuilt store/ingress boundary, directive family와 close ordering 두 축을 확인했다. matched signatures=temporal_state, concurrent_consistency, boundary_contract, variant_product; triggered=true이며 route/grade에는 영향을 주지 않는다.

## 구현 체크리스트

- [x] REVIEW_OPENAI_REBUILDER_LIFECYCLE-1 nil receiver를 closed error로 처리하고, Close가 registration된 모든 rebuild의 patch/lease release와 store drain 뒤에만 반환하도록 lifecycle barrier를 구현한다.
- [x] REVIEW_OPENAI_REBUILDER_LIFECYCLE-2 Chat continuation과 Responses schema patched rebuild의 Close-wait ordering, duplicate Close, nil receiver를 deterministic하게 검증하고 actual test-name matching fresh/race/package/OpenAI smoke evidence를 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_OPENAI_REBUILDER_LIFECYCLE-1] terminal Close lifecycle barrier

- 문제: apps/edge/internal/openai/openai_request_rebuilder.go:362-425는 closed flag만 설정하고 store close를 즉시 수행한다. gate를 지난 continuation/schema rebuild는 patchEntry release 전에 Close가 반환할 수 있고 nil receiver는 r.mu에서 panic한다.
- 해결 방법: constructor에서 rebuilder mutex에 연결한 sync.Cond를 만든다. RebuildRequest는 nil guard 뒤 closed check와 in-flight registration을 같은 mutex 구간에서 수행하고 registration 뒤 모든 exit에서 patch entry/temporary lease final release 뒤 deregister한다. Close는 새 registration을 막고 in-flight 0까지 기다린 뒤 store를 닫으며 concurrent Close도 terminal drain 완료를 기다린다.

Before (apps/edge/internal/openai/openai_request_rebuilder.go:329):

~~~go
type openAIRequestRebuilder struct {
    mu       sync.Mutex
    ingress  *openAIIngressSnapshot
    endpoint string
    patches  *openAIRecoveryPatchStore
    rebuilt  *openAIRebuiltRequestStore
    closed   bool
}
~~~

After:

~~~go
type openAIRequestRebuilder struct {
    mu        sync.Mutex
    cond      *sync.Cond
    inFlight  int
    closed    bool
    closeDone bool
    ingress   *openAIIngressSnapshot
    endpoint  string
    patches   *openAIRecoveryPatchStore
    rebuilt   *openAIRebuiltRequestStore
}
~~~

- 수정 파일 및 체크리스트:
  - [x] apps/edge/internal/openai/openai_request_rebuilder.go: constructor에서 cond를 초기화하고 begin/finish lifecycle invariant를 추가한다.
  - [x] apps/edge/internal/openai/openai_request_rebuilder.go: RebuildRequest nil guard를 mutex 전에 두고 every post-registration exit가 patch/lease release 뒤 deregister되게 한다.
  - [x] apps/edge/internal/openai/openai_request_rebuilder.go: Close와 duplicate Close가 Close 반환 전에 all in-flight completion 및 store drain을 보장하게 한다.
- 테스트 작성: 작성. deterministic barrier가 taken patch와 Close return을 명시적으로 순서화한다.
- 중간 검증: go test -count=1 ./apps/edge/internal/openai -run '^TestOpenAIRequestRebuilder(CloseWaitsForInFlightPatchedRebuild|NilReceiver)$' -v — 두 test PASS.

~~~text
=== RUN   TestOpenAIRequestRebuilderNilReceiver
--- PASS: TestOpenAIRequestRebuilderNilReceiver (0.00s)
=== RUN   TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild
=== RUN   TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild/continuation
=== RUN   TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild/schema
=== RUN   TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild/duplicateClose
--- PASS: TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild (0.01s)
    --- PASS: TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild/continuation (0.00s)
    --- PASS: TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild/schema (0.00s)
    --- PASS: TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild/duplicateClose (0.00s)
PASS
ok  	iop/apps/edge/internal/openai	0.164s
~~~

### [REVIEW_OPENAI_REBUILDER_LIFECYCLE-2] deterministic patched lifecycle regressions

- 문제: apps/edge/internal/openai/openai_request_rebuilder_test.go:415-480의 exact race는 Close 반환 순간을 관찰하지 않고 buildErr nil/closed 모두를 허용한다. planned patched/nil tests가 없어 claimed race command은 no tests to run이다.
- 해결 방법: second context Err check에서 continuation/schema patch take 뒤 rebuild를 정지시키는 test-only context barrier를 둔다. Chat continuation과 Responses schema subtest가 Close와 duplicate Close의 non-return을 release 전 확인하고 barrier 해제 뒤 Close 반환, store closed/empty, ReservedTempBytes 0, post-close error를 단정한다. nil receiver RebuildRequest는 panic 없이 ErrIngressSnapshotClosed를 반환해야 한다.
- 수정 파일 및 체크리스트:
  - [x] apps/edge/internal/openai/openai_request_rebuilder_test.go: TestOpenAIRequestRebuilderCloseWaitsForInFlightPatchedRebuild에 continuation/schema table, Close return barrier, duplicate Close, reservation/lease zero assertion을 추가한다.
  - [x] apps/edge/internal/openai/openai_request_rebuilder_test.go: TestOpenAIRequestRebuilderNilReceiver를 추가하고 obsolete exact-only CloseRace를 lifecycle assertion으로 대체하거나 제거한다.
  - [x] apps/edge/internal/openai/openai_request_rebuilder_test.go: verification regex가 실제 test names와 정확히 일치하게 한다.
- 테스트 작성: 작성. scheduler 경쟁이 아닌 explicit context barrier로 Close 반환 순서를 검증한다.
- 중간 검증: go test -race -count=20 ./apps/edge/internal/openai -run '^TestOpenAIRequestRebuilder(CloseWaitsForInFlightPatchedRebuild|NilReceiver)$' -v — test count가 0이 아니고 PASS.

~~~text
(20회 반복, 모두 PASS, race 조건 없음)
PASS
ok  	iop/apps/edge/internal/openai	1.114s
~~~

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| apps/edge/internal/openai/openai_request_rebuilder.go | REVIEW_OPENAI_REBUILDER_LIFECYCLE-1 |
| apps/edge/internal/openai/openai_request_rebuilder_test.go | REVIEW_OPENAI_REBUILDER_LIFECYCLE-1, REVIEW_OPENAI_REBUILDER_LIFECYCLE-2 |

## 최종 검증

1. command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT — host Go 원문 기록.
2. gofmt -l apps/edge/internal/openai/openai_request_rebuilder.go apps/edge/internal/openai/openai_request_rebuilder_test.go — 출력 없음.
3. go test -count=1 ./apps/edge/internal/openai -run '^TestOpenAIRequestRebuilder(CloseWaitsForInFlightPatchedRebuild|NilReceiver)$' -v — named regression PASS.
4. go test -race -count=20 ./apps/edge/internal/openai -run '^TestOpenAIRequestRebuilder(CloseWaitsForInFlightPatchedRebuild|NilReceiver)$' -v — named regression 20회 PASS, zero-test output 불허.
5. go test -count=1 ./apps/edge/internal/openai ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/service — 모두 PASS.
6. go test -race -count=1 ./apps/edge/internal/openai ./packages/go/streamgate — 모두 PASS.
7. make test-openai-ollama — Edge/Node OpenAI smoke PASS.
8. git diff --check — 출력 없음.

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