Architecture and deployment boundaries
Document: SPEC-002. Revision: 0.10. Date: 2026-11-05. Lifecycle: Partially approved under delegated decision D-024; V1 resolution sections are active, later scope remains Draft. Supersedes: none.
Logical servers within an instance (D-227)
SPEC-028 adds an API resource scope for independently owned logical servers sharing the existing instance account authority. Logical server UUIDs, membership, channels and settings do not replace the existing deployment server or its discovery identity. Existing clients and media remain on their established protocol; the new resource/text profile advertises neither logical-server navigation nor media support. Instance credentials are never reinterpreted as logical ownership.
Approved video-delivery successor — D-167
D-167/SPEC-024 selects a standard RTP/SFU presentation adapter as the next evaluation target, alongside the existing native adapter. Managed media may route globally; D-216 placement selection continues for controllable application data/control components. Native leases, jurisdiction-restricted Container placement and its cohort lifecycle remain native-specific. The managed adapter declares closure and rate capabilities rather than claiming identical internals or an independently enforced cutoff during control loss. This supersedes universal provider-equivalence requirements only for the managed presentation profile. See SPEC-024.
Confirmed boundaries: R-006–R-008 and R-035–R-037. The designs below are PROPOSED unless explicitly identified as accepted.
Direct voice and relay fan-out (D-086)
D-086 and SPEC-017 supersede relay-only routing for private encrypted calls. The application relay remains prepared for fallback; direct participant connections use authenticated peer signaling and STUN. PEER_STUN_URLS is a deployment JSON-string setting, default [], validated and applied exactly as specified by SPEC-017; no implicit external STUN service. The Cloudflare deployment explicitly selects its STUN endpoint. Existing jurisdictions, generation lifetime and private service bindings remain.
Service separation
Use a serverless application/control service for identity, room state, permissions, messaging, federation, administration, and media authorisation. Use separate media infrastructure for real-time forwarding. Support both managed and self-hosted media, as accepted in D-001.
Cloudflare's documented example uses a Worker for signalling, a Durable Object for room state, and a separate SFU for media. This supports the separation; it does not prove that every required moderation or encryption feature is available from a selected provider. Cloudflare SFU quickstart.
flowchart LR
A[Client from domain A] -->|Account authentication| HA[Home domain A]
B[Client from domain B] -->|Account authentication| HB[Home domain B]
HA <-->|Federated identity and control| RH[Room host domain]
HB <-->|Federated identity and control| RH
A <-->|Proposed room event connection| RH
B <-->|Proposed room event connection| RH
RH -->|Scoped admission and revocation| M[Managed or self-hosted media service]
A <-->|Encrypted media transport| M
B <-->|Encrypted media transport| M
RH --> S[Durable state, object storage and queues]
Direct client connections to a foreign room host are proposed, not implied by the accepted federation model. Home-domain proxying versus direct connections remains Q-09. Both approaches must preserve authentication, policy, and encryption boundaries.
Select browser deployment and origin policy with that route. Under a direct route, a client served by domain A needs an authorised connection to room host B; native-client success alone does not demonstrate web interoperability. The selected profile must define API and realtime-channel origin checks, token audiences, credential attachment, discovery, independent client origins, and refusal behaviour. Never forward reusable home credentials or unrelated cookies to a foreign host. Origin policy is not account authorisation; allowing arbitrary credentialed origins is not a substitute for a defined federation/web contract. The reference server exposes only the origin-policy settings permitted by that profile under SPEC-008.
Authoritative room hosting
Confirmed D-056/D-057 separate an account's identity domain from its service hostname. The existing authority remains sloppyvoice.com at https://chat.sloppyvoice.com/; a second independent instance must use another identity domain. SPEC-010, selected under D-058, defines DNS URI/SRV hints authenticated by identity-origin HTTPS and a full service base URL with an optional path. The alpha.6 clients use HTTPS discovery; the shared DNS-hint parser does not add a native resolver. Federation remains closed. The Cloudflare deployment publishes URI plus explicit SRV unavailability because Worker targets are rewritten to an unusable origin address.
D-003 establishes one hosting domain per room. That host decides room membership, grants, bans, configuration, event acceptance, and ordering. A foreign user's domain authenticates that user; it does not grant administrator rights in another domain's room.
The room's domain/community/room policy hierarchy refers to the hosting domain. Whether a home domain may assert additional restrictions on its own users, and how a host would authenticate/enforce them, remains Q-38. Until that contract is approved, do not treat a foreign role or arbitrary home-domain policy field as host authority. Local user controls can further restrict the user's own capture/playback; they cannot grant publication permission.
Proposed behaviour:
- Participants retain their home identities without manually creating a second login account. The room host may persist a local membership record for the foreign identity.
- Remote administrators act only on privileges explicitly granted by the room host.
- Read caches and backups do not acquire room authority.
- A room-host outage prevents authoritative changes and may interrupt live sessions. Cached history can remain readable with its age shown. There is no automatic independent continuation of the room on another domain.
- Moving a room to another domain requires a separately specified authenticated migration. Silent takeover is forbidden.
- Home-domain outages affect login and renewal. The lifetime of existing foreign sessions must be bounded and specified before implementation.
Federation permits mutual access, not unconditional admission. Operators can close federation, allow selected domains, or apply blocks. Policy conflicts result in a specific refusal; foreign claims never override local limits.
Portable server contract
Keep externally visible protocol semantics independent of the provider's database, queues, identity product, and media SDK. Separate domain logic from adapters with documented capabilities and failure behaviour.
| Capability | Cloudflare reference candidate | Portable contract |
|---|---|---|
| HTTP APIs and signalling | Workers | Authenticated requests, streaming/events, bounded execution |
| Room coordination | Durable Objects | Serialized authoritative room updates and durable state |
| Metadata storage | Durable Object storage and/or D1 | Transactions, indexes, migration and backup semantics |
| Attachments and snapshots | R2 | Private object storage with scoped access and lifecycle |
| Background work | Queues and scheduled triggers | Retryable jobs, idempotency, expiry and dead-letter handling |
| Media | Realtime SFU/TURN candidate | Publish, subscribe, revoke, connectivity, statistics and encryption compatibility |
These are proposed mappings, not a dependency selection or proof of equivalent behaviour. D1, R2, queue limits, regional placement, current pricing, and SDK suitability require evaluation before selection.
Portable deployment does not mean that a self-hosted SFU runs inside ordinary functions. A fully independent deployment can combine a serverless control service with an operator-run media process. The second deployment target and packaging format remain open. No mandatory reference-server account, licence check, or central directory is proposed.
Media provider boundary
Specify an adapter contract before selecting a provider: session creation, participant admission, publish/subscribe permissions, track ownership, per-track or per-participant revocation, short-lived credentials, supported codecs, region selection, metrics, reconnect, and deletion.
Hard mute and removal require timely enforcement on existing media paths, not merely hiding track names from a client. If a provider cannot revoke an existing subscription or publication, a different session topology or provider is required. Opaque E2EE media forwarding must be validated on the actual client/provider combination.
Raw provider credentials remain server-side. Public provider handles are not capabilities by themselves. Any adapter that cannot satisfy required semantics fails conformance even if basic calls work.
The adapter must distinguish admission-token validity from the authority to continue forwarding an established stream. Its contract includes active forwarding-lease expiry, revocation by account/device/track, enforcement evidence, ambiguous outcomes, idempotent operation identity, and recovery after control-service loss. A token checked only at connection setup cannot satisfy bounded continuation. Use the enforcement states in SPEC-008 and lifetimes in SPEC-003; select hard bounds before provider acceptance. Encrypted-room forwarding must remain revocable without possession of content keys. Peer-to-peer direct calls remain a separate Q-12 decision, not an exemption from moderated-room relay enforcement.
Protocol foundation decision
OPEN: adopt an existing protocol/profile, extend it, or define a new protocol. The following is an initial fit assessment, not a selection:
| Candidate | Useful starting point | Work and potential conflict |
|---|---|---|
| XMPP with selected extensions | Domain federation; MUC specifies room administration; Jingle specifies session negotiation | Select an interoperable extension profile; establish Workers-compatible server transport and persistence; add exact PTT/media policy and capability semantics |
| Matrix with a defined application profile | Documented events, identity/federation APIs, device and encryption ecosystem | Standard room federation maintains shared room state across homeservers; assess the conflict with D-003 and serverless operational cost before claiming compatibility |
| New protocol using established transports and cryptography | Can fit authoritative room hosting and serverless operations directly | Must specify and maintain identity, delivery, federation security, device handling, compatibility, and conformance from scratch |
The XMPP and Matrix observations are grounded in XEP-0045, XEP-0166, and the Matrix specification. The fit judgments are project-specific inferences.
A bridge does not make a new protocol natively compatible with another network. Bridges change the confidentiality boundary when they decrypt or translate content and need their own approved specification.
Current feasibility gap: Workers distinguishes Worker-forwarded TCP from general public inbound TCP, which its protocol table still labels as forthcoming. XMPP's core TCP binding therefore needs an ingress/gateway assessment; client WebSocket support alone does not solve standard server-to-server ingress. A permanent gateway is a separate deployment dependency requiring review, not covered automatically by approval of a media service. Workers protocols, XMPP core.
Protocol details required before coding
Resolve discovery and domain delegation; canonical identifiers; client and server authentication; required transports; version and feature negotiation; request/event schemas; idempotency; room ordering; membership and policy epochs; history cursors; pagination; errors; numeric limits; rate limits; resumability; and downgrade behaviour.
Publish a versioned interoperability profile containing mandatory authentication, encryption and codec capabilities; optional-extension handling; unknown-field/action behaviour; browser connection/origin rules; membership transitions; authorisation evidence; and operation outcome schemas. Include positive and negative example exchanges and upgrade compatibility. A configured preference cannot remove the mandatory common baseline. Mandatory profiles and negotiated outcomes are protocol declarations/state, not unconstrained per-deployment algorithm switches.
Proposed control transports are HTTPS requests plus an authenticated persistent event channel. Do not freeze URLs or JSON fields before the foundation decision. WebRTC is a media candidate, not the entire federation or messaging protocol. D-221 note: the implemented event channel is SPEC-027 advisory WebSocket hints over hibernatable Durable Object sockets (Node: ws), with authenticated HTTPS reads authoritative and polling retained; sockets require no persistent worker process.
Configuration
Logical keys below are PROPOSED; they are not selected wire fields or environment variables. Provider-specific property names will be documented when adapters are selected. A deployment must not silently fall back to the reference service for missing endpoints.
| Property | Scope and type | Default and status | Behaviour |
|---|---|---|---|
service.domain | Deployment; canonical domain name | Required — no default, PROPOSED | Identity authority advertised by this deployment; canonicalisation awaits protocol selection. |
service.public_endpoint | Deployment; authenticated public service URL | Required — no default, PROPOSED | Discovery/client entry point; any derivation from the domain must be specified explicitly. |
service.delegation | Historical draft logical name, not a shipped setting | Mechanism selected in SPEC-010/D-058 | D-056 confirms separation of identity from endpoint; SPEC-010 now owns the selected DNS/HTTPS/base-URL contract. |
adapters.control, adapters.coordination, adapters.metadata, adapters.objects, adapters.jobs | Deployment; adapter identifiers and provider bindings | OPEN — no adapter default selected | Bind application capabilities to deployment services; the reference target is Workers but exact mappings are unapproved. |
media.adapter | Deployment; managed/self-hosted adapter identifier | Required — no default, PROPOSED | Chooses the media provider; both classes must be supported. |
media.endpoint | Deployment; provider endpoint | Required — no default, PROPOSED | Media control endpoint; validates against the selected adapter contract. |
media.credential_ref | Deployment; secret-store reference | Required — no default, PROPOSED | Provider credential remains server-side; no credential value belongs in this document or client responses. |
media.region_policy | Domain; region/placement policy | OPEN — not selected | Constrains media placement; unknown/unsupported regions must not be ignored. |
federation.mode | Domain; enum closed, allowlist, open | OPEN — not selected | Controls which foreign domains may establish application relationships. |
federation.allowed_domains, federation.blocked_domains | Domain; sets of canonical domain identities | Empty sets — PROPOSED | Inputs to federation policy; conflict precedence must be specified before use. |
Session/token lifetimes are owned by SPEC-003; queue limits, retries, quotas, and persistence by SPEC-006. Domain identity and adapter changes require explicit migration/restart semantics before implementation; do not treat them as harmless live toggles.
Foreign-room connection routing is an OPEN protocol/deployment contract under Q-09, not a runtime configuration property at this stage. If the chosen profile supports multiple routes, document the actual selector and interoperability rules before adding it to this inventory.
Selected V1 architecture and deployment (D-024/D-025)
For V1, select TypeScript, Workers and one SQLite-backed Durable Object per domain. Co-locate the bounded V1 account and room authority so admission, permissions, operation results and external work can commit atomically. The local development adapter uses SQLite with the same service and SQL repository. This is not a production multi-room scale claim. SPEC-009 selects the versioned HTTPS/polling protocol and direct foreign-host boundary for V2; V1 federation is closed. No community layer or home-domain override of foreign-room policy is introduced.
Select a separate Go/Pion WebRTC data relay, deployable as an independently operated process/container or a managed container service. The same signed lease/revocation contract applies in either deployment. LiveKit/OpenMLS remain evaluated candidates, not dependencies. Third-party managed SFU compatibility is unimplemented; supporting a deployment mode does not claim an actual hosted service was deployed. All client media passes through the relay; peer-to-peer participant routes are absent.
The following replaces the draft logical deployment inventory for V1. Deployment settings are supplied by operator environment/Workers bindings, read at startup, persisted externally by the deployment platform and changed by restart. Local adapters optionally load .dev.vars first; explicitly supplied environment values take precedence. Workers uses platform bindings only. There are no inherited room overrides. Omission is permitted only where an explicit default is listed; null/empty/unknown values fail. Keys outside this schema are not application configuration. Schema version is 1; incompatible state is rejected.
| Code property | Type / validation / default | Ownership and effect |
|---|---|---|
SERVICE_DOMAIN | Required canonical lower-case ASCII DNS domain, or localhost for development; no default | Operator; domain identity, immutable after bootstrap without a migration. |
DATA_JURISDICTION | Cloudflare adapter only: optional string eu or us; omission retains unrestricted placement for existing independent deployments; empty/null/other values reject requests rather than falling back | Operator deployment setting; supplied by Workers binding, read per deployment; us is selected for sloppyvoice.com and shittyshittybangbang.com under D-217; Canada would take precedence but is not a supported jurisdiction. Select before bootstrap. Changing it selects a different object identity; once accounts exist, change it only together with CONTROL_MIGRATION_SOURCE. No room/client override. |
CONTROL_MIGRATION_SOURCE | Cloudflare adapter only: optional string eu or us, different from DATA_JURISDICTION; omission (the default) means no migration; empty/null/equal/other values make the control object unavailable rather than serving | Operator deployment setting under D-217. Names the jurisdiction of the previous domain-v1 object whose database a fresh target copies before serving. Ignored once the target has completed its copy; may then be removed. No room/client override. |
PUBLIC_ORIGIN | Required HTTPS origin, no path/query/userinfo; loopback HTTP permitted for development; no default | Operator; browser/transport origin boundary. Account identity is SERVICE_DOMAIN, not this hostname. |
ALLOWED_ORIGINS | Comma-separated exact origins; omitted means only PUBLIC_ORIGIN; empty/invalid entries rejected | Operator; browser API allowlist, never an authorisation grant. |
RELAY_URL | Required HTTPS origin, loopback HTTP permitted; no default | Operator; external media API. |
MEDIA_SECRET | Required base64url encoding of 32 random bytes; no default | Operator secret shared only by control/relay; signs purpose-separated lease and revocation tokens. |
TOKEN_SECRET | Required base64url encoding of 32 random bytes; no default | Operator secret for command tickets and purpose-separated invitation derivation; never sent to relay/clients. |
BOOTSTRAP_TOKEN | Required base64url encoding of 32 random bytes; no default | Operator secret used only before owner creation; no shared admin password. |
SLOPPY_VOICE_URL | Full HTTPS service base URL under SPEC-010; explicit loopback development allowed. Generic packaged builds have no default; source development uses http://127.0.0.1:8787/. | User environment at startup; precedence over VOICE_CHAT_URL, then package base/origin. Root vault names remain compatible; path mounts bind the full base. Empty/invalid values fail. |
VOICE_CHAT_URL | Legacy alias for SLOPPY_VOICE_URL; used only when the new variable is absent. | Compatibility with the initial alpha; no additional default. |
defaultControlBaseUrl | Optional canonical full base URL under SPEC-010; generic packages omit it. | Build option --server-base-url, mutually exclusive with --server-origin. Takes precedence over legacy package defaultControlOrigin, after environment/launcher overrides. |
defaultControlOrigin | Optional exact HTTPS/loopback origin in packaged resources/app/package.json; omitted by generic builds, never null/empty. | Build-time deployment setting supplied through npm run build:desktop -- --server-origin ORIGIN, validated before packaging and again before navigation. D-050's deployment packages select https://chat.sloppyvoice.com. Used only in packaged execution after environment/launcher overrides; read at startup, not remotely mutable. Changing it requires rebuilding the package; no secrets or account data are included. |
${XDG_CONFIG_HOME:-$HOME/.config}/sloppy-voice/server | Gentoo optional UTF-8 file containing one full service base URL under SPEC-010, optional final newline; no default. | User-owned data read only when neither URL variable is supplied; exported as SLOPPY_VOICE_URL. |
PORT | Local development adapter integer 1–65535; default 8787 | Operator; local HTTP listener on loopback only. |
DATABASE_PATH | Local development SQLite path; default .local/control.sqlite | Operator; parent created by setup, no destructive reset on error. |
RELAY_LISTEN | Relay host:port; default 127.0.0.1:8788 | Operator; deployment ingress binding. |
RELAY_PUBLIC_IP | Optional IPv4 address; omitted means discovered host candidates | Operator; explicit NAT address mapping, no implicit external service. |
RELAY_UDP_PORT | Integer 0–65535; default 0 (OS-assigned); fixed nonzero port for container ingress | Operator; UDP multiplexer bind. |
RELAY_ICE_SERVERS | Optional JSON list of STUN URLs; omitted means empty list | Operator; no public STUN dependency by default. Credentials are not embedded in client assets. |
Workers binds DOMAIN to the domain Durable Object and ASSETS to compiled static assets. These are required platform bindings, not user settings. Relay uses the same PUBLIC_ORIGIN, ALLOWED_ORIGINS and MEDIA_SECRET schema. No reference-domain endpoint or secret fallback exists.
D-216 supersedes D-040's Canada → EU → US order: placement follows the latency of the expected client population, and us is permitted and preferred for the current North American cohort. D-217 moves both hosted control objects to us using the migration below. Current Cloudflare Durable Object jurisdictions and Container constraints support eu and us restrictions, but no Canada-specific restriction. The control adapter selects the jurisdictional namespace before deriving domain-v1; both lookup operations use that namespace. It checks actual object jurisdiction before serving and reports it in the X-Sloppy-Control-Jurisdiction response header for deployment verification. Objects outside the selected jurisdiction stop their recurring alarms; their data is retained for migration, not silently deleted. The initial cutover is allowed only after verifying that bootstrap is unused. This restriction covers the object's execution/storage; global Worker ingress, TURN routing and Cloudflare billing/debug metadata remain outside this claim.
Control database migration between jurisdictions (D-217)
A control object is active only when its own jurisdiction equals DATA_JURISDICTION. Only the active object builds the service; an inactive object serves no API request, deletes its own alarm and performs no writes. When CONTROL_MIGRATION_SOURCE is set, the active target opens as follows before any request is served:
- Empty target: read the source object's table and index definitions over private Durable Object RPC, create them, then copy every row of every table in rowid order, preserving rowids. Pages are bounded to 256 rows or about 4,000,000 characters of encoded data. Each page commits together with its progress cursor in the target's
_sloppy_migrationtable. - Partly copied target: resume from the last committed page. A partly copied target never serves, even if the setting has been removed.
- Copy finished: compare row counts per table and a SHA-256 chain over schema and every row on both sides. On a match, mark the copy complete and open the service normally. On a mismatch, discard the copied tables, log the event and remain unavailable; the next request starts again.
- Target holding only state generated at object start (only the
recordsandmessagestables, no message, nouserrecord and nometa/ownerrecord) and no migration record: discard it and copy. An older code version can create this state when it briefly serves the new target during a deployment. - Target holding any other data without a migration record: refuse to serve rather than overwrite or ignore it.
The source exports only while it is inactive and not itself partly copied, so the snapshot cannot change during the copy. Export methods are RPC-only and reachable only through the DOMAIN namespace binding; there is no public endpoint or migration secret. While the target is unavailable, the Worker returns 503 with retry-after: 5 and cache-control: no-store. Copy progress and the final counts and digest are logged as control-migration events without row contents.
The source database is retained unchanged. To roll back, redeploy the previous DATA_JURISDICTION without CONTROL_MIGRATION_SOURCE; the old object resumes with its state as of the copy, and writes made after the move are lost. Moving back into a target that has already completed a copy requires a new decision, because that target would serve its own older data.
Both hosted control configurations set the Durable Object code_update_strategy to immediate, so an object adopts a new version as soon as it is deployed. Without it, a busy object kept its previous version for 300 seconds while the Worker used the new one, which made the first D-217 attempt fail. Use immediate for any deployment that changes placement or migration settings. The setting lives in Wrangler configuration and has no application default. The migration copies Durable Object SQLite only; this control service has no KV, R2 or D1 state.
Cloudflare reachability prototype (D-034)
D-042 requires the eventual service to accommodate geographic disparity. One relay path is permitted for V1 testing; it is not a permanent globally shared relay constraint. Keep the domain/room authority boundary separate from media placement. Future regional selection should allow different rooms or voice sessions to use suitable locations while retaining one coherent permission/revision authority for each room. Exact selection signals, operator/residency preferences, participant relocation, handover, capacity, failover and cross-region forwarding remain OPEN for a later specification. A location change must preserve the existing encryption, consent and bounded-authority guarantees. No automatic regional failover or multi-region relay implementation is selected by this statement.
D-034 authorises an isolated Containers plus TURN test using the bounded prototype contract. That document owns its temporary configuration and deadlines. The application architecture, production relay settings and SPEC-009 media authority remain unchanged. The prototype selects relay-only ICE candidates and retains the same Go/Pion per-packet enforcement; successful local testing is not proof of Cloudflare reachability or production readiness.
Selected Cloudflare alpha adapter (D-043)
The bounded alpha policy is selected for the first hosted cohort. Its configuration table's bounded values are authoritative here and supersede that table's historical PROPOSED labels: MEDIA_ADAPTER is direct (omitted default) or cloudflare-turn; MEDIA_ENABLED is a boolean binding defaulting to false for the Cloudflare adapter; TURN_KEY_ID and secret TURN_API_TOKEN are required with no default on the media Worker; TURN_RELAY_TRANSPORT is udp (default) or tls. Empty/null/unknown values fail. The control Worker needs no TURN API token; it uses the private MEDIA_GATEWAY service binding to the media Worker's MediaControl entrypoint. Its MEDIA binding addresses one SQLite Durable Object media-v1 in the selected jurisdiction. The production TURN key is separate from the test probe. Public GET /health never starts a Container.
D-216 media placement: the media Worker's DATA_JURISDICTION is required, eu or us, with no default; omission, empty, null, upper-case or other values (including fedramp) reject configuration with 503 before Container or TURN work. The MEDIA namespace uses that jurisdiction, the Container uses the same constraints.jurisdiction, and an object reporting any other or no jurisdiction rejects preparation and forwarding. Unrestricted media placement is not permitted because the object would be created near its first caller, normally the control object. The setting is operator deployment configuration read at startup; changing it selects a different media-v1 object and ends active calls without migrating stored data, because generation state is ephemeral. Independent direct hosting is unaffected.
Keep one lite Container in the media deployment's selected jurisdiction, maximum one instance, a 3600-second generation lifetime under D-054, 300-second warning/closing-admission margin, 4200-second provider credential TTL (the existing ten-minute headroom), 60-second inactivity timeout, and a 180-second preparation deadline. These are fixed cohort policy, not user/room overrides. Preparation is limited to two new attempts per account/device and ten per domain per fixed UTC minute, with one in flight per device. Polls of the same attempt do not consume a new-attempt allowance. Deployment settings are read at startup and changes require a generation transition; identity/storage settings retain their existing migration rules. Disabling media rejects preparation and grants immediately; the deployment procedure calls the disabled gateway health endpoint to destroy the current Container, with idle/deadline shutdown as independent backstops.
The native Container receives required RELAY_URL, PUBLIC_ORIGIN, MEDIA_SECRET, RELAY_GENERATION (UUID), RELAY_UNTIL and TURN_EXPIRES_AT (canonical decimal Unix seconds), and TURN_RELAY_JSON (one authenticated TURN server with the exact selected URL). No defaults apply. The future deadline is at most 3600 seconds away; asserted credential expiry is at least 60 seconds later and at most 4200 seconds away. Empty, malformed or expired input rejects startup with redacted errors. These values are internal generation state, supplied by the gateway; the long-lived TURN API token and participant content keys never enter the image/process. The listener is fixed at internal HTTP port 8788 and the process runs unprivileged. Independent direct hosting retains its existing schema. Each native process also generates a fresh internal boot UUID, returned only by its internal health response. The gateway stores it during preparation and supplies it in the internal X-Sloppy-Relay-Boot header on every forwarded request; the process rejects a mismatch. Admission verifies the current boot identity. This prevents a rapid process restart with unchanged environment/Container identity from accepting fresh grants for an old generation. The boot UUID is internal state, not an operator setting or client field.
Public website deployment (D-051)
The separate sloppy-voice-site Worker serves www.sloppyvoice.com and the otherwise unused apex sloppyvoice.com, redirecting the apex to www. Verify existing DNS before assigning either hostname. It has no control/media service or account database binding. The static website and manifest-listed desktop downloads are public. D-061 adds the verified Linux archive to the existing distribution service for automatic Gentoo fetching. R2 activation requires the human's separate acceptance of its recurring terms; prepare and test the site before requesting it.
Configuration is build/deployment-owned in wrangler.site.jsonc: ASSETS is the required static asset binding from site/public; DOWNLOADS is the required R2 binding to sloppy-voice-downloads in jurisdiction eu, Standard storage. No secret or public-write API is configured. The existing EU jurisdiction was selected under D-040 because R2 provides no Canada-specific jurisdiction, and remains until a migration is approved under D-216; global delivery is not restricted to the storage region. The Worker uses compatibility date 2026-10-18, a 50 ms CPU limit per invocation, and disabled workers.dev/preview URLs. Asset routing uses run_worker_first: true, html_handling: drop-trailing-slash and not_found_handling: 404-page; the build output is the sole static asset directory. These deployment choices change only through an operator deployment. Generic app/server origin defaults are unchanged.
site/release.json (Windows installer) and site/linux-release.json (Linux archive, D-061) are release manifests, schema 1: exact version, filename, positive integer byte length, lower-case hexadecimal SHA-256 and matching versioned object key. These are build-time public release declarations, not end-user settings. The original Windows release was 0.1.0-alpha.3; the first published Linux archive is 0.1.0-alpha.6. Each manifest is required, with no implicit release or URL fallback; the current pair uses the same version. The build verifies both local files against their manifests before generating pages and combined checksum text. Publish new versions through new manifest/object keys, never silently replace different bytes at an existing versioned URL. Retain each Linux manifest entry and download route while its ebuild remains available; later releases extend the allowlist rather than remove supported distfiles. HTML revalidates; versioned downloads have a fixed 86400-second public cache lifetime. Unknown files and unavailable/mismatched objects are not cached.
site/windows-portable-release.json (D-155) is an additional required schema-1 build-time manifest using the same validation rules, with filename sloppy-voice-${version}-windows-x64.zip. There is no default or fallback. Its version is independent of the retained installer/Linux pair. Verify the ZIP bytes before building the page and include it in SHA256SUMS.txt and the exact object allowlist; archived ZIP entries use the same immutable retention rules.
site/linux-flatpak-release.json (D-160) is a required schema-1 release manifest with filename sloppy-voice-${version}-linux-x64.flatpak. Its version is independent of other package formats. Exact positive bytes, lowercase SHA-256 and matching immutable release key are required; there is no default/fallback. Build verification reads the local bundle, and publication adds it to the same checksum file and exact download allowlist. Flatpak runtime/app metadata is owned by SPEC-005.
site/android-release.json (D-170) is a required schema-1 manifest for the existing experimental APK. Its independent version has the form X.Y.Z-alpha.N-android.M (positive Android build number); filename is sloppy-voice-${version}.apk, key releases/${version}/${filename}, bytes a positive safe integer and SHA-256 64 lowercase hexadecimal characters. No default or fallback. Verify the exact local APK before page generation; include it in SHA256SUMS.txt and the download allowlist. Use a new key for changed bytes. Android signing/platform behavior remains owned by SPEC-005.
The site exposes only GET/HEAD, enumerated release objects and static assets. It adds no account, analytics, cookie, upload, auto-update or media capability. Client presentation belongs to SPEC-005 and HTTP download behavior to SPEC-008.
Testing
D-170 build checks reject missing/malformed Android manifests and byte/hash mismatches; generated links/checksums use the verified independent Android version. Live publication checks exact public APK length/hash and the retained desktop URLs.
D-210 unit tests require site/archived-releases.json to contain exactly the current Gentoo source manifest, and require a superseded versioned download URL to return 404 without reaching storage. Live publication checks that each current download returns 200 and a superseded one returns 404.
Unit tests for discovery, host routing, adapter selection, and federation policy should cover:
- Required configuration missing, invalid endpoints, unsupported adapters, and invalid combinations produce explicit validation errors; errors redact secrets and never select an implicit reference-server dependency.
- Domain canonicalisation and discovery validation follow the chosen protocol, including delegation, malicious redirects/private-network destinations, timeouts, and untrusted foreign responses.
- A room resolves to exactly its authoritative host; cached replicas and foreign administrator claims do not acquire authority.
- Each federation mode and approved allow/block precedence are applied consistently to new and existing operations according to policy.
- Adapter capability validation rejects a provider lacking required encrypted forwarding or active-stream revocation; it does not mark such a provider usable because session creation succeeded.
- Provider failures, duplicate callbacks, and revoked/expired media grants produce defined state transitions without accepting an unauthorised stream.
- Reconstructed state after simulated worker restart preserves accepted room authority; stale local memory is not treated as authoritative.
- Browser routing policy validates independent client origins, API/realtime requests, token audiences and credential isolation; host-domain policy is not inferred from a foreign role.
- Active-stream leases expire even after admission succeeds; partial revocation, duplicate/late adapter results and control partitions never manufacture an enforcement-complete result.
D-217 unit tests copy a multi-page database with rowid gaps, blobs, indexes and large rows into an empty target and compare digests. They cover resumption after a failed page, mismatch discard, replacement of generated-only target state, populated-target refusal (owner marker or message), refusal to export from a partly copied source, unsafe table names and cursors, and CONTROL_MIGRATION_SOURCE validation. A hosted rehearsal on a disposable Worker moves a seeded EU object to us, then rolls back.
D-216 unit tests cover media configuration for both permitted jurisdictions and each rejected value, and EU/US/unrestricted object mismatches without Container start or TURN issuance.
Use fake discovery, storage, and media adapters with controlled failures. Actual Workers execution, cross-domain federation, and provider enforcement require separate integration tests.
Acceptance criteria
- D-210: during alpha, the live website serves only each platform's current download and the current Gentoo source archive; any earlier versioned release URL returns 404, and both Gentoo overlays contain only the current ebuild.
- D-217: after the move, each hosted control service reports
X-Sloppy-Control-Jurisdiction: usand the same server ID,bootstrap: falsestate and signed-in account data as before; the migration log reports equal source and target counts and digest. - D-217: an interrupted copy resumes without duplicate rows, a digest mismatch leaves the target unavailable and empty, a populated target refuses migration, and the source is unchanged.
- D-216: media configuration accepts
euandus, rejects omitted, empty, null and other values before Container or TURN work, and rejects an object whose jurisdiction differs from the configured value; control namespace selection retainseu,usand omission. - D-170: the required Android manifest validates its independent APK version and exact artifact bytes; the page, checksum file and immutable public route agree.
- Run the reference control service on Workers with a separate media service and establish room communication; confirm provider secrets are absent from client-visible responses and logs.
- Configure two distinct domains and demonstrate remote users joining rooms hosted by either domain using their existing identities; reject a foreign domain's attempt to grant itself host authority. See A-02.
- Stop a room host and verify that no other domain silently accepts authoritative changes for that room; observed reconnect/failure behaviour matches the approved outage contract.
- Demonstrate managed and self-hosted media with equivalent required publication, subscription, encryption, and hard-mute behaviour; test revocation on already-established media paths.
- Demonstrate the agreed alternative control deployment against the same applicable conformance cases before claiming full portability under A-03.
- Record the protocol selection comparison and its resolution of authoritative room hosting, serverless ingress, encryption, and permission constraints before implementing the chosen protocol. See A-21.
- Validate a deployment with missing/invalid required settings and confirm an explicit error rather than silent adapter, endpoint, or security fallback.
- From a supported web origin on A, join a room on B under the approved routing/origin profile; test independent client origins, wrong-audience credentials and disallowed origins without exposing home-domain credentials. See A-31.
- Partition control from media after a stream is established; forwarding terminates by the approved active-lease hard bound even when the admission token was valid at setup. See A-29.
D-063 release retention adds site/archived-releases.json, an explicit array of previously published schema-1 release manifests. The download Worker allowlist includes the current pair and these immutable archived entries. Unknown objects remain inaccessible. New publication retains old Linux entries while corresponding ebuilds are available, avoiding an authenticated/private-repository dependency for Portage.
CONFIRMED — D-210 (2026-11-03), alpha releases only: keep only the most recent build available. site/archived-releases.json lists only the current Gentoo source archive (site/gentoo-source-release.json's entry); each platform's current manifest supplies the other downloads. Publishing a newer build removes its predecessor from the allowlist, so earlier versioned URLs return 404. Retired R2 objects are not deleted. This supersedes the D-063 retention above until a later decision, expected when releases leave alpha.