seller node: buzz persona — kind-0 rate card + ephemeral presence - #158
Closed
orveth wants to merge 1 commit into
Closed
seller node: buzz persona — kind-0 rate card + ephemeral presence#158orveth wants to merge 1 commit into
orveth wants to merge 1 commit into
Conversation
Slice 1 of mobee×buzz. The seller node enrolls as a buzz inhabitant under
its existing protocol identity: it publishes a NIP-01 kind-0 persona carrying
a human-readable rate card and maintains a live presence heartbeat while up.
Config-off (no `[buzz]` section) is fully inert.
- home::BuzzConfig — optional `[buzz]` section (relay URL + persona: name,
about, rate, capabilities, mint, heartbeat cadence).
- seller_node::buzz — the persona subsystem:
* kind-0 rate card published on boot, rate/caps/mint assembled into a
human-readable `about` (rate_card_about, pure + unit-tested);
* clobber guard — kind-0 is one-per-key replaceable, so before the first
publish the node fetches the key's current kind-0 and REFUSES a foreign
one (no mobee marker), fail-closed if it cannot read it (clobber_decision,
pure + unit-tested);
* presence — a live WS connection publishing an ephemeral kind-20001
PRESENCE_UPDATE every 30s; clean disconnect on shutdown clears presence,
crash lets the relay TTL expire it.
- One identity, one signer. The persona (and any NIP-42 auth the relay asks of
the presence socket) is signed by the existing seller_node::signer actor via
NodeNostrSigner, so the seller key never leaves the actor. The only signer
change is a new sign_unsigned command that refuses a foreign-authored event.
- SellerNode::start_buzz — the boot hook (Ok(None) when `[buzz]` absent). The
`mobee sell` -> node cutover (separate slice) will call it.
Zero changes to any money-path file, to buyer/, or to mcp.rs. PROTOCOL_VERSION
"0" untouched. Live kind-0 + presence logic proven against an in-process
nostr-relay-builder fixture (seller_node::buzz_relay_it); a deployed-relay
round-trip is wired behind #[ignore] for the live check once demo keys are
admitted.
Stacks on #135 (seller-node-durable-store-roster).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
orveth
force-pushed
the
seller-node-buzz-persona
branch
from
July 25, 2026 16:35
c8bceee to
9708313
Compare
orveth
added a commit
that referenced
this pull request
Jul 25, 2026
SellerNodeRunner::boot opens the durable node, reconciles durable state, then connects ONE authenticated relay client and builds the shared publisher over it: - Key custody (team-lead approved option a): the seller key lives in exactly two places — the signer actor and this one authenticated relay client, constructed at a single site with the secret read once and dropped. No accessor exposes it, it is never logged/serialized. The client holds it because mobee-relay authenticates the seller via NIP-42 before delivering p-gated kind-1059 wraps. Follow-up (post-#158): converge onto the NostrSigner-over-actor adapter to collapse custody back to actor-only. - NIP-42 handshake via the neutral crate::relay_auth (Authenticated / NoChallenge degrade / fatal), notifications subscribed before connect. - RelayPublisher now shares the runner's authenticated client (Arc clone) instead of opening its own — one connection, one NIP-42 session. run() subscribes the targeted offer/award/gift-wrap filters and runs the select loop: a periodic tick drains the outbox; each event dispatches by kind. The offer->claim, award->execute, gift-wrap->pay arms + the #150 watchdog + #162 recovery are ported on top of this next (marked PORT); `mobee sell` is NOT yet pointed here, so this is inert scaffold. Money-suite 558 passed / 0 failed; runner compiles clean (no new warnings). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
orveth
added a commit
that referenced
this pull request
Jul 26, 2026
Union-resolved with the #169 cutover in seller_node/{mod,signer}.rs: both sides' actor commands kept; signer() stays pub(crate) (cutover's narrowing) with the persona's default-deny rationale folded into the doc.
Contributor
Author
|
Closing per gudnuf's V1 process ruling (2026-07-27): dev is the single integration line — no parallel open-for-main PRs. This PR's content is fully integrated into |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebased on
main(includes #135 seller-node, plus #147/#149).Slice 1 of mobee×buzz: the seller node enrolls as a buzz inhabitant under its existing protocol identity — publishing a kind-0 persona with a human-readable rate card and maintaining a live presence heartbeat while it is up. Config-off ⇒ fully inert.
What this adds
[buzz]config (home::BuzzConfig, optional): relay URL + persona (name, about, rate, capabilities, mint, heartbeat cadence). Absent ⇒ the node opens no connection and publishes nothing.seller_node::buzz— the persona subsystem:about(rate_card_about, pure + unit-tested).clobber_decision, pure + unit-tested.)"online"status every 30s (the deployed relay keys presence on the authed pubkey and reads the content status; tags ignored). Clean disconnect on shutdown clears presence immediately; crash ⇒ relay TTL expiry (~60s).seller_node::signeractor — including the NIP-42 auth the relay asks of the presence socket, viaNodeNostrSigner(a nostr-sdk signer that delegates every sign to the actor). No new keys; no key material leaves the actor.{kind 0 persona, kind 20001 presence, kind 22242 NIP-42 auth}, enforced in two layers:sign_unsigned_event, source of truthUNSIGNED_SIGN_ALLOWLIST): default-deny for every caller ofSignerHandle::sign_unsigned, not just the buzz wrapper — any other kind is refused + logged. So even a future holder of the handle cannot turn this path into a sign-anything oracle for the protocol key.NodeNostrSigner::sign_event): belt-and-suspenders, sourced from the same actor constant.sign(draft)(the trade path, kinds set by the gateway draft constructors; the outbox's designed protocol-signing path, unchanged) andsign_unsigned(the buzz/auth path). The allowlist gatessign_unsignedonly.sign_unsigned's sole production caller is the buzz wrapper; the actor also refuses a foreign-authored event.SellerNode::signer()stays public but is safe — the actor default-denies, so a holder of the handle cannot sign a trade kind through it.sign_unsigned_refuses_non_allowlisted_kind_directly(the gate's exact probe — a directsign_unsigned(kind-3402)with no adapter is refused at the actor, and the full trade block 3400/3401/3402/3403/3404/3405/30340; red-on-revert on the actor-side check),adapter_signs_only_allowlisted_kinds(wrapper layer),sign_unsigned_signs_own_and_refuses_foreign_author.SellerNode::start_buzz— the boot hook (returnsOk(None)when[buzz]is absent). Themobee sell→ node cutover (separate slice) will call it.Scope discipline
Zero changes to any money-path file (
payment.rs,authorize_pay.rs,wallet*.rs,seller.rs,seller_daemon/), tobuyer/, or tomcp.rs. No buzz-side/server code. PROTOCOL_VERSION "0" untouched. No machine-readable rate card, no mention-watching/chat-bridge/paste-pay/notary (later slices).Acceptance evidence
cargo test --no-default-features --features gateway,git-delivery,wallet --release→ 568 passed ·cargo test --features acp,gateway,git-delivery,wallet --release→ 575 passed ·cargo build --features acp --release·cargo test --features acp --release·cargo build -p mobee --release— all rc0.nostr-relay-builderfixture (buzz_relay_it.rs) — boot publishes a kind-0 fetchable by pubkey with the rate card; a pre-seeded foreign kind-0 makesstart_buzzrefuse; our own marked kind-0 is replaced; presence beats flow while up and stop on clean shutdown. All green.#[ignore]harnesses PASS:live_buzz_kind0_round_trip_against_deployed_relay: NIP-42 auth via the signer actor + kind-0 publish + fetch-back. kind-0 idaa164db1…, about =Rust reviewer · rate 50 sat/job · does: code, test · pays via testnut · hire me on mobee.live_buzz_presence_against_deployed_relay: a live authed WS SUB{kinds:[20001], authors:[persona]}receives the"online"heartbeat while up (2 beats, content"online"), and receives no further beats after a clean shutdown (count stable). The canonical operator snapshot is the relay's NIP-98POST /query(authors filter, relay-signed response); a WSREQfor an ephemeral hits the DB and returns empty by design.🤖 Generated with Claude Code