Files
amethyst/nestsClient/specs/EGG-08.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

3.3 KiB

EGG-08: Scheduling

status: draft requires: EGG-01 category: optional

Summary

A host can announce a room before it opens by publishing a kind:30312 with status="planned" and a ["starts", <unix-seconds>] tag. Listing clients render these as upcoming-room cards; the audio plane stays cold until the host transitions the room to open.

Wire format

{
  "kind": 30312,
  "pubkey": "<host pubkey hex>",
  "tags": [
    ["d",       "<room d>"],
    ["room",    "<room name>"],
    ["status",  "planned"],
    ["starts",  "<unix seconds>"],
    ["service", "<...>"],
    ["endpoint","<...>"],
    ["p", "<host pubkey>", "<relay hint>", "host"]
    // ...other EGG-01 tags
  ],
  "content": "",
  ...
}

The starts value is a base-10 ASCII unix timestamp in seconds. Clients that need finer-than-second precision are out of scope; round to the nearest second.

Behavior

  1. A planned room MUST carry status="planned" AND a starts tag with a non-empty numeric value. A planned room without a starts value MUST be treated as un-renderable (rooms with unknown start times pollute the listing UI).
  2. The starts value SHOULD be in the future at the time the event is published. Receivers MUST tolerate past values: a planned room whose starts is in the past renders as "starts soon" rather than surfacing the past time.
  3. To start a planned room, the host MUST re-publish the same (kind, d) with status="open" and a higher created_at. The host MAY drop the starts tag at this point (no longer informative).
  4. To cancel a planned room, the host MUST re-publish the same (kind, d) with status="closed". The starts tag SHOULD be preserved so receivers can label the cancellation ("Cancelled — was scheduled for ...").
  5. Planned rooms MUST NOT mint moq-auth tokens (EGG-02). The auth sidecar MUST reject POST /auth requests targeting a (host, room d) whose most-recent kind:30312 is status="planned", returning HTTP 403 with {"error":"room_closed"} per the EGG-02 error taxonomy. (The auth path becomes available the moment the host re-publishes with status="open"; clients SHOULD NOT pre-mint tokens against starts.)
  6. Listing UIs SHOULD sort planned rooms ahead of live ones until 5 minutes before starts, then promote the room to the same surface as live rooms (the host is unlikely to be more than 5 minutes late).
  7. A peer MUST NOT use ["expiration", <ts>] (NIP-40) on a planned kind:30312. The implicit 8-hour staleness window from EGG-01 is the only auto-close mechanism.

Example

{
  "kind": 30312,
  "pubkey": "abc...host",
  "created_at": 1714000000,
  "tags": [
    ["d", "monthly-q-and-a"],
    ["room", "Monthly Q&A"],
    ["summary", "Bring your questions"],
    ["status", "planned"],
    ["starts", "1714172400"],
    ["service",  "https://moq.nostrnests.com"],
    ["endpoint", "https://moq.nostrnests.com"],
    ["p", "abc...host", "wss://relay.example", "host"]
  ],
  "content": "",
  "id": "...",
  "sig": "..."
}

Compatibility

Receivers without EGG-08 see a planned room as status="planned" and SHOULD treat it as un-joinable per EGG-01 rule 6 (status-not-open means non-renderable for live UIs). The room becomes joinable as soon as the host re-publishes with status="open".