One API call, three execution backends

POST /cases/{id}/annotate creates a job row, then events.launch() picks a backend from configuration alone. The pipeline code never changes.

events.launch(job)
Chooses on settings, in order. Everything downstream writes back to the same jobs row, so the UI polls one endpoint whichever path ran.
app/services/events.py
1. cloudrun_job set → Cloud Run job
Executes the Nextflow driver as a job with argument overrides, via the Jobs API. Scales to zero between runs; the service account holds only run.jobsExecutorWithOverrides on that one job.
2. pubsub_topic set → Pub/Sub publish
Publishes the job as an event. Argo Events' sensor subscribes and triggers an Argo Workflow on GKE. Decouples the API from the compute entirely; retries and back-pressure belong to the queue.
3. otherwise → local subprocess
Runs Nextflow directly and streams stdout into the job log, so the UI can show live progress. This is what a developer gets with no cloud configured at all.
Nextflow pipeline
NORMALISE → VEP → LOAD_DB
loader writes results
Variants inserted, job marked succeeded, and the run records what evidence it looked up.
has_frequencies, has_effect_scores

The database URL never appears on a command line: it is passed by environment, or as a Nextflow secret, so it stays out of .command.sh and the workflow logs.