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

66 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`](event-flow.md)).