From 9b41b57377dbe75f6216846bbf7d02e42e0962de Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 27 May 2026 14:22:59 +0200 Subject: [PATCH] Add server-initiated-static-delay scenario (#63) Adds the static-delay measurement scenario from Sendspin/conformance#63. Per the audit, the highest-severity bug in the survey is the "parse-but-ignore" failure mode: sendspin-rs and SendspinKit parse `static_delay_ms` from the protocol but never apply it in the playback path. Seven SDKs additionally don't persist the value across restarts. `static-delay-applied` is declared on the aiosendspin server only. Every client case fails fast until each adapter declares the capability based on the SDK's exposed surface. --- README.md | 1 + src/conformance/implementations.py | 1 + src/conformance/scenarios.py | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/README.md b/README.md index ad507b6..d0b6054 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Current scenarios: - `server-initiated-pcm-24bit` (Server initiates connection and client wants 24-bit PCM): start the server first, then the client. The server re-packs the fixture as 24-bit PCM (3-byte packed, little-endian, two's complement). No SDK in the matrix declares the `pcm-24bit-decode` capability yet, so every case currently fails fast and the matrix surfaces the gap (notably the `sendspin-go` SDK has no 24-bit code path per the audit) - `server-initiated-external-source` (Client enters and leaves `external_source` mid-stream): start the server first, then the client. The client transitions to `external_source` and back; the harness verifies the server emits the right `group/update` and `stream/end` and that the previous group is restored. Per the audit only SendspinKit exposes a client-side API; no client in the matrix declares the `external-source-client-api` capability yet, so every case fails fast - `server-initiated-multi-server-arbitration` (Client arbitrates between two connected servers): two servers connect to one client with different `connection_reason` values; the harness verifies the client sends `client/goodbye` with `'another_server'` on the right socket and ends up speaking to the right server per the spec's decision table. Per the audit only sendspin-cpp and sendspin-cli implement these rules; no client in the matrix declares the `multi-server-arbitration` capability yet, so every case fails fast +- `server-initiated-static-delay` (Client applies configured `static_delay_ms`): start the server first, then the client. The client is configured with a non-zero `static_delay_ms`; the server pre-compensates and the harness asserts the client's actual emission time matches `T − static_delay_ms` within the spec's drift tolerance. Per the audit, `sendspin-rs` and `SendspinKit` parse the field but never apply it (highest-severity bug); seven SDKs don't persist across restarts. No client in the matrix declares the `static-delay-applied` capability yet, so every case fails fast ## Current coverage diff --git a/src/conformance/implementations.py b/src/conformance/implementations.py index 85cd7ae..685e2e4 100644 --- a/src/conformance/implementations.py +++ b/src/conformance/implementations.py @@ -68,6 +68,7 @@ "pcm-24bit-decode", "external-source-client-api", "multi-server-arbitration", + "static-delay-applied", ), ), ), diff --git a/src/conformance/scenarios.py b/src/conformance/scenarios.py index d84d81b..abc6687 100644 --- a/src/conformance/scenarios.py +++ b/src/conformance/scenarios.py @@ -196,6 +196,26 @@ ) +SERVER_INITIATED_STATIC_DELAY = ScenarioSpec( + id="server-initiated-static-delay", + display_name="Client applies configured `static_delay_ms`", + description=( + "Start the server first, then the client. The client is configured with a " + "non-zero `static_delay_ms`; the server pre-compensates and the harness " + "asserts the client's actual emission time is `T − static_delay_ms` within " + "the spec's drift tolerance. Per the audit, the highest-severity bug is " + "\"parse-but-ignore\": sendspin-rs and SendspinKit parse the field but " + "never apply it. A configure-restart-replay variant covers the " + "\"forgot-to-persist\" gap that affects seven SDKs." + ), + initiator_role="server", + preferred_codec="pcm", + required_role_families=("player",), + verification_mode="capability-only", + required_capability="static-delay-applied", +) + + SERVER_INITIATED_MULTI_SERVER_ARBITRATION = ScenarioSpec( id="server-initiated-multi-server-arbitration", display_name="Client arbitrates between two connected servers", @@ -269,6 +289,7 @@ SERVER_INITIATED_PCM_24BIT, SERVER_INITIATED_EXTERNAL_SOURCE, SERVER_INITIATED_MULTI_SERVER_ARBITRATION, + SERVER_INITIATED_STATIC_DELAY, ) SCENARIOS: dict[str, ScenarioSpec] = {scenario.id: scenario for scenario in SCENARIO_LIST}