| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Composition
Description
The composition-root wiring: turn a validated Config and the
process-global credential providers into the served MountBindings, failing fast
and aggregated on any boot problem.
This is the pure, IO-free heart of the composition root (Ecluse calls it): it
holds no sockets, no network, and no real clock of its own — the clock and the
ecosystem-to-adapter resolver are injected — so the boot-time validation is
unit-tested without opening a listener, mirroring how Env assembly is
kept pure of IO.
Global providers, per-mount reference
A CredentialProvider is the service's own cloud identity,
built once from the environment layer (initCredentialProviders) and held
process-global; a mount does not carry a provider, it only names which backend
it draws on (its mtCredential). The boot-time check is the resolution of that
reference: every distinct credential backend named across all mounts must resolve
to an initialized provider, or the app halts at boot (see
docs/architecture/cloud-backends.md → "Credential Provider"). Only the
static backend has a leaf (from MIRROR_TARGET_TOKEN); a mount naming
codeartifact or adc resolves to no provider and is an honest boot failure.
Fail-fast at boot
Three boot failures are aggregated into one report so a single run shows every
problem: a rule policy that does not resolve (PolicyBootError, surfaced by
loadConfig), a configured mount whose ecosystem has no adapter
wired (MissingAdapter), and a mount naming a credential backend with no
initialized provider (UnresolvedCredential). A bad configuration is thus a
loud, immediate startup failure, never a quietly mis-enforced or half-wired state
(see docs/architecture/configuration.md → Validation).
Synopsis
- data CredentialProviders
- initCredentialProviders :: CredentialReporters -> EnvConfig -> IO (Either [BootError] CredentialProviders)
- initializedBackends :: CredentialProviders -> Set CredentialBackend
- lookupProvider :: CredentialBackend -> CredentialProviders -> Maybe CredentialProvider
- planMirrorCredential :: EnvConfig -> Either [BootError] (Maybe CodeArtifactConfig)
- resolveCodeArtifactConfig :: EnvConfig -> Either [BootError] CodeArtifactConfig
- data BootError
- = PolicyBootError PolicyError
- | MissingAdapter Ecosystem
- | UnresolvedCredential Ecosystem CredentialBackend
- | QueueProviderUnavailable QueueBackend
- | QueueRegionMissing
- | QueueUrlMissing QueueBackend
- | QueueEndpointMalformed Text
- | MirrorCredentialProviderUnavailable CredentialBackend
- | CodeArtifactConfigMissing Text
- | CodeArtifactConfigInvalid Text Text
- | CodeArtifactMintFailed Text
- renderBootError :: BootError -> Text
- planMounts :: (Ecosystem -> Maybe PackumentDeps -> Maybe MountBinding) -> IO UTCTime -> CredentialProviders -> EnvConfig -> Maybe ConfigDoc -> Either [BootError] [MountBinding]
- composeBindings :: (Ecosystem -> Maybe PackumentDeps -> Maybe MountBinding) -> IO UTCTime -> CredentialProviders -> Config -> Either [BootError] [MountBinding]
- data MirrorQueuePlan
- planMirrorQueue :: EnvConfig -> Either [BootError] MirrorQueuePlan
- mirrorQueuePlanWarning :: MirrorQueuePlan -> Maybe Text
- memoryQueueBootWarning :: Text
- memoryQueueDropWarning :: Int -> Text
- data PublishTarget = PublishTarget {}
- planPublishTargets :: CredentialProviders -> EnvConfig -> Maybe ConfigDoc -> Either [BootError] [PublishTarget]
- cacheConfigFor :: EnvConfig -> CacheConfig
Global credential providers
data CredentialProviders Source #
The process-global credential providers, keyed by the backend they implement. Built once at the composition root from the environment layer; a mount references one by name and never holds its own.
The keyset (see initializedBackends) is the boot-check's pure surface — a mount
that names a backend absent from it has an unresolved credential reference.
initCredentialProviders :: CredentialReporters -> EnvConfig -> IO (Either [BootError] CredentialProviders) Source #
Build the global credential providers from the environment layer, or the
aggregated boot errors that block them. The mirror-target write provider is
selected by cfgMirrorTargetCredentialProvider (see planMirrorCredential):
static— built fromMIRROR_TARGET_TOKEN(cfgMirrorTargetToken) when set; absent, no static provider is initialized, so a mount namingstaticfails the boot-time credential-reference check.codeartifact— the CodeArtifact inputs are resolved (resolveCodeArtifactConfig); a required input that resolves by neither an explicit key nor the mirror-target host is a fail-loud boot error. On success the generic refresh/cache wrapper around the CodeArtifact mint leaf (newCodeArtifactProvider) is built, which mints once eagerly — so a misconfigured identity fails here at boot. AWS credentials are the ambient container/task role (the standard chain), never an Écluse key. A mint that throws at boot (a transient AWS error, or a permanent one like a bad domain/region or missing permission) is caught and rendered as aCodeArtifactMintFailedboot error rather than escaping as a raw exception, so it joins the aggregated failure block.gcp-artifact-registry— recognised but not built in this binary, so selecting it is a fail-loud boot error rather than a silent fall-through.
The static provider is also built whenever MIRROR_TARGET_TOKEN is present,
independent of the selector, so a static token never goes unused.
The CredentialReporters are handed to the refreshing CodeArtifact provider so its
mint breaker and refresh outcomes record to telemetry; the static provider never
refreshes, so they do not concern it. The composition root supplies the deferred
reporters that go live once the telemetry substrate exists.
initializedBackends :: CredentialProviders -> Set CredentialBackend Source #
The set of credential backends that resolved to an initialized provider — the pure surface the boot-time credential-reference check reasons over.
lookupProvider :: CredentialBackend -> CredentialProviders -> Maybe CredentialProvider Source #
Look up the initialized provider for a backend, Nothing when none is
initialized (the unresolved-reference case the boot check rejects).
Mirror-target credential provider selection
planMirrorCredential :: EnvConfig -> Either [BootError] (Maybe CodeArtifactConfig) Source #
Decide what mirror-target write provider the environment layer selects, as the
pure half of initCredentialProviders: Nothing when the static provider is
selected (its leaf is the MIRROR_TARGET_TOKEN already handled there), Just a
resolved CodeArtifactConfig when codeartifact is selected, or the aggregated
boot errors that block the selection.
The gcp-artifact-registry arm is recognised but not built in this binary, so it is
a fail-loud MirrorCredentialProviderUnavailable boot error — never a silent
fall-through to a different provider, mirroring how planMirrorQueue treats the GCP
queue arm.
resolveCodeArtifactConfig :: EnvConfig -> Either [BootError] CodeArtifactConfig Source #
Resolve the CodeArtifact inputs for the mirror-target token, or the aggregated boot errors naming each input that could not be resolved.
Each required input is resolved __(a) from its explicit MIRROR_TARGET_CODEARTIFACT_*
key, else (b) by parsing the mirror-target URL host__ of the form
{domain}-{owner}.d.codeartifact.{region}.amazonaws.com (the documented host
fallback). The region resolves explicit key → host → AWS_REGION: the endpoint host
encodes the domain's authoritative region, so it outranks the process-wide
AWS_REGION (a cross-region deploy mints against the domain's region, not the
caller's). The mirror-target URL is the resolved one — an unset MIRROR_TARGET_URL
has already folded onto the private upstream — so a private-upstream CodeArtifact
endpoint is parsed too. The optional token-duration carries through
(cfgMirrorCodeArtifactTokenDuration).
The {owner} is a 12-digit AWS account id: a resolved owner (from either source)
that is not 12 digits is a fail-loud CodeArtifactConfigInvalid error, and a host
whose tail after the last hyphen is not an account id is not a CodeArtifact endpoint
at all (so it falls through to the named-key check). If a required input resolves by
neither source, that is a fail-loud CodeArtifactConfigMissing boot error naming
the exact key the operator must set, aggregated so one run reports every problem.
Boot-time wiring
A reason the composition root refuses to start. Every case is a fail-loud boot failure; they are aggregated so a single run reports every problem an operator must fix.
Constructors
| PolicyBootError PolicyError | A rule policy did not resolve (surfaced by |
| MissingAdapter Ecosystem | A configured mount's ecosystem has no adapter wired, so it cannot be served (a loud miss, never a silent drop). Carries the ecosystem. |
| UnresolvedCredential Ecosystem CredentialBackend | A mount names a credential backend with no initialized provider. Carries the ecosystem of the mount and the unresolved backend. |
| QueueProviderUnavailable QueueBackend | The configured mirror-queue backend has no implementation compiled into this binary, so no queue can be built for it. Carries the unavailable backend. An honest refusal — never a silent fall-through to a different backend. |
| QueueRegionMissing | The SQS mirror-queue backend was selected but no AWS region was supplied
( |
| QueueUrlMissing QueueBackend | A cloud mirror-queue backend (e.g. |
| QueueEndpointMalformed Text | The configured SQS endpoint override ( |
| MirrorCredentialProviderUnavailable CredentialBackend | The selected mirror-target credential provider has no implementation compiled into this binary. Carries the unavailable provider. An honest refusal, never a silent fall-through. |
| CodeArtifactConfigMissing Text | A required CodeArtifact input for the mirror-target token could not be resolved from either its explicit key or the mirror-target host. Carries the name of the key the operator must set. |
| CodeArtifactConfigInvalid Text Text | A CodeArtifact input resolved but is malformed (e.g. a domain owner that is not a 12-digit AWS account id). Carries the key and a reason. |
| CodeArtifactMintFailed Text | The eager boot-time CodeArtifact mint threw — a transient AWS error (worth a retry) or a permanent one (a bad domain/region or missing permission, to be fixed). Carries the rendered exception so the cause is legible and aggregated. |
renderBootError :: BootError -> Text Source #
Render a BootError as a human-facing line for the aggregated failure block.
planMounts :: (Ecosystem -> Maybe PackumentDeps -> Maybe MountBinding) -> IO UTCTime -> CredentialProviders -> EnvConfig -> Maybe ConfigDoc -> Either [BootError] [MountBinding] Source #
Validate the environment layer and optional document into the served mount
bindings, or the aggregated boot errors. The composition root's single entry: it
runs loadConfig (whose policy errors become PolicyBootErrors) and then
composeBindings, so policy, missing-adapter, and unresolved-credential failures
all surface from one call.
The ecosystem-to-adapter resolver and the wall-clock source are injected (the
composition root supplies mountBindingFor and getCurrentTime), so
this validation is pure of IO and unit-testable without a socket.
composeBindings :: (Ecosystem -> Maybe PackumentDeps -> Maybe MountBinding) -> IO UTCTime -> CredentialProviders -> Config -> Either [BootError] [MountBinding] Source #
Turn a validated Config into the served MountBindings, or the aggregated
boot errors. For each mount, in ecosystem order: its credential reference must
resolve to an initialized provider, and its ecosystem must resolve to an adapter
(through the injected resolver, fed real PackumentDeps so the packument route is
served rather than the 501 stub). Errors aggregate across every mount.
Mirror-queue backend selection
data MirrorQueuePlan Source #
Which mirror-queue backend the composition root will build, resolved from
config: the durable AWS sqs backend (with its SqsConfig), or the bounded
best-effort in-memory backend (with its MemoryQueueConfig). The pure decision
planMirrorQueue yields; the composition root pattern-matches it to make the one
constructor call, and mirrorQueuePlanWarning tells it whether a boot warning is due.
Constructors
| SqsBackend SqsConfig | The durable AWS SQS backend, built by |
| MemoryBackend MemoryQueueConfig | The bounded in-memory backend, built by
|
Instances
| Show MirrorQueuePlan Source # | |
Defined in Ecluse.Composition Methods showsPrec :: Int -> MirrorQueuePlan -> ShowS # show :: MirrorQueuePlan -> String # showList :: [MirrorQueuePlan] -> ShowS # | |
| Eq MirrorQueuePlan Source # | |
Defined in Ecluse.Composition Methods (==) :: MirrorQueuePlan -> MirrorQueuePlan -> Bool # (/=) :: MirrorQueuePlan -> MirrorQueuePlan -> Bool # | |
planMirrorQueue :: EnvConfig -> Either [BootError] MirrorQueuePlan Source #
Select the mirror-queue backend from the environment layer, yielding the
MirrorQueuePlan the composition root builds the queue from, or the aggregated boot
errors that block it.
This is the pure half of the queue's backend choice — the single place that knows
which backends this binary can build. The AWS sqs backend resolves to a
SqsBackend carrying its SqsConfig (the queue URL and region, with the provider
knobs at their defaults); the composition root passes that to
Ecluse.Queue.Sqs.newSqsQueue. The memory backend resolves to a MemoryBackend
carrying its depth cap, built in-process with no cloud queue (MIRROR_QUEUE_URL and
AWS_REGION are not consulted for it) — an explicit operator choice for a simple,
single-node, or air-gapped deployment, never an automatic fallback (which would
soften the fail-loud-on-misconfig posture); the composition root emits the
memoryQueueBootWarning on selection. The GCP pubsub arm is recognised but not
built, so it is a fail-loud QueueProviderUnavailable boot error rather than a
silent fall-through. MIRROR_QUEUE_URL is optional at the env layer; it is required
here for sqs (the jobs need a queue), so a missing one is a fail-loud
QueueUrlMissing boot error, and a missing AWS_REGION under sqs is a
QueueRegionMissing boot error — the sqs arm aggregates the region, queue-URL, and
endpoint failures, and the whole result is a list so it aggregates with the rest of
the boot-time validation.
When an endpoint override is configured (AWS_ENDPOINT_URL_SQS, else
AWS_ENDPOINT_URL — the AWS-SDK-standard variables), it is parsed into the
backend's SqsEndpoint so the released image can target a local emulator
(ministack) or a VPC endpoint without a test-only code path; a malformed override URL is
a fail-loud QueueEndpointMalformed boot error. With no override, the SQS backend
uses AWS's default endpoint and credential resolution.
mirrorQueuePlanWarning :: MirrorQueuePlan -> Maybe Text Source #
The loud boot warning a MirrorQueuePlan warrants before its queue is built, or
Nothing for a durable backend that needs none. The composition root logs the
Just at WarningS on selection, so an operator who chose the in-memory backend is
told plainly that the mirror is non-durable — never a silent surprise.
memoryQueueBootWarning :: Text Source #
The boot warning emitted when the in-memory mirror-queue backend is selected: it states plainly that the mirror is in-memory, non-durable, and best-effort, and that a lost job is re-mirrored on the next demand (so there is no data loss, only deferred mirroring), so the choice is never mistaken for a durable cloud backend.
memoryQueueDropWarning :: Int -> Text Source #
The cap-overflow drop warning for the in-memory backend, carrying the running
total of dropped jobs (this report is rate-limited at the queue, so it does not fire
per dropped job). A note on a one-line follow-up: a drop metric
(ecluse.mirror.*, S26 PR2) hooks in alongside this log once that catalogue lands.
Publish-side wiring
data PublishTarget Source #
One ecosystem's resolved publish target: the mirror-target endpoint the mirror worker writes approved artifacts to, paired with the credential provider that mints its bearer token.
This is the publish side of the per-ecosystem composition (the serve side is the
mount's PackumentDeps). The worker's single consumer builds a registry-protocol
client from these — the endpoint as its base URL, the provider's token as its
bearer — so the publish client is resolved here at the composition root rather than
re-derived per request.
Constructors
| PublishTarget | |
Fields
| |
planPublishTargets :: CredentialProviders -> EnvConfig -> Maybe ConfigDoc -> Either [BootError] [PublishTarget] Source #
Resolve each configured mount to its publish target, or the aggregated boot
errors. The publish side of planMounts: it validates the same config and resolves
each mount's mirror-target endpoint and write credential, so the worker's publish
client can be built at the composition root.
An unresolved credential reference is the same fail-loud boot error composeBindings
reports for the serve side, so the two surfaces never disagree on what is wired.
Config-derived runtime settings
cacheConfigFor :: EnvConfig -> CacheConfig Source #
The metadata-cache tunables drawn from the validated environment layer — its TTL and entry bound — so a deployment's cache settings flow from config rather than the built-in defaults (see Ecluse.Server.Cache).