@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,294 @@
|
|
|
1
|
+
import { canonicalJson } from '../../canonical-json.js';
|
|
2
|
+
import { AiRuntimeError, isAiRuntimeError } from '../../ai-runtime-error.js';
|
|
3
|
+
import { compareText } from '../../portable.js';
|
|
4
|
+
import {
|
|
5
|
+
generateMigrationChecklistForContext,
|
|
6
|
+
generateMigrationExtractiveChecklistForContext
|
|
7
|
+
} from '../generator.js';
|
|
8
|
+
import {
|
|
9
|
+
MIGRATION_EXTRACTIVE_PLANNING_TASK,
|
|
10
|
+
MIGRATION_EXTRACTIVE_PROMPT_VERSION
|
|
11
|
+
} from '../extractive-prompt.js';
|
|
12
|
+
import { MIGRATION_PLANNING_PROMPT_VERSION, MIGRATION_PLANNING_TASK } from '../prompt.js';
|
|
13
|
+
import { compareMigrationEvaluationCaseV2 } from './comparator-v2.js';
|
|
14
|
+
import { buildMigrationEvaluationContext, loadMigrationEvaluationDataset } from './dataset.js';
|
|
15
|
+
import {
|
|
16
|
+
loadMigrationEvaluationDatasetV2,
|
|
17
|
+
migrationEvaluationDatasetV2Digest,
|
|
18
|
+
resolveMigrationEvaluationV2Case,
|
|
19
|
+
validateMigrationEvaluationDatasetV2
|
|
20
|
+
} from './dataset-v2.js';
|
|
21
|
+
import { computeMigrationEvaluationMetricsV2 } from './metrics-v2.js';
|
|
22
|
+
import {
|
|
23
|
+
qualifyMigrationExtractiveRuntimeV2,
|
|
24
|
+
qualifyMigrationPlanningRuntimeV2
|
|
25
|
+
} from './qualification-v2.js';
|
|
26
|
+
import { buildMigrationEvaluationScorecardV2 } from './scorecard-v2.js';
|
|
27
|
+
import {
|
|
28
|
+
classifyMigrationExtractiveOutput,
|
|
29
|
+
resolveMigrationExtractiveEvaluationV2Case
|
|
30
|
+
} from './extractive-fixtures-v2.js';
|
|
31
|
+
|
|
32
|
+
export const MIGRATION_EVALUATION_REPORT_V2_VERSION = '2.0.0';
|
|
33
|
+
export const MIGRATION_EXTRACTIVE_EVALUATION_REPORT_V2_VERSION = '2.0.0-extractive';
|
|
34
|
+
|
|
35
|
+
function deepFreeze(value) {
|
|
36
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
37
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
38
|
+
return Object.freeze(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isoTimestamp(value) {
|
|
42
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
43
|
+
if (typeof timestamp !== 'string' || !Number.isFinite(Date.parse(timestamp))) {
|
|
44
|
+
throw new TypeError('Migration evaluation v2 requires an injected Date or ISO timestamp.');
|
|
45
|
+
}
|
|
46
|
+
return timestamp;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function runtimeIdentity(mode, metadata = {}, fakeDefaults) {
|
|
50
|
+
const allowed = new Set(['provider', 'model', 'adapter']);
|
|
51
|
+
const unknown = Object.keys(metadata).filter((field) => !allowed.has(field));
|
|
52
|
+
if (unknown.length > 0) {
|
|
53
|
+
throw new TypeError(`Migration evaluation v2 runtime metadata contains unsupported field ${unknown.sort(compareText)[0]}.`);
|
|
54
|
+
}
|
|
55
|
+
const defaults = mode === 'fake' ? (fakeDefaults ?? {
|
|
56
|
+
provider: 'migration-golden-fake',
|
|
57
|
+
model: '2.0.0',
|
|
58
|
+
adapter: 'role-routed-recorded-runtime'
|
|
59
|
+
}) : {};
|
|
60
|
+
const identity = { mode, ...defaults, ...metadata };
|
|
61
|
+
for (const field of allowed) {
|
|
62
|
+
if (typeof identity[field] !== 'string' || identity[field].length === 0) {
|
|
63
|
+
throw new TypeError(`Migration evaluation v2 requires runtimeMetadata.${field}.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return identity;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fixedRuntime(output, runtimeErrorCode, identity) {
|
|
70
|
+
return {
|
|
71
|
+
async generateStructured() {
|
|
72
|
+
if (runtimeErrorCode) {
|
|
73
|
+
throw new AiRuntimeError(runtimeErrorCode, 'Injected sanitized evaluation failure.', {
|
|
74
|
+
retryable: false
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
output: structuredClone(output),
|
|
79
|
+
provider: identity.provider,
|
|
80
|
+
model: identity.model,
|
|
81
|
+
latencyMs: 0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function evaluateCase({
|
|
88
|
+
dataset,
|
|
89
|
+
goldenCase,
|
|
90
|
+
activeRuntime,
|
|
91
|
+
identity,
|
|
92
|
+
promptVersion,
|
|
93
|
+
retainFailureDetails,
|
|
94
|
+
strategy
|
|
95
|
+
}) {
|
|
96
|
+
const resolved = strategy.resolveCase(dataset, goldenCase);
|
|
97
|
+
const context = buildMigrationEvaluationContext(resolved.baseCase);
|
|
98
|
+
const localRuntime = goldenCase.role === 'LIVE_QUALITY'
|
|
99
|
+
? activeRuntime
|
|
100
|
+
: fixedRuntime(resolved.fixedOutput, resolved.runtimeErrorCode, identity);
|
|
101
|
+
let rawOutput;
|
|
102
|
+
let runtimeErrorCode = null;
|
|
103
|
+
const observedIdentity = {};
|
|
104
|
+
const capturingRuntime = {
|
|
105
|
+
async generateStructured(request) {
|
|
106
|
+
try {
|
|
107
|
+
const response = await localRuntime.generateStructured(request);
|
|
108
|
+
rawOutput = structuredClone(response?.output);
|
|
109
|
+
if (goldenCase.role === 'LIVE_QUALITY') {
|
|
110
|
+
if (typeof response?.provider === 'string') observedIdentity.provider = response.provider;
|
|
111
|
+
if (typeof response?.model === 'string') observedIdentity.model = response.model;
|
|
112
|
+
}
|
|
113
|
+
return response;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
runtimeErrorCode = isAiRuntimeError(error) ? error.code : 'UNKNOWN';
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const generation = await strategy.generateContext(context, {
|
|
121
|
+
aiRuntime: capturingRuntime,
|
|
122
|
+
runId: `migration-evaluation-v2:${goldenCase.id}`,
|
|
123
|
+
promptVersion
|
|
124
|
+
});
|
|
125
|
+
const replay = await strategy.generateContext(context, {
|
|
126
|
+
aiRuntime: fixedRuntime(rawOutput, runtimeErrorCode, identity),
|
|
127
|
+
runId: `migration-evaluation-v2:${goldenCase.id}`,
|
|
128
|
+
promptVersion
|
|
129
|
+
});
|
|
130
|
+
const rawClassification = strategy.classifyRawOutput
|
|
131
|
+
? strategy.classifyRawOutput(rawOutput, context, runtimeErrorCode)
|
|
132
|
+
: null;
|
|
133
|
+
return {
|
|
134
|
+
result: compareMigrationEvaluationCaseV2(goldenCase, {
|
|
135
|
+
baseCase: resolved.baseCase,
|
|
136
|
+
context,
|
|
137
|
+
generation,
|
|
138
|
+
rawOutput,
|
|
139
|
+
runtimeErrorCode,
|
|
140
|
+
rawClassification,
|
|
141
|
+
publishedEvaluationInstructions:
|
|
142
|
+
strategy.evaluatePublishedSupportFromSelectedSpans
|
|
143
|
+
&& rawClassification?.outcome === 'ACTIONABLE'
|
|
144
|
+
? rawClassification.candidate.items.map((item) => item.instruction)
|
|
145
|
+
: null,
|
|
146
|
+
deterministicReplayPassed: canonicalJson(generation) === canonicalJson(replay),
|
|
147
|
+
retainFailureDetails
|
|
148
|
+
}),
|
|
149
|
+
observedIdentity
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const FREE_FORM_STRATEGY = Object.freeze({
|
|
154
|
+
task: MIGRATION_PLANNING_TASK,
|
|
155
|
+
reportVersion: MIGRATION_EVALUATION_REPORT_V2_VERSION,
|
|
156
|
+
defaultPromptVersion: MIGRATION_PLANNING_PROMPT_VERSION,
|
|
157
|
+
fakeRuntime: {
|
|
158
|
+
provider: 'migration-golden-fake',
|
|
159
|
+
model: '2.0.0',
|
|
160
|
+
adapter: 'role-routed-recorded-runtime'
|
|
161
|
+
},
|
|
162
|
+
resolveCase: resolveMigrationEvaluationV2Case,
|
|
163
|
+
generateContext: generateMigrationChecklistForContext,
|
|
164
|
+
classifyRawOutput: null,
|
|
165
|
+
qualify: qualifyMigrationPlanningRuntimeV2
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const EXTRACTIVE_STRATEGY = Object.freeze({
|
|
169
|
+
task: MIGRATION_EXTRACTIVE_PLANNING_TASK,
|
|
170
|
+
reportVersion: MIGRATION_EXTRACTIVE_EVALUATION_REPORT_V2_VERSION,
|
|
171
|
+
defaultPromptVersion: MIGRATION_EXTRACTIVE_PROMPT_VERSION,
|
|
172
|
+
fakeRuntime: {
|
|
173
|
+
provider: 'migration-extractive-golden-fake',
|
|
174
|
+
model: '2.0.0',
|
|
175
|
+
adapter: 'extractive-role-routed-recorded-runtime'
|
|
176
|
+
},
|
|
177
|
+
resolveCase: resolveMigrationExtractiveEvaluationV2Case,
|
|
178
|
+
generateContext: generateMigrationExtractiveChecklistForContext,
|
|
179
|
+
classifyRawOutput: classifyMigrationExtractiveOutput,
|
|
180
|
+
evaluatePublishedSupportFromSelectedSpans: true,
|
|
181
|
+
qualify: qualifyMigrationExtractiveRuntimeV2
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
async function runMigrationEvaluationV2WithStrategy({
|
|
185
|
+
dataset,
|
|
186
|
+
datasetPath,
|
|
187
|
+
mode = 'fake',
|
|
188
|
+
runtime,
|
|
189
|
+
runtimeMetadata,
|
|
190
|
+
generatedAt,
|
|
191
|
+
promptVersion,
|
|
192
|
+
retainFailureDetails = true
|
|
193
|
+
}, strategy) {
|
|
194
|
+
if (!['fake', 'real'].includes(mode)) {
|
|
195
|
+
throw new TypeError('Migration evaluation v2 mode must be fake or real.');
|
|
196
|
+
}
|
|
197
|
+
if (generatedAt === undefined) {
|
|
198
|
+
throw new TypeError('Migration evaluation v2 requires an injected generatedAt timestamp.');
|
|
199
|
+
}
|
|
200
|
+
if (mode === 'real' && !runtime) {
|
|
201
|
+
throw new TypeError('Real migration evaluation v2 requires an explicitly injected AiRuntime.');
|
|
202
|
+
}
|
|
203
|
+
if (typeof retainFailureDetails !== 'boolean') {
|
|
204
|
+
throw new TypeError('retainFailureDetails must be boolean.');
|
|
205
|
+
}
|
|
206
|
+
const loaded = dataset ? {
|
|
207
|
+
...structuredClone(dataset),
|
|
208
|
+
legacyDataset: dataset.legacyDataset ?? await loadMigrationEvaluationDataset()
|
|
209
|
+
} : await loadMigrationEvaluationDatasetV2(datasetPath);
|
|
210
|
+
const core = {
|
|
211
|
+
schemaVersion: loaded.schemaVersion,
|
|
212
|
+
datasetId: loaded.datasetId,
|
|
213
|
+
task: loaded.task,
|
|
214
|
+
baseDataset: structuredClone(loaded.baseDataset),
|
|
215
|
+
cases: structuredClone(loaded.cases)
|
|
216
|
+
};
|
|
217
|
+
validateMigrationEvaluationDatasetV2(core, loaded.legacyDataset);
|
|
218
|
+
const timestamp = isoTimestamp(generatedAt);
|
|
219
|
+
const activePromptVersion = promptVersion ?? strategy.defaultPromptVersion;
|
|
220
|
+
const identity = runtimeIdentity(mode, runtimeMetadata, strategy.fakeRuntime);
|
|
221
|
+
let providerRequestCount = 0;
|
|
222
|
+
const liveRuntime = mode === 'real' ? {
|
|
223
|
+
async generateStructured(request) {
|
|
224
|
+
providerRequestCount += 1;
|
|
225
|
+
return runtime.generateStructured(request);
|
|
226
|
+
}
|
|
227
|
+
} : null;
|
|
228
|
+
const cases = [];
|
|
229
|
+
const observedProviders = new Set();
|
|
230
|
+
const observedModels = new Set();
|
|
231
|
+
for (const goldenCase of [...loaded.cases].sort((left, right) => compareText(left.id, right.id))) {
|
|
232
|
+
const resolved = strategy.resolveCase(loaded, goldenCase);
|
|
233
|
+
const activeRuntime = goldenCase.role === 'LIVE_QUALITY'
|
|
234
|
+
? (liveRuntime ?? fixedRuntime(resolved.fixedOutput, null, identity))
|
|
235
|
+
: null;
|
|
236
|
+
const evaluated = await evaluateCase({
|
|
237
|
+
dataset: loaded,
|
|
238
|
+
goldenCase,
|
|
239
|
+
activeRuntime,
|
|
240
|
+
identity,
|
|
241
|
+
promptVersion: activePromptVersion,
|
|
242
|
+
retainFailureDetails,
|
|
243
|
+
strategy
|
|
244
|
+
});
|
|
245
|
+
cases.push(evaluated.result);
|
|
246
|
+
if (evaluated.observedIdentity.provider) observedProviders.add(evaluated.observedIdentity.provider);
|
|
247
|
+
if (evaluated.observedIdentity.model) observedModels.add(evaluated.observedIdentity.model);
|
|
248
|
+
}
|
|
249
|
+
const observedRuntime = {
|
|
250
|
+
...identity,
|
|
251
|
+
observedProviders: [...observedProviders].sort(compareText),
|
|
252
|
+
observedModels: [...observedModels].sort(compareText)
|
|
253
|
+
};
|
|
254
|
+
const metrics = computeMigrationEvaluationMetricsV2(cases, { providerRequestCount });
|
|
255
|
+
const datasetIdentity = {
|
|
256
|
+
datasetId: core.datasetId,
|
|
257
|
+
schemaVersion: core.schemaVersion,
|
|
258
|
+
datasetDigest: migrationEvaluationDatasetV2Digest(core, loaded.legacyDataset)
|
|
259
|
+
};
|
|
260
|
+
const qualification = strategy.qualify({
|
|
261
|
+
dataset: datasetIdentity,
|
|
262
|
+
metrics,
|
|
263
|
+
runtime: observedRuntime,
|
|
264
|
+
generatedAt: timestamp,
|
|
265
|
+
promptVersion: activePromptVersion
|
|
266
|
+
});
|
|
267
|
+
const base = {
|
|
268
|
+
schemaVersion: strategy.reportVersion,
|
|
269
|
+
generatedAt: timestamp,
|
|
270
|
+
task: strategy.task,
|
|
271
|
+
dataset: datasetIdentity,
|
|
272
|
+
promptVersion: activePromptVersion,
|
|
273
|
+
runtime: observedRuntime,
|
|
274
|
+
cases,
|
|
275
|
+
metrics,
|
|
276
|
+
qualification
|
|
277
|
+
};
|
|
278
|
+
return deepFreeze({ ...base, scorecard: buildMigrationEvaluationScorecardV2(base) });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Historical free-form v2 evaluation. Retained byte-for-byte in identity and behavior.
|
|
283
|
+
*/
|
|
284
|
+
export async function runMigrationEvaluationV2(options = {}) {
|
|
285
|
+
return runMigrationEvaluationV2WithStrategy(options, FREE_FORM_STRATEGY);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Offline-first production extractive evaluation. Recorded containment and injected
|
|
290
|
+
* failures always remain local; only LIVE_QUALITY may reach an explicitly injected runtime.
|
|
291
|
+
*/
|
|
292
|
+
export async function runMigrationExtractiveEvaluationV2(options = {}) {
|
|
293
|
+
return runMigrationEvaluationV2WithStrategy(options, EXTRACTIVE_STRATEGY);
|
|
294
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { canonicalJson } from '../../canonical-json.js';
|
|
2
|
+
import { isAiRuntimeError, AiRuntimeError } from '../../ai-runtime-error.js';
|
|
3
|
+
import { compareText } from '../../portable.js';
|
|
4
|
+
import { generateMigrationChecklistForContext } from '../generator.js';
|
|
5
|
+
import { MIGRATION_PLANNING_PROMPT_VERSION, MIGRATION_PLANNING_TASK } from '../prompt.js';
|
|
6
|
+
import { compareMigrationEvaluationCase } from './comparator.js';
|
|
7
|
+
import {
|
|
8
|
+
buildMigrationEvaluationContext,
|
|
9
|
+
createMigrationGoldenFakeRuntime,
|
|
10
|
+
loadMigrationEvaluationDataset,
|
|
11
|
+
migrationEvaluationDatasetDigest,
|
|
12
|
+
validateMigrationEvaluationDataset
|
|
13
|
+
} from './dataset.js';
|
|
14
|
+
import { computeMigrationEvaluationMetrics } from './metrics.js';
|
|
15
|
+
import { qualifyMigrationPlanningRuntime } from './qualification.js';
|
|
16
|
+
import { buildMigrationEvaluationScorecard } from './scorecard.js';
|
|
17
|
+
|
|
18
|
+
export const MIGRATION_EVALUATION_REPORT_VERSION = '1.0.0';
|
|
19
|
+
|
|
20
|
+
function deepFreeze(value) {
|
|
21
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
22
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
23
|
+
return Object.freeze(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function coreDataset(dataset) {
|
|
27
|
+
return {
|
|
28
|
+
schemaVersion: dataset.schemaVersion,
|
|
29
|
+
datasetId: dataset.datasetId,
|
|
30
|
+
task: dataset.task,
|
|
31
|
+
cases: structuredClone(dataset.cases)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isoTimestamp(value) {
|
|
36
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
37
|
+
if (typeof timestamp !== 'string' || !Number.isFinite(Date.parse(timestamp))) {
|
|
38
|
+
throw new TypeError('Migration evaluation generatedAt must be an injected Date or ISO timestamp.');
|
|
39
|
+
}
|
|
40
|
+
return timestamp;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function runtimeIdentity(mode, metadata = {}) {
|
|
44
|
+
const unknownFields = Object.keys(metadata).filter((field) => (
|
|
45
|
+
!['provider', 'model', 'adapter'].includes(field)
|
|
46
|
+
));
|
|
47
|
+
if (unknownFields.length > 0) {
|
|
48
|
+
throw new TypeError(`Migration evaluation runtimeMetadata contains unsupported field ${unknownFields.sort(compareText)[0]}.`);
|
|
49
|
+
}
|
|
50
|
+
const defaults = mode === 'fake' ? {
|
|
51
|
+
provider: 'migration-golden-fake',
|
|
52
|
+
model: '1.0.0',
|
|
53
|
+
adapter: 'recorded-candidate-runtime'
|
|
54
|
+
} : {};
|
|
55
|
+
const identity = { mode, ...defaults, ...metadata };
|
|
56
|
+
for (const field of ['provider', 'model', 'adapter']) {
|
|
57
|
+
if (typeof identity[field] !== 'string' || identity[field].length === 0) {
|
|
58
|
+
throw new TypeError(`Real migration evaluation requires runtimeMetadata.${field}.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return identity;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function replayRuntime(rawOutput, runtimeErrorCode, identity) {
|
|
65
|
+
return {
|
|
66
|
+
async generateStructured() {
|
|
67
|
+
if (runtimeErrorCode) {
|
|
68
|
+
throw new AiRuntimeError(runtimeErrorCode, 'Recorded sanitized runtime failure.', {
|
|
69
|
+
retryable: false
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
output: structuredClone(rawOutput),
|
|
74
|
+
provider: identity.provider,
|
|
75
|
+
model: identity.model,
|
|
76
|
+
latencyMs: 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function evaluateCase(goldenCase, activeRuntime, identity, promptVersion) {
|
|
83
|
+
const context = buildMigrationEvaluationContext(goldenCase);
|
|
84
|
+
let rawOutput;
|
|
85
|
+
let runtimeErrorCode = null;
|
|
86
|
+
const observedIdentity = {};
|
|
87
|
+
const capturingRuntime = {
|
|
88
|
+
async generateStructured(request) {
|
|
89
|
+
try {
|
|
90
|
+
const result = await activeRuntime.generateStructured(request);
|
|
91
|
+
rawOutput = structuredClone(result?.output);
|
|
92
|
+
if (typeof result?.provider === 'string') observedIdentity.provider = result.provider;
|
|
93
|
+
if (typeof result?.model === 'string') observedIdentity.model = result.model;
|
|
94
|
+
return result;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
runtimeErrorCode = isAiRuntimeError(error) ? error.code : 'UNKNOWN';
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const generation = await generateMigrationChecklistForContext(context, {
|
|
102
|
+
aiRuntime: capturingRuntime,
|
|
103
|
+
runId: `migration-evaluation:${goldenCase.id}`,
|
|
104
|
+
promptVersion
|
|
105
|
+
});
|
|
106
|
+
const replay = await generateMigrationChecklistForContext(context, {
|
|
107
|
+
aiRuntime: replayRuntime(rawOutput, runtimeErrorCode, identity),
|
|
108
|
+
runId: `migration-evaluation:${goldenCase.id}`,
|
|
109
|
+
promptVersion
|
|
110
|
+
});
|
|
111
|
+
const result = compareMigrationEvaluationCase(goldenCase, {
|
|
112
|
+
context,
|
|
113
|
+
generation,
|
|
114
|
+
rawOutput,
|
|
115
|
+
runtimeErrorCode,
|
|
116
|
+
deterministicReplayPassed: canonicalJson(generation) === canonicalJson(replay)
|
|
117
|
+
});
|
|
118
|
+
return { result, observedIdentity };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Run the migration-specific dataset. Real providers are called only when mode is explicitly `real`.
|
|
123
|
+
* Raw provider payloads and raw error messages are never included in the returned report.
|
|
124
|
+
*/
|
|
125
|
+
export async function runMigrationEvaluation({
|
|
126
|
+
dataset,
|
|
127
|
+
datasetPath,
|
|
128
|
+
mode = 'fake',
|
|
129
|
+
runtime,
|
|
130
|
+
runtimeMetadata,
|
|
131
|
+
generatedAt,
|
|
132
|
+
promptVersion = MIGRATION_PLANNING_PROMPT_VERSION
|
|
133
|
+
} = {}) {
|
|
134
|
+
if (!['fake', 'real'].includes(mode)) {
|
|
135
|
+
throw new TypeError('Migration evaluation mode must be fake or real.');
|
|
136
|
+
}
|
|
137
|
+
if (generatedAt === undefined) {
|
|
138
|
+
throw new TypeError('Migration evaluation requires an injected generatedAt timestamp.');
|
|
139
|
+
}
|
|
140
|
+
if (mode === 'real' && !runtime) {
|
|
141
|
+
throw new TypeError('Real migration evaluation requires an explicitly injected AiRuntime.');
|
|
142
|
+
}
|
|
143
|
+
const loaded = dataset ?? await loadMigrationEvaluationDataset(datasetPath);
|
|
144
|
+
const value = coreDataset(loaded);
|
|
145
|
+
validateMigrationEvaluationDataset(value);
|
|
146
|
+
if (value.task !== MIGRATION_PLANNING_TASK) {
|
|
147
|
+
throw new TypeError(`Migration evaluation dataset task must be ${MIGRATION_PLANNING_TASK}.`);
|
|
148
|
+
}
|
|
149
|
+
const timestamp = isoTimestamp(generatedAt);
|
|
150
|
+
const identity = runtimeIdentity(mode, runtimeMetadata);
|
|
151
|
+
const activeRuntime = runtime ?? createMigrationGoldenFakeRuntime(value);
|
|
152
|
+
const cases = [];
|
|
153
|
+
const observedProviders = new Set();
|
|
154
|
+
const observedModels = new Set();
|
|
155
|
+
for (const goldenCase of [...value.cases].sort((left, right) => compareText(left.id, right.id))) {
|
|
156
|
+
const evaluated = await evaluateCase(goldenCase, activeRuntime, identity, promptVersion);
|
|
157
|
+
cases.push(evaluated.result);
|
|
158
|
+
if (evaluated.observedIdentity.provider) observedProviders.add(evaluated.observedIdentity.provider);
|
|
159
|
+
if (evaluated.observedIdentity.model) observedModels.add(evaluated.observedIdentity.model);
|
|
160
|
+
}
|
|
161
|
+
const observedRuntime = {
|
|
162
|
+
...identity,
|
|
163
|
+
observedProviders: [...observedProviders].sort(compareText),
|
|
164
|
+
observedModels: [...observedModels].sort(compareText)
|
|
165
|
+
};
|
|
166
|
+
const metrics = computeMigrationEvaluationMetrics(cases);
|
|
167
|
+
const datasetIdentity = {
|
|
168
|
+
datasetId: value.datasetId,
|
|
169
|
+
schemaVersion: value.schemaVersion,
|
|
170
|
+
datasetDigest: migrationEvaluationDatasetDigest(value)
|
|
171
|
+
};
|
|
172
|
+
const qualification = qualifyMigrationPlanningRuntime({
|
|
173
|
+
dataset: datasetIdentity,
|
|
174
|
+
metrics,
|
|
175
|
+
runtime: observedRuntime,
|
|
176
|
+
generatedAt: timestamp,
|
|
177
|
+
promptVersion
|
|
178
|
+
});
|
|
179
|
+
const base = {
|
|
180
|
+
schemaVersion: MIGRATION_EVALUATION_REPORT_VERSION,
|
|
181
|
+
generatedAt: timestamp,
|
|
182
|
+
task: MIGRATION_PLANNING_TASK,
|
|
183
|
+
dataset: datasetIdentity,
|
|
184
|
+
promptVersion,
|
|
185
|
+
runtime: observedRuntime,
|
|
186
|
+
cases,
|
|
187
|
+
metrics,
|
|
188
|
+
qualification
|
|
189
|
+
};
|
|
190
|
+
return deepFreeze({
|
|
191
|
+
...base,
|
|
192
|
+
scorecard: buildMigrationEvaluationScorecard(base)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { compareText } from '../../portable.js';
|
|
2
|
+
|
|
3
|
+
const METRIC_SECTIONS = Object.freeze({
|
|
4
|
+
liveProviderQuality: [
|
|
5
|
+
'actionSupportPrecision', 'publishedUnsupportedActionRate', 'ambiguousPublishedActionRate',
|
|
6
|
+
'actionSpecificityRate', 'identifierSpecificityRate', 'versionScopePreservationRate',
|
|
7
|
+
'abstentionPrecision', 'abstentionRecall', 'falseAbstentionRate',
|
|
8
|
+
'safeCandidateAcceptanceRate', 'liveProviderCompletionRate'
|
|
9
|
+
],
|
|
10
|
+
trustContainment: [
|
|
11
|
+
'unsafeCandidateContainmentRate', 'inventedIdentifierContainmentRate',
|
|
12
|
+
'prohibitedCapabilityContainmentRate', 'invalidEvidenceContainmentRate',
|
|
13
|
+
'wholeCandidateContainmentRate', 'recordedSafeCandidateAcceptanceRate'
|
|
14
|
+
],
|
|
15
|
+
runtimeFailureHandling: [
|
|
16
|
+
'injectedFailureFallbackRate', 'providerFailureIsolationRate',
|
|
17
|
+
'schemaFailureContainmentRate', 'sanitizedFailureRate'
|
|
18
|
+
],
|
|
19
|
+
sharedInvariants: [
|
|
20
|
+
'stepEvidenceReferencePrecision', 'exactExcerptPassRate', 'locationPreservationRate',
|
|
21
|
+
'identityPreservationRate', 'humanReviewCorrectnessRate',
|
|
22
|
+
'versionUncertaintyPreservationRate', 'deterministicPostProcessingPassRate'
|
|
23
|
+
]
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function formatRate(metric) {
|
|
27
|
+
return metric.value === null
|
|
28
|
+
? `n/a (${metric.numerator}/${metric.denominator})`
|
|
29
|
+
: `${(metric.value * 100).toFixed(2)}% (${metric.numerator}/${metric.denominator})`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function buildMigrationEvaluationScorecardV2(evaluation) {
|
|
33
|
+
const section = (name) => Object.fromEntries(METRIC_SECTIONS[name]
|
|
34
|
+
.filter((key) => evaluation.metrics.metrics[key])
|
|
35
|
+
.map((key) => [key, structuredClone(evaluation.metrics.metrics[key])]));
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
schemaVersion: '2.0.0',
|
|
38
|
+
identity: structuredClone(evaluation.qualification.identity),
|
|
39
|
+
coverage: structuredClone(evaluation.metrics.coverage),
|
|
40
|
+
providerRequestCount: evaluation.metrics.runtime.providerRequestCount,
|
|
41
|
+
sections: {
|
|
42
|
+
liveProviderQuality: section('liveProviderQuality'),
|
|
43
|
+
trustContainment: section('trustContainment'),
|
|
44
|
+
runtimeFailureHandling: section('runtimeFailureHandling'),
|
|
45
|
+
sharedInvariants: section('sharedInvariants')
|
|
46
|
+
},
|
|
47
|
+
criticalGates: structuredClone(evaluation.metrics.criticalGates),
|
|
48
|
+
thresholdResults: structuredClone(evaluation.qualification.thresholdResults),
|
|
49
|
+
limitations: structuredClone(evaluation.qualification.limitations),
|
|
50
|
+
retainedFailureDetails: evaluation.cases.flatMap((item) => (
|
|
51
|
+
structuredClone(item.retainedFailureDetails)
|
|
52
|
+
)),
|
|
53
|
+
verdict: evaluation.qualification.verdict
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderMigrationEvaluationScorecardV2(scorecard) {
|
|
58
|
+
const lines = [
|
|
59
|
+
'# Migration Planning Evaluation Scorecard v2',
|
|
60
|
+
'',
|
|
61
|
+
`- Task: ${scorecard.identity.task}`,
|
|
62
|
+
`- Dataset: ${scorecard.identity.datasetId}@${scorecard.identity.datasetVersion}`,
|
|
63
|
+
`- Evaluation criteria: ${scorecard.identity.evaluationCriteriaId}@${scorecard.identity.evaluationCriteriaVersion}`,
|
|
64
|
+
`- Comparator/normalization: ${scorecard.identity.comparatorVersion}/${scorecard.identity.normalizationVersion}`,
|
|
65
|
+
`- Provider/model: ${scorecard.identity.runtime.provider}/${scorecard.identity.runtime.model}`,
|
|
66
|
+
`- Runtime mode/adapter: ${scorecard.identity.runtime.mode}/${scorecard.identity.runtime.adapter}`,
|
|
67
|
+
`- Real provider calls: ${scorecard.providerRequestCount}`,
|
|
68
|
+
`- Verdict: ${scorecard.verdict}`,
|
|
69
|
+
'',
|
|
70
|
+
'## Fixture Coverage',
|
|
71
|
+
'',
|
|
72
|
+
`- Roles: ${JSON.stringify(scorecard.coverage.roles)}`,
|
|
73
|
+
`- Live ecosystems: ${scorecard.coverage.ecosystems.join(', ')}`,
|
|
74
|
+
`- Live actionable/abstention: ${scorecard.coverage.liveActionableCases}/${scorecard.coverage.liveAbstentionCases}`,
|
|
75
|
+
`- Recorded unsafe/safe: ${scorecard.coverage.recordedUnsafeCases}/${scorecard.coverage.recordedSafeCases}`,
|
|
76
|
+
`- Injected failures: ${scorecard.coverage.injectedFailureCases}`
|
|
77
|
+
];
|
|
78
|
+
const headings = {
|
|
79
|
+
liveProviderQuality: 'Live Provider Quality',
|
|
80
|
+
trustContainment: 'Trust Containment',
|
|
81
|
+
runtimeFailureHandling: 'Runtime Failure Handling',
|
|
82
|
+
sharedInvariants: 'Shared Invariants'
|
|
83
|
+
};
|
|
84
|
+
for (const sectionName of Object.keys(headings)) {
|
|
85
|
+
lines.push('', `## ${headings[sectionName]}`, '');
|
|
86
|
+
for (const name of Object.keys(scorecard.sections[sectionName]).sort(compareText)) {
|
|
87
|
+
const metric = scorecard.sections[sectionName][name];
|
|
88
|
+
lines.push(`- ${name}: ${formatRate(metric)}; role=${metric.caseRole}; cases=${metric.applicableCaseCount}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
lines.push('', '## Critical Gates', '');
|
|
92
|
+
for (const gate of scorecard.criticalGates) {
|
|
93
|
+
lines.push(`- ${gate.id}: ${gate.passed ? 'PASS' : `FAIL (${gate.violations.join(', ')})`}`);
|
|
94
|
+
}
|
|
95
|
+
lines.push('', '## Thresholds', '');
|
|
96
|
+
for (const item of scorecard.thresholdResults) {
|
|
97
|
+
lines.push(`- ${item.metric}: ${item.passed ? 'PASS' : 'FAIL'}; actual=${item.actual ?? 'n/a'}; ${item.operator}=${item.threshold}`);
|
|
98
|
+
}
|
|
99
|
+
lines.push('', '## Limitations', '');
|
|
100
|
+
if (scorecard.limitations.length === 0) lines.push('- None.');
|
|
101
|
+
else for (const item of scorecard.limitations) lines.push(`- ${item.code}: ${item.message}`);
|
|
102
|
+
lines.push('', '## Retained Sanitized Failure Details', '');
|
|
103
|
+
lines.push(scorecard.retainedFailureDetails.length === 0
|
|
104
|
+
? '- None.' : `- ${scorecard.retainedFailureDetails.length} bounded record(s).`);
|
|
105
|
+
return `${lines.join('\n')}\n`;
|
|
106
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { compareText } from '../../portable.js';
|
|
2
|
+
|
|
3
|
+
function formatRate(metric) {
|
|
4
|
+
return metric.value === null
|
|
5
|
+
? `n/a (${metric.numerator}/${metric.denominator})`
|
|
6
|
+
: `${(metric.value * 100).toFixed(2)}% (${metric.numerator}/${metric.denominator})`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Build a presentation-only scorecard from already computed results. */
|
|
10
|
+
export function buildMigrationEvaluationScorecard(evaluation) {
|
|
11
|
+
const metrics = evaluation.metrics;
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
schemaVersion: '1.0.0',
|
|
14
|
+
identity: structuredClone(evaluation.qualification.identity),
|
|
15
|
+
coverage: structuredClone(metrics.coverage),
|
|
16
|
+
rawOutcomes: Object.fromEntries(
|
|
17
|
+
['ACTIONABLE', 'ABSTAIN', 'INVALID', 'RUNTIME_FAILURE'].map((outcome) => [
|
|
18
|
+
outcome,
|
|
19
|
+
evaluation.cases.filter((item) => item.raw.outcome === outcome).length
|
|
20
|
+
])
|
|
21
|
+
),
|
|
22
|
+
trustOutcomes: Object.fromEntries(
|
|
23
|
+
['ACCEPTED', 'REJECTED', 'NOT_EVALUATED'].map((outcome) => [
|
|
24
|
+
outcome,
|
|
25
|
+
evaluation.cases.filter((item) => item.raw.trustDecision === outcome).length
|
|
26
|
+
])
|
|
27
|
+
),
|
|
28
|
+
publishedOutcomes: {
|
|
29
|
+
generated: metrics.runtime.generated,
|
|
30
|
+
abstained: metrics.runtime.abstained,
|
|
31
|
+
rejected: metrics.runtime.rejected,
|
|
32
|
+
failed: metrics.runtime.failed
|
|
33
|
+
},
|
|
34
|
+
metrics: structuredClone(metrics.metrics),
|
|
35
|
+
criticalGates: structuredClone(metrics.criticalGates),
|
|
36
|
+
thresholdResults: structuredClone(evaluation.qualification.thresholdResults),
|
|
37
|
+
limitations: structuredClone(evaluation.qualification.limitations),
|
|
38
|
+
verdict: evaluation.qualification.verdict
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Render only precomputed scorecard data; this function never derives a verdict. */
|
|
43
|
+
export function renderMigrationEvaluationScorecard(scorecard) {
|
|
44
|
+
const lines = [
|
|
45
|
+
'# Migration Planning Evaluation Scorecard',
|
|
46
|
+
'',
|
|
47
|
+
`- Task: ${scorecard.identity.task}`,
|
|
48
|
+
`- Dataset: ${scorecard.identity.datasetId}@${scorecard.identity.datasetVersion}`,
|
|
49
|
+
`- Provider/model: ${scorecard.identity.runtime.provider}/${scorecard.identity.runtime.model}`,
|
|
50
|
+
`- Runtime mode/adapter: ${scorecard.identity.runtime.mode}/${scorecard.identity.runtime.adapter}`,
|
|
51
|
+
`- Verdict: ${scorecard.verdict}`,
|
|
52
|
+
'',
|
|
53
|
+
'## Coverage',
|
|
54
|
+
'',
|
|
55
|
+
`- Ecosystems: ${scorecard.coverage.ecosystems.join(', ')}`,
|
|
56
|
+
`- Cases by ecosystem: ${JSON.stringify(scorecard.coverage.casesByEcosystem)}`,
|
|
57
|
+
`- Cases by scenario group: ${JSON.stringify(scorecard.coverage.scenarioGroups)}`,
|
|
58
|
+
`- Actionable quality cases: ${scorecard.coverage.actionableQualityCases}`,
|
|
59
|
+
`- Abstention quality cases: ${scorecard.coverage.abstentionQualityCases}`,
|
|
60
|
+
`- Adversarial policy probes: ${scorecard.coverage.adversarialPolicyProbes}`,
|
|
61
|
+
'',
|
|
62
|
+
'## Raw / Trust / Published',
|
|
63
|
+
'',
|
|
64
|
+
`- Raw: ${JSON.stringify(scorecard.rawOutcomes)}`,
|
|
65
|
+
`- Trust: ${JSON.stringify(scorecard.trustOutcomes)}`,
|
|
66
|
+
`- Published: ${JSON.stringify(scorecard.publishedOutcomes)}`,
|
|
67
|
+
'',
|
|
68
|
+
'## Metrics',
|
|
69
|
+
''
|
|
70
|
+
];
|
|
71
|
+
for (const name of Object.keys(scorecard.metrics).sort(compareText)) {
|
|
72
|
+
lines.push(`- ${name}: ${formatRate(scorecard.metrics[name])}`);
|
|
73
|
+
}
|
|
74
|
+
lines.push('', '## Critical Gates', '');
|
|
75
|
+
for (const gate of scorecard.criticalGates) {
|
|
76
|
+
lines.push(`- ${gate.id}: ${gate.passed ? 'PASS' : `FAIL (${gate.violations.join(', ')})`}`);
|
|
77
|
+
}
|
|
78
|
+
lines.push('', '## Thresholds', '');
|
|
79
|
+
for (const item of scorecard.thresholdResults) {
|
|
80
|
+
lines.push(`- ${item.metric}: ${item.passed ? 'PASS' : 'FAIL'}; actual=${item.actual ?? 'n/a'}; ${item.operator}=${item.threshold}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push('', '## Limitations', '');
|
|
83
|
+
if (scorecard.limitations.length === 0) lines.push('- None.');
|
|
84
|
+
else for (const item of scorecard.limitations) lines.push(`- ${item.code}: ${item.message}`);
|
|
85
|
+
return `${lines.join('\n')}\n`;
|
|
86
|
+
}
|