Files
timmy-tower/artifacts/api-server
alexpaynex ba88824e37 Task #27: Fully atomic free-tier gate — no advisory-charge gap under concurrency
Architecture:
  serve="free" (fully-free jobs/sessions):
    - decide() atomically debits pool via FOR UPDATE transaction at decision time
    - Work starts immediately after, so no window for pool drain between debit+work
    - On work failure → releaseReservation() refunds pool

  serve="partial" (partial-subsidy jobs):
    - decide() is advisory; pool NOT debited at eval time
    - Prevents economic DoS from users who abandon the payment flow
    - At work-payment-confirmation: reservePartialGrant() atomically debits pool
      (re-validates daily limits, uses FOR UPDATE lock)
    - If pool is empty at payment time: job is failed with clear message
      ("Generosity pool exhausted — please retry at full price.")
      Free service pauses rather than Timmy operating at a loss

  serve="partial" (sessions — synchronous):
    - decide() advisory; reservePartialGrant() called after work completes
    - Partial debit uses actual cost capped at advisory limit

Grant reconciliation (both paths):
    - recordGrant(pubkey, reqHash, actualAbsorbed, reservedAbsorbed)
    - actualAbsorbed = min(actualCostSats, reservedAbsorbed)
    - Over-reservation (estimated > actual token usage) returned to pool atomically
    - Daily absorption counter and audit log reflect actual absorbed, not estimate
    - Pool never goes negative; identity daily budget never overstated

Added: freeTierService.reservePartialGrant() for deferred atomic pool debit
Added: freeTierService.releaseReservation() for failure/rejection refund

Result: Zero-loss guarantee — pool debit and charge reduction always consistent.
2026-03-19 17:12:02 +00:00
..