Skip to content

feat(conformance): add client-initiated metadata, artwork and controller scenarios - #144

Merged
chrisuthe merged 6 commits into
Sendspin:mainfrom
chrisuthe:chrisuthe/task/add-the-missing-client-initiated-scenarios-and
Oct 7, 2026
Merged

chrisuthe merged 6 commits into
Sendspin:mainfrom
chrisuthe:chrisuthe/task/add-the-missing-client-initiated-scenarios-and

Conversation

@chrisuthe

@chrisuthe chrisuthe commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Part of #24.

RC1 connection.md says "Servers MUST support both methods described below" (checked at the 1.0.0-rc1 tag). The matrix opened a client-initiated connection for the player role only. This adds the same coverage for metadata, artwork and controller.

What changes

  • Three new scenarios, client-initiated-metadata, client-initiated-artwork and client-initiated-controller. Each repeats its server-initiated sibling with the client opening the connection: same verification mode, role family and CLI arguments. Each is a new id at scenario_revision=1. No existing scenario's revision or assertions change, and runner.py is untouched.
  • These three ids were already dispatched on by the aiosendspin, sendspin-go, sendspin-dotnet, sendspin-rs and sendspin-jvm adapters with no ScenarioSpec behind them. They are now real scenarios, so those branches are no longer dead.
  • No capability flag is added or set.

Adapter branches, checked by running them

Every SDK is a fresh clone of its upstream default branch (spec at 1.0.0-rc1).

Adapter Result
aiosendspin server and client Existing branches are sound: the three scenarios pass aiosendspin to aiosendspin.
sendspin-dotnet client Existing branches are sound: all three pass against aiosendspin.
SendspinKit client Matches by substring, no change needed. Metadata and controller pass; artwork times out exactly as server-initiated-artwork does on the live baseline.
sendspin-cpp client Had no branch. The three ids are added to its existing predicates; all three pass against aiosendspin.
sendspin-js client Had no branch. Metadata and controller ids added; both pass. It declares no artwork role family, so client-initiated-artwork is harness-stamped unsupported and never launched, as for the sibling.
sendspin-go server Existing branches are sound: one session routine serves both directions.
sendspin-go client, sendspin-rs client Existing branches were not sound. Their outbound path only drives a player session, so a metadata, artwork or controller case reported "client received zero audio chunks". They now fail fast with "Harness gap, not a protocol result: … has no client-initiated path for scenario …".
sendspin-jvm client Not built or run: no JDK on the machine I used. One blind change: its client-initiated-controller branch sent the command on the first controller state, and the aiosendspin server sets repeat and shuffle before it lists supported commands, so the command could go out before it was advertised and be rejected. It now waits for supported_commands to list the command, the same loop the server-initiated branch uses a few lines above it. Unverified until CI builds it.

Matrix growth

The matrix grows by 48 rows (3 scenarios x 16 pairs), from 192 on the live baseline to 240. Re-measured after merging main at 55c4d0b, so every figure here includes the time_exchange, group/update, activation and available-gate verdicts on all 48 new rows; the figures did not change. Measured locally on macOS against the live index.json: detect_regressions.py reports no regressions (the live baseline still carries pre-#143 revisions, so it exempts every existing cell; the statement that follows is a direct status comparison, not that script's verdict), and of the 192 existing rows only the two SendspinKit cells listed further down change status, both from failed to passed.

Scenario Passed Failed
client-initiated-metadata 5 11
client-initiated-controller 5 11
client-initiated-artwork 3 13

Expected status per cell:

  • aiosendspin server, passing: aiosendspin, sendspin-cpp and sendspin-dotnet clients on all three; sendspin-js and SendspinKit on metadata and controller.
  • aiosendspin server, failing: sendspin-go and sendspin-rs (harness gap, above); sendspin-js on artwork (unsupported); SendspinKit on artwork (server times out waiting for the client, as in the sibling); sendspin-jvm (see below).
  • sendspin-go server, all 8 failing per scenario: five clients hit "expected client/hello, got client/init", the same pre-RC1 handshake failure as every other sendspin-go server row; sendspin-go and sendspin-rs are the harness gap; sendspin-jvm as below.
  • sendspin-jvm, 6 cells: not measured. I expect them red, since every sendspin-jvm cell on the live baseline is red, including client-initiated-pcm. The client-initiated-controller cells for sendspin-jvm do not measure sendspin-jvm's conformance in this PR: the adapter branch behind them was changed without being run, so whatever colour they show, read it as the state of an untested adapter until someone runs it with a JDK.

Retired client-initiated-request-format-* ids

These two ids have had no ScenarioSpec since the client-initiated-state-format-* scenarios replaced them, but three adapters still dispatched on them.

  • sendspin-rs: removed. The branch sent stream/request-format, a message RC1 does not have, and nothing else used it. Its cells are unchanged.
  • SendspinKit: re-keyed, not removed. The branch behind those ids was not dead code. It sends the RC1 client/state format preference and writes the renegotiation block the format-preference comparison reads, so it now answers to client-initiated-state-format-pcm and -flac. The change is the two id strings in one file.
  • sendspin-jvm: left untouched, no JDK to build it with.

After this change no adapter dispatches on a scenario id that no ScenarioSpec declares, except client-initiated-request-format-pcm and -flac in the sendspin-jvm adapter. Checked by scanning every tracked adapter file for scenario-id literals against the registry.

Cell movement from the SendspinKit change

Two existing cells go failed to passed. Every other SendspinKit cell keeps its live-baseline status (all 30 SendspinKit rows re-run).

Cell Live baseline This branch
client-initiated-state-format-pcm, aiosendspin to SendspinKit failed: "Server received no player format preference in client/state" passed: "Renegotiated pcm/8000Hz/24bit/1ch -> pcm/8000Hz/16bit/1ch"
client-initiated-state-format-flac, aiosendspin to SendspinKit failed, same reason passed: "Renegotiated pcm/8000Hz/16bit/1ch -> flac/8000Hz/16bit/1ch"

No scenario_revision is bumped: only an adapter changed, not any scenario's stimulus or assertions.

Both passes rest on what the aiosendspin server recorded, not on the client's account of itself:

  • the server adapter's format_preference.received, taken from the client/state it received, is the target format (16-bit PCM; FLAC);
  • the server's own stream, its last stream/start, is in that format;
  • the server's trace shows two stream/start messages, each after a client/state reporting available.

The comparison requires the first two before it reads anything the client reported. The client summary's request_message_type: "client/state" is a fixed string in the adapter and is not what the verdict relies on.

Found in review

My first sendspin-rs fail-fast guard let through only the player ids it listed, which also caught client-initiated-state-format-pcm and -flac and would have turned two real player cases into harness gaps. It is narrowed to the metadata, controller and artwork predicates, and those two cells were re-run to confirm they reach the SDK's outbound path again.

Not in this PR

  • Client-initiated FLAC and OPUS variants.
  • A real outbound path for the non-player roles in the sendspin-go and sendspin-rs client adapters.

Checks

  • python -m unittest discover -s tests: 344 tests pass on the merged tree.
  • The 13 existing scenarios carry exactly the revisions on main; the three new ones are at 1.
  • The available gate runs on client-initiated-artwork: test_only_scenarios_that_open_a_stream_are_judged requires it to be judged, and the aiosendspin server's summary for that case records its client/state then artwork stream/start trace.
  • Report renders; read the client-initiated-artwork aiosendspin to sendspin-cpp case page and a harness-gap case page.
  • gofmt, go vet and cargo fmt --check are clean for the two adapters touched.

…ler scenarios

RC1 requires every server to support client-initiated connections, but the
matrix only opened one for the player role. Declare the three scenarios the
adapters already dispatched on without a ScenarioSpec behind them, each a
client-initiated repeat of its server-initiated sibling at scenario_revision 1.

The sendspin-cpp and sendspin-js client adapters take the new ids on their
existing outbound path. The sendspin-go and sendspin-rs client adapters only
drive a player session outbound, so they fail fast as a harness gap instead of
reporting a missing audio stream.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The newly activated JVM controller path can send commands before support is advertised, producing a harness-induced failure.

2 open findings
What changed in this PR

Expands the conformance matrix with client-initiated metadata, artwork, and controller coverage.

Changes:

  • Registers three new scenarios with matching sibling configuration.
  • Updates adapters for new scenario dispatch or explicit harness-gap failures.
  • Adds documentation and scenario-invariant tests.
File Description
src/​conformance/​scenarios.py Registers scenarios and shares CLI argument constants.
README.md Documents scenarios and adapter coverage.
adapters/​sendspin-cpp/​client/​main.cpp Recognizes new scenario IDs.
adapters/​sendspin-go/​client/​main.go Fails unsupported outbound paths explicitly.
adapters/​sendspin-js/​client.mjs Adds metadata and controller dispatch.
adapters/​sendspin-rs/​client/​src/​main.rs Reports missing outbound role paths.
tests/​test_client_initiated_role_scenarios.py Verifies parity with server-initiated siblings.
tests/​test_stream_start_gate.py Updates streamed-role expectations.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/conformance/scenarios.py
Comment thread README.md Outdated
The client-initiated-request-format-* scenarios no longer exist, and the
branch behind them sent stream/request-format, a message RC1 does not have.
…he declared scenarios

The branch that sends the client/state format preference answered to the
retired client-initiated-request-format-* ids, so the scenarios that replaced
them never reached it and SendspinKit failed both.
…re sending it client-initiated

The server sets repeat and shuffle before it lists supported commands, so the
first controller state can arrive without the command the scenario sends.
…the-missing-client-initiated-scenarios-and

# Conflicts:
#	src/conformance/scenarios.py
@chrisuthe
chrisuthe marked this pull request as ready for review October 7, 2026 18:26
@chrisuthe
chrisuthe merged commit 27f7bce into Sendspin:main Oct 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants