A revoked / TTL-expired / off-policy bunker binding now surfaces a dedicated
"Pairing Required" screen instead of a raw error, and a signer/relay timeout
shows "Signer Unreachable" (transient). Shared classifyInitError() maps the
typed BunkerRejectedError / BunkerTimeoutError (by name, so it survives bundle
boundaries) to maintenance-screen sentinels, used at every store init catch +
the App.vue fallback. App.vue's nested-ternary screen copy refactored to a
keyed map (cleaner, and the new screens drop in).
Scope: boot-time detection (covers the dominant restart-after-revoke case).
Mid-session re-pair detection (flipping the screen when a sign fails during a
live flow) is a deliberate follow-up.
Part of Phase D, aiolabs/bitspire#52.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the error-handling layer agreed in the 2026-05-26 cross-session
handshake (aiolabs/bitspire#52). LnbitsClient now rejects ERROR responses
with a typed LnbitsRpcError carrying the machine-readable code + its retry
disposition, so callers (and the state machine, Phase D.3) branch on
disposition rather than string-matching the human-readable message.
- error-codes.ts: LnbitsErrorCode (14 codes, signer/transport/app classes)
mirroring the lnbits canonical enum; retryPolicyFor() classifier;
LnbitsRpcError.fromResponse().
- error_code is optional-additive on the wire: an absent or unknown code
maps to internal_error (retry-once), so this is safe to land before lnbits
emits codes — no string-matching, no special parser paths.
- invoice_already_paid is flagged terminal-idempotent (isIdempotentSuccess)
for the cash-out resume-after-reboot case.
Part of Phase D, aiolabs/bitspire#52.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>