Files
timmy-tower/lib/db/migrations/0005_free_tier.sql

50 lines
2.5 KiB
MySQL
Raw Normal View History

Task #27: Complete cost-routing + free-tier gate — all critical fixes applied 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
2026-03-19 17:02:02 +00:00
-- 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);