ecluse
Safe HaskellNone
LanguageGHC2021

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 listener-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. Its one effect is preparing each mount's rule set (prepare), which allocates per-rule engine state once at boot (a breaker for a resilient rule; the built-in rules need none today), so binding assembly is IO; everything else stays a pure function of the validated config.

The composition root's other concerns live in the sibling modules: the boot-error vocabulary and rendering in Ecluse.Composition.BootError, the credential providers and mirror-target credential selection in Ecluse.Composition.Credential, the mirror-queue backend selection in Ecluse.Composition.MirrorQueue, and the config-derived runtime sizings in Ecluse.Composition.Sizing.

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 with no initialised mirror-write 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.mdValidation).

Synopsis

Boot-time wiring

planMounts :: (Ecosystem -> PackumentDeps -> Maybe PublishDeps -> Maybe MountBinding) -> IO UTCTime -> (Ecosystem -> RuleDeps) -> CredentialProviders -> Limits -> Maybe PublishBudget -> Config -> IO (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, the wall-clock source, and the rules' boot-bound capabilities are injected (the composition root supplies mountBindingFor, getCurrentTime, and each ecosystem's RuleDeps), so this validation opens no socket. The capabilities are per ecosystem because a mount's rules must borrow their ecosystem's advisory database, never a neighbour's. It is IO only because composeBindings prepares each mount's rules (allocating per-rule engine state once at boot).

composeBindings :: (Ecosystem -> PackumentDeps -> Maybe PublishDeps -> Maybe MountBinding) -> IO UTCTime -> (Ecosystem -> RuleDeps) -> CredentialProviders -> Limits -> Maybe PublishBudget -> Config -> IO (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 initialised provider, and its ecosystem must resolve to an adapter (through the injected resolver, which the mount's PackumentDeps are built from). Errors aggregate across every mount. The Limits arrive resolved (the byte cap from the memory plan, Ecluse.Composition.MemoryPlan, married to the pinned structural counts) and are carried onto every mount's deps, so the data plane reads each metadata body bounded (security.md invariant 4).

validateComposition :: Config -> [BootError] Source #

The pure structural validation a boot enforces beyond loadConfig, shared with ecluse check-config so the checker can never pass a configuration the proxy refuses: a served mount whose ecosystem has no registered adapter (MissingAdapter), and the publish policy of every configured publication target (PublishAllowMissing, PublishStaticCredentialNeedsEdge). Pure and side-effect-free: no provider is initialised and no credential minted -- the mirrored-mount credential expectations are already structural on Config itself (each mirrored mount carries the credential loadConfig derived from its target). Only the provider-initialisation check (UnresolvedCredential) stays with composeBindings, which consumes this same function for everything else.

Publish-side wiring

data PublishBudget Source #

The publish-side byte discipline the composition root builds from the memory plan's publish tenant and hands to every publishing mount: the process-wide aggregate byte-admission and the per-request cap (the chunked-body weight). Present exactly when a publication target is configured -- the tenant and the target derive from the same predicate, so a publishing mount without a budget is unrepresentable at the root.

Constructors

PublishBudget 

Fields

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

  • ptEcosystem :: Ecosystem

    The ecosystem this publish target serves.

  • ptMirrorUrl :: Text

    The mirror-target endpoint approved artifacts are published to.

  • ptCredentials :: CredentialProvider

    The provider minting the mirror-target write token.

planPublishTargets :: CredentialProviders -> Config -> 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.