Repository navigation
Backend Configuration
The per-tier backend-selection surface for NoETL's five internal
platform tiers. Phase 10 is the final phase of the
EHDB completion program:
it consolidates the scattered NOETL_EHDB_* per-tier flags into one
coherent, documented schema so an operator can select — and verify —
whether each platform tier runs on EHDB or on its external incumbent.
EHDB is the default, not a lock-in. Every tier stays first-class selectable back to the engine it replaced. A deployment can run the log on JetStream+Postgres and vectors on EHDB, or any per-tier mix.
Platform-only boundary. This surface configures NoETL's internal platform storage tiers. Business/tenant data is never in EHDB — it stays in real business systems (PostgreSQL, Snowflake, Kafka, object stores, …) reached via playbook connectors, entirely outside this surface.
| Tier | Runtime env var | Default backend (program end) | Selectable back to (incumbent) |
|---|---|---|---|
| Event log | NOETL_EHDB_EVENTLOG |
EHDB event-log engine | NATS JetStream + Postgres |
| Projection / read-model | NOETL_EHDB_PROJECTION |
EHDB projection engine | Postgres materializer |
| KV / state | NOETL_EHDB_KV |
EHDB KV engine | NATS KV |
| Object / blob | NOETL_EHDB_OBJECT |
EHDB object engine | external object store (GCS/S3) / Postgres |
| Vector | NOETL_EHDB_VECTOR |
EHDB vector engine | Qdrant |
Each tier is selected on two axes. The mode (NOETL_EHDB_<TIER>) is
the operational state; the backend is derived from it.
Mode (NOETL_EHDB_<TIER>) |
What runs | Serving backend |
|---|---|---|
off (default) |
strict no-op — no EHDB engine opens | external incumbent |
shadow |
EHDB dual-writes + parity-compares, never serves | external incumbent |
primary |
EHDB serves authoritatively, incumbent dual-run parity-checked | EHDB |
Backend derivation is exactly: primary ⇒ EHDB serves; shadow/off ⇒
the external incumbent serves. shadow is a migration state — EHDB is
being written and verified but the incumbent is still authoritative, so the
serving backend is still external.
The consolidated config resolves in this order:
-
Umbrella enable —
NOETL_EHDB_ENABLED(truthy). The single gate every tier sits under. When unset, the whole integration is a strict no-op. -
Per-tier mode —
NOETL_EHDB_<TIER>∈off|shadow|primary, read through each tier's own runtime parser. -
Derived backend —
primary⇒ EHDB, else the external incumbent.
The role env (NOETL_EHDB_CLIENT_ROLE) is orthogonal but load-bearing for
coherence: only data-plane roles (worker/playbook/system) may serve a
tier; control-plane gatekeepers (gateway/api/server) never do.
Phase 10 adds a unifying resolution + validation layer, not a breaking
rename. The existing NOETL_EHDB_* env vars remain the source of truth,
and the worker resolves each tier's mode through the same
<Tier>Mode::from_env parser the runtime dispatch already uses
(ehdb_reference::backends in the ehdb crate; noetl_worker::ehdb::backends
in the worker). So the consolidated view can never drift from actual
behavior: a deployment with no NOETL_EHDB_* env resolves every tier to its
current default (external incumbent, off), and an enabled primary tier
resolves to EHDB exactly as dispatch serves it.
The resolver rejects incoherent combinations with clear operator messages (the runtime stays a disabled-by-default no-op regardless — the validation classifies a misconfig, it does not change the strict-no-op guarantee):
- A tier requesting
shadoworprimarywhileNOETL_EHDB_ENABLEDis unset — a tier cannot shadow-mirror or serve while the umbrella integration is disabled. - A tier requesting
shadow/primaryon a control-plane role (gateway/api/server) — those roles are gatekeepers and never serve a data-plane tier.
The ehdb-selfcheck config verb (alias backends), shipped in the same
image as noetl-worker, prints the resolved per-tier backend+mode matrix.
It is read-only (opens no engine), the render is secret-free (only tier
keys/modes/backends + the role token — never an env value), and its exit
code conveys coherence: 0 coherent, 4 incoherent.
$ ehdb-selfcheck config
{"op":"config","coherent":true,"errors":[],"secret_free":true,"matrix":{
"enabled":false,"role":"worker","control_plane":false,
"integration_mode":"disabled","coherent":true,"errors":[],
"tiers":[
{"tier":"eventlog","env_var":"NOETL_EHDB_EVENTLOG","mode":"off","backend":"external","incumbent":"NATS JetStream + Postgres"},
{"tier":"projection","env_var":"NOETL_EHDB_PROJECTION","mode":"off","backend":"external","incumbent":"Postgres materializer"},
{"tier":"kv","env_var":"NOETL_EHDB_KV","mode":"off","backend":"external","incumbent":"NATS KV"},
{"tier":"object","env_var":"NOETL_EHDB_OBJECT","mode":"off","backend":"external","incumbent":"external object store (GCS/S3) / Postgres"},
{"tier":"vector","env_var":"NOETL_EHDB_VECTOR","mode":"off","backend":"external","incumbent":"Qdrant"}]}}
# exit 0Set the umbrella enable, a data-plane role + local-reference log, and each
tier's flag to primary:
$ NOETL_EHDB_ENABLED=true NOETL_EHDB_MODE=local_reference \
NOETL_EHDB_CLIENT_ROLE=worker NOETL_EHDB_LOCAL_REFERENCE_LOG=/var/lib/ehdb/ref.jsonl \
NOETL_EHDB_EVENTLOG=primary NOETL_EHDB_PROJECTION=primary NOETL_EHDB_KV=primary \
NOETL_EHDB_OBJECT=primary NOETL_EHDB_VECTOR=primary \
ehdb-selfcheck config
# → coherent:true, every tier backend:"ehdb", exit 0Log + vector on EHDB, projection dual-writing (shadow), KV/object left on
their incumbents:
$ NOETL_EHDB_ENABLED=true NOETL_EHDB_MODE=local_reference \
NOETL_EHDB_CLIENT_ROLE=worker NOETL_EHDB_LOCAL_REFERENCE_LOG=/var/lib/ehdb/ref.jsonl \
NOETL_EHDB_EVENTLOG=primary NOETL_EHDB_VECTOR=primary NOETL_EHDB_PROJECTION=shadow \
ehdb-selfcheck config
# → eventlog:ehdb, vector:ehdb, projection:external(shadow), kv:external, object:external; coherent:true, exit 0$ NOETL_EHDB_EVENTLOG=primary ehdb-selfcheck config
# coherent:false, exit 4
# errors: ["[eventlog] NOETL_EHDB_EVENTLOG requests 'primary' but NOETL_EHDB_ENABLED
# is not set — a tier cannot serve from EHDB while the umbrella EHDB
# integration is disabled (set NOETL_EHDB_ENABLED or return
# NOETL_EHDB_EVENTLOG to 'off')"]
$ NOETL_EHDB_ENABLED=true NOETL_EHDB_CLIENT_ROLE=gateway \
NOETL_EHDB_MODE=local_reference NOETL_EHDB_LOCAL_REFERENCE_LOG=/tmp/x.jsonl \
NOETL_EHDB_EVENTLOG=primary ehdb-selfcheck config
# coherent:false, exit 4
# errors: control-plane role 'gateway' never serves a data-plane tierBecause the backend is a runtime choice, rollback is a flag flip with no
redeploy: set the tier's NOETL_EHDB_<TIER> back to shadow (keep verifying)
or off (fully external). The incumbent is authoritative again immediately;
the primary path only ever appends to the EHDB store and never mutates what
the incumbent owns, so there is zero data loss. Each tier also carries a
compile-time PRIMARY_SERVE_ACTIVATED kill switch as a structural
belt-and-suspenders revert. See the per-tier rollback procedures in the
Roadmap Phase 9 sections.
-
ehdbcrate —ehdb-reference::backends: the pure schema (PlatformTier,TierMode,Backend,BackendMatrix), thebackend_for_modederivation, the coherencevalidate(), and the secret-freeto_json()render. Reads no env, opens no engine. (ehdb#252, merged4c0df81.) -
worker—noetl_worker::ehdb::backends:resolve(&EnvMap)maps the process env (through the per-tierfrom_envparsers) into aBackendMatrix, and theehdb-selfcheck configverb prints it. (worker#166, releasedv5.66.0.)
Phase 10 — IMPLEMENTED (2026-07-06). The config surface landed
(cargo test + clippy -D warnings clean; ehdb-selfcheck config matrix
validated in-cluster batched with the Phase-9 kind runs). With Phase 10 the
whole EHDB completion program (Phases 6–10) is code-complete and
kind-validated — every platform tier has a built + shadow-verified engine,
a per-tier reversible primary cutover, and a consolidated backend-selection
surface. The only remaining step is the prod/GKE cutover, gated on the
user, per tier; nothing in prod has changed (prod runs worker v5.52.0,
all NOETL_EHDB_* flags default off).
- Home
- Architecture
- Architecture — the four engines
- Architecture — resilient KV core
- Consistency Invariants (per tier)
- Roadmap
- Sessions Log
- Claude Handoff
- RFC: Completion Program
- RFC: External EHDB Driver
- L1 Command-Bus Cutover (T4/T5 — prepared, human-gated)
- Prod Cutover — Event-Log Tier (Phase 9, Tier 1)
- Runbook: Async Event-Log Mirror
- Durable Event-Log — Prod Durability Sign-off (§C, slice 6)