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

# Plan - REVIEW_VERIFY

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

이 작업은 이전 리뷰의 Required 하나만 닫는 후속 루프다. 직접 사용자에게 질문하거나 선택지를 제시하지 말고, selected Milestone의 `구현 잠금 > 결정 필요` 항목이 새로 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청`을 채운다. source sync, token, remote runtime, provider availability, smoke evidence 공백은 `USER_REVIEW.md` 대상이 아니며 `검증 결과`와 `계획 대비 변경 사항`에 실제 명령 출력과 blocker로 기록한다.

## 배경

이전 루프에서 local guard와 regression test 자체는 통과했다. 그러나 `agent-test/dev/edge-smoke.md`는 OpenAI-compatible tool-call 경계 변경 후 dev `18083` `/v1/chat/completions`에 Pi/Cline형 `tools[]` 요청을 non-stream/stream 각각 최소 1회 보내 raw text boundary smoke를 수행하도록 요구한다. 이전 리뷰 시점의 원격 checkout은 `59e55d4`로 guard 미포함이고 `IOP_OPENAI_TOKEN`은 `token_missing`이라 guarded source 기준 live smoke evidence가 없다.

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/plan_cloud_G07_0.log`
- Current archived review: `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/code_review_cloud_G07_0.log`
- Verdict: FAIL
- Required summary:
  - VERIFY-2 dev-runtime boundary smoke was not collected against source containing the native stream guard.
  - Remote preflight still showed HEAD `59e55d4`, clean status, and `token_missing`.
- 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: `ssh ... /Users/toki/agent-work/iop-dev` -> `59e55d4`, `token_missing`.
- Required verification still missing:
  - dev `18083` `/v1/chat/completions` Pi/Cline형 `tools[]` non-stream smoke on guarded source.
  - dev `18083` `/v1/chat/completions` Pi/Cline형 `tools[]` stream smoke on guarded source.
  - 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_0.log`
  - `agent-task/m-openai-compatible-tool-call-boundary-hardening/03+02_closure_verification/code_review_cloud_G07_0.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 변경이 아니라 remote guarded-source verification 수집이다.
- dev-runtime 전체 capacity smoke가 목적은 아니다. 단, Edge binary/runtime이 guarded source를 실제 반영했는지 증명해야 하므로 `config check`, refresh help/dry-run, process/port/health 확인은 포함한다.
- 원격 token 원문은 어떤 stdout/stderr, tracked file, roadmap, review output에도 남기지 않는다.
- 원격 checkout clean sync 기준을 우선한다. guard가 포함된 배포 ref가 아직 없고 patch sync만 가능한 경우에는 patch 적용 방식, `git diff --check`, guard symbol 확인, dirty 상태를 명확히 기록한다. source를 반영할 수 없으면 smoke를 실행하지 말고 blocker로 남긴다.

## 범위 결정 근거

- In scope: remote source가 guard를 포함한다는 증거, dev-runtime Edge rebuild/restart 또는 안전한 refresh, token presence preflight, dev `18083` non-stream/stream boundary smoke evidence 수집, raw/secret marker 부재 검증.
- Out of scope: 새 API field, provider prompt/template 재설계, capacity smoke 확장, git commit/push, roadmap 직접 갱신.

## 빌드 등급

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

## 구현 체크리스트

- [ ] remote runner source가 native stream guard와 regression test를 포함하는지 증명하고, 포함하지 않으면 guard가 포함된 ref 또는 좁은 patch sync로 반영한다. 반영할 수 없으면 blocker로 기록한다.
- [ ] guarded source 기준으로 dev-runtime Edge binary를 rebuild하고, `config check`, `config refresh --help`, `config refresh --mode dry-run`, port/health 상태가 새 runtime을 가리키는지 확인한다.
- [ ] `IOP_OPENAI_TOKEN` presence를 secret 원문 없이 확인한다. `token_missing`이면 live smoke를 실행하지 말고 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가 없는지 `rg --sort path`로 확인하고, `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_VERIFY-1] Dev-Runtime Boundary Smoke Evidence 수집

- 문제: 이전 리뷰에서 local guard/test는 통과했지만 dev `18083` live boundary smoke가 guarded source 기준으로 실행되지 않았다.
- 해결 방법:
  - remote runner `/Users/toki/agent-work/iop-dev`가 guard source를 포함하는지 먼저 증명한다.
  - source sync 후 Edge binary/runtime이 새 source를 반영하도록 rebuild/restart 또는 안전한 refresh를 수행한다.
  - token presence가 확인된 경우에만 non-stream/stream smoke를 실행한다.
  - evidence는 ignored run 위치에 저장하고 token 원문과 Authorization header를 저장하지 않는다.
- 수정 파일 및 체크리스트:
  - [ ] tracked source에는 secret을 쓰지 않는다.
  - [ ] 원격 source 반영 방식과 runtime identity를 `검증 결과`에 기록한다.
  - [ ] 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 && git rev-parse --short HEAD && git status --short && rg --sort path -n "FlushNonCandidate|TestChatCompletionsStreamDropsRawTextWhenNativeToolCallsArrive" apps/edge/internal/openai/stream.go apps/edge/internal/openai/server_test.go && 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.

  ```bash
  ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com '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 && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --mode dry-run --addr 127.0.0.1:19093 && curl -fsS http://127.0.0.1:18083/healthz >/tmp/iop-edge-dev-healthz.txt'
  ```
  기대: all commands pass. If the running Edge must be restarted to use the new binary, record restart command, pid/process evidence, and post-restart health/model checks.

  ```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"
  if rg --sort path -n '<tool_call|\{\{|<\|mask_end\|>|Authorization|Bearer' "$out"/known_nonstream.json "$out"/known_stream.sse "$out"/unknown_nonstream.json; then
    printf 'raw_or_secret_marker_found\n' >&2
    exit 1
  fi
  rg --sort path -n 'tool_calls|tool_validation_error' "$out"/known_nonstream.json "$out"/known_stream.sse "$out"/unknown_nonstream.json
  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_VERIFY-1 dev smoke가 모두 통과해야 PASS 후보가 된다. source sync 또는 token presence가 막히면 `사용자 리뷰 요청`을 채우지 말고 `검증 결과`에 blocker로 기록한다.
