-- +goose Up -- Full prompt/response capture for every LLM call, for the platform-admin AI -- inspector (/admin/ai-calls). -- -- Worker logs only ever carried a 500-rune head/tail preview and -- processed_products.gpt_response keeps response metadata without the prompt, so -- there was no way to answer "what exactly did we send for this job". -- -- Rows are large (a category-formula prompt runs several KB) and high volume (one -- per product per language), so they are strictly short-lived: CleanupExpiredAICalls -- prunes them on the worker's retention tick. This table is a debugging buffer, not -- an audit trail — never build billing or reporting on it. CREATE TABLE IF NOT EXISTS ai_call_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Nullable: platform-level probes (admin AI role test) belong to no tenant. company_id UUID REFERENCES companies(id) ON DELETE CASCADE, -- Who triggered the work (job starter / campaign author). Kept on user delete -- so a tenant's recent calls stay readable; the row expires on its own. user_id UUID REFERENCES users(id) ON DELETE SET NULL, job_id UUID, raw_product_id UUID, -- aiprovider role: processing | categorize | seo_meta | campaign | support | probe. role TEXT NOT NULL DEFAULT 'other', provider_mode TEXT NOT NULL DEFAULT '', model TEXT NOT NULL DEFAULT '', system_prompt TEXT NOT NULL DEFAULT '', user_prompt TEXT NOT NULL DEFAULT '', response_text TEXT NOT NULL DEFAULT '', error TEXT NOT NULL DEFAULT '', finish_reason TEXT NOT NULL DEFAULT '', prompt_tokens INTEGER NOT NULL DEFAULT 0, completion_tokens INTEGER NOT NULL DEFAULT 0, total_tokens INTEGER NOT NULL DEFAULT 0, duration_ms INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Primary browse: one company's most recent calls. CREATE INDEX IF NOT EXISTS ai_call_logs_company_created_idx ON ai_call_logs (company_id, created_at DESC); -- Drill into a single job from the Processing page. CREATE INDEX IF NOT EXISTS ai_call_logs_job_idx ON ai_call_logs (job_id, created_at DESC) WHERE job_id IS NOT NULL; -- Retention sweep scans by age across all tenants. CREATE INDEX IF NOT EXISTS ai_call_logs_created_idx ON ai_call_logs (created_at); -- Failures are the rare rows worth finding fast across a whole tenant. CREATE INDEX IF NOT EXISTS ai_call_logs_failed_idx ON ai_call_logs (company_id, created_at DESC) WHERE error <> ''; -- +goose Down DROP TABLE IF EXISTS ai_call_logs;