@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,615 @@
|
|
|
1
|
+
import { compareText, isPortableRelativePath, isSorted } from './portable.js';
|
|
2
|
+
import { validateResearchPlan } from './research-plan.js';
|
|
3
|
+
import { resolveSourceProvenance, validateSourceGraph } from './source-provenance.js';
|
|
4
|
+
|
|
5
|
+
const RESULT_VERSION = '1';
|
|
6
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
7
|
+
const MAX_CONCURRENCY = 32;
|
|
8
|
+
const PACKAGE_STATUSES = new Set(['resolved', 'partial', 'notFound', 'invalid', 'unavailable']);
|
|
9
|
+
const CACHE_OUTCOMES = new Set(['hit', 'miss', 'revalidated', 'corrupted-replaced', 'corrupted', 'expired']);
|
|
10
|
+
|
|
11
|
+
function compareBy(...selectors) {
|
|
12
|
+
return (left, right) => {
|
|
13
|
+
for (const selector of selectors) {
|
|
14
|
+
const result = compareText(selector(left), selector(right));
|
|
15
|
+
if (result !== 0) return result;
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const compareOccurrences = compareBy(
|
|
22
|
+
(item) => item.projectId,
|
|
23
|
+
(item) => item.manifest,
|
|
24
|
+
(item) => item.dependencyType,
|
|
25
|
+
(item) => item.declaredName,
|
|
26
|
+
(item) => item.declaredVersion ?? ''
|
|
27
|
+
);
|
|
28
|
+
const compareInvalidOccurrences = compareBy(
|
|
29
|
+
(item) => item.projectId,
|
|
30
|
+
(item) => item.manifest,
|
|
31
|
+
(item) => item.dependencyType,
|
|
32
|
+
(item) => item.declaredName,
|
|
33
|
+
(item) => item.declaredVersion ?? '',
|
|
34
|
+
(item) => item.reason
|
|
35
|
+
);
|
|
36
|
+
const compareWarnings = compareBy(
|
|
37
|
+
(item) => item.packageId ?? '',
|
|
38
|
+
(item) => item.code,
|
|
39
|
+
(item) => item.sourceId ?? '',
|
|
40
|
+
(item) => item.message
|
|
41
|
+
);
|
|
42
|
+
const comparePackageOutcomes = compareBy((item) => item.packageId);
|
|
43
|
+
|
|
44
|
+
function sortedUnique(values) {
|
|
45
|
+
return [...new Set(values)].sort(compareText);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function clone(value) {
|
|
49
|
+
return structuredClone(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function plainObject(value) {
|
|
53
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
54
|
+
const prototype = Object.getPrototypeOf(value);
|
|
55
|
+
return prototype === Object.prototype || prototype === null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function dateFromClock(clock) {
|
|
59
|
+
const value = clock();
|
|
60
|
+
const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
61
|
+
if (Number.isNaN(date.getTime())) throw new Error('Knowledge Research clock returned an invalid time.');
|
|
62
|
+
return date;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validateConcurrency(value) {
|
|
66
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_CONCURRENCY) {
|
|
67
|
+
throw new Error(`Knowledge Research concurrency must be an integer from 1 to ${MAX_CONCURRENCY}.`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function mapBounded(items, concurrency, worker) {
|
|
73
|
+
const results = new Array(items.length);
|
|
74
|
+
let next = 0;
|
|
75
|
+
async function runWorker() {
|
|
76
|
+
while (next < items.length) {
|
|
77
|
+
const index = next;
|
|
78
|
+
next += 1;
|
|
79
|
+
results[index] = await worker(items[index]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, runWorker));
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function fallbackUrls(researchPackage) {
|
|
87
|
+
if (researchPackage.registry === 'npm') {
|
|
88
|
+
return {
|
|
89
|
+
registryBaseUrl: 'https://registry.npmjs.org',
|
|
90
|
+
packageUrl: `https://www.npmjs.com/package/${researchPackage.normalizedName}`,
|
|
91
|
+
apiUrl: `https://registry.npmjs.org/${encodeURIComponent(researchPackage.normalizedName)}`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
registryBaseUrl: 'https://pypi.org',
|
|
96
|
+
packageUrl: `https://pypi.org/project/${encodeURIComponent(researchPackage.normalizedName)}/`,
|
|
97
|
+
apiUrl: `https://pypi.org/pypi/${encodeURIComponent(researchPackage.normalizedName)}/json`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function unavailableResult(researchPackage, message, retryable) {
|
|
102
|
+
const sourceId = `${researchPackage.id}:registry`;
|
|
103
|
+
const urls = fallbackUrls(researchPackage);
|
|
104
|
+
const warning = {
|
|
105
|
+
code: 'REGISTRY_UNAVAILABLE',
|
|
106
|
+
packageId: researchPackage.id,
|
|
107
|
+
sourceId,
|
|
108
|
+
message,
|
|
109
|
+
retryable
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
package: {
|
|
113
|
+
id: researchPackage.id,
|
|
114
|
+
ecosystem: researchPackage.ecosystem,
|
|
115
|
+
status: 'unavailable',
|
|
116
|
+
identity: {
|
|
117
|
+
observedDeclaredNames: clone(researchPackage.observedDeclaredNames),
|
|
118
|
+
normalizedName: researchPackage.normalizedName,
|
|
119
|
+
registry: researchPackage.registry,
|
|
120
|
+
...urls
|
|
121
|
+
},
|
|
122
|
+
occurrences: clone(researchPackage.occurrences),
|
|
123
|
+
metadata: {},
|
|
124
|
+
latest: null,
|
|
125
|
+
releaseIndex: [],
|
|
126
|
+
sourceIds: [sourceId],
|
|
127
|
+
warningCodes: ['REGISTRY_UNAVAILABLE']
|
|
128
|
+
},
|
|
129
|
+
source: {
|
|
130
|
+
id: sourceId,
|
|
131
|
+
kind: 'registry',
|
|
132
|
+
authority: 'registryAuthoritative',
|
|
133
|
+
trust: 'publisher',
|
|
134
|
+
url: urls.packageUrl,
|
|
135
|
+
apiUrl: urls.apiUrl,
|
|
136
|
+
status: 'unavailable',
|
|
137
|
+
supports: ['identity'],
|
|
138
|
+
discoveredFrom: null,
|
|
139
|
+
trustEvidenceSourceIds: [],
|
|
140
|
+
snapshot: null
|
|
141
|
+
},
|
|
142
|
+
sourceCandidates: [],
|
|
143
|
+
warnings: [warning],
|
|
144
|
+
cache: null
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function adapterResultFor(researchPackage, result) {
|
|
149
|
+
const packageRecord = result?.package;
|
|
150
|
+
const source = result?.source;
|
|
151
|
+
if (!packageRecord || packageRecord.id !== researchPackage.id
|
|
152
|
+
|| packageRecord.ecosystem !== researchPackage.ecosystem
|
|
153
|
+
|| !PACKAGE_STATUSES.has(packageRecord.status)
|
|
154
|
+
|| !source || source.id !== `${researchPackage.id}:registry` || source.kind !== 'registry') {
|
|
155
|
+
throw new Error('adapter returned an incompatible normalized registry result.');
|
|
156
|
+
}
|
|
157
|
+
if (!plainObject(packageRecord.identity) || !plainObject(packageRecord.metadata)
|
|
158
|
+
|| !Array.isArray(packageRecord.releaseIndex) || !Array.isArray(packageRecord.sourceIds)
|
|
159
|
+
|| !Array.isArray(packageRecord.warningCodes) || !Array.isArray(result.sourceCandidates ?? [])
|
|
160
|
+
|| !Array.isArray(source.supports) || typeof source.url !== 'string'
|
|
161
|
+
|| typeof source.status !== 'string' || source.discoveredFrom !== null
|
|
162
|
+
|| !Array.isArray(source.trustEvidenceSourceIds)) {
|
|
163
|
+
throw new Error('adapter returned an incomplete normalized registry result.');
|
|
164
|
+
}
|
|
165
|
+
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
|
|
166
|
+
if (!warnings.every((warning) => warning && typeof warning.code === 'string'
|
|
167
|
+
&& typeof warning.message === 'string' && typeof warning.retryable === 'boolean')) {
|
|
168
|
+
throw new Error('adapter returned an invalid warning record.');
|
|
169
|
+
}
|
|
170
|
+
const outcome = result.cache?.outcome;
|
|
171
|
+
if (outcome !== undefined && !CACHE_OUTCOMES.has(outcome)) {
|
|
172
|
+
throw new Error('adapter returned an unknown cache outcome.');
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
package: {
|
|
176
|
+
id: packageRecord.id,
|
|
177
|
+
ecosystem: packageRecord.ecosystem,
|
|
178
|
+
status: packageRecord.status,
|
|
179
|
+
identity: clone(packageRecord.identity),
|
|
180
|
+
occurrences: clone(researchPackage.occurrences),
|
|
181
|
+
metadata: clone(packageRecord.metadata),
|
|
182
|
+
latest: clone(packageRecord.latest),
|
|
183
|
+
releaseIndex: clone(packageRecord.releaseIndex),
|
|
184
|
+
sourceIds: clone(packageRecord.sourceIds),
|
|
185
|
+
warningCodes: sortedUnique(packageRecord.warningCodes ?? [])
|
|
186
|
+
},
|
|
187
|
+
source: clone(source),
|
|
188
|
+
sourceCandidates: clone(result.sourceCandidates ?? []),
|
|
189
|
+
warnings: clone(warnings),
|
|
190
|
+
cache: outcome ?? null
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function invalidWarnings(invalidOccurrences) {
|
|
195
|
+
return invalidOccurrences.map((occurrence) => ({
|
|
196
|
+
code: 'INVALID_PACKAGE_REFERENCE',
|
|
197
|
+
message: `Dependency reference is not a supported public ${occurrence.ecosystem} package identity.`,
|
|
198
|
+
retryable: false
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function deduplicateWarnings(warnings) {
|
|
203
|
+
const byKey = new Map();
|
|
204
|
+
for (const warning of warnings) {
|
|
205
|
+
const normalized = {
|
|
206
|
+
code: warning.code,
|
|
207
|
+
...(warning.packageId ? { packageId: warning.packageId } : {}),
|
|
208
|
+
...(warning.sourceId ? { sourceId: warning.sourceId } : {}),
|
|
209
|
+
message: warning.message,
|
|
210
|
+
retryable: warning.retryable
|
|
211
|
+
};
|
|
212
|
+
const key = JSON.stringify(normalized);
|
|
213
|
+
if (!byKey.has(key)) byKey.set(key, normalized);
|
|
214
|
+
}
|
|
215
|
+
return [...byKey.values()].sort(compareWarnings);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function cacheSummary(outcomes) {
|
|
219
|
+
const summary = {
|
|
220
|
+
cacheHitCount: 0,
|
|
221
|
+
cacheMissCount: 0,
|
|
222
|
+
cacheRevalidationCount: 0,
|
|
223
|
+
cacheCorruptionReplacementCount: 0,
|
|
224
|
+
cacheCorruptedCount: 0
|
|
225
|
+
};
|
|
226
|
+
for (const outcome of outcomes) {
|
|
227
|
+
if (outcome.cacheOutcome === 'hit') summary.cacheHitCount += 1;
|
|
228
|
+
if (outcome.cacheOutcome === 'miss') summary.cacheMissCount += 1;
|
|
229
|
+
if (outcome.cacheOutcome === 'revalidated') summary.cacheRevalidationCount += 1;
|
|
230
|
+
if (outcome.cacheOutcome === 'corrupted-replaced') summary.cacheCorruptionReplacementCount += 1;
|
|
231
|
+
if (outcome.cacheOutcome === 'corrupted') summary.cacheCorruptedCount += 1;
|
|
232
|
+
}
|
|
233
|
+
return summary;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function statusSummary(packages) {
|
|
237
|
+
const counts = {
|
|
238
|
+
resolvedPackageCount: 0,
|
|
239
|
+
partialPackageCount: 0,
|
|
240
|
+
notFoundPackageCount: 0,
|
|
241
|
+
invalidPackageCount: 0,
|
|
242
|
+
unavailablePackageCount: 0
|
|
243
|
+
};
|
|
244
|
+
for (const packageRecord of packages) {
|
|
245
|
+
if (packageRecord.status === 'resolved') counts.resolvedPackageCount += 1;
|
|
246
|
+
if (packageRecord.status === 'partial') counts.partialPackageCount += 1;
|
|
247
|
+
if (packageRecord.status === 'notFound') counts.notFoundPackageCount += 1;
|
|
248
|
+
if (packageRecord.status === 'invalid') counts.invalidPackageCount += 1;
|
|
249
|
+
if (packageRecord.status === 'unavailable') counts.unavailablePackageCount += 1;
|
|
250
|
+
}
|
|
251
|
+
return counts;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function adapterInvocationCounts(packageOutcomes, plannedPackages) {
|
|
255
|
+
const counts = { npm: 0, pypi: 0 };
|
|
256
|
+
for (const outcome of packageOutcomes) {
|
|
257
|
+
if (!outcome.adapterInvoked) continue;
|
|
258
|
+
const registry = plannedPackages.get(outcome.packageId)?.registry;
|
|
259
|
+
if (registry === 'npm' || registry === 'pypi') counts[registry] += 1;
|
|
260
|
+
}
|
|
261
|
+
return counts;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sourceIdsByPackage(graph) {
|
|
265
|
+
return new Map(graph.packages.map((record) => [record.packageId, record.sourceIds]));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function sanitizeObjectErrors(value, path = '') {
|
|
269
|
+
const errors = [];
|
|
270
|
+
const forbiddenKeys = /(?:cachekey|rootdirectory|storepath|etag|lastmodified|headers|stack|responsebody|rawbody)/i;
|
|
271
|
+
if (typeof value === 'string') {
|
|
272
|
+
if (value.startsWith('/') || /^[A-Za-z]:[\\/]/.test(value)) errors.push(`${path} contains an absolute local path.`);
|
|
273
|
+
if (/[a-z][a-z0-9+.-]*:\/\/[^/?#]*@/i.test(value)) errors.push(`${path} contains URL credentials.`);
|
|
274
|
+
if (/\?(?:[^\s]*)(?:token|secret|password|api[_-]?key|signature|auth)=/i.test(value)) {
|
|
275
|
+
errors.push(`${path} contains a query token.`);
|
|
276
|
+
}
|
|
277
|
+
return errors;
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(value)) return value.flatMap((item, index) => sanitizeObjectErrors(item, `${path}[${index}]`));
|
|
280
|
+
if (!value || typeof value !== 'object') return errors;
|
|
281
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
282
|
+
const nestedPath = path ? `${path}.${key}` : key;
|
|
283
|
+
if (forbiddenKeys.test(key)) errors.push(`${nestedPath} exposes forbidden internal data.`);
|
|
284
|
+
errors.push(...sanitizeObjectErrors(nested, nestedPath));
|
|
285
|
+
}
|
|
286
|
+
return errors;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function throwInvariant(errors) {
|
|
290
|
+
if (errors.length > 0) {
|
|
291
|
+
throw new Error(`Knowledge Research invariant violation: ${[...new Set(errors)].sort(compareText).join(' ')}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Validate the private, canonical research result before a future task turns
|
|
297
|
+
* it into a public Knowledge Manifest.
|
|
298
|
+
*/
|
|
299
|
+
export function validateKnowledgeResearchResult(result, researchPlan) {
|
|
300
|
+
validateResearchPlan(researchPlan);
|
|
301
|
+
const errors = [];
|
|
302
|
+
if (result?.resultVersion !== RESULT_VERSION) errors.push(`resultVersion must be ${RESULT_VERSION}.`);
|
|
303
|
+
if (JSON.stringify(result?.input) !== JSON.stringify({
|
|
304
|
+
projectManifest: researchPlan.input.projectManifest,
|
|
305
|
+
researchPlanVersion: researchPlan.planVersion
|
|
306
|
+
})) errors.push('input lineage must match the Research Plan.');
|
|
307
|
+
|
|
308
|
+
const packages = result?.packages ?? [];
|
|
309
|
+
const sources = result?.sources ?? [];
|
|
310
|
+
const warnings = result?.warnings ?? [];
|
|
311
|
+
const evidence = result?.evidence ?? [];
|
|
312
|
+
const invalidOccurrences = result?.invalidOccurrences ?? [];
|
|
313
|
+
const unsupported = result?.unsupported ?? [];
|
|
314
|
+
const packageOutcomes = result?.execution?.packageOutcomes ?? [];
|
|
315
|
+
const summary = result?.summary ?? {};
|
|
316
|
+
const knownSourceIds = new Set(sources.map((source) => source.id));
|
|
317
|
+
const plannedPackages = new Map(researchPlan.packages.map((item) => [item.id, item]));
|
|
318
|
+
|
|
319
|
+
if (!isSorted(packages, (left, right) => compareText(left.id, right.id))) errors.push('packages must be sorted by id.');
|
|
320
|
+
if (!isSorted(sources, (left, right) => compareText(left.id, right.id))) errors.push('sources must be sorted by id.');
|
|
321
|
+
if (!isSorted(warnings, compareWarnings)) errors.push('warnings must be sorted canonically.');
|
|
322
|
+
if (!isSorted(invalidOccurrences, compareInvalidOccurrences)) errors.push('invalidOccurrences must be canonically sorted.');
|
|
323
|
+
if (!isSorted(unsupported, (left, right) => compareText(left.ecosystem, right.ecosystem))) errors.push('unsupported must be sorted by ecosystem.');
|
|
324
|
+
if (!isSorted(packageOutcomes, comparePackageOutcomes)) errors.push('execution packageOutcomes must be sorted by packageId.');
|
|
325
|
+
if (!Array.isArray(evidence)
|
|
326
|
+
|| !isSorted(evidence, (left, right) => compareText(left.id, right.id))) {
|
|
327
|
+
errors.push('evidence must be an array sorted by id.');
|
|
328
|
+
}
|
|
329
|
+
for (const item of evidence) {
|
|
330
|
+
if (!knownSourceIds.has(item.sourceId)) errors.push(`evidence ${item.id} references an unknown source.`);
|
|
331
|
+
if (!plannedPackages.has(item.packageId)) errors.push(`evidence ${item.id} references an unknown package.`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const packageIds = new Set();
|
|
335
|
+
for (const packageRecord of packages) {
|
|
336
|
+
if (!packageRecord || packageIds.has(packageRecord.id) || !plannedPackages.has(packageRecord.id)) {
|
|
337
|
+
errors.push('packages must correspond one-to-one with planned packages.');
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
packageIds.add(packageRecord.id);
|
|
341
|
+
const planned = plannedPackages.get(packageRecord.id);
|
|
342
|
+
if (packageRecord.ecosystem !== planned.ecosystem || packageRecord.identity?.registry !== planned.registry
|
|
343
|
+
|| packageRecord.identity?.normalizedName !== planned.normalizedName
|
|
344
|
+
|| JSON.stringify(packageRecord.occurrences) !== JSON.stringify(planned.occurrences)) {
|
|
345
|
+
errors.push(`package ${packageRecord.id} does not preserve its Research Plan identity and occurrences.`);
|
|
346
|
+
}
|
|
347
|
+
if (!PACKAGE_STATUSES.has(packageRecord.status)) errors.push(`package ${packageRecord.id} has an invalid status.`);
|
|
348
|
+
if (!isSorted(packageRecord.occurrences ?? [], compareOccurrences)) errors.push(`package ${packageRecord.id} occurrences must be sorted.`);
|
|
349
|
+
if (!isSorted(packageRecord.sourceIds ?? [], compareText)
|
|
350
|
+
|| new Set(packageRecord.sourceIds ?? []).size !== (packageRecord.sourceIds ?? []).length) {
|
|
351
|
+
errors.push(`package ${packageRecord.id} sourceIds must be sorted and unique.`);
|
|
352
|
+
}
|
|
353
|
+
if (!isSorted(packageRecord.warningCodes ?? [], compareText)) errors.push(`package ${packageRecord.id} warningCodes must be sorted.`);
|
|
354
|
+
for (const sourceId of packageRecord.sourceIds ?? []) {
|
|
355
|
+
if (!knownSourceIds.has(sourceId)) errors.push(`package ${packageRecord.id} references unknown source ${sourceId}.`);
|
|
356
|
+
}
|
|
357
|
+
if (packageRecord.latest?.sourceId && !knownSourceIds.has(packageRecord.latest.sourceId)) {
|
|
358
|
+
errors.push(`package ${packageRecord.id} latest references an unknown source.`);
|
|
359
|
+
}
|
|
360
|
+
for (const release of packageRecord.releaseIndex ?? []) {
|
|
361
|
+
if (!isSorted(release.sourceIds ?? [], compareText)) errors.push(`package ${packageRecord.id} release sourceIds must be sorted.`);
|
|
362
|
+
for (const sourceId of release.sourceIds ?? []) {
|
|
363
|
+
if (!knownSourceIds.has(sourceId)) errors.push(`package ${packageRecord.id} release references an unknown source.`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const code of packageRecord.warningCodes ?? []) {
|
|
367
|
+
if (!warnings.some((warning) => warning.packageId === packageRecord.id && warning.code === code)) {
|
|
368
|
+
errors.push(`package ${packageRecord.id} warning code has no warning.`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (packageIds.size !== plannedPackages.size) errors.push('every Research Plan package must have exactly one result.');
|
|
373
|
+
if (new Set(sources.map((source) => source.id)).size !== sources.length) errors.push('source IDs must be unique.');
|
|
374
|
+
|
|
375
|
+
for (const warning of warnings) {
|
|
376
|
+
if (!warning || typeof warning.code !== 'string' || typeof warning.message !== 'string'
|
|
377
|
+
|| typeof warning.retryable !== 'boolean') errors.push('warnings must have a code, message, and retryable value.');
|
|
378
|
+
if (warning.packageId && !packageIds.has(warning.packageId)) errors.push(`warning ${warning.code} references an unknown package.`);
|
|
379
|
+
if (warning.sourceId && !knownSourceIds.has(warning.sourceId)) errors.push(`warning ${warning.code} references an unknown source.`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (JSON.stringify(invalidOccurrences) !== JSON.stringify(researchPlan.invalidOccurrences)) {
|
|
383
|
+
errors.push('invalidOccurrences must match the Research Plan.');
|
|
384
|
+
}
|
|
385
|
+
if (JSON.stringify(unsupported) !== JSON.stringify(researchPlan.unsupported)) {
|
|
386
|
+
errors.push('unsupported records must match the Research Plan.');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
validateSourceGraph({
|
|
391
|
+
packages: packages.map((item) => ({ packageId: item.id, sourceIds: item.sourceIds })),
|
|
392
|
+
sources,
|
|
393
|
+
warnings: warnings.filter((warning) => warning.code === 'SOURCE_CONFLICT')
|
|
394
|
+
});
|
|
395
|
+
} catch (error) {
|
|
396
|
+
errors.push(error.message);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const cache = cacheSummary(packageOutcomes);
|
|
400
|
+
const invocationCounts = adapterInvocationCounts(packageOutcomes, plannedPackages);
|
|
401
|
+
const statuses = statusSummary(packages);
|
|
402
|
+
const partialFailureCount = packages.filter((item) => item.status !== 'resolved').length;
|
|
403
|
+
const outcomeIds = new Set();
|
|
404
|
+
for (const outcome of packageOutcomes) {
|
|
405
|
+
if (!plannedPackages.has(outcome.packageId) || outcomeIds.has(outcome.packageId)) {
|
|
406
|
+
errors.push('execution packageOutcomes must correspond one-to-one with planned packages.');
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
outcomeIds.add(outcome.packageId);
|
|
410
|
+
if (typeof outcome.adapterInvoked !== 'boolean'
|
|
411
|
+
|| (outcome.cacheOutcome !== null && !CACHE_OUTCOMES.has(outcome.cacheOutcome))) {
|
|
412
|
+
errors.push(`execution outcome for ${outcome.packageId} is invalid.`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (outcomeIds.size !== plannedPackages.size) errors.push('execution packageOutcomes are incomplete.');
|
|
416
|
+
const expectedSummary = {
|
|
417
|
+
inputOccurrenceCount: researchPlan.summary.inputOccurrenceCount,
|
|
418
|
+
packageCount: packages.length,
|
|
419
|
+
...statuses,
|
|
420
|
+
invalidOccurrenceCount: invalidOccurrences.length,
|
|
421
|
+
unsupportedOccurrenceCount: researchPlan.summary.unsupportedOccurrenceCount,
|
|
422
|
+
sourceCount: sources.length,
|
|
423
|
+
warningCount: warnings.length,
|
|
424
|
+
...cache,
|
|
425
|
+
retryCount: 0,
|
|
426
|
+
partialFailureCount
|
|
427
|
+
};
|
|
428
|
+
for (const [key, value] of Object.entries(expectedSummary)) {
|
|
429
|
+
if (summary[key] !== value) errors.push(`summary.${key} is inconsistent.`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const execution = result?.execution ?? {};
|
|
433
|
+
const startedAt = Date.parse(execution.startedAt);
|
|
434
|
+
const completedAt = Date.parse(execution.completedAt);
|
|
435
|
+
if (!Number.isFinite(startedAt) || !Number.isFinite(completedAt) || execution.durationMs !== completedAt - startedAt) {
|
|
436
|
+
errors.push('execution timestamps and durationMs are inconsistent.');
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
if (execution.concurrency !== validateConcurrency(execution.concurrency)) errors.push('execution concurrency is invalid.');
|
|
440
|
+
} catch {
|
|
441
|
+
errors.push('execution concurrency is invalid.');
|
|
442
|
+
}
|
|
443
|
+
if (execution.inputPackageCount !== packages.length
|
|
444
|
+
|| execution.adapterInvocationCount !== packageOutcomes.filter((item) => item.adapterInvoked).length
|
|
445
|
+
|| JSON.stringify(execution.adapterInvocationCounts) !== JSON.stringify(invocationCounts)
|
|
446
|
+
|| execution.sourceCount !== sources.length || execution.warningCount !== warnings.length
|
|
447
|
+
|| execution.partialFailureCount !== partialFailureCount) {
|
|
448
|
+
errors.push('execution counts are inconsistent.');
|
|
449
|
+
}
|
|
450
|
+
for (const [key, value] of Object.entries(cache)) {
|
|
451
|
+
if (execution[key] !== value) errors.push(`execution.${key} is inconsistent.`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
errors.push(...sanitizeObjectErrors(result));
|
|
455
|
+
throwInvariant(errors);
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Create an explicit, private Knowledge Research orchestrator. It consumes a
|
|
461
|
+
* prevalidated Research Plan; it does not read a repository or write an
|
|
462
|
+
* artifact.
|
|
463
|
+
*/
|
|
464
|
+
export function createKnowledgeResearchOrchestrator({
|
|
465
|
+
adapters = {},
|
|
466
|
+
evidenceSourceAdapter = null,
|
|
467
|
+
sourceProvenanceResolver = resolveSourceProvenance,
|
|
468
|
+
clock = () => new Date(),
|
|
469
|
+
concurrency = DEFAULT_CONCURRENCY
|
|
470
|
+
} = {}) {
|
|
471
|
+
const configuredConcurrency = validateConcurrency(concurrency);
|
|
472
|
+
if (typeof sourceProvenanceResolver !== 'function') {
|
|
473
|
+
throw new Error('Knowledge Research requires a source provenance resolver function.');
|
|
474
|
+
}
|
|
475
|
+
if (evidenceSourceAdapter !== null && typeof evidenceSourceAdapter?.enrich !== 'function') {
|
|
476
|
+
throw new Error('Knowledge Research evidence source adapter must provide enrich(input).');
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
async run(researchPlan, options = {}) {
|
|
480
|
+
validateResearchPlan(researchPlan);
|
|
481
|
+
const effectiveConcurrency = validateConcurrency(options.concurrency ?? configuredConcurrency);
|
|
482
|
+
const started = dateFromClock(clock);
|
|
483
|
+
const registryResults = await mapBounded(researchPlan.packages, effectiveConcurrency, async (researchPackage) => {
|
|
484
|
+
const adapter = adapters[researchPackage.registry];
|
|
485
|
+
if (!adapter || typeof adapter.researchPackage !== 'function') {
|
|
486
|
+
return {
|
|
487
|
+
result: unavailableResult(
|
|
488
|
+
researchPackage,
|
|
489
|
+
`No ${researchPackage.registry} registry adapter is configured for package research.`,
|
|
490
|
+
false
|
|
491
|
+
),
|
|
492
|
+
adapterInvoked: false
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
return {
|
|
497
|
+
result: adapterResultFor(researchPackage, await adapter.researchPackage(clone(researchPackage))),
|
|
498
|
+
adapterInvoked: true
|
|
499
|
+
};
|
|
500
|
+
} catch {
|
|
501
|
+
return {
|
|
502
|
+
result: unavailableResult(
|
|
503
|
+
researchPackage,
|
|
504
|
+
`${researchPackage.registry} Registry package research failed.`,
|
|
505
|
+
true
|
|
506
|
+
),
|
|
507
|
+
adapterInvoked: true
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const normalizedResults = registryResults.map((item) => item.result);
|
|
513
|
+
const provenance = sourceProvenanceResolver(normalizedResults);
|
|
514
|
+
const initialPackageSources = sourceIdsByPackage(provenance);
|
|
515
|
+
const provisionalPackages = normalizedResults.map((item) => ({
|
|
516
|
+
...clone(item.package),
|
|
517
|
+
sourceIds: clone(initialPackageSources.get(item.package.id) ?? [])
|
|
518
|
+
}));
|
|
519
|
+
const enrichment = evidenceSourceAdapter
|
|
520
|
+
? await evidenceSourceAdapter.enrich({ packages: provisionalPackages, sources: clone(provenance.sources) })
|
|
521
|
+
: { packageSources: [], sources: [], evidence: [], warnings: [] };
|
|
522
|
+
const sourcesById = new Map(provenance.sources.map((source) => [source.id, source]));
|
|
523
|
+
for (const source of enrichment.sources ?? []) sourcesById.set(source.id, source);
|
|
524
|
+
const sources = [...sourcesById.values()].sort((left, right) => compareText(left.id, right.id));
|
|
525
|
+
const packageSources = new Map(initialPackageSources);
|
|
526
|
+
for (const record of enrichment.packageSources ?? []) {
|
|
527
|
+
packageSources.set(record.packageId, sortedUnique([
|
|
528
|
+
...(packageSources.get(record.packageId) ?? []),
|
|
529
|
+
...record.sourceIds
|
|
530
|
+
]));
|
|
531
|
+
}
|
|
532
|
+
const provenanceWarnings = provenance.warnings;
|
|
533
|
+
const adapterWarnings = normalizedResults.flatMap((item) => item.warnings);
|
|
534
|
+
const warnings = deduplicateWarnings([
|
|
535
|
+
...adapterWarnings,
|
|
536
|
+
...provenanceWarnings,
|
|
537
|
+
...(enrichment.warnings ?? []),
|
|
538
|
+
...invalidWarnings(researchPlan.invalidOccurrences)
|
|
539
|
+
]);
|
|
540
|
+
const warningsByPackage = new Map();
|
|
541
|
+
for (const warning of warnings) {
|
|
542
|
+
if (!warning.packageId) continue;
|
|
543
|
+
const codes = warningsByPackage.get(warning.packageId) ?? new Set();
|
|
544
|
+
codes.add(warning.code);
|
|
545
|
+
warningsByPackage.set(warning.packageId, codes);
|
|
546
|
+
}
|
|
547
|
+
const conflictPackages = new Set(provenanceWarnings.map((warning) => warning.packageId));
|
|
548
|
+
const packages = normalizedResults.map((item) => {
|
|
549
|
+
const packageRecord = clone(item.package);
|
|
550
|
+
packageRecord.sourceIds = clone(packageSources.get(packageRecord.id) ?? []);
|
|
551
|
+
packageRecord.warningCodes = sortedUnique([
|
|
552
|
+
...(packageRecord.warningCodes ?? []),
|
|
553
|
+
...(warningsByPackage.get(packageRecord.id) ?? [])
|
|
554
|
+
]);
|
|
555
|
+
if (packageRecord.status === 'resolved' && conflictPackages.has(packageRecord.id)) {
|
|
556
|
+
packageRecord.status = 'partial';
|
|
557
|
+
}
|
|
558
|
+
return packageRecord;
|
|
559
|
+
}).sort((left, right) => compareText(left.id, right.id));
|
|
560
|
+
const packageOutcomes = registryResults.map((item) => ({
|
|
561
|
+
packageId: item.result.package.id,
|
|
562
|
+
adapterInvoked: item.adapterInvoked,
|
|
563
|
+
cacheOutcome: item.result.cache
|
|
564
|
+
})).sort(comparePackageOutcomes);
|
|
565
|
+
const completed = dateFromClock(clock);
|
|
566
|
+
const cache = cacheSummary(packageOutcomes);
|
|
567
|
+
const invocationCounts = adapterInvocationCounts(
|
|
568
|
+
packageOutcomes,
|
|
569
|
+
new Map(researchPlan.packages.map((item) => [item.id, item]))
|
|
570
|
+
);
|
|
571
|
+
const statuses = statusSummary(packages);
|
|
572
|
+
const partialFailureCount = packages.filter((item) => item.status !== 'resolved').length;
|
|
573
|
+
const result = {
|
|
574
|
+
resultVersion: RESULT_VERSION,
|
|
575
|
+
input: {
|
|
576
|
+
projectManifest: clone(researchPlan.input.projectManifest),
|
|
577
|
+
researchPlanVersion: researchPlan.planVersion
|
|
578
|
+
},
|
|
579
|
+
execution: {
|
|
580
|
+
startedAt: started.toISOString(),
|
|
581
|
+
completedAt: completed.toISOString(),
|
|
582
|
+
durationMs: completed.getTime() - started.getTime(),
|
|
583
|
+
concurrency: effectiveConcurrency,
|
|
584
|
+
inputPackageCount: packages.length,
|
|
585
|
+
adapterInvocationCount: packageOutcomes.filter((item) => item.adapterInvoked).length,
|
|
586
|
+
adapterInvocationCounts: invocationCounts,
|
|
587
|
+
sourceCount: sources.length,
|
|
588
|
+
warningCount: warnings.length,
|
|
589
|
+
partialFailureCount,
|
|
590
|
+
...cache,
|
|
591
|
+
packageOutcomes
|
|
592
|
+
},
|
|
593
|
+
summary: {
|
|
594
|
+
inputOccurrenceCount: researchPlan.summary.inputOccurrenceCount,
|
|
595
|
+
packageCount: packages.length,
|
|
596
|
+
...statuses,
|
|
597
|
+
invalidOccurrenceCount: researchPlan.invalidOccurrences.length,
|
|
598
|
+
unsupportedOccurrenceCount: researchPlan.summary.unsupportedOccurrenceCount,
|
|
599
|
+
sourceCount: sources.length,
|
|
600
|
+
warningCount: warnings.length,
|
|
601
|
+
...cache,
|
|
602
|
+
retryCount: 0,
|
|
603
|
+
partialFailureCount
|
|
604
|
+
},
|
|
605
|
+
packages,
|
|
606
|
+
sources,
|
|
607
|
+
evidence: clone(enrichment.evidence ?? []),
|
|
608
|
+
warnings,
|
|
609
|
+
invalidOccurrences: clone(researchPlan.invalidOccurrences),
|
|
610
|
+
unsupported: clone(researchPlan.unsupported)
|
|
611
|
+
};
|
|
612
|
+
return validateKnowledgeResearchResult(result, researchPlan);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|