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
3.9 KiB
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
21001–21099is OFF-LIMITS. Aiolabs custom band is22000–22099; 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:2200xfor the whole booking (request + record). Rejected: the20000–29999range 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:30017–30019). 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 22000–22099 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).