-- ADR-0026: append-only UsageFact ledger. AgentRun.costUsd/inputTokens/ -- outputTokens become a derived rollup cache; the truth is in UsageFact. CREATE TABLE "UsageFact" ( "id" TEXT NOT NULL, "runId" TEXT NOT NULL, "occurredAt" TIMESTAMP(3) NOT NULL, "kind" TEXT NOT NULL, "provider" TEXT NOT NULL, "model" TEXT, "inputTokens" INTEGER, "outputTokens" INTEGER, "quantity" DECIMAL(18, 6), "unit" TEXT, "costUsd" DECIMAL(18, 8), "costSource" TEXT NOT NULL, "capabilityId" TEXT, "correlationId" TEXT, "metadata" JSONB NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "UsageFact_pkey" PRIMARY KEY ("id") ); CREATE INDEX "UsageFact_runId_occurredAt_idx" ON "UsageFact"("runId", "occurredAt"); CREATE INDEX "UsageFact_runId_kind_idx" ON "UsageFact"("runId", "kind"); CREATE INDEX "UsageFact_provider_model_occurredAt_idx" ON "UsageFact"("provider", "model", "occurredAt"); CREATE INDEX "UsageFact_capabilityId_occurredAt_idx" ON "UsageFact"("capabilityId", "occurredAt"); ALTER TABLE "UsageFact" ADD CONSTRAINT "UsageFact_runId_fkey" FOREIGN KEY ("runId") REFERENCES "AgentRun"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- Backfill: one synthetic model_completion fact per AgentRun that has any -- recorded usage (cost or tokens). correlationId = run id marks these as -- backfill artefacts (real facts use an external correlation id or null). -- Runs with no recorded usage stay fact-less and remain runsWithoutCost, -- matching ADR-0022's "missing cost ≠ zero" rule and the pre-existing -- migration 20260709143000_agent_run_cost_tracking's "no backfill for the -- truly unrecorded" stance. INSERT INTO "UsageFact" ( "id", "runId", "occurredAt", "kind", "provider", "model", "inputTokens", "outputTokens", "costUsd", "costSource", "correlationId", "metadata" ) SELECT 'usagefact_backfill_' || "AgentRun"."id", "AgentRun"."id", COALESCE("AgentRun"."finishedAt", "AgentRun"."startedAt", CURRENT_TIMESTAMP), 'model_completion', "AgentRun"."provider", "AgentRun"."model", "AgentRun"."inputTokens", "AgentRun"."outputTokens", "AgentRun"."costUsd", COALESCE("AgentRun"."costSource", 'unknown'), "AgentRun"."id", '{}'::jsonb FROM "AgentRun" WHERE "AgentRun"."costUsd" IS NOT NULL OR "AgentRun"."inputTokens" IS NOT NULL OR "AgentRun"."outputTokens" IS NOT NULL;