@thomasminh1995/depverdict 0.6.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/bin/depverdict.js +7 -0
- package/bin/upgradelens.js +7 -0
- package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
- package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
- package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
- package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
- package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
- package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
- package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
- package/docs/IA-04-CLI-Orchestration.md +235 -0
- package/docs/IA-05-Real-Provider-Validation.md +235 -0
- package/docs/IA-05-VinGrade-Validation.md +339 -0
- package/docs/MVP-01.md +48 -0
- package/docs/MVP-02-Architecture.md +536 -0
- package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
- package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
- package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
- package/docs/MVP-02-Knowledge-Manifest.md +279 -0
- package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
- package/docs/MVP-02-Knowledge-Store.md +87 -0
- package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
- package/docs/MVP-02-Research-Planning.md +196 -0
- package/docs/MVP-02-Source-Provenance.md +81 -0
- package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
- package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
- package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
- package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
- package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
- package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
- package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
- package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
- package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
- package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
- package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
- package/docs/VinGrade-MVP-02-Validation.md +480 -0
- package/docs/VinGrade-RC02-Live-Validation.md +273 -0
- package/docs/ai-capability-discovery.md +573 -0
- package/docs/ai-engineering-production-readiness.md +390 -0
- package/docs/ai-engineering-review.md +251 -0
- package/docs/ai-runtime-governance-discovery.md +754 -0
- package/docs/architecture-overview.md +78 -0
- package/docs/cli-progress.md +111 -0
- package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
- package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
- package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
- package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
- package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
- package/docs/gateway-runtime-discovery.md +614 -0
- package/docs/live-ai-validation.md +299 -0
- package/docs/migration-planning-qualification-resolution.md +99 -0
- package/docs/migrations/upgradelens-to-depverdict.md +97 -0
- package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
- package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
- package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
- package/docs/mvp-05-deterministic-context-runtime.md +127 -0
- package/docs/mvp-05-migration-checklist-contract.md +166 -0
- package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
- package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
- package/docs/mvp-05-provider-neutral-generator.md +79 -0
- package/docs/ollama-local-smoke-validation.md +156 -0
- package/docs/openai-compatible-runtime-discovery.md +789 -0
- package/docs/openrouter-one-dependency-validation.md +205 -0
- package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
- package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
- package/docs/package-content-policy.md +98 -0
- package/docs/releases/v0.5.0-technical-preview.md +114 -0
- package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
- package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
- package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
- package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
- package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
- package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
- package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
- package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
- package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
- package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
- package/docs/reviews/oss-03-community-scaffolding.md +378 -0
- package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
- package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
- package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
- package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
- package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
- package/docs/runtime-contract-discovery.md +521 -0
- package/docs/structured-output-compatibility-report.md +100 -0
- package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
- package/docs/version-analysis-architecture.md +827 -0
- package/eval/README.md +86 -0
- package/eval/datasets/generic/declared-constraint.json +59 -0
- package/eval/datasets/generic/evidence-conflict.json +73 -0
- package/eval/datasets/generic/major-breaking-release.json +66 -0
- package/eval/datasets/generic/missing-evidence.json +46 -0
- package/eval/datasets/generic/patch-release-low.json +59 -0
- package/eval/datasets/node/axios-patch-low.json +59 -0
- package/eval/datasets/node/react-major-breaking.json +66 -0
- package/eval/datasets/node/react-minor-compatibility.json +66 -0
- package/eval/datasets/python/fastapi-deprecation.json +66 -0
- package/eval/datasets/python/pydantic-major-breaking.json +66 -0
- package/eval/migration-planning/golden-dataset-v2.json +306 -0
- package/eval/migration-planning/golden-dataset.json +214 -0
- package/eval/schemas/expected-result.schema.json +88 -0
- package/eval/schemas/golden-case.schema.json +181 -0
- package/package.json +57 -0
- package/schemas/ai-scorecard.schema.json +132 -0
- package/schemas/benchmark-report.schema.json +189 -0
- package/schemas/benchmark.schema.json +55 -0
- package/schemas/capability-profile.schema.json +44 -0
- package/schemas/conformance-report.schema.json +150 -0
- package/schemas/deployment-profile.schema.json +64 -0
- package/schemas/evaluation-report.schema.json +158 -0
- package/schemas/knowledge-evidence-bundle.schema.json +150 -0
- package/schemas/knowledge-manifest.schema.json +548 -0
- package/schemas/metrics.schema.json +178 -0
- package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
- package/schemas/migration-checklist.schema.json +706 -0
- package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
- package/schemas/migration-evaluation-dataset.schema.json +204 -0
- package/schemas/migration-planning-qualification-record.schema.json +234 -0
- package/schemas/project-manifest.schema.json +308 -0
- package/schemas/qualification-record.schema.json +56 -0
- package/schemas/repository-impact-evidence.schema.json +232 -0
- package/schemas/repository-impact.schema.json +202 -0
- package/schemas/upgrade-decision.schema.json +273 -0
- package/schemas/usage-index.schema.json +179 -0
- package/schemas/version-analysis.schema.json +449 -0
- package/src/ai-runtime-debug.js +325 -0
- package/src/ai-runtime-error.js +42 -0
- package/src/ai-runtime.js +174 -0
- package/src/ai-scorecard.js +204 -0
- package/src/ai-version-analysis.js +484 -0
- package/src/artifact-root-compatibility.js +91 -0
- package/src/benchmark-report.js +111 -0
- package/src/benchmark-runner.js +191 -0
- package/src/canonical-json.js +69 -0
- package/src/cli.js +1299 -0
- package/src/conformance-report.js +158 -0
- package/src/conformance-runner.js +253 -0
- package/src/constants.js +73 -0
- package/src/cooperative-scheduler.js +79 -0
- package/src/dependencies.js +44 -0
- package/src/dependency-ai-context.js +625 -0
- package/src/detectors.js +253 -0
- package/src/discovery.js +234 -0
- package/src/ecosystem-version-adapter.js +294 -0
- package/src/environment-compatibility.js +77 -0
- package/src/evaluation-comparator.js +158 -0
- package/src/evaluation-report.js +76 -0
- package/src/evaluation-runner.js +248 -0
- package/src/evidence-source-adapter.js +472 -0
- package/src/files.js +89 -0
- package/src/governance-diagnostics.js +64 -0
- package/src/governance-loader.js +63 -0
- package/src/governance-metadata.js +346 -0
- package/src/governance-validator.js +360 -0
- package/src/http/bounded-fetch.js +278 -0
- package/src/http/cli-http-runtime.js +44 -0
- package/src/impact/input-loader.js +157 -0
- package/src/impact/matcher.js +40 -0
- package/src/impact/repository-impact.js +199 -0
- package/src/impact/runtime.js +24 -0
- package/src/impact/status.js +62 -0
- package/src/impact/writer.js +30 -0
- package/src/impact-evidence/input-loader.js +202 -0
- package/src/impact-evidence/repository-impact-evidence.js +234 -0
- package/src/impact-evidence/runtime.js +16 -0
- package/src/impact-evidence/writer.js +30 -0
- package/src/index.js +563 -0
- package/src/installed-version-baseline.js +196 -0
- package/src/knowledge-cache.js +324 -0
- package/src/knowledge-evidence-bundle.js +101 -0
- package/src/knowledge-evidence-producer.js +233 -0
- package/src/knowledge-manifest-builder.js +188 -0
- package/src/knowledge-manifest-writer.js +32 -0
- package/src/knowledge-manifest.js +255 -0
- package/src/knowledge-research.js +615 -0
- package/src/metrics-engine.js +205 -0
- package/src/migration-checklist/ai-candidate.js +320 -0
- package/src/migration-checklist/assembler.js +37 -0
- package/src/migration-checklist/context-runtime.js +828 -0
- package/src/migration-checklist/evaluation/action-criteria.js +244 -0
- package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
- package/src/migration-checklist/evaluation/comparator.js +279 -0
- package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
- package/src/migration-checklist/evaluation/dataset.js +336 -0
- package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
- package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
- package/src/migration-checklist/evaluation/metrics.js +158 -0
- package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
- package/src/migration-checklist/evaluation/qualification.js +239 -0
- package/src/migration-checklist/evaluation/runner-v2.js +294 -0
- package/src/migration-checklist/evaluation/runner.js +194 -0
- package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
- package/src/migration-checklist/evaluation/scorecard.js +86 -0
- package/src/migration-checklist/extractive-candidate.js +166 -0
- package/src/migration-checklist/extractive-prompt.js +62 -0
- package/src/migration-checklist/generator.js +702 -0
- package/src/migration-checklist/grounding-policy.js +117 -0
- package/src/migration-checklist/input-loader.js +613 -0
- package/src/migration-checklist/migration-checklist.js +635 -0
- package/src/migration-checklist/presentation.js +292 -0
- package/src/migration-checklist/progress.js +96 -0
- package/src/migration-checklist/prompt.js +83 -0
- package/src/migration-checklist/qualification-guard.js +462 -0
- package/src/migration-checklist/qualification-resolution.js +122 -0
- package/src/migration-checklist/qualification-store.js +225 -0
- package/src/migration-checklist/runtime.js +205 -0
- package/src/migration-checklist/verification.js +134 -0
- package/src/migration-checklist/writer.js +35 -0
- package/src/openai-compatible-provider.js +451 -0
- package/src/orchestration/failure-log.js +32 -0
- package/src/orchestration/pipeline.js +200 -0
- package/src/orchestration/progress-events.js +337 -0
- package/src/orchestration/progress-reporter.js +131 -0
- package/src/orchestration/text-writer.js +22 -0
- package/src/portable.js +13 -0
- package/src/product-completion.js +249 -0
- package/src/project-manifest-input.js +90 -0
- package/src/project-manifest.js +141 -0
- package/src/python-requirements.js +137 -0
- package/src/registry/npm-packument.js +256 -0
- package/src/registry/npm-registry-adapter.js +262 -0
- package/src/registry/pypi-project.js +300 -0
- package/src/registry/pypi-registry-adapter.js +235 -0
- package/src/registry/sanitize-registry-body.js +51 -0
- package/src/renderers/console.js +160 -0
- package/src/renderers/impact-presentation.js +278 -0
- package/src/renderers/markdown.js +172 -0
- package/src/research-plan.js +455 -0
- package/src/runtime-conformance.js +275 -0
- package/src/source-provenance.js +393 -0
- package/src/source-url.js +62 -0
- package/src/structured-output-schema.js +66 -0
- package/src/target-selector.js +306 -0
- package/src/upgrade-decision/input-loader.js +107 -0
- package/src/upgrade-decision/presentation.js +43 -0
- package/src/upgrade-decision/runtime.js +21 -0
- package/src/upgrade-decision/upgrade-decision.js +626 -0
- package/src/upgrade-decision/writer.js +30 -0
- package/src/usage/analyzer-registry.js +63 -0
- package/src/usage/coverage.js +116 -0
- package/src/usage/input-loader.js +139 -0
- package/src/usage/js/analyzer.js +240 -0
- package/src/usage/js/parser.js +21 -0
- package/src/usage/runtime.js +187 -0
- package/src/usage/scope.js +44 -0
- package/src/usage/source-files.js +50 -0
- package/src/usage/usage-index.js +217 -0
- package/src/usage/writer.js +31 -0
- package/src/version-analysis-loader.js +203 -0
- package/src/version-analysis-manifest.js +314 -0
- package/src/version-analysis-writer.js +30 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_KNOWLEDGE_MANIFEST_PATH,
|
|
7
|
+
DEFAULT_MANIFEST_PATH,
|
|
8
|
+
DEFAULT_REPOSITORY_IMPACT_EVIDENCE_PATH,
|
|
9
|
+
DEFAULT_REPOSITORY_IMPACT_PATH,
|
|
10
|
+
DEFAULT_USAGE_INDEX_PATH,
|
|
11
|
+
DEFAULT_VERSION_ANALYSIS_PATH
|
|
12
|
+
} from '../constants.js';
|
|
13
|
+
import { resolveArtifactRootChain } from '../artifact-root-compatibility.js';
|
|
14
|
+
import { validateRepositoryImpactEvidence } from '../impact-evidence/repository-impact-evidence.js';
|
|
15
|
+
import {
|
|
16
|
+
EXACT_SYMBOL_MATCHER_ID,
|
|
17
|
+
EXACT_SYMBOL_MATCHER_VERSION,
|
|
18
|
+
isMatchableUsageSymbol,
|
|
19
|
+
matchFindingToUsage
|
|
20
|
+
} from '../impact/matcher.js';
|
|
21
|
+
import {
|
|
22
|
+
classifyDependencyImpact,
|
|
23
|
+
classifyFindingImpact
|
|
24
|
+
} from '../impact/status.js';
|
|
25
|
+
import { validateRepositoryImpact } from '../impact/repository-impact.js';
|
|
26
|
+
import { isPortableRelativePath } from '../portable.js';
|
|
27
|
+
import { loadProjectManifestInput } from '../project-manifest-input.js';
|
|
28
|
+
import { validateUsageIndex } from '../usage/usage-index.js';
|
|
29
|
+
import { coverageForProject } from '../usage/coverage.js';
|
|
30
|
+
import {
|
|
31
|
+
DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH,
|
|
32
|
+
loadKnowledgeEvidenceBundleInput,
|
|
33
|
+
loadKnowledgeManifestInput
|
|
34
|
+
} from '../version-analysis-loader.js';
|
|
35
|
+
import { validateVersionAnalysisManifest } from '../version-analysis-manifest.js';
|
|
36
|
+
|
|
37
|
+
const ARTIFACTS = Object.freeze([
|
|
38
|
+
Object.freeze({ key: 'projectManifest', label: 'Project Manifest', artifact: DEFAULT_MANIFEST_PATH }),
|
|
39
|
+
Object.freeze({ key: 'knowledgeManifest', label: 'Knowledge Manifest', artifact: DEFAULT_KNOWLEDGE_MANIFEST_PATH }),
|
|
40
|
+
Object.freeze({
|
|
41
|
+
key: 'knowledgeEvidenceBundle',
|
|
42
|
+
label: 'Knowledge Evidence Bundle',
|
|
43
|
+
artifact: DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH
|
|
44
|
+
}),
|
|
45
|
+
Object.freeze({ key: 'versionAnalysis', label: 'Version Analysis', artifact: DEFAULT_VERSION_ANALYSIS_PATH }),
|
|
46
|
+
Object.freeze({ key: 'usageIndex', label: 'Usage Index', artifact: DEFAULT_USAGE_INDEX_PATH }),
|
|
47
|
+
Object.freeze({ key: 'repositoryImpact', label: 'Repository Impact', artifact: DEFAULT_REPOSITORY_IMPACT_PATH }),
|
|
48
|
+
Object.freeze({
|
|
49
|
+
key: 'repositoryImpactEvidence',
|
|
50
|
+
label: 'Repository Impact Evidence',
|
|
51
|
+
artifact: DEFAULT_REPOSITORY_IMPACT_EVIDENCE_PATH
|
|
52
|
+
})
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const LINEAGE_FIELDS = Object.freeze({
|
|
56
|
+
projectManifest: Object.freeze([
|
|
57
|
+
'schemaVersion',
|
|
58
|
+
'artifact',
|
|
59
|
+
'artifactDigest',
|
|
60
|
+
'repository.name',
|
|
61
|
+
'repository.root'
|
|
62
|
+
]),
|
|
63
|
+
knowledgeManifest: Object.freeze(['schemaVersion', 'artifact', 'artifactDigest', 'researchId']),
|
|
64
|
+
artifact: Object.freeze(['schemaVersion', 'artifact', 'artifactDigest'])
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export class MigrationChecklistInputError extends Error {
|
|
68
|
+
constructor(message, code = 'MIGRATION_CHECKLIST_INPUT_INVALID', details = {}) {
|
|
69
|
+
super(`Migration Checklist input error: ${message}`);
|
|
70
|
+
this.name = 'MigrationChecklistInputError';
|
|
71
|
+
this.code = code;
|
|
72
|
+
Object.assign(this, details);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fail(message, code, details) {
|
|
77
|
+
throw new MigrationChecklistInputError(message, code, details);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function digest(bytes) {
|
|
81
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readField(value, field) {
|
|
85
|
+
return field.split('.').reduce((current, segment) => current?.[segment], value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sameJson(left, right) {
|
|
89
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function deepFreeze(value) {
|
|
93
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
94
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
95
|
+
return Object.freeze(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function invocationSources(input, options) {
|
|
99
|
+
if (typeof input === 'string') {
|
|
100
|
+
return { repositoryRoot: input, sources: null, artifacts: options.artifacts ?? {} };
|
|
101
|
+
}
|
|
102
|
+
if (input instanceof URL) {
|
|
103
|
+
const rootUrl = new URL(input);
|
|
104
|
+
if (!rootUrl.pathname.endsWith('/')) rootUrl.pathname = `${rootUrl.pathname}/`;
|
|
105
|
+
return { repositoryRoot: null, sources: null, rootUrl, artifacts: options.artifacts ?? {} };
|
|
106
|
+
}
|
|
107
|
+
if (input === undefined || input === null) {
|
|
108
|
+
return { repositoryRoot: '.', sources: null, artifacts: options.artifacts ?? {} };
|
|
109
|
+
}
|
|
110
|
+
if (typeof input !== 'object') {
|
|
111
|
+
fail('input must be a repository root, URL, or an object containing repositoryRoot or sources.');
|
|
112
|
+
}
|
|
113
|
+
const directSources = ARTIFACTS.some(({ key }) => input[key] !== undefined)
|
|
114
|
+
? input
|
|
115
|
+
: null;
|
|
116
|
+
const rootUrl = input.repositoryRoot instanceof URL ? new URL(input.repositoryRoot) : null;
|
|
117
|
+
if (rootUrl && !rootUrl.pathname.endsWith('/')) rootUrl.pathname = `${rootUrl.pathname}/`;
|
|
118
|
+
return {
|
|
119
|
+
repositoryRoot: input.repositoryRoot ?? (directSources ? null : '.'),
|
|
120
|
+
rootUrl,
|
|
121
|
+
sources: input.sources ?? directSources,
|
|
122
|
+
artifacts: { ...(options.artifacts ?? {}), ...(input.artifacts ?? {}) }
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sourceFor(descriptor, invocation) {
|
|
127
|
+
const alias = descriptor.key === 'knowledgeEvidenceBundle'
|
|
128
|
+
? invocation.sources?.evidenceBundle
|
|
129
|
+
: undefined;
|
|
130
|
+
const explicit = invocation.sources?.[descriptor.key] ?? alias;
|
|
131
|
+
if (explicit !== undefined) return explicit;
|
|
132
|
+
if (invocation.sources) return undefined;
|
|
133
|
+
if (invocation.rootUrl) return new URL(descriptor.artifact, invocation.rootUrl);
|
|
134
|
+
return path.resolve(
|
|
135
|
+
invocation.repositoryRoot,
|
|
136
|
+
invocation.selectedArtifacts?.[descriptor.key] ?? descriptor.artifact
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function artifactFor(descriptor, source, invocation) {
|
|
141
|
+
const explicitArtifact = source && typeof source === 'object' && !(source instanceof URL)
|
|
142
|
+
? source.artifact
|
|
143
|
+
: undefined;
|
|
144
|
+
const artifact = explicitArtifact
|
|
145
|
+
?? invocation.artifacts[descriptor.key]
|
|
146
|
+
?? invocation.selectedArtifacts?.[descriptor.key]
|
|
147
|
+
?? descriptor.artifact;
|
|
148
|
+
if (!isPortableRelativePath(artifact)) {
|
|
149
|
+
fail(`${descriptor.label} artifact must be a portable repository-relative path.`, 'INVALID_ARTIFACT_PATH');
|
|
150
|
+
}
|
|
151
|
+
return artifact;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function readArtifact(descriptor, source, artifact) {
|
|
155
|
+
if (source === undefined) {
|
|
156
|
+
fail(`${descriptor.label} artifact is required.`, 'MISSING_ARTIFACT', { artifact: descriptor.key });
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
let bytes;
|
|
160
|
+
if (typeof source === 'string' || source instanceof URL) {
|
|
161
|
+
bytes = await readFile(source);
|
|
162
|
+
} else if (source && typeof source === 'object' && source.bytes instanceof Uint8Array) {
|
|
163
|
+
bytes = Buffer.from(source.bytes);
|
|
164
|
+
} else if (source && typeof source === 'object' && (typeof source.path === 'string' || source.path instanceof URL)) {
|
|
165
|
+
bytes = await readFile(source.path);
|
|
166
|
+
} else {
|
|
167
|
+
fail(
|
|
168
|
+
`${descriptor.label} source must be a file path, URL, or { bytes, artifact }.`,
|
|
169
|
+
'INVALID_ARTIFACT_SOURCE',
|
|
170
|
+
{ artifact: descriptor.key }
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
let value;
|
|
174
|
+
try {
|
|
175
|
+
value = JSON.parse(bytes.toString('utf8'));
|
|
176
|
+
} catch {
|
|
177
|
+
fail(`${descriptor.label} bytes are not valid JSON.`, 'INVALID_JSON', { artifact: descriptor.key });
|
|
178
|
+
}
|
|
179
|
+
return { bytes, artifact, value };
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error instanceof MigrationChecklistInputError) throw error;
|
|
182
|
+
if (error?.code === 'ENOENT') {
|
|
183
|
+
fail(`${descriptor.label} artifact is missing.`, 'MISSING_ARTIFACT', { artifact: descriptor.key });
|
|
184
|
+
}
|
|
185
|
+
fail(`${descriptor.label} could not be read.`, 'ARTIFACT_READ_FAILED', { artifact: descriptor.key });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function lineageMetadata(records, projectInput, knowledgeInput, bundleInput) {
|
|
190
|
+
const metadata = {
|
|
191
|
+
projectManifest: projectInput.input.projectManifest,
|
|
192
|
+
knowledgeManifest: knowledgeInput.input.knowledgeManifest,
|
|
193
|
+
knowledgeEvidenceBundle: bundleInput.input.evidenceArtifact
|
|
194
|
+
};
|
|
195
|
+
for (const key of ['versionAnalysis', 'usageIndex', 'repositoryImpact', 'repositoryImpactEvidence']) {
|
|
196
|
+
metadata[key] = {
|
|
197
|
+
schemaVersion: records[key].value.schemaVersion,
|
|
198
|
+
artifact: records[key].artifact,
|
|
199
|
+
artifactDigest: digest(records[key].bytes)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return metadata;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function assertLineage(consumer, upstream, expected, actual, fields) {
|
|
206
|
+
for (const field of fields) {
|
|
207
|
+
const expectedValue = readField(expected, field);
|
|
208
|
+
const actualValue = readField(actual, field);
|
|
209
|
+
if (expectedValue !== actualValue) {
|
|
210
|
+
fail(
|
|
211
|
+
`lineage mismatch: ${consumer} declares ${upstream} ${field} as ${String(expectedValue)}; actual value is ${String(actualValue)}.`,
|
|
212
|
+
'LINEAGE_MISMATCH',
|
|
213
|
+
{ consumer, upstream, field, expected: expectedValue, actual: actualValue }
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function validateMigrationChecklistInputLineage(artifacts) {
|
|
220
|
+
const { input } = artifacts;
|
|
221
|
+
const checks = [
|
|
222
|
+
['Knowledge Manifest', 'Project Manifest', artifacts.knowledgeManifest.input.projectManifest,
|
|
223
|
+
input.projectManifest, LINEAGE_FIELDS.projectManifest],
|
|
224
|
+
['Knowledge Evidence Bundle', 'Knowledge Manifest', artifacts.knowledgeEvidenceBundle.input.knowledgeManifest,
|
|
225
|
+
input.knowledgeManifest, LINEAGE_FIELDS.knowledgeManifest],
|
|
226
|
+
['Version Analysis', 'Project Manifest', artifacts.versionAnalysis.input.projectManifest,
|
|
227
|
+
input.projectManifest, LINEAGE_FIELDS.projectManifest],
|
|
228
|
+
['Version Analysis', 'Knowledge Manifest', artifacts.versionAnalysis.input.knowledgeManifest,
|
|
229
|
+
input.knowledgeManifest, LINEAGE_FIELDS.knowledgeManifest],
|
|
230
|
+
['Version Analysis', 'Knowledge Evidence Bundle', artifacts.versionAnalysis.input.evidenceArtifact,
|
|
231
|
+
input.knowledgeEvidenceBundle, LINEAGE_FIELDS.artifact],
|
|
232
|
+
['Usage Index', 'Project Manifest', artifacts.usageIndex.input.projectManifest,
|
|
233
|
+
input.projectManifest, LINEAGE_FIELDS.projectManifest],
|
|
234
|
+
['Usage Index', 'Version Analysis', artifacts.usageIndex.input.versionAnalysis,
|
|
235
|
+
input.versionAnalysis, LINEAGE_FIELDS.artifact],
|
|
236
|
+
['Repository Impact', 'Project Manifest', artifacts.repositoryImpact.input.projectManifest,
|
|
237
|
+
input.projectManifest, LINEAGE_FIELDS.projectManifest],
|
|
238
|
+
['Repository Impact', 'Version Analysis', artifacts.repositoryImpact.input.versionAnalysis,
|
|
239
|
+
input.versionAnalysis, LINEAGE_FIELDS.artifact],
|
|
240
|
+
['Repository Impact', 'Usage Index', artifacts.repositoryImpact.input.usageIndex,
|
|
241
|
+
input.usageIndex, LINEAGE_FIELDS.artifact],
|
|
242
|
+
['Repository Impact Evidence', 'Project Manifest', artifacts.repositoryImpactEvidence.input.projectManifest,
|
|
243
|
+
input.projectManifest, LINEAGE_FIELDS.projectManifest],
|
|
244
|
+
['Repository Impact Evidence', 'Version Analysis', artifacts.repositoryImpactEvidence.input.versionAnalysis,
|
|
245
|
+
input.versionAnalysis, LINEAGE_FIELDS.artifact],
|
|
246
|
+
['Repository Impact Evidence', 'Usage Index', artifacts.repositoryImpactEvidence.input.usageIndex,
|
|
247
|
+
input.usageIndex, LINEAGE_FIELDS.artifact],
|
|
248
|
+
['Repository Impact Evidence', 'Repository Impact', artifacts.repositoryImpactEvidence.input.repositoryImpact,
|
|
249
|
+
input.repositoryImpact, LINEAGE_FIELDS.artifact]
|
|
250
|
+
];
|
|
251
|
+
for (const check of checks) assertLineage(...check);
|
|
252
|
+
return artifacts;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function occurrenceKey(dependency) {
|
|
256
|
+
return [
|
|
257
|
+
dependency.projectId,
|
|
258
|
+
dependency.packageId,
|
|
259
|
+
dependency.manifest,
|
|
260
|
+
dependency.dependencyType,
|
|
261
|
+
dependency.declaredName,
|
|
262
|
+
dependency.versions?.declaredVersion ?? dependency.declaredVersion ?? ''
|
|
263
|
+
].join('\0');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function usageIdentityKey(value) {
|
|
267
|
+
return `${value.projectId}\0${value.packageId}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function versionResultsByUsageIdentity(results) {
|
|
271
|
+
const indexed = new Map();
|
|
272
|
+
for (const result of results) {
|
|
273
|
+
const key = usageIdentityKey(result.dependency);
|
|
274
|
+
if (!indexed.has(key)) indexed.set(key, []);
|
|
275
|
+
indexed.get(key).push(result);
|
|
276
|
+
}
|
|
277
|
+
return indexed;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function validateDependencyIdentities(artifacts) {
|
|
281
|
+
const projects = new Map(artifacts.projectManifest.projects.map((project) => [project.id, project]));
|
|
282
|
+
const packages = new Map(artifacts.knowledgeManifest.packages.map((item) => [item.id, item]));
|
|
283
|
+
const occurrenceOwners = new Set();
|
|
284
|
+
const resultIds = new Set();
|
|
285
|
+
|
|
286
|
+
for (const result of artifacts.versionAnalysis.results) {
|
|
287
|
+
const dependency = result.dependency;
|
|
288
|
+
if (resultIds.has(result.id)) {
|
|
289
|
+
fail(`Version Analysis result ${result.id} is duplicated.`, 'AMBIGUOUS_REFERENCE');
|
|
290
|
+
}
|
|
291
|
+
resultIds.add(result.id);
|
|
292
|
+
const project = projects.get(dependency.projectId);
|
|
293
|
+
if (!project) fail(`Version Analysis result ${result.id} references unknown project ${dependency.projectId}.`, 'REFERENCE_MISMATCH');
|
|
294
|
+
const declared = project.dependencies.filter((item) => (
|
|
295
|
+
item.name === dependency.declaredName
|
|
296
|
+
&& item.normalizedName === dependency.normalizedName
|
|
297
|
+
&& item.declaredVersion === result.versions.declaredVersion
|
|
298
|
+
&& item.type === dependency.dependencyType
|
|
299
|
+
&& item.manifest === dependency.manifest
|
|
300
|
+
));
|
|
301
|
+
if (declared.length !== 1 || project.ecosystem !== dependency.ecosystem) {
|
|
302
|
+
fail(`Version Analysis result ${result.id} does not match one exact Project Manifest dependency occurrence.`, 'REFERENCE_MISMATCH');
|
|
303
|
+
}
|
|
304
|
+
const packageRecord = packages.get(dependency.packageId);
|
|
305
|
+
if (!packageRecord
|
|
306
|
+
|| packageRecord.ecosystem !== dependency.ecosystem
|
|
307
|
+
|| packageRecord.identity.normalizedName !== dependency.normalizedName
|
|
308
|
+
|| packageRecord.identity.registry !== dependency.registry) {
|
|
309
|
+
fail(`Version Analysis result ${result.id} does not match Knowledge package ${dependency.packageId}.`, 'REFERENCE_MISMATCH');
|
|
310
|
+
}
|
|
311
|
+
const occurrences = packageRecord.occurrences.filter((item) => (
|
|
312
|
+
item.projectId === dependency.projectId
|
|
313
|
+
&& item.projectPath === project.path
|
|
314
|
+
&& item.manifest === dependency.manifest
|
|
315
|
+
&& item.dependencyType === dependency.dependencyType
|
|
316
|
+
&& item.declaredName === dependency.declaredName
|
|
317
|
+
&& item.declaredVersion === result.versions.declaredVersion
|
|
318
|
+
));
|
|
319
|
+
if (occurrences.length !== 1) {
|
|
320
|
+
fail(`Version Analysis result ${result.id} does not match one exact Knowledge occurrence.`, 'REFERENCE_MISMATCH');
|
|
321
|
+
}
|
|
322
|
+
const key = occurrenceKey({ ...dependency, versions: result.versions });
|
|
323
|
+
if (occurrenceOwners.has(key)) {
|
|
324
|
+
fail(`Version Analysis occurrence for ${dependency.projectId}/${dependency.packageId} is ambiguous.`, 'AMBIGUOUS_REFERENCE');
|
|
325
|
+
}
|
|
326
|
+
occurrenceOwners.add(key);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function validateEvidenceReferences(artifacts) {
|
|
331
|
+
const bundleEvidence = new Map(artifacts.knowledgeEvidenceBundle.evidence.map((item) => [item.id, item]));
|
|
332
|
+
const sources = new Map(artifacts.knowledgeManifest.sources.map((item) => [item.id, item]));
|
|
333
|
+
const packages = new Map(artifacts.knowledgeManifest.packages.map((item) => [item.id, item]));
|
|
334
|
+
|
|
335
|
+
for (const item of artifacts.knowledgeEvidenceBundle.evidence) {
|
|
336
|
+
const packageRecord = packages.get(item.packageId);
|
|
337
|
+
const source = sources.get(item.sourceId);
|
|
338
|
+
if (!packageRecord || !source || !packageRecord.sourceIds.includes(item.sourceId)) {
|
|
339
|
+
fail(`Knowledge evidence ${item.id} has cross-package or unknown provenance.`, 'REFERENCE_MISMATCH');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (const result of artifacts.versionAnalysis.results) {
|
|
344
|
+
const resultEvidence = new Map(result.evidence.map((item) => [item.id, item]));
|
|
345
|
+
const findingIds = new Set();
|
|
346
|
+
for (const finding of result.findings) {
|
|
347
|
+
if (findingIds.has(finding.id)) {
|
|
348
|
+
fail(`Version Analysis result ${result.id} has duplicate finding ${finding.id}.`, 'AMBIGUOUS_REFERENCE');
|
|
349
|
+
}
|
|
350
|
+
findingIds.add(finding.id);
|
|
351
|
+
for (const ref of finding.evidenceRefs) {
|
|
352
|
+
if (!resultEvidence.has(ref)) {
|
|
353
|
+
fail(`Finding ${result.id}/${finding.id} references unknown Version Analysis evidence ${ref}.`, 'REFERENCE_MISMATCH');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const evidence of result.evidence) {
|
|
358
|
+
const bundled = bundleEvidence.get(evidence.id);
|
|
359
|
+
const source = sources.get(evidence.sourceId);
|
|
360
|
+
if (!bundled || bundled.packageId !== result.dependency.packageId || !source) {
|
|
361
|
+
fail(`Version Analysis evidence ${evidence.id} has cross-package or unknown provenance.`, 'REFERENCE_MISMATCH');
|
|
362
|
+
}
|
|
363
|
+
const expected = {
|
|
364
|
+
id: bundled.id,
|
|
365
|
+
kind: bundled.kind,
|
|
366
|
+
sourceId: bundled.sourceId,
|
|
367
|
+
sourceUrl: source.url,
|
|
368
|
+
authority: source.authority,
|
|
369
|
+
trust: source.trust,
|
|
370
|
+
retrievedAt: bundled.retrievedAt,
|
|
371
|
+
contentDigest: bundled.contentDigest,
|
|
372
|
+
locator: bundled.locator,
|
|
373
|
+
releaseVersions: bundled.releaseVersions
|
|
374
|
+
};
|
|
375
|
+
if (!sameJson(evidence, expected)) {
|
|
376
|
+
fail(`Version Analysis evidence ${evidence.id} does not exactly match Knowledge evidence metadata.`, 'REFERENCE_MISMATCH');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function validateUsageReferences(artifacts) {
|
|
383
|
+
const results = versionResultsByUsageIdentity(artifacts.versionAnalysis.results);
|
|
384
|
+
for (const usage of artifacts.usageIndex.dependencies) {
|
|
385
|
+
const matching = results.get(usageIdentityKey(usage)) ?? [];
|
|
386
|
+
if (matching.length === 0
|
|
387
|
+
|| matching.some((result) => result.dependency.declaredName !== usage.name)) {
|
|
388
|
+
fail(`Usage Index dependency ${usage.projectId}/${usage.packageId} has no exact Version Analysis identity.`, 'REFERENCE_MISMATCH');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const projects = new Map(artifacts.projectManifest.projects.map((project) => [project.id, project]));
|
|
392
|
+
for (const coverage of artifacts.usageIndex.analysis.coverage ?? []) {
|
|
393
|
+
const project = projects.get(coverage.projectId);
|
|
394
|
+
if (!project || project.path !== coverage.projectPath || project.ecosystem !== coverage.ecosystem) {
|
|
395
|
+
fail(`Usage Index coverage ${coverage.projectId} has no exact Project Manifest identity.`, 'REFERENCE_MISMATCH');
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function validateImpactReferences(artifacts) {
|
|
401
|
+
if (artifacts.repositoryImpact.analysis.matcher.id !== EXACT_SYMBOL_MATCHER_ID
|
|
402
|
+
|| artifacts.repositoryImpact.analysis.matcher.version !== EXACT_SYMBOL_MATCHER_VERSION) {
|
|
403
|
+
fail(
|
|
404
|
+
`Repository Impact matcher must be ${EXACT_SYMBOL_MATCHER_ID}@${EXACT_SYMBOL_MATCHER_VERSION}.`,
|
|
405
|
+
'MATCHER_UNSUPPORTED'
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const results = new Map(artifacts.versionAnalysis.results.map((result) => [result.id, result]));
|
|
409
|
+
const usages = new Map(artifacts.usageIndex.dependencies.map((usage) => (
|
|
410
|
+
[`${usage.projectId}\0${usage.packageId}`, usage]
|
|
411
|
+
)));
|
|
412
|
+
const seen = new Set();
|
|
413
|
+
for (const dependency of artifacts.repositoryImpact.dependencies) {
|
|
414
|
+
const result = results.get(dependency.analysisResultId);
|
|
415
|
+
if (!result || seen.has(dependency.analysisResultId)) {
|
|
416
|
+
fail(`Repository Impact has unknown or duplicate result ${dependency.analysisResultId}.`, 'REFERENCE_MISMATCH');
|
|
417
|
+
}
|
|
418
|
+
seen.add(dependency.analysisResultId);
|
|
419
|
+
if (dependency.projectId !== result.dependency.projectId
|
|
420
|
+
|| dependency.packageId !== result.dependency.packageId
|
|
421
|
+
|| dependency.name !== result.dependency.declaredName) {
|
|
422
|
+
fail(`Repository Impact identity differs for result ${dependency.analysisResultId}.`, 'REFERENCE_MISMATCH');
|
|
423
|
+
}
|
|
424
|
+
const expectedFindings = result.findings.filter((finding) => finding.kind === 'breakingChange');
|
|
425
|
+
if (dependency.findings.length !== expectedFindings.length) {
|
|
426
|
+
fail(`Repository Impact finding count differs for result ${dependency.analysisResultId}.`, 'REFERENCE_MISMATCH');
|
|
427
|
+
}
|
|
428
|
+
const usage = usages.get(`${dependency.projectId}\0${dependency.packageId}`) ?? null;
|
|
429
|
+
const coverage = dependency.coverage ?? coverageForProject(
|
|
430
|
+
artifacts.usageIndex,
|
|
431
|
+
dependency.projectId,
|
|
432
|
+
result.dependency.ecosystem
|
|
433
|
+
);
|
|
434
|
+
const impacts = new Map(dependency.findings.map((finding) => [finding.id, finding]));
|
|
435
|
+
for (const finding of expectedFindings) {
|
|
436
|
+
const impact = impacts.get(finding.id);
|
|
437
|
+
const expectedMatches = matchFindingToUsage(finding, usage);
|
|
438
|
+
const expectedState = classifyFindingImpact({
|
|
439
|
+
versionStatus: result.status,
|
|
440
|
+
coverage,
|
|
441
|
+
usage,
|
|
442
|
+
matches: expectedMatches
|
|
443
|
+
});
|
|
444
|
+
if (!impact
|
|
445
|
+
|| impact.kind !== finding.kind
|
|
446
|
+
|| impact.summary !== finding.summary
|
|
447
|
+
|| impact.impacted !== (expectedMatches.length > 0)
|
|
448
|
+
|| (impact.status !== undefined
|
|
449
|
+
&& (impact.status !== expectedState.status || impact.reasonCode !== expectedState.reasonCode))
|
|
450
|
+
|| !sameJson(impact.matches, expectedMatches)) {
|
|
451
|
+
fail(`Repository Impact finding ${dependency.analysisResultId}/${finding.id} is inconsistent with Version Analysis or Usage Index.`, 'REFERENCE_MISMATCH');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (dependency.status !== undefined) {
|
|
455
|
+
const expectedState = classifyDependencyImpact({
|
|
456
|
+
versionStatus: result.status,
|
|
457
|
+
coverage,
|
|
458
|
+
usage,
|
|
459
|
+
findings: dependency.findings
|
|
460
|
+
});
|
|
461
|
+
if (dependency.status !== expectedState.status
|
|
462
|
+
|| dependency.reasonCode !== expectedState.reasonCode
|
|
463
|
+
|| !sameJson(dependency.coverage, coverage)) {
|
|
464
|
+
fail(`Repository Impact state ${dependency.analysisResultId} is inconsistent with usage coverage.`, 'REFERENCE_MISMATCH');
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (seen.size !== results.size) {
|
|
469
|
+
fail('Repository Impact does not contain every Version Analysis result exactly once.', 'REFERENCE_MISMATCH');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function expectedMatchedSymbols(impactFinding) {
|
|
474
|
+
return impactFinding.matches.map((match) => ({
|
|
475
|
+
symbol: match.symbol,
|
|
476
|
+
usages: match.files.map((file) => ({ file }))
|
|
477
|
+
}));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function expectedImpactEvidenceReason(impactFinding, usage) {
|
|
481
|
+
if (impactFinding.status === 'COVERAGE_UNAVAILABLE') return 'COVERAGE_UNAVAILABLE';
|
|
482
|
+
if (impactFinding.status === 'NOT_ANALYZED') return 'NOT_ANALYZED';
|
|
483
|
+
if (impactFinding.status === 'USAGE_NOT_FOUND') return 'USAGE_NOT_FOUND';
|
|
484
|
+
if (impactFinding.impacted) return 'EXACT_SYMBOL_USAGE_FOUND';
|
|
485
|
+
if (!usage) return 'DEPENDENCY_NOT_USED';
|
|
486
|
+
if (!usage.symbols.some((symbol) => isMatchableUsageSymbol(symbol.name))) {
|
|
487
|
+
return 'NO_MATCHABLE_SYMBOL_FOUND';
|
|
488
|
+
}
|
|
489
|
+
return 'NO_EXACT_SYMBOL_USAGE_FOUND';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function validateImpactEvidenceReferences(artifacts) {
|
|
493
|
+
const impacts = new Map(artifacts.repositoryImpact.dependencies.map((dependency) => [dependency.analysisResultId, dependency]));
|
|
494
|
+
const usages = new Map(artifacts.usageIndex.dependencies.map((usage) => (
|
|
495
|
+
[`${usage.projectId}\0${usage.packageId}`, usage]
|
|
496
|
+
)));
|
|
497
|
+
const seen = new Set();
|
|
498
|
+
for (const dependency of artifacts.repositoryImpactEvidence.dependencies) {
|
|
499
|
+
const impact = impacts.get(dependency.analysisResultId);
|
|
500
|
+
if (!impact || seen.has(dependency.analysisResultId)) {
|
|
501
|
+
fail(`Repository Impact Evidence has unknown or duplicate result ${dependency.analysisResultId}.`, 'REFERENCE_MISMATCH');
|
|
502
|
+
}
|
|
503
|
+
seen.add(dependency.analysisResultId);
|
|
504
|
+
if (dependency.projectId !== impact.projectId
|
|
505
|
+
|| dependency.packageId !== impact.packageId
|
|
506
|
+
|| dependency.name !== impact.name
|
|
507
|
+
|| dependency.impacted !== impact.impacted
|
|
508
|
+
|| dependency.findings.length !== impact.findings.length) {
|
|
509
|
+
fail(`Repository Impact Evidence identity differs for result ${dependency.analysisResultId}.`, 'REFERENCE_MISMATCH');
|
|
510
|
+
}
|
|
511
|
+
const evidenceFindings = new Map(dependency.findings.map((finding) => [finding.findingId, finding]));
|
|
512
|
+
const usage = usages.get(`${dependency.projectId}\0${dependency.packageId}`) ?? null;
|
|
513
|
+
for (const finding of impact.findings) {
|
|
514
|
+
const evidence = evidenceFindings.get(finding.id);
|
|
515
|
+
if (!evidence
|
|
516
|
+
|| evidence.kind !== finding.kind
|
|
517
|
+
|| evidence.summary !== finding.summary
|
|
518
|
+
|| evidence.impacted !== finding.impacted
|
|
519
|
+
|| (finding.status !== undefined && evidence.status !== finding.status)
|
|
520
|
+
|| evidence.reasonCode !== expectedImpactEvidenceReason(finding, usage)
|
|
521
|
+
|| !sameJson(evidence.matchedSymbols, expectedMatchedSymbols(finding))) {
|
|
522
|
+
fail(`Repository Impact Evidence location ${dependency.analysisResultId}/${finding.id} is inconsistent with Repository Impact.`, 'REFERENCE_MISMATCH');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (impact.status !== undefined
|
|
526
|
+
&& (dependency.status !== impact.status
|
|
527
|
+
|| dependency.reasonCode !== impact.reasonCode
|
|
528
|
+
|| !sameJson(dependency.coverage, impact.coverage))) {
|
|
529
|
+
fail(`Repository Impact Evidence state ${dependency.analysisResultId} is inconsistent with Repository Impact.`, 'REFERENCE_MISMATCH');
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (seen.size !== impacts.size) {
|
|
533
|
+
fail('Repository Impact Evidence does not contain every Repository Impact result exactly once.', 'REFERENCE_MISMATCH');
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function validateMigrationChecklistInputReferences(artifacts) {
|
|
538
|
+
validateDependencyIdentities(artifacts);
|
|
539
|
+
validateEvidenceReferences(artifacts);
|
|
540
|
+
validateUsageReferences(artifacts);
|
|
541
|
+
validateImpactReferences(artifacts);
|
|
542
|
+
validateImpactEvidenceReferences(artifacts);
|
|
543
|
+
return artifacts;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function validateParsedArtifacts(records) {
|
|
547
|
+
let projectInput;
|
|
548
|
+
let knowledgeInput;
|
|
549
|
+
let bundleInput;
|
|
550
|
+
try {
|
|
551
|
+
[projectInput, knowledgeInput, bundleInput] = await Promise.all([
|
|
552
|
+
loadProjectManifestInput(
|
|
553
|
+
{ bytes: records.projectManifest.bytes, artifact: records.projectManifest.artifact }
|
|
554
|
+
),
|
|
555
|
+
loadKnowledgeManifestInput(
|
|
556
|
+
{ bytes: records.knowledgeManifest.bytes, artifact: records.knowledgeManifest.artifact }
|
|
557
|
+
),
|
|
558
|
+
loadKnowledgeEvidenceBundleInput(
|
|
559
|
+
{ bytes: records.knowledgeEvidenceBundle.bytes, artifact: records.knowledgeEvidenceBundle.artifact }
|
|
560
|
+
)
|
|
561
|
+
]);
|
|
562
|
+
validateVersionAnalysisManifest(records.versionAnalysis.value);
|
|
563
|
+
validateUsageIndex(records.usageIndex.value);
|
|
564
|
+
validateRepositoryImpact(records.repositoryImpact.value);
|
|
565
|
+
validateRepositoryImpactEvidence(records.repositoryImpactEvidence.value);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
if (error instanceof MigrationChecklistInputError) throw error;
|
|
568
|
+
fail(error.message, error.code ?? 'ARTIFACT_VALIDATION_FAILED');
|
|
569
|
+
}
|
|
570
|
+
return { projectInput, knowledgeInput, bundleInput };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Load, schema-validate, and cross-check the seven immutable MVP-05 inputs. */
|
|
574
|
+
export async function loadMigrationChecklistInputs(input, options = {}) {
|
|
575
|
+
let invocation = invocationSources(input, options);
|
|
576
|
+
if (
|
|
577
|
+
!invocation.sources
|
|
578
|
+
&& !invocation.rootUrl
|
|
579
|
+
&& Object.keys(invocation.artifacts).length === 0
|
|
580
|
+
) {
|
|
581
|
+
const selection = await resolveArtifactRootChain(
|
|
582
|
+
invocation.repositoryRoot,
|
|
583
|
+
ARTIFACTS.map(({ artifact }) => artifact),
|
|
584
|
+
{ onDiagnostic: options.onCompatibilityDiagnostic }
|
|
585
|
+
);
|
|
586
|
+
invocation = {
|
|
587
|
+
...invocation,
|
|
588
|
+
selectedArtifacts: Object.fromEntries(
|
|
589
|
+
ARTIFACTS.map(({ key }, index) => [key, selection.artifacts[index]])
|
|
590
|
+
)
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const records = {};
|
|
594
|
+
for (const descriptor of ARTIFACTS) {
|
|
595
|
+
const source = sourceFor(descriptor, invocation);
|
|
596
|
+
const artifact = artifactFor(descriptor, source, invocation);
|
|
597
|
+
records[descriptor.key] = await readArtifact(descriptor, source, artifact);
|
|
598
|
+
}
|
|
599
|
+
const { projectInput, knowledgeInput, bundleInput } = await validateParsedArtifacts(records);
|
|
600
|
+
const artifacts = {
|
|
601
|
+
projectManifest: projectInput.manifest,
|
|
602
|
+
knowledgeManifest: knowledgeInput.manifest,
|
|
603
|
+
knowledgeEvidenceBundle: bundleInput.bundle,
|
|
604
|
+
versionAnalysis: records.versionAnalysis.value,
|
|
605
|
+
usageIndex: records.usageIndex.value,
|
|
606
|
+
repositoryImpact: records.repositoryImpact.value,
|
|
607
|
+
repositoryImpactEvidence: records.repositoryImpactEvidence.value,
|
|
608
|
+
input: lineageMetadata(records, projectInput, knowledgeInput, bundleInput)
|
|
609
|
+
};
|
|
610
|
+
validateMigrationChecklistInputLineage(artifacts);
|
|
611
|
+
validateMigrationChecklistInputReferences(artifacts);
|
|
612
|
+
return deepFreeze(structuredClone(artifacts));
|
|
613
|
+
}
|