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
This commit is contained in:
parent
e7fabd57bf
commit
f0c24ebba6
1 changed files with 66 additions and 0 deletions
66
docs/adr-0001-nostr-event-model.md
Normal file
66
docs/adr-0001-nostr-event-model.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# 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–21099` is OFF-LIMITS.** Aiolabs custom band is
|
||||||
|
`22000–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:2200x` for the whole booking (request + record).**
|
||||||
|
Rejected: the `20000–29999` 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: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`](event-flow.md)).
|
||||||
Loading…
Add table
Add a link
Reference in a new issue