Fix 1 — Add `estimateRequestCost(request, model)` to PricingService (pricing.ts) - Unified: estimateInputTokens + estimateOutputTokens + calculateWorkFeeUsd - Replaces duplicated estimation in jobs.ts, sessions.ts, estimate.ts Fix 2 — Sessions pre-gate: estimate → decide → execute → reconcile - freeTierService.decide() runs on ESTIMATED cost BEFORE executeWork() - Fixed double-margin: estimateRequestCost already includes infra+margin; convert directly - absorbedSats capped at actual cost post-execution (Math.min) Fix 3 — Correct isFree derivation for partial jobs in advanceJob() (jobs.ts) - isFreeExecution = workAmountSats === 0 (not job.freeTier) - Partial jobs run paid accounting: actual sats, refund, pool credit, deferred grant Fix 4 — Defer ALL grant recording to post-work execution (jobs.ts) - Fully-free path: removed recordGrant from eval time; now in runWorkInBackground - For isFree jobs: absorbCap = actual post-execution cost (calculateActualChargeSats) - For partial jobs: grant deferred from invoice creation to after work completes Fix 5 — Atomic, pool-bounded grant recording with row locking (free-tier.ts) - SELECT ... FOR UPDATE locks pool row inside transaction - actualAbsorbed = Math.min(absorbSats, poolBalance) — pool can never go negative - Daily absorption: SQL CASE expression atomically handles new-day reset - Audit log and identity counter both reflect actualAbsorbed, not requested amount - If pool is empty at grant time, transaction returns without writing Fix 6 — Remove fire-and-forget from all recordGrant() call sites - All three call sites now use await; failures propagate correctly Fix 7 — Add migration 0005_free_tier.sql - Creates timmy_config, free_tier_grants tables - Adds nostr_identities.sats_absorbed_today / absorbed_reset_at columns - Adds jobs.free_tier / absorbed_sats columns - Adds sessions.nostr_pubkey FK column (for migration-driven deploys) - All IF NOT EXISTS — safe to run on already-pushed DBs
50 lines
2.5 KiB
SQL
50 lines
2.5 KiB
SQL
-- Migration: Free-tier cost routing (Task #27)
|
|
-- Adds generosity pool store, grant audit log, and free-tier tracking columns.
|
|
|
|
-- ── timmy_config ─────────────────────────────────────────────────────────────
|
|
-- Key/value store for Timmy's internal config (e.g. generosity_pool_sats).
|
|
|
|
CREATE TABLE IF NOT EXISTS timmy_config (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── nostr_identities: free-tier absorption columns ───────────────────────────
|
|
-- Rolling daily budget: how many sats Timmy has subsidised for this identity
|
|
-- today; resets when absorbed_reset_at is older than 24 h.
|
|
|
|
ALTER TABLE nostr_identities
|
|
ADD COLUMN IF NOT EXISTS sats_absorbed_today INTEGER NOT NULL DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS absorbed_reset_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
|
|
|
-- ── jobs: free-tier tracking columns ─────────────────────────────────────────
|
|
-- free_tier=TRUE → Timmy absorbed part or all of this job's cost from the pool.
|
|
-- absorbed_sats → how many sats Timmy absorbed (NULL or 0 for fully-paid jobs).
|
|
|
|
ALTER TABLE jobs
|
|
ADD COLUMN IF NOT EXISTS free_tier BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN IF NOT EXISTS absorbed_sats INTEGER;
|
|
|
|
-- ── sessions: nostr identity FK ──────────────────────────────────────────────
|
|
-- Added in task #26; included here for completeness in migration-driven deploys.
|
|
|
|
ALTER TABLE sessions
|
|
ADD COLUMN IF NOT EXISTS nostr_pubkey TEXT
|
|
REFERENCES nostr_identities(pubkey);
|
|
|
|
-- ── free_tier_grants ─────────────────────────────────────────────────────────
|
|
-- Audit log: one row each time Timmy absorbs cost on behalf of an identity.
|
|
|
|
CREATE TABLE IF NOT EXISTS free_tier_grants (
|
|
id TEXT PRIMARY KEY,
|
|
pubkey TEXT NOT NULL REFERENCES nostr_identities(pubkey),
|
|
request_hash TEXT NOT NULL,
|
|
sats_absorbed INTEGER NOT NULL,
|
|
pool_balance_after INTEGER NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_free_tier_grants_pubkey
|
|
ON free_tier_grants(pubkey);
|