Validate portable provider conversation grammars
Problem
Mixed-provider history can produce requests with the wrong Responses assistant content type, unsupported leading/trailing roles, or detached reasoning items. Dropping whitespace at stream-fragment boundaries also changes visible answers.
Design
Add one pure validate_input(adapter, body) function, with per-protocol conversation grammar checks keyed to the provider-input research rule IDs. It checks roles, content variants, call/result pairing, names, IDs, argument shapes, reasoning-item placement, and Gemini batch order.
Projection uses one sequence fold for role boundaries and Responses reasoning adjacency. Ordinary text is normalized after same-role messages are merged, so whitespace inside an answer survives while Anthropic whitespace-only runs are omitted. Native signed parts remain byte-exact and separate. Complete Gemini batches move results into call order using a linear permutation; already-ordered batches allocate nothing.
Content positions declare their allowed shapes in shared protocol data. One walker checks Chat message content, Responses message content and Responses tool outputs; their former part-validation loops are deleted. Chat null content requires tool calls, and file parts require a reference or named inline source. Required fields and native reasoning shapes share the same constraint data. Tool-name and call-ID constraints are shared with their projection tables. Validators check immediate reasoning successors, string-content interruptions, duplicate call IDs, portable user boundaries, and tool definitions with either Responses input representation.
Deletions
Replace the inline role/batch fold with the shared sequence function. Remove fabricated null Chat messages when foreign opaque reasoning produces no content. Net text delta: +1,921 lines (+2,023/-102), plus two binary fixtures. Round two was +433 lines (+545/-112); this test-oracle correction is +56 lines (+102/-46).
Tests
- Linux provider library at signed head
4391f896e: 188 passed in 0.68 s, including the unchanged stock native workflow gate, mixed-origin batches, native byte preservation, and negative controls for all four re-review findings. - One generated shrinking property walks all 41 declared position/shape combinations, including tool outputs, call records and native reasoning. Every required field (including the discriminator) is removed or assigned incompatible JSON types; both the shape check and full local validator must reject it. Independent controls remove shape requirements or make the full validator ignore mutations; either rejection assertion family is necessary for these controls to pass. A numeric-string mutant also fails the same oracle. Incompatible mutation types are selected from declarations, with only valid array replacements exempted; acceptance never filters a mutation. The superseded text property is deleted, retaining its wrong-type rule-ID probes.
- Nested escaped-string properties retain original native bytes and reject generated lone surrogates at arbitrary bounded depths, including map keys and ignored fields. The former prefix-only check admits the regression fixtures.
- Pinned nightly
provider_streamfuzz target: 256 runs, <1 s. Five new corpus seeds cover malformed tool outputs, numeric Chat content, empty file sources and nested native surrogates. Native admission is independently checked against the harness bounded literal JSON decoder. - Windows
lb.ps1 cargo check -p acyclic-harness-providers --lib --testswith the provider fixture feature closure: passed, 15.51 s (queue time excluded). - No live endpoints or credentials.
Decisions
- Validators operate on already-decoded JSON and are available for local probes; dispatch keeps its typed encoder and does not allocate a second request graph for validation.
- This is the conversation-grammar slice of transcript validation. Model-specific schema/parameter policies, native media normalization, malformed exchange repair, and the broader total-projection fuzzer remain follow-up work. It does not claim to implement every rule in the research brief.
- Native continuity undergoes one strict streaming string-validation pass before shape inspection, with serde_json’s 128-level recursion bound. It builds no Value tree and returns the original RawValue; nested unknown fields receive the same check as known fields. Preserve native payload bytes and signature boundaries. The existing answer oracle now explicitly allows only the documented omission of wholly whitespace Anthropic output; its Unicode, chunk, tool-loop, order, and native-byte assertions remain enabled.
- This adds no task-state store or protocol. Canonical transcript state remains in volume files. WASM/TS caller replacement remains #508.