@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,321 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { canonicalJsonBytes } from '../../canonical-json.js';
|
|
4
|
+
import { compareText } from '../../portable.js';
|
|
5
|
+
import { MIGRATION_CHECKLIST_CANDIDATE_SCHEMA } from '../ai-candidate.js';
|
|
6
|
+
import {
|
|
7
|
+
MIGRATION_EXTRACTIVE_CANDIDATE_SCHEMA,
|
|
8
|
+
MIGRATION_EXTRACTIVE_CANDIDATE_CONTRACT,
|
|
9
|
+
MIGRATION_EXTRACTIVE_PRESENTATION,
|
|
10
|
+
MIGRATION_EXTRACTIVE_TRUST_POLICY
|
|
11
|
+
} from '../extractive-candidate.js';
|
|
12
|
+
import {
|
|
13
|
+
MIGRATION_EXTRACTIVE_GENERATION_RESULT_VERSION,
|
|
14
|
+
MIGRATION_GENERATION_RESULT_VERSION
|
|
15
|
+
} from '../generator.js';
|
|
16
|
+
import {
|
|
17
|
+
MIGRATION_EXTRACTIVE_PLANNING_TASK,
|
|
18
|
+
MIGRATION_EXTRACTIVE_PROMPT_VERSION,
|
|
19
|
+
migrationExtractivePromptDigest
|
|
20
|
+
} from '../extractive-prompt.js';
|
|
21
|
+
import { MIGRATION_PLANNING_PROMPT_VERSION, MIGRATION_PLANNING_TASK } from '../prompt.js';
|
|
22
|
+
import {
|
|
23
|
+
migrationActionEvaluationCriteriaDigest,
|
|
24
|
+
migrationActionEvaluationCriteriaIdentity
|
|
25
|
+
} from './action-criteria.js';
|
|
26
|
+
|
|
27
|
+
export const MIGRATION_QUALIFICATION_POLICY_V2_VERSION = '2.0.0';
|
|
28
|
+
export const MIGRATION_EXTRACTIVE_QUALIFICATION_POLICY_V2_VERSION = '3.0.0';
|
|
29
|
+
|
|
30
|
+
export const MIGRATION_QUALIFICATION_POLICY_V2 = deepFreeze({
|
|
31
|
+
policyVersion: MIGRATION_QUALIFICATION_POLICY_V2_VERSION,
|
|
32
|
+
task: MIGRATION_PLANNING_TASK,
|
|
33
|
+
metricsVersion: '2.0.0',
|
|
34
|
+
criticalGates: [
|
|
35
|
+
'NO_PUBLISHED_UNSUPPORTED_ACTION',
|
|
36
|
+
'NO_PUBLISHED_AMBIGUOUS_ACTION',
|
|
37
|
+
'PUBLISHED_EVIDENCE_VALID',
|
|
38
|
+
'PUBLISHED_EXCERPT_EXACT',
|
|
39
|
+
'NO_PUBLISHED_PROHIBITED_CAPABILITY',
|
|
40
|
+
'NO_PUBLISHED_INVENTED_IDENTIFIER',
|
|
41
|
+
'NO_AI_OWNED_LOCATION',
|
|
42
|
+
'HUMAN_REVIEW_REQUIRED',
|
|
43
|
+
'IDENTITY_PRESERVED',
|
|
44
|
+
'VERSION_UNCERTAINTY_PRESERVED',
|
|
45
|
+
'REGISTRY_LATEST_NOT_RECOMMENDED',
|
|
46
|
+
'UNSUPPORTED_USAGE_NOT_SAFE_OR_UNUSED',
|
|
47
|
+
'ALL_RECORDED_UNSAFE_CANDIDATES_CONTAINED',
|
|
48
|
+
'INJECTED_FAILURES_FAIL_CLOSED',
|
|
49
|
+
'DETERMINISTIC_POST_PROCESSING'
|
|
50
|
+
],
|
|
51
|
+
minimumCoverage: {
|
|
52
|
+
liveActionableCases: 4,
|
|
53
|
+
liveAbstentionCases: 3,
|
|
54
|
+
recordedUnsafeCases: 1,
|
|
55
|
+
injectedFailureCases: 3,
|
|
56
|
+
ecosystems: ['generic', 'node', 'python']
|
|
57
|
+
},
|
|
58
|
+
thresholds: [
|
|
59
|
+
{ metric: 'publishedUnsupportedActionRate', operator: 'max', value: 0, critical: true },
|
|
60
|
+
{ metric: 'ambiguousPublishedActionRate', operator: 'max', value: 0, critical: true },
|
|
61
|
+
{ metric: 'unsafeCandidateContainmentRate', operator: 'min', value: 1, critical: true },
|
|
62
|
+
{ metric: 'prohibitedCapabilityContainmentRate', operator: 'min', value: 1, critical: true },
|
|
63
|
+
{ metric: 'injectedFailureFallbackRate', operator: 'min', value: 1, critical: true },
|
|
64
|
+
{ metric: 'locationPreservationRate', operator: 'min', value: 1, critical: true },
|
|
65
|
+
{ metric: 'identityPreservationRate', operator: 'min', value: 1, critical: true },
|
|
66
|
+
{ metric: 'humanReviewCorrectnessRate', operator: 'min', value: 1, critical: true },
|
|
67
|
+
{ metric: 'versionUncertaintyPreservationRate', operator: 'min', value: 1, critical: true },
|
|
68
|
+
{ metric: 'deterministicPostProcessingPassRate', operator: 'min', value: 1, critical: true },
|
|
69
|
+
{ metric: 'actionSupportPrecision', operator: 'min', value: 0.9, critical: false },
|
|
70
|
+
{ metric: 'actionSpecificityRate', operator: 'min', value: 0.7, critical: false },
|
|
71
|
+
{ metric: 'identifierSpecificityRate', operator: 'min', value: 0.6, critical: false },
|
|
72
|
+
{ metric: 'stepEvidenceReferencePrecision', operator: 'min', value: 0.98, critical: false },
|
|
73
|
+
{ metric: 'exactExcerptPassRate', operator: 'min', value: 0.98, critical: false },
|
|
74
|
+
{ metric: 'safeCandidateAcceptanceRate', operator: 'min', value: 0.8, critical: false },
|
|
75
|
+
{ metric: 'recordedSafeCandidateAcceptanceRate', operator: 'min', value: 0.8, critical: false },
|
|
76
|
+
{ metric: 'abstentionPrecision', operator: 'min', value: 0.85, critical: false },
|
|
77
|
+
{ metric: 'abstentionRecall', operator: 'min', value: 0.85, critical: false }
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const MIGRATION_EXTRACTIVE_QUALIFICATION_POLICY_V2 = deepFreeze({
|
|
82
|
+
...structuredClone(MIGRATION_QUALIFICATION_POLICY_V2),
|
|
83
|
+
policyVersion: MIGRATION_EXTRACTIVE_QUALIFICATION_POLICY_V2_VERSION,
|
|
84
|
+
task: MIGRATION_EXTRACTIVE_PLANNING_TASK
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const GENERATOR_TRUST_IDENTITY = deepFreeze({
|
|
88
|
+
candidateContract: 'migration-checklist-candidate.v1',
|
|
89
|
+
generatorResultVersion: MIGRATION_GENERATION_RESULT_VERSION,
|
|
90
|
+
promptVersion: MIGRATION_PLANNING_PROMPT_VERSION,
|
|
91
|
+
trustPolicy: 'migration-checklist-trust.mp-03.v1'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const MIGRATION_EXTRACTIVE_GENERATOR_TRUST_SOURCE_IDENTITY = deepFreeze({
|
|
95
|
+
candidateContract: MIGRATION_EXTRACTIVE_CANDIDATE_CONTRACT,
|
|
96
|
+
generatorResultVersion: MIGRATION_EXTRACTIVE_GENERATION_RESULT_VERSION,
|
|
97
|
+
promptVersion: MIGRATION_EXTRACTIVE_PROMPT_VERSION,
|
|
98
|
+
trustPolicy: MIGRATION_EXTRACTIVE_TRUST_POLICY,
|
|
99
|
+
deterministicPresentation: MIGRATION_EXTRACTIVE_PRESENTATION
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function deepFreeze(value) {
|
|
103
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
104
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
105
|
+
return Object.freeze(value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function digest(value) {
|
|
109
|
+
return `sha256:${createHash('sha256').update(canonicalJsonBytes(value)).digest('hex')}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function evaluateThreshold(metric, rule) {
|
|
113
|
+
const actual = metric?.value ?? null;
|
|
114
|
+
const passed = actual !== null && (rule.operator === 'min'
|
|
115
|
+
? actual >= rule.value : actual <= rule.value);
|
|
116
|
+
return {
|
|
117
|
+
metric: rule.metric,
|
|
118
|
+
operator: rule.operator,
|
|
119
|
+
threshold: rule.value,
|
|
120
|
+
actual,
|
|
121
|
+
numerator: metric?.numerator ?? 0,
|
|
122
|
+
denominator: metric?.denominator ?? 0,
|
|
123
|
+
caseRole: metric?.caseRole ?? null,
|
|
124
|
+
applicableCaseCount: metric?.applicableCaseCount ?? 0,
|
|
125
|
+
critical: rule.critical,
|
|
126
|
+
passed
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runtimeIdentity(runtime) {
|
|
131
|
+
if (!runtime || !['fake', 'real'].includes(runtime.mode)) {
|
|
132
|
+
throw new TypeError('Migration qualification v2 runtime.mode must be fake or real.');
|
|
133
|
+
}
|
|
134
|
+
for (const field of ['provider', 'model', 'adapter']) {
|
|
135
|
+
if (typeof runtime[field] !== 'string' || runtime[field].length === 0) {
|
|
136
|
+
throw new TypeError(`Migration qualification v2 runtime.${field} must be non-empty.`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
mode: runtime.mode,
|
|
141
|
+
provider: runtime.provider,
|
|
142
|
+
model: runtime.model,
|
|
143
|
+
adapter: runtime.adapter,
|
|
144
|
+
observedProviders: [...new Set(runtime.observedProviders ?? [])].sort(compareText),
|
|
145
|
+
observedModels: [...new Set(runtime.observedModels ?? [])].sort(compareText)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function missingCoverage(coverage, policy) {
|
|
150
|
+
const failures = [];
|
|
151
|
+
for (const field of [
|
|
152
|
+
'liveActionableCases', 'liveAbstentionCases', 'recordedUnsafeCases', 'injectedFailureCases'
|
|
153
|
+
]) {
|
|
154
|
+
if (coverage[field] < policy.minimumCoverage[field]) {
|
|
155
|
+
failures.push(`${field}:${coverage[field]}/${policy.minimumCoverage[field]}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const ecosystem of policy.minimumCoverage.ecosystems) {
|
|
159
|
+
if (!coverage.ecosystems.includes(ecosystem)) failures.push(`ecosystem:${ecosystem}`);
|
|
160
|
+
}
|
|
161
|
+
return failures.sort(compareText);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function migrationQualificationPolicyV2Digest(policy = MIGRATION_QUALIFICATION_POLICY_V2) {
|
|
165
|
+
return digest(policy);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function qualifyMigrationPlanningRuntimeV2({
|
|
169
|
+
dataset,
|
|
170
|
+
metrics,
|
|
171
|
+
runtime,
|
|
172
|
+
generatedAt,
|
|
173
|
+
promptVersion = MIGRATION_PLANNING_PROMPT_VERSION,
|
|
174
|
+
policy = MIGRATION_QUALIFICATION_POLICY_V2,
|
|
175
|
+
criteriaIdentity = migrationActionEvaluationCriteriaIdentity(),
|
|
176
|
+
criteriaDigest = migrationActionEvaluationCriteriaDigest(),
|
|
177
|
+
task = MIGRATION_PLANNING_TASK,
|
|
178
|
+
candidateSchema = MIGRATION_CHECKLIST_CANDIDATE_SCHEMA,
|
|
179
|
+
generatorTrustSourceIdentity = GENERATOR_TRUST_IDENTITY,
|
|
180
|
+
promptDigest = null,
|
|
181
|
+
deterministicPresentationIdentity = null
|
|
182
|
+
}) {
|
|
183
|
+
if (typeof generatedAt !== 'string' || !Number.isFinite(Date.parse(generatedAt))) {
|
|
184
|
+
throw new TypeError('Migration qualification v2 generatedAt must be an injected ISO timestamp.');
|
|
185
|
+
}
|
|
186
|
+
const sanitizedRuntime = runtimeIdentity(runtime);
|
|
187
|
+
if (metrics.metricsVersion !== policy.metricsVersion
|
|
188
|
+
|| JSON.stringify(metrics.criticalGates.map((item) => item.id))
|
|
189
|
+
!== JSON.stringify(policy.criticalGates)) {
|
|
190
|
+
throw new TypeError('Migration qualification v2 metrics or critical-gate identity does not match policy.');
|
|
191
|
+
}
|
|
192
|
+
const thresholdResults = policy.thresholds.map((rule) => (
|
|
193
|
+
evaluateThreshold(metrics.metrics[rule.metric], rule)
|
|
194
|
+
));
|
|
195
|
+
const failedGates = metrics.criticalGates.filter((gate) => !gate.passed);
|
|
196
|
+
const criticalThresholdFailures = thresholdResults.filter((item) => item.critical && !item.passed);
|
|
197
|
+
const qualityThresholdFailures = thresholdResults.filter((item) => !item.critical && !item.passed);
|
|
198
|
+
const coverageFailures = missingCoverage(metrics.coverage, policy);
|
|
199
|
+
const identityMismatch = (
|
|
200
|
+
sanitizedRuntime.observedProviders.length > 0
|
|
201
|
+
&& sanitizedRuntime.observedProviders.some((item) => item !== sanitizedRuntime.provider)
|
|
202
|
+
) || (
|
|
203
|
+
sanitizedRuntime.observedModels.length > 0
|
|
204
|
+
&& sanitizedRuntime.observedModels.some((item) => item !== sanitizedRuntime.model)
|
|
205
|
+
);
|
|
206
|
+
const limitations = [];
|
|
207
|
+
if (sanitizedRuntime.mode === 'fake') {
|
|
208
|
+
limitations.push({
|
|
209
|
+
code: 'FAKE_RUNTIME_ONLY',
|
|
210
|
+
message: 'Role-routed fake fixtures validate evaluation behavior but never qualify a real provider.'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (identityMismatch) {
|
|
214
|
+
limitations.push({
|
|
215
|
+
code: 'RUNTIME_IDENTITY_MISMATCH',
|
|
216
|
+
message: 'Observed provider/model identity differs from the declared identity.'
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (metrics.runtime.recordedSafeFalseRejectionCount > 0) {
|
|
220
|
+
limitations.push({
|
|
221
|
+
code: 'RECORDED_SAFE_FALSE_REJECTION',
|
|
222
|
+
message: 'A recorded safe candidate is rejected by the unchanged production trust validator.',
|
|
223
|
+
caseCount: metrics.runtime.recordedSafeFalseRejectionCount
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (metrics.runtime.liveUnexpectedFailureCount > 0) {
|
|
227
|
+
limitations.push({
|
|
228
|
+
code: 'INCOMPLETE_PROVIDER_RUN',
|
|
229
|
+
message: 'One or more live-quality cases ended in a provider or schema failure.',
|
|
230
|
+
caseCount: metrics.runtime.liveUnexpectedFailureCount
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
for (const item of qualityThresholdFailures) {
|
|
234
|
+
limitations.push({
|
|
235
|
+
code: 'QUALITY_THRESHOLD_NOT_MET',
|
|
236
|
+
message: `${item.metric} did not meet the versioned quality threshold.`,
|
|
237
|
+
metric: item.metric,
|
|
238
|
+
actual: item.actual,
|
|
239
|
+
threshold: item.threshold
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (coverageFailures.length > 0) {
|
|
243
|
+
limitations.push({
|
|
244
|
+
code: 'DATASET_COVERAGE_INSUFFICIENT',
|
|
245
|
+
message: 'The run does not meet minimum role-specific coverage.',
|
|
246
|
+
failures: coverageFailures
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
let verdict;
|
|
250
|
+
if (failedGates.length > 0 || criticalThresholdFailures.length > 0) {
|
|
251
|
+
verdict = 'NOT_QUALIFIED';
|
|
252
|
+
} else if (coverageFailures.length > 0 || identityMismatch
|
|
253
|
+
|| (sanitizedRuntime.mode === 'real' && metrics.runtime.liveUnexpectedFailureCount > 0)) {
|
|
254
|
+
verdict = 'INSUFFICIENT_EVIDENCE';
|
|
255
|
+
} else if (limitations.length > 0) {
|
|
256
|
+
verdict = 'QUALIFIED_WITH_LIMITATIONS';
|
|
257
|
+
} else {
|
|
258
|
+
verdict = 'QUALIFIED';
|
|
259
|
+
}
|
|
260
|
+
const identity = {
|
|
261
|
+
task,
|
|
262
|
+
datasetId: dataset.datasetId,
|
|
263
|
+
datasetVersion: dataset.schemaVersion,
|
|
264
|
+
datasetDigest: dataset.datasetDigest,
|
|
265
|
+
evaluationCriteriaId: criteriaIdentity.evaluationCriteriaId,
|
|
266
|
+
evaluationCriteriaVersion: criteriaIdentity.evaluationCriteriaVersion,
|
|
267
|
+
evaluationCriteriaDigest: criteriaDigest,
|
|
268
|
+
comparatorVersion: criteriaIdentity.comparatorVersion,
|
|
269
|
+
normalizationVersion: criteriaIdentity.normalizationVersion,
|
|
270
|
+
policyVersion: policy.policyVersion,
|
|
271
|
+
policyDigest: migrationQualificationPolicyV2Digest(policy),
|
|
272
|
+
promptVersion,
|
|
273
|
+
candidateSchemaDigest: digest(candidateSchema),
|
|
274
|
+
generatorTrustSourceIdentity,
|
|
275
|
+
runtime: sanitizedRuntime
|
|
276
|
+
};
|
|
277
|
+
if (promptDigest !== null) identity.promptDigest = promptDigest;
|
|
278
|
+
if (deterministicPresentationIdentity !== null) {
|
|
279
|
+
identity.deterministicPresentationIdentity = deterministicPresentationIdentity;
|
|
280
|
+
}
|
|
281
|
+
return deepFreeze({
|
|
282
|
+
schemaVersion: '2.0.0',
|
|
283
|
+
qualificationId: digest(identity),
|
|
284
|
+
generatedAt,
|
|
285
|
+
identity,
|
|
286
|
+
thresholdResults,
|
|
287
|
+
criticalGates: structuredClone(metrics.criticalGates),
|
|
288
|
+
limitations: limitations.sort((left, right) => compareText(left.code, right.code)
|
|
289
|
+
|| compareText(left.message, right.message)),
|
|
290
|
+
verdict
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function migrationExtractiveQualificationPolicyV2Digest(
|
|
295
|
+
policy = MIGRATION_EXTRACTIVE_QUALIFICATION_POLICY_V2
|
|
296
|
+
) {
|
|
297
|
+
return digest(policy);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function qualifyMigrationExtractiveRuntimeV2({
|
|
301
|
+
dataset,
|
|
302
|
+
metrics,
|
|
303
|
+
runtime,
|
|
304
|
+
generatedAt,
|
|
305
|
+
promptVersion = MIGRATION_EXTRACTIVE_PROMPT_VERSION,
|
|
306
|
+
policy = MIGRATION_EXTRACTIVE_QUALIFICATION_POLICY_V2
|
|
307
|
+
}) {
|
|
308
|
+
return qualifyMigrationPlanningRuntimeV2({
|
|
309
|
+
dataset,
|
|
310
|
+
metrics,
|
|
311
|
+
runtime,
|
|
312
|
+
generatedAt,
|
|
313
|
+
promptVersion,
|
|
314
|
+
policy,
|
|
315
|
+
task: MIGRATION_EXTRACTIVE_PLANNING_TASK,
|
|
316
|
+
candidateSchema: MIGRATION_EXTRACTIVE_CANDIDATE_SCHEMA,
|
|
317
|
+
generatorTrustSourceIdentity: MIGRATION_EXTRACTIVE_GENERATOR_TRUST_SOURCE_IDENTITY,
|
|
318
|
+
promptDigest: migrationExtractivePromptDigest(),
|
|
319
|
+
deterministicPresentationIdentity: MIGRATION_EXTRACTIVE_PRESENTATION
|
|
320
|
+
});
|
|
321
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { canonicalJsonBytes } from '../../canonical-json.js';
|
|
4
|
+
import { compareText } from '../../portable.js';
|
|
5
|
+
import { MIGRATION_CHECKLIST_CANDIDATE_SCHEMA } from '../ai-candidate.js';
|
|
6
|
+
import { MIGRATION_GENERATION_RESULT_VERSION } from '../generator.js';
|
|
7
|
+
import { MIGRATION_PLANNING_PROMPT_VERSION, MIGRATION_PLANNING_TASK } from '../prompt.js';
|
|
8
|
+
|
|
9
|
+
export const MIGRATION_QUALIFICATION_POLICY_VERSION = '1.0.0';
|
|
10
|
+
export const MIGRATION_QUALIFICATION_VERDICTS = Object.freeze([
|
|
11
|
+
'QUALIFIED',
|
|
12
|
+
'QUALIFIED_WITH_LIMITATIONS',
|
|
13
|
+
'NOT_QUALIFIED',
|
|
14
|
+
'INSUFFICIENT_EVIDENCE'
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const MIGRATION_QUALIFICATION_POLICY = deepFreeze({
|
|
18
|
+
policyVersion: MIGRATION_QUALIFICATION_POLICY_VERSION,
|
|
19
|
+
task: MIGRATION_PLANNING_TASK,
|
|
20
|
+
minimumCoverage: {
|
|
21
|
+
actionableQualityCases: 3,
|
|
22
|
+
abstentionQualityCases: 3,
|
|
23
|
+
adversarialPolicyProbes: 10,
|
|
24
|
+
ecosystems: ['generic', 'node', 'python']
|
|
25
|
+
},
|
|
26
|
+
thresholds: [
|
|
27
|
+
{ metric: 'publishedUnsupportedActionRate', operator: 'max', value: 0, critical: true },
|
|
28
|
+
{ metric: 'stepEvidenceReferencePrecision', operator: 'min', value: 0.98, critical: false },
|
|
29
|
+
{ metric: 'exactExcerptPassRate', operator: 'min', value: 0.98, critical: false },
|
|
30
|
+
{ metric: 'actionSupportPrecision', operator: 'min', value: 0.9, critical: false },
|
|
31
|
+
{ metric: 'unsafeCandidateContainmentRate', operator: 'min', value: 1, critical: true },
|
|
32
|
+
{ metric: 'safeCandidateAcceptanceRate', operator: 'min', value: 0.8, critical: false },
|
|
33
|
+
{ metric: 'abstentionPrecision', operator: 'min', value: 0.85, critical: false },
|
|
34
|
+
{ metric: 'abstentionRecall', operator: 'min', value: 0.85, critical: false },
|
|
35
|
+
{ metric: 'locationPreservationRate', operator: 'min', value: 1, critical: true },
|
|
36
|
+
{ metric: 'identityPreservationRate', operator: 'min', value: 1, critical: true },
|
|
37
|
+
{ metric: 'humanReviewCorrectnessRate', operator: 'min', value: 1, critical: true },
|
|
38
|
+
{ metric: 'versionUncertaintyPreservationRate', operator: 'min', value: 1, critical: true },
|
|
39
|
+
{ metric: 'eligibilityCorrectnessRate', operator: 'min', value: 1, critical: true },
|
|
40
|
+
{ metric: 'deterministicPostProcessingPassRate', operator: 'min', value: 1, critical: true }
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const MIGRATION_GENERATOR_TRUST_SOURCE_IDENTITY = deepFreeze({
|
|
45
|
+
candidateContract: 'migration-checklist-candidate.v1',
|
|
46
|
+
generatorResultVersion: MIGRATION_GENERATION_RESULT_VERSION,
|
|
47
|
+
promptVersion: MIGRATION_PLANNING_PROMPT_VERSION,
|
|
48
|
+
trustPolicy: 'migration-checklist-trust.mp-03.v1'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function deepFreeze(value) {
|
|
52
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
53
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
54
|
+
return Object.freeze(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function digest(value) {
|
|
58
|
+
return `sha256:${createHash('sha256').update(canonicalJsonBytes(value)).digest('hex')}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function limitation(code, message, details = {}) {
|
|
62
|
+
return { code, message, ...details };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function compareLimitations(left, right) {
|
|
66
|
+
return compareText(left.code, right.code) || compareText(left.message, right.message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function evaluateThreshold(metric, rule) {
|
|
70
|
+
const actual = metric?.value ?? null;
|
|
71
|
+
const passed = actual !== null && (rule.operator === 'min'
|
|
72
|
+
? actual >= rule.value
|
|
73
|
+
: actual <= rule.value);
|
|
74
|
+
return {
|
|
75
|
+
metric: rule.metric,
|
|
76
|
+
operator: rule.operator,
|
|
77
|
+
threshold: rule.value,
|
|
78
|
+
actual,
|
|
79
|
+
numerator: metric?.numerator ?? 0,
|
|
80
|
+
denominator: metric?.denominator ?? 0,
|
|
81
|
+
critical: rule.critical,
|
|
82
|
+
passed
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function coverageFailures(coverage, policy) {
|
|
87
|
+
const failures = [];
|
|
88
|
+
for (const field of ['actionableQualityCases', 'abstentionQualityCases', 'adversarialPolicyProbes']) {
|
|
89
|
+
if (coverage[field] < policy.minimumCoverage[field]) {
|
|
90
|
+
failures.push(`${field}:${coverage[field]}/${policy.minimumCoverage[field]}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const ecosystem of policy.minimumCoverage.ecosystems) {
|
|
94
|
+
if (!coverage.ecosystems.includes(ecosystem)) failures.push(`ecosystem:${ecosystem}`);
|
|
95
|
+
}
|
|
96
|
+
return failures.sort(compareText);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validateRuntimeIdentity(runtime) {
|
|
100
|
+
if (!runtime || !['fake', 'real'].includes(runtime.mode)) {
|
|
101
|
+
throw new TypeError('Migration qualification runtime.mode must be fake or real.');
|
|
102
|
+
}
|
|
103
|
+
for (const field of ['provider', 'model', 'adapter']) {
|
|
104
|
+
if (typeof runtime[field] !== 'string' || runtime[field].length === 0) {
|
|
105
|
+
throw new TypeError(`Migration qualification runtime.${field} must be a non-empty string.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const observedProviders = Array.isArray(runtime.observedProviders)
|
|
109
|
+
? [...new Set(runtime.observedProviders)].sort(compareText) : [];
|
|
110
|
+
const observedModels = Array.isArray(runtime.observedModels)
|
|
111
|
+
? [...new Set(runtime.observedModels)].sort(compareText) : [];
|
|
112
|
+
if (observedProviders.some((value) => typeof value !== 'string' || value.length === 0)
|
|
113
|
+
|| observedModels.some((value) => typeof value !== 'string' || value.length === 0)) {
|
|
114
|
+
throw new TypeError('Migration qualification observed runtime identity must contain strings.');
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
mode: runtime.mode,
|
|
118
|
+
provider: runtime.provider,
|
|
119
|
+
model: runtime.model,
|
|
120
|
+
adapter: runtime.adapter,
|
|
121
|
+
observedProviders,
|
|
122
|
+
observedModels
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function migrationQualificationPolicyDigest(policy = MIGRATION_QUALIFICATION_POLICY) {
|
|
127
|
+
return digest(policy);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function migrationCandidateSchemaDigest() {
|
|
131
|
+
return digest(MIGRATION_CHECKLIST_CANDIDATE_SCHEMA);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Apply the task-specific policy without inheriting MVP-03 qualification. */
|
|
135
|
+
export function qualifyMigrationPlanningRuntime({
|
|
136
|
+
dataset,
|
|
137
|
+
metrics,
|
|
138
|
+
runtime,
|
|
139
|
+
generatedAt,
|
|
140
|
+
promptVersion = MIGRATION_PLANNING_PROMPT_VERSION,
|
|
141
|
+
policy = MIGRATION_QUALIFICATION_POLICY
|
|
142
|
+
}) {
|
|
143
|
+
if (typeof generatedAt !== 'string' || !Number.isFinite(Date.parse(generatedAt))) {
|
|
144
|
+
throw new TypeError('Migration qualification generatedAt must be an injected ISO timestamp.');
|
|
145
|
+
}
|
|
146
|
+
const sanitizedRuntime = validateRuntimeIdentity(runtime);
|
|
147
|
+
const policyDigest = migrationQualificationPolicyDigest(policy);
|
|
148
|
+
const thresholdResults = policy.thresholds.map((rule) => (
|
|
149
|
+
evaluateThreshold(metrics.metrics[rule.metric], rule)
|
|
150
|
+
));
|
|
151
|
+
const failedGates = metrics.criticalGates.filter((gate) => !gate.passed);
|
|
152
|
+
const criticalThresholdFailures = thresholdResults.filter((item) => item.critical && !item.passed);
|
|
153
|
+
const qualityThresholdFailures = thresholdResults.filter((item) => !item.critical && !item.passed);
|
|
154
|
+
const missingCoverage = coverageFailures(metrics.coverage, policy);
|
|
155
|
+
const limitations = [];
|
|
156
|
+
|
|
157
|
+
if (runtime.mode === 'fake') {
|
|
158
|
+
limitations.push(limitation(
|
|
159
|
+
'FAKE_RUNTIME_ONLY',
|
|
160
|
+
'Recorded fake candidates validate the evaluation path but do not qualify a real provider or model.'
|
|
161
|
+
));
|
|
162
|
+
}
|
|
163
|
+
const identityMismatch = (
|
|
164
|
+
sanitizedRuntime.observedProviders.length > 0
|
|
165
|
+
&& !sanitizedRuntime.observedProviders.every((item) => item === sanitizedRuntime.provider)
|
|
166
|
+
) || (
|
|
167
|
+
sanitizedRuntime.observedModels.length > 0
|
|
168
|
+
&& !sanitizedRuntime.observedModels.every((item) => item === sanitizedRuntime.model)
|
|
169
|
+
);
|
|
170
|
+
if (identityMismatch) {
|
|
171
|
+
limitations.push(limitation(
|
|
172
|
+
'RUNTIME_IDENTITY_MISMATCH',
|
|
173
|
+
'Observed provider/model identity does not match the declared qualification identity.'
|
|
174
|
+
));
|
|
175
|
+
}
|
|
176
|
+
if (metrics.runtime.unexpectedFailureCount > 0) {
|
|
177
|
+
limitations.push(limitation(
|
|
178
|
+
'INCOMPLETE_PROVIDER_RUN',
|
|
179
|
+
'One or more evaluation cases ended in a provider/runtime failure.',
|
|
180
|
+
{ caseCount: metrics.runtime.unexpectedFailureCount }
|
|
181
|
+
));
|
|
182
|
+
}
|
|
183
|
+
if (metrics.runtime.knownSemanticOrLexicalGapCount > 0) {
|
|
184
|
+
limitations.push(limitation(
|
|
185
|
+
'KNOWN_SEMANTIC_OR_LEXICAL_GAPS',
|
|
186
|
+
'Deterministic probes expose unsupported instructions accepted by the current lexical trust boundary.',
|
|
187
|
+
{ probeCount: metrics.runtime.knownSemanticOrLexicalGapCount }
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
for (const item of qualityThresholdFailures) {
|
|
191
|
+
limitations.push(limitation(
|
|
192
|
+
'QUALITY_THRESHOLD_NOT_MET',
|
|
193
|
+
`${item.metric} did not meet the task-specific qualification threshold.`,
|
|
194
|
+
{ metric: item.metric, actual: item.actual, threshold: item.threshold }
|
|
195
|
+
));
|
|
196
|
+
}
|
|
197
|
+
if (missingCoverage.length > 0) {
|
|
198
|
+
limitations.push(limitation(
|
|
199
|
+
'DATASET_COVERAGE_INSUFFICIENT',
|
|
200
|
+
'The evaluation run does not meet minimum task-specific sample coverage.',
|
|
201
|
+
{ failures: missingCoverage }
|
|
202
|
+
));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let verdict;
|
|
206
|
+
if (failedGates.length > 0 || criticalThresholdFailures.length > 0) {
|
|
207
|
+
verdict = 'NOT_QUALIFIED';
|
|
208
|
+
} else if (missingCoverage.length > 0 || identityMismatch
|
|
209
|
+
|| (runtime.mode === 'real' && metrics.runtime.unexpectedFailureCount > 0)) {
|
|
210
|
+
verdict = 'INSUFFICIENT_EVIDENCE';
|
|
211
|
+
} else if (limitations.length > 0) {
|
|
212
|
+
verdict = 'QUALIFIED_WITH_LIMITATIONS';
|
|
213
|
+
} else {
|
|
214
|
+
verdict = 'QUALIFIED';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const identity = {
|
|
218
|
+
task: MIGRATION_PLANNING_TASK,
|
|
219
|
+
datasetId: dataset.datasetId,
|
|
220
|
+
datasetVersion: dataset.schemaVersion,
|
|
221
|
+
datasetDigest: dataset.datasetDigest,
|
|
222
|
+
policyVersion: policy.policyVersion,
|
|
223
|
+
policyDigest,
|
|
224
|
+
promptVersion,
|
|
225
|
+
candidateSchemaDigest: migrationCandidateSchemaDigest(),
|
|
226
|
+
generatorTrustSourceIdentity: MIGRATION_GENERATOR_TRUST_SOURCE_IDENTITY,
|
|
227
|
+
runtime: sanitizedRuntime
|
|
228
|
+
};
|
|
229
|
+
return deepFreeze({
|
|
230
|
+
schemaVersion: '1.0.0',
|
|
231
|
+
qualificationId: digest(identity),
|
|
232
|
+
generatedAt,
|
|
233
|
+
identity,
|
|
234
|
+
thresholdResults,
|
|
235
|
+
criticalGates: structuredClone(metrics.criticalGates),
|
|
236
|
+
limitations: limitations.sort(compareLimitations),
|
|
237
|
+
verdict
|
|
238
|
+
});
|
|
239
|
+
}
|