{- | Écluse: a supply-chain policy proxy for package registries. É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: * __Domain model__: "Ecluse.Core.Package" (the ecosystem-agnostic package vocabulary the rules reason over), "Ecluse.Core.Version" (version identity and per-ecosystem ordering), and "Ecluse.Core.Ecosystem" (the ecosystem tag the rest dispatches on). * __Policy__: "Ecluse.Core.Rules" (deny-by-default evaluation) over the rule types in "Ecluse.Core.Rules.Types". * __Protocol boundary__: "Ecluse.Core.Registry" (the registry-protocol handle), "Ecluse.Core.Registry.Npm.Wire" and "Ecluse.Core.Registry.Npm.Project" (the lenient npm wire decoders and their projection onto the domain model), "Ecluse.Core.Registry.Npm.Route" (the npm path grammar), and "Ecluse.Core.Server.Route" (the shared serve-action 'Route' set and the injected route classifier). * __Cloud handles__: "Ecluse.Core.Credential" (minting the mirror-target write token) and "Ecluse.Core.Queue" (the durable mirror-job hand-off to the worker). * __Mirror worker__: "Ecluse.Core.Worker" (the supervised consume loop that fetches, verifies against the job's integrity digest, and publishes an approved artifact). '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. -} module Ecluse ( -- * Entry point run, -- * Split-ready services runServer, runWorker, -- * npm front door npmServerConfig, mountBindingFor, -- * Composition glue (exposed for direct testing) orExit, BootAborted (..), -- * Default handles unconfiguredRegistry, ) where import Ecluse.Boot import Ecluse.CLI (AppCommand (..), execCLI) import Ecluse.Dredger import Ecluse.Pilot import Ecluse.Proxy run :: IO () run :: IO () run = do cmd <- IO AppCommand execCLI withBootEnv $ \BootEnv bootEnv -> case AppCommand cmd of AppCommand RunProxy -> BootEnv -> IO () runProxy BootEnv bootEnv AppCommand RunPilot -> BootEnv -> IO () runPilot BootEnv bootEnv RunPilotCompile PilotCompileOptions opts -> IO FilePath -> IO () forall (f :: * -> *) a. Functor f => f a -> f () void (LogEnv -> Telemetry -> AppConfig -> PilotCompileOptions -> IO FilePath runPilotCompile (BootEnv -> LogEnv beLogEnv BootEnv bootEnv) (BootEnv -> Telemetry beTelemetry BootEnv bootEnv) (BootEnv -> AppConfig beConfig BootEnv bootEnv) PilotCompileOptions opts) AppCommand RunDredger -> BootEnv -> IO () runDredger BootEnv bootEnv