| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Env
Contents
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 registry-protocol client, the mirror queue, and the outbound-credential
provider — 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(notApp), 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 ()in Ecluse), 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 that pairs it with the matched mount; the
worker/service layer reads it through Ecluse.App's App monad.
Synopsis
- data Env = Env {}
- newEnv :: RegistryClient -> MirrorQueue -> CredentialProvider -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env
- withEnv :: MonadUnliftIO m => RegistryClient -> MirrorQueue -> CredentialProvider -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> (Env -> m a) -> m a
- 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
| |
newEnv :: RegistryClient -> MirrorQueue -> CredentialProvider -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env Source #
Assemble an Env from its constructed handles and the two data-plane HTTP
Managers — the guarded one for the untrusted public/artifact fetches and the
trusted one for the private upstream.
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 withEnv), 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.
withEnv :: MonadUnliftIO m => RegistryClient -> MirrorQueue -> CredentialProvider -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> (Env -> m a) -> m a Source #
Build an Env, run an action against it, and tear it down — even on
exception or asynchronous cancellation. The teardown is bracketed via unliftio,
so the composition root's resources are released along every exit path; this is
the scope within which the server and worker run.
Worker heartbeat
data WorkerHeartbeat Source #
The mirror worker's consume-loop heartbeat: the wall-clock time of the worker's last successful poll of the queue.
It is the worker's own liveness signal, kept apart from the server's HTTP
readiness so single-process health reflects a stalled worker today and a future
standalone worker binary keeps the same probe. The worker recordPolls after each
successful receive (whether or not the batch was empty — an empty long-poll is a
healthy idle, not a stall); a liveness probe reads lastPoll and compares it
against the wall clock to decide whether the loop has gone quiet for too long.
newWorkerHeartbeat :: IO WorkerHeartbeat Source #
Build a fresh WorkerHeartbeat with no poll yet recorded (lastPoll is
Nothing until the worker's first successful receive).
recordPoll :: WorkerHeartbeat -> UTCTime -> IO () Source #
Record the time of a successful queue poll, advancing the heartbeat. Called
by the worker after each receive returns (the loop is alive even on an empty
batch).