Skip to content

Latest commit

 

History

History

trust-api

README.md

trust-api

Python 3.12+ FLIP Trust API CI trust-api Coverage

The trust-api is the gateway service deployed at each participating healthcare Trust site. It polls the FLIP Central Hub for tasks and coordinates local operations — cohort queries, model training, and imaging project management — without exposing patient data externally.

Role in the FLIP Platform

The trust-api acts as the local orchestrator at each Trust:

  1. Cohort queries — polls for and executes OMOP SQL queries from the Central Hub, delegates to data-access-api, and returns aggregated statistics
  2. Imaging projects — creates projects in XNAT via imaging-api in response to approved FL studies
  3. Service health — a background collector probes the trust stack every HEALTH_COLLECT_INTERVAL_SECONDS (imaging-api + data-access-api /health, XNAT via its anonymous buildInfo endpoint, the PACS connector transitively via imaging-api's ping_pacs DIMSE echo, the OMOP database via a raw TCP connect) and attaches the snapshot to the heartbeat; the hub surfaces it on the Connection Status page's per-container drawer. Each PACS echo opens an association to the hospital PACS, so it runs at most every five minutes, the cycles in between report its last result, and a PACS outage can show up to five minutes late
  4. Audit — logs all operations locally for governance purposes

The trust-api polls the flip-api (Central Hub) for tasks. It does not accept inbound requests from the hub or expose an external user interface.

Deployment

The trust-api is deployed as part of the Trust-side stack. In the local test environment it starts as part of:

make up-trusts   # trust services for both mock Trusts

or the full stack:

make up

API documentation (Swagger UI) is available at the port defined by TRUST_API_PORT in this trust's kit file (trust/.env.<CODE>.<env>; templates: trust/.env.GSTT.development.example, trust/.env.KCH.development.example, default 8020):

http://localhost:<TRUST_API_PORT>/docs

Configuration

Key environment variables. Per-trust values (ports, keys, expected trust id) live in this trust's kit file (trust/.env.<CODE>.<env>); hub-shared values (AES_KEY_BASE64, CENTRAL_HUB_API_URL) are synced into the kit's Hub-shared block by make register-trust KIT=<CODE> (live in prod, inherited from the hub .env.<env> in dev):

Variable Description
EXPECTED_TRUST_ID Optional self-check. If set, trust-api aborts at startup when the hub-resolved trust id does not match this value
DATA_ACCESS_API_URL Internal URL of the data-access-api
IMAGING_API_URL Internal URL of the imaging-api
CENTRAL_HUB_API_URL URL of the Central Hub API (for task polling)
TRUST_API_KEY Per-trust API key for authenticating with the Central Hub. Lives in this trust's kit file (trust/.env.<CODE>.<env>), written by make register-trust KIT=<CODE>
AES_KEY_BASE64 Base64-encoded AES-256 key shared with the hub; task payloads arrive as AES-256-GCM envelopes (FLIP#1179). Must be byte-identical to the hub's key: a mismatch fails closed, reporting every task as Invalid payload: failed authentication
POLL_INTERVAL_SECONDS Polling frequency in seconds (default: 5)
COHORT_QUERY_TIMEOUT_SECONDS Timeout on trust-api's one cohort-query request, POST /cohort to data-access-api (default: 300). A cohort re-runs against OMOP at every later stage too, but those are other services' calls (imaging-api → /cohort/accession-ids, fl-client → /cohort/dataframe) and this setting does not reach them
HEALTH_COLLECT_INTERVAL_SECONDS How often the health collector probes the trust services (default: 30)
HEALTH_PROBE_DEGRADED_MS A successful probe slower than this reports degraded (default: 1000)
XNAT_URL Internal URL of XNAT for the health probe (default http://xnat-web:8080)
OMOP_DB_HOST / OMOP_DB_PORT OMOP PostgreSQL address for the TCP health probe (defaults omop-db / 5432)
TRUST_INTERNAL_SERVICE_KEY_HEADER Header name for trust-internal service auth (default X-Trust-Internal-Service-Key)
TRUST_INTERNAL_SERVICE_KEY Per-trust plaintext key. Forwarded outbound on every call to imaging-api and data-access-api so those services can authenticate the caller. Minted by register_trust (make register-trust KIT=<CODE>) into this trust's kit file (trust/.env.<CODE>.<env>).

XNAT_URL, OMOP_DB_HOST/OMOP_DB_PORT, DATA_ACCESS_API_URL, IMAGING_API_URL and TRUST_INTERNAL_SERVICE_KEY_HEADER are internal-topology constants fixed by the compose network, so they default in trust_api/config.py and none of them is injected into trust-api's own compose environment: block — injecting them would let an empty ${VAR} from a kit that omits the line override the code default. XNAT_URL and OMOP_DB_HOST appear in no kit file at all (the kit's OMOP_DB_PORT is a different variable, see below). The last three are in every kit file (trust/.env.example, the GSTT/KCH examples) and in the fl-client's compose block, because the fl-client reads them from env too and its own code defaults are localhost URLs, not the trust-network names; the Helm chart also injects them into trust-api itself (trust/deploy/helm/templates/trust-api.yaml). Override them through the environment only for a non-standard deployment. Note the kit's own OMOP_DB_PORT is a different variable: it is the host-published port for the omop-db container, while trust-api probes the container port over the trust network.

Authentication

trust-api authenticates outbound in two directions:

  • To the Central Hub: every request carries TRUST_API_KEY in the TRUST_API_KEY_HEADER. The hub validates against the api_key_hash column on the trust's row in the trust table.
  • To sibling trust services (imaging-api, data-access-api): every request carries TRUST_INTERNAL_SERVICE_KEY in the configured header. Receivers compare with hmac.compare_digest against their own copy of the same per-trust key. See the public security model for the threat model.

trust-api itself does not receive inbound requests from the hub or any external caller — it only polls outbound — so it does not validate an inbound trust-internal header.

Scaling Assumptions

The trust-api task poller is designed to run as a single replica per trust. The central hub's task-claim endpoint does not use row-level database locking, so running multiple poller replicas for the same trust would cause duplicate task execution.

If horizontal scaling is needed, the hub endpoint (GET /tasks/pending) must be updated to use SELECT ... FOR UPDATE SKIP LOCKED to ensure each task is claimed by exactly one replica.

Testing

Tests are split into tests/ (unit-level, no real backing services — tests/routers/, tests/services/, etc.) and tests/integration/ (real OMOP database via the shared trust/deploy/compose/compose.test.yml stack). See Where does my test go? in CONTRIBUTING.md for the placement rule, and trust/README.md for how the cohort-query end-to-end suite is wired.

make local_test         # ruff + mypy + unit suite (no Docker required)
make integration_test   # ruff + mypy + cohort-query end-to-end suite (Docker required)

Further Reading