ClientSideOnlySigner organizers can never publish — and the #55 sweep retries them forever #57
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two problems, one root
An organizer whose account is
ClientSideOnlySignerhas no Nostr presence at all.publish_or_delete_nostr_eventresolves a signer for the event's wallet,can_sign()is false, and it returns. The event exists in LNbits and never appears on any relay. Before #54 this was completely silent.And since #55, the sweep retries them every 5 minutes forever. The sweep's premise is "this will work later" — true for a signer outage, a dead NostrClient, a relay blip. For
ClientSideOnlySignerit is false by construction: the server has no signing authority for that account and never will. So the row stays flagged, the retry can never succeed, and the log fills up.The second is a nuisance #55 introduced. The first predates it — #55 only made it visible.
Not the same as the bunker direction
Worth separating, because they look alike. Under
RemoteBunkerSignerthe server still initiates signing and server-side republish keeps working; the key just lives elsewhere. The workspace's "bunker for everything, no nsec at rest" endgame therefore does not make the sweep obsolete.ClientSideOnlySigneris the genuinely different case — the server cannot originate a signature at all. It's the only signer type the sweep can't serve.What a real fix probably needs
A terminal state distinct from
nostr_publish_pending. Pending means "retry"; this means "the server is not allowed to do this". The sweep skips terminal rows, logs once rather than every 5 minutes, and "events awaiting client-side signature" becomes a queryable thing instead of noise.resolve_for_walletsurfacing why it returned None. Callers get a bareNonetoday and cannot distinguish "outage, retry" from "not permitted, never retry". Everything above depends on that distinction, and it lives inaiolabs/lnbitscore (lnbits/core/signers/resolve.py), not here. Three of its four soft-fail branches also log atdebug, so the reason is invisible at the INFO level instances run at — same defect class as #51, one layer down.A client-driven publish path. The server already has
build_nip52_event(event, pubkey), so it can hand a client a deterministic unsigned event to sign via NIP-07/NIP-46 and publish. This is the TODO already sitting innostr_hooks.py:It's also the client-agnostic destination the workspace notes aim at, so it's worth building for its own sake rather than as a workaround.
Cheap interim
Independent of the design: have the sweep skip terminal cases and log once. Kills the 5-minute noise without prejudging anything. Worth doing if a production instance turns out to have a ClientSideOnly organizer.
A no-op edit does not work around this
Worth recording, since it's the obvious first thought.
api_event_updatecallspublish_or_delete_nostr_eventunconditionally when the event is approved — there's no diff check, so saving an unchanged form really does trigger a republish. But it doesn't help here, for two reasons:event.wallet, not from whoever is acting.api_event_updatealso requiresevent.wallet == wallet.wallet.id, so the editor is the event's wallet owner — a different user with a working bunker signer cannot republish someone else's event.walletis not in the update endpoint's field list, so the event can't be moved to a wallet whose account can sign.There's also a trap in trying it:
api_event_updaterecomputesevent.status = "approved" if (is_admin or auto_approve) else "proposed". A non-admin organizer on an instance withauto_approveoff who hits save takes their live event off the public feed and publishes a kind-5 delete.Live lead on cfaun
The relay's stored copy of
Q6mhQQzQPSXqkQuFndzXnfwas published by pubkeyba3f45a8d618…on 2026-09-12. Soresolve_for_wallet("7fe0a44ed7f342efb3ed3bcf7c18cb93")worked then and fails now — that account had a working server-side signer and lost it somewhere between Sep 12 and the Sep 21 sale.That makes "was it always ClientSideOnly?" the wrong question. Something reclassified it, cleared its pubkey, or left it unclassified. Worth resolving before designing around it:
If
pubkeystill equalsba3f45a8d618…then the identity survived and only the signing authority changed.Blocks nothing, but #55's sweep needs revisiting when this lands.
Parked — the load-bearing part is lnbits-side, not here
Deferring this. The decision that unblocks everything in it belongs in
aiolabs/lnbits, not in the extension:resolve_for_walletreturns a bareNonefor four different reasons, so this extension cannot distinguish "signer outage, retry" from "server has no authority, never retry". Every design option listed above depends on that distinction existing first.debug(lnbits/core/signers/resolve.py:131-162), so the reason isn't even visible at the INFO level instances run at — the same defect class as #51, one layer down.Until that lands, anything built here would be guessing at the cause from the extension side, which is how you get a heuristic that's wrong the first time a new signer type appears.
What's still true and worth not losing
ClientSideOnlySignerorganizer has no Nostr presence at all today — the event exists in LNbits and never reaches a relay. Silent before #54; now at least logged.Reopen for scoping once lnbits can say why a signer resolution failed.