Contracts and compatibility
Package versions, HTTP versions, WebSocket frame versions, and the shared transport version are separate. Negotiate the contract your consumer implements.
Discover and negotiate
GET /api/v1/contracts returns service version, contract lifecycle state, and the
compatibility matrix. Client::contracts() and dm contracts expose the same
information. In a sandbox, supply its app secret; lifecycle counters are isolated.
| Consumer | HTTP contract | Standalone frames | Shared transport |
|---|---|---|---|
| DM client / CLI 0.5.x | 1 | 3 | 1 |
| DM client / CLI 0.4.x | 1 | 3 | Not used; standalone connection retained |
HTTP clients send X-DM-Contract-Version: 1. Standalone WebSocket clients may
send X-DM-Protocol-Version: 3; shared clients use version 1. Omitting a version
retains the existing default. Unsupported or repeated version headers are rejected
with 406 and compatibility information. Responses identify the selected versions.
Shared WebSocket v1
Open /api/v1/ws/shared once per backend. The server immediately sends:
{"type":"prewarmed","data":{"protocol_version":1,"max_subscriptions":64}}
Authenticate each subscription inside the encrypted connection, never in a URL:
{"type":"subscribe","data":{"subscription_id":"local-profile-id","token":"ACCESS-TOKEN","organization_id":"tos","actor_id":"cos:tos","device_id":"stable-device","testing_key":null,"testing_generation":null}}
For a sandbox, testing_key is its test app secret. Only that authenticated
actor can use the subscription. Responses and commands use:
{"type":"channel","data":{"subscription_id":"local-profile-id","frame":{"type":"pong","data":{"ping_id":"PING-ID"}}}}
The inner frame is an unchanged WebSocket v3 frame. Each subscription receives
its own ready, cursors, and generation. The outer transport also has its own
ping/pong. Respond immediately at the same layer as the incoming ping.
unsubscribe removes one subscription. Authentication failures do not confer
access to any other subscription. The connection is bounded to 64 subscriptions;
slow or overflowing clients disconnect and recover through durable replay.
Backward compatibility
Existing HTTP v1 and standalone WebSocket v3 consumers keep their shapes. Shared transport is an additive endpoint. Optional discovery fields do not change existing mandatory fields. Breaking changes require a new contract, explicit compatibility documentation, and consumer-driven tests; package SemVer alone is not a protocol negotiation mechanism.
The common HTTP and v3 envelope has exactly type and data, with message
metadata inside data. Local callbacks intentionally have type, data, and
metadata, as specified in the relay guide.
Deprecation and sunset
The contract_versions table exists separately in each plane. It tracks state,
request count, last request, deprecation time, and sunset time. This is local
contract lifecycle bookkeeping, not Space Station telemetry.
An operator first marks a supported predecessor deprecated and sets
deprecated_at, after publishing a tested replacement. A deprecated contract is
sunset after seven complete days with no requests, measured from the later
of deprecation and last use. Never-used deprecated contracts use their introduction
time and deprecation date. Active contracts are never retired just because a
new deployment has no traffic. Open streams refresh last-use during heartbeats.
The delivery worker performs retirement each minute; admission and discovery
also check retirement. A sunset contract returns 410 and is never revived by
late traffic. Keep its compatibility record for consumers.
Use a migration or operator database procedure to deprecate a release. This control is deliberately not an unauthenticated application endpoint. Example:
UPDATE contract_versions
SET status = 'deprecated', deprecated_at = clock_timestamp()
WHERE family = 'http' AND version = <REPLACED-VERSION> AND status = 'active';
Consumer-driven tests
tests/wire_contract.rs sends SDK-generated bytes to backend extractors and
round-trips realtime frames independently. Contract tests cover unsupported and
duplicate headers; integration tests exercise lifecycle and multiplexed routing.
Run cargo test --workspace --all-targets --all-features before release.