-
Notifications
You must be signed in to change notification settings - Fork 0
architecture
How the gateway fits into the NoETL stack and what each internal module does.
For the underlying principle, see Ephemeral Blueprints + Compute-Data Boundary. The gateway is the concrete expression of "gatekeeper only" in that model.
┌─────────────────────────────┐
│ Client (browser, CLI, │
│ partner integration) │
└────┬───────────────────┬────┘
│ session_token │ SSE /events
▼ ▼
┌──────────────────────────────────────────────────┐
│ noetl-gateway (this repo) │
│ │
│ - Auth: /api/auth/login, /api/auth/validate │
│ - GraphQL: executePlaybook │
│ - Proxy: /noetl/* -> noetl-server │
│ - SSE: /events fan-out per client_id │
│ - Callbacks: /api/internal/callback{,async} │
│ - NATS bridge: playbook/state frames │
└────┬─────────────────────────────────────────────┘
│ HTTP /api/execute
│ NATS subscribe
▼
┌──────────────────────────────────────────────────┐
│ noetl-server (FastAPI) + worker pool │
│ noetl-server publishes execution events on │
│ NATS subject prefix `noetl.events.` │
│ the gateway subscribes and re-emits as │
│ playbook/state SSE frames. │
└──────────────────────────────────────────────────┘
Every arrow is a real network hop. The gateway never reaches a domain database; the worker pool never reaches the browser; the browser never reaches the worker pool directly.
Application bootstrap: builds the axum router, wires
middleware (auth, CORS, tracing), and starts the server.
-
login: Auth0 ID-token exchange. Dispatches theapi_integration/auth0/auth0_loginplaybook in noetl-server via the gateway-to-noetl proxy. On success, persists the session and returns the session token. -
validate: confirms a presented session token is still valid. -
middleware: extracts the session token from every authenticated request, verifies it, and attaches the authenticated user record to the request context.
GraphQL endpoint at POST /graphql. The primary mutation is
executePlaybook(path, workload):
- Resolves the authenticated user and
client_id. - Stores
request_id→{ client_id, session_token, execution_id, playbook_path }inRequestStore(NATS K/V). - POSTs to noetl-server's
/api/executeto start the playbook. - Returns
{ requestId, executionId }immediately. The client awaits completion over the SSE channel rather than blocking the HTTP request.
-
GET /events?session_token=...&client_id=...opens an SSE stream. The handler validates the session, registers the client inConnectionHub, sends an initmessageframe containing the assignedclient_id, and merges per-client messages with periodicpingheartbeats. -
ConnectionHubis the in-memory registry ofclient_id→ channel for fan-out.send_to_clientis the universal outbound primitive.
-
POST /api/internal/callbackreceives synchronous callbacks from playbooks (e.g. thesend_success_callbackstep inauth0_login). Looks up therequest_idinRequestStoreand routes the result to the corresponding SSE client as aplaybook/resultframe. -
POST /api/internal/callback/asynchandles asynchronous callbacks the same way.
NATS subscriber that listens on subjects matching
noetl.events.* and forwards a curated allowlist of event
types to interested clients. Forwarded event types
(FORWARDED_EVENT_TYPES):
step.exitplaybook.completedplaybook.failed-
calendar.event.touched(added v2.12.0; orchestrator-side signal that a calendar entry was written, so SPA clients can re-read on a specific signal rather than the genericplaybook.completed)
For each forwarded frame:
- Looks up which
client_idcares about theexecution_id(the subscriber maintains a per-client filter map seeded byexecutePlaybookandsubscribeToExecutioncalls). - Forwards the frame as a
playbook/stateSSE event to that client.
Added in v2.11.0; the allowlist gained
calendar.event.touched in v2.12.0. This lets the SPA-side
waitForExecution move off polling onto a push model.
The gateway used to expose POST /api/subscriptions/firestore
and DELETE /api/subscriptions/{subscription_id} plus a Python
sidecar (scripts/firestore_listener.py) that opened
Firestore onSnapshot watches on the SPA's behalf. This
violated the gatekeeper-only boundary — the gateway was
reaching a domain database to satisfy a client request.
Removed in v2.12.0 (noetl/gateway#18
- Dockerfile / config cleanup in
noetl/gateway#19).
Live-update transport for SPA clients now goes through the
NoETL event stream — the orchestrator emits
calendar.event.touchedevents that the gateway forwards via the SSE channel (seeplaybook_state.rsabove). See Subscriptions for the historical reference page.
NATS K/V store keyed by request_id. Holds the in-flight
mapping needed for callback routing. Cleared when the
corresponding playbook/result frame is delivered.
Proxies authenticated requests under /noetl/* to
noetl-server, attaching the session token's user context as
headers. Lets CLIs and other clients use the same auth model
as the SPA without re-implementing NoETL API calls.
Short-TTL in-memory cache for validated session tokens. Avoids
hammering noetl-server's auth.sessions table on every
request.
NATS connection setup and helpers. Reads NATS_URL from
the environment. NATS connection itself is platform-runtime,
not a business-logic credential (see
secrets-and-credentials rule).
Postgres pool wiring for the auth/session tables. Same platform-runtime classification.
SPA -> POST /api/auth/login { id_token }
SPA <- 200 { session_token, expires_at, user_id }
SPA -> GET /events?session_token=...
SPA <- SSE: message { result: { clientId } }
SPA -> POST /graphql { query: executePlaybook(...), variables }
SPA <- 200 { requestId, executionId }
... worker pool runs the playbook ...
playbook -> POST /api/internal/callback { request_id, status, data }
SPA <- SSE: playbook/result { requestId, executionId, status, data }
SPA -> POST /graphql { executePlaybook(travel/playbooks/catalog/calendar/list, ...) }
SPA <- 200 { requestId, executionId }
... orchestrator writes a calendar event during a chat turn ...
worker -> emits calendar.event.touched on NATS subject noetl.events.<exec_id>.*
playbook_state.rs receives -> ConnectionHub::send_to_client
SPA <- SSE: playbook/state { execution_id, event_type: calendar.event.touched, ... }
SPA: re-runs the read playbook to fetch the updated calendar list
Pre-v2.12.0 the SPA went through POST /api/subscriptions/firestore
and a Python sidecar that watched a Firestore collection. See
Subscriptions for the historical reference.
... worker emits events on NATS subject playbooks.executions.<id>.step.exit ...
playbook_state.rs receives -> ConnectionHub::send_to_client
SPA <- SSE: playbook/state { execution_id, event_type, step_name, at }
... again for playbook.completed ...
SPA <- SSE: playbook/state { execution_id, event_type: playbook.completed, at }
-
SSE events — full frame schema reference,
including
FORWARDED_EVENT_TYPES. - Subscriptions — historical reference for the removed Firestore subscription endpoint (v2.11.0 → v2.12.0).
- Configuration — env vars.
- Deployment — Docker, GKE, Helm.
Gateway
Surfaces
Operations
See also
- noetl wiki
- ops wiki
- travel wiki (consumer)
- Ephemeral Blueprints