| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.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 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(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 ()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 -- 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
- envRegistry :: RegistryClient
- envQueue :: MirrorQueue
- envManager :: Manager
- envPrivateManager :: Manager
- envMetadataCache :: MetadataCache
- envLogEnv :: LogEnv
- envTelemetry :: Telemetry
- envMetrics :: Metrics
- envDdContext :: DdContext
- envWorkerHeartbeat :: WorkerHeartbeat
- newEnv :: RegistryClient -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env
- newEnvWithAdmission :: ServeAdmission -> RegistryClient -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env
- withEnv :: MonadUnliftIO m => RegistryClient -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> (Env -> m a) -> m a
- withEnvWithAdmission :: MonadUnliftIO m => ServeAdmission -> RegistryClient -> 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
| |
newEnv :: RegistryClient -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env Source #
Assemble an Env from its constructed handles 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 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.
newEnvWithAdmission :: ServeAdmission -> RegistryClient -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> IO Env Source #
withEnv :: MonadUnliftIO m => RegistryClient -> MirrorQueue -> 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.
withEnvWithAdmission :: MonadUnliftIO m => ServeAdmission -> RegistryClient -> MirrorQueue -> Manager -> Manager -> MetadataCache -> LogEnv -> Telemetry -> WorkerHeartbeat -> (Env -> m a) -> m a Source #
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 publish-side registry
client, 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 re-evaluation bundles are passed in rather than read from the Env: they
are derived from the served mounts (the same prepared rules and public origin the serve path
gates with), which the composition root resolves alongside the handles, so the worker re-runs
current policy against a job before mirroring it through one codepath with the serve gate.
Worker heartbeat (re-exported from Ecluse.Core.Worker)
data WorkerHeartbeat #
recordPoll :: WorkerHeartbeat -> UTCTime -> IO () #