refactor(v2): canonical sat-amount vocabulary + delete Lamassu-era reverse-derivation
Cross-codebase decision logged at memory `reference_sat_amount_vocabulary.md`
and at `~/dev/coordination/log.md` (2026-05-26). Canonical names with
explicit units across satmachineadmin, lamassu-next, atm-tui:
- `wire_sats` — actual Lightning payment amount (direction-agnostic;
was `gross_sats`, only "gross" for cash-out)
- `principal_sats` — market-rate sats before commission (unchanged)
- `fee_sats` — commission (was `commission_sats` internally;
already the wire format)
- `fee_fraction` — commission rate as unit fraction in [0, 1]
(was `*_pct` / `fee_percent`; eliminates the
latent 100x bug from `feePercent * 100` on the
lamassu-next side)
Invariants enforced in bitspire._assert_sat_invariants on every
parsed settlement — range (all sats >= 0, 0 <= fee_fraction <= 1) +
direction-specific sum:
- cash-out: wire_sats == principal_sats + fee_sats
- cash-in: wire_sats == principal_sats - fee_sats
AND fee_sats <= principal_sats
Breaches raise SettlementInvariantError; tasks._handle_payment
records the row as `status='rejected'` with the exception message
and skips distribution. Attribution failure path symmetric.
Schema changes (m001 + m006):
- dca_settlements.gross_sats -> wire_sats
- dca_settlements.commission_sats -> fee_sats
- super_config.super_fee_pct -> super_fee_fraction
- dca_commission_splits.pct -> fraction
- dca_machines.fallback_commission_pct DROPPED (obsolete)
- dca_settlements.used_fallback_split DROPPED (obsolete)
m006 idempotently renames + drops columns on existing installs;
m001 lays down the canonical schema for fresh installs.
Obsolete code removed (Lamassu-era reverse-derivation):
- calculations.calculate_commission — back-derived principal+fee
from gross-with-commission-baked-in. v2 stamps both directly.
- calculations.calculate_exchange_rate — bitSpire stamps directly.
- bitspire._parse_fallback — sole caller of calculate_commission.
- Machine.fallback_commission_fraction — only read by _parse_fallback.
- DcaSettlement.used_fallback_split — only written by _parse_fallback.
parse_settlement now raises SettlementMetadataError if Payment.extra
lacks the bitSpire stamp or required absolute sat fields. No silent
back-derivation; upstream-bug surfacing via dashboard rejection.
Frontend (JS + Quasar templates) updated for the column renames and
the removed fallback fields. Settlements table renders "Wire" + "Fee"
columns; the "(fallback split)" warning badge is gone.
Tests:
- test_calculations.py: kept distribution tests; deleted
calculate_commission + calculate_exchange_rate tests.
- test_two_stage_split.py: renamed variables; rewrote docstring
value literals (e.g. `super_fee_fraction=0.30` not `=30%`).
- test_nostr_attribution.py: dropped fallback_commission_fraction
from machine fixture.
- 72/72 pass on regtest container.
Cross-codebase follow-ups tracked in coordination log:
- lamassu-next: rename `fee_percent` -> `fee_fraction` on
Payment.extra + state.db; drop the `* 100` at lightning.ts:780.
- atm-tui: read `fee_fraction` column in db.zig.
Memory artefacts:
- reference_sat_amount_vocabulary.md (canonical + invariants)
- feedback_pct_to_fraction_renames_need_value_sweep.md (gotcha)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6348c55e37
commit
d717a6e214
12 changed files with 530 additions and 681 deletions
41
crud.py
41
crud.py
|
|
@ -78,10 +78,9 @@ async def create_machine(operator_user_id: str, data: CreateMachineData) -> Mach
|
|||
"""
|
||||
INSERT INTO satoshimachine.dca_machines
|
||||
(id, operator_user_id, machine_npub, wallet_id, name, location,
|
||||
fiat_code, is_active, fallback_commission_pct, created_at, updated_at)
|
||||
fiat_code, is_active, created_at, updated_at)
|
||||
VALUES (:id, :operator_user_id, :machine_npub, :wallet_id, :name,
|
||||
:location, :fiat_code, :is_active, :fallback_commission_pct,
|
||||
:created_at, :updated_at)
|
||||
:location, :fiat_code, :is_active, :created_at, :updated_at)
|
||||
""",
|
||||
{
|
||||
"id": machine_id,
|
||||
|
|
@ -92,7 +91,6 @@ async def create_machine(operator_user_id: str, data: CreateMachineData) -> Mach
|
|||
"location": data.location,
|
||||
"fiat_code": data.fiat_code,
|
||||
"is_active": True,
|
||||
"fallback_commission_pct": data.fallback_commission_pct,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
},
|
||||
|
|
@ -555,14 +553,14 @@ async def create_settlement_idempotent(
|
|||
"""
|
||||
INSERT INTO satoshimachine.dca_settlements
|
||||
(id, machine_id, payment_hash, bitspire_event_id, bitspire_txid,
|
||||
gross_sats, fiat_amount, fiat_code, exchange_rate, principal_sats,
|
||||
commission_sats, platform_fee_sats, operator_fee_sats,
|
||||
used_fallback_split, tx_type, bills_json, cassettes_json,
|
||||
wire_sats, fiat_amount, fiat_code, exchange_rate, principal_sats,
|
||||
fee_sats, platform_fee_sats, operator_fee_sats,
|
||||
tx_type, bills_json, cassettes_json,
|
||||
status, error_message, created_at)
|
||||
VALUES (:id, :machine_id, :payment_hash, :bitspire_event_id,
|
||||
:bitspire_txid, :gross_sats, :fiat_amount, :fiat_code,
|
||||
:exchange_rate, :principal_sats, :commission_sats,
|
||||
:platform_fee_sats, :operator_fee_sats, :used_fallback_split,
|
||||
:bitspire_txid, :wire_sats, :fiat_amount, :fiat_code,
|
||||
:exchange_rate, :principal_sats, :fee_sats,
|
||||
:platform_fee_sats, :operator_fee_sats,
|
||||
:tx_type, :bills_json, :cassettes_json, :status,
|
||||
:error_message, :created_at)
|
||||
""",
|
||||
|
|
@ -572,15 +570,14 @@ async def create_settlement_idempotent(
|
|||
"payment_hash": data.payment_hash,
|
||||
"bitspire_event_id": data.bitspire_event_id,
|
||||
"bitspire_txid": data.bitspire_txid,
|
||||
"gross_sats": data.gross_sats,
|
||||
"wire_sats": data.wire_sats,
|
||||
"fiat_amount": data.fiat_amount,
|
||||
"fiat_code": data.fiat_code,
|
||||
"exchange_rate": data.exchange_rate,
|
||||
"principal_sats": data.principal_sats,
|
||||
"commission_sats": data.commission_sats,
|
||||
"fee_sats": data.fee_sats,
|
||||
"platform_fee_sats": data.platform_fee_sats,
|
||||
"operator_fee_sats": data.operator_fee_sats,
|
||||
"used_fallback_split": data.used_fallback_split,
|
||||
"tx_type": data.tx_type,
|
||||
"bills_json": data.bills_json,
|
||||
"cassettes_json": data.cassettes_json,
|
||||
|
|
@ -840,9 +837,9 @@ async def reset_settlement_for_retry(
|
|||
async def apply_partial_dispense(
|
||||
settlement_id: str,
|
||||
*,
|
||||
new_gross_sats: int,
|
||||
new_wire_sats: int,
|
||||
new_principal_sats: int,
|
||||
new_commission_sats: int,
|
||||
new_fee_sats: int,
|
||||
new_platform_fee_sats: int,
|
||||
new_operator_fee_sats: int,
|
||||
new_fiat_amount: float,
|
||||
|
|
@ -858,9 +855,9 @@ async def apply_partial_dispense(
|
|||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.dca_settlements
|
||||
SET gross_sats = :gross,
|
||||
SET wire_sats = :gross,
|
||||
principal_sats = :principal,
|
||||
commission_sats = :commission,
|
||||
fee_sats = :commission,
|
||||
platform_fee_sats = :platform,
|
||||
operator_fee_sats = :operator,
|
||||
fiat_amount = :fiat,
|
||||
|
|
@ -875,9 +872,9 @@ async def apply_partial_dispense(
|
|||
""",
|
||||
{
|
||||
"id": settlement_id,
|
||||
"gross": new_gross_sats,
|
||||
"gross": new_wire_sats,
|
||||
"principal": new_principal_sats,
|
||||
"commission": new_commission_sats,
|
||||
"commission": new_fee_sats,
|
||||
"platform": new_platform_fee_sats,
|
||||
"operator": new_operator_fee_sats,
|
||||
"fiat": new_fiat_amount,
|
||||
|
|
@ -1013,9 +1010,9 @@ async def replace_commission_splits(
|
|||
await db.execute(
|
||||
"""
|
||||
INSERT INTO satoshimachine.dca_commission_splits
|
||||
(id, machine_id, operator_user_id, target, label, pct,
|
||||
(id, machine_id, operator_user_id, target, label, fraction,
|
||||
sort_order, created_at)
|
||||
VALUES (:id, :machine_id, :uid, :target, :label, :pct,
|
||||
VALUES (:id, :machine_id, :uid, :target, :label, :fraction,
|
||||
:sort_order, :created_at)
|
||||
""",
|
||||
{
|
||||
|
|
@ -1024,7 +1021,7 @@ async def replace_commission_splits(
|
|||
"uid": operator_user_id,
|
||||
"target": leg.target,
|
||||
"label": leg.label,
|
||||
"pct": leg.pct,
|
||||
"fraction": leg.fraction,
|
||||
"sort_order": leg.sort_order,
|
||||
"created_at": now,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue