ecluse
Safe HaskellNone
LanguageGHC2021

Ecluse.Boot

Description

Écluse (package ecluse) sits between consumers (developers, CI) and a package registry, applying a configurable resilience policy before any dependency reaches a build, without hosting packages itself. The name is French for a canal lock: a chamber whose gates never open at once. Every dependency is held and cleared through that controlled passage before it is admitted to a build.

The goal is resilience, not malware detection: shrink the blast radius of a bad publish (a hijacked maintainer account, a race-to-publish, a typosquat) rather than promise to recognise malice. Écluse is not a registry: storage is delegated to whatever backend the operator runs (AWS CodeArtifact, GCP Artifact Registry), and Écluse only governs what may be fetched from, and mirrored to, those backends. npm is the first ecosystem; the domain model is ecosystem-agnostic so PyPI and RubyGems can follow.

How a request is cleared

Écluse speaks a registry's native protocol across three read-path registries (the client's, a private upstream of already-vetted packages, and the public registry), and the two request shapes use them differently:

  • A tarball request is gated for that one version: a private-upstream hit is streamed unfiltered (already vetted); on a miss, the proxy fetches the version's public metadata, evaluates the rules, and either streams it from public and enqueues an asynchronous mirror job or returns a denial.
  • A packument (metadata) request is a merge: the private and public upstreams are fetched in parallel, public versions are filtered by the rules while private versions are trusted, and the two are combined into one document (private wins a version collision, an integrity divergence is flagged as a supply-chain signal, and latest is repointed to the newest survivor).

Two properties run through both shapes: the rules engine is deny by default (a version is admitted only if some rule allows it and none denies it), and mirroring is demand-driven, so only versions actually pulled are mirrored, never on the request's critical path.

How the code is organised

Écluse is a functional core with effects at the edges: the policy and protocol logic is pure and trivially testable, and IO is confined to a thin shell. Swappable backends sit behind handles (records of functions chosen at a single composition root), so a new cloud or a new ecosystem is an added implementation behind an existing handle, not a structural change.

The library's vocabulary, roughly from the pure core outward:

run is the entry point the ecluse executable invokes (see Main). It lives in the library, not in app/Main.hs, so the composition root is a single importable unit and app/Main.hs stays a thin shell that only calls it.

Further reading

docs/architecture.md is the systems-design index: the vision, the end-to-end request lifecycle, and a map to the per-concern design documents. CONTRIBUTING.md covers the codebase layout and testing strategy, and STYLE.md the coding and documentation conventions.

Synopsis

Documentation

data BootEnv Source #

The boot context assembled once at start-up and handed to each subcommand: the validated configuration, the process logger, and the telemetry handle. withBootEnv builds it, and the ecluse entry point (see Ecluse) dispatches the selected subcommand over it. The heavier serve- and worker-side handles (the HTTP managers, the mirror queue, the metadata cache) are built later, per subcommand (see Ecluse.Proxy).

Constructors

BootEnv 

Fields

  • beConfig :: AppConfig

    The application-level configuration slice the subcommands read.

  • beLogEnv :: LogEnv

    The process structured-logging environment.

  • beTelemetry :: Telemetry

    The telemetry handle, inert unless ECLUSE_TELEMETRY enabled it.

  • beConfigFull :: Config

    The whole loaded configuration document, for subcommands that need more than beConfig (the serve path's mount and rule wiring, for one).

withBootEnv :: (BootEnv -> IO ()) -> IO () Source #

Assemble the BootEnv and run action within it: load and validate the configuration (failing fast on any error), apply the runtime posture, build the logger, and bracket the telemetry substrate for the action's lifetime.

data BootAborted Source #

Raised to abort start-up after a boot phase has reported its aggregated failure to stderr. A distinct type -- rather than a bare exitFailure -- so the abort is observable in a test without the process actually exiting; uncaught, it propagates to main and the runtime exits non-zero, the operator-facing fail-fast.

Constructors

BootAborted 

orExit :: (e -> Text) -> Either e a -> IO a Source #