@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.
Files changed (252) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/bin/depverdict.js +7 -0
  4. package/bin/upgradelens.js +7 -0
  5. package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
  6. package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
  7. package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
  8. package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
  9. package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
  10. package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
  11. package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
  12. package/docs/IA-04-CLI-Orchestration.md +235 -0
  13. package/docs/IA-05-Real-Provider-Validation.md +235 -0
  14. package/docs/IA-05-VinGrade-Validation.md +339 -0
  15. package/docs/MVP-01.md +48 -0
  16. package/docs/MVP-02-Architecture.md +536 -0
  17. package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
  18. package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
  19. package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
  20. package/docs/MVP-02-Knowledge-Manifest.md +279 -0
  21. package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
  22. package/docs/MVP-02-Knowledge-Store.md +87 -0
  23. package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
  24. package/docs/MVP-02-Research-Planning.md +196 -0
  25. package/docs/MVP-02-Source-Provenance.md +81 -0
  26. package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
  27. package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
  28. package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
  29. package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
  30. package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
  31. package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
  32. package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
  33. package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
  34. package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
  35. package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
  36. package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
  37. package/docs/VinGrade-MVP-02-Validation.md +480 -0
  38. package/docs/VinGrade-RC02-Live-Validation.md +273 -0
  39. package/docs/ai-capability-discovery.md +573 -0
  40. package/docs/ai-engineering-production-readiness.md +390 -0
  41. package/docs/ai-engineering-review.md +251 -0
  42. package/docs/ai-runtime-governance-discovery.md +754 -0
  43. package/docs/architecture-overview.md +78 -0
  44. package/docs/cli-progress.md +111 -0
  45. package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
  46. package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
  47. package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
  48. package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
  49. package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
  50. package/docs/gateway-runtime-discovery.md +614 -0
  51. package/docs/live-ai-validation.md +299 -0
  52. package/docs/migration-planning-qualification-resolution.md +99 -0
  53. package/docs/migrations/upgradelens-to-depverdict.md +97 -0
  54. package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
  55. package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
  56. package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
  57. package/docs/mvp-05-deterministic-context-runtime.md +127 -0
  58. package/docs/mvp-05-migration-checklist-contract.md +166 -0
  59. package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
  60. package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
  61. package/docs/mvp-05-provider-neutral-generator.md +79 -0
  62. package/docs/ollama-local-smoke-validation.md +156 -0
  63. package/docs/openai-compatible-runtime-discovery.md +789 -0
  64. package/docs/openrouter-one-dependency-validation.md +205 -0
  65. package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
  66. package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
  67. package/docs/package-content-policy.md +98 -0
  68. package/docs/releases/v0.5.0-technical-preview.md +114 -0
  69. package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
  70. package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
  71. package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
  72. package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
  73. package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
  74. package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
  75. package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
  76. package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
  77. package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
  78. package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
  79. package/docs/reviews/oss-03-community-scaffolding.md +378 -0
  80. package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
  81. package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
  82. package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
  83. package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
  84. package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
  85. package/docs/runtime-contract-discovery.md +521 -0
  86. package/docs/structured-output-compatibility-report.md +100 -0
  87. package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
  88. package/docs/version-analysis-architecture.md +827 -0
  89. package/eval/README.md +86 -0
  90. package/eval/datasets/generic/declared-constraint.json +59 -0
  91. package/eval/datasets/generic/evidence-conflict.json +73 -0
  92. package/eval/datasets/generic/major-breaking-release.json +66 -0
  93. package/eval/datasets/generic/missing-evidence.json +46 -0
  94. package/eval/datasets/generic/patch-release-low.json +59 -0
  95. package/eval/datasets/node/axios-patch-low.json +59 -0
  96. package/eval/datasets/node/react-major-breaking.json +66 -0
  97. package/eval/datasets/node/react-minor-compatibility.json +66 -0
  98. package/eval/datasets/python/fastapi-deprecation.json +66 -0
  99. package/eval/datasets/python/pydantic-major-breaking.json +66 -0
  100. package/eval/migration-planning/golden-dataset-v2.json +306 -0
  101. package/eval/migration-planning/golden-dataset.json +214 -0
  102. package/eval/schemas/expected-result.schema.json +88 -0
  103. package/eval/schemas/golden-case.schema.json +181 -0
  104. package/package.json +57 -0
  105. package/schemas/ai-scorecard.schema.json +132 -0
  106. package/schemas/benchmark-report.schema.json +189 -0
  107. package/schemas/benchmark.schema.json +55 -0
  108. package/schemas/capability-profile.schema.json +44 -0
  109. package/schemas/conformance-report.schema.json +150 -0
  110. package/schemas/deployment-profile.schema.json +64 -0
  111. package/schemas/evaluation-report.schema.json +158 -0
  112. package/schemas/knowledge-evidence-bundle.schema.json +150 -0
  113. package/schemas/knowledge-manifest.schema.json +548 -0
  114. package/schemas/metrics.schema.json +178 -0
  115. package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
  116. package/schemas/migration-checklist.schema.json +706 -0
  117. package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
  118. package/schemas/migration-evaluation-dataset.schema.json +204 -0
  119. package/schemas/migration-planning-qualification-record.schema.json +234 -0
  120. package/schemas/project-manifest.schema.json +308 -0
  121. package/schemas/qualification-record.schema.json +56 -0
  122. package/schemas/repository-impact-evidence.schema.json +232 -0
  123. package/schemas/repository-impact.schema.json +202 -0
  124. package/schemas/upgrade-decision.schema.json +273 -0
  125. package/schemas/usage-index.schema.json +179 -0
  126. package/schemas/version-analysis.schema.json +449 -0
  127. package/src/ai-runtime-debug.js +325 -0
  128. package/src/ai-runtime-error.js +42 -0
  129. package/src/ai-runtime.js +174 -0
  130. package/src/ai-scorecard.js +204 -0
  131. package/src/ai-version-analysis.js +484 -0
  132. package/src/artifact-root-compatibility.js +91 -0
  133. package/src/benchmark-report.js +111 -0
  134. package/src/benchmark-runner.js +191 -0
  135. package/src/canonical-json.js +69 -0
  136. package/src/cli.js +1299 -0
  137. package/src/conformance-report.js +158 -0
  138. package/src/conformance-runner.js +253 -0
  139. package/src/constants.js +73 -0
  140. package/src/cooperative-scheduler.js +79 -0
  141. package/src/dependencies.js +44 -0
  142. package/src/dependency-ai-context.js +625 -0
  143. package/src/detectors.js +253 -0
  144. package/src/discovery.js +234 -0
  145. package/src/ecosystem-version-adapter.js +294 -0
  146. package/src/environment-compatibility.js +77 -0
  147. package/src/evaluation-comparator.js +158 -0
  148. package/src/evaluation-report.js +76 -0
  149. package/src/evaluation-runner.js +248 -0
  150. package/src/evidence-source-adapter.js +472 -0
  151. package/src/files.js +89 -0
  152. package/src/governance-diagnostics.js +64 -0
  153. package/src/governance-loader.js +63 -0
  154. package/src/governance-metadata.js +346 -0
  155. package/src/governance-validator.js +360 -0
  156. package/src/http/bounded-fetch.js +278 -0
  157. package/src/http/cli-http-runtime.js +44 -0
  158. package/src/impact/input-loader.js +157 -0
  159. package/src/impact/matcher.js +40 -0
  160. package/src/impact/repository-impact.js +199 -0
  161. package/src/impact/runtime.js +24 -0
  162. package/src/impact/status.js +62 -0
  163. package/src/impact/writer.js +30 -0
  164. package/src/impact-evidence/input-loader.js +202 -0
  165. package/src/impact-evidence/repository-impact-evidence.js +234 -0
  166. package/src/impact-evidence/runtime.js +16 -0
  167. package/src/impact-evidence/writer.js +30 -0
  168. package/src/index.js +563 -0
  169. package/src/installed-version-baseline.js +196 -0
  170. package/src/knowledge-cache.js +324 -0
  171. package/src/knowledge-evidence-bundle.js +101 -0
  172. package/src/knowledge-evidence-producer.js +233 -0
  173. package/src/knowledge-manifest-builder.js +188 -0
  174. package/src/knowledge-manifest-writer.js +32 -0
  175. package/src/knowledge-manifest.js +255 -0
  176. package/src/knowledge-research.js +615 -0
  177. package/src/metrics-engine.js +205 -0
  178. package/src/migration-checklist/ai-candidate.js +320 -0
  179. package/src/migration-checklist/assembler.js +37 -0
  180. package/src/migration-checklist/context-runtime.js +828 -0
  181. package/src/migration-checklist/evaluation/action-criteria.js +244 -0
  182. package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
  183. package/src/migration-checklist/evaluation/comparator.js +279 -0
  184. package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
  185. package/src/migration-checklist/evaluation/dataset.js +336 -0
  186. package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
  187. package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
  188. package/src/migration-checklist/evaluation/metrics.js +158 -0
  189. package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
  190. package/src/migration-checklist/evaluation/qualification.js +239 -0
  191. package/src/migration-checklist/evaluation/runner-v2.js +294 -0
  192. package/src/migration-checklist/evaluation/runner.js +194 -0
  193. package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
  194. package/src/migration-checklist/evaluation/scorecard.js +86 -0
  195. package/src/migration-checklist/extractive-candidate.js +166 -0
  196. package/src/migration-checklist/extractive-prompt.js +62 -0
  197. package/src/migration-checklist/generator.js +702 -0
  198. package/src/migration-checklist/grounding-policy.js +117 -0
  199. package/src/migration-checklist/input-loader.js +613 -0
  200. package/src/migration-checklist/migration-checklist.js +635 -0
  201. package/src/migration-checklist/presentation.js +292 -0
  202. package/src/migration-checklist/progress.js +96 -0
  203. package/src/migration-checklist/prompt.js +83 -0
  204. package/src/migration-checklist/qualification-guard.js +462 -0
  205. package/src/migration-checklist/qualification-resolution.js +122 -0
  206. package/src/migration-checklist/qualification-store.js +225 -0
  207. package/src/migration-checklist/runtime.js +205 -0
  208. package/src/migration-checklist/verification.js +134 -0
  209. package/src/migration-checklist/writer.js +35 -0
  210. package/src/openai-compatible-provider.js +451 -0
  211. package/src/orchestration/failure-log.js +32 -0
  212. package/src/orchestration/pipeline.js +200 -0
  213. package/src/orchestration/progress-events.js +337 -0
  214. package/src/orchestration/progress-reporter.js +131 -0
  215. package/src/orchestration/text-writer.js +22 -0
  216. package/src/portable.js +13 -0
  217. package/src/product-completion.js +249 -0
  218. package/src/project-manifest-input.js +90 -0
  219. package/src/project-manifest.js +141 -0
  220. package/src/python-requirements.js +137 -0
  221. package/src/registry/npm-packument.js +256 -0
  222. package/src/registry/npm-registry-adapter.js +262 -0
  223. package/src/registry/pypi-project.js +300 -0
  224. package/src/registry/pypi-registry-adapter.js +235 -0
  225. package/src/registry/sanitize-registry-body.js +51 -0
  226. package/src/renderers/console.js +160 -0
  227. package/src/renderers/impact-presentation.js +278 -0
  228. package/src/renderers/markdown.js +172 -0
  229. package/src/research-plan.js +455 -0
  230. package/src/runtime-conformance.js +275 -0
  231. package/src/source-provenance.js +393 -0
  232. package/src/source-url.js +62 -0
  233. package/src/structured-output-schema.js +66 -0
  234. package/src/target-selector.js +306 -0
  235. package/src/upgrade-decision/input-loader.js +107 -0
  236. package/src/upgrade-decision/presentation.js +43 -0
  237. package/src/upgrade-decision/runtime.js +21 -0
  238. package/src/upgrade-decision/upgrade-decision.js +626 -0
  239. package/src/upgrade-decision/writer.js +30 -0
  240. package/src/usage/analyzer-registry.js +63 -0
  241. package/src/usage/coverage.js +116 -0
  242. package/src/usage/input-loader.js +139 -0
  243. package/src/usage/js/analyzer.js +240 -0
  244. package/src/usage/js/parser.js +21 -0
  245. package/src/usage/runtime.js +187 -0
  246. package/src/usage/scope.js +44 -0
  247. package/src/usage/source-files.js +50 -0
  248. package/src/usage/usage-index.js +217 -0
  249. package/src/usage/writer.js +31 -0
  250. package/src/version-analysis-loader.js +203 -0
  251. package/src/version-analysis-manifest.js +314 -0
  252. 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
+ }