| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Runtime.Env
Description
The composition root: the single record from which every effectful component is reached.
Env is the one place backend choice is resolved. It holds the proxy's handles
-- the mirror queue foremost -- each an opaque record of functions (the Handle
pattern) whose closures
already capture their backend's private state. Nothing downstream inspects which
backend a handle is; it only applies the field. Alongside the handles it carries the
shared http-client Manager that the data plane (metadata fetch, artifact
streaming) reuses across every request, so connection pooling and TLS setup are
established once.
Two invariants make this hold together:
- No backend SDK appears here.
Envimports only the handle records, never a cloud SDK (noamazonka, no GCP client). Each handle's effectful fields returnIO(not an application monad), so an adapter never imports back into this module -- there is no import cycle and no recursiveEnv-holds-a-handle-whose-methods-need-Envknot (seedocs/architecture/technology-stack.md→ "Key Decisions"). - It is the sole composition root. The server and worker are each a
self-contained entry function over this shared record
(
runServer :: Env -> IO (),runWorker :: Env -> IO ()inEcluse), so the single-process program and any future split into separate binaries both wire up through here and nowhere else (seedocs/architecture/cloud-backends.md→ "Process model").
Request handlers read this Env through a per-request
RequestCtx -- the request runtime projected by
serveRuntimeOf, paired with the matched mount; the mirror worker reads it through the
WorkerRuntime projected by workerRuntimeOf.
Synopsis
- data Env = Env {
- envServeAdmission :: ServeAdmission
- envQueue :: MirrorQueue
- envManager :: Manager
- envPrivateManager :: Manager
- envMetadataCache :: MetadataCache
- envLogEnv :: LogEnv
- envTelemetry :: Telemetry
- envMetrics :: Metrics
- envDdContext :: DdContext
- envWorkerHeartbeat :: WorkerHeartbeat
- newEnvWithAdmission :: ServeAdmission -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env
- withEnvWithAdmission :: MonadIO m => ServeAdmission -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> (Env -> m a) -> m a
- serveRuntimeOf :: Env -> ServeRuntime
- workerRuntimeOf :: WorkerPolicies -> Env -> WorkerRuntime
- data WorkerHeartbeat
- newWorkerHeartbeat :: IO WorkerHeartbeat
- recordPoll :: WorkerHeartbeat -> UTCTime -> IO ()
- lastPoll :: WorkerHeartbeat -> IO (Maybe UTCTime)
Composition root
The composition-root record: the handles plus the shared HTTP manager and the metadata cache, from which the whole effectful shell is reached. See the module header for the no-SDK and sole-composition-root invariants it upholds.
Constructors
| Env | |
Fields
| |
newEnvWithAdmission :: ServeAdmission -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env Source #
Assemble an Env from its constructed handles, an explicit process-wide serve
admission handle, and the two data-plane HTTP Managers (one per origin: the untrusted
public/artifact fetches and the trusted private upstream, both the validating TLS
manager). The executable passes the admission handle sized from its configured bound.
The Managers, MetadataCache, LogEnv, and Telemetry handle are taken as
arguments rather than built here: a Manager owns a connection pool whose lifetime
should be bracketed by the caller that also owns teardown (see withEnvWithAdmission),
and injecting them keeps Env assembly pure of network, logging, and telemetry setup,
so it can be exercised in tests against in-memory handle doubles with no sockets
opened, no scribe attached to stdout, and no exporter initialised. Backend
selection happens in the handle smart constructors that produce the arguments;
this only gathers them.
withEnvWithAdmission :: MonadIO m => ServeAdmission -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> (Env -> m a) -> m a Source #
Assemble an Env carrying an explicit serve admission handle and run an action
within its scope: the scope the server and worker run in. The composition root
borrows every resource it holds (the Managers, the Telemetry providers); each
is owned and torn down by the caller that supplied it, so this root has nothing of its
own to release and needs no teardown bracket.
Runtime projections
serveRuntimeOf :: Env -> ServeRuntime Source #
Project the request runtime (Ecluse.Core.Server.Context.ServeRuntime) the serve
path is closed over from the composition root: the two data-plane managers, the
metadata cache and mirror queue, and the OpenTelemetry-backed metric and tracing ports
(metricsPortOf, tracingPortOf).
Built at dispatch per request -- it gathers existing handles and wraps the instrument and
telemetry handles in their ports -- so the core pipeline reads its backends through the
core interface without depending on this application Env.
workerRuntimeOf :: WorkerPolicies -> Env -> WorkerRuntime Source #
Project the worker runtime (Ecluse.Core.Worker.WorkerRuntime) the mirror worker
is closed over from the composition root: the mirror queue, the untrusted
data-plane manager, the consume-loop heartbeat, and the
OpenTelemetry-backed worker metric and tracing ports
(workerMetricsPortOf,
workerTracingPortOf). Built at the worker entry point -- it
gathers existing handles and wraps the instrument and telemetry handles in their worker
ports -- so the core loop reads its backends through the core interface without depending
on this application Env (the analogue of serveRuntimeOf for the serve path).
The per-ecosystem bundles are passed in rather than read from the Env:
they are derived from the served mounts and the resolved publish targets (the same
prepared rules, artifact request formation, and public origin the serve path gates
with, plus each mount's married mirror-write capability), which the composition root
resolves alongside the handles (workerPoliciesFor), so
the worker re-runs current policy against a job before mirroring it through one
codepath with the serve gate, and publishes through the job ecosystem's own bundle.
Worker heartbeat (re-exported from Ecluse.Core.Worker)
data WorkerHeartbeat #
recordPoll :: WorkerHeartbeat -> UTCTime -> IO () #