Decision 1's partial row said "operator confirms → distribute scaled"
and left the undispensed remainder's fate implicit. Made explicit:
partial_pending holds everything — including the share of the notes
that did dispense — until the operator records how the shortfall was
resolved (remediated → full amount; vouchered or written off → scaled),
then one distribution runs at that amount with the existing scaling
arithmetic. A vouchered remainder waits for redemption or expiry.
The alternative (scaled part now, remainder on resolution) is recorded
as deferred, not rejected: it needs a second additive distribution pass
the repo lacks, and a partial is almost always a terminal fault that
has latched cash-out off, so resolution is hours. Decided 2026-10-10.
Also carries the hold-invoice decision rule that fell out of the same
analysis: dispensed > 0 → settle, dispensed == 0 → cancel. An exit jam
that reports zero cancels cleanly — the customer is charged nothing and
the stuck note is the operator's to recover — so hold invoices remove
owed-cash for full faults and exit jams, not for true partials.
Records where the cash-out design is heading so Decisions 1–7 are made
with the destination in view:
- Hold invoices move authorize/capture from spirekeeper into the
Lightning layer. LNbits core already has create/settle/cancel
(lndrest + lndgrpc only; not yet on the nostr-transport). Settlement is
all-or-nothing per HTLC, so a full fault cancels cleanly but a partial
fault still needs a voucher — hold invoices remove owed-cash for the
common case, not every case.
- Vouchers: a fiat-denominated claim at the original rate, a liability
row linked to its origin settlement, whose undispensed sats stay
undistributed until redemption or expiry.
- CLINK as the eventual favoured customer protocol; the availability
beacon should align with the CLINK Beacon spec rather than grow a third
shape.
- Operator notification is a Nostr event to the operator's pubkey, not
email/SMS. The pubkey is already on the LNbits account; nsecbunkerd
supports nip44 so no operator ever needs their nsec.
- An operator-facing error glossary, seeded from the F56-BDU error list
and this incident's 78 42.
- Cross-reference to the Lamassu Port Backlog, and the finding that
bitSpire carried lamassu's narrow GTQ window byte for byte.
- Bay layout is machine-authoritative (VITE_LAMASSU_CASSETTES on first
boot, then state.db); spirekeeper adopts and deletes absent positions;
no operator document describes any of it, and fleet targets are keyed
by hostname so a second Tejo cannot join without a new flake target.
Refs #122
A customer paid a 40 EUR cash-out, a note jammed at the cassette exit,
and the dashboard showed `processed`. The machine had recorded the
failure correctly. Nothing it knew ever left the box.
The root is ordering, not display: spirekeeper spawns process_settlement
the instant the payment lands, which is before the machine has begun to
dispense. The legs are paid sub-second; the dispense fails afterwards;
and the one remediation tool refuses once any leg has completed. It is
unreachable for the exact case it was built for.
ADR-005 makes payment the authorization and dispense confirmation the
capture — distribution waits for the machine's report. The report is a
report_dispense RPC (not the state doc: ADR-004's losing-writer problem),
carried through a durable outbox, sent on success and failure, adopting
lamassu's dispense_confirmed / error / error_code taxonomy and its
per-bay action log — which the machine already records in cassette_bills
and simply never ships.
Deviates from lamassu in three places it got wrong or never did: a
zero-dispensed report that arrives with an error is treated as
unverified, not as zero; a mechanical fault is its own customer screen
with evidence and is not "out of cash"; and terminal dispenser faults
latch cash-out off until a recount or an explicit operator op, because
re-initialising does not move a stuck note.
Closes the review loop with ten findings outside the ADR's decisions.
Refs #122, #27, #78