Closesaiolabs/restaurant#11. Pre-cascade prerequisite for
aiolabs/lnbits#17 (signer abstraction phase 1), which lands an m002
startup job that NULLs the legacy `accounts.prvkey` column. After
this migration, the restaurant extension reads no plaintext nsec and
works with any NostrSigner backend (LocalSigner / RemoteBunkerSigner
/ ClientSideOnlySigner).
## What changed
### views_api.py — _resolve_signing_keypair → _resolve_signer
Was: `_resolve_signing_keypair(restaurant)` returned `(pubkey, prvkey)`
read directly from `account.pubkey` / `account.prvkey` after walking
wallet → account.
Now: `_resolve_signer(restaurant)` returns `NostrSigner | None`.
Precedence order preserved:
1. `restaurant.nostr_pubkey` set → per-restaurant identity. Still
a no-op TODO returning None until a per-restaurant signer /
vault ships (separate concern, future work).
2. fallback → `resolve_for_wallet(restaurant.wallet)` (the DRY
helper from aiolabs/lnbits#23 — wallet → account → signer →
can_sign-check in one call, returns None on any soft-fail).
Three call sites updated (`_publish_restaurant`, `_publish_menu_item`,
`_publish_menu_item_delete`): each now passes the resolved `signer`
to `publish_event` instead of the keypair tuple, and uses
`signer.pubkey` for tag construction. The discovery-echo line in
`_publish_restaurant` (`restaurant.nostr_pubkey = signer.pubkey`)
preserves prior behavior.
Dropped now-unused imports: `get_account`, `get_wallet`.
### nostr_publisher.py — publish_event
Was: `publish_event(client, event, private_key_hex)` called a local
`sign_nostr_event` helper that signed in place via
`coincurve.PrivateKey.sign_schnorr`.
Now: `publish_event(client, event, signer: NostrSigner)` builds the
unsigned dict (`kind`/`created_at`/`tags`/`content`), hands it to
`await signer.sign_event(...)`, and writes `id`/`pubkey`/`sig` back
onto the local `NostrEvent` model before publishing. The signer
backend (LocalSigner / RemoteBunkerSigner) is transparent.
Removed the `sign_nostr_event` helper entirely — the signer
abstraction handles all signing now.
Dropped the `coincurve` import; no direct crypto in this extension.
### docs/nostr-layer.md — signing prose
Updated the Signing section to reflect the signer-abstraction model:
`resolve_for_wallet` resolves a `NostrSigner`, the extension no
longer touches `account.prvkey` or calls `coincurve.sign_schnorr`
directly. The per-restaurant-identity TODO is preserved.
## Acceptance
- [x] `_resolve_signing_keypair` replaced with `_resolve_signer` returning NostrSigner
- [x] `sign_nostr_event` helper removed (signer handles it internally)
- [x] `publish_event` accepts a NostrSigner instead of private_key_hex
- [x] all three call sites updated to pass the signer
- [x] re-grep `restaurant/`: zero `account.prvkey` references
- [x] coincurve import dropped
- [x] docs/nostr-layer.md updated in the same commit
Manual smoke testing + tag + catalog entry follow the migration
landing; will run against the regtest stack with lnbits on
`issue-18-phase-2.3` (which validates both LocalSigner and
RemoteBunkerSigner signing paths end-to-end).
## Cross-references
- aiolabs/restaurant#11 — issue this commit closes
- aiolabs/lnbits#17 — the cascading signer-abstraction PR
- aiolabs/lnbits#23 — the resolve_for_wallet helper this uses
- aiolabs/lnbits#21 — umbrella audit (5 affected extensions)
- aiolabs/events#23 / aiolabs/tasks#3 — sister migrations (already on signer-abstraction branches)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a navigable Obsidian vault as the project's first-class
technical documentation. Notes cross-reference with [[wikilinks]];
docs/index.md is the Map of Content.
New notes:
index.md MOC, entry point
architecture.md what the extension owns vs what lives outside
data-model.md entity-by-entity schema reference
menu-tree.md the arbitrary-depth tree concept
order-flow.md state machine + invoice listener + print
nostr-layer.md kinds 0/30402/5/1059, signing, t-tags
api-reference.md endpoint catalog by audience
cms.md Vue 3 + Quasar 2 UMD conventions, q-tree
webapp-integration.md multi-restaurant cart pattern + atomicity
glossary.md domain terms
Existing notes (kept as-is):
adr-0001-menu-tree.md the storage choice rationale
design-conversation.md trimmed transcript
README.md adds a Documentation section pointing at docs/index.md
with the headline note list. Each note links to ~3-5 others; the
vault forms a connected graph.
A project-level memory rule (saved outside the repo) commits us to
keeping these docs in sync as the code evolves: any commit that
materially changes schema, API, order flow, Nostr surface, CMS
conventions, or webapp integration must update the relevant note(s)
in the same commit.