@shapeshift-labs/frontier-lang-compiler 0.2.48 → 0.2.50
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/README.md +25 -0
- package/bench/native-transform-suite.mjs +19 -0
- package/bench/smoke.mjs +7 -0
- package/dist/declarations/runtime.d.ts +12 -0
- package/dist/declarations/universal-conversion-artifacts.d.ts +148 -0
- package/dist/declarations/universal-conversion-plan.d.ts +203 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/internal/index-impl/createUniversalConversionPlan.js +5 -0
- package/dist/internal/index-impl/queryUniversalConversionPlan.js +5 -0
- package/dist/universal-conversion-artifact-query.js +60 -0
- package/dist/universal-conversion-artifacts.js +287 -0
- package/dist/universal-conversion-plan-merge-refs.js +125 -0
- package/dist/universal-conversion-plan-scoring.js +134 -0
- package/dist/universal-conversion-plan.js +279 -0
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { idFragment, uniqueStrings } from './native-import-utils.js';
|
|
2
|
+
import { createUniversalConversionPlan } from './universal-conversion-plan.js';
|
|
3
|
+
import { artifactIndex } from './universal-conversion-artifact-query.js';
|
|
4
|
+
import { createSemanticHistoryRecord } from './internal/index-impl/semanticHistoryRecords.js';
|
|
5
|
+
import { createSemanticPatchBundleRecord } from './internal/index-impl/semanticPatchBundleRecords.js';
|
|
6
|
+
|
|
7
|
+
export { queryUniversalConversionArtifacts } from './universal-conversion-artifact-query.js';
|
|
8
|
+
|
|
9
|
+
export function createUniversalConversionArtifacts(input = {}, options = {}) {
|
|
10
|
+
const generatedAt = options.generatedAt ?? input.generatedAt ?? Date.now();
|
|
11
|
+
const plan = input?.kind === 'frontier.lang.universalConversionPlan'
|
|
12
|
+
? input
|
|
13
|
+
: input?.target && input?.sourceLanguage
|
|
14
|
+
? undefined
|
|
15
|
+
: createUniversalConversionPlan(input);
|
|
16
|
+
const routes = selectRoutes(plan?.routes ?? (input?.target && input?.sourceLanguage ? [input] : []), options);
|
|
17
|
+
const routeArtifacts = routes.map((route) => createRouteArtifact(route, {
|
|
18
|
+
generatedAt,
|
|
19
|
+
planId: options.planId ?? plan?.id ?? route.mergeRefs?.planId,
|
|
20
|
+
metadata: options.metadata
|
|
21
|
+
}));
|
|
22
|
+
const historyRecords = routeArtifacts.map((artifact) => artifact.history);
|
|
23
|
+
const patchBundleRecords = routeArtifacts.map((artifact) => artifact.patchBundle);
|
|
24
|
+
const index = artifactIndex(routeArtifacts);
|
|
25
|
+
return {
|
|
26
|
+
kind: 'frontier.lang.universalConversionArtifacts',
|
|
27
|
+
version: 1,
|
|
28
|
+
schema: 'frontier.lang.universalConversionArtifacts.v1',
|
|
29
|
+
id: options.id ?? `universal_conversion_artifacts_${idFragment(index.routeIds.join('_') || plan?.id || 'routes')}`,
|
|
30
|
+
planId: plan?.id ?? options.planId,
|
|
31
|
+
generatedAt,
|
|
32
|
+
routeArtifacts,
|
|
33
|
+
historyRecords,
|
|
34
|
+
patchBundleRecords,
|
|
35
|
+
index,
|
|
36
|
+
summary: {
|
|
37
|
+
routes: routeArtifacts.length,
|
|
38
|
+
histories: historyRecords.length,
|
|
39
|
+
patchBundles: patchBundleRecords.length,
|
|
40
|
+
reviewRequired: routeArtifacts.filter((artifact) => artifact.reviewRequired).length,
|
|
41
|
+
blocked: routeArtifacts.filter((artifact) => artifact.admissionStatus === 'blocked').length,
|
|
42
|
+
autoMergeClaims: 0,
|
|
43
|
+
semanticEquivalenceClaims: 0
|
|
44
|
+
},
|
|
45
|
+
metadata: {
|
|
46
|
+
autoMergeClaim: false,
|
|
47
|
+
semanticEquivalenceClaim: false,
|
|
48
|
+
note: 'Materialized conversion artifacts are merge-review records. They preserve provenance and admission state but do not prove target semantic equivalence.',
|
|
49
|
+
...options.metadata
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createRouteArtifact(route, options) {
|
|
55
|
+
const refs = route.mergeRefs ?? {};
|
|
56
|
+
const planId = options.planId ?? refs.planId;
|
|
57
|
+
const sources = normalizeSources(refs.sources, route);
|
|
58
|
+
const regions = routeRegions(route, refs, sources);
|
|
59
|
+
const sourceMapLinks = routeSourceMapLinks(route, refs, sources, regions);
|
|
60
|
+
const admissionStatus = routeAdmissionStatus(route);
|
|
61
|
+
const reasonCodes = routeReasonCodes(route);
|
|
62
|
+
const historyId = refs.historyIds?.[0] ?? `history_${route.id}`;
|
|
63
|
+
const patchBundleId = refs.patchBundleIds?.[0] ?? `semantic_patch_bundle_${route.id}`;
|
|
64
|
+
const history = createSemanticHistoryRecord({
|
|
65
|
+
id: historyId,
|
|
66
|
+
createdAt: options.generatedAt,
|
|
67
|
+
language: route.sourceLanguage,
|
|
68
|
+
sourcePath: sources[0]?.sourcePath,
|
|
69
|
+
sources,
|
|
70
|
+
ownershipRegions: regions,
|
|
71
|
+
semanticCandidates: routeSemanticCandidates(route, refs),
|
|
72
|
+
evidenceIds: refs.evidenceIds,
|
|
73
|
+
proofIds: refs.proofIds,
|
|
74
|
+
replayLinks: refs.replayLinks,
|
|
75
|
+
admission: {
|
|
76
|
+
status: admissionStatus,
|
|
77
|
+
readiness: route.readiness,
|
|
78
|
+
reasonCodes,
|
|
79
|
+
evidenceIds: refs.evidenceIds,
|
|
80
|
+
metadata: routeAdmissionMetadata(route, planId)
|
|
81
|
+
},
|
|
82
|
+
metadata: routeRecordMetadata(route, planId, options.metadata)
|
|
83
|
+
}, { id: historyId, createdAt: options.generatedAt });
|
|
84
|
+
const patchBundle = createSemanticPatchBundleRecord({
|
|
85
|
+
id: patchBundleId,
|
|
86
|
+
language: route.sourceLanguage,
|
|
87
|
+
sourcePath: sources[0]?.sourcePath,
|
|
88
|
+
sources,
|
|
89
|
+
changedRegions: regions,
|
|
90
|
+
sourceMapLinks,
|
|
91
|
+
evidenceIds: refs.evidenceIds,
|
|
92
|
+
proofIds: refs.proofIds,
|
|
93
|
+
historyIds: [history.id],
|
|
94
|
+
readiness: route.readiness,
|
|
95
|
+
conflictKeys: refs.conflictKeys,
|
|
96
|
+
admission: {
|
|
97
|
+
status: admissionStatus,
|
|
98
|
+
readiness: route.readiness,
|
|
99
|
+
reviewRequired: true,
|
|
100
|
+
reasonCodes,
|
|
101
|
+
conflictKeys: refs.conflictKeys,
|
|
102
|
+
evidenceIds: refs.evidenceIds,
|
|
103
|
+
metadata: routeAdmissionMetadata(route, planId)
|
|
104
|
+
},
|
|
105
|
+
metadata: routeRecordMetadata(route, planId, options.metadata)
|
|
106
|
+
}, { id: patchBundleId, createdAt: options.generatedAt });
|
|
107
|
+
const materialization = {
|
|
108
|
+
status: 'materialized',
|
|
109
|
+
plannedHistoryIds: refs.historyIds ?? [],
|
|
110
|
+
materializedHistoryIds: [history.id],
|
|
111
|
+
patchBundleIds: [patchBundle.id],
|
|
112
|
+
sourceMapLinkIds: patchBundle.index.sourceMapLinkIds,
|
|
113
|
+
evidenceIds: history.evidenceIds,
|
|
114
|
+
proofIds: history.proofIds,
|
|
115
|
+
autoMergeClaim: false,
|
|
116
|
+
semanticEquivalenceClaim: false
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
kind: 'frontier.lang.universalConversionRouteArtifact',
|
|
120
|
+
version: 1,
|
|
121
|
+
schema: 'frontier.lang.universalConversionRouteArtifact.v1',
|
|
122
|
+
routeId: route.id,
|
|
123
|
+
planId,
|
|
124
|
+
sourceLanguage: route.sourceLanguage,
|
|
125
|
+
target: route.target,
|
|
126
|
+
mode: route.mode,
|
|
127
|
+
routeAction: route.routeAction,
|
|
128
|
+
priority: route.priority,
|
|
129
|
+
readiness: route.readiness,
|
|
130
|
+
admissionAction: route.admissionAction,
|
|
131
|
+
admissionStatus,
|
|
132
|
+
reviewRequired: true,
|
|
133
|
+
history,
|
|
134
|
+
patchBundle,
|
|
135
|
+
materialization,
|
|
136
|
+
mergeScore: route.mergeScore,
|
|
137
|
+
autoMergeClaim: false,
|
|
138
|
+
semanticEquivalenceClaim: false,
|
|
139
|
+
metadata: { ...routeRecordMetadata(route, planId, options.metadata), materialization }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function selectRoutes(routes, options) {
|
|
144
|
+
const selected = (routes ?? []).filter((route) => {
|
|
145
|
+
if (options.routeId && route.id !== options.routeId) return false;
|
|
146
|
+
if (options.sourceLanguage && route.sourceLanguage !== options.sourceLanguage) return false;
|
|
147
|
+
if (options.target && route.target !== options.target) return false;
|
|
148
|
+
if (options.mode && route.mode !== options.mode) return false;
|
|
149
|
+
if (options.readiness && route.readiness !== options.readiness) return false;
|
|
150
|
+
if (options.admissionAction && route.admissionAction !== options.admissionAction) return false;
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
return Number.isFinite(options.maxRoutes) ? selected.slice(0, Math.max(0, Number(options.maxRoutes))) : selected;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeSources(sources, route) {
|
|
157
|
+
return (sources?.length ? sources : [{}]).map((source, index) => ({
|
|
158
|
+
id: source.sourceId ?? source.id ?? `route_source_${idFragment(route.id)}_${index + 1}`,
|
|
159
|
+
importId: source.importId,
|
|
160
|
+
language: route.sourceLanguage,
|
|
161
|
+
sourcePath: source.sourcePath,
|
|
162
|
+
sourceHash: source.sourceHash,
|
|
163
|
+
baseHash: source.baseHash,
|
|
164
|
+
targetHash: source.targetHash,
|
|
165
|
+
metadata: {
|
|
166
|
+
routeId: route.id,
|
|
167
|
+
target: route.target,
|
|
168
|
+
mode: route.mode
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function routeRegions(route, refs, sources) {
|
|
174
|
+
const source = sources[0] ?? {};
|
|
175
|
+
const keys = refs.semanticOwnershipKeys?.length
|
|
176
|
+
? refs.semanticOwnershipKeys
|
|
177
|
+
: [`conversion#${route.sourceLanguage ?? 'source'}#${route.target ?? 'target'}#${route.id}`];
|
|
178
|
+
return uniqueStrings(keys).map((key, index) => ({
|
|
179
|
+
id: `route_region_${idFragment(route.id)}_${index + 1}`,
|
|
180
|
+
key,
|
|
181
|
+
conflictKey: refs.conflictKeys?.[index] ?? refs.conflictKeys?.[0] ?? key,
|
|
182
|
+
changeKind: 'conversion-route',
|
|
183
|
+
regionKind: route.mode,
|
|
184
|
+
granularity: 'conversion-route',
|
|
185
|
+
precision: route.mode === 'preserve-source' ? 'exact-source' : 'semantic-route',
|
|
186
|
+
language: route.sourceLanguage,
|
|
187
|
+
sourcePath: source.sourcePath,
|
|
188
|
+
sourceHash: source.sourceHash,
|
|
189
|
+
sourceMapIds: refs.sourceMapIds,
|
|
190
|
+
sourceMapMappingIds: refs.sourceMapMappingIds,
|
|
191
|
+
admission: {
|
|
192
|
+
readiness: route.readiness,
|
|
193
|
+
action: route.admissionAction,
|
|
194
|
+
conflictKeys: refs.conflictKeys
|
|
195
|
+
},
|
|
196
|
+
metadata: {
|
|
197
|
+
routeId: route.id,
|
|
198
|
+
target: route.target,
|
|
199
|
+
mode: route.mode,
|
|
200
|
+
autoMergeClaim: false,
|
|
201
|
+
semanticEquivalenceClaim: false
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function routeSourceMapLinks(route, refs, sources, regions) {
|
|
207
|
+
const source = sources[0] ?? {};
|
|
208
|
+
const max = Math.max(refs.sourceMapIds?.length ?? 0, refs.sourceMapMappingIds?.length ?? 0);
|
|
209
|
+
return Array.from({ length: max }, (_, index) => ({
|
|
210
|
+
id: refs.sourceMapLinkIds?.[index] ?? `route_source_map_link_${idFragment(route.id)}_${index + 1}`,
|
|
211
|
+
sourceMapId: refs.sourceMapIds?.[index] ?? refs.sourceMapIds?.[0],
|
|
212
|
+
sourceMapMappingId: refs.sourceMapMappingIds?.[index],
|
|
213
|
+
sourcePath: source.sourcePath,
|
|
214
|
+
sourceHash: source.sourceHash,
|
|
215
|
+
targetPath: `${route.target}:${source.sourcePath ?? route.id}`,
|
|
216
|
+
precision: route.mode === 'preserve-source' ? 'exact-source' : 'semantic-route',
|
|
217
|
+
regionKey: regions[index % Math.max(1, regions.length)]?.key,
|
|
218
|
+
regionKind: route.mode
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function routeSemanticCandidates(route, refs) {
|
|
223
|
+
const ids = refs.mergeCandidateIds?.length ? refs.mergeCandidateIds : [`candidate_${route.id}`];
|
|
224
|
+
return uniqueStrings(ids).map((id) => ({
|
|
225
|
+
id,
|
|
226
|
+
sourcePath: refs.sources?.[0]?.sourcePath,
|
|
227
|
+
baseHash: refs.sources?.[0]?.baseHash,
|
|
228
|
+
targetHash: refs.sources?.[0]?.targetHash,
|
|
229
|
+
readiness: route.readiness,
|
|
230
|
+
conflictKeys: refs.conflictKeys ?? [],
|
|
231
|
+
ownershipKeys: refs.semanticOwnershipKeys ?? [],
|
|
232
|
+
evidenceIds: refs.evidenceIds ?? [],
|
|
233
|
+
proofIds: refs.proofIds ?? [],
|
|
234
|
+
replayIds: (refs.replayLinks ?? []).map((link) => link?.id).filter(Boolean),
|
|
235
|
+
metadata: {
|
|
236
|
+
routeId: route.id,
|
|
237
|
+
target: route.target,
|
|
238
|
+
mode: route.mode,
|
|
239
|
+
risk: route.mergeScore?.risk
|
|
240
|
+
}
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function routeAdmissionStatus(route) {
|
|
245
|
+
if (route.readiness === 'blocked' || route.admissionAction === 'reject') return 'blocked';
|
|
246
|
+
if (route.readiness === 'ready' && route.missingEvidence?.length === 0) return 'queued';
|
|
247
|
+
return 'needs-review';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function routeReasonCodes(route) {
|
|
251
|
+
return uniqueStrings([
|
|
252
|
+
`mode:${route.mode}`,
|
|
253
|
+
`action:${route.routeAction}`,
|
|
254
|
+
...(route.missingEvidence ?? []).map((item) => `missing:${item}`),
|
|
255
|
+
...(route.blockers ?? []).map((item) => `blocker:${item}`),
|
|
256
|
+
...(route.review ?? []).map((item) => `review:${item}`)
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function routeAdmissionMetadata(route, planId) {
|
|
261
|
+
return {
|
|
262
|
+
planId,
|
|
263
|
+
routeId: route.id,
|
|
264
|
+
routeAction: route.routeAction,
|
|
265
|
+
admissionAction: route.admissionAction,
|
|
266
|
+
priority: route.priority,
|
|
267
|
+
mergeScore: route.mergeScore,
|
|
268
|
+
autoMergeClaim: false,
|
|
269
|
+
semanticEquivalenceClaim: false
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function routeRecordMetadata(route, planId, metadata) {
|
|
274
|
+
return {
|
|
275
|
+
planId,
|
|
276
|
+
routeId: route.id,
|
|
277
|
+
target: route.target,
|
|
278
|
+
mode: route.mode,
|
|
279
|
+
routeAction: route.routeAction,
|
|
280
|
+
missingEvidence: route.missingEvidence ?? [],
|
|
281
|
+
blockers: route.blockers ?? [],
|
|
282
|
+
review: route.review ?? [],
|
|
283
|
+
autoMergeClaim: false,
|
|
284
|
+
semanticEquivalenceClaim: false,
|
|
285
|
+
...metadata
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
idFragment,
|
|
3
|
+
normalizeNativeLanguageId,
|
|
4
|
+
uniqueStrings
|
|
5
|
+
} from './native-import-utils.js';
|
|
6
|
+
|
|
7
|
+
export function importsForConversionLanguage(imports, language) {
|
|
8
|
+
const ids = new Set([language?.language, ...(language?.aliases ?? [])].map(normalizeNativeLanguageId).filter(Boolean));
|
|
9
|
+
return (imports ?? []).filter((imported) => ids.has(normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language)));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function conversionMergeRefs(input) {
|
|
13
|
+
const routeImports = input.imports ?? [];
|
|
14
|
+
const routeId = input.routeId;
|
|
15
|
+
const sourceMaps = routeImports.flatMap(sourceMapsForImport);
|
|
16
|
+
return {
|
|
17
|
+
planId: input.planId,
|
|
18
|
+
routeId,
|
|
19
|
+
historyIds: [`history_${routeId}`],
|
|
20
|
+
patchBundleIds: [],
|
|
21
|
+
patchIds: [],
|
|
22
|
+
mergeCandidateIds: uniqueStrings(routeImports.flatMap(mergeCandidateIds)),
|
|
23
|
+
replayLinks: routeImports.flatMap((imported) => imported?.replayLinks ?? imported?.universalAst?.replayLinks ?? []),
|
|
24
|
+
evidenceIds: uniqueStrings(routeImports.flatMap((imported) => evidenceRecords(imported).map((record) => record.id))),
|
|
25
|
+
proofIds: uniqueStrings(routeImports.flatMap(proofIdsForImport)),
|
|
26
|
+
sources: routeImports.map(conversionSourceRef),
|
|
27
|
+
semanticOwnershipKeys: uniqueStrings(routeImports.flatMap(semanticOwnershipKeysForImport)),
|
|
28
|
+
conflictKeys: uniqueStrings(routeImports.flatMap(conflictKeysForImport)),
|
|
29
|
+
sourceMapIds: uniqueStrings(sourceMaps.map((sourceMap) => sourceMap?.id)),
|
|
30
|
+
sourceMapMappingIds: uniqueStrings(sourceMaps.flatMap((sourceMap) => (sourceMap?.mappings ?? []).map((mapping) => mapping.id))),
|
|
31
|
+
sourceMapLinkIds: [],
|
|
32
|
+
readiness: input.readiness,
|
|
33
|
+
admissionStatus: input.admissionStatus,
|
|
34
|
+
metadata: {
|
|
35
|
+
plannedHistoryId: true,
|
|
36
|
+
note: 'Merge refs are compact route provenance for semantic history and patch-bundle builders; planned IDs are not proof records until materialized.'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function conversionSourceRef(imported) {
|
|
42
|
+
return {
|
|
43
|
+
sourceId: imported?.nativeSource?.id ?? imported?.id,
|
|
44
|
+
importId: imported?.id,
|
|
45
|
+
sourcePath: imported?.sourcePath ?? imported?.nativeSource?.sourcePath,
|
|
46
|
+
sourceHash: imported?.sourceHash
|
|
47
|
+
?? imported?.nativeSource?.sourceHash
|
|
48
|
+
?? imported?.metadata?.sourcePreservation?.sourceHash,
|
|
49
|
+
baseHash: imported?.baseHash ?? imported?.metadata?.baseHash,
|
|
50
|
+
targetHash: imported?.targetHash ?? imported?.metadata?.targetHash
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function evidenceRecords(imported) {
|
|
55
|
+
return [
|
|
56
|
+
...(imported?.evidence ?? []),
|
|
57
|
+
...(imported?.universalAst?.evidence ?? []),
|
|
58
|
+
...(imported?.patch?.evidence ?? [])
|
|
59
|
+
].filter((record) => record?.id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function proofIdsForImport(imported) {
|
|
63
|
+
return [
|
|
64
|
+
...(imported?.proofIds ?? []),
|
|
65
|
+
...evidenceRecords(imported).filter((record) => record?.kind === 'proof' || record?.type === 'proof').map((record) => record.id)
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function mergeCandidateIds(imported) {
|
|
70
|
+
return (imported?.mergeCandidates ?? []).map((candidate) => candidate.id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sourceMapsForImport(imported) {
|
|
74
|
+
return [
|
|
75
|
+
...(imported?.sourceMaps ?? []),
|
|
76
|
+
...(imported?.universalAst?.sourceMaps ?? [])
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function semanticOwnershipKeysForImport(imported) {
|
|
81
|
+
return uniqueStrings([
|
|
82
|
+
...ownershipKeysFromCandidates(imported),
|
|
83
|
+
...ownershipKeysFromSourceMaps(imported),
|
|
84
|
+
...ownershipKeysFromSymbols(imported)
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function conflictKeysForImport(imported) {
|
|
89
|
+
return uniqueStrings([
|
|
90
|
+
...conflictKeysFromCandidates(imported),
|
|
91
|
+
...ownershipKeysFromSourceMaps(imported)
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ownershipKeysFromCandidates(imported) {
|
|
96
|
+
return uniqueStrings((imported?.mergeCandidates ?? []).flatMap((candidate) => [
|
|
97
|
+
...(candidate?.ownershipKeys ?? []),
|
|
98
|
+
...(candidate?.semanticOwnershipKeys ?? []),
|
|
99
|
+
...(candidate?.changedRegions ?? []).map((region) => region?.key)
|
|
100
|
+
]));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function conflictKeysFromCandidates(imported) {
|
|
104
|
+
return uniqueStrings((imported?.mergeCandidates ?? []).flatMap((candidate) => [
|
|
105
|
+
...(candidate?.conflictKeys ?? []),
|
|
106
|
+
...(candidate?.changedRegions ?? []).flatMap((region) => [region?.conflictKey, ...(region?.admission?.conflictKeys ?? [])])
|
|
107
|
+
]));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ownershipKeysFromSourceMaps(imported) {
|
|
111
|
+
return uniqueStrings(sourceMapsForImport(imported).flatMap((sourceMap) => (sourceMap?.mappings ?? []).flatMap((mapping) => [
|
|
112
|
+
mapping?.ownershipRegionKey,
|
|
113
|
+
mapping?.ownershipRegionId,
|
|
114
|
+
mapping?.ownershipRegionKind ? `${mapping.sourceSpan?.path ?? imported?.sourcePath ?? 'source'}#${mapping.ownershipRegionKind}` : undefined
|
|
115
|
+
])));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function ownershipKeysFromSymbols(imported) {
|
|
119
|
+
const symbols = imported?.semanticIndex?.symbols ?? imported?.universalAst?.semanticIndex?.symbols ?? [];
|
|
120
|
+
return uniqueStrings(symbols.flatMap((symbol) => [
|
|
121
|
+
symbol?.metadata?.ownershipRegionKey,
|
|
122
|
+
symbol?.metadata?.ownershipRegionId,
|
|
123
|
+
symbol?.metadata?.ownershipRegionKind ? `${symbol.language ?? imported?.language ?? 'source'}#${idFragment(symbol.name)}#${symbol.metadata.ownershipRegionKind}` : undefined
|
|
124
|
+
]));
|
|
125
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { uniqueStrings } from './native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
const readinessScore = Object.freeze({ ready: 100, 'ready-with-losses': 76, 'needs-review': 48, blocked: 0 });
|
|
4
|
+
const readinessRank = Object.freeze({ ready: 3, 'ready-with-losses': 2, 'needs-review': 1, blocked: 0 });
|
|
5
|
+
const actionRank = Object.freeze({ admit: 2, prioritize: 1, reject: 0 });
|
|
6
|
+
const modeRank = Object.freeze({
|
|
7
|
+
'preserve-source': 5,
|
|
8
|
+
'target-adapter': 4,
|
|
9
|
+
'stub-only': 3,
|
|
10
|
+
'semantic-index-only': 2,
|
|
11
|
+
blocked: 0
|
|
12
|
+
});
|
|
13
|
+
const componentWeights = Object.freeze({
|
|
14
|
+
importEvidence: 22,
|
|
15
|
+
parserEvidence: 18,
|
|
16
|
+
semanticIndex: 18,
|
|
17
|
+
projectionPath: 24,
|
|
18
|
+
proofEvidence: 18
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export function conversionScoreComponents(language, targetCell, readiness, mode, evidence) {
|
|
22
|
+
return {
|
|
23
|
+
importEvidence: scoreComponent('importEvidence', readinessScore[language.imports.readiness] ?? 48, [
|
|
24
|
+
...(language.imports.total ? [] : ['No source import evidence.']),
|
|
25
|
+
...(language.imports.readiness === 'ready' ? [] : [`Import readiness is ${language.imports.readiness}.`])
|
|
26
|
+
], { imports: language.imports.total, losses: language.imports.losses }),
|
|
27
|
+
parserEvidence: scoreComponent('parserEvidence', parserScore(language), [
|
|
28
|
+
...(language.parser.rows ? [] : ['No parser feature row matched this language.']),
|
|
29
|
+
...(language.parser.blockingFeatures ?? []).map((feature) => `Parser feature is blocked: ${feature}.`)
|
|
30
|
+
], { rows: language.parser.rows, mergeReadyParsers: language.parser.mergeReadyParsers.length }),
|
|
31
|
+
semanticIndex: scoreComponent('semanticIndex', semanticIndexScore(language), [
|
|
32
|
+
...(language.imports.symbols ? [] : ['No semantic symbols were imported.']),
|
|
33
|
+
...(language.imports.sourceMapMappings ? [] : ['No source-map mappings were imported.'])
|
|
34
|
+
], { symbols: language.imports.symbols, sourceMapMappings: language.imports.sourceMapMappings }),
|
|
35
|
+
projectionPath: scoreComponent('projectionPath', projectionPathScore(targetCell, mode, readiness), projectionPathReasons(targetCell, mode), {
|
|
36
|
+
mode,
|
|
37
|
+
lossClass: targetCell?.lossClass,
|
|
38
|
+
adapter: targetCell?.adapter,
|
|
39
|
+
readiness
|
|
40
|
+
}),
|
|
41
|
+
proofEvidence: scoreComponent('proofEvidence', proofEvidenceScore(evidence), proofEvidenceReasons(evidence), {
|
|
42
|
+
records: evidence.length,
|
|
43
|
+
passed: evidence.filter((record) => passedEvidence(record)).length,
|
|
44
|
+
failed: evidence.filter((record) => record?.status === 'failed').length
|
|
45
|
+
})
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function conversionMergeScore(input) {
|
|
50
|
+
const weighted = Object.values(input.components).reduce((sum, component) => sum + component.weightedScore, 0);
|
|
51
|
+
const weight = Object.values(input.components).reduce((sum, component) => sum + component.weight, 0);
|
|
52
|
+
const uncappedValue = Math.round(weight ? weighted * 100 / weight : 0);
|
|
53
|
+
const action = input.blockers.length || input.readiness === 'blocked' || input.mode === 'blocked' ? 'reject'
|
|
54
|
+
: input.readiness === 'ready' && input.mode !== 'stub-only' && input.mode !== 'semantic-index-only' ? 'admit'
|
|
55
|
+
: 'prioritize';
|
|
56
|
+
const value = action === 'reject' ? Math.min(35, uncappedValue) : uncappedValue;
|
|
57
|
+
return {
|
|
58
|
+
schema: 'frontier.lang.semanticMergeScore.v1',
|
|
59
|
+
version: 1,
|
|
60
|
+
value,
|
|
61
|
+
uncappedValue,
|
|
62
|
+
sortKey: value + (actionRank[action] ?? 0) * 1000 + (modeRank[input.mode] ?? 0) * 100 + (readinessRank[input.readiness] ?? 0) * 10,
|
|
63
|
+
higherIsBetter: true,
|
|
64
|
+
readiness: input.readiness,
|
|
65
|
+
risk: input.readiness === 'blocked' ? 'high' : input.readiness === 'needs-review' ? 'medium' : 'low',
|
|
66
|
+
action,
|
|
67
|
+
components: input.components,
|
|
68
|
+
penalties: uniqueStrings([
|
|
69
|
+
...(action === 'reject' ? ['Conversion route is rejected until blockers are resolved.'] : []),
|
|
70
|
+
...Object.values(input.components).flatMap((component) => component.score < 100 ? component.reasons : [])
|
|
71
|
+
])
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parserScore(language) {
|
|
76
|
+
if (!language.parser.rows) return 0;
|
|
77
|
+
return Math.min(100, (readinessScore[language.parser.readiness] ?? 48) + Math.min(16, language.parser.mergeReadyParsers.length * 8));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function semanticIndexScore(language) {
|
|
81
|
+
const symbols = language.imports.symbols ?? 0;
|
|
82
|
+
const mappings = language.imports.sourceMapMappings ?? 0;
|
|
83
|
+
if (!symbols) return 0;
|
|
84
|
+
return Math.min(100, 62 + Math.min(22, symbols * 3) + Math.min(16, mappings * 2));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function projectionPathScore(targetCell, mode, readiness) {
|
|
88
|
+
if (mode === 'blocked') return 0;
|
|
89
|
+
if (mode === 'preserve-source') return 92;
|
|
90
|
+
if (mode === 'target-adapter') return Math.min(92, (readinessScore[readiness] ?? 48) + (targetCell?.adapter ? 12 : 0));
|
|
91
|
+
if (mode === 'stub-only') return 44;
|
|
92
|
+
if (mode === 'semantic-index-only') return 30;
|
|
93
|
+
return 10;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function proofEvidenceScore(evidence) {
|
|
97
|
+
if (!evidence.length) return 45;
|
|
98
|
+
const passed = evidence.filter((record) => passedEvidence(record)).length;
|
|
99
|
+
const failed = evidence.filter((record) => record?.status === 'failed').length;
|
|
100
|
+
return Math.max(0, Math.min(100, 55 + passed * 12 - failed * 35));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function projectionPathReasons(targetCell, mode) {
|
|
104
|
+
return uniqueStrings([
|
|
105
|
+
...(targetCell?.reason ? [targetCell.reason] : []),
|
|
106
|
+
...(mode === 'blocked' ? ['Projection path is blocked.'] : []),
|
|
107
|
+
...(mode === 'semantic-index-only' ? ['Semantic index can guide review, but code emission needs a target adapter.'] : [])
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function proofEvidenceReasons(evidence) {
|
|
112
|
+
return uniqueStrings([
|
|
113
|
+
...(!evidence.length ? ['No proof, oracle, test, or replay evidence was attached to this conversion route.'] : []),
|
|
114
|
+
...(evidence.filter((record) => record?.status === 'failed').length ? ['At least one attached evidence record failed.'] : [])
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function scoreComponent(key, score, reasons, signals) {
|
|
119
|
+
const normalized = Math.max(0, Math.min(100, Math.round(score)));
|
|
120
|
+
const weight = componentWeights[key];
|
|
121
|
+
return {
|
|
122
|
+
key,
|
|
123
|
+
score: normalized,
|
|
124
|
+
weight,
|
|
125
|
+
weightedScore: normalized * weight / 100,
|
|
126
|
+
status: normalized >= 80 ? 'strong' : normalized >= 50 ? 'partial' : normalized > 0 ? 'weak' : 'blocked',
|
|
127
|
+
reasons: uniqueStrings(reasons),
|
|
128
|
+
signals
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function passedEvidence(record) {
|
|
133
|
+
return record?.status === 'passed' || record?.status === 'ok' || record?.status === 'success';
|
|
134
|
+
}
|