<!-- task=m-iop-owned-single-request-agent-execution/04+03_stream_terminal plan=0 tag=API milestone-task=stream-terminal -->

# Single-request Anthropic SSE Projection

## For the Implementing Agent

Do not start until predecessor 03 has `complete.log`. Implement only this plan, run every verification command, fill every implementation-owned section of `CODE_REVIEW-cloud-G10.md` with actual notes and stdout/stderr, leave active files in place, and report ready for review. Finalization belongs only to the code-review skill. If blocked, record the exact blocker, attempts/output, and resume condition; do not ask the user, call user-input tools, create stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The existing Anthropic Hot Path codec can progressively expose normalized reasoning and tool blocks and is scoped to one caller turn. SDD S03 requires a stricter projector for the single-request coordinator: one outer envelope, closed redacted progress, liveness ping, no private stage/provider/tool wire, and one endpoint-native terminal after all internal stages.

## Analysis

### Files Read

- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`
- `apps/edge/internal/openai/anthropic_handler.go`
- `apps/edge/internal/openai/anthropic_stream.go`
- `apps/edge/internal/openai/hot_path_direct.go`
- `apps/edge/internal/openai/hot_path_anthropic_gate_test.go`
- `apps/edge/internal/openai/hot_path_terminal_control.go`
- `apps/edge/internal/openai/hot_path_terminal_control_test.go`
- `apps/edge/internal/openai/request_identity_handler_test.go`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-spec/runtime/stream-evidence-gate.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`

### SDD Criteria

- SDD approved; Milestone task `stream-terminal`; Acceptance Scenario S03.
- Evidence Map requires fragmented multi-stage SSE proof with redacted progress/ping, one envelope, collision-free blocks, and one terminal.
- The checklist and verification directly count wire events, check block indices/order, scan forbidden private fixtures, and race terminal/ping/cancel paths.

### Verification Context

- No separate handoff was supplied. Repository-native evidence is the current Anthropic codec, exact-wire Hot Path gate tests, terminal race tests, and outer contract.
- Baseline relevant tests passed at starting HEAD.
- Precondition: `agent-task/m-iop-owned-single-request-agent-execution/03+02_single_ingress/complete.log`.
- No external runner is required. In-process flushing recorder and manual tick channel provide deterministic progress/ping ordering; actual Claude liveness remains S12.
- Confidence: high for framing/privacy/exactly-once behavior. Provider-specific latency and real network proxy behavior are explicitly outside this packet.

### Test Coverage Gaps

- Existing codec tests cover ordinary progressive reasoning/text/tool blocks and error-after-commit, but that behavior is too permissive for fixed single-request output.
- No test covers endpoint-native `ping`, fixed redacted phase summaries, a stream held across several internal stages, or forbidden-value absence.
- Add a separate projector and exact-wire/race tests rather than weakening generic Hot Path behavior.

### Symbol References

- No symbol is renamed or removed.
- Reuse `writeDirectAnthropicEvent` from `hot_path_direct.go` for framing only.
- Do not route coordinator events through `anthropicHotPathCodec.writeProgressiveDelta` because that method accepts reasoning and tool-call fragments.

### Split Judgment

- The indivisible invariant is one serialized projector lock owning message start, block sequence, ping, and terminal. Splitting ping from terminal ownership would create post-terminal and concurrent-write races.
- Predecessor index 03 resolves to active sibling `agent-task/m-iop-owned-single-request-agent-execution/03+02_single_ingress/`; at planning time its `complete.log` is missing, so implementation is pending and unambiguous.
- This is the final packet in the Epic preparation chain; Node tool loop, error budget matrix, cleanup observation, and real Claude smoke remain separate Milestone tasks.

### Scope Rationale

- Do not change ordinary Anthropic native relay, Chat bridge, generic preset codec semantics, provider decoding, Stream Evidence Gate filters, Node wire, or tool execution.
- Do not emit provider reasoning, tool names/arguments/results, route/provider ids, credentials, raw command output, or internal stage terminals.
- Do not claim multiple review cycles or durable stream resume.

### Final Routing

- evaluation_mode `first-pass`; finalizer `finalize-task-policy.sh` pair.
- Build closures all true. Scores 2/2/2/1/2 = G09; route `grade-boundary`; lane `cloud`; filename `PLAN-cloud-G09.md`.
- Build signals: `large_indivisible_context=false`; risks `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, `variant_product` (5); risk matched but grade basis retained; rework 0; evidence integrity false; no capability gap.
- Review closures all true. Scores 2/2/2/2/2 = G10; `official-review` on cloud Codex `gpt-5.6-sol` xhigh; filename `CODE_REVIEW-cloud-G10.md`.

## Dependencies and Execution Order

1. Require predecessor 03 `complete.log` at its active path or matching same-group archive path.
2. Implement the isolated projector and exact-wire tests first.
3. Integrate the predecessor coordinator callback and tick lifecycle into the marked streaming handler.
4. Keep non-stream and every unmarked route on predecessor/existing codecs.

## Implementation Checklist

- [ ] Implement a serialized single-request Anthropic SSE projector with one envelope, fixed redacted progress, injectable liveness ping, final text/error, and exactly-once terminal ownership.
- [ ] Integrate the projector with the marked coordinator stream lifetime, stop ping before terminal/cancel, and prove one POST plus no private wire across fragmented multi-stage events.
- [ ] Preserve ordinary Anthropic/Hot Path behavior and synchronize the outer contract and matching current specs.
- [ ] Run targeted exact-wire, race, package regression, and `git diff --check` verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Implementation Plan

### [API-1] Add a privacy-closed Anthropic stream projector

**Problem**

- `apps/edge/internal/openai/anthropic_stream.go:339`-`365` stores generic progressive state including tool release.
- `apps/edge/internal/openai/anthropic_stream.go:463`-`501` explicitly accepts reasoning, text, and tool-call fragments.
- `apps/edge/internal/openai/anthropic_stream.go:870`-`943` owns one start/terminal but has no ping or closed single-request phase vocabulary.

**Solution**

Before (`apps/edge/internal/openai/anthropic_stream.go:482`):

    switch delta.Kind {
    case streamgate.EventKindReasoningDelta:
        ...
    case streamgate.EventKindTextDelta:
        ...
    case streamgate.EventKindToolCallFragment:
        ...
    }

After, in a new isolated projector:

    type singleRequestAnthropicStream struct {
        mu       sync.Mutex
        writer   http.ResponseWriter
        flusher  http.Flusher
        model    string
        messageID string
        started  bool
        terminal bool
        nextBlock int
    }

    func (s *singleRequestAnthropicStream) Progress(phase singleRequestPublicPhase) error
    func (s *singleRequestAnthropicStream) Ping() error
    func (s *singleRequestAnthropicStream) Final(text string, usage json.RawMessage) error
    func (s *singleRequestAnthropicStream) Error(kind, message string) error

- Start one `message_start` with the coordinator message id and requested public model before internal work.
- Map only the closed phases plan/work/review/finalizing to fixed redacted text owned by Edge. Never accept arbitrary provider text as progress.
- Emit Anthropic `event: ping` with `{"type":"ping"}` while nonterminal and flush it without opening/closing content blocks.
- Emit progress/final text as ordered content blocks with monotonically increasing indices. Final success closes any block, emits one `message_delta` with `end_turn`, then one `message_stop`.
- Before stream commit, a validation failure may use normal JSON error. After start, an error/cancel race emits at most one terminal `error` event and never also emits `message_stop`.
- Guard every write and terminal check with one mutex. Post-terminal progress/ping/final calls return the established terminal error and write nothing.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/openai/single_request_anthropic_stream.go` — implement closed framing, fixed phase mapping, ping, final/error, and terminal lock.
- [ ] `apps/edge/internal/openai/single_request_anthropic_stream_test.go` — add deterministic recorder, parser, wire-order, privacy, and terminal tests.

**Test Strategy**

- Write `TestSingleRequestAnthropicStreamOneEnvelopeOneTerminal` and assert exact counts for `message_start`, `message_delta`, `message_stop` and strictly increasing block indices.
- Write `TestSingleRequestAnthropicStreamPingAndProgressOrdering` using explicit `Ping()` calls/manual ticks.
- Write `TestSingleRequestAnthropicStreamRedactsPrivateEvents` with sentinel reasoning, provider id, credential ref, tool name/args/result, raw command output, and internal terminal; assert none occur on wire.
- Write `TestSingleRequestAnthropicStreamErrorTerminalRace` under `-race` and assert one of error or success terminal, never both.

**Verification**

- `go test -race ./apps/edge/internal/openai -run 'TestSingleRequestAnthropicStream' -count=1`
- Expected: exact-wire and race tests pass with no data race or duplicate terminal.

### [API-2] Pump coordinator progress and liveness on the same request

**Problem**

- The predecessor marked handler buffers the coordinator result and uses the generic final codec.
- Long provider/tool stages need liveness without allowing a ping goroutine to race final/cancel writes.

**Solution**

- In `handleAnthropicSingleRequest`, keep non-stream behavior unchanged. For `stream=true`, create/start the new projector and run the coordinator with its closed public phase callback.
- Add a helper whose ticker channel/factory is injected for tests and defaults to a conservative endpoint liveness interval in production. All tick writes go through the projector lock.
- Stop and drain/close the ticker before committing final/error. Wait for the ping worker to exit before returning from the handler so no write occurs after terminal or after `ResponseWriter` lifetime.
- On caller disconnect, cancel the coordinator, stop liveness, and do not invent a wire success. On coordinator error after start, emit one sanitized endpoint-native error.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/openai/anthropic_handler.go` — select the new projector only for marked streaming requests and own ticker shutdown.
- [ ] `apps/edge/internal/openai/single_request_anthropic_stream.go` — add the deterministic coordinator/ticker pump.
- [ ] `apps/edge/internal/openai/single_request_anthropic_stream_test.go` — add fragmented multi-stage handler integration and disconnect/terminal ordering.

**Test Strategy**

- Write `TestAnthropicSingleRequestStreamingUsesOnePost` with an actual HTTP counting wrapper, delayed stage events, manual ticks, and final success.
- Assert one POST, one message envelope, at least one ping during the delay, fixed progress only, no internal sentinels, and one final terminal.
- Write `TestAnthropicSingleRequestStreamStopsPingBeforeTerminal` and `TestAnthropicSingleRequestStreamDisconnectStopsWriter`.

**Verification**

- `go test ./apps/edge/internal/openai -run 'Test(AnthropicSingleRequestStreaming|AnthropicSingleRequestStream)' -count=1`
- Expected: same connection remains live, post-terminal bytes do not change, and actual POST count is one.

### [API-3] Synchronize SSE and compatibility contracts

**Problem**

- `agent-contract/outer/anthropic-compatible-api.md` documents ordinary Anthropic SSE and generic Hot Path behavior but not the fixed single-request event subset.
- The input and Stream Evidence Gate specs do not identify this projector's privacy/terminal boundary.

**Solution**

- Document the exact allowed event/order set, public model identity, fixed progress semantics, endpoint-native ping, success/error terminal exclusivity, and forbidden private values.
- State that this projector consumes already classified coordinator events and does not make raw internal provider/tool events public or alter ordinary Hot Path/Stream Evidence Gate behavior.
- Link source/test evidence in the current specs without claiming real-provider or Claude smoke.

**Modified Files and Checklist**

- [ ] `agent-contract/outer/anthropic-compatible-api.md` — add fixed single-request SSE contract and terminal/error rules.
- [ ] `agent-spec/input/openai-compatible-surface.md` — synchronize endpoint behavior and tests.
- [ ] `agent-spec/runtime/stream-evidence-gate.md` — record the separate coordinator projection boundary and non-expansion of generic filters.

**Test Strategy**

- No standalone prose test. The exact-wire parser and forbidden-sentinel assertions are the normative executable evidence.

**Verification**

- `rg --sort path -n 'single-request|event: ping|message_start|message_stop|private|tool_use' agent-contract/outer/anthropic-compatible-api.md agent-spec/input/openai-compatible-surface.md agent-spec/runtime/stream-evidence-gate.md`
- Expected: allowed and forbidden behavior is explicit and consistent with tests.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/openai/single_request_anthropic_stream.go` | API-1, API-2 |
| `apps/edge/internal/openai/single_request_anthropic_stream_test.go` | API-1, API-2 |
| `apps/edge/internal/openai/anthropic_handler.go` | API-2 |
| `agent-contract/outer/anthropic-compatible-api.md` | API-3 |
| `agent-spec/input/openai-compatible-surface.md` | API-3 |
| `agent-spec/runtime/stream-evidence-gate.md` | API-3 |
| `agent-task/m-iop-owned-single-request-agent-execution/04+03_stream_terminal/CODE_REVIEW-cloud-G10.md` | API-1, API-2, API-3 |

## Final Verification

1. `test -f agent-task/m-iop-owned-single-request-agent-execution/03+02_single_ingress/complete.log || find agent-task/archive -type f -path '*/m-iop-owned-single-request-agent-execution/03+02_single_ingress/complete.log' -print | sort | grep -q .`
2. `go test -race ./apps/edge/internal/openai -run 'TestSingleRequestAnthropicStream' -count=1`
3. `go test ./apps/edge/internal/openai -run 'Test(AnthropicSingleRequestStreaming|AnthropicSingleRequestStream|HotPathAnthropic)' -count=1`
4. `rg --sort path -n 'single-request|event: ping|message_start|message_stop|private|tool_use' agent-contract/outer/anthropic-compatible-api.md agent-spec/input/openai-compatible-surface.md agent-spec/runtime/stream-evidence-gate.md`
5. `go test -race ./apps/edge/internal/openai -count=1`
6. `go test ./apps/edge/... ./packages/go/streamgate/... -count=1`
7. `git diff --check`

Expected: dependency exists; one-envelope/one-terminal ordering and privacy tests pass; no race or post-terminal ping occurs; ordinary Anthropic/Hot Path regressions pass. All Go results are fresh via `-count=1`. Actual Claude/provider smoke remains outside this packet.

**After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.**
