BEGIN; CREATE TABLE export_task ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), public_id uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES tenant(id), subject_id uuid NOT NULL, requested_by uuid NOT NULL, scope jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(scope)='object'), status varchar(24) NOT NULL DEFAULT 'QUEUED' CHECK (status IN ('QUEUED', 'RUNNING', 'READY', 'FAILED', 'EXPIRED')), object_ref varchar(700), object_size bigint CHECK (object_size IS NULL OR object_size >= 0), content_digest char(64), expires_at timestamptz, attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0), last_error varchar(500), idempotency_key varchar(255) NOT NULL, request_digest char(64) NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), completed_at timestamptz, CONSTRAINT export_task_subject_tenant_fk FOREIGN KEY (subject_id, tenant_id) REFERENCES subject_profile (id, tenant_id), CONSTRAINT export_task_requester_tenant_fk FOREIGN KEY (requested_by, tenant_id) REFERENCES user_account (id, tenant_id), CONSTRAINT export_task_actor_idempotency_key UNIQUE (tenant_id, requested_by, idempotency_key), CONSTRAINT export_task_ready_fields CHECK ( status <> 'READY' OR (object_ref IS NOT NULL AND object_size IS NOT NULL AND content_digest IS NOT NULL AND expires_at IS NOT NULL AND completed_at IS NOT NULL) ) ); CREATE INDEX export_task_subject_time_idx ON export_task (tenant_id, subject_id, created_at DESC, id DESC); CREATE INDEX export_task_expiry_idx ON export_task (expires_at) WHERE status='READY'; COMMIT;