fix(v2): decouple listener + skipped-leg audit (fix bundle 2)
Closes H4, H5, M8 from the v2-bitspire review (omnibus follow-up #11). H4 — Decouple invoice listener from distribution. tasks._handle_payment now spawns process_settlement on a background task instead of awaiting it. The LNbits invoice queue is shared across every extension on the node; under load (a machine with 50 LPs, a stalled internal payment, etc.) the previous synchronous path could freeze the queue for everyone. Concurrency is safe because fix bundle 1's claim_settlement_for_processing already prevents double-processing on listener re-fires. RUF006 fix: hold strong refs to in-flight tasks via a module-level set so the GC doesn't collect them mid-flight (asyncio.create_task only weakly references its task). Tasks self-clean via add_done_callback(set.discard). H5 + M8 — Skipped-leg audit rows for stranded sats. Previously, four paths in distribution.py logged a warning and left sats in the machine wallet, marking the settlement 'processed' with no row-level visibility into where the un-paid sats sit: 1. _pay_super_fee: super_fee_pct > 0 but super_fee_wallet_id unset 2. _pay_operator_splits: no commission ruleset (default + override) 3. _pay_dca_distributions: exchange_rate = 0 (fallback path) 4. _pay_dca_distributions: no eligible LPs with positive balance Plus a fifth case the review didn't enumerate but is the same shape: 5. _pay_dca_distributions: no flow-mode LPs at the machine at all Each now writes a dca_payments row with status='skipped', the intended leg_type (super_fee / operator_split / dca), the stranded amount in amount_sats, and a human-readable error_message explaining why. New _record_skipped_leg helper consolidates the pattern. This makes stranded sats visible in: - The machine detail dialog's settlements rows (the legs are filtered into the audit blob alongside completed/failed legs) - The payments CSV export - GET /api/v1/dca/payments?leg_type=... 'skipped' is a documented leg-status value now (alongside pending / completed / failed / voided / refunded) — no schema change since status is TEXT. Knock-on fix: void_open_legs_for_settlement (used by partial-dispense recompute) now also includes status='skipped' in its WHERE clause so a re-run doesn't double-count the audit rows from a prior attempt. 72/72 tests still pass. Lint clean. Refs: aiolabs/satmachineadmin#11 — fix bundle 2 ✅ Remaining in #11: H6 (partial-dispense split ratio) + fix bundle 3 (dead-code purge) + the M and N items. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f4eb7ec928
commit
ecef916dda
4 changed files with 118 additions and 28 deletions
11
crud.py
11
crud.py
|
|
@ -786,14 +786,17 @@ async def append_settlement_note(
|
|||
|
||||
|
||||
async def void_open_legs_for_settlement(settlement_id: str) -> None:
|
||||
"""Marks pending/failed legs as 'voided' before re-running distribution
|
||||
on a partial-dispense recompute. Preserves the rows for audit but stops
|
||||
them from being interpreted as live."""
|
||||
"""Marks open legs as 'voided' before re-running distribution on a
|
||||
partial-dispense recompute. Preserves the rows for audit but stops
|
||||
them from being interpreted as live. Includes 'skipped' so that audit
|
||||
rows from a prior attempt don't double-count once the new attempt
|
||||
writes its own (possibly different) skipped reasons."""
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.dca_payments
|
||||
SET status = 'voided'
|
||||
WHERE settlement_id = :sid AND status IN ('pending', 'failed')
|
||||
WHERE settlement_id = :sid
|
||||
AND status IN ('pending', 'failed', 'skipped')
|
||||
""",
|
||||
{"sid": settlement_id},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue