@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,336 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
7
|
+
|
|
8
|
+
import { AI_RUNTIME_ERROR_CODES, AiRuntimeError } from '../../ai-runtime-error.js';
|
|
9
|
+
import { canonicalJson, canonicalJsonBytes } from '../../canonical-json.js';
|
|
10
|
+
import { compareText } from '../../portable.js';
|
|
11
|
+
import {
|
|
12
|
+
isMigrationChecklistTrustError,
|
|
13
|
+
trustValidateMigrationChecklistCandidate,
|
|
14
|
+
validateMigrationChecklistCandidate
|
|
15
|
+
} from '../ai-candidate.js';
|
|
16
|
+
import { MIGRATION_PLANNING_TASK } from '../prompt.js';
|
|
17
|
+
|
|
18
|
+
export const MIGRATION_EVALUATION_DATASET_VERSION = '1.0.0';
|
|
19
|
+
export const MIGRATION_EVALUATION_DATASET_ID = 'migration-planning-golden';
|
|
20
|
+
export const DEFAULT_MIGRATION_EVALUATION_DATASET_PATH = 'eval/migration-planning/golden-dataset.json';
|
|
21
|
+
const BUNDLED_MIGRATION_EVALUATION_DATASET_PATH = fileURLToPath(
|
|
22
|
+
new URL('../../../eval/migration-planning/golden-dataset.json', import.meta.url)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const schema = JSON.parse(await readFile(
|
|
26
|
+
new URL('../../../schemas/migration-evaluation-dataset.schema.json', import.meta.url),
|
|
27
|
+
'utf8'
|
|
28
|
+
));
|
|
29
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true });
|
|
30
|
+
const validateSchema = ajv.compile(schema);
|
|
31
|
+
|
|
32
|
+
function digest(value) {
|
|
33
|
+
const bytes = typeof value === 'string' ? value : canonicalJsonBytes(value);
|
|
34
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function deepFreeze(value) {
|
|
38
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
39
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
40
|
+
return Object.freeze(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function datasetError(message) {
|
|
44
|
+
return new Error(`Migration Evaluation Dataset error: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sorted(values = []) {
|
|
48
|
+
return [...values].sort(compareText);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function duplicate(values) {
|
|
52
|
+
return sorted([...new Set(values.filter((value, index) => values.indexOf(value) !== index))]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function same(left, right) {
|
|
56
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function limitationInput() {
|
|
60
|
+
const artifact = (name) => ({
|
|
61
|
+
schemaVersion: '1.0.0',
|
|
62
|
+
artifact: `.upgradelens/${name}.json`,
|
|
63
|
+
artifactDigest: digest(name)
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
projectManifest: {
|
|
67
|
+
schemaVersion: '2.0.0',
|
|
68
|
+
artifact: '.upgradelens/project-manifest.json',
|
|
69
|
+
artifactDigest: digest('project-manifest'),
|
|
70
|
+
repository: { name: 'migration-evaluation', root: '.' }
|
|
71
|
+
},
|
|
72
|
+
knowledgeManifest: {
|
|
73
|
+
...artifact('knowledge-manifest'),
|
|
74
|
+
researchId: digest('knowledge-research')
|
|
75
|
+
},
|
|
76
|
+
knowledgeEvidenceBundle: artifact('knowledge-evidence-bundle'),
|
|
77
|
+
versionAnalysis: artifact('version-analysis'),
|
|
78
|
+
usageIndex: artifact('usage-index'),
|
|
79
|
+
repositoryImpact: artifact('repository-impact'),
|
|
80
|
+
repositoryImpactEvidence: artifact('repository-impact-evidence')
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function buildMigrationEvaluationContext(goldenCase) {
|
|
85
|
+
const evidence = goldenCase.fixture.evidence.map((item) => ({
|
|
86
|
+
id: item.id,
|
|
87
|
+
sourceId: item.sourceId,
|
|
88
|
+
sourceUrl: `https://evaluation.invalid/${goldenCase.id}`,
|
|
89
|
+
kind: item.kind,
|
|
90
|
+
authority: item.authority,
|
|
91
|
+
trust: item.trust,
|
|
92
|
+
retrievedAt: '2026-07-16T00:00:00.000Z',
|
|
93
|
+
contentDigest: digest(item.content),
|
|
94
|
+
locator: `case:${goldenCase.id}`,
|
|
95
|
+
releaseVersions: sorted(item.releaseVersions),
|
|
96
|
+
content: item.content
|
|
97
|
+
})).sort((left, right) => compareText(left.id, right.id));
|
|
98
|
+
const evidenceAllowlist = evidence.map((item) => item.id).sort(compareText);
|
|
99
|
+
const dependency = {
|
|
100
|
+
projectId: `${goldenCase.ecosystem}:${goldenCase.id}`,
|
|
101
|
+
packageId: goldenCase.fixture.dependency.packageId,
|
|
102
|
+
declaredName: goldenCase.fixture.dependency.declaredName,
|
|
103
|
+
normalizedName: goldenCase.fixture.dependency.normalizedName,
|
|
104
|
+
ecosystem: goldenCase.ecosystem,
|
|
105
|
+
registry: goldenCase.fixture.dependency.registry,
|
|
106
|
+
packageManager: goldenCase.fixture.dependency.packageManager,
|
|
107
|
+
dependencyType: goldenCase.fixture.dependency.dependencyType,
|
|
108
|
+
manifest: goldenCase.fixture.dependency.manifest
|
|
109
|
+
};
|
|
110
|
+
const payload = {
|
|
111
|
+
dependency,
|
|
112
|
+
versions: structuredClone(goldenCase.fixture.versions),
|
|
113
|
+
analysisResultId: digest(`${goldenCase.id}:analysis`),
|
|
114
|
+
finding: {
|
|
115
|
+
id: goldenCase.fixture.findingId,
|
|
116
|
+
kind: 'breakingChange',
|
|
117
|
+
summary: goldenCase.fixture.findingSummary,
|
|
118
|
+
appliesToVersions: [goldenCase.fixture.versions.targetVersion],
|
|
119
|
+
evidenceRefs: evidenceAllowlist
|
|
120
|
+
},
|
|
121
|
+
evidence,
|
|
122
|
+
evidenceAllowlist,
|
|
123
|
+
positiveCandidateLocations: structuredClone(goldenCase.fixture.locations),
|
|
124
|
+
eligibility: { status: 'ELIGIBLE', reasonCode: 'ELIGIBLE' },
|
|
125
|
+
locationEligibility: structuredClone(goldenCase.fixture.locationEligibility),
|
|
126
|
+
requiresHumanReview: true,
|
|
127
|
+
humanReviewReasons: ['MIGRATION_CHECKLIST_DRAFT_REVIEW_REQUIRED'],
|
|
128
|
+
limitations: structuredClone(goldenCase.fixture.limitations)
|
|
129
|
+
};
|
|
130
|
+
return deepFreeze({
|
|
131
|
+
contextVersion: '1',
|
|
132
|
+
contextId: digest({ caseId: goldenCase.id, payload }),
|
|
133
|
+
...payload
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function buildMigrationEvaluationPrepared(goldenCase) {
|
|
138
|
+
return deepFreeze({
|
|
139
|
+
contextVersion: '1',
|
|
140
|
+
input: limitationInput(),
|
|
141
|
+
eligibleContexts: [buildMigrationEvaluationContext(goldenCase)],
|
|
142
|
+
fallbackRecords: [],
|
|
143
|
+
summary: {
|
|
144
|
+
totalFindings: 1,
|
|
145
|
+
eligible: 1,
|
|
146
|
+
notAnalyzed: 0,
|
|
147
|
+
noGroundedAction: 0,
|
|
148
|
+
unsupportedUsageCoverage: goldenCase.fixture.locationEligibility.reasonCode
|
|
149
|
+
=== 'UNSUPPORTED_USAGE_COVERAGE' ? 1 : 0,
|
|
150
|
+
conflictedEvidence: 0
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function buildMigrationPolicyProbeCandidate(probe) {
|
|
156
|
+
return {
|
|
157
|
+
status: 'ACTIONABLE',
|
|
158
|
+
items: [{
|
|
159
|
+
instruction: probe.instruction,
|
|
160
|
+
evidenceRefs: [probe.evidenceRef],
|
|
161
|
+
supportingExcerpts: [{ evidenceRef: probe.evidenceRef, text: probe.excerpt }]
|
|
162
|
+
}],
|
|
163
|
+
abstentionReason: null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function expectedCandidateState(goldenCase, context) {
|
|
168
|
+
const response = goldenCase.response;
|
|
169
|
+
if (response.kind === 'runtimeError') {
|
|
170
|
+
if (!AI_RUNTIME_ERROR_CODES.includes(response.code)) {
|
|
171
|
+
throw datasetError(`${goldenCase.id} uses unknown runtime error code ${response.code}.`);
|
|
172
|
+
}
|
|
173
|
+
return { rawOutcome: 'RUNTIME_FAILURE', trustDecision: 'NOT_EVALUATED', detailCode: response.code };
|
|
174
|
+
}
|
|
175
|
+
if (response.kind === 'rawText') {
|
|
176
|
+
try {
|
|
177
|
+
validateMigrationChecklistCandidate(response.output);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return { rawOutcome: 'INVALID', trustDecision: 'NOT_EVALUATED', detailCode: error.code };
|
|
180
|
+
}
|
|
181
|
+
throw datasetError(`${goldenCase.id} rawText unexpectedly satisfies the candidate contract.`);
|
|
182
|
+
}
|
|
183
|
+
let candidate;
|
|
184
|
+
try {
|
|
185
|
+
candidate = validateMigrationChecklistCandidate(response.candidate);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return { rawOutcome: 'INVALID', trustDecision: 'NOT_EVALUATED', detailCode: error.code };
|
|
188
|
+
}
|
|
189
|
+
if (candidate.status === 'ABSTAIN') {
|
|
190
|
+
return { rawOutcome: 'ABSTAIN', trustDecision: 'NOT_EVALUATED', detailCode: null };
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
trustValidateMigrationChecklistCandidate(candidate, context);
|
|
194
|
+
return { rawOutcome: 'ACTIONABLE', trustDecision: 'ACCEPTED', detailCode: null };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (!isMigrationChecklistTrustError(error)) throw error;
|
|
197
|
+
return {
|
|
198
|
+
rawOutcome: 'ACTIONABLE',
|
|
199
|
+
trustDecision: 'REJECTED',
|
|
200
|
+
detailCode: error.detailCode ?? error.code
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function validateCaseInvariants(goldenCase) {
|
|
206
|
+
const evidenceIds = goldenCase.fixture.evidence.map((item) => item.id);
|
|
207
|
+
const evidenceSet = new Set(evidenceIds);
|
|
208
|
+
if (duplicate(evidenceIds).length > 0) throw datasetError(`${goldenCase.id} has duplicate evidence ids.`);
|
|
209
|
+
for (const evidence of goldenCase.fixture.evidence) {
|
|
210
|
+
if (!evidence.releaseVersions.includes(goldenCase.fixture.versions.targetVersion)) {
|
|
211
|
+
throw datasetError(`${goldenCase.id} evidence ${evidence.id} is not target scoped.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const ref of goldenCase.expected.evidenceRefs) {
|
|
215
|
+
if (!evidenceSet.has(ref)) throw datasetError(`${goldenCase.id} expects unknown evidence ref ${ref}.`);
|
|
216
|
+
}
|
|
217
|
+
if (!same(goldenCase.expected.locations, goldenCase.fixture.locations)) {
|
|
218
|
+
throw datasetError(`${goldenCase.id} expected locations do not match deterministic fixture locations.`);
|
|
219
|
+
}
|
|
220
|
+
const locationPositive = goldenCase.fixture.locations.length > 0;
|
|
221
|
+
if (locationPositive !== (goldenCase.fixture.locationEligibility.reasonCode === 'POSITIVE_USAGE_MATCH')) {
|
|
222
|
+
throw datasetError(`${goldenCase.id} location eligibility is inconsistent.`);
|
|
223
|
+
}
|
|
224
|
+
const context = buildMigrationEvaluationContext(goldenCase);
|
|
225
|
+
const actualState = expectedCandidateState(goldenCase, context);
|
|
226
|
+
for (const field of ['rawOutcome', 'trustDecision']) {
|
|
227
|
+
if (goldenCase.expected[field] !== actualState[field]) {
|
|
228
|
+
throw datasetError(`${goldenCase.id} expected ${field} is inconsistent with its recorded response.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (goldenCase.expected.expectedTrustDetailCode !== actualState.detailCode) {
|
|
232
|
+
throw datasetError(`${goldenCase.id} expected trust detail code is inconsistent.`);
|
|
233
|
+
}
|
|
234
|
+
const outcomeByState = {
|
|
235
|
+
RUNTIME_FAILURE: 'failed',
|
|
236
|
+
INVALID: 'rejected',
|
|
237
|
+
ABSTAIN: 'abstained'
|
|
238
|
+
};
|
|
239
|
+
const expectedFinal = actualState.rawOutcome === 'ACTIONABLE'
|
|
240
|
+
? (actualState.trustDecision === 'ACCEPTED' ? 'generated' : 'rejected')
|
|
241
|
+
: outcomeByState[actualState.rawOutcome];
|
|
242
|
+
if (goldenCase.expected.finalOutcome !== expectedFinal) {
|
|
243
|
+
throw datasetError(`${goldenCase.id} expected finalOutcome is inconsistent.`);
|
|
244
|
+
}
|
|
245
|
+
for (const probe of goldenCase.policyProbes) {
|
|
246
|
+
const candidate = validateMigrationChecklistCandidate(buildMigrationPolicyProbeCandidate(probe));
|
|
247
|
+
let decision = 'ACCEPTED';
|
|
248
|
+
let detailCode = null;
|
|
249
|
+
try {
|
|
250
|
+
trustValidateMigrationChecklistCandidate(candidate, context);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (!isMigrationChecklistTrustError(error)) throw error;
|
|
253
|
+
decision = 'REJECTED';
|
|
254
|
+
detailCode = error.detailCode ?? error.code;
|
|
255
|
+
}
|
|
256
|
+
if (decision !== probe.expectedDecision || detailCode !== probe.expectedDetailCode) {
|
|
257
|
+
throw datasetError(`${goldenCase.id}/${probe.id} policy probe expectation is inconsistent.`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function validateMigrationEvaluationDataset(dataset) {
|
|
263
|
+
if (dataset?.schemaVersion !== MIGRATION_EVALUATION_DATASET_VERSION) {
|
|
264
|
+
throw datasetError(`unsupported schema version; expected ${MIGRATION_EVALUATION_DATASET_VERSION}.`);
|
|
265
|
+
}
|
|
266
|
+
if (!validateSchema(dataset)) {
|
|
267
|
+
throw datasetError(`schema validation failed: ${ajv.errorsText(validateSchema.errors, { separator: '; ' })}`);
|
|
268
|
+
}
|
|
269
|
+
const ids = dataset.cases.map((item) => item.id);
|
|
270
|
+
const duplicates = duplicate(ids);
|
|
271
|
+
if (duplicates.length > 0) throw datasetError(`duplicate case id ${duplicates[0]}.`);
|
|
272
|
+
if (!same(ids, sorted(ids))) throw datasetError('cases must use stable lexical ordering by id.');
|
|
273
|
+
const ecosystems = new Set(dataset.cases.map((item) => item.ecosystem));
|
|
274
|
+
for (const ecosystem of ['generic', 'node', 'python']) {
|
|
275
|
+
if (!ecosystems.has(ecosystem)) throw datasetError(`required ecosystem ${ecosystem} is missing.`);
|
|
276
|
+
}
|
|
277
|
+
const actionCases = dataset.cases.filter((item) => item.expected.actionExpected).length;
|
|
278
|
+
const abstentionCases = dataset.cases.filter((item) => !item.expected.actionExpected).length;
|
|
279
|
+
const probeCount = dataset.cases.reduce((count, item) => count + item.policyProbes.length, 0);
|
|
280
|
+
if (actionCases < 3 || abstentionCases < 3 || probeCount < 10) {
|
|
281
|
+
throw datasetError('dataset has insufficient action, abstention, or adversarial policy coverage.');
|
|
282
|
+
}
|
|
283
|
+
for (const goldenCase of dataset.cases) validateCaseInvariants(goldenCase);
|
|
284
|
+
return dataset;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function migrationEvaluationDatasetDigest(dataset) {
|
|
288
|
+
validateMigrationEvaluationDataset(dataset);
|
|
289
|
+
return digest(dataset);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function loadMigrationEvaluationDataset(
|
|
293
|
+
datasetPath
|
|
294
|
+
) {
|
|
295
|
+
const inputPath = datasetPath ?? BUNDLED_MIGRATION_EVALUATION_DATASET_PATH;
|
|
296
|
+
let parsed;
|
|
297
|
+
try {
|
|
298
|
+
parsed = JSON.parse(await readFile(inputPath, 'utf8'));
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (error instanceof SyntaxError) throw datasetError(`${inputPath} is not valid JSON.`);
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
validateMigrationEvaluationDataset(parsed);
|
|
304
|
+
const value = structuredClone(parsed);
|
|
305
|
+
return deepFreeze({
|
|
306
|
+
datasetPath: path.resolve(inputPath),
|
|
307
|
+
datasetDigest: migrationEvaluationDatasetDigest(value),
|
|
308
|
+
...value
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function createMigrationGoldenFakeRuntime(dataset) {
|
|
313
|
+
validateMigrationEvaluationDataset(dataset);
|
|
314
|
+
const responseByContext = new Map(dataset.cases.map((goldenCase) => [
|
|
315
|
+
buildMigrationEvaluationContext(goldenCase).contextId,
|
|
316
|
+
goldenCase.response
|
|
317
|
+
]));
|
|
318
|
+
return {
|
|
319
|
+
async generateStructured(request) {
|
|
320
|
+
if (request.task !== MIGRATION_PLANNING_TASK) {
|
|
321
|
+
throw new TypeError(`Golden runtime received unexpected task ${request.task}.`);
|
|
322
|
+
}
|
|
323
|
+
const response = responseByContext.get(request.contextId);
|
|
324
|
+
if (!response) throw new TypeError(`Golden runtime received unknown context ${request.contextId}.`);
|
|
325
|
+
if (response.kind === 'runtimeError') {
|
|
326
|
+
throw new AiRuntimeError(response.code, 'Golden runtime failure.', { retryable: false });
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
output: response.kind === 'candidate' ? structuredClone(response.candidate) : response.output,
|
|
330
|
+
provider: 'migration-golden-fake',
|
|
331
|
+
model: MIGRATION_EVALUATION_DATASET_VERSION,
|
|
332
|
+
latencyMs: 0
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isMigrationChecklistTrustError
|
|
3
|
+
} from '../ai-candidate.js';
|
|
4
|
+
import {
|
|
5
|
+
isMigrationExtractiveCandidateError,
|
|
6
|
+
trustValidateMigrationExtractiveCandidate,
|
|
7
|
+
validateMigrationExtractiveCandidate
|
|
8
|
+
} from '../extractive-candidate.js';
|
|
9
|
+
import { buildMigrationPolicyProbeCandidate } from './dataset.js';
|
|
10
|
+
|
|
11
|
+
function actionable(actions, extra = {}) {
|
|
12
|
+
return { status: 'ACTIONABLE', actions, abstentionReason: null, ...extra };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fromFreeFormCandidate(candidate, { unsafeSecondItem = false } = {}) {
|
|
16
|
+
if (candidate.status === 'ABSTAIN') {
|
|
17
|
+
return {
|
|
18
|
+
status: 'ABSTAIN',
|
|
19
|
+
actions: [],
|
|
20
|
+
abstentionReason: candidate.abstentionReason
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return actionable(candidate.items.map((item, index) => ({
|
|
24
|
+
evidenceRef: item.evidenceRefs[0],
|
|
25
|
+
actionExcerpt: unsafeSecondItem && index > 0
|
|
26
|
+
? item.instruction
|
|
27
|
+
: item.supportingExcerpts[0].text
|
|
28
|
+
})));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function recordedOutput(item, baseCase) {
|
|
32
|
+
if (item.recordedSource.kind === 'CUSTOM_CANDIDATE') {
|
|
33
|
+
return fromFreeFormCandidate(item.recordedSource.candidate);
|
|
34
|
+
}
|
|
35
|
+
if (item.recordedSource.kind === 'BASE_RESPONSE') {
|
|
36
|
+
return fromFreeFormCandidate(baseCase.response.candidate, { unsafeSecondItem: true });
|
|
37
|
+
}
|
|
38
|
+
const probe = baseCase.policyProbes.find((value) => (
|
|
39
|
+
value.id === item.recordedSource.probeId
|
|
40
|
+
));
|
|
41
|
+
if (!probe) throw new Error(`Unknown extractive v2 policy probe ${item.id}.`);
|
|
42
|
+
const freeForm = buildMigrationPolicyProbeCandidate(probe);
|
|
43
|
+
const action = {
|
|
44
|
+
evidenceRef: probe.evidenceRef,
|
|
45
|
+
actionExcerpt: item.id === 'containment/paraphrased-excerpt'
|
|
46
|
+
? probe.excerpt : probe.instruction
|
|
47
|
+
};
|
|
48
|
+
if (item.id === 'containment/code-fence') action.code = freeForm.items[0].instruction;
|
|
49
|
+
if (item.id === 'containment/command') action.command = freeForm.items[0].instruction;
|
|
50
|
+
if (item.id === 'containment/invented-url') action.url = 'https://example.invalid/migrate';
|
|
51
|
+
if (item.id === 'containment/repository-location') {
|
|
52
|
+
action.file = 'src/config.ts';
|
|
53
|
+
action.symbol = 'config';
|
|
54
|
+
}
|
|
55
|
+
return actionable([action], item.id === 'containment/repository-location'
|
|
56
|
+
? { packageId: baseCase.fixture.dependency.packageId } : {});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveMigrationExtractiveEvaluationV2Case(dataset, item) {
|
|
60
|
+
const baseCase = dataset.legacyDataset.cases.find((value) => value.id === item.baseCaseId);
|
|
61
|
+
if (!baseCase) throw new Error(`Unknown extractive v2 base case ${item.baseCaseId}.`);
|
|
62
|
+
if (item.role === 'LIVE_QUALITY') {
|
|
63
|
+
return {
|
|
64
|
+
baseCase,
|
|
65
|
+
fixedOutput: fromFreeFormCandidate(baseCase.response.candidate),
|
|
66
|
+
runtimeErrorCode: null
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (item.role === 'RECORDED_CONTAINMENT') {
|
|
70
|
+
return { baseCase, fixedOutput: recordedOutput(item, baseCase), runtimeErrorCode: null };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
baseCase,
|
|
74
|
+
fixedOutput: item.injectedFailure.kind === 'RUNTIME_ERROR'
|
|
75
|
+
? null : structuredClone(item.injectedFailure.output),
|
|
76
|
+
runtimeErrorCode: item.injectedFailure.kind === 'RUNTIME_ERROR'
|
|
77
|
+
? item.injectedFailure.code : null
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function comparatorCandidate(candidate) {
|
|
82
|
+
return {
|
|
83
|
+
status: candidate.status,
|
|
84
|
+
items: candidate.actions
|
|
85
|
+
.map((action) => ({
|
|
86
|
+
instruction: action.actionExcerpt,
|
|
87
|
+
evidenceRefs: [action.evidenceRef],
|
|
88
|
+
supportingExcerpts: [{
|
|
89
|
+
evidenceRef: action.evidenceRef,
|
|
90
|
+
text: action.actionExcerpt
|
|
91
|
+
}]
|
|
92
|
+
}))
|
|
93
|
+
.sort((left, right) => (
|
|
94
|
+
left.evidenceRefs[0].localeCompare(right.evidenceRefs[0])
|
|
95
|
+
|| left.instruction.localeCompare(right.instruction)
|
|
96
|
+
)),
|
|
97
|
+
abstentionReason: candidate.abstentionReason
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function classifyMigrationExtractiveOutput(rawOutput, context, runtimeErrorCode) {
|
|
102
|
+
if (runtimeErrorCode) {
|
|
103
|
+
return {
|
|
104
|
+
outcome: 'RUNTIME_FAILURE',
|
|
105
|
+
candidate: null,
|
|
106
|
+
trustDecision: 'NOT_EVALUATED',
|
|
107
|
+
trustDetailCode: runtimeErrorCode
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
let candidate;
|
|
111
|
+
try {
|
|
112
|
+
candidate = validateMigrationExtractiveCandidate(rawOutput);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!isMigrationExtractiveCandidateError(error)) throw error;
|
|
115
|
+
return {
|
|
116
|
+
outcome: 'INVALID',
|
|
117
|
+
candidate: null,
|
|
118
|
+
trustDecision: 'NOT_EVALUATED',
|
|
119
|
+
trustDetailCode: error.code
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const adapted = comparatorCandidate(candidate);
|
|
123
|
+
if (candidate.status === 'ABSTAIN') {
|
|
124
|
+
return {
|
|
125
|
+
outcome: 'ABSTAIN',
|
|
126
|
+
candidate: adapted,
|
|
127
|
+
trustDecision: 'NOT_EVALUATED',
|
|
128
|
+
trustDetailCode: null
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
trustValidateMigrationExtractiveCandidate(candidate, context);
|
|
133
|
+
return {
|
|
134
|
+
outcome: 'ACTIONABLE',
|
|
135
|
+
candidate: adapted,
|
|
136
|
+
trustDecision: 'ACCEPTED',
|
|
137
|
+
trustDetailCode: null
|
|
138
|
+
};
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (!isMigrationChecklistTrustError(error)) throw error;
|
|
141
|
+
return {
|
|
142
|
+
outcome: 'ACTIONABLE',
|
|
143
|
+
candidate: adapted,
|
|
144
|
+
trustDecision: 'REJECTED',
|
|
145
|
+
trustDetailCode: error.detailCode ?? error.code
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|