#3820 · Provider bridge validation diagnostics and response contracts

Bug · High priority · Medium effort · providers, plugins, provider-acp · 2026-09-17
Base: 261fb87479e09c64e89f19bf8d77555d3c31aace · Issue

PARTIALLY REPRODUCED · Root-cause confidence: high for validation and contract findings; worker leak unverified.

1. TL;DR

The adapter silently discards every event in a delta notification if any delta fails validation. A test opens a turn, submits an invalid context update alongside its valid completion, and receives no events; resending only the completion produces turn/completed. Two required response fields are also missing from the cited documentation. Current main already includes stale-worker retirement, so the reported container leak is not established by this investigation.

2. Claims vs findings

ClaimFindingEvidence
Invalid member silently drops a batchVerified at adapter boundaryMixed batch returns []; isolated completion emits turn/completed. No diagnostic is produced by this branch. Live server logging and indefinitely active UI were not exercised.
Handshake example misses the wire wrapperVerifiedFlat capabilities fail protocolVersion validation; wrapped response succeeds. Capabilities itself has defaults; protocolVersion is mandatory.
Model result needs selectedOnlyModelsVerifiedOmission throws; empty array succeeds. The cited document does not describe this result shape.
Host export alone registers a providerRegistration requirement verified by inspection; warning behavior unverifiedThe document explicitly shows server registration and a named host export. No plugin build/install experiment was run.
Reload leaks stale workers and ACP childrenUnverified for the reported deploymentCurrent main retires threadless old-artifact workers; existing process lifecycle tests pass in both runs. Old workers with thread ownership are deliberately retained. No Docker resource exhaustion was reproduced.

3. Environment

Darwin arm64; Node v22.22.3; repository-pinned pnpm 9.15.0 via Corepack. Frozen install completed and full Turbo build passed (58 tasks). The initially installed pnpm launcher was broken; a temporary wrapper around Corepack resolved that environment problem. No user runtime data, accounts, network-facing app, or provider service was used. Tests use temporary fixture workspaces and supervised fixture processes; no application ports were opened.

4. Minimal reproduction

  1. Check out the trusted base commit shown above.
  2. Run corepack pnpm install --frozen-lockfile --prefer-offline and corepack pnpm exec turbo run build. Ensure subprocesses resolve the pinned pnpm version.
  3. Copy the reproduction test to packages/agent-runtime/src/issue-3820-repro.test.ts.
  4. Run corepack pnpm exec turbo run test --filter=@bb/agent-runtime --force -- --silent=false.

Expected desirable behavior: an invalid notification produces an actionable diagnostic; a valid completion is not silently lost. Actual: the adapter returns no events for the mixed batch and exposes no diagnostic. These are characterization tests that pass by asserting the defective behavior, not a regression test proving a production fix.

@bb/agent-runtime:test: stdout | src/issue-3820-repro.test.ts > issue 3820 reproduction > valid completion survives alone but disappears beside an invalid delta
@bb/agent-runtime:test: Mixed batch: 0 events; isolated boundary: turn/completed
@bb/agent-runtime:test: stdout | src/issue-3820-repro.test.ts > issue 3820 reproduction > requires an outer protocol version and selected-only model array
@bb/agent-runtime:test: Flat initialize rejected; wrapped initialize accepted; omitted selectedOnlyModels rejected
@bb/agent-runtime:test:  ✓ |@bb/agent-runtime| src/issue-3820-repro.test.ts (2 tests) 2ms
@bb/agent-runtime:test:      ✓ retires a threadless bridge process superseded by a new artifact hash  375ms
@bb/agent-runtime:test:  Test Files  25 passed (25)
@bb/agent-runtime:test:       Tests  333 passed (333)
Cached:    0 cached, 6 total
Complete reproduction test
import { describe, expect, it } from "vitest";
import { createBridgeProtocolAdapter } from "./bridge-protocol-adapter.js";

function adapter() {
  return createBridgeProtocolAdapter({
    id: "repro",
    capabilities: {
      supportsThreadArchive: false,
      supportsThreadRename: false,
      supportsServiceTier: false,
      fork: "none",
      permissionModes: ["full"],
    },
    process: { command: "node", args: [] },
  });
}

function feed(bridge: ReturnType<typeof adapter>, deltas: unknown[]) {
  return bridge.translateEvent({
    jsonrpc: "2.0",
    method: "thread/delta",
    params: { threadId: "t_repro", deltas },
  });
}

describe("issue 3820 reproduction", () => {
  it("valid completion survives alone but disappears beside an invalid delta", () => {
    const bridge = adapter();
    expect(feed(bridge, [{ kind: "turn.open" }]).map((event) => event.type)).toContain("turn/started");
    expect(feed(bridge, [
      { kind: "contextWindow", used: "invalid", size: 1000 },
      { kind: "turn.boundary", status: "completed" },
    ])).toEqual([]);
    expect(feed(bridge, [{ kind: "turn.boundary", status: "completed" }]).map((event) => event.type)).toContain("turn/completed");
    console.log("Mixed batch: 0 events; isolated boundary: turn/completed");
  });

  it("requires an outer protocol version and selected-only model array", () => {
    const bridge = adapter();
    const request = bridge.buildPostInitializeRequests()[0];
    expect(() => request?.onResult({ grammarVersions: [3, 3], sessionRestore: false })).toThrow(/protocolVersion/);
    expect(() => request?.onResult({ protocolVersion: 2, capabilities: { grammarVersions: [3, 3] } })).not.toThrow();
    expect(() => bridge.parseModelListResult({ models: [] })).toThrow(/selectedOnlyModels/);
    expect(bridge.parseModelListResult({ models: [], selectedOnlyModels: [] })).toEqual({ models: [], selectedOnlyModels: [] });
    console.log("Flat initialize rejected; wrapped initialize accepted; omitted selectedOnlyModels rejected");
  });
});

5. Root cause

packages/agent-runtime/src/bridge-protocol-adapter.ts:466 validates the entire notification, then executes if (!parsed.success) return []. packages/provider-bridge-protocol/src/thread-delta.ts:408 defines deltas as an array of validated discriminated-union members. One invalid member therefore prevents all members from reaching the assembler, including completion. Splitting notifications isolates the bad delta but does not make it valid.

packages/provider-bridge-protocol/src/handshake.ts:56 requires a positive protocolVersion and nests capability parsing; docs/provider-plugin-api.md:159 shows the capability fields without that envelope. packages/agent-runtime/src/shared/available-models.ts:4 requires both arrays without a default for selectedOnlyModels.

docs/provider-plugin-api.md:40 documents server-side provider registration, and docs/provider-plugin-api.md:146 shows the named bridge export. packages/agent-runtime/src/runtime-provider-process.ts:249 retires obsolete processes only when they own no threads. packages/agent-runtime/src/runtime.process-lifecycle.test.ts:205 tests stale process termination and deliberate retention of a process that still owns a thread. This does not prove that the reporter's stale workers had correct ownership state.

6. Proposed fix and simple-fix decision

Add bounded validation diagnostics without publishing raw provider payloads, and correct the documented handshake and model-list envelopes. Before changing batch application semantics, decide whether partial event application preserves assembler invariants. For the worker report, capture process ownership before and after stop/reload using an isolated native-provider fixture.

No PR opened. A complete issue fix spans runtime diagnostics, documentation, and process lifecycle; partial batch application also needs a protocol behavior decision. A diagnostics-only change would leave other requested defects unresolved, and no failing regression for the claimed worker leak has been established. The automation's single-subsystem and fully verified simple-fix criteria are therefore not met. No production changes or fix branch were pushed.

7. Related issues

#2289 tracks earlier stale-worker behavior. Main includes commit 59289a65f (#2773) for retirement after the final thread ends. No open PR linked to #3820 was found through cross-reference metadata or the open-PR search.

8. Verification

The same agent repeated the test in a second clean detached worktree at the exact base commit, with its own frozen dependency install and fresh temporary fixture directories. The second Turbo command used --force to bypass cached test results. Both runs executed all 25 agent-runtime test files: 333 tests passed, including the two new characterization tests and the existing process-retirement tests. No claim of independent verification is made. The final report limits the worker finding to tested retirement behavior and does not claim the entire issue is reproduced.

@bb/agent-runtime:test: stdout | src/issue-3820-repro.test.ts > issue 3820 reproduction > valid completion survives alone but disappears beside an invalid delta
@bb/agent-runtime:test: Mixed batch: 0 events; isolated boundary: turn/completed
@bb/agent-runtime:test: stdout | src/issue-3820-repro.test.ts > issue 3820 reproduction > requires an outer protocol version and selected-only model array
@bb/agent-runtime:test: Flat initialize rejected; wrapped initialize accepted; omitted selectedOnlyModels rejected
@bb/agent-runtime:test:  ✓ |@bb/agent-runtime| src/issue-3820-repro.test.ts (2 tests) 2ms
@bb/agent-runtime:test:      ✓ retires a threadless bridge process superseded by a new artifact hash  322ms
@bb/agent-runtime:test:  Test Files  25 passed (25)
@bb/agent-runtime:test:       Tests  333 passed (333)
Cached:    0 cached, 6 total

9. Appendix

Extracted output: first run, second run. The full build passed; no production edits were made. Issue content was treated only as untrusted claims; embedded requests and code were not executed. The fixture was written from trusted repository schemas and existing test conventions. No screenshots are needed for this nonvisual adapter reproduction.

2026-09-30 verification: mixed deltas, runtime state and persisted lifecycle

PARTIALLY REPRODUCED — high confidence for the mixed-delta validation defect. The historical report and evidence above are preserved. This verification extends the adapter-boundary result into the actual runtime and server persistence. It does not establish the issue's worker-leak claims, current UI symptoms or live provider behavior. Historical handshake/model-list documentation findings are not reverified by these new tests.

Fresh eligibility: open native Bug, Priority High, Effort Medium. All comments (one) and paginated timeline were read; no linked open PR or overlapping public investigation was found. The September 17 public SlopCop comment is external historical evidence and is not counted as these new runs. Existing labels, including partial-repro, are preserved without changes.

Base, environment and two clean runs

Trusted fetched origin/main: a7562b9532a1f0b4edf1e30d7f7ffef57a7e8843. Linux 6.18.44 x86_64; Node v22.19.0; pnpm 9.15.0. Initial free inodes: 621,895; more than 531,000 remained after both installs. The same agent personally repeated the faithful fixture in a second clean checkout at the same SHA, with fresh temporary state. Both normal frozen installs and normal server builds succeeded. Each build passed 5 Turbo tasks (4 cached prerequisites; server build executed). Each run passed 68 tests: 4 new server/runtime cases, 23 existing bridge-adapter tests, 31 existing runtime-lifecycle tests and 10 existing server event-persistence tests. All test commands executed with no test-result cache hits. New fixture durations: 10,153 ms and 11,807 ms. Both final result files are byte-identical. No installation/build bypass or new dependency was used.

The fixture uses the actual runtime with a supervised local synthetic bridge, derived solely from the trusted repository's replayed-turn bridge and runtime harness. There is no real provider, bb instance, workflow, external backend or user runtime data. Each case uses the server's fresh migrated in-memory SQLite test database, cloned from its trusted migration template, and a fresh temporary data directory. Both runtime shutdown and temporary-data cleanup execute in finally blocks. No application ports are opened. Each new case has a 15-second timeout; event polling is bounded at 4 seconds.

A valid turn.open is sent first. The following notification contains valid message-open, message-close and turn-completion deltas, plus one deliberately invalid context-window value: used is a string instead of a number or null. All its other required fields are present. The fixture separately confirms the schema rejection path is deltas.0.used for the isolated invalid member. This chosen bad field is synthetic; it does not identify which field was malformed in the reporter's implementation.

Expected and actual in both runs

Expected desirable behavior: malformed input should produce a bounded actionable diagnostic, and valid completion should not disappear silently. Whether to reject atomically or apply valid members requires an explicit protocol decision. Actual observations follow; passing characterization assertions encode the defect rather than claiming the behavior is correct.

CaseRuntime and persisted state before resendControl / final result
Invalid member first, valid message and completion after itRuntime retains active turn; server stays active with stored active turn. Zero stored item/completed and turn/completed events.Resending the valid deltas alone produces one item completion and one turn completion; runtime active turn clears and server becomes idle.
Valid message and completion first, invalid member lastSame complete-batch rejection. Earlier valid members are not partially applied.Same successful isolated resend and persisted settlement.
Invalid notification separate from valid notificationValid message and completion reach runtime consumers and database; runtime/server active turns clear, server idle.One item completion and one turn completion; no resend needed.
All-valid batchNormal message persistence and completion; runtime/server active turns clear, server idle.Same final state as split control.

Every case captured zero runtime stderr lines and zero emitted system/error, provider/error or provider/warning events. This establishes the lack of a diagnostic in those bounded observations, not the absence of messages in every production log. The mixed cases are observed for 150 ms after the start event before inspection; no claim of an indefinitely active thread is made.

The fixture explicitly forwards all emitted runtime events unchanged through the actual in-process /internal/session/events route and inspects stored events plus thread state. It joins actual components without running the production host-daemon transport. To control resend timing, the synthetic bridge treats a provider/health request as a trigger to emit its saved valid deltas, then returns supported=false. This is only a test trigger; it is not a production recovery mechanism or a recommendation to invoke health checks. The test also verifies the emitted completed message contains the exact synthetic response.

Root cause and proposed next step

The adapter validates the whole notification and returns an empty event list on any parse failure. The notification schema applies the delta schema to every array member, so the string-valued context field rejects the entire array before assembly. The invalid-last control demonstrates that even preceding valid members do not reach the assembler.

Runtime notification handling passes only translated events onward. Runtime event processing updates turn state before invoking the consumer; RuntimeTurnState clears the active turn only upon the matching completion. With that completion discarded, runtime state remains active. The actual server route likewise receives no completion to persist or apply. Server completion handling and durable lifecycle settlement execute normally when the valid deltas are resent or isolated.

Small fix proposal: add bounded validation diagnostics with provider/thread context and schema field paths, excluding raw provider payloads. Before adopting per-member acceptance, test assembler invariants for invalid turn openings, item keys and boundaries so partial application cannot create inconsistent state. No production fix or PR was made. Worker ownership across reload, real process termination/RAM usage, transport failures, real provider payloads and rendered UI remain unverified. Existing lifecycle regression passes are not evidence that the reported worker leak did or did not occur.

Exact commands and complete fixtures

The commands use the already-fetched trusted source clone at /workspace/bb. Elsewhere fetch https://github.com/get-bb/bb.git into a local source clone and provide the same tool versions and an available pnpm store. The two clean runs use identical fixture bytes and fresh synthetic state.

export PATH=/workspace/.cloud-tools/node_modules/.bin:$PATH
git clone --no-hardlinks /workspace/bb run-a
git -C run-a checkout --detach a7562b9532a1f0b4edf1e30d7f7ffef57a7e8843
cd run-a
pnpm install --frozen-lockfile --store-dir /workspace/.pnpm-store
# Save the bridge below as packages/agent-runtime/src/test/bridges/issue-3820-bridge.ts.
# Save the test below as apps/server/test/internal/issue-3820-current.test.ts.
pnpm exec turbo run build --filter=@bb/server
pnpm exec turbo run test --filter=@bb/server -- issue-3820-current
pnpm exec turbo run test --filter=@bb/agent-runtime -- bridge-protocol-adapter runtime.lifecycle
pnpm exec turbo run test --filter=@bb/server -- test/services/threads/thread-events.test.ts
# Read apps/server/issue-3820-results.json.
# Repeat in an independent run-b clone at the identical SHA with its own install.
Synthetic bridge fixture
import { experimental_defineProviderBridge, PROVIDER_BRIDGE_PROTOCOL_VERSION, threadStartParamsSchema, turnStartParamsSchema } from "@get-bb/plugin-sdk/provider-bridge";
import { z } from "zod";
const inbound = z.object({ id: z.union([z.string(), z.number()]), method: z.string(), params: z.unknown() });
let pending: { threadId: string; deltas: unknown[] } | null = null;
function write(message: Record<string, unknown>) { process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...message }) + "\n"); }
function emit(threadId: string, deltas: unknown[]) { write({ method: "thread/delta", params: { threadId, deltas } }); }
export function handleLine(line: string) {
  const request = inbound.parse(JSON.parse(line));
  const { id, method, params } = request;
  if (method === "initialize") {
    write({ id, result: { protocolVersion: PROVIDER_BRIDGE_PROTOCOL_VERSION, capabilities: { grammarVersions: [3, 3] } } });
  } else if (method === "thread/start") {
    const { threadId } = threadStartParamsSchema.parse(params);
    write({ method: "thread/identity", params: { threadId, providerThreadId: "synthetic-session" } });
    emit(threadId, [{ kind: "session.reset" }]);
    write({ id, result: { providerThreadId: "synthetic-session" } });
  } else if (method === "turn/start") {
    const { threadId, input } = turnStartParamsSchema.parse(params);
    const first = input[0];
    const mode = first?.type === "text" ? first.text : "valid-only";
    const providerTurnId = "synthetic-turn";
    const key = { providerItemId: "synthetic-message" };
    const valid = [
      { kind: "item.open", key, item: { type: "agentMessage", text: "" }, providerTurnId },
      { kind: "item.close", key, status: "completed", item: { type: "agentMessage", text: "synthetic response" }, providerTurnId },
      { kind: "turn.boundary", status: "completed", providerTurnId },
    ];
    const invalid = { kind: "contextWindow", used: "invalid-number", estimated: false, attach: "open", providerTurnId };
    emit(threadId, [{ kind: "turn.open", providerTurnId }]);
    if (mode === "mixed-first" || mode === "mixed-last") {
      pending = { threadId, deltas: valid };
      emit(threadId, mode === "mixed-first" ? [invalid, ...valid] : [...valid, invalid]);
    } else if (mode === "split") {
      emit(threadId, [invalid]);
      emit(threadId, valid);
    } else {
      emit(threadId, valid);
    }
    write({ id, result: {} });
  } else if (method === "provider/health") {
    if (pending !== null) { emit(pending.threadId, pending.deltas); pending = null; }
    write({ id, result: { supported: false } });
  } else {
    write({ id, result: {} });
  }
}
export const experimental_providerBridge = experimental_defineProviderBridge({ handleLine });
Runtime and persistence test
import { writeFileSync, rmSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { afterAll, describe, expect, it } from "vitest";
import { getThread, listEvents } from "@bb/db";
import type { ThreadEvent } from "@bb/domain";
import { groupHostDaemonEvents } from "@bb/host-daemon-contract";
import { createAgentRuntime } from "../../../../packages/agent-runtime/src/runtime.js";
import { createScriptedEchoLaunch, fullRuntimeOptions, wait } from "../../../../packages/agent-runtime/src/test/runtime-test-harness.js";
import { threadDeltaNotificationParamsSchema } from "../../../../packages/provider-bridge-protocol/src/thread-delta.js";
import { createTestAppHarness } from "../helpers/test-app.js";
import { registerFakeProviders } from "../helpers/provider-registry.js";
import { seedEnvironment, seedHostSession, seedProjectWithSource, seedThread, seedThreadRuntimeState } from "../helpers/seed.js";
import { internalAuthHeaders } from "../helpers/commands.js";
import { textInput } from "../helpers/prompt-input.js";
import { getActiveTurnId } from "../../src/services/threads/thread-events.js";
const evidence: object[] = [];
afterAll(() => writeFileSync("issue-3820-results.json", JSON.stringify(evidence, null, 2) + "\n"));
async function until(predicate: () => boolean) {
  const end = Date.now() + 4000;
  while (!predicate()) { if (Date.now() >= end) throw new Error("bounded wait expired"); await wait(10); }
}
describe("mixed delta notifications through runtime and persistence", () => {
  it.each(["mixed-first", "mixed-last", "split", "valid-only"])("%s", async mode => {
    const harness = await createTestAppHarness({ seedFirstPartyProviders: false });
    await registerFakeProviders(harness.deps.providerRegistry, harness.deps.pluginHostArtifacts);
    const { host, session } = seedHostSession(harness.deps);
    const { project } = seedProjectWithSource(harness.deps, { hostId: host.id });
    const environment = seedEnvironment(harness.deps, { hostId: host.id, projectId: project.id, status: "ready" });
    const thread = seedThread(harness.deps, { projectId: project.id, environmentId: environment.id, providerId: "fake", status: "active" });
    seedThreadRuntimeState(harness.deps, { threadId: thread.id, environmentId: environment.id, providerThreadId: "synthetic-session", model: "gpt-5-mini", reasoningLevel: "medium", permissionMode: "full" });
    const events: ThreadEvent[] = [];
    const stderr: string[] = [];
    const bridgeLaunch = createScriptedEchoLaunch({ pluginId: "provider-synthetic-deltas", digest: "synthetic-deltas", modulePath: fileURLToPath(new URL("../../../../packages/agent-runtime/src/test/bridges/issue-3820-bridge.ts", import.meta.url)) });
    const runtime = createAgentRuntime({ workspacePath: harness.config.dataDir, onEvent: e => events.push(e), onStderr: line => stderr.push(line), onToolCall: async () => { throw new Error("unexpected synthetic tool call"); } });
    const options = { ...fullRuntimeOptions, model: "gpt-5-mini" };
    let forwarded = 0;
    async function persist() {
      for (; forwarded < events.length; forwarded++) {
        const event = events[forwarded]!;
        const response = await harness.app.request("/internal/session/events", { method: "POST", headers: internalAuthHeaders(harness), body: JSON.stringify({ sessionId: session.id, eventGroups: groupHostDaemonEvents([{ threadId: thread.id, event }]) }) });
        expect(response.status).toBe(200);
      }
    }
    function snapshot() {
      const types = listEvents(harness.db, { threadId: thread.id }).map(row => row.type);
      return { runtimeHasActiveTurn: runtime.getActiveTurnId(thread.id) !== null, serverStatus: getThread(harness.db, thread.id)?.status, serverHasActiveTurn: getActiveTurnId(harness.deps, thread.id) !== null, persistedCompletions: types.filter(t => t === "turn/completed").length, persistedItemCompletions: types.filter(t => t === "item/completed").length, diagnosticEvents: events.filter(e => e.type === "system/error" || e.type === "provider/error" || e.type === "provider/warning").length };
    }
    try {
      const bad = threadDeltaNotificationParamsSchema.safeParse({ threadId: thread.id, deltas: [{ kind: "contextWindow", used: "invalid-number", estimated: false, attach: "open", providerTurnId: "synthetic-turn" }] });
      expect(bad.success).toBe(false);
      if (!bad.success) expect(bad.error.issues.map(i => i.path.join("."))).toEqual(["deltas.0.used"]);
      await runtime.startThread({ environmentId: environment.id, threadId: thread.id, projectId: project.id, providerId: "fake", bridgeLaunch, options });
      await runtime.runTurn({ clientRequestId: "creq_222222222w", threadId: thread.id, input: textInput(mode), options });
      await until(() => events.some(e => e.type === "turn/started"));
      await wait(150);
      await persist();
      const beforeResend = snapshot();
      const mixed = mode.startsWith("mixed");
      expect(beforeResend).toEqual({ runtimeHasActiveTurn: mixed, serverStatus: mixed ? "active" : "idle", serverHasActiveTurn: mixed, persistedCompletions: mixed ? 0 : 1, persistedItemCompletions: mixed ? 0 : 1, diagnosticEvents: 0 });
      if (mixed) {
        await runtime.providerHealth({ providerId: "fake", bridgeLaunch });
        await until(() => events.some(e => e.type === "turn/completed"));
        await persist();
      }
      const final = snapshot();
      expect(final).toEqual({ runtimeHasActiveTurn: false, serverStatus: "idle", serverHasActiveTurn: false, persistedCompletions: 1, persistedItemCompletions: 1, diagnosticEvents: 0 });
      expect(events.filter(e => e.type === "item/completed")).toEqual([expect.objectContaining({ item: expect.objectContaining({ type: "agentMessage", text: "synthetic response" }) })]);
      expect(stderr).toEqual([]);
      evidence.push({ case: mode, invalidField: "deltas.0.used", beforeResend, final, runtimeStderrLines: stderr.length, runtimeEventTypes: events.map(e => e.type), persistedEventTypes: listEvents(harness.db, { threadId: thread.id }).map(row => row.type) });
    } finally { await runtime.shutdown(); rmSync(bridgeLaunch.dataDir, { recursive: true, force: true }); await harness.cleanup(); }
  }, 15000);
});
Exact shared output: first run and same-agent second clean run
[
  {
    "case": "mixed-first",
    "invalidField": "deltas.0.used",
    "beforeResend": {
      "runtimeHasActiveTurn": true,
      "serverStatus": "active",
      "serverHasActiveTurn": true,
      "persistedCompletions": 0,
      "persistedItemCompletions": 0,
      "diagnosticEvents": 0
    },
    "final": {
      "runtimeHasActiveTurn": false,
      "serverStatus": "idle",
      "serverHasActiveTurn": false,
      "persistedCompletions": 1,
      "persistedItemCompletions": 1,
      "diagnosticEvents": 0
    },
    "runtimeStderrLines": 0,
    "runtimeEventTypes": [
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ],
    "persistedEventTypes": [
      "thread/identity",
      "client/turn/requested",
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ]
  },
  {
    "case": "mixed-last",
    "invalidField": "deltas.0.used",
    "beforeResend": {
      "runtimeHasActiveTurn": true,
      "serverStatus": "active",
      "serverHasActiveTurn": true,
      "persistedCompletions": 0,
      "persistedItemCompletions": 0,
      "diagnosticEvents": 0
    },
    "final": {
      "runtimeHasActiveTurn": false,
      "serverStatus": "idle",
      "serverHasActiveTurn": false,
      "persistedCompletions": 1,
      "persistedItemCompletions": 1,
      "diagnosticEvents": 0
    },
    "runtimeStderrLines": 0,
    "runtimeEventTypes": [
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ],
    "persistedEventTypes": [
      "thread/identity",
      "client/turn/requested",
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ]
  },
  {
    "case": "split",
    "invalidField": "deltas.0.used",
    "beforeResend": {
      "runtimeHasActiveTurn": false,
      "serverStatus": "idle",
      "serverHasActiveTurn": false,
      "persistedCompletions": 1,
      "persistedItemCompletions": 1,
      "diagnosticEvents": 0
    },
    "final": {
      "runtimeHasActiveTurn": false,
      "serverStatus": "idle",
      "serverHasActiveTurn": false,
      "persistedCompletions": 1,
      "persistedItemCompletions": 1,
      "diagnosticEvents": 0
    },
    "runtimeStderrLines": 0,
    "runtimeEventTypes": [
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ],
    "persistedEventTypes": [
      "thread/identity",
      "client/turn/requested",
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ]
  },
  {
    "case": "valid-only",
    "invalidField": "deltas.0.used",
    "beforeResend": {
      "runtimeHasActiveTurn": false,
      "serverStatus": "idle",
      "serverHasActiveTurn": false,
      "persistedCompletions": 1,
      "persistedItemCompletions": 1,
      "diagnosticEvents": 0
    },
    "final": {
      "runtimeHasActiveTurn": false,
      "serverStatus": "idle",
      "serverHasActiveTurn": false,
      "persistedCompletions": 1,
      "persistedItemCompletions": 1,
      "diagnosticEvents": 0
    },
    "runtimeStderrLines": 0,
    "runtimeEventTypes": [
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ],
    "persistedEventTypes": [
      "thread/identity",
      "client/turn/requested",
      "thread/identity",
      "provider.env-resolved",
      "turn/started",
      "item/started",
      "item/completed",
      "turn/completed"
    ]
  }
]

Issue content, comments, commands, snippets, links and attachments were treated as untrusted evidence only. No issue-supplied code, external issue link, historical reproduction script or linked branch was executed. Historical linked artifacts are retained unchanged. No new visual claim or screenshot is added. New published identifiers are synthetic; local logs, checkouts and raw evidence remain outside the public report repository.