| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Core.Package.Filter
Description
The ecosystem-agnostic filtering decision for a single public-upstream
packument: which versions survive a rule set, which version dist-tags.latest
resolves to, and the per-version decisions a no-survivors outcome must report.
This mirrors Ecluse.Core.Package.Merge -- the pure fold above the registry handle that
emits a plan rather than a finished document. It reasons over the typed
PackageInfo domain model only; it never touches a registry's wire
format. The per-ecosystem adapter replays this plan onto the raw upstream
document, so unmodeled wire keys survive (the typed model is lossy, so re-encoding
it would drop them). See docs/architecture/registry-model.md → "Decision
surface vs served surface".
Decision, not served surface. A FilterPlan carries exactly the decisions the
filter owns:
- Survivors. A version key survives iff the rules engine
Admittedit; every other verdict -- a denial, deny-by-default, or an undecidable outcome -- drops it. Presence in the served packument is availability (seedocs/research/reverse-engineering/npm.md§8), so a non-approved version is removed rather than flagged. - Resolved
latest. The survivingdist-tags.latestunder the shared keep-unless-denied, stable-preferring rule (selectLatest): the upstreamlatestis kept untouched while it survives, and only repointed -- to the highest stable survivor -- when it was itself denied. This is thelatestwithin the public set, which the cross-upstream merge then re-resolves over the union; it is not the final servedlatest. - Decisions. Every version's
Decision, in version-key order, so a no-survivors outcome can render each denial and choose a status.
What the plan deliberately omits is any "dropped tags" list: a stale tag -- one
whose target did not survive -- is droppable structurally from the survivor set
alone (a tag is kept iff its target is in fpSurvivors), so the replay needs no
extra field to find them. The plan stays minimal: the decisions the filter owns,
nothing the replay can recompute.
This filters a single public packument (the gated set). Combining it with the trusted private set is the cross-upstream merge (Ecluse.Core.Package.Merge).
Egress-scheme enforcement
The module also owns the other ecosystem-agnostic reduction a fetched packument needs
before serve: normalising every served artifact URL against the https-only egress policy
(resolveTarballUrl), parameterised by the upstream base URL
the packument was served from. Like the filter above, it reasons over the domain model and
the agnostic egress policy alone (no wire format in sight), so it is the projection
post-step every ecosystem shares rather than copies: a divergent copy of an egress-policy
application is exactly the drift the policy's correct-by-construction design exists to
prevent, and the foreign-host artifact locations of PyPI and RubyGems make it matter there
even more than for npm. An https artifact URL is kept, a same-host http URL is upgraded to
https, and a version whose artifact is http on a foreign host (or any non-http(s) URL) is
dropped from the served set and recorded as an InvalidVersionManifest.
Synopsis
- data FilterPlan = FilterPlan {
- fpSurvivors :: Set Text
- fpLatest :: Maybe Version
- fpDecisions :: [Decision]
- filterPlanFromDecisions :: Map Text Decision -> PackageInfo -> FilterPlan
- restrictToSurvivors :: Set Text -> PackageInfo -> PackageInfo
- enforceArtifactScheme :: Text -> PackageInfo -> PackageInfo
- enforceArtifactSchemeDetails :: Text -> PackageDetails -> Maybe PackageDetails
Rule-filter plan
data FilterPlan Source #
The decisions filtering a single public packument owns, for the adapter to
replay onto the raw upstream Value. Carries only what the filter decides over the
typed model -- never a finished, re-serialisable document (see this module's
header). The replay derives everything else (which stale tags to drop, which
time entries to prune) from these fields.
Constructors
| FilterPlan | |
Fields
| |
Instances
| Show FilterPlan Source # | |
Defined in Ecluse.Core.Package.Filter Methods showsPrec :: Int -> FilterPlan -> ShowS # show :: FilterPlan -> String # showList :: [FilterPlan] -> ShowS # | |
| Eq FilterPlan Source # | |
Defined in Ecluse.Core.Package.Filter | |
filterPlanFromDecisions :: Map Text Decision -> PackageInfo -> FilterPlan Source #
Build a FilterPlan from per-version Decisions already taken. This is the
path the effectful tier feeds: it decides each version in IO (see
Ecluse.Core.Rules), then hands the decisions here for the pure
survivor/latest resolution. latest is resolved by
selectLatest from the upstream-tagged latest (looked up
among the versions, so a tag aimed at an absent version contributes nothing) and
the surviving versions -- kept while it survives, else repointed downward to the
highest stable survivor. The decision map is keyed by raw version string and
must cover exactly the packument's versions; a version with no decision is
treated as not surviving.
A version survives iff its decision is an Admitted; every other
verdict -- denial, deny-by-default, or Undecidable -- drops it,
so a fail-closed undecidable version is filtered out exactly like a denial, while its
decision is still carried in fpDecisions for the no-survivors status.
restrictToSurvivors :: Set Text -> PackageInfo -> PackageInfo Source #
Restrict a PackageInfo to the version keys that survived filtering -- the
FilterPlan's own fpSurvivors -- so the typed view handed to the cross-upstream
merge carries exactly the gated set (mergePackuments
treats a gated source as already filtered and never re-filters). dist-tags is
pruned to the surviving keys likewise (the merge reconciles tags over the union);
dist-tags targets absent from the survivors are dropped. Each surviving version
carries its own publish time, so restricting the versions carries the times with it
(the merge reconstructs the served time from the survivors).
Egress-scheme enforcement
enforceArtifactScheme :: Text -> PackageInfo -> PackageInfo Source #
Normalise every served version's artifact URL scheme against the https-only egress
policy (resolveTarballUrl), given the upstreamBaseUrl the
packument was served from. An https artifact URL is kept, a same-host http URL is
upgraded to https, and a version whose artifact is http on a foreign host (or any
non-http(s) URL) is dropped from the served set and recorded as an
InvalidVersionManifest carrying the offending URL (the #486
drop-and-record contract), so the version is never dialled in plaintext and the drop is
observable.
The enforcement applies only when the upstream is https (in production every configured upstream is https by construction). A non-https upstream is the test/dev loopback opt-in, whose artifact URLs are left untouched. Applied as a projection post-step at the fetch boundary, where the upstream URL is known, so each ecosystem's projection stays context-free and shares this one fold rather than copying it.
enforceArtifactSchemeDetails :: Text -> PackageDetails -> Maybe PackageDetails Source #
The single-version form of enforceArtifactScheme for the selective decode path:
Nothing drops the version (its artifact URL is non-https and not upgradeable), a Just
carries the version with each artifact's URL normalised to https. A non-https (test/dev
loopback) upstream leaves the version untouched.