ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Package.Admission

Description

The single public-version admission gate, shared by the serve path and the mirror worker.

Admitting a public version to a concrete artifact request is a three-step decision: the rules engine decides the version (evalRules), the requested filename selects the artifact (artifactFor), and the integrity-floor admission policy decides whether that artifact's digests are strong enough to gate (classifyArtifacts). Both consumers of that decision -- the serve pipeline's public tarball gate and the mirror worker's ingest-time re-evaluation -- call the one admitArtifact here, so the two contexts cannot drift: a version the worker would freeze into the rule-exempt mirror is exactly a version the serve gate would admit, and a tightened policy (a new deny rule, a raised floor, a withdrawn file) refuses it in both places for the same reason. Each context projects the shared ArtifactAdmission onto its own surface (an HTTP status, a queue ack/redeliver), and those projections are total, so a new admission outcome cannot be silently ignored by either.

Synopsis

Documentation

data ArtifactAdmission Source #

The admission verdict for one requested artifact of one public version -- the shared vocabulary both the serve gate and the worker's ingest re-evaluation project onto their own surfaces.

The constructors separate the deliberate refusals (a rule denial, an integrity-policy refusal, an absent file) from the inability to decide (AdmissionUndecidable), because the two consumers act on that split differently: serve renders a denial 403 and an inability 503/500, the worker retires a denied job (ack, never publish) and leaves an undecidable one to redeliver.

Constructors

AdmissionAdmit Artifact (NonEmpty Hash)

The rules admitted the version, the requested filename selected an artifact, and its digests clear the integrity floor: serve it / mirror it. Carries the artifact and its integrity digests exactly as the floor checked them, non-empty as a fact of admission, so both consumers act on this one floor-checked set (the serve pipeline captures it on the mirror job it enqueues; the worker's tamper gate verifies the fetched bytes against it) rather than each re-deriving and re-guarding it from the artifact.

AdmissionDenied Decision

A rule (or deny-by-default) blocked the version. Carries the Blocked / BlockedByDefault Decision so each consumer renders the deciding rule and reason on its own surface.

AdmissionUndecidable Decision

The version could not be decided (a fail-closed rule whose evaluation was unavailable). Carries the Undecidable Decision with its Transience, so serve can choose 503 vs 500 and the worker can leave the job to redeliver.

AdmissionFileAbsent

The rules admitted the version but no artifact carries the requested filename: a forwarded miss on the serve path, a withdrawn-file drop at the worker -- never a fabricated location.

AdmissionIntegrityMissing

The selected artifact carries no integrity digest of any kind, so its bytes cannot be tied to a tamper-evident fingerprint. Refused by the admission policy (deny-by-default), distinct from AdmissionBelowFloor so the refusal can say which.

AdmissionBelowFloor

The selected artifact carries digests, but none meets the configured public-integrity floor (e.g. a legacy SHA-1 shasum only, under the SHA-256 floor). Refused by the admission policy.

admitArtifact Source #

Arguments

:: EvalContext 
-> [PreparedRule] 
-> MinIntegrity 
-> Text

The requested artifact filename (the client's, or the mirror job's).

-> PackageDetails 
-> IO ArtifactAdmission 

Decide one requested artifact of one public version under current policy: the rules first (the engine's first decisive verdict), then artifact selection by the requested filename, then the integrity-floor admission policy over the selected artifact.

The rules run first so an artifact-level refusal never masks a version-level denial, and no integrity classification is paid for a version a rule already denies. The floor is applied to the selected artifact only (the one whose bytes would be served or mirrored), exactly as the serve path has always gated it.

This is the one admission decision for both contexts. The serve pipeline calls it on a public tarball request; the mirror worker calls it at ingest with the same prepared rules, the same clock, the same configured floor, and the job's own filename -- so the enqueue → process window can only ever narrow what is mirrored (policy tightened, file withdrawn), never admit past the serve gate.

artifactFor :: Text -> PackageDetails -> Maybe Artifact Source #

Select the artifact a request's filename names from a version's distribution files. npm has exactly one artifact per version, so the match is the single file; a many-per-version ecosystem (PyPI) would select the wheel/sdist whose filename the client requested. Nothing when no artifact carries the requested filename -- a forwarded miss, never a fabricated location.