<!-- task=provider_stream_buffer_compat plan=1 tag=STREAM_BUFFER -->

# Provider stream single-event buffer compatibility hardening

## For the Implementing Agent

Filling the implementation-owned sections in `CODE_REVIEW-*-G??.md` is mandatory. Run every verification command, paste actual notes and stdout/stderr into the active review file, keep the active PLAN/CODE_REVIEW files in place, and report ready for review; only the code-review skill may finalize or archive the task. If blocked, record only the exact blocker, attempted commands/output, and resume conditions in implementation-owned evidence fields. Do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The current branch's configured repeat policy inherits Core's 4,096-rune default hard buffer for both its rolling text guard and terminal action sibling. A provider tunnel SSE frame can decode into one valid content or reasoning event larger than that default, causing Core to emit `buffer_overflow` before repeat evaluation and Edge to return a 502 `provider_tunnel_error`. This patch changes only the repeat policy's infrastructure hold envelope to Core's existing 1,048,576-rune ceiling; it does not change provider output-filter decisions or provider wire bytes.

## Archive Evidence Snapshot

- The user requested a second plan review before implementation. The prior unimplemented draft pair is preserved as `agent-task/provider_stream_buffer_compat/plan_cloud_G05_0.log` and `agent-task/provider_stream_buffer_compat/code_review_cloud_G05_0.log`.
- The prior review stub has no verdict, implementation evidence, Required/Suggested/Nit findings, or verification result. This replan retains the diagnosed two-requirement bound fix and production-path regression, corrects the unit from “MiB” to runes, removes a stale prior-task reference, and removes non-diagnostic proto/general smoke commands.
- No roadmap state or completion claim carries over.

## Analysis

### Files Read

- Workflow and rules: `AGENTS.md`, `agent-ops/rules/project/rules.md`, `agent-ops/rules/private/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/rules/common/rules-agent-spec.md`, `agent-ops/rules/project/domain/edge/rules.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`, and `agent-ops/skills/common/plan/templates/review-stub-template.md`.
- Verification profiles: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, and `agent-test/local/testing-smoke.md`.
- Contracts and current specs: `agent-contract/index.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-node-runtime-wire.md`, `agent-spec/index.md`, `agent-spec/runtime/stream-evidence-gate.md`, and `agent-spec/input/openai-compatible-surface.md`.
- Edge source: `apps/edge/internal/openai/stream_gate_filters.go`, `apps/edge/internal/openai/stream_gate_policy.go`, `apps/edge/internal/openai/stream_gate_tunnel_codec.go`, `apps/edge/internal/openai/stream_gate_release_sink.go`, and `apps/edge/internal/openai/stream_gate_runtime.go`.
- Core source: `packages/go/streamgate/evidence_tail.go`, `packages/go/streamgate/runtime.go`, and `packages/go/streamgate/filter_registry.go`.
- Tests: `apps/edge/internal/openai/stream_gate_filters_test.go`, `apps/edge/internal/openai/stream_gate_policy_test.go`, `apps/edge/internal/openai/stream_gate_pipeline_test.go`, and `packages/go/streamgate/stream_release_test.go`.
- Prior task-local draft: `agent-task/provider_stream_buffer_compat/plan_cloud_G05_0.log` and `agent-task/provider_stream_buffer_compat/code_review_cloud_G05_0.log`.

### SDD Criteria

Not applicable. This is a non-roadmap compatibility patch and must not check or modify any Milestone Task or SDD state.

### Verification Context

- No `verification_context` handoff was supplied. Repository-native evidence is the current OpenAI contract/spec, Core hold implementation, production Edge tunnel codec/runtime/release sink, existing exact-wire tests, and local test rules.
- Local preflight on 2026-07-30: branch `feature/openai-compatible-output-validation-filters`, HEAD `213eee4e28dfa69ff1e412faf23978ee9a9a3b9f`; Go resolves from `/config/.local/bin/go` to `/config/opt/go/bin/go`, version `go1.26.2 linux/arm64`, `GOROOT=/config/opt/go`, `GOMOD=/config/workspace/iop/go.mod`.
- The worktree has user-owned deletions under `agent-task/m-openai-compatible-output-validation-filters/02+01_repeat_guard/` and this task's plan artifacts. Preserve those deletions; no planned source file has a diff. No dispatcher process is running.
- Existing focused harness proof passed uncached: `go test -count=1 ./apps/edge/internal/openai -run 'Test(RepeatGuardIdleDoesNotRelease|OpenAIOutputFilterRegistrations|OpenAITunnelCodecTerminalWire|StreamGateConfiguredRepeatActionSplitLifecycle)$'`.
- No external runner, provider, shared runtime, dev deployment, credential, network endpoint, protobuf generation, or config migration is required. The decisive oracle is an in-process production registry + tunnel event source + Core runtime + tunnel release sink driven by deterministic provider frames.
- General mock/provider smoke commands do not enable `stream_evidence_gate` with blocking `repeat_guard` and cannot distinguish the old 4,096-rune failure from the fix. They are not acceptance evidence for this patch; the production-path regression below is.
- Fresh `-count=1` and `-race -count=1` results are required; cached Go test output is not acceptable. Confidence is high because the regression crosses the exact production codec/Core/sink boundary.

### Test Coverage Gaps

- Core tests cover explicit `max_buffer_runes`, Unicode rune counting, overflow, discard, and terminal behavior, but no Edge test locks the bound produced by the repeat text guard plus its automatically registered action sibling.
- Existing tunnel tests cover Chat/Responses event shapes, terminal framing, split tool identity, and byte-identical release only with small semantic deltas.
- No current test drives one content or reasoning event larger than 4,096 runes through the configured repeat policy and complete tunnel runtime. `STREAM_BUFFER-1` closes the policy-composition gap; `STREAM_BUFFER-2` closes the observed transport regression gap.

### Symbol References

None. No symbol is renamed or removed, and no public API or dependency is introduced.

### Split Judgment

Keep one compact plan. The indivisible invariant is that a repeat-enabled provider tunnel accepts one valid coalesced semantic event within Core's existing ceiling and releases the original SSE bytes exactly once; the hold configuration and runtime regression cannot independently prove that invariant.

### Scope Rationale

- Change only the repeat policy's rolling text/reasoning hold and terminal action sibling hold. Both need the same bound because Core composes blocking requirements on one channel using the minimum positive `max_buffer_runes`.
- Preserve `hold_evidence_runes` as the repeat evaluation window. The new hard bound is buffer compatibility, not a larger repeat-detection window.
- Raising the action sibling's bound also raises the channel-level allowance for pending tool-call fragments. This is an unavoidable consequence of the current channel-level Core composition, remains bounded at 1,048,576 runes, and does not change action evaluation or release rules.
- Preserve the schema gate's default 4,096-rune terminal bound. A request with `metadata.scheme` is a distinct full-output validation boundary and is not claimed fixed by this communication patch.
- Do not change `packages/go/streamgate`, codec framing/parsing, release queue semantics, 502 serialization, config schema, contracts, specs, roadmap, deployment, or provider output-filter evaluation behavior.
- Do not split or reserialize provider frames. Core already buffers and releases a normalized event as one entry; codec fragmentation would require group-aware release semantics and is outside this fix.
- Events above 1,048,576 runes remain intentionally fail-closed.

### Final Routing

- `status=routed`; `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`; `finalizer_mode=pair`.
- Build closures: scope/context/verification/evidence/ownership/decision closed. Grade scores `1/1/1/1/1`, base basis `local-fit`, final route basis `risk-boundary`, lane `cloud`, grade `G05`, canonical file `PLAN-cloud-G05.md`.
- Review closures: scope/context/verification/evidence/ownership/decision closed. Grade scores `1/1/1/1/1`, route `official-review`, lane `cloud`, grade `G05`, adapter `codex`, model `gpt-5.6-sol`, reasoning effort `xhigh`, canonical file `CODE_REVIEW-cloud-G05.md`.
- `large_indivisible_context=false`.
- Positive loop risks: `temporal_state`, `boundary_contract`, `structured_interpretation`, and `variant_product`; count `4`.
- Recovery signals: `review_rework_count=0`, `evidence_integrity_failure=false`; risk boundary matched, recovery boundary did not.
- Capability gap: none.

## Implementation Checklist

- [ ] [STREAM_BUFFER-1] Align both repeat-policy hold requirements to Core's existing 1,048,576-rune ceiling and lock their composed envelope with a unit regression.
- [ ] [STREAM_BUFFER-2] Add Chat/Responses content/reasoning tunnel regressions for one semantic event above 4,096 runes with byte-identical successful release.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [STREAM_BUFFER-1] Repeat-policy hold envelope

#### Problem

`apps/edge/internal/openai/stream_gate_filters.go:131-173` builds the repeat rolling guard and terminal action sibling with default constructors. Both inherit the 4,096-rune default from `packages/go/streamgate/evidence_tail.go:74-81,157-197`. `packages/go/streamgate/evidence_tail.go:448-483` composes same-channel blocking requirements using the minimum positive bound, so changing only one repeat participant leaves 4,096 effective.

Before:

```go
// apps/edge/internal/openai/stream_gate_filters.go:131-173
case openAIOutputFilterRepeatGuard:
	req, err := streamgate.NewFilterHoldRequirementRolling(
		f.channel,
		[]streamgate.EventKind{
			streamgate.EventKindTextDelta,
			streamgate.EventKindReasoningDelta,
		},
		f.holdRunes,
	)
	// ...
case openAIOutputFilterRepeatActionGuard:
	req, err := streamgate.NewFilterHoldRequirementTerminalGate(
		f.channel,
		[]streamgate.EventKind{
			streamgate.EventKindToolCallFragment,
			streamgate.EventKindTerminal,
		},
		streamgate.EventKindTerminal,
	)
```

#### Solution

- Add private Edge constant `openAIRepeatHoldMaxBufferRunes = 1 << 20`, documented as a coalesced semantic-event allowance measured in runes, not bytes and not the rolling evidence window.
- Use `NewFilterHoldRequirementRollingWithMaxBuffer` for `openAIOutputFilterRepeatGuard` and `NewFilterHoldRequirementTerminalGateWithMaxBuffer` for `openAIOutputFilterRepeatActionGuard`, including their defensive fallback calls.
- Leave `openAIOutputFilterSchemaGate` on the default constructor.
- Add `TestOpenAIRepeatHoldBufferContract`: resolve the production repeat registration without schema, compile `streamgate.EvidencePlan`, and assert the composed channel maximum and both repeat participant maxima are 1,048,576 runes, the rolling evidence value remains configured, and a standalone schema requirement remains 4,096.

After:

```go
// A provider may coalesce one valid content/reasoning delta above Core's
// default hold size. This bound is measured in runes and does not widen evidence.
const openAIRepeatHoldMaxBufferRunes = 1 << 20

case openAIOutputFilterRepeatGuard:
	req, err := streamgate.NewFilterHoldRequirementRollingWithMaxBuffer(
		f.channel,
		[]streamgate.EventKind{
			streamgate.EventKindTextDelta,
			streamgate.EventKindReasoningDelta,
		},
		f.holdRunes,
		openAIRepeatHoldMaxBufferRunes,
	)
	// fallback uses the same explicit bound
	return req
case openAIOutputFilterRepeatActionGuard:
	req, err := streamgate.NewFilterHoldRequirementTerminalGateWithMaxBuffer(
		f.channel,
		[]streamgate.EventKind{
			streamgate.EventKindToolCallFragment,
			streamgate.EventKindTerminal,
		},
		streamgate.EventKindTerminal,
		openAIRepeatHoldMaxBufferRunes,
	)
	// fallback uses the same explicit bound
	return req
case openAIOutputFilterSchemaGate:
	// Keep the existing default constructor and bound.
```

#### Modified Files and Checklist

- [ ] `apps/edge/internal/openai/stream_gate_filters.go` — add the private rune bound and apply it to both repeat hold requirements only.
- [ ] `apps/edge/internal/openai/stream_gate_filters_test.go` — add `TestOpenAIRepeatHoldBufferContract` for participant, composed-plan, evidence-window, and schema non-regression assertions.
- [ ] `agent-task/provider_stream_buffer_compat/CODE_REVIEW-cloud-G05.md` — record actual implementation decisions and verification output.

#### Test Strategy

Write a regression because this is a bug fix. The test must use production registration/resolution rather than only direct constructors, so omission of the automatically registered action sibling fails.

#### Verification

```bash
gofmt -d apps/edge/internal/openai/stream_gate_filters.go apps/edge/internal/openai/stream_gate_filters_test.go
go test -count=1 ./apps/edge/internal/openai -run '^Test(OpenAIRepeatHoldBufferContract|OpenAIOutputFilterRegistrations)$'
```

Expected: no formatting diff; fresh tests prove the repeat pair composes to 1,048,576 runes while the evidence window and schema bound remain unchanged.

### [STREAM_BUFFER-2] Large semantic-event exact-wire tunnel regression

#### Problem

`apps/edge/internal/openai/stream_gate_tunnel_codec.go:239-286` converts a complete SSE frame into one or more semantic events and attaches the original wire frame to the first releasable event. `packages/go/streamgate/evidence_tail.go:1564-1582` rejects the whole event before evaluation when its rune count exceeds the composed hold bound. Existing production lifecycle coverage at `apps/edge/internal/openai/stream_gate_pipeline_test.go:612-720` uses small payloads and does not reproduce the observed greater-than-4,096 event.

Before:

```go
// apps/edge/internal/openai/stream_gate_pipeline_test.go:612-634
func TestStreamGateConfiguredRepeatActionSplitLifecycle(t *testing.T) {
	// Production registry/Core/tunnel coverage exists, but its frames are small.
	gateCfg := config.StreamEvidenceGateConf{
		Enabled: true,
		Filters: []config.StreamGateFilterPolicyConf{{
			Filter:            config.StreamGateFilterRepeatGuard,
			Enforcement:       config.StreamGateFilterEnforcementBlocking,
			HoldEvidenceRunes: 500,
		}},
	}
}
```

#### Solution

- Add `TestStreamGateConfiguredRepeatGuardLargeTunnelEvent` beside the production tunnel lifecycle tests.
- Use four cases: Chat content (`choices[].delta.content`), Chat reasoning (`choices[].delta.reasoning_content`), Responses output text (`response.output_text.delta`), and Responses reasoning (`response.reasoning_text.delta`).
- Encode one SSE JSON frame with 5,000 distinct Korean runes per case, followed by endpoint-native terminal wire. The payload is above the old default, below the new bound, and cannot trigger repeat matching.
- Configure only blocking `repeat_guard` with the normal 500-rune evidence window and no schema metadata. Drive the production registry, tunnel event source, Core runtime, and tunnel release sink with buffered `ProviderTunnelFrame` fixtures.
- Require `runtime.Run` success, one HTTP 200 start, one successful terminal, zero recovery dispatches, and caller body bytes exactly equal to the concatenated provider frames.
- Keep each provider frame whole; do not add codec fragmentation or a replacement serializer.

After:

```go
func TestStreamGateConfiguredRepeatGuardLargeTunnelEvent(t *testing.T) {
	payload := uniqueKoreanRunes(5000)
	tests := []struct {
		name     string
		endpoint string
		frame    []byte
	}{
		{name: "chat content", endpoint: openAIRebuildEndpointChat, frame: chatContentFrame(payload)},
		{name: "chat reasoning", endpoint: openAIRebuildEndpointChat, frame: chatReasoningFrame(payload)},
		{name: "responses output text", endpoint: openAIRebuildEndpointResponses, frame: responsesTextFrame(payload)},
		{name: "responses reasoning", endpoint: openAIRebuildEndpointResponses, frame: responsesReasoningFrame(payload)},
	}
	// Each case runs production registry -> tunnel source -> Core -> tunnel sink
	// and compares caller bytes with the original semantic and terminal frames.
}
```

Helper names are illustrative. Build the JSON/SSE fixtures locally with `encoding/json` or existing imports; add no dependency.

#### Modified Files and Checklist

- [ ] `apps/edge/internal/openai/stream_gate_pipeline_test.go` — add the four-case production runtime regression and exact-wire assertions.
- [ ] `agent-task/provider_stream_buffer_compat/CODE_REVIEW-cloud-G05.md` — record actual regression output and any justified deviation.

#### Test Strategy

Write the integration regression in the existing pipeline test file. The distinct 5,000-rune payload exercises UTF-8 rune accounting and both endpoint and semantic-kind axes without changing filter semantics.

#### Verification

```bash
gofmt -d apps/edge/internal/openai/stream_gate_pipeline_test.go
go test -count=1 ./apps/edge/internal/openai -run '^TestStreamGateConfiguredRepeatGuardLargeTunnelEvent$'
```

Expected: no formatting diff; all four fresh subtests return the original wire once with HTTP 200, one successful terminal, and no recovery dispatch.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/edge/internal/openai/stream_gate_filters.go` | STREAM_BUFFER-1 |
| `apps/edge/internal/openai/stream_gate_filters_test.go` | STREAM_BUFFER-1 |
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | STREAM_BUFFER-2 |
| `agent-task/provider_stream_buffer_compat/CODE_REVIEW-cloud-G05.md` | STREAM_BUFFER-1, STREAM_BUFFER-2 implementation evidence |

## Dependencies and Execution Order

1. Implement `STREAM_BUFFER-1` so the production registry resolves the corrected composed bound.
2. Implement `STREAM_BUFFER-2` against that registry and retain exact provider wire.
3. Run focused and final verification, then fill the active review evidence.

## Final Verification

```bash
command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT && go env GOMOD
gofmt -d apps/edge/internal/openai/stream_gate_filters.go apps/edge/internal/openai/stream_gate_filters_test.go apps/edge/internal/openai/stream_gate_pipeline_test.go
go test -count=1 ./apps/edge/internal/openai -run '^Test(OpenAIRepeatHoldBufferContract|StreamGateConfiguredRepeatGuardLargeTunnelEvent)$'
go test -race -count=1 ./packages/go/streamgate ./apps/edge/internal/openai ./packages/go/config
git diff --check
```

Expected: host Go identity is unchanged; formatting is clean; fresh focused and race suites pass; all four large-event tunnel variants preserve the exact provider wire with one successful terminal; and the diff has no whitespace errors. Proto generation and general mock/provider smoke are excluded because no proto/config/route is changed and those commands do not activate this repeat-policy failure condition.

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