feat(v2): CRUD + per-direction fee cap validation (#38 2/5)
Wires the new directional fee fields through the write path and adds the 15%-per-direction cap guard at the API boundary. CRUD: - create_machine INSERT includes operator_cash_in_fee_fraction + operator_cash_out_fee_fraction (Pydantic default 0 covers existing callers). - update_machine + update_super_config already use generic update_data dict, so the new fields flow through without per-call changes. API boundary (views_api.py): - _assert_machine_fee_cap_safe(operator_in, operator_out) — pairs candidates against current super-config, rejects if (super_X + operator_X) > 0.15 for either direction. Called from api_create_machine + api_update_machine (with partial-PATCH semantics: unset fields keep the machine's current value). - _assert_super_config_cap_safe(new_super_in, new_super_out) — fetches every active machine; rejects with offending-machine name in the 400 detail if any (effective_super + operator) > cap. Called from api_update_super_config. Cap rounding: float arithmetic rounds (super + operator) to 4 decimals (DECIMAL(10,4) precision) before comparing, so the IEEE 754 surprise 0.10 + 0.05 = 0.15000000000000002 doesn't trip the cap. Tests (13 cases, all green): both directions hit the cap, exact-cap acceptance, no-super-config degenerate path, partial PATCH on super-config, offending-machine name in error detail, empty-fleet vacuous safety. Refs: aiolabs/satmachineadmin#38 (Layer 1), coord-log §2026-06-01T07:22Z (cap lock at 15% per direction, defense in depth). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d87d0db324
commit
4cd0041923
3 changed files with 342 additions and 2 deletions
10
crud.py
10
crud.py
|
|
@ -80,9 +80,13 @@ 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, created_at, updated_at)
|
||||
fiat_code, is_active,
|
||||
operator_cash_in_fee_fraction, operator_cash_out_fee_fraction,
|
||||
created_at, updated_at)
|
||||
VALUES (:id, :operator_user_id, :machine_npub, :wallet_id, :name,
|
||||
:location, :fiat_code, :is_active, :created_at, :updated_at)
|
||||
:location, :fiat_code, :is_active,
|
||||
:operator_cash_in_fee_fraction, :operator_cash_out_fee_fraction,
|
||||
:created_at, :updated_at)
|
||||
""",
|
||||
{
|
||||
"id": machine_id,
|
||||
|
|
@ -93,6 +97,8 @@ async def create_machine(operator_user_id: str, data: CreateMachineData) -> Mach
|
|||
"location": data.location,
|
||||
"fiat_code": data.fiat_code,
|
||||
"is_active": True,
|
||||
"operator_cash_in_fee_fraction": data.operator_cash_in_fee_fraction,
|
||||
"operator_cash_out_fee_fraction": data.operator_cash_out_fee_fraction,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue