Files
amethyst/nestsClient/specs/EGG-01.md
T
Claude fd03122206 docs(nestsClient/specs): close hosting-side ambiguities
Define the host-side flow that was previously implicit. A new-room
implementer can now go from "I want to start broadcasting" to first
audio frame without reading our source.

README — new "Hosting a new room" section:
- 8-step ASCII walkthrough symmetrical to "Joining sequence".
- Covers service/endpoint selection, kind:30312 compose + publish, JWT
  mint with publish:true, WT/Setup, Announce, presence, Opus stream.
- Lists ongoing host duties (add speaker, kick, edit, close, recording).

EGG-01 — two new behavior rules:
- Rule 12 "Publish-before-mint ordering": peers MUST publish the
  kind:30312 to relays BEFORE requesting a JWT for it, since the auth
  sidecar reads the most-recent event by (pubkey, kind, d) to validate
  existence / status. 410 unknown_room → retry 1s/2s/4s. Closes the
  silent footgun where a host could mint a token before their event
  has propagated.
- Rule 13 "Service/endpoint selection (host-side guidance)": pre-fill
  from kind:10112 first entry, fall back to client default with user
  override, both MUST be https://.

EGG-07 — new "Audio publish authorisation" section:
- Spells out who gets a publish:true JWT: host (by authorship,
  implicitly — no need for ["p", _, _, "speaker"] on the host's own
  p-tag), explicit "speaker" role, or "admin" role. All others 403
  publish_forbidden per EGG-02.
- Relay does NOT re-read kind:30312; demoting a speaker mid-session
  does NOT terminate their stream — host MUST kick (kind:4312) to
  end an active broadcast.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 13:05:49 +00:00

142 lines
6.0 KiB
Markdown

# EGG-01: Room event (`kind:30312`)
`status: draft`
`requires: NIP-01`
`category: required`
## Summary
A nests audio room is a NIP-53 addressable event of `kind:30312`. The author
is the host. Subscribers read this event to learn who runs the room, where the
audio plane lives, who else is invited, and whether the room is currently live.
Every other EGG layers on top of this event.
## Wire format
```json
{
"kind": 30312,
"pubkey": "<host pubkey hex>",
"tags": [
["d", "<room id>"],
["room", "<room name>"],
["summary", "<one-line description>"],
["image", "<optional cover image URL>"],
["status", "open" | "private" | "closed" | "planned"],
["service", "<https URL of moq-auth sidecar>"],
["endpoint", "<https URL of moq-relay WebTransport>"],
["relays", "<wss relay 1>", "<wss relay 2>", ...], // ONE tag, multiple values
["p", "<pubkey>", "<relay hint>", "host" | "admin" | "speaker"],
...
],
"content": "",
...
}
```
The `d` tag is the room id; `(pubkey, kind, d)` is the addressable identity.
The `relays` tag is encoded as a SINGLE tag whose first element is `"relays"`
and whose remaining elements are wss URLs. Implementers MUST NOT emit one
`["relays", url]` tag per relay; receivers MUST treat such input as a
malformed event and MAY repair it by concatenating the values, but
publishers that emit it are non-conformant.
## Behavior
1. Hosts MUST emit exactly one `kind:30312` event per room. Updating the room
(rename, status change, role grants) MUST re-publish the same `d` tag with
a higher `created_at`.
2. The `room`, `status`, `service`, and `endpoint` tags MUST be present. A
client receiving an event without all four MUST treat the room as
un-joinable.
3. The `service` value MUST be the base URL of an EGG-02 auth sidecar (do
not include the `/auth` suffix). Receivers MUST normalize the value by
stripping a single trailing `/` before constructing sub-paths.
4. The `endpoint` value MUST be the base URL of a WebTransport-capable
moq-relay implementing EGG-03.
5. The `p` tag MUST list the host as the first participant. Additional
`p` tags grant roles (EGG-07).
6. Status semantics:
- `open` — room is live, anyone can join. The auth sidecar MUST mint a
listener token to any well-formed NIP-98 request.
- `private` — room is live, but the auth sidecar applies an out-of-band
allowlist. The allowlist mechanism is implementation-defined; this
spec only mandates that a non-allowlisted requester receives `403`
(see EGG-02 error taxonomy). Clients without a path to acquire access
MUST render `private` rooms as un-joinable rather than attempt to
connect blind.
- `closed` — room is over, audio plane SHOULD be torn down server-side.
- `planned` — room hasn't started; see EGG-08.
7. A host MAY treat a room as auto-closed after 8 h of `created_at` staleness
even if the published status is still `open`/`private`. Receivers SHOULD do
the same to avoid stale rooms hanging at the top of the live UI.
8. An empty `content` field is REQUIRED. Future EGGs MAY define structured
content; until then, peers MUST ignore non-empty content rather than
rejecting the event.
9. The `d` tag MUST contain only characters from `[A-Za-z0-9._-]`. Colons,
slashes, whitespace, and percent-encoded sequences are forbidden because
the `d` is interpolated unescaped into the moq-auth namespace
`nests/<kind>:<host pubkey hex>:<d>` (EGG-02). Receivers MUST reject
events whose `d` violates this charset.
10. **Relay discovery.** Hosts SHOULD publish the `kind:30312` event to (a)
every relay listed in the event's own `relays` tag, AND (b) their
NIP-65 (`kind:10002`) write relays. Receivers SHOULD subscribe to the
same union to track room metadata changes (rename, status flip,
role grants).
11. **Tie-break on identical `created_at`.** When two `kind:30312` events
share `(pubkey, d)` AND `created_at`, receivers MUST keep the event
with the lexicographically SMALLEST `id` and discard the other (per
NIP-01 replaceable-event tie-break).
12. **Publish-before-mint ordering.** A peer MUST NOT request a JWT
(EGG-02) for a room before that room's `kind:30312` event has
propagated to relays the auth sidecar can read. The auth sidecar
looks up the most-recent `kind:30312` by `(pubkey, kind, d)` to
validate the room exists and to enforce status-based gates
(EGG-08 rule 5). For hosts: this means publishing the freshly-
composed event BEFORE minting their own `publish: true` token.
For listeners: this means waiting until the event is visible on
the listener's own relay set (an unknown room cannot be joined).
Auth sidecars that cannot find the room MUST return HTTP 410
`unknown_room` (EGG-02 error taxonomy); peers SHOULD retry with
1 s / 2 s / 4 s exponential backoff before surfacing the error.
13. **Service / endpoint selection (host-side guidance).** When
composing a new room, hosts SHOULD pre-fill `service` and
`endpoint` from the FIRST entry of their own `kind:10112` user
server list (EGG-09). When the host has no `kind:10112`, the
client MAY ship a built-in default URL but MUST allow user
override. Both `service` and `endpoint` MUST be `https://` URLs;
`http://` MUST be rejected at compose time (mirrors EGG-09
rule 1). The `service` and `endpoint` MAY be the same base URL
(a single deployment serving both is the common case).
## Example
```json
{
"kind": 30312,
"pubkey": "abc...host",
"created_at": 1714003200,
"tags": [
["d", "office-hours-2026-04"],
["room", "Office Hours"],
["summary", "Weekly Q&A"],
["status", "open"],
["service", "https://moq.nostrnests.com"],
["endpoint", "https://moq.nostrnests.com"],
["p", "abc...host", "wss://relay.example", "host"],
["p", "def...co", "wss://relay.example", "speaker"]
],
"content": "",
"id": "...",
"sig": "..."
}
```
## Compatibility
Peers without EGG-01 cannot interpret any other EGG. EGG-04, EGG-05, EGG-06,
EGG-07, and EGG-12 reference rooms by the `(kind, pubkey, d)` tuple defined
here.