@shapeshift-labs/frontier-lang-compiler 0.2.10 → 0.2.11

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/dist/index.d.ts CHANGED
@@ -112,6 +112,7 @@ export type NativeImportKnownLossKind =
112
112
  | 'parserDiagnostic'
113
113
  | 'unsupportedSyntax'
114
114
  | 'unsupportedSemantic'
115
+ | 'unverifiedNativeAst'
115
116
  | 'partialSemanticIndex'
116
117
  | 'sourceMapApproximation'
117
118
  | 'targetProjectionLoss'
@@ -511,6 +512,7 @@ export interface ImportNativeSourceOptions {
511
512
  readonly sourceMaps?: readonly SourceMapRecord[];
512
513
  readonly universalAstId?: string;
513
514
  readonly universalAstMetadata?: Record<string, unknown>;
515
+ readonly exactAst?: boolean;
514
516
  readonly metadata?: Record<string, unknown>;
515
517
  }
516
518
 
@@ -724,7 +726,62 @@ export interface NativeSourceProjectionResult {
724
726
  readonly metadata: Record<string, unknown>;
725
727
  }
726
728
 
729
+ export type NativeImportRoundtripReadinessStatus =
730
+ | 'exact'
731
+ | 'preserved-source'
732
+ | 'stub-only'
733
+ | 'blocked'
734
+ | 'needs-review';
735
+
736
+ export interface NativeImportRoundtripReadinessOptions extends ProjectNativeImportToSourceOptions {
737
+ readonly projection?: NativeSourceProjectionResult;
738
+ }
739
+
740
+ export interface NativeImportRoundtripReadinessClassification {
741
+ readonly kind: 'frontier.lang.nativeImportRoundtripReadiness';
742
+ readonly version: 1;
743
+ readonly status: NativeImportRoundtripReadinessStatus;
744
+ readonly semanticMergeReadiness: SemanticMergeReadiness;
745
+ readonly reasons: readonly string[];
746
+ readonly importReadiness: NativeImportReadinessClassification;
747
+ readonly projectionReadiness: NativeImportReadinessClassification;
748
+ readonly projectionMode: NativeSourceProjectionMode;
749
+ readonly checks: {
750
+ readonly nativeImport: {
751
+ readonly imports: number;
752
+ readonly exactAst: boolean;
753
+ readonly losses: number;
754
+ readonly readiness: SemanticMergeReadiness;
755
+ };
756
+ readonly universalAst: {
757
+ readonly present: boolean;
758
+ readonly valid: boolean;
759
+ readonly issues: readonly string[];
760
+ readonly nativeSources: number;
761
+ readonly semanticSymbols: number;
762
+ readonly sourceMaps: number;
763
+ readonly sourceMapMappings: number;
764
+ };
765
+ readonly projectedSource: {
766
+ readonly mode: NativeSourceProjectionMode;
767
+ readonly outputHash: string;
768
+ readonly expectedSourceHash?: string;
769
+ readonly sourceHashVerified: boolean;
770
+ readonly declarations: number;
771
+ readonly losses: number;
772
+ readonly readiness: SemanticMergeReadiness;
773
+ };
774
+ };
775
+ readonly evidence: {
776
+ readonly importEvidenceIds: readonly string[];
777
+ readonly projectionEvidenceIds: readonly string[];
778
+ readonly failedEvidenceIds: readonly string[];
779
+ };
780
+ readonly metadata: Record<string, unknown>;
781
+ }
782
+
727
783
  export declare const FrontierCompileTargets: readonly FrontierCompileTarget[];
784
+ export declare const NativeImportRoundtripReadinessStatuses: readonly NativeImportRoundtripReadinessStatus[];
728
785
  export declare const NativeImportTaxonomyKinds: readonly NativeImportTaxonomyKind[];
729
786
  export declare const NativeImportLossKinds: readonly NativeImportKnownLossKind[];
730
787
  export declare const NativeImportReadinessBySeverity: Readonly<Record<NativeImportLossSummary['highestSeverity'], SemanticMergeReadiness>>;
@@ -737,6 +794,7 @@ export declare function renderTargetAst(ast: FrontierTargetAst, target?: Frontie
737
794
  export declare function resolveCapabilityAdapters(document: FrontierLangDocument, target?: FrontierCompileOptions['target'], options?: { readonly platform?: string }): readonly CapabilityResolution[];
738
795
  export declare function summarizeNativeImportLosses(losses?: readonly NativeAstLossRecord[], options?: NativeImportLossSummaryOptions): NativeImportLossSummary;
739
796
  export declare function classifyNativeImportReadiness(losses?: readonly NativeAstLossRecord[], options?: NativeImportLossSummaryOptions): NativeImportReadinessClassification;
797
+ export declare function classifyNativeImportRoundtripReadiness(importResult: NativeSourceImportResult | NativeProjectImportResult, options?: NativeImportRoundtripReadinessOptions): NativeImportRoundtripReadinessClassification;
740
798
  export declare function createNativeImportCoverageMatrix(options?: NativeImportCoverageMatrixOptions): NativeImportCoverageMatrix;
741
799
  export declare function createNativeSourcePreservation(options: CreateNativeSourcePreservationOptions): NativeSourcePreservation;
742
800
  export declare function createSemanticImportSidecar(importResult: NativeSourceImportResult | NativeProjectImportResult, options?: SemanticImportSidecarOptions): SemanticImportSidecar;
package/dist/index.js CHANGED
@@ -67,6 +67,14 @@ const semanticMergeReadinessRank = Object.freeze({
67
67
  blocked: 3
68
68
  });
69
69
 
70
+ export const NativeImportRoundtripReadinessStatuses = Object.freeze([
71
+ 'exact',
72
+ 'preserved-source',
73
+ 'stub-only',
74
+ 'blocked',
75
+ 'needs-review'
76
+ ]);
77
+
70
78
  export const NativeImportTaxonomyKinds = Object.freeze([
71
79
  'exactAstImport',
72
80
  'declarationsOnly',
@@ -106,6 +114,7 @@ export const NativeImportLossKinds = Object.freeze([
106
114
  'parserDiagnostic',
107
115
  'unsupportedSyntax',
108
116
  'unsupportedSemantic',
117
+ 'unverifiedNativeAst',
109
118
  'partialSemanticIndex',
110
119
  'sourceMapApproximation',
111
120
  'targetProjectionLoss'
@@ -327,6 +336,134 @@ export function classifyNativeImportReadiness(losses = [], options = {}) {
327
336
  };
328
337
  }
329
338
 
339
+ export function classifyNativeImportRoundtripReadiness(importResult, options = {}) {
340
+ if (!importResult || typeof importResult !== 'object') {
341
+ throw new Error('classifyNativeImportRoundtripReadiness requires a native import result');
342
+ }
343
+ const imports = nativeImportEntries(importResult);
344
+ const importLosses = uniqueByLossId([
345
+ ...(importResult.losses ?? []),
346
+ ...imports.flatMap((imported) => imported?.losses ?? [])
347
+ ]);
348
+ const importEvidence = uniqueByEvidenceId([
349
+ ...(importResult.evidence ?? []),
350
+ ...imports.flatMap((imported) => imported?.evidence ?? [])
351
+ ]);
352
+ const exactAst = imports.length > 0 && imports.every((imported) => nativeImportHasExactAstCoverage(imported));
353
+ const importReadiness = classifyNativeImportReadiness(importLosses, {
354
+ exactAst,
355
+ evidence: importEvidence,
356
+ parser: nativeImportRoundtripParser(importResult, imports),
357
+ scanKind: importResult.metadata?.nativeImportLossSummary?.scanKind,
358
+ semanticStatus: importResult.metadata?.semanticStatus ?? importResult.universalAst?.metadata?.semanticStatus
359
+ });
360
+ const projection = options.projection ?? projectNativeImportToSource(importResult, options);
361
+ const projectionReadiness = projection.readiness ?? classifyNativeImportReadiness(projection.losses ?? [], {
362
+ evidence: projection.evidence,
363
+ parser: projection.metadata?.nativeImportLossSummary?.parser,
364
+ scanKind: 'native-source-projection'
365
+ });
366
+ const universalAst = importResult.universalAst;
367
+ const universalAstIssues = universalAst
368
+ ? validateUniversalAstEnvelope(universalAst)
369
+ : ['missing-universal-ast'];
370
+ const universalAstNativeSources = universalAst?.nativeSources?.length ?? importResult.nativeSources?.length ?? (importResult.nativeSource ? 1 : 0);
371
+ const semanticIndex = importResult.semanticIndex ?? universalAst?.semanticIndex;
372
+ const semanticSymbols = semanticIndex?.symbols?.length ?? 0;
373
+ const sourceMaps = importResult.sourceMaps ?? universalAst?.sourceMaps ?? [];
374
+ const sourceMapMappings = sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap?.mappings?.length ?? 0), 0);
375
+ const projectionMatchesSourceHash = Boolean(projection.sourceHash && projection.outputHash === projection.sourceHash);
376
+ const preservedSource = projection.mode === 'preserved-source';
377
+ const failedEvidenceIds = uniqueStrings([
378
+ ...importEvidence.filter((record) => record?.status === 'failed').map((record) => record.id),
379
+ ...(projection.evidence ?? []).filter((record) => record?.status === 'failed').map((record) => record.id)
380
+ ]);
381
+ const blockingReasons = [
382
+ ...(importReadiness.readiness === 'blocked' ? importReadiness.reasons : []),
383
+ ...(projectionReadiness.readiness === 'blocked' ? projectionReadiness.reasons : []),
384
+ ...(failedEvidenceIds.length ? [`Failed evidence prevents native roundtrip readiness: ${failedEvidenceIds.join(', ')}`] : []),
385
+ ...(universalAstIssues.length ? [`Universal AST validation failed: ${universalAstIssues.join('; ')}`] : [])
386
+ ];
387
+ const reviewReasons = [
388
+ ...(semanticSymbols === 0 ? ['Universal AST semantic index has no symbols for source projection review.'] : []),
389
+ ...(sourceMapMappings === 0 ? ['Universal AST has no native source-map mappings for roundtrip review.'] : []),
390
+ ...(preservedSource && !projectionMatchesSourceHash ? ['Projected source was preserved without a verified import source hash match.'] : []),
391
+ ...importReadiness.reasons.filter((reason) => importReadiness.readiness !== 'ready' || !exactAst),
392
+ ...projectionReadiness.reasons.filter((reason) => projectionReadiness.readiness !== 'ready')
393
+ ];
394
+ let status;
395
+ if (blockingReasons.length) {
396
+ status = 'blocked';
397
+ } else if (projection.mode === 'native-source-stubs') {
398
+ status = 'stub-only';
399
+ } else if (reviewReasons.some((reason) => reason.startsWith('Universal AST')) || (preservedSource && !projectionMatchesSourceHash)) {
400
+ status = 'needs-review';
401
+ } else if (exactAst && preservedSource && projectionMatchesSourceHash && projectionReadiness.readiness === 'ready') {
402
+ status = 'exact';
403
+ } else if (preservedSource && projectionMatchesSourceHash) {
404
+ status = 'preserved-source';
405
+ } else {
406
+ status = 'needs-review';
407
+ }
408
+ const reasons = nativeImportRoundtripReasons(status, {
409
+ blockingReasons,
410
+ reviewReasons,
411
+ projection,
412
+ importReadiness,
413
+ projectionReadiness
414
+ });
415
+ return {
416
+ kind: 'frontier.lang.nativeImportRoundtripReadiness',
417
+ version: 1,
418
+ status,
419
+ semanticMergeReadiness: maxSemanticMergeReadiness(importReadiness.readiness, projectionReadiness.readiness),
420
+ reasons,
421
+ importReadiness,
422
+ projectionReadiness,
423
+ projectionMode: projection.mode,
424
+ checks: {
425
+ nativeImport: {
426
+ imports: imports.length,
427
+ exactAst,
428
+ losses: importReadiness.summary.total,
429
+ readiness: importReadiness.readiness
430
+ },
431
+ universalAst: {
432
+ present: Boolean(universalAst),
433
+ valid: universalAstIssues.length === 0,
434
+ issues: universalAstIssues,
435
+ nativeSources: universalAstNativeSources,
436
+ semanticSymbols,
437
+ sourceMaps: sourceMaps.length,
438
+ sourceMapMappings
439
+ },
440
+ projectedSource: {
441
+ mode: projection.mode,
442
+ outputHash: projection.outputHash,
443
+ expectedSourceHash: projection.sourceHash,
444
+ sourceHashVerified: projectionMatchesSourceHash,
445
+ declarations: projection.declarations?.length ?? 0,
446
+ losses: projection.lossSummary?.total ?? projection.losses?.length ?? 0,
447
+ readiness: projectionReadiness.readiness
448
+ }
449
+ },
450
+ evidence: {
451
+ importEvidenceIds: importEvidence.map((record) => record.id).filter(Boolean),
452
+ projectionEvidenceIds: (projection.evidence ?? []).map((record) => record.id).filter(Boolean),
453
+ failedEvidenceIds
454
+ },
455
+ metadata: {
456
+ nativeImportId: importResult.id,
457
+ universalAstId: universalAst?.id,
458
+ projectionId: projection.id,
459
+ sourcePath: projection.sourcePath ?? importResult.sourcePath,
460
+ language: projection.language ?? importResult.language,
461
+ sourcePreservationId: projection.metadata?.sourcePreservationId,
462
+ ...options.metadata
463
+ }
464
+ };
465
+ }
466
+
330
467
  export function createNativeImportCoverageMatrix(input = {}) {
331
468
  const imports = input.imports ?? [];
332
469
  const adapters = input.adapters ?? [];
@@ -917,7 +1054,17 @@ export function importNativeSource(input) {
917
1054
  const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
918
1055
  const semanticNodes = input.semanticNodes ?? [];
919
1056
  const semanticStatus = input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only');
920
- const losses = normalizeNativeLossRecords(input.losses ?? nativeAst.losses ?? lightweight?.losses ?? []);
1057
+ const nativeAstExact = hasNativeExactAstEvidence(input, nativeAst, lightweight);
1058
+ const baseLosses = normalizeNativeLossRecords(input.losses ?? nativeAst.losses ?? lightweight?.losses ?? []);
1059
+ const losses = normalizeNativeLossRecords([
1060
+ ...baseLosses,
1061
+ ...unverifiedNativeAstLosses(input, nativeAst, {
1062
+ importIdPart,
1063
+ exactAst: nativeAstExact,
1064
+ hasLosses: baseLosses.length > 0,
1065
+ lightweight
1066
+ })
1067
+ ]);
921
1068
  const nativeSource = nativeSourceNode({
922
1069
  id: input.nativeSourceId ?? `native_source_${importIdPart}`,
923
1070
  name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
@@ -978,7 +1125,7 @@ export function importNativeSource(input) {
978
1125
  }
979
1126
  }];
980
1127
  const lossSummary = summarizeNativeImportLosses(losses, {
981
- exactAst: Boolean(input.nativeAst || input.nodes),
1128
+ exactAst: nativeAstExact,
982
1129
  evidence: baseEvidence,
983
1130
  parser: nativeAst.parser,
984
1131
  scanKind: lightweight?.metadata?.scanKind,
@@ -3106,7 +3253,7 @@ function nativeImportCategoryForLossKind(kind) {
3106
3253
  if (kind === 'sourcePreservation' || kind === 'commentsTrivia' || kind === 'nonRoundTrippable') return 'sourcePreservation';
3107
3254
  if (kind === 'parserDiagnostic') return 'parserDiagnostics';
3108
3255
  if (kind === 'unsupportedSyntax' || kind === 'unsupportedSemantic') return 'unsupportedSyntax';
3109
- if (kind === 'partialSemanticIndex') return 'partialSemanticIndex';
3256
+ if (kind === 'partialSemanticIndex' || kind === 'unverifiedNativeAst') return 'partialSemanticIndex';
3110
3257
  if (kind === 'sourceMapApproximation') return 'sourceMapApproximation';
3111
3258
  if (kind === 'targetProjectionLoss') return 'targetProjectionLoss';
3112
3259
  return String(kind ?? 'opaqueNative');
@@ -3392,6 +3539,36 @@ function attachNativeImportLossSummary(evidence, lossSummary) {
3392
3539
  }));
3393
3540
  }
3394
3541
 
3542
+ function hasNativeExactAstEvidence(input, nativeAst, lightweight) {
3543
+ if (lightweight) return false;
3544
+ if (!(input?.nativeAst || input?.nodes)) return false;
3545
+ if (input.exactAst === true || input.metadata?.exactAst === true || input.nativeAstMetadata?.exactAst === true) return true;
3546
+ const coverage = input.metadata?.adapterCoverage
3547
+ ?? input.nativeAstMetadata?.adapterCoverage
3548
+ ?? input.nativeSourceMetadata?.adapterCoverage
3549
+ ?? nativeAst?.metadata?.adapterCoverage;
3550
+ if (coverage?.exactAst !== true) return false;
3551
+ const observedNodes = coverage.observed?.nativeAstNodes;
3552
+ return observedNodes === undefined || observedNodes > 0;
3553
+ }
3554
+
3555
+ function unverifiedNativeAstLosses(input, nativeAst, context) {
3556
+ if (context.lightweight || context.exactAst || context.hasLosses) return [];
3557
+ if (!(input?.nativeAst || input?.nodes)) return [];
3558
+ return [{
3559
+ id: `loss_${context.importIdPart}_unverified_native_ast`,
3560
+ severity: 'warning',
3561
+ kind: 'unverifiedNativeAst',
3562
+ nodeId: nativeAst?.rootId,
3563
+ message: 'Caller supplied native AST nodes without explicit exactAst or adapter coverage evidence.',
3564
+ metadata: {
3565
+ reason: 'missing-exact-ast-evidence',
3566
+ nativeAstId: nativeAst?.id,
3567
+ parser: nativeAst?.parser
3568
+ }
3569
+ }];
3570
+ }
3571
+
3395
3572
  function withNativeImportReadiness(importResult, lossSummary) {
3396
3573
  const mergeCandidates = (importResult.mergeCandidates ?? []).map((candidate) => {
3397
3574
  const readiness = maxSemanticMergeReadiness(candidate.readiness, lossSummary.semanticMergeReadiness);
@@ -3427,6 +3604,49 @@ function withNativeImportReadiness(importResult, lossSummary) {
3427
3604
  };
3428
3605
  }
3429
3606
 
3607
+ function nativeImportEntries(importResult) {
3608
+ if (Array.isArray(importResult?.imports)) return importResult.imports.filter(Boolean);
3609
+ return [importResult].filter(Boolean);
3610
+ }
3611
+
3612
+ function nativeImportHasExactAstCoverage(imported) {
3613
+ if (imported?.metadata?.nativeImportLossSummary?.exactAst === true) return true;
3614
+ if (imported?.adapter?.coverage?.exactAst === true && !(imported?.losses?.length)) return true;
3615
+ return false;
3616
+ }
3617
+
3618
+ function nativeImportRoundtripParser(importResult, imports) {
3619
+ const parsers = uniqueStrings([
3620
+ importResult.nativeAst?.parser,
3621
+ importResult.nativeSource?.parser,
3622
+ importResult.metadata?.parser,
3623
+ ...imports.map((imported) => imported?.nativeAst?.parser ?? imported?.nativeSource?.parser ?? imported?.metadata?.parser)
3624
+ ].filter(Boolean));
3625
+ return parsers.length === 1 ? parsers[0] : parsers.length ? parsers.join(',') : undefined;
3626
+ }
3627
+
3628
+ function nativeImportRoundtripReasons(status, input) {
3629
+ if (status === 'blocked') return uniqueStrings(input.blockingReasons);
3630
+ if (status === 'stub-only') {
3631
+ return uniqueStrings([
3632
+ `Native source projection emitted declaration stubs in ${input.projection.mode} mode.`,
3633
+ ...input.projectionReadiness.reasons,
3634
+ ...input.importReadiness.reasons.filter((reason) => input.importReadiness.readiness !== 'ready')
3635
+ ]);
3636
+ }
3637
+ if (status === 'needs-review') return uniqueStrings(input.reviewReasons);
3638
+ if (status === 'exact') {
3639
+ return ['Exact native AST import and verified preserved source projection are available.'];
3640
+ }
3641
+ if (status === 'preserved-source') {
3642
+ return uniqueStrings([
3643
+ 'Verified native source text is preserved for projection; semantic import evidence may still require review.',
3644
+ ...input.importReadiness.reasons.filter((reason) => input.importReadiness.readiness !== 'ready')
3645
+ ]);
3646
+ }
3647
+ return ['Native import roundtrip readiness requires review.'];
3648
+ }
3649
+
3430
3650
  function maxSemanticMergeReadiness(left, right) {
3431
3651
  const leftRank = semanticMergeReadinessRank[left] ?? semanticMergeReadinessRank['needs-review'];
3432
3652
  const rightRank = semanticMergeReadinessRank[right] ?? semanticMergeReadinessRank['needs-review'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",