Skip to content

Commit ca0ad5e

Browse files
Validate migrated documentation provenance
1 parent 37d81b0 commit ca0ad5e

3 files changed

Lines changed: 35 additions & 3 deletions

File tree

‎docgen/README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ Package metadata always comes from the canonical `purs.json` stored in the
3636
registry tarball. Historical Pursuit JSON is used during migration only for
3737
compiler-produced documentation and resolution data; it is not an alternative
3838
source for package identity, metadata, dependencies, or source files.
39+
Migration accepts its exact resolutions and module ownership only when they are
40+
consistent with the canonical manifest; inconsistent artifacts must be
41+
regenerated through the registry documentation pipeline.
3942

4043
The main modules are:
4144

‎docgen/src/Registry/Docgen/Convert.purs‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ import Data.Maybe (Maybe(..), fromMaybe)
1515
import Data.Newtype (unwrap)
1616
import Data.String as String
1717
import Data.Traversable (traverse)
18+
import Data.Tuple (Tuple(..))
1819
import Registry.Docgen.Docs (DataConstructorName(..), DocChildDeclaration(..), DocChildDeclarationInfo(..), DocConstraint(..), DocDeclaration(..), DocDeclarationInfo(..), DocModule(..), DocPackage(..), DocReexport(..), DocType(..), ForallBinding(..), FunDep(..), Ident(..), InfixAlias(..), IntLiteral(..), ModuleName(..), OperatorName(..), Qualified(..), RawRange(..), Readme, RowLabel(..), RowRep, SourceArtifact, SourceSpan(..), StringLiteral, TypeName(..), TypeVar(..), ValueName(..), isPrim, schemaVersion)
1920
import Registry.Docgen.Legacy.Docs (InPackage(..))
2021
import Registry.Docgen.Legacy.Docs as L
2122
import Registry.LimitedString as LimitedString
2223
import Registry.Manifest (Manifest(..))
24+
import Registry.PackageName (PackageName)
2325
import Registry.PackageName as PackageName
26+
import Registry.Range (Range)
2427
import Registry.Range as Range
2528
import Registry.Version as Version
2629
import Safe.Coerce (coerce)
@@ -35,13 +38,14 @@ fromLegacyPackage
3538
}
3639
-> L.DocPackage
3740
-> Either String DocPackage
38-
fromLegacyPackage input (L.DocPackage pkg@{ packageMeta: L.DocPackageMeta meta }) = do
39-
let Manifest manifest = input.manifest
41+
fromLegacyPackage input@{ manifest: Manifest manifest } (L.DocPackage pkg@{ packageMeta: L.DocPackageMeta meta }) = do
4042
if meta.name /= manifest.name then
4143
Left $ "Legacy documentation package " <> PackageName.print meta.name <> " does not match manifest package " <> PackageName.print manifest.name
4244
else if pkg.version /= manifest.version then
4345
Left $ "Legacy documentation version " <> Version.print pkg.version <> " does not match manifest version " <> Version.print manifest.version
4446
else do
47+
_ <- traverse validateDependency (Map.toUnfoldable manifest.dependencies :: Array (Tuple PackageName Range))
48+
_ <- traverse validateModuleOwner (Map.toUnfoldable pkg.moduleMap :: Array (Tuple ModuleName PackageName))
4549
compilerVersion <- Version.parse pkg.compilerVersion
4650
modules <- traverse convertModule pkg.modules
4751
pure $ DocPackage
@@ -61,6 +65,18 @@ fromLegacyPackage input (L.DocPackage pkg@{ packageMeta: L.DocPackageMeta meta }
6165
, version: manifest.version
6266
}
6367
where
68+
validateDependency (Tuple dependency range) = case Map.lookup dependency pkg.resolvedDependencies of
69+
Nothing ->
70+
Left $ "Legacy documentation is missing an exact resolution for manifest dependency " <> PackageName.print dependency
71+
Just resolved | not (Range.includes range resolved) ->
72+
Left $ "Legacy documentation resolves " <> PackageName.print dependency <> "@" <> Version.print resolved <> " outside manifest range " <> Range.print range
73+
Just _ ->
74+
Right unit
75+
76+
validateModuleOwner (Tuple moduleName owner)
77+
| owner == manifest.name || Map.member owner pkg.resolvedDependencies = Right unit
78+
| otherwise = Left $ "Legacy documentation attributes module " <> unwrap moduleName <> " to unresolved package " <> PackageName.print owner
79+
6480
convertModule legacy@(L.DocModule { name })
6581
| isPrim name = Right $ fromLegacyModule sourcePaths legacy
6682
| otherwise = case Map.lookup name sourcePaths of

‎docgen/test/Test/Registry/Docgen.purs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ main = runSpecAndExitProcess [ consoleReporter ] do
106106
let Manifest manifest = undefinedManifest
107107
let canonicalDescription = Utils.fromRight "description" $ LimitedString.parse "Canonical package metadata"
108108
let manifestDependencies = Map.singleton (packageName "prelude") (range ">=4.0.0 <5.0.0")
109+
let L.DocPackage legacyPackage = legacy
109110
let
110111
authoritativeManifest = Manifest $ manifest
111112
{ dependencies = manifestDependencies
@@ -114,14 +115,26 @@ main = runSpecAndExitProcess [ consoleReporter ] do
114115
, location = Git { url: "https://example.com/undefined.git", subdir: Nothing }
115116
, ref = "release-1.0.2"
116117
}
117-
case Convert.fromLegacyPackage (conversionInput { manifest = authoritativeManifest }) legacy of
118+
let legacyWithResolution = L.DocPackage $ legacyPackage { resolvedDependencies = Map.singleton (packageName "prelude") (version "4.1.0") }
119+
case Convert.fromLegacyPackage (conversionInput { manifest = authoritativeManifest }) legacyWithResolution of
118120
Right (DocPackage convertedPackage) -> do
119121
convertedPackage.dependencies `Assert.shouldEqual` Map.singleton (packageName "prelude") (RawRange ">=4.0.0 <5.0.0")
120122
convertedPackage.description `Assert.shouldEqual` Just "Canonical package metadata"
121123
convertedPackage.license `Assert.shouldEqual` (Utils.fromRight "license" $ License.parse "BSD-3-Clause")
122124
convertedPackage.location `Assert.shouldEqual` Git { url: "https://example.com/undefined.git", subdir: Nothing }
123125
convertedPackage.locationRef `Assert.shouldEqual` Just "release-1.0.2"
124126
Left err -> Assert.fail $ "Failed to convert historical fixture with authoritative manifest: " <> err
127+
case Convert.fromLegacyPackage (conversionInput { manifest = authoritativeManifest }) legacy of
128+
Left err -> shouldContainString err "missing an exact resolution for manifest dependency prelude"
129+
Right _ -> Assert.fail "Conversion unexpectedly accepted a missing direct resolution"
130+
let outOfRange = L.DocPackage $ legacyPackage { resolvedDependencies = Map.singleton (packageName "prelude") (version "3.0.0") }
131+
case Convert.fromLegacyPackage (conversionInput { manifest = authoritativeManifest }) outOfRange of
132+
Left err -> shouldContainString err "prelude@3.0.0 outside manifest range >=4.0.0 <5.0.0"
133+
Right _ -> Assert.fail "Conversion unexpectedly accepted an out-of-range direct resolution"
134+
let unknownOwner = L.DocPackage $ legacyPackage { moduleMap = Map.singleton (ModuleName "Dependency") (packageName "other") }
135+
case Convert.fromLegacyPackage conversionInput unknownOwner of
136+
Left err -> shouldContainString err "module Dependency to unresolved package other"
137+
Right _ -> Assert.fail "Conversion unexpectedly accepted an unresolved module owner"
125138
let mismatchedManifest = Manifest $ manifest { name = packageName "other" }
126139
case Convert.fromLegacyPackage (conversionInput { manifest = mismatchedManifest }) legacy of
127140
Left err -> shouldContainString err "does not match manifest package other"

0 commit comments

Comments
 (0)