BEGIN; -- Keep transport receipts independently from execution retention. Their absence -- must never be used as proof that a paid invocation did not happen. CREATE TABLE canvas_ai_requests ( project text NOT NULL, workspace_id uuid NOT NULL, account_id uuid NOT NULL, request_id uuid NOT NULL, board_id uuid NOT NULL, run_id uuid, action text NOT NULL CHECK(action IN('run.start','run.cancel','run.apply')), operation_id uuid NOT NULL, payload_hash text NOT NULL, outcome text NOT NULL CHECK(outcome IN('applied','not-applied')), response_status integer NOT NULL, response_body jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY(project,workspace_id,account_id,request_id), FOREIGN KEY(project,workspace_id) REFERENCES workspaces(project,id) ON DELETE CASCADE ); -- A known zero-effect rejection may be explicitly replanned under another key; -- a completed logical action must never execute twice under different keys. CREATE UNIQUE INDEX canvas_ai_request_operation ON canvas_ai_requests(project,workspace_id,account_id,operation_id) WHERE outcome='applied'; -- Preserve accepted canonical requests that predate this receipt table. INSERT INTO canvas_ai_requests(project,workspace_id,account_id,request_id,board_id,action,operation_id,payload_hash,outcome,response_status,response_body) SELECT project,workspace_id,account_id,idempotency_key,board_id,'run.start',logical_operation_id,payload_hash,'applied',202,create_response FROM canvas_ai_runs WHERE logical_operation_id IS NOT NULL AND create_response->>'protocol_version'='2' ORDER BY created_at,id ON CONFLICT DO NOTHING; COMMIT;