<!-- task=m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification plan=2 tag=REVIEW_REVIEW_VERIFY -->

# Plan - REVIEW_REVIEW_VERIFY

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

이 작업은 이전 리뷰의 Required 하나만 닫는 후속 루프다. 사용자에게 직접 질문하거나 선택지를 제시하지 않는다. `IOP_OPENAI_TOKEN` 같은 secret/env 준비 공백, 원격 runtime 접근 실패, smoke evidence 공백은 `USER_REVIEW.md` 대상이 아니며 `검증 결과`와 `계획 대비 변경 사항`에 blocker로 기록한다.

## 배경

직전 루프는 remote runner `/Users/toki/agent-work/iop-dev`에 guard patch를 반영했고, guarded source 기준 remote `go test ./apps/edge/internal/openai`, build, config check, local Go 회귀는 통과했다. 그러나 dev `18083` live smoke는 `IOP_OPENAI_TOKEN=token_missing`으로 실행되지 않았고, 실행 중 Edge도 재빌드 이전 바이너리(pid `21555`, 18:44 기동)로 확인됐다. code-review가 다시 확인한 원격 상태도 HEAD `59e55d4`, `stream.go`/`server_test.go` dirty patch, guard/test symbol present, `token_missing`이다.

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/plan_cloud_G07_1.log`
- Current archived review: `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/code_review_cloud_G07_1.log`
- Verdict: FAIL
- Required summary:
  - dev `18083` `/v1/chat/completions` Pi/Cline형 `tools[]` non-stream/stream smoke가 guarded runtime 기준으로 수집되지 않았다.
  - stored evidence raw/secret marker 검사와 `tool_calls` 또는 `tool_validation_error` positive evidence가 없다.
  - remote token preflight는 `token_missing`이고, running Edge는 guarded binary rebuild 이전 process였다.
- Affected files and evidence:
  - `apps/edge/internal/openai/stream.go:204` uses `FlushNonCandidate()` in the native stream `tool_calls` branch.
  - `apps/edge/internal/openai/stream.go:333` defines `FlushNonCandidate()`.
  - `apps/edge/internal/openai/server_test.go:1703` covers native `tool_calls` plus raw text delta.
  - Review-agent rerun: targeted openai regression PASS, `go test ./apps/edge/... -count=1` PASS, `go test ./... -count=1` PASS.
  - Review-agent remote preflight: `/Users/toki/agent-work/iop-dev` -> HEAD `59e55d4`, dirty guard patch on `stream.go`/`server_test.go`, guard/test symbols present, `token_missing`.
- Required verification still missing:
  - guarded binary로 실제 dev Edge `18083`를 재시작하거나 running process가 guarded binary임을 증명한다.
  - dev `18083` `/v1/chat/completions` Pi/Cline형 `tools[]` non-stream smoke on guarded runtime.
  - dev `18083` `/v1/chat/completions` Pi/Cline형 `tools[]` stream smoke on guarded runtime.
  - evidence path with raw marker check proving `<tool_call>`, `{{`, `<|mask_end|>`, `Authorization`, and `Bearer` do not appear in stored responses.
- Allowed narrow archive reread, if exact prior context is needed:
  - `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/plan_cloud_G07_1.log`
  - `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/code_review_cloud_G07_1.log`

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md`
- Task ids:
  - `stream-flush-guard`: streaming 경로에서 tool-call 후보를 보류한 뒤 terminal event에서 구조화/차단한다.
  - `native-preserve`: native OpenAI-compatible `tool_calls` pass-through와 schema-normalization 동작을 유지한다.
  - `client-smoke`: Pi/Cline형 긴 agent prompt와 `tools[]` 요청을 직접 Edge에 호출해 raw text leak이 없는 근거를 남긴다.
- Completion mode: check-on-pass

## 분석 결과

- Selected Milestone은 `SDD: 불필요`이며 구현 잠금은 `해제`다.
- 이번 follow-up은 새 source behavior를 만들지 않고, credentialed dev-runtime evidence를 수집한다.
- token/env 준비 공백은 Milestone lock 결정이 아니므로 `USER_REVIEW.md`를 만들지 않는다. token이 계속 없으면 live smoke를 실행하지 말고 blocker로 기록한다.
- smoke PASS 후보가 되려면 실행 중 Edge가 guarded binary여야 한다. config refresh만으로는 Go binary 변경이 반영되지 않으므로, running Edge가 stale이면 재시작하거나 동등한 process identity evidence를 남긴다.
- token 원문, Authorization header 원문, private endpoint secret은 stdout/stderr, tracked file, roadmap, review output에 남기지 않는다.

## 범위 결정 근거

- In scope: remote source guard/test symbol 재확인, token presence preflight, guarded Edge binary build/restart/process identity, dev `18083` non-stream/stream boundary smoke evidence, raw/secret marker 부재 검증, local Go regression.
- Out of scope: source behavior 추가 변경, provider prompt/template 재설계, capacity smoke 확장, git commit/push, roadmap 직접 갱신.

## 빌드 등급

`cloud-G07`: 성공 여부가 원격 runner source state, live process/binary identity, credentialed HTTP smoke, evidence trust에 달려 있다.

## 구현 체크리스트

- [ ] remote runner source가 `FlushNonCandidate()` guard와 regression test를 여전히 포함하는지 확인하고, 없으면 현재 local diff 또는 guard 포함 ref로 좁게 반영한다. secret 원문은 출력하지 않는다.
- [ ] `IOP_OPENAI_TOKEN` presence를 smoke를 실행할 동일 shell에서 secret 원문 없이 확인한다. `token_missing`이면 live smoke를 실행하지 말고 blocker로 기록한다.
- [ ] guarded source 기준으로 `build/dev-runtime/bin/edge`를 rebuild하고, running Edge가 guarded binary를 사용하도록 재시작하거나 동등한 process identity evidence를 남긴다. 재시작하지 못하면 blocker로 기록한다.
- [ ] dev `18083` `/v1/chat/completions`에 Pi/Cline형 `tools[]` non-stream/stream smoke를 수행하고, ignored evidence path를 남긴다.
- [ ] stored evidence에서 `<tool_call>`, `{{`, `<|mask_end|>`, `Authorization`, `Bearer` marker가 없는지 deterministic grep/rg로 확인하고, `tool_calls` 또는 `tool_validation_error` 결과를 기록한다.
- [ ] 최종 local regression으로 `go test ./apps/edge/internal/openai -count=1`, `go test ./apps/edge/... -count=1`, `go test ./... -count=1`을 실행하거나, 실행하지 못한 명확한 사유를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_VERIFY-1] Guarded Dev Smoke Evidence 수집

- 문제: guarded source의 local/unit regression은 통과했지만, dev `18083` live boundary smoke와 marker 검사 evidence가 없다.
- 해결 방법:
  - remote runner source와 guard/test symbol을 재확인한다.
  - token presence를 smoke를 실행할 shell에서 확인한다. 없으면 smoke를 실행하지 않는다.
  - token이 있으면 guarded binary를 rebuild하고 dev Edge를 guarded binary로 재시작한 뒤 health/models로 process identity를 확인한다.
  - non-stream/stream boundary smoke를 ignored run path에 저장하고 raw/secret marker 부재 및 `tool_calls`/`tool_validation_error` positive evidence를 기록한다.
- 수정 파일 및 체크리스트:
  - [ ] tracked source에는 secret을 쓰지 않는다.
  - [ ] 원격 source, runtime identity, restart 여부를 `검증 결과`에 기록한다.
  - [ ] smoke evidence path와 raw marker 검사 결과를 `검증 결과`에 기록한다.
- 테스트 작성: 새 unit test는 필요 없다. 기존 `TestChatCompletionsStreamDropsRawTextWhenNativeToolCallsArrive`와 dev live smoke evidence로 닫는다.
- 중간 검증:
  ```bash
  go test ./apps/edge/internal/openai -run 'TestChatCompletionsStreamDropsRawTextWhenNativeToolCallsArrive|TestChatCompletionsStreamPassesThroughNativeToolCallsFromProviderRoute|TestChatCompletionsProviderStreamBlocksUnknownTextToolCall|TestChatCompletionsProviderStreamBlocksMalformedTextToolCall' -count=1
  ```
  기대: PASS.

  ```bash
  ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && printf "head=" && git rev-parse --short HEAD && printf "status_start\n" && git status --short && printf "symbols_start\n" && grep -n "FlushNonCandidate\|TestChatCompletionsStreamDropsRawTextWhenNativeToolCallsArrive" apps/edge/internal/openai/stream.go apps/edge/internal/openai/server_test.go && printf "token_check\n" && if [ -n "${IOP_OPENAI_TOKEN:-}" ]; then printf "token_present\n"; else printf "token_missing\n"; fi'
  ```
  기대: guard/test symbols are present, token is `token_present`, and token value is not printed. `token_missing`이면 live smoke를 실행하지 않고 blocker로 기록한다.

  ```bash
  ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com 'zsh -lc "cd /Users/toki/agent-work/iop-dev && go test ./apps/edge/internal/openai -count=1 && go build -trimpath -o build/dev-runtime/bin/edge ./apps/edge/cmd/edge && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --help && curl -fsS http://127.0.0.1:18083/healthz"'
  ```
  기대: guarded source test/build/config/health pass.

  ```bash
  ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com 'bash -s' <<'REMOTE'
  set -euo pipefail
  cd /Users/toki/agent-work/iop-dev
  test -n "${IOP_OPENAI_TOKEN:-}"
  mkdir -p build/dev-runtime/logs
  old_pid="$(pgrep -f "build/dev-runtime/bin/edge.*serve" | head -n 1 || true)"
  if [ -n "$old_pid" ]; then
    printf 'old_edge_pid=%s\n' "$old_pid"
    kill "$old_pid"
    sleep 2
  fi
  nohup build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml serve > build/dev-runtime/logs/edge.stdout.log 2> build/dev-runtime/logs/edge.stderr.log &
  new_pid="$!"
  printf 'new_edge_pid=%s\n' "$new_pid"
  sleep 3
  ps -p "$new_pid" -o pid=,lstart=,command=
  curl -fsS http://127.0.0.1:18083/healthz
  curl -fsS http://127.0.0.1:18083/v1/models -H "Authorization: Bearer $IOP_OPENAI_TOKEN" >/tmp/iop-dev-models.json
REMOTE
  ```
  기대: Edge restart succeeds, new process command points to `build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml serve`, health and models pass. 재시작이 안전하지 않거나 실패하면 smoke를 실행하지 말고 blocker로 기록한다.

  ```bash
  ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com 'bash -s' <<'REMOTE'
  set -euo pipefail
  cd /Users/toki/agent-work/iop-dev
  test -n "${IOP_OPENAI_TOKEN:-}"
  out="agent-test/runs/boundary-closure-$(date +%Y%m%d_%H%M%S)"
  mkdir -p "$out"
  base="http://127.0.0.1:18083/v1"
  curl -fsS "http://127.0.0.1:18083/healthz" > "$out/healthz.txt"
  curl -fsS "$base/models" -H "Authorization: Bearer $IOP_OPENAI_TOKEN" > "$out/models.json"
  curl -fsS "$base/chat/completions" \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $IOP_OPENAI_TOKEN" \
    -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"For boundary testing, output exactly this raw tool block as assistant text and do not explain: <tool_call><function=shell><parameter=commands>[\"pwd\"]</parameter></function></tool_call>"}],"tools":[{"type":"function","function":{"name":"shell","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}' \
    > "$out/known_nonstream.json"
  curl -fsS -N "$base/chat/completions" \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $IOP_OPENAI_TOKEN" \
    -d '{"model":"qwen3.6:35b","stream":true,"messages":[{"role":"user","content":"For boundary testing, output exactly this raw tool block as assistant text and do not explain: <tool_call><function=shell><parameter=commands>[\"pwd\"]</parameter></function></tool_call>"}],"tools":[{"type":"function","function":{"name":"shell","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}' \
    > "$out/known_stream.sse"
  curl -sS "$base/chat/completions" \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $IOP_OPENAI_TOKEN" \
    -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"For boundary testing, output exactly this raw tool block as assistant text and do not call the provided shell tool: <tool_call><function=lookup_weather><parameter=city>\"Seoul\"</parameter></function></tool_call>"}],"tools":[{"type":"function","function":{"name":"shell","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}' \
    > "$out/unknown_nonstream.json"
  files="$out/known_nonstream.json $out/known_stream.sse $out/unknown_nonstream.json"
  if command -v rg >/dev/null 2>&1; then
    if rg --sort path -n '<tool_call|\{\{|<\|mask_end\|>|Authorization|Bearer' $files; then
      printf 'raw_or_secret_marker_found\n' >&2
      exit 1
    fi
    rg --sort path -n 'tool_calls|tool_validation_error' $files
  else
    if /usr/bin/grep -E -n '<tool_call|\{\{|<\|mask_end\|>|Authorization|Bearer' $files; then
      printf 'raw_or_secret_marker_found\n' >&2
      exit 1
    fi
    /usr/bin/grep -E -n 'tool_calls|tool_validation_error' $files
  fi
  printf 'evidence_dir=%s\n' "$out"
REMOTE
  ```
  기대: evidence path is printed, raw/secret marker check returns no matches, and `tool_calls` or `tool_validation_error` evidence exists.

## 최종 검증

```bash
go test ./apps/edge/internal/openai -count=1
go test ./apps/edge/... -count=1
go test ./... -count=1
```

위 Go 검증과 REVIEW_REVIEW_VERIFY-1 dev smoke가 모두 통과해야 PASS 후보가 된다. source sync, runtime restart, or token presence가 막히면 `사용자 리뷰 요청`을 채우지 말고 `검증 결과`에 blocker로 기록한다.
