# Domain identity and service discovery

Document: SPEC-010. Revision: 0.1. Date: 2026-10-19. Lifecycle: **Approved for implementation under D-058**, implementing D-056/D-057 and R-074–R-076. Supersedes: the unresolved discovery/delegation mechanism in SPEC-002 for this profile. Shipped `voice-chat/1` remains governed by SPEC-009.

## Identity and location

The account address is `username@identity-domain`. An omitted suffix means the selected account service's canonical identity domain, not its web hostname, parent DNS zone, or the room currently being viewed. SPEC-003 owns address interpretation and account identity. An address resembles an email address but does not imply a mailbox, email verification or an XMPP account.

D-057 keeps existing accounts at `@sloppyvoice.com`, served by `https://chat.sloppyvoice.com/`. Preserve account/device UUIDs, passwords, sessions, keys, permissions and history. No identity migration or alias to `@chat.sloppyvoice.com` is authorised. D-109 resolves Q-49 with the independent identity domain `shittyshittybangbang.com`, served by `https://shittyshittybangbang.com/`, with separate storage, application secrets and bootstrap. `alpha.sloppyvoice.com` below remains an illustration.

The identity domain identifies an account authority. The service base URL identifies an HTTPS application location, including an optional path prefix. A domain may delegate its endpoint without renaming accounts. Two independent authorities cannot simultaneously own the same identity domain. DNS changes alone neither migrate stored identities nor transfer authority, keys or history.

## Discovery records

Use the locally defined experimental service label `_sloppy-voice._tcp.<identity-domain>`. This label is not an IANA registration or an XMPP service. Do not publish `_xmpp-*` records for this protocol. Review registration before claiming general standards interoperability.

The preferred DNS record is URI (type 256, RFC 7553), which contains the complete HTTPS base URL. SRV (RFC 2782) is an optional root-path fallback for deployments with a reachable direct target. SRV contains priority, weight, port and hostname; it cannot contain `/sloppyvoice/` or any other URL path. SVCB/HTTPS transport records do not replace this application identity-to-path mapping.

Current Cloudflare records, TTL 300 seconds, DNS only:

```dns
_sloppy-voice._tcp.sloppyvoice.com. 300 IN URI 0 0 "https://chat.sloppyvoice.com/"
_sloppy-voice._tcp.sloppyvoice.com. 300 IN SRV 0 0 0 .
```

The SRV dot target explicitly says there is no SRV service. It must not be converted into a hostname or retried at an invented address. In this profile, URI discovery takes precedence; an SRV-only client cannot discover this installation. The independent HTTPS discovery mechanism below is the client discovery mechanism.

The negative SRV record is deliberate. On 2026-10-19, Cloudflare rewrote an SRV target of `chat.sloppyvoice.com` to `_dc-srv.9bcd65b345ab._sloppy-voice._tcp.sloppyvoice.com`, which returned `AAAA 100::` and no A record. That target cannot reach the Worker. Cloudflare documents rewriting SRV targets to bypass its proxy; a Worker has no conventional origin to expose. The trial record was corrected to the dot target. Do not disable the Worker's proxy or pin Cloudflare anycast IP addresses to make SRV appear to work.

For a direct server, an illustrative valid record is `0 0 443 voice.example.net.` at the identity domain's service label, provided the target has usable A/AAAA records, is not a CNAME, and agrees with the authenticated descriptor. A DNS SRV fallback constructs a root HTTPS URL only; a path deployment must publish URI. The initial profile permits one active URI endpoint, priority/weight `0 0`; multi-endpoint failover and geographic selection require a later authenticated policy. A control endpoint record does not select a media relay or change the Canada → EU → US preference.

## Authenticated HTTPS discovery

DNS supplies discoverable routing hints. Before sending any password, signed login proof, bearer token or invitation key to a discovered location, every client must fetch:

```text
https://<identity-domain>/.well-known/sloppy-voice
```

This is at the identity origin's root, even if the application is mounted below a path. The experimental well-known suffix requires registration review before public interoperability claims. The operator must serve this path directly with valid TLS for the identity domain; an apex-to-www website redirect must exempt it. Do not follow redirects for discovery or use a document returned from a different identity host. TLS also validates the endpoint's hostname when connecting to it. A valid certificate for an arbitrary DNS-supplied target alone does not authorise it to receive an identity domain's credentials.

Version 1 response, `200`, `Content-Type: application/json`, UTF-8:

```json
{
  "discoveryVersion": 1,
  "domain": "sloppyvoice.com",
  "baseUrl": "https://chat.sloppyvoice.com/",
  "profiles": ["voice-chat/1"],
  "federation": {"mode": "closed"}
}
```

All five fields are required. `domain` must equal the canonical requested identity domain. `profiles` is a nonempty, duplicate-free list of supported protocol names; no common supported profile is an explicit incompatibility. `federation.mode` is exactly `closed` in this first discovery profile. Later federation modes, credentials and server-to-server endpoints need their own versioned contract; do not advertise them now. Unknown fields can be ignored; unknown versions, missing fields, wrong types, duplicate JSON keys and unsupported security values fail closed. The descriptor carries no secrets, usernames, member lists, private allowlists or content keys.

The public descriptor uses `Access-Control-Allow-Origin: *` without credential allowance; fetch it with credentials omitted and no referrer. This does not loosen the separate authenticated API origin policy. Browser clients use this document directly and do not depend on raw DNS access or a new public DNS-proxy API. Native clients and future federation adapters may also query URI/SRV. They must still authenticate the identity mapping using this document. DNSSEC is recommended for DNS publication; a validating resolver's bogus result must not be treated as a trusted alternative. An unsigned record is never sufficient credential-delegation proof.

Resolution sequence:

1. Parse the address using SPEC-003 and establish its identity domain. Preserve the distinction between the chosen local account service and a foreign room host.
2. Fetch and validate the identity-origin descriptor. An unsupported/missing descriptor is an explicit discovery failure; do not guess `chat.`, reuse the parent domain, scrape a login page or navigate to an invitation's arbitrary origin.
3. Use its validated `baseUrl`. Available DNS hints should agree. A stale/mismatched hint is diagnostic only and cannot replace the authenticated URL; URI absence, SRV dot or lack of browser DNS APIs does not defeat a valid descriptor. DNS is not a second permission authority.
4. Fetch `api/info` relative to that base without credentials. The discovery-capable response retains SPEC-009's existing fields and adds `baseUrl` (string) and `capabilities` (array of unique strings containing `identity-discovery/1`). Require the same canonical `domain` and `baseUrl`, a compatible `profile`, and `origin` equal to the base URL's origin. Any mismatch stops login; never repair it by changing the requested account domain.
5. Present the fully qualified identity and service location. Attach credentials only to the selected, validated service and intended route. Selecting another domain is explicit; a foreign suffix must never be stripped and submitted to the local login endpoint.

Malformed responses, invalid TLS, timeouts, redirects, unsupported profiles or wrong domains are failures, not triggers for insecure fallback. The descriptor may be cached only within the bounds in Configuration. Never use an expired cache to send credentials after a failed refresh. A changed base URL triggers an explicit reconnect/sign-in; do not silently forward an existing bearer session to a newly discovered endpoint. Changing location still requires an operator migration that preserves the authority's state, not just a new JSON value.

For initial compatibility, an explicitly configured existing root service may continue the SPEC-009 login flow with its current domain check. It must be labelled as configured service access, not successful automatic discovery. Old clients do not implement URI/SRV discovery, the descriptor, a path mount or qualified login-field parsing merely because DNS records exist.

## URL paths and instance boundaries

Illustrative future delegation: identity `alpha.sloppyvoice.com`, base URL `https://chat.sloppyvoice.com/sloppyvoice/`. Its URI record belongs at `_sloppy-voice._tcp.alpha.sloppyvoice.com`, and its descriptor belongs at `https://alpha.sloppyvoice.com/.well-known/sloppy-voice`. With dedicated hosting the base can instead be `https://alpha.sloppyvoice.com/`. Neither choice transfers `@sloppyvoice.com` away from the existing service.

The base URL always ends in `/`. Join relative routes without a leading slash: `new URL("api/info", baseUrl)` yields `/sloppyvoice/api/info` for the path example. Apply this consistently to assets, API calls, navigation, setup, invitation URLs, realtime routes, desktop startup URLs and service-worker scope if introduced. Never join `"/api/info"`, which discards the prefix. Match complete prefix segments; `/sloppyvoice-other/` is not part of `/sloppyvoice/`.

Initial public URL grammar: HTTPS port 443 only; a valid DNS hostname; root `/` or slash-separated nonempty ASCII segments containing letters, digits, `_` or `-`, followed by `/`; maximum 2048 ASCII characters. Reject userinfo, query, fragment, IP literals, dot segments, backslashes, percent escapes, duplicate slashes and other schemes/ports. Validate raw input before URL-parser normalization can erase forbidden segments. Hostnames are lowercase; path case is preserved. Local loopback HTTP development remains explicit SPEC-002 configuration and is never a public discovery fallback.

The edge router selects the instance before authentication and enforces the configured mount; each instance has independent account storage, bootstrap state, secrets and media authority. In the current Worker, changing a path does not change the `DOMAIN` binding or the `domain-v1` object. Shared storage or one login session across unrelated identity domains is forbidden. Invitations, device storage, caches and command/session audiences must bind the canonical identity and instance context; retain account UUIDs as durable references.

Paths on one HTTPS origin share a browser security boundary. Namespacing local storage by domain/base URL avoids accidental collisions but does not isolate an instance from malicious scripts in another path on that origin. Prefer dedicated hostnames for independently trusted instances. Separate origins still require correct API/CORS, cookie, CSP and credential isolation; a hostname is not itself an account-permission grant.

Future server-side discovery accepts only public HTTPS destinations, rejects loopback/private/link-local/reserved/multicast addresses, and must enforce the restriction on actual connection resolution as well as initial lookup. Redirects remain disabled. An adapter unable to prevent DNS rebinding/private-network access must not expose arbitrary remote discovery fetches. The current increment needs no unauthenticated server-side URL fetch proxy.

## Configuration

The following runtime values are selected under D-058/P-028. SPEC-002 continues to own `SERVICE_DOMAIN`, `PUBLIC_ORIGIN`, `ALLOWED_ORIGINS` and local development exceptions. SPEC-003 owns username parsing. SPEC-010 owns the new URL/discovery schema; client and server specifications reference it.

| Property/rule | Kind, type, default and validation | Writer, precedence, timing and persistence |
| --- | --- | --- |
| `PUBLIC_BASE_URL` | New deployment string; omitted means `PUBLIC_ORIGIN + "/"`; explicit empty/null invalid; grammar above; its origin must equal `PUBLIC_ORIGIN`. | Operator environment/Worker vars, same source precedence as SPEC-002; startup validation; restart/deploy to change. Nonsecret; never inferred from an untrusted Host or forwarded header. Existing root deployments preserve their URL. |
| Desktop service location | Extension of SPEC-002's `SLOPPY_VOICE_URL`, legacy `VOICE_CHAT_URL` and Gentoo server file to accept a full base URL; an existing bare origin normalizes to its root. Add optional package string `defaultControlBaseUrl`; missing falls back to existing `defaultControlOrigin`, then the existing generic-build behavior. Empty/null/invalid values fail rather than falling through. | Retain existing environment/launcher-over-package precedence. Validate the effective base before navigation; retain existing root device storage through explicit migration. Add packaging option `--server-base-url`; specifying it with `--server-origin` is an error. SPEC-002/005 reference this inventory in place of their older origin-only interface. |
| Descriptor `domain` | Derived from immutable `SERVICE_DOMAIN`, no independent override. | Operator owns the identity; changing it after initialization is rejected, not an account rename. |
| Descriptor `baseUrl` | Derived from `PUBLIC_BASE_URL`; no separate admin UI override. | Website/identity-origin publication must match the application configuration. Do not keep competing manual defaults. |
| DNS URI/SRV | Operator DNS publication; one URI `0 0` for this profile; optional direct-root SRV `0 0 443 target.` or explicit `0 0 0 .`; TTL 300 seconds for this installation. | Stored in DNS, independent of app deployment. Never treat a missing URI as permission to strip a path. No secrets. Resolver caches may delay changes. |
| Descriptor version/capability | Fixed integer `1` and string `identity-discovery/1`; not a runtime setting. | Server advertises only after the matching contract is implemented; persistent identity/data schema remains separate. |
| Federation announcement | Fixed `{"mode":"closed"}` in this profile; no new enable-federation switch. | Derived from implemented policy, not a declaration that overrides enforcement. |
| Fetch limits | Fixed 16,384 decoded response bytes, 5,000 ms per request and 10,000 ms total resolution deadline, zero redirects. | Applied by clients/adapters; not operator tuning knobs in v1. Abort on limit; partial responses unusable. |
| Cache bounds | Fixed maximum 300 seconds for positive descriptors, further reduced by HTTP freshness; absent freshness defaults to 300 seconds; `no-store`/`no-cache` honoured; errors cached at most 60 seconds. | Cache by canonical identity domain; no credential data. Expired entries cannot authorise new login. DNS caching respects record TTL independently. |

Omitted settings use only their stated defaults; null is not inheritance. Explicit loopback development may use SPEC-002's HTTP exception for locally configured base URLs; public discovery never advertises that exception. Invalid settings fail startup or discovery without changing persisted accounts. Profile/schema upgrades require version negotiation and mixed-version fixtures; the build must not claim discovery capability merely because a configuration field is present. SPEC-002/005 reference this authoritative base-URL inventory. The current clients perform fresh HTTPS discovery on each explicit selection and do not cache descriptors or query DNS directly. The shared DNS parser supports supplied hints for future resolver adapters; live DNS resolution is not claimed.

## Testing

Unit tests cover SPEC-003 address parsing, local-domain selection, canonical equality and invalid/multiple suffixes; descriptor schema and duplicate keys; URI/SRV interpretation including dot; host/path validation before normalization; relative route joining; prefix-boundary matching; profile negotiation; timeout/size/cache limits; omitted/default/null configuration and origin mismatches. Expected failures must occur before credential dispatch or invitation consumption.

Use recorded DNS fixtures for a direct target, missing URI, an SRV dot, conflicting hints, a CNAME target, Cloudflare's rewritten target, DNSSEC failure, and a URI containing a path. Verify that browser discovery succeeds with no DNS API, authenticated HTTPS mapping wins over an untrusted hint, and a changed descriptor does not receive old tokens automatically. Include SSRF/rebinding tests for any future server fetch adapter; parser unit tests alone are insufficient evidence.

Integration tests host two isolated instances, one at root and one below `/sloppyvoice/`, with distinct identity domains and accounts sharing a local username. Confirm all APIs/assets/invites/setup remain within the selected mount, origin/domain/session mismatches fail, permissions and keys never cross instances, and the existing `sloppyvoice.com` account UUIDs/history survive upgrading the original service. Test legacy explicitly configured root clients separately from discovery-capable clients. Verify the apex descriptor is JSON rather than the website redirect or SPA HTML.

Deployment checks query both authoritative nameservers plus a recursive resolver, compare the URI with the configured service, and validate TLS/descriptor/info responses. DNS publication alone passes only the DNS portion. It cannot pass automatic login, path hosting, multi-device support or federation acceptance.

## Acceptance criteria

- `saxon` on the existing service and `saxon@sloppyvoice.com` select the same account; a foreign suffix never silently selects it. Existing IDs, keys, sessions, roles and history remain unchanged.
- The current DNS URI resolves to `https://chat.sloppyvoice.com/`; the SRV record is `0 0 0 .`, with no unreachable Worker origin represented as a working route.
- A browser with no DNS API discovers an implemented service through valid identity-origin HTTPS JSON; invalid TLS, redirects, schema/domain/profile mismatches and expired cache entries cannot receive credentials.
- A native client handles URI, direct SRV and explicit SRV absence without treating DNS alone as credential-delegation proof.
- A distinct test identity delegated to `/sloppyvoice/` loads assets and completes setup, invitations, login, text and voice API routing under that prefix; the original instance remains isolated.
- The same-origin limitation is documented and no test or UI claims that two path mounts isolate mutually untrusted scripts.
- Live `/api/info` advertises the extension only after its behavior is implemented; old `voice-chat/1` clients retain explicit root-service access without being misrepresented as discovery-capable.
- The discovery descriptor announces `closed`; DNS publication does not enable foreign authentication, disclose private policy or start a second authority for `sloppyvoice.com`.

## Sources and delivery status

Primary sources reviewed 2026-10-19: [RFC 2782 — SRV](https://www.rfc-editor.org/rfc/rfc2782), [RFC 7553 — URI](https://www.rfc-editor.org/rfc/rfc7553), [RFC 8615 — well-known locations](https://www.rfc-editor.org/rfc/rfc8615), [RFC 9460 — SVCB/HTTPS](https://www.rfc-editor.org/rfc/rfc9460), [Cloudflare record types](https://developers.cloudflare.com/dns/manage-dns-records/reference/dns-record-types/), [Cloudflare SRV rewriting](https://developers.cloudflare.com/dns/manage-dns-records/troubleshooting/unexpected-dns-records/), and [MDN same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy). The protocol composition, precedence and numeric bounds are project proposals, not requirements imposed by these RFCs.

D-058 selects this profile for alpha.6. Qualified login parsing, HTTPS descriptor publication, explicit discovery and mount-prefix routing are implemented; native live DNS resolution and server federation remain outside this increment. The alpha.6 release report records tested and deployed scope. See the [deployment record](../docs/discovery/2026-10-19-dns.md) and A-40.
