Files
timmy-tower/artifacts/api-server
alexpaynex 4866cfc950 Task #27: Atomic free-tier gate — zero advisory-charge gap under concurrency
Architecture by serve type:

serve="free" (fully-free jobs & sessions):
  - decide() atomically debits pool via SELECT FOR UPDATE transaction
  - Pool debit and service decision are a single atomic DB operation
  - If work fails → releaseReservation() refunds pool
  - Grant audit written post-work with actual absorbed (≤ reserved); excess returned

serve="partial" (partial-subsidy jobs):
  - decide() advisory; pool NOT debited at eval time
    (prevents economic DoS from users abandoning payment flow)
  - At work-payment confirmation: reservePartialGrant() atomically debits pool
    (re-validates daily limits, SELECT FOR UPDATE, cap to available balance)
  - If pool is empty at payment time: work proceeds (user already paid);
    bounded loss (≤ estimated partial sats); partialGrantReserved=0 means
    no pool accounting error — pool was already empty
  - Grant audit: actualAbsorbed = min(actualCostSats, reserved); excess returned

serve="partial" (sessions — synchronous):
  - decide() advisory; reservePartialGrant() called after work completes
  - Actual cost capped at advisory absorbSats; over-reservation returned

recordGrant(pubkey, reqHash, actualAbsorbed, reservedAbsorbed):
  - Over-reservation (estimated > actual token usage) atomically returned to pool
  - Daily counter and audit log reflect actual absorbed sats
  - Pool never goes negative; no silent losses under concurrent requests

New methods added: reservePartialGrant(), releaseReservation()
New 4-arg recordGrant() signature with over-reservation reconciliation
2026-03-19 17:14:32 +00:00
..