chatelet/docs/adr-0001-nostr-event-model.md
Padreug f0c24ebba6 docs: ADR-0001 nostr event model
Records the decision to compose standard NIPs (99 listing, 78 reservation,
52 calendar, 17/59 DMs) and reserve aiolabs kind:22000/22001 only for the
ephemeral availability RPC. Documents rejected alternatives (bespoke 2200x
booking record, RSVP-as-booking, deprecated NIP-15) and flags the 22000/1
allocation to register in workspace CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
2026-07-19 00:16:39 +02:00

3.9 KiB
Raw Permalink Blame History

ADR-0001 — Nostr event model for room rentals

Status: accepted (design phase) · Date: 2026-07-19

Context

Chatelet needs a Nostr representation for: a room listing, a private booking negotiation, a durable reservation record for the guest, public availability, and a live "is it free?" query. No single NIP defines a rental-booking protocol. We must decide whether to invent a bespoke set of kinds or compose existing NIPs.

Constraints from the workspace:

  • Respect protocol semantics over friction reduction — pick the kind a primitive is for, not whatever listener happens to exist.
  • CLINK band 2100121099 is OFF-LIMITS. Aiolabs custom band is 2200022099; document each allocation.
  • No nsec at rest — all signing/encryption via resolve_signer.

Decision

Compose standard NIPs; reach into the aiolabs band only for the one case no standard kind fits.

Concern Choice Why
Room listing NIP-99 kind:30402 Purpose-built for listings; the NIP names rentals explicitly. Has price with a frequency (night), location, g geohash, images. Addressable (d=room id) so edits replace.
Guest reservation record NIP-78 kind:30078 App-specific replaceable data; workspace already endorses 30078 for app state. Addressable (d=booking id) so status transitions replace in place. NIP-44 encrypted to the guest.
Public availability NIP-52 kind:31923/31924 Calendar events are exactly "these dates are occupied". Published without PII, gated by settings.publish_availability, so third-party clients can render open dates without querying us.
Private request → quote → confirm → check-in NIP-17/59 giftwrap (kind:1059) The private-messaging standard. Relays store them (survives operator downtime), metadata-private, no bespoke transport to maintain.
Live availability query + response aiolabs kind:22000 / kind:22001 (ephemeral) A live ping/pong; no standard kind fits, and it should be ephemeral (no need to persist a stale "free?" check). Mirrors how CLINK uses the ephemeral range for payment RPC.

Rejected alternatives

  • A single bespoke kind:2200x for the whole booking (request + record). Rejected: the 2000029999 range is ephemeral — relays don't store it, so a booking request would be lost if the operator were briefly offline. Persistent booking state belongs in stored kinds (giftwrap DMs + 30078), not the ephemeral band. Using ephemeral only for the genuinely-transient availability ping is the semantically honest split.
  • NIP-52 RSVP (kind:31925) as the booking request. Rejected: RSVP means "attendance intent for a calendar event" — a commercial reservation with money and a hold is a different relationship. Repurposing it would conflate attendance with booking (the exact anti-pattern the workspace CLINK/kind rule warns against).
  • NIP-15 marketplace order flow (kind:3001730019). Rejected: the NIP is deprecated ("too complicated, use NIP-99") and its stall/product/order model is a poor fit for date-ranged, single-inventory lodging.

Kind allocation register (action required before ship)

22000 (availability query) and 22001 (availability response) are hereby claimed for Chatelet in the aiolabs 2200022099 band. Follow-up: record this allocation in the workspace CLAUDE.md kind-allocation section so a future extension doesn't reuse the numbers. Ephemeral, so on-wire collisions would be transient — but still worth registering.

Consequences

  • The heavy lifting stays in standard NIPs → third-party Nostr clients get listings + availability for free; less bespoke code to maintain.
  • Exactly one custom, low-stakes (ephemeral) kind pair to own.
  • Booking authority stays in LNbits regardless of event choices — the events are the interop skin over a DB-arbitrated flow (see event-flow.md).