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

# Plan - OpenAI rebuilder deterministic Close-waiter barrier repair

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

구현·테스트·evidence만 수행하고 CODE_REVIEW-*-G??.md의 구현 소유 섹션을 실제 출력으로 채운다. active pair는 그대로 두고 ready for review만 보고한다. blocker는 구현 evidence에만 남긴다. 사용자 질문, 다음 상태 분류, archive, complete.log, WORK_LOG.md 수정은 하지 않는다. 최종화는 code-review skill 전용이다.

## 배경

직전 회귀 테스트는 two-Close 순서를 검증하려 했지만 Close goroutine 등록을 time sleep으로 추정했다. Chat continuation과 Responses schema에서 실제 waiter 등록을 condition으로 관찰한 뒤에만 rebuild release와 terminal drain을 단정한다. 인접 OpenAI smoke의 transport 시작 실패는 private rebuilder lifecycle의 성공 증거로 사용하지 않고, 적용 범위와 runner 상태를 명확히 남긴다.

## 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_6.log`
- Archived review: `agent-task/m-stream-evidence-gate-core/17+13,16_openai_request_rebuilder/code_review_cloud_G06_6.log`
- Verdict: FAIL; Required 2, Suggested 0, Nit 0.
- Required summary: Close waiter registration is scheduler-dependent; the planned Ollama smoke fails before Edge-Node transport opens.
- Affected files: `apps/edge/internal/openai/openai_request_rebuilder_test.go`; smoke runner evidence only.
- Verification evidence: fresh/race/package tests and `make proto` passed; `make test-openai-ollama` failed with Edge-Node transport startup failure.
- Roadmap carryover: `request-rebuilder` remains incomplete; Milestone and approved SDD locks are released.

## 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_dispatcher.go`
- `apps/edge/internal/openai/stream_gate_ingress.go`
- `Makefile`
- `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/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-contract/outer/openai-compatible-api.md`
- `agent-spec/index.md`
- `agent-spec/input/openai-compatible-surface.md`

### SDD 기준

- SDD는 `[승인됨]`, SDD 잠금은 `해제`이며 USER_REVIEW.md는 없다.
- `request-rebuilder`의 S15는 Chat/Responses bounded lossless exact/continuation/schema rebuild와 host admission evidence를, S22는 retained object release와 no-dispatch/no-raw-retention boundary를 요구한다.
- 이번 checklist는 S15/S22의 Chat/Responses release 근거 중 현재 빠진 two-Close actual waiter ordering만 닫는다. focused fresh/race와 reservation 0 assertion이 해당 evidence를 결정적으로 만든다.

### 테스트 환경 규칙

- test_env=local. `agent-test/local/rules.md`, edge-smoke와 testing-smoke profile을 읽었다.
- host Go identity, `make proto`, fresh focused/package test, race test를 적용한다. fresh execution은 `-count=1`과 checkout 외부 `mktemp -d` GOCACHE를 사용하며 cache output은 허용하지 않는다.
- `newOpenAIRequestRebuilder`는 production handler가 아닌 test fixture와 dispatcher test fixture에서만 생성된다. `make test-openai-ollama`는 local runner가 Edge-Node transport를 열 수 있을 때만 인접 OpenAI smoke로 기록하며, private lifecycle PASS를 대체하지 않는다.
- 현재 smoke는 `[openai-ollama] edge node transport did not open`에서 종료했다. 외부 endpoint/secret blocker가 아니라 local startup evidence이므로 scripts/config를 이번 test-only 범위에서 수정하지 않는다.

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

- `blockingRebuildContext.entered`는 patch take 뒤 rebuild block을 고정하지만, current two-Close fixture는 lines 573/663의 sleep 후 waiter 수를 읽는다.
- 새 condition helper는 `closeWaiters == 2` registration을 먼저 관찰하고, 두 endpoint에서 release 전 non-return과 release 뒤 rebuild/Close/store drain/reservation 0을 계속 단정한다.
- nil receiver와 one-shot release regression은 기존 fixture로 유지한다.

### 심볼 참조

- rename/remove 없음.
- `newOpenAIRequestRebuilder`는 생성 정의와 `openai_request_rebuilder_test.go` fixture, `stream_gate_dispatcher_test.go` fixture에서만 확인됐다. production HTTP handler call site는 없다.
- 새 test helper는 같은 test file 내부에서만 사용한다.

### 분할 판단

- split 정책을 평가했다. condition helper와 Chat/Responses ordering assertion은 같은 shared mutex invariant이며, production change 없이 test와 evidence를 분리하면 어느 child도 독립 PASS가 되지 않는다.
- smoke runner repair는 current Required의 소유 범위를 넘어 scripts/config/runtime startup을 바꿀 수 있으므로 분리하지 않고 blocker evidence로만 기록한다. API/call-site/domain boundary와 predecessor dependency가 없어 기존 single subtask를 유지한다.

### 범위 결정 근거

- `apps/edge/internal/openai/openai_request_rebuilder_test.go`만 수정한다.
- `openai_request_rebuilder.go` lifecycle semantics, dispatcher/ingress, OpenAI public contract/spec, Makefile/scripts/config, streamgate와 roadmap은 변경하지 않는다.
- smoke transport 자체를 고치려면 별도 startup/Edge-Node task가 필요하다.

### 최종 라우팅

- evaluation_mode=isolated-reassessment, finalizer=finalize-task-policy.sh, finalizer_mode=pair.
- Build closures: scope_closed=true (test-only waiter barrier), context_closed=true (rebuilder/test/SDD/contract/readback), verification_closed=true (fresh/race/package and explicit smoke evidence), evidence_trusted=true (current test output plus exact sleep locations), ownership_closed=true (Edge private test lifecycle), decision_closed=true (Milestone lock released).
- Build: route_basis=local-fit, capability_gap=none, scores scope_coupling=1, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1; lane=local, grade=G06, filename=PLAN-local-G06.md.
- Review: route_basis=official-review, scores scope_coupling=1, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1; lane=cloud, grade=G06, filename=CODE_REVIEW-cloud-G06.md, target=Codex gpt-5.6-sol xhigh.
- Build loop-risk: ordered states=4 with delayed/early-return/wake-up paths=3; rebuild plus two Close actors=3 under exact waiter and drain constraints; rebuilder/patch/rebuilt components=3 and Chat/Responses consumers=2; structured mechanism=none; endpoint and close-order axes=2. matched signatures are temporal_state, concurrent_consistency, boundary_contract, variant_product; triggered=true; floor=none.

## 구현 체크리스트

- [ ] REVIEW_OPENAI_REBUILDER_WAIT_BARRIER-1 condition 기반 two-Close waiter barrier를 추가해 Chat continuation과 Responses schema가 scheduler sleep 없이 actual Close entry를 관찰하게 한다.
- [ ] REVIEW_OPENAI_REBUILDER_WAIT_BARRIER-2 fresh/race/package evidence와 production handler 부재·Ollama smoke startup 결과를 분리해 retained-object release 검증 신뢰도를 닫는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_OPENAI_REBUILDER_WAIT_BARRIER-1] deterministic Close waiter registration

- 문제: `apps/edge/internal/openai/openai_request_rebuilder_test.go:573`, `:663`은 `time.Sleep` 뒤에 waiter count를 읽어 close caller의 실제 entry를 보장하지 못한다.
- 해결 방법: test-local helper가 `rebuilder.mu`와 `rebuilder.cond` 아래에서 `closeWaiters == 2`가 될 때까지 대기한다. helper 반환 뒤에만 두 done channel이 아직 닫히지 않았음을 확인하고 rebuild release 뒤 두 Close/store drain/reservation 0을 기다린다.
- Before (`apps/edge/internal/openai/openai_request_rebuilder_test.go:572`):

~~~go
time.Sleep(10 * time.Millisecond)
rebuilder.mu.Lock()
waiters := rebuilder.closeWaiters
rebuilder.mu.Unlock()
if waiters != 2 {
	t.Fatalf("close waiters = %d, want 2", waiters)
}
~~~

- After:

~~~go
waitForOpenAIRebuilderCloseWaiters(t, rebuilder, 2)
~~~

- 수정 파일 및 체크리스트:
  - [ ] `apps/edge/internal/openai/openai_request_rebuilder_test.go`: condition helper를 추가하고 continuation/schema fixture의 sleep/direct read를 교체한다.
  - [ ] `apps/edge/internal/openai/openai_request_rebuilder_test.go`: 불필요해진 `sync/atomic`와 `time` import 및 finished bool을 제거하고 done channel으로 completion을 단정한다.
- 테스트 작성: 수정. `TestOpenAIRequestRebuilderConcurrentCloseWaitsForStoreDrain`의 continuation/schema가 actual waiter barrier, release 전 non-return, release 후 drain을 검증한다.
- 중간 검증: `go test -count=1 -timeout=30s` focused command가 두 endpoint subtest를 실행하고 PASS.

### [REVIEW_OPENAI_REBUILDER_WAIT_BARRIER-2] verification scope and startup evidence

- 문제: current focused/race output은 scheduler-timing defect를 구분하지 못했고, plan의 Ollama smoke PASS expectation은 local transport startup failure와 충돌한다.
- 해결 방법: deterministic fixture를 fresh/race/package 명령으로 재검증하고, stable symbol search로 rebuilder의 production handler 부재를 확인한다. Ollama smoke는 실행 결과와 startup blocker만 evidence에 기록하며 private lifecycle PASS 근거로 승격하지 않는다.
- 수정 파일 및 체크리스트:
  - [ ] `apps/edge/internal/openai/openai_request_rebuilder_test.go`: deterministic barrier에 맞춰 focused/race regression을 통과시킨다.
  - [ ] source change 없음: `Makefile`, scripts, configs와 production OpenAI handler는 smoke blocker만으로 변경하지 않는다.
- 테스트 작성: 별도 test 없음. item 1의 endpoint regression이 required behavior를 직접 검증한다.
- 중간 검증: deterministic `rg --sort path` call-site search와 `make test-openai-ollama` actual output을 review evidence에 남긴다.

## 수정 파일 요약

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

## 최종 검증

1. `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT` — host identity 확인.
2. `make proto` — protobuf generation PASS.
3. `gofmt -l apps/edge/internal/openai/openai_request_rebuilder_test.go` — 출력 없음.
4. `iop_rebuilder_gocache="$(mktemp -d -t iop-s0-openai-waiter.XXXXXX)"; GOCACHE="$iop_rebuilder_gocache" go test -count=1 -timeout=30s ./apps/edge/internal/openai -run '^TestOpenAIRequestRebuilder(ConcurrentCloseWaitsForStoreDrain|CloseWaitsForInFlightPatchedRebuild|NilReceiver)$' -v; GOCACHE="$iop_rebuilder_gocache" go test -race -count=20 -timeout=30s ./apps/edge/internal/openai -run '^TestOpenAIRequestRebuilder(ConcurrentCloseWaitsForStoreDrain|CloseWaitsForInFlightPatchedRebuild|NilReceiver)$' -v` — focused Chat/Responses regression PASS, race 없음.
5. `iop_rebuilder_gocache="$(mktemp -d -t iop-s0-openai-waiter-package.XXXXXX)"; GOCACHE="$iop_rebuilder_gocache" go test -count=1 ./apps/edge/internal/openai ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/service; GOCACHE="$iop_rebuilder_gocache" go test -race -count=1 ./apps/edge/internal/openai ./packages/go/streamgate` — package/race PASS.
6. `rg --sort path -n --glob '*.go' 'newOpenAIRequestRebuilder\\(' apps/edge/internal/openai` — production handler call site 없음.
7. `make test-openai-ollama` — PASS면 인접 smoke evidence로 기록한다. transport start 전 실패하면 exact stdout/stderr와 private lifecycle 범위 밖 blocker를 기록하며 성공 evidence로 주장하지 않는다.
8. `git diff --check` — 출력 없음.

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