Rust conformance runner
The Rust conformance runner, plus the fixture bug typed decoding exposes. Card: W3 conformance runner. Base of the stack, on rust-sdk; the releng PR (#159) adds the CI job that runs it. Supersedes #160, which GitHub closed when the stack was reordered (its review threads and replies remain there).
Runner
conformance/runner/rust is its own workspace with a path dependency on ../../../rust/fizzy-sdk. It ports hey-sdk's runner (axum mock server, fixture loader, assertions) and adopts fizzy's generic dispatch, mirroring the Go runner: expand path with pathParams, append queryParams (arrays repeat the key), strip /{accountId} for the scoped client, raw verbs with RequestOptions (no_retry when the route has no retry policy and the method is not POST, idempotent for idempotent POSTs — read off the generated route table rather than a second copy of the behavior model), get_all when the case has several linked responses or a urlOrigin assertion, fast delays unless a delayBetweenRequests assertion exists, and configOverrides.baseUrl with zero mocks means "expect the builder to refuse".
Adaptations from hey's runner, all deliberate: exact path matching (no .json normalization); every retry interval checked and fewer than two requests fails the delay assertion; no maxPages/maxItems (rejected loudly, not ignored); the mock server rewrites a Link target only when its parsed origin is exactly the configured one, and serves every other target as written — foreign host, downgraded scheme, prefix-sharing host — while the transport the runner hands the SDK (the shipped reqwest client, wrapped) refuses any request bound anywhere but the mock server and counts it as a failure, so the SDK is judged on the URL the fixture meant and nothing leaves the machine; a follow-on request must ask for the page the previous Link named — a wrong page is a failure in its own right, whatever the SDK makes of the 500 it gets; urlOrigin: rejected requires the mocked pages to have been fetched and the error to be usage. All 17 schema assertion types are implemented (fixtures use 11); responseBody compares JSON structurally; name, operation and assertions are required at the type level; an unknown operation or assertion type fails the case. Every request's method must be the case's. Output ends with Passed: N, Failed: M, Total: T.
Makefile: conformance-rust (alias conformance-rs), conformance-runner-tests-rust, membership in conformance and conformance-build. The CI job that runs them is in #159.
Every case is validated against the generated route it names — same method, same path template, .json the one allowance — before any request goes out; the request itself still travels through the raw verbs.
Fixture fixes
Every id in the OpenAPI schemas is a string; 29 mock bodies across 9 fixture files wrote them as numbers (pagination.json's Board.id was the one the plan called out). The raw-JSON runners never noticed; a typed decoder rejects them. Fixed as "id": "1" throughout; Card.number, position and the other integer fields stay numeric as the schema says. The route validation then found three more: UpdateAccountEntropy and UpdateBoardEntropy sent PATCH where the model says PUT, and GetNotificationTray dropped its /{accountId} prefix — corrected. Go still passes 127/127 locally.
Result
Transport conformance against rust-sdk @ 320e1c5: 124/127 (runner unit tests 11/11; clippy -D warnings and rustfmt clean). The three failures are SDK retry semantics, reported to the crate branch: two cases get 4 attempts where the behavior model's max: 3 is total attempts (Go: for attempt = 1; attempt <= MaxRetries), and an exhausted 429 maps to api_error instead of rate_limit. This job will be green when those land on rust-sdk.
Adversarial review (Codex, gpt-6-astra, xhigh)
Accepted: substring Link rewrite corrupting prefix-sharing foreign origins (now per-target origin comparison); serving pages by index without checking the request matches the prior Link (now enforced); urlOrigin passing on any usage error before the first fetch (now requires the mocked pages fetched). Declined: making the "protocol downgrade" case test scheme rather than host — against an http://127.0.0.1 mock no runner in this repo can, and the sibling runners share the limitation; scheme comparison is covered by the SDK's own unit tests.
Review round 1 (Copilot, Codex): foreign-origin stand-in listener; structural responseBody comparison; empty path as root; required fixture fields — fixed. Declined with reasons in-thread: dispatching through generated routes and carrying the route's retry budget into raw calls (generic raw-verb dispatch is the family's contract; generated-call coverage lives in the crate's tests/generated_calls).
Review round 2 (Copilot, Codex): make targets moved here; the SDK's pagination::next_link reused and Link targets rewritten by bracket, not by comma; fixture method/path validated against the generated route (which surfaced the three fixture fixes above); statusCode on an error reads the error's own status only. Raw dispatch kept, reasons in-thread.
Codex CLI pass 2 (gpt-6-astra, xhigh, over the whole stack): accepted — wrong-page requests counted as unconditional failures; a success carrying an error status fails statusCode; exact integer comparison beyond i64; Link URLs left as written with an intercepting HttpClient refusing foreign hosts (replacing the second listener, which had reduced every foreign origin to a port difference). Declined: none.