ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Server.Pipeline.Internal

Description

Internal guts of the serve pipeline (Ecluse.Core.Server.Pipeline), exposed for tests without widening that module's two-handler public API -- the .Internal convention, as Ecluse.Core.Credential.Refresh.Internal uses. Importing it opts out of the public module's stability promise.

It holds the operator-facing warning helpers for the bad-upstream and misconfiguration conditions the response-bound guards leave silent -- an upstream whose body does not decode into a usable packument (logDecodeFailure), one whose packument self-reports a name for a different package (logNameMismatch), a mount whose configured base URL cannot be formed into a request (logUpstreamUnformable), and an upstream the transport could not reach (logUpstreamUnreachable) -- each surfaced at a WarningS through the ambient katip context before the contribution degrades. The conditions themselves are classified on the serve path as a typed MetadataError; this module only renders their warning lines. Alongside them sit the pure integrity-floor admission and the metric-label projections the serve path records.

Synopsis

Documentation

logDecodeFailure :: KatipContext m => PackageName -> m () Source #

Log a parse failure at WarningS -- the one bad-upstream condition the response-bound guards leave silent: the upstream answered, but its body did not decode into the typed view and raw document the serve path needs. Same fail-closed degrade and the same module/package payload convention as the breach log in Ecluse.Core.Server.Pipeline, so an operator sees an undecodable upstream distinctly rather than as silence. Emitted through the ambient katip context (the request's, so the line carries its trace-correlation dd).

logNameMismatch :: KatipContext m => PackageName -> Text -> Text -> m () Source #

Log an upstream name mismatch at WarningS before the contribution degrades: the origin answered, but its packument self-reported a name for a different package than the one requested, so it is dropped as untrusted for this request. The structured payload carries both names and the origin (its base URL) -- the high-cardinality identifiers that belong on the log line -- so an operator can tell a misconfigured or hostile upstream from an ordinary outage. Same fail-closed degrade and payload convention as logDecodeFailure.

logUpstreamUnformable :: KatipContext m => PackageName -> Text -> UrlFormationError -> m () Source #

Log an unformable upstream request URL at WarningS before the contribution degrades: the base URL configured for this origin is empty or could not be parsed into a request, so no fetch could even be attempted. A configuration fault, surfaced distinctly from a decode failure or a transient outage (it carries its own MetadataUrlUnformable) so an operator sees a misconfigured mount rather than an upstream that merely appears unreachable. The structured payload carries the origin and the rendered URL fault; same fail-closed degrade and payload convention as logNameMismatch.

logUpstreamUnreachable :: KatipContext m => PackageName -> Text -> TransportFault -> m () Source #

Log an unreachable upstream at WarningS before the contribution degrades: the transport failed before a usable body returned (a timeout, a refused connection, a TLS refusal), so the origin contributes nothing this request. The structured payload carries the origin, the bounded TransportCause, and the rendered detail, so an operator can tell an outage apart from a decode failure or a misconfigured mount; same fail-closed degrade and payload convention as logUpstreamUnformable.

Integrity-floor admission (pure)

admitByIntegrity :: IntegrityFloor floor => floor -> ServeDecision -> ServeDecision -> PackageInfo -> (PackageInfo, [ServeDecision]) Source #

Apply an integrity-floor admission policy to a PackageInfo, keeping only the versions whose strongest digest meets the floor and projecting the rest to refusals. A version whose digests are all weaker than the floor (or absent) cannot be tied to a floor-strength tamper-evident fingerprint, so it is dropped from the served listing rather than served a client could never safely verify. Used by both gates: the public gate (gatePublic) with the hard-floored MinIntegrity, and the trusted gate (admitTrusted) with the loosenable MinTrustedIntegrity. Returns the admissible PackageInfo (with dist-tags pruned to the kept keys, exactly as restrictToSurvivors prunes for the rules; each kept version carries its own publish time, so restricting the versions carries the times with it) and the refusals for the dropped versions: BelowIntegrityFloor for a too-weak digest, MissingIntegrity for none at all, each feeding the no-survivors status.

Metric-label projections (pure)

packumentServeDecision :: [ServeDecision] -> Decision Source #

Classify a no-survivors packument outcome into the bounded ecluse.serve.decision value: a forbidden set is a denial, any other non-served status a transient unavailability. (A served set is recorded as an admit at the call site, not here.)

serveDecisionClass :: ServeDecision -> Decision Source #

Classify a single artifact-path serve decision into the bounded metric decision: a policy or integrity refusal is a denial, an upstream outage or invalid response an unavailability.

denialLabels :: RejectReason -> (Maybe Text, ReasonClass) Source #

Map a reject reason to the ecluse.rule.denials labels: the deciding rule (only a policy denial names one) and the bounded reason class.

evalTier :: [PreparedRule] -> Tier Source #

The rule-evaluation tier a duration is attributed to, from the mount's rule set: a mount with any resilient (effectful) rule is attributed to the effectful tier; a purely-pure rule set reduces to the structural tier. The resilience policy a prepared rule carries -- not a separate list -- is what distinguishes an effectful rule now that the two tiers are one engine.

transienceCause :: Transience -> Cause Source #

Map an undecidable verdict's transience to the bounded ecluse.rule.effectful.failures cause: a retryable cause is a connection-class fault (the source was unreachable now), a permanent one the catch-all other.

Metric emits (off a serve outcome)

recordDenials :: MetricsPort -> [ServeDecision] -> IO () Source #

Record the ecluse.rule.denials counter for each rejected decision, labelled by the bounded reason class and -- for a policy denial -- the deciding rule name (denialLabels). An admit records nothing.

recordEffectfulFailures :: MetricsPort -> [Decision] -> IO () Source #

Count each effectful-rule failure among a packument's per-version decisions: an Undecidable is an effectful rule whose source could not be consulted, so it is the effectful-failure signal, classified to a bounded cause by its transience (transienceCause). A decided version (allowed or denied) is not a failure.

Denial audit trail (structured log)

data VersionVerdict Source #

A per-version serve outcome that keeps the version (the decision's subject), so a denial's audit line can name it. gatePublic preserves it rather than dropping the version when it projects to ServeDecision.

newtype Metadata Source #

An extensible bag of audit fields folded into a denial line's JSON at emit time. It lives here, at the audit boundary, deliberately not on the pure Decision: the rule engine carries no logging concern, and new audit data (a CVE id, an EPSS score) is added at this layer without threading a field through the decision path.

Constructors

Metadata (Map Text Text) 

data DenialAudit Source #

Everything one denial audit line records: typed and stable. The advisory DbEtag is the database active when the request was admitted (carried on the EvalContext); it is named as active at emit, never claimed as the database the decision was evaluated against, since a shadow-swap may land mid-request.

denialAuditPayload :: DenialAudit -> SimpleLogPayload Source #

Render a DenialAudit to the structured payload katip folds into the line's data object.

logDenials :: KatipContext m => PackageName -> Maybe DbEtag -> [VersionVerdict] -> m () Source #

Emit one audit log line per denied version, denials only (an admit logs nothing). Companion to recordDenials, which counts the same denials as metrics. The DbEtag is the advisory database active at emit (from the request's EvalContext), so the line answers "which database was live when this verdict was logged", not "which database produced it".