Files
amethyst/nestsClient/specs/EGG-01.md
T
Claude bd12d5ef72 docs(nestsClient/specs): close interop gaps surfaced in spec review
Edits across all 13 EGGs to remove implementer guesswork. After this an
implementer can build a listener and speaker without reading our source.

README:
- Conventions section: hex casing rule (lowercase, 64 chars, no 0x),
  NIP-01 foundations, `a`-tag form, created_at tie-break, JSON / time.
- Joining sequence: numbered end-to-end walkthrough from `kind:30312`
  to first audio frame, referencing each EGG.

EGG-01 (room event):
- `relays` tag is one tag with multiple values (not multi-tag).
- `service` URL trailing-slash normalization.
- `private` status: gate is implementation-defined, not "render as open".
- `d`-tag charset locked to [A-Za-z0-9._-] so it interpolates safely
  into the moq-auth namespace.
- Relay-discovery rule: publish to `relays` tag ∪ NIP-65 outbox.
- Tie-break on identical created_at via smallest event id.

EGG-02 (auth):
- JWT signing pinned: ES256 over P-256, JWKS at /.well-known/jwks.json,
  5-minute relay cache.
- NIP-98 tags pinned: u / method / payload, base64 RFC 4648 standard
  (not base64url).
- Error taxonomy: full HTTP status + `error` slug table for /auth, plus
  WebTransport CONNECT 200/401/403/404 table.

EGG-03 (audio):
- Pin moq-lite Lite-03 to kixelated/moq-rs `rs/moq-lite/src/lite/`.
- One Opus packet per moq Frame (no container, no timestamp).
- Pubkey hex casing reaffirmed at the suffix / broadcast slots.
- AnnouncePlease prefix="" for speaker discovery.
- Mute = stop publishing (not silence frames, not Announce Ended).
- Mid-stream join: discard pre-skip per RFC 7845.

EGG-04 (presence):
- Heartbeat jitter ±5 s required (anti-thundering-herd).
- "0"/"1" are strings, not booleans.
- Single-room rule via replaceable-event semantics.

EGG-05 (chat): 8 KB suggested, 64 KB hard cap, 3 msg/s render rate.
EGG-06 (reactions): 30s window measured against created_at, drop-on-arrival
                    if already stale.
EGG-07 (moderation): replay protection — dedupe kicks by id within 120 s.
EGG-08 (scheduling): planned rooms MUST 403 at the auth sidecar.
EGG-10 (theming): bg image caps (1 MB / 4096 px soft, 8 MB / 8192 px hard).

Deferred (per review): EGG-00 (Conventions as a standalone spec), EGG-13
(capability advertisement), EGG-14 (discovery), test vectors corpus.
The "Conventions" section in README covers EGG-00's most urgent content
inline.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
2026-04-27 12:31:19 +00:00

4.7 KiB

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

{
  "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).

Example

{
  "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.