@shapeshift-labs/frontier-lang-compiler 0.2.99 → 0.2.101

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 (57) hide show
  1. package/dist/declarations/bidirectional-target-change-evidence.d.ts +299 -0
  2. package/dist/declarations/bidirectional-target-change.d.ts +19 -120
  3. package/dist/declarations/native-project-admission.d.ts +43 -22
  4. package/dist/declarations/semantic-edit-bundle.d.ts +13 -1
  5. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +24 -0
  6. package/dist/declarations/semantic-edit-script.d.ts +53 -51
  7. package/dist/declarations/semantic-lineage.d.ts +62 -51
  8. package/dist/declarations/semantic-merge-candidates.d.ts +39 -0
  9. package/dist/declarations/semantic-patch-bundle.d.ts +13 -0
  10. package/dist/declarations/semantic-sidecar-admission.d.ts +14 -0
  11. package/dist/declarations/semantic-sidecar.d.ts +12 -14
  12. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +200 -0
  13. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +62 -17
  14. package/dist/internal/index-impl/createNativeSourcePreservation.js +16 -1
  15. package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +151 -1
  16. package/dist/internal/index-impl/createSemanticImportSidecar.js +5 -0
  17. package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +29 -11
  18. package/dist/internal/index-impl/declarationRecord.js +2 -2
  19. package/dist/internal/index-impl/inferSemanticLineageEvents.js +8 -0
  20. package/dist/internal/index-impl/nativeChangeProjectionEndpoint.js +56 -16
  21. package/dist/internal/index-impl/projectImportAdmissionMergeScore.js +26 -74
  22. package/dist/internal/index-impl/projectImportAdmissionProjectionCoverage.js +74 -0
  23. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +92 -74
  24. package/dist/internal/index-impl/replaySemanticEditProjection.js +114 -40
  25. package/dist/internal/index-impl/semanticEditBundleAdmission.js +95 -12
  26. package/dist/internal/index-impl/semanticEditBundleIndex.js +16 -10
  27. package/dist/internal/index-impl/semanticEditInsertionAnchors.js +8 -5
  28. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +167 -0
  29. package/dist/internal/index-impl/semanticEditSourceRanges.js +283 -0
  30. package/dist/internal/index-impl/semanticHistoryLineageResolution.js +56 -3
  31. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +2 -2
  32. package/dist/internal/index-impl/semanticLineageHashEvidence.js +97 -0
  33. package/dist/internal/index-impl/semanticLineageInferenceMatching.js +158 -13
  34. package/dist/internal/index-impl/semanticLineageResolutionRecords.js +46 -2
  35. package/dist/internal/index-impl/semanticMergeCandidateRecords.js +22 -2
  36. package/dist/internal/index-impl/semanticMergeCandidateScoreFacets.js +221 -0
  37. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +122 -20
  38. package/dist/internal/index-impl/semanticPatchBundleLineageLinks.js +199 -0
  39. package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +29 -3
  40. package/dist/internal/index-impl/semanticPatchBundleRecords.js +28 -104
  41. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +127 -0
  42. package/dist/internal/index-impl/sourcePreservationFromProjectionContext.js +9 -2
  43. package/dist/internal/index-impl/sourceTextForSpan.js +4 -9
  44. package/dist/lightweight-dependency-relations.js +113 -7
  45. package/dist/native-import-language-profiles.js +10 -2
  46. package/dist/native-import-utils.js +15 -1
  47. package/dist/native-region-scanner-js-helpers.js +68 -18
  48. package/dist/native-region-scanner-js-imports.js +7 -0
  49. package/dist/native-region-scanner-js.js +16 -8
  50. package/dist/native-region-scanner.js +2 -1
  51. package/dist/semantic-import-regions.js +8 -6
  52. package/dist/semantic-import-sidecar-admission-types.d.ts +14 -0
  53. package/dist/semantic-import-sidecar-entry.js +151 -7
  54. package/dist/semantic-import-sidecar-types.d.ts +18 -13
  55. package/dist/semantic-import-source-preservation-utils.js +55 -0
  56. package/dist/semantic-import-source-preservation.js +98 -3
  57. package/package.json +1 -1
@@ -0,0 +1,283 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+
3
+ export function projectionCoveredContainerOperationIds(operations, workerSourceText) {
4
+ if (typeof workerSourceText !== 'string') return new Set();
5
+ const result = new Set();
6
+ for (const operation of operations ?? []) {
7
+ if (!isProjectionCoverableContainer(operation)) continue;
8
+ if (workerContainerCoveredByInsertedChildren(operation, operations, workerSourceText)) result.add(operation.id);
9
+ }
10
+ return result;
11
+ }
12
+
13
+ export function spanOffsets(sourceText, span) {
14
+ if (typeof sourceText !== 'string' || !span) return undefined;
15
+ if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) {
16
+ return { start: span.start, end: span.end };
17
+ }
18
+ if (typeof span.startLine !== 'number') return undefined;
19
+ const lineStarts = [0];
20
+ for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') lineStarts.push(index + 1);
21
+ const startLine = Math.max(1, span.startLine);
22
+ const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
23
+ const start = lineStarts[startLine - 1];
24
+ const endLineStart = lineStarts[endLine - 1];
25
+ if (start === undefined || endLineStart === undefined) return undefined;
26
+ const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
27
+ const lineEnd = lineContentEndOffset(sourceText, lineStarts[endLine]);
28
+ const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
29
+ return { start: start + startColumn, end: endLineStart + endColumn };
30
+ }
31
+
32
+ export function scopedBodyReplacement(operation, headSourceText, workerSourceText, headOffsets, workerOffsets) {
33
+ if (!isBodyReplacement(operation)) return undefined;
34
+ const head = bodyContentRange(headSourceText, headOffsets);
35
+ const worker = bodyContentRange(workerSourceText, workerOffsets);
36
+ if (!head || !worker) return undefined;
37
+ const headPrefix = headSourceText.slice(headOffsets.start, head.start);
38
+ const workerPrefix = workerSourceText.slice(workerOffsets.start, worker.start);
39
+ const headSuffix = headSourceText.slice(head.end, headOffsets.end);
40
+ const workerSuffix = workerSourceText.slice(worker.end, workerOffsets.end);
41
+ if (headPrefix !== workerPrefix || headSuffix !== workerSuffix) return undefined;
42
+ return { sourceRangeKind: 'body-content', head, worker };
43
+ }
44
+
45
+ export function bodyContentRange(sourceText, range) {
46
+ const pairs = bracePairs(sourceText, range);
47
+ const close = trailingBodyCloseOffset(sourceText, range);
48
+ const pair = close === undefined ? undefined : pairs.find((candidate) => candidate.close === close);
49
+ return pair ? { start: pair.open + 1, end: pair.close } : undefined;
50
+ }
51
+
52
+ export function insertionOffset(sourceText, insertion, context = {}) {
53
+ if (typeof sourceText !== 'string') return { ok: false, reasonCodes: ['missing-head-source-text'] };
54
+ const mode = insertion?.mode;
55
+ if (mode === 'file-start') return { ok: true, offset: 0 };
56
+ if (mode === 'file-end') return { ok: true, offset: sourceText.length };
57
+ const resolved = insertionAnchorResolution(sourceText, insertion, context);
58
+ if (!resolved?.range) return { ok: false, reasonCodes: ['insertion-anchor-not-resolvable'] };
59
+ if (resolved.mode === 'before') return { ok: true, offset: resolved.range.start };
60
+ if (resolved.mode === 'after') return { ok: true, offset: afterLineOffset(sourceText, resolved.range.end) };
61
+ return { ok: false, reasonCodes: ['insertion-mode-unsupported'] };
62
+ }
63
+
64
+ export function removalRange(sourceText, span) {
65
+ const range = { ...span };
66
+ const next = lineBreakEndOffset(sourceText, range.end);
67
+ if (next !== range.end) range.end = next;
68
+ else {
69
+ const previous = previousLineBreakStartOffset(sourceText, range.start);
70
+ if (previous !== range.start) range.start = previous;
71
+ }
72
+ return range;
73
+ }
74
+
75
+ export function insertionReplacement(text, sourceText, offset) {
76
+ let replacement = String(text ?? '');
77
+ const newline = sourceLineEnding(sourceText);
78
+ if (offset > 0 && !isLineBreak(sourceText[offset - 1])) replacement = `${newline}${replacement}`;
79
+ if (offset < sourceText.length && !endsWithLineBreak(replacement)) replacement += newline;
80
+ if (offset === sourceText.length && sourceText && !endsWithLineBreak(sourceText)) replacement = `${newline}${replacement}`;
81
+ if (offset === sourceText.length && !endsWithLineBreak(replacement)) replacement += newline;
82
+ return replacement;
83
+ }
84
+
85
+ export function afterLineOffset(sourceText, offset) {
86
+ return lineBreakEndOffset(sourceText, offset);
87
+ }
88
+
89
+ function isProjectionCoverableContainer(operation) {
90
+ if (['portable', 'already-applied', 'covered'].includes(operation.status)) return false;
91
+ if (operation.changeKind !== 'modified') return false;
92
+ if (!operation.spans?.worker || !operation.hashes?.baseTextHash) return false;
93
+ const kind = String(operation.anchor?.regionKind ?? operation.regionKind ?? '');
94
+ return kind === 'type' || kind === 'config' || kind === 'content' || kind === 'route' || kind === 'property';
95
+ }
96
+
97
+ function workerContainerCoveredByInsertedChildren(container, operations, workerSourceText) {
98
+ const containerWorker = spanOffsets(workerSourceText, container.spans?.worker);
99
+ if (!containerWorker) return false;
100
+ const childRanges = (operations ?? [])
101
+ .filter((operation) => operation.id !== container.id)
102
+ .filter((operation) => operation.changeKind === 'added' || String(operation.kind ?? '').startsWith('add'))
103
+ .filter((operation) => ['portable', 'already-applied'].includes(operation.status))
104
+ .map((operation) => spanOffsets(workerSourceText, operation.spans?.worker))
105
+ .filter((range) => containedRange(range, containerWorker))
106
+ .map((range) => insertionRemovalRange(workerSourceText, range, containerWorker));
107
+ if (!childRanges.length) return false;
108
+ const stripped = childRanges
109
+ .sort((left, right) => right.start - left.start || right.end - left.end)
110
+ .reduce((text, range) => text.slice(0, range.start - containerWorker.start) + text.slice(range.end - containerWorker.start), workerSourceText.slice(containerWorker.start, containerWorker.end));
111
+ return hashSemanticValue(stripped) === container.hashes.baseTextHash;
112
+ }
113
+
114
+ function containedRange(inner, outer) {
115
+ return Boolean(inner && outer && outer.start <= inner.start && inner.end <= outer.end);
116
+ }
117
+
118
+ function insertionRemovalRange(sourceText, span, container) {
119
+ const range = { ...span };
120
+ const next = lineBreakEndOffset(sourceText, range.end);
121
+ if (next !== range.end && next <= container.end) range.end = next;
122
+ else {
123
+ const previous = previousLineBreakStartOffset(sourceText, range.start);
124
+ if (previous !== range.start && previous >= container.start) range.start = previous;
125
+ }
126
+ return range;
127
+ }
128
+
129
+ function lineContentEndOffset(sourceText, nextLineStart) {
130
+ if (nextLineStart === undefined) return sourceText.length;
131
+ const lineBreakStart = sourceText[nextLineStart - 2] === '\r' ? nextLineStart - 2 : nextLineStart - 1;
132
+ return Math.max(0, lineBreakStart);
133
+ }
134
+
135
+ function lineBreakEndOffset(sourceText, offset) {
136
+ if (sourceText[offset] === '\r' && sourceText[offset + 1] === '\n') return offset + 2;
137
+ if (isLineBreak(sourceText[offset])) return offset + 1;
138
+ return offset;
139
+ }
140
+
141
+ function previousLineBreakStartOffset(sourceText, offset) {
142
+ if (sourceText[offset - 1] === '\n') return sourceText[offset - 2] === '\r' ? offset - 2 : offset - 1;
143
+ if (sourceText[offset - 1] === '\r') return offset - 1;
144
+ return offset;
145
+ }
146
+
147
+ function sourceLineEnding(sourceText) {
148
+ if (sourceText.includes('\r\n')) return '\r\n';
149
+ return sourceText.includes('\r') ? '\r' : '\n';
150
+ }
151
+
152
+ function endsWithLineBreak(value) {
153
+ return isLineBreak(value[value.length - 1]);
154
+ }
155
+
156
+ function isLineBreak(char) {
157
+ return char === '\n' || char === '\r';
158
+ }
159
+
160
+ function isBodyReplacement(operation) {
161
+ return operation.changeKind === 'modified' && (operation.kind === 'replaceBody' || operation.anchor?.regionKind === 'body');
162
+ }
163
+
164
+ function insertionAnchorResolution(sourceText, insertion, context) {
165
+ const candidates = insertionAnchorCandidates(insertion);
166
+ for (const candidate of candidates) {
167
+ const symbol = insertionAnchorSymbol(candidate, context.symbols);
168
+ const range = spanOffsets(sourceText, symbol?.sourceSpan);
169
+ if (range) return { mode: candidate.mode, range };
170
+ }
171
+ for (const candidate of candidates) {
172
+ const range = spanOffsets(sourceText, candidate.headSpan);
173
+ if (range) return { mode: candidate.mode, range };
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ function insertionAnchorCandidates(insertion) {
179
+ const seen = new Set();
180
+ const result = [];
181
+ for (const candidate of [insertion, ...(Array.isArray(insertion?.anchorCandidates) ? insertion.anchorCandidates : [])]) {
182
+ if (!candidate || (candidate.mode !== 'before' && candidate.mode !== 'after')) continue;
183
+ const key = [candidate.mode, candidate.anchorKey, candidate.anchorSymbolId, candidate.anchorSymbolName, candidate.anchorSymbolKind].join('\0');
184
+ if (seen.has(key)) continue;
185
+ seen.add(key);
186
+ result.push(candidate);
187
+ }
188
+ return result;
189
+ }
190
+
191
+ function insertionAnchorSymbol(candidate, symbols) {
192
+ const symbolList = Array.isArray(symbols)
193
+ ? symbols
194
+ : symbols?.values
195
+ ? [...symbols.values()]
196
+ : [];
197
+ const keys = [candidate.anchorKey, candidate.anchorSymbolId].filter(Boolean);
198
+ const exact = symbolList.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && keys.includes(key)));
199
+ if (exact) return exact;
200
+ return symbolList.find((symbol) => symbol.name === candidate.anchorSymbolName && (!candidate.anchorSymbolKind || symbol.kind === candidate.anchorSymbolKind));
201
+ }
202
+
203
+ function trailingBodyCloseOffset(sourceText, range) {
204
+ if (typeof sourceText !== 'string' || !range) return undefined;
205
+ let index = range.end - 1;
206
+ index = previousCodeOffset(sourceText, index, range.start);
207
+ if (sourceText[index] === ';' || sourceText[index] === ',') {
208
+ index -= 1;
209
+ index = previousCodeOffset(sourceText, index, range.start);
210
+ }
211
+ return sourceText[index] === '}' ? index : undefined;
212
+ }
213
+
214
+ function previousCodeOffset(sourceText, index, minIndex) {
215
+ let cursor = index;
216
+ while (cursor >= minIndex) {
217
+ while (cursor >= minIndex && /\s/.test(sourceText[cursor])) cursor -= 1;
218
+ const blockStart = sourceText.lastIndexOf('/*', cursor);
219
+ if (sourceText[cursor] === '/' && sourceText[cursor - 1] === '*' && blockStart >= minIndex) {
220
+ cursor = blockStart - 1;
221
+ continue;
222
+ }
223
+ const lineStart = Math.max(minIndex, sourceText.lastIndexOf('\n', cursor) + 1);
224
+ const lineComment = sourceText.lastIndexOf('//', cursor);
225
+ if (lineComment >= lineStart) {
226
+ cursor = lineComment - 1;
227
+ continue;
228
+ }
229
+ return cursor;
230
+ }
231
+ return cursor;
232
+ }
233
+
234
+ function bracePairs(sourceText, range) {
235
+ if (typeof sourceText !== 'string' || !range || range.end <= range.start) return [];
236
+ const stack = [];
237
+ const pairs = [];
238
+ let quote;
239
+ let escaped = false;
240
+ let lineComment = false;
241
+ let blockComment = false;
242
+ for (let index = range.start; index < range.end; index += 1) {
243
+ const char = sourceText[index];
244
+ const next = sourceText[index + 1];
245
+ if (lineComment) {
246
+ if (char === '\n' || char === '\r') lineComment = false;
247
+ continue;
248
+ }
249
+ if (blockComment) {
250
+ if (char === '*' && next === '/') {
251
+ blockComment = false;
252
+ index += 1;
253
+ }
254
+ continue;
255
+ }
256
+ if (quote) {
257
+ if (escaped) escaped = false;
258
+ else if (char === '\\') escaped = true;
259
+ else if (char === quote) quote = undefined;
260
+ continue;
261
+ }
262
+ if (char === '/' && next === '/') {
263
+ lineComment = true;
264
+ index += 1;
265
+ continue;
266
+ }
267
+ if (char === '/' && next === '*') {
268
+ blockComment = true;
269
+ index += 1;
270
+ continue;
271
+ }
272
+ if (char === '\'' || char === '"' || char === '`') {
273
+ quote = char;
274
+ continue;
275
+ }
276
+ if (char === '{') stack.push(index);
277
+ else if (char === '}') {
278
+ const open = stack.pop();
279
+ if (open !== undefined) pairs.push({ open, close: index });
280
+ }
281
+ }
282
+ return pairs;
283
+ }
@@ -22,7 +22,11 @@ export function resolveSemanticHistoryRecordLineage(record = {}, eventsOrMap = [
22
22
  semanticAnchorKeys: resolveIndexKeys(sourceIndex.semanticAnchorKeys, byAnchorKey, options),
23
23
  sourcePaths: uniqueStrings([
24
24
  ...array(sourceIndex.sourcePaths),
25
- ...resolutions.flatMap((resolution) => resolution.currentAnchors.map((anchor) => anchor.sourcePath))
25
+ ...resolutions.flatMap((resolution) => resolutionSourcePaths(resolution))
26
+ ]),
27
+ lineageResolutionIds: uniqueStrings([
28
+ ...array(sourceIndex.lineageResolutionIds),
29
+ ...resolutions.map((resolution) => resolution.id)
26
30
  ]),
27
31
  lineageEventIds: uniqueStrings([
28
32
  ...array(sourceIndex.lineageEventIds),
@@ -105,6 +109,13 @@ function createResolvedHistoryRecord(record, index, resolution) {
105
109
  inactiveAnchorKeys: resolution.summary.inactiveAnchorKeys,
106
110
  blockedAnchorKeys: resolution.summary.blockedAnchorKeys
107
111
  },
112
+ lineageResolutionIds: resolution.summary.lineageResolutionIds,
113
+ lineageEventIds: resolution.summary.traversedEventIds,
114
+ terminalEventIds: resolution.summary.terminalEventIds,
115
+ sourcePaths: resolution.summary.sourcePaths,
116
+ evidenceIds: resolution.summary.evidenceIds,
117
+ proofIds: resolution.summary.proofIds,
118
+ reasonCodes: resolution.summary.reasonCodes,
108
119
  autoMergeClaim: false,
109
120
  semanticEquivalenceClaim: false
110
121
  }
@@ -126,12 +137,17 @@ function resolveIndexKeys(values, resolutions, options) {
126
137
  const key = String(value);
127
138
  const resolution = resolutions.get(key);
128
139
  if (!resolution) return [key];
140
+ const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
129
141
  if (resolution.status === 'deleted' && options.keepDeletedAnchors !== true) return [];
130
142
  if (resolution.status === 'cycle' && options.keepBlockedAnchors !== true) return [];
131
143
  if (resolution.status === 'max-depth' && options.keepBlockedAnchors !== true) return [];
132
144
  if (resolution.status === 'not-found' && options.keepUnresolvedAnchors !== true) return [];
145
+ if (resolution.status === 'ambiguous' && resolutionHasInactiveTerminal(resolution)) {
146
+ if (options.keepCandidateAnchors === true) return current.length ? current : [key];
147
+ if (options.keepDeletedAnchors === true || options.keepInactiveAnchors === true) return [key];
148
+ return [];
149
+ }
133
150
  if (resolution.status === 'ambiguous' && options.keepCandidateAnchors === false) return [];
134
- const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
135
151
  return current.length ? current : [key];
136
152
  }));
137
153
  }
@@ -159,8 +175,11 @@ function summarizeSemanticHistoryLineageResolutions(resolutions, index, anchorIn
159
175
  deletedAnchorKeys: uniqueStrings(anchorInventory.deleted.map((anchor) => anchor.key)),
160
176
  unresolvedAnchorKeys: uniqueStrings(anchorInventory.unresolved.map((anchor) => anchor.key)),
161
177
  blockedAnchorKeys: uniqueStrings(anchorInventory.blocked.map((anchor) => anchor.key)),
178
+ lineageResolutionIds: uniqueStrings(resolutions.map((resolution) => resolution.id)),
162
179
  traversedEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.traversedEventIds)),
163
180
  terminalEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.terminalEventIds)),
181
+ evidenceIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.evidenceIds)),
182
+ proofIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.proofIds)),
164
183
  reasonCodes: uniqueStrings(resolutions.flatMap((resolution) => resolution.reasonCodes))
165
184
  };
166
185
  }
@@ -179,7 +198,10 @@ function createAnchorInventory(resolutions) {
179
198
  const current = resolution.currentAnchors.map((anchor) => anchorEntry(anchor, resolution));
180
199
  if (resolution.status === 'ambiguous') {
181
200
  inventory.candidate.push(...current);
182
- if (start) inventory.inactive.push(start);
201
+ if (start) {
202
+ inventory.inactive.push(start);
203
+ if (resolutionHasDeletedTerminal(resolution)) inventory.deleted.push(start);
204
+ }
183
205
  continue;
184
206
  }
185
207
  if (resolution.status === 'deleted') {
@@ -225,12 +247,32 @@ function anchorEntry(anchor, resolution) {
225
247
  sourcePath: anchor.sourcePath,
226
248
  symbolId: anchor.symbolId,
227
249
  symbolName: anchor.symbolName,
250
+ sourcePaths: resolutionSourcePaths(resolution, anchor),
251
+ lineageEventIds: uniqueStrings(resolution.traversedEventIds),
252
+ terminalEventIds: uniqueStrings(resolution.terminalEventIds),
253
+ evidenceIds: uniqueStrings(resolution.evidenceIds),
254
+ proofIds: uniqueStrings(resolution.proofIds),
255
+ crdtOperationIds: uniqueStrings(resolution.crdtOperationIds),
256
+ crdtHeads: uniqueStrings(resolution.crdtHeads),
257
+ lineageEventKinds: uniqueStrings(resolution.lineageEventKinds),
228
258
  status: resolution.status,
229
259
  resolutionId: resolution.id,
260
+ confidence: resolution.confidence,
230
261
  reasonCodes: uniqueStrings(resolution.reasonCodes)
231
262
  });
232
263
  }
233
264
 
265
+ function resolutionHasInactiveTerminal(resolution) {
266
+ return array(resolution.terminalEventIds).length > 0
267
+ || resolutionHasDeletedTerminal(resolution)
268
+ || array(resolution.reasonCodes).some((code) => code === 'lineage-event-without-target-anchor' || code === 'inactive-anchor-has-active-candidates');
269
+ }
270
+
271
+ function resolutionHasDeletedTerminal(resolution) {
272
+ return array(resolution.lineageEventKinds).includes('deleted')
273
+ || array(resolution.reasonCodes).includes('anchor-deleted');
274
+ }
275
+
234
276
  function queryAnchor(resolution) {
235
277
  return compactRecord({
236
278
  key: resolution.query?.anchorKey,
@@ -250,6 +292,17 @@ function uniqueAnchorEntries(entries) {
250
292
  });
251
293
  }
252
294
 
295
+ function resolutionSourcePaths(resolution, anchor) {
296
+ return uniqueStrings([
297
+ anchor?.sourcePath,
298
+ ...array(anchor?.lineageSourcePaths),
299
+ resolution.query?.sourcePath,
300
+ resolution.startAnchor?.sourcePath,
301
+ ...array(resolution.sourcePaths),
302
+ ...resolution.currentAnchors.flatMap((entry) => [entry.sourcePath, ...array(entry.lineageSourcePaths)])
303
+ ]);
304
+ }
305
+
253
306
  function array(value) { return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value]; }
254
307
  function uniqueStrings(values) { return uniqueRawStrings(array(values).map((value) => String(value ?? '')).filter(Boolean)); }
255
308
  function firstString(...values) { return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean); }
@@ -1,4 +1,4 @@
1
- import{idFragment}from'../../native-import-utils.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
1
+ import{idFragment,caseSensitiveIdFragment}from'../../native-import-utils.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
2
2
  import{relationPredicateForDeclaration}from'./relationPredicateForDeclaration.js';
3
3
  export function semanticIndexFromNativeDeclarations(declarations, input, options) {
4
4
  const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
@@ -9,7 +9,7 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
9
9
  const facts = [];
10
10
  const mappings = [];
11
11
  for (const declaration of declarations) {
12
- const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${idFragment(declaration.name)}`;
12
+ const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${caseSensitiveIdFragment(declaration.name)}`;
13
13
  const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
14
14
  const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
15
15
  ...declaration,
@@ -0,0 +1,97 @@
1
+ import { uniqueStrings } from '../../native-import-utils.js';
2
+
3
+ export function addIdentityHashEvidence(before, after, add, note) {
4
+ const matches = matchingIdentityHashReasons(before, after);
5
+ if (matches.length === 0) return;
6
+ if (!compatibleLineageSurface(before, after)) {
7
+ note('identity-hash-match-surface-mismatch');
8
+ return;
9
+ }
10
+ const primary = matches.includes('semantic-identity-hash-match')
11
+ ? 'semantic-identity-hash-match'
12
+ : matches.includes('source-identity-hash-match')
13
+ ? 'source-identity-hash-match'
14
+ : 'identity-hash-match';
15
+ add(0.62, primary);
16
+ for (const reason of matches) {
17
+ if (reason !== primary) note(reason);
18
+ }
19
+ }
20
+
21
+ export function addSourceHashEvidence(before, after, add, note, reasons, sameSymbolSurface) {
22
+ const beforeHash = firstString(before.anchor.sourceHash, before.sourceHash);
23
+ const afterHash = firstString(after.anchor.sourceHash, after.sourceHash);
24
+ if (!beforeHash || !afterHash) return;
25
+ if (beforeHash !== afterHash) {
26
+ note('source-hash-changed');
27
+ return;
28
+ }
29
+ if (!hasSourceHashSupport(before, after, reasons, sameSymbolSurface)) {
30
+ note('source-hash-match-without-lineage-support');
31
+ return;
32
+ }
33
+ add(0.04, 'source-hash-match');
34
+ if (before.anchor.sourcePath && after.anchor.sourcePath && before.anchor.sourcePath !== after.anchor.sourcePath) {
35
+ note('source-hash-preserved-across-path');
36
+ }
37
+ }
38
+
39
+ export function hashEvidenceSummary(reasons) {
40
+ return {
41
+ semanticIdentityHashMatch: reasons.includes('semantic-identity-hash-match'),
42
+ sourceIdentityHashMatch: reasons.includes('source-identity-hash-match'),
43
+ identityHashMatch: reasons.includes('identity-hash-match'),
44
+ sourceHashMatch: reasons.includes('source-hash-match'),
45
+ signatureHashMatch: reasons.includes('signature-hash-match'),
46
+ bodyHashMatch: reasons.includes('body-hash-match')
47
+ };
48
+ }
49
+
50
+ function matchingIdentityHashReasons(before, after) {
51
+ const beforeHashes = identityHashEntries(before);
52
+ const afterHashes = new Map(identityHashEntries(after).map((entry) => [entry.value, entry.reason]));
53
+ const reasons = [];
54
+ for (const entry of beforeHashes) {
55
+ const afterReason = afterHashes.get(entry.value);
56
+ if (!afterReason) continue;
57
+ reasons.push(identityHashMatchReason(entry.reason, afterReason));
58
+ }
59
+ return uniqueStrings(reasons);
60
+ }
61
+
62
+ function identityHashEntries(symbol) {
63
+ return [
64
+ { reason: 'semantic-identity-hash-match', value: firstString(symbol.semanticIdentityHash, symbol.anchor.metadata?.semanticIdentityHash) },
65
+ { reason: 'source-identity-hash-match', value: firstString(symbol.sourceIdentityHash, symbol.anchor.metadata?.sourceIdentityHash) },
66
+ { reason: 'identity-hash-match', value: firstString(symbol.identityHash, symbol.anchor.metadata?.identityHash) }
67
+ ].filter((entry) => entry.value);
68
+ }
69
+
70
+ function identityHashMatchReason(beforeReason, afterReason) {
71
+ if (beforeReason === afterReason) return beforeReason;
72
+ if (beforeReason === 'semantic-identity-hash-match' || afterReason === 'semantic-identity-hash-match') return 'semantic-identity-hash-match';
73
+ if (beforeReason === 'source-identity-hash-match' || afterReason === 'source-identity-hash-match') return 'source-identity-hash-match';
74
+ return 'identity-hash-match';
75
+ }
76
+
77
+ function compatibleLineageSurface(before, after) {
78
+ return (!before.language || !after.language || before.language === after.language)
79
+ && (!before.kind || !after.kind || before.kind === after.kind)
80
+ && (!before.anchor.kind || !after.anchor.kind || before.anchor.kind === after.anchor.kind);
81
+ }
82
+
83
+ function hasSourceHashSupport(before, after, reasons, sameSymbolSurface) {
84
+ return reasons.some((reason) => [
85
+ 'semantic-identity-hash-match',
86
+ 'source-identity-hash-match',
87
+ 'identity-hash-match',
88
+ 'signature-hash-match',
89
+ 'body-hash-match',
90
+ 'symbol-name-match'
91
+ ].includes(reason))
92
+ || sameSymbolSurface(before, after);
93
+ }
94
+
95
+ function firstString(...values) {
96
+ return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
97
+ }