@shapeshift-labs/frontier-lang-compiler 0.2.21 → 0.2.22
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 +19 -0
- package/bench/smoke.mjs +11 -1
- package/dist/index.d.ts +47 -0
- package/dist/index.js +241 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,25 @@ console.log(readiness.readiness);
|
|
|
64
64
|
|
|
65
65
|
The loss taxonomy separates broad scanner limits from specific round-trip risks such as conditional compilation, reflection, overload/type-inference gaps, comments/trivia preservation, source-map approximation, parser diagnostics, and target projection loss. These records are evidence labels for merge admission; they are not claims that the lightweight scanner expanded macros, evaluated inactive branches, resolved overloads, or ran a type checker.
|
|
66
66
|
|
|
67
|
+
High-risk native features also have explicit evidence policies. These policies are advisory in this package: they tell a swarm or admission queue what evidence is missing without silently changing the existing readiness classification.
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import {
|
|
71
|
+
getNativeImportFeatureEvidencePolicy,
|
|
72
|
+
summarizeNativeImportFeatureEvidence
|
|
73
|
+
} from '@shapeshift-labs/frontier-lang-compiler';
|
|
74
|
+
|
|
75
|
+
const policy = getNativeImportFeatureEvidencePolicy('preprocessor');
|
|
76
|
+
console.log(policy.requiredEvidenceKeys); // ["preprocessedOutputHash", "definesHash"]
|
|
77
|
+
|
|
78
|
+
const featureEvidence = summarizeNativeImportFeatureEvidence(imported.losses, {
|
|
79
|
+
evidence: imported.evidence
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log(featureEvidence.highestRisk);
|
|
83
|
+
console.log(featureEvidence.missingRequiredEvidence);
|
|
84
|
+
```
|
|
85
|
+
|
|
67
86
|
Ask the compiler what is actually covered before sending native imports into a merge queue:
|
|
68
87
|
|
|
69
88
|
```js
|
package/bench/smoke.mjs
CHANGED
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
createSemanticImportSidecar,
|
|
10
10
|
importNativeSource,
|
|
11
11
|
projectNativeImportToSource,
|
|
12
|
-
runNativeImporterAdapter
|
|
12
|
+
runNativeImporterAdapter,
|
|
13
|
+
summarizeNativeImportFeatureEvidence
|
|
13
14
|
} from '../dist/index.js';
|
|
14
15
|
|
|
15
16
|
const source = `
|
|
@@ -88,6 +89,13 @@ const semanticSidecars = nativeImportResults.map((imported) => createSemanticImp
|
|
|
88
89
|
const sidecarDurationMs = performance.now() - sidecarStart;
|
|
89
90
|
const sidecarOwnershipRegions = semanticSidecars.reduce((sum, sidecar) => sum + sidecar.ownershipRegions.length, 0);
|
|
90
91
|
|
|
92
|
+
const featureEvidenceStart = performance.now();
|
|
93
|
+
const featureEvidenceSummaries = nativeImportResults.map((imported) => summarizeNativeImportFeatureEvidence(imported.losses, {
|
|
94
|
+
evidence: imported.evidence
|
|
95
|
+
}));
|
|
96
|
+
const featureEvidenceDurationMs = performance.now() - featureEvidenceStart;
|
|
97
|
+
const featureEvidencePolicyMatches = featureEvidenceSummaries.reduce((sum, summary) => sum + summary.total, 0);
|
|
98
|
+
|
|
91
99
|
const projectionStart = performance.now();
|
|
92
100
|
const nativeProjections = nativeImportResults.map((imported) => projectNativeImportToSource(imported));
|
|
93
101
|
const projectionDurationMs = performance.now() - projectionStart;
|
|
@@ -150,6 +158,8 @@ console.log(JSON.stringify({
|
|
|
150
158
|
semanticSidecars: semanticSidecars.length,
|
|
151
159
|
sidecarOwnershipRegions,
|
|
152
160
|
sidecarDurationMs: Number(sidecarDurationMs.toFixed(2)),
|
|
161
|
+
featureEvidencePolicyMatches,
|
|
162
|
+
featureEvidenceDurationMs: Number(featureEvidenceDurationMs.toFixed(2)),
|
|
153
163
|
nativeProjections: nativeProjections.length,
|
|
154
164
|
projectionBytes,
|
|
155
165
|
projectionDurationMs: Number(projectionDurationMs.toFixed(2)),
|
package/dist/index.d.ts
CHANGED
|
@@ -151,6 +151,49 @@ export interface NativeImportLossSummaryOptions {
|
|
|
151
151
|
readonly semanticStatus?: string;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
export type NativeImportFeatureEvidenceRisk = 'low' | 'medium' | 'high' | 'critical' | string;
|
|
155
|
+
|
|
156
|
+
export interface NativeImportFeatureEvidencePolicy {
|
|
157
|
+
readonly kind: NativeImportKnownLossKind;
|
|
158
|
+
readonly category: NativeImportTaxonomyKind;
|
|
159
|
+
readonly risk: NativeImportFeatureEvidenceRisk;
|
|
160
|
+
readonly minimumReadiness: SemanticMergeReadiness;
|
|
161
|
+
readonly missingEvidenceReadiness: SemanticMergeReadiness;
|
|
162
|
+
readonly requiredEvidenceKeys: readonly string[];
|
|
163
|
+
readonly recommendedEvidenceKeys: readonly string[];
|
|
164
|
+
readonly notes: readonly string[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface NativeImportFeatureEvidenceIssue {
|
|
168
|
+
readonly lossId: string;
|
|
169
|
+
readonly kind: NativeImportKnownLossKind;
|
|
170
|
+
readonly policyKind: NativeImportKnownLossKind;
|
|
171
|
+
readonly risk: NativeImportFeatureEvidenceRisk;
|
|
172
|
+
readonly category: NativeImportTaxonomyKind;
|
|
173
|
+
readonly readiness: SemanticMergeReadiness;
|
|
174
|
+
readonly missingRequiredEvidence: readonly string[];
|
|
175
|
+
readonly presentRequiredEvidence: readonly string[];
|
|
176
|
+
readonly presentRecommendedEvidence: readonly string[];
|
|
177
|
+
readonly evidenceIds: readonly string[];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface NativeImportFeatureEvidenceSummary {
|
|
181
|
+
readonly total: number;
|
|
182
|
+
readonly policyKinds: readonly NativeImportKnownLossKind[];
|
|
183
|
+
readonly byKind: Readonly<Record<string, number>>;
|
|
184
|
+
readonly byRisk: Readonly<Record<string, number>>;
|
|
185
|
+
readonly highestRisk: NativeImportFeatureEvidenceRisk;
|
|
186
|
+
readonly semanticMergeReadiness: SemanticMergeReadiness;
|
|
187
|
+
readonly missingRequiredEvidence: readonly {
|
|
188
|
+
readonly lossId: string;
|
|
189
|
+
readonly kind: NativeImportKnownLossKind;
|
|
190
|
+
readonly policyKind: NativeImportKnownLossKind;
|
|
191
|
+
readonly evidenceKey: string;
|
|
192
|
+
}[];
|
|
193
|
+
readonly issues: readonly NativeImportFeatureEvidenceIssue[];
|
|
194
|
+
readonly reasons: readonly string[];
|
|
195
|
+
}
|
|
196
|
+
|
|
154
197
|
export interface NativeImportLossSummary {
|
|
155
198
|
readonly total: number;
|
|
156
199
|
readonly hasLosses: boolean;
|
|
@@ -165,6 +208,7 @@ export interface NativeImportLossSummary {
|
|
|
165
208
|
readonly reviewLossIds: readonly string[];
|
|
166
209
|
readonly informationalLossIds: readonly string[];
|
|
167
210
|
readonly failedEvidenceIds: readonly string[];
|
|
211
|
+
readonly featureEvidence: NativeImportFeatureEvidenceSummary;
|
|
168
212
|
readonly parser?: string;
|
|
169
213
|
readonly scanKind?: string;
|
|
170
214
|
readonly semanticStatus?: string;
|
|
@@ -1428,6 +1472,7 @@ export declare const NativeImportLossKinds: readonly NativeImportKnownLossKind[]
|
|
|
1428
1472
|
export declare const NativeImportRegionTaxonomyKinds: readonly NativeImportRegionTaxonomyKind[];
|
|
1429
1473
|
export declare const ProjectionTargetLossClasses: readonly ProjectionTargetLossClass[];
|
|
1430
1474
|
export declare const NativeImportReadinessBySeverity: Readonly<Record<NativeImportLossSummary['highestSeverity'], SemanticMergeReadiness>>;
|
|
1475
|
+
export declare const NativeImportFeatureEvidencePolicies: Readonly<Record<string, NativeImportFeatureEvidencePolicy>>;
|
|
1431
1476
|
export declare const NativeImportLanguageProfiles: readonly NativeImportLanguageProfile[];
|
|
1432
1477
|
export declare function normalizeCompileTarget(target?: string): FrontierCompileTarget;
|
|
1433
1478
|
export declare function compileFrontierSource(source: string, options?: FrontierCompileOptions): FrontierCompileResult;
|
|
@@ -1438,6 +1483,8 @@ export declare function renderTargetAst(ast: FrontierTargetAst, target?: Frontie
|
|
|
1438
1483
|
export declare function renderTargetAstWithSourceMap(ast: FrontierTargetAst, target?: FrontierCompileOptions['target'], options?: FrontierCompileEmitOptions): FrontierTargetSourceMapResult;
|
|
1439
1484
|
export declare function emitForTargetWithSourceMap(document: FrontierLangDocument, target?: FrontierCompileOptions['target'], options?: FrontierCompileEmitOptions): FrontierTargetDocumentSourceMapResult;
|
|
1440
1485
|
export declare function resolveCapabilityAdapters(document: FrontierLangDocument, target?: FrontierCompileOptions['target'], options?: { readonly platform?: string }): readonly CapabilityResolution[];
|
|
1486
|
+
export declare function getNativeImportFeatureEvidencePolicy(kind: NativeImportKnownLossKind | string): NativeImportFeatureEvidencePolicy | undefined;
|
|
1487
|
+
export declare function summarizeNativeImportFeatureEvidence(losses?: readonly NativeAstLossRecord[], options?: NativeImportLossSummaryOptions): NativeImportFeatureEvidenceSummary;
|
|
1441
1488
|
export declare function summarizeNativeImportLosses(losses?: readonly NativeAstLossRecord[], options?: NativeImportLossSummaryOptions): NativeImportLossSummary;
|
|
1442
1489
|
export declare function classifyNativeImportReadiness(losses?: readonly NativeAstLossRecord[], options?: NativeImportLossSummaryOptions): NativeImportReadinessClassification;
|
|
1443
1490
|
export declare function classifyNativeImportRoundtripReadiness(importResult: NativeSourceImportResult | NativeProjectImportResult, options?: NativeImportRoundtripReadinessOptions): NativeImportRoundtripReadinessClassification;
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,13 @@ const semanticMergeReadinessRank = Object.freeze({
|
|
|
76
76
|
blocked: 3
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
const nativeFeatureEvidenceRiskRank = Object.freeze({
|
|
80
|
+
low: 0,
|
|
81
|
+
medium: 1,
|
|
82
|
+
high: 2,
|
|
83
|
+
critical: 3
|
|
84
|
+
});
|
|
85
|
+
|
|
79
86
|
export const NativeImportRoundtripReadinessStatuses = Object.freeze([
|
|
80
87
|
'exact',
|
|
81
88
|
'preserved-source',
|
|
@@ -136,6 +143,117 @@ export const NativeImportReadinessBySeverity = Object.freeze({
|
|
|
136
143
|
error: 'blocked'
|
|
137
144
|
});
|
|
138
145
|
|
|
146
|
+
export const NativeImportFeatureEvidencePolicies = Object.freeze({
|
|
147
|
+
macroExpansion: nativeImportFeatureEvidencePolicy('macroExpansion', {
|
|
148
|
+
category: 'macroExpansion',
|
|
149
|
+
risk: 'high',
|
|
150
|
+
minimumReadiness: 'needs-review',
|
|
151
|
+
requiredEvidenceKeys: ['macroDefinitionsHash', 'expandedSourceHash'],
|
|
152
|
+
recommendedEvidenceKeys: ['expansionMapId', 'sourceMapId', 'macroCallSites'],
|
|
153
|
+
notes: ['Macro-expanded code must retain a link from generated output back to macro call sites before semantic merges can be trusted.']
|
|
154
|
+
}),
|
|
155
|
+
macroHygiene: nativeImportFeatureEvidencePolicy('macroHygiene', {
|
|
156
|
+
category: 'macroExpansion',
|
|
157
|
+
risk: 'critical',
|
|
158
|
+
minimumReadiness: 'needs-review',
|
|
159
|
+
missingEvidenceReadiness: 'blocked',
|
|
160
|
+
requiredEvidenceKeys: ['hygieneContextHash', 'bindingMapId'],
|
|
161
|
+
recommendedEvidenceKeys: ['expansionMapId', 'captureSetHash'],
|
|
162
|
+
notes: ['Hygiene-sensitive macros can change binding identity even when emitted text looks equivalent.']
|
|
163
|
+
}),
|
|
164
|
+
preprocessor: nativeImportFeatureEvidencePolicy('preprocessor', {
|
|
165
|
+
category: 'preprocessor',
|
|
166
|
+
risk: 'high',
|
|
167
|
+
minimumReadiness: 'needs-review',
|
|
168
|
+
requiredEvidenceKeys: ['preprocessedOutputHash', 'definesHash'],
|
|
169
|
+
recommendedEvidenceKeys: ['includeGraphHash', 'conditionalBranches', 'sourceMapId'],
|
|
170
|
+
notes: ['Preprocessor imports need the active defines/includes and preprocessed output hash to make replayable claims.']
|
|
171
|
+
}),
|
|
172
|
+
conditionalCompilation: nativeImportFeatureEvidencePolicy('conditionalCompilation', {
|
|
173
|
+
category: 'conditionalCompilation',
|
|
174
|
+
risk: 'high',
|
|
175
|
+
minimumReadiness: 'needs-review',
|
|
176
|
+
requiredEvidenceKeys: ['activeBranches', 'inactiveBranchesHash'],
|
|
177
|
+
recommendedEvidenceKeys: ['compileTarget', 'featureFlags', 'preprocessedOutputHash'],
|
|
178
|
+
notes: ['Conditional branches that were not active still affect portability and conflict review.']
|
|
179
|
+
}),
|
|
180
|
+
metaprogramming: nativeImportFeatureEvidencePolicy('metaprogramming', {
|
|
181
|
+
category: 'metaprogramming',
|
|
182
|
+
risk: 'critical',
|
|
183
|
+
minimumReadiness: 'needs-review',
|
|
184
|
+
missingEvidenceReadiness: 'blocked',
|
|
185
|
+
requiredEvidenceKeys: ['generatedArtifactHash', 'generatorIdentity'],
|
|
186
|
+
recommendedEvidenceKeys: ['generatorInputsHash', 'generatedRanges', 'replayCommand'],
|
|
187
|
+
notes: ['Generated or metaprogrammed declarations need replayable generator identity and input evidence.']
|
|
188
|
+
}),
|
|
189
|
+
reflection: nativeImportFeatureEvidencePolicy('reflection', {
|
|
190
|
+
category: 'reflection',
|
|
191
|
+
risk: 'high',
|
|
192
|
+
minimumReadiness: 'needs-review',
|
|
193
|
+
requiredEvidenceKeys: ['reflectionSurface', 'runtimeContract'],
|
|
194
|
+
recommendedEvidenceKeys: ['observedMembers', 'fixtureIds', 'runtimeVersion'],
|
|
195
|
+
notes: ['Reflection-heavy code needs a declared runtime contract because static AST evidence is incomplete.']
|
|
196
|
+
}),
|
|
197
|
+
dynamicRuntime: nativeImportFeatureEvidencePolicy('dynamicRuntime', {
|
|
198
|
+
category: 'reflection',
|
|
199
|
+
risk: 'high',
|
|
200
|
+
minimumReadiness: 'needs-review',
|
|
201
|
+
requiredEvidenceKeys: ['runtimeContract'],
|
|
202
|
+
recommendedEvidenceKeys: ['fixtureIds', 'observedEffects', 'runtimeVersion'],
|
|
203
|
+
notes: ['Dynamic runtime behavior should stay review-required until fixtures or traces describe the observed contract.']
|
|
204
|
+
}),
|
|
205
|
+
dynamicDispatch: nativeImportFeatureEvidencePolicy('dynamicDispatch', {
|
|
206
|
+
category: 'overloadTypeInference',
|
|
207
|
+
risk: 'medium',
|
|
208
|
+
minimumReadiness: 'needs-review',
|
|
209
|
+
requiredEvidenceKeys: ['dispatchTargets'],
|
|
210
|
+
recommendedEvidenceKeys: ['callGraphId', 'typeEvidenceId', 'fixtureIds'],
|
|
211
|
+
notes: ['Dynamic dispatch needs candidate target evidence before call graph or porting claims are merge-ready.']
|
|
212
|
+
}),
|
|
213
|
+
generatedCode: nativeImportFeatureEvidencePolicy('generatedCode', {
|
|
214
|
+
category: 'generatedCode',
|
|
215
|
+
risk: 'high',
|
|
216
|
+
minimumReadiness: 'needs-review',
|
|
217
|
+
requiredEvidenceKeys: ['generatedArtifactHash', 'generatedRanges'],
|
|
218
|
+
recommendedEvidenceKeys: ['generatorIdentity', 'generatorInputsHash', 'sourceMapId'],
|
|
219
|
+
notes: ['Generated code must preserve generated ranges and artifact hashes so workers can avoid editing derived output blindly.']
|
|
220
|
+
}),
|
|
221
|
+
overloadResolution: nativeImportFeatureEvidencePolicy('overloadResolution', {
|
|
222
|
+
category: 'overloadTypeInference',
|
|
223
|
+
risk: 'medium',
|
|
224
|
+
minimumReadiness: 'needs-review',
|
|
225
|
+
requiredEvidenceKeys: ['resolvedOverloads'],
|
|
226
|
+
recommendedEvidenceKeys: ['typeEvidenceId', 'compilerVersion', 'callSiteSpans'],
|
|
227
|
+
notes: ['Overload-sensitive imports should record compiler/type evidence for each call site.']
|
|
228
|
+
}),
|
|
229
|
+
typeInference: nativeImportFeatureEvidencePolicy('typeInference', {
|
|
230
|
+
category: 'overloadTypeInference',
|
|
231
|
+
risk: 'medium',
|
|
232
|
+
minimumReadiness: 'needs-review',
|
|
233
|
+
requiredEvidenceKeys: ['inferredTypesHash'],
|
|
234
|
+
recommendedEvidenceKeys: ['typeEvidenceId', 'compilerVersion', 'symbolTableHash'],
|
|
235
|
+
notes: ['Inferred types need a stable type-evidence hash before cross-language projection can claim fidelity.']
|
|
236
|
+
}),
|
|
237
|
+
unsupportedSyntax: nativeImportFeatureEvidencePolicy('unsupportedSyntax', {
|
|
238
|
+
category: 'unsupportedSyntax',
|
|
239
|
+
risk: 'high',
|
|
240
|
+
minimumReadiness: 'needs-review',
|
|
241
|
+
missingEvidenceReadiness: 'blocked',
|
|
242
|
+
requiredEvidenceKeys: ['unsupportedSyntaxKind', 'sourceSpan'],
|
|
243
|
+
recommendedEvidenceKeys: ['parserDiagnosticId', 'nativeAstNodeId', 'sourceSnippetHash'],
|
|
244
|
+
notes: ['Unsupported syntax must remain anchored to source spans and parser diagnostics for later adapter work.']
|
|
245
|
+
}),
|
|
246
|
+
unsupportedSemantic: nativeImportFeatureEvidencePolicy('unsupportedSemantic', {
|
|
247
|
+
category: 'unsupportedSyntax',
|
|
248
|
+
risk: 'high',
|
|
249
|
+
minimumReadiness: 'needs-review',
|
|
250
|
+
missingEvidenceReadiness: 'blocked',
|
|
251
|
+
requiredEvidenceKeys: ['unsupportedSemanticKind', 'semanticSymbolId'],
|
|
252
|
+
recommendedEvidenceKeys: ['semanticIndexId', 'sourceMapId', 'reason'],
|
|
253
|
+
notes: ['Unsupported semantics should name the affected symbol so merge tools can isolate the unsafe region.']
|
|
254
|
+
})
|
|
255
|
+
});
|
|
256
|
+
|
|
139
257
|
export const NativeImportRegionTaxonomyKinds = Object.freeze([
|
|
140
258
|
'symbol',
|
|
141
259
|
'declaration',
|
|
@@ -473,6 +591,70 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
|
|
|
473
591
|
});
|
|
474
592
|
}
|
|
475
593
|
|
|
594
|
+
export function getNativeImportFeatureEvidencePolicy(kind) {
|
|
595
|
+
const normalized = normalizeNativeLossKind({ kind }, 'warning');
|
|
596
|
+
return NativeImportFeatureEvidencePolicies[normalized] ?? NativeImportFeatureEvidencePolicies[String(kind ?? '')];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export function summarizeNativeImportFeatureEvidence(losses = [], options = {}) {
|
|
600
|
+
const normalizedLosses = normalizeNativeLossRecords(losses);
|
|
601
|
+
const evidence = options.evidence ?? [];
|
|
602
|
+
const issues = [];
|
|
603
|
+
const byKind = {};
|
|
604
|
+
const byRisk = {};
|
|
605
|
+
const policyKinds = [];
|
|
606
|
+
let highestRisk = 'low';
|
|
607
|
+
let semanticMergeReadiness = 'ready';
|
|
608
|
+
|
|
609
|
+
for (const loss of normalizedLosses) {
|
|
610
|
+
const policy = getNativeImportFeatureEvidencePolicy(loss.kind);
|
|
611
|
+
if (!policy) continue;
|
|
612
|
+
byKind[policy.kind] = (byKind[policy.kind] ?? 0) + 1;
|
|
613
|
+
byRisk[policy.risk] = (byRisk[policy.risk] ?? 0) + 1;
|
|
614
|
+
if ((nativeFeatureEvidenceRiskRank[policy.risk] ?? 0) > (nativeFeatureEvidenceRiskRank[highestRisk] ?? 0)) {
|
|
615
|
+
highestRisk = policy.risk;
|
|
616
|
+
}
|
|
617
|
+
policyKinds.push(policy.kind);
|
|
618
|
+
semanticMergeReadiness = maxSemanticMergeReadiness(semanticMergeReadiness, policy.minimumReadiness);
|
|
619
|
+
const missingRequiredEvidence = policy.requiredEvidenceKeys.filter((key) => !nativeImportFeatureEvidenceHasKey(loss, evidence, key));
|
|
620
|
+
const presentRequiredEvidence = policy.requiredEvidenceKeys.filter((key) => !missingRequiredEvidence.includes(key));
|
|
621
|
+
const presentRecommendedEvidence = policy.recommendedEvidenceKeys.filter((key) => nativeImportFeatureEvidenceHasKey(loss, evidence, key));
|
|
622
|
+
if (missingRequiredEvidence.length) {
|
|
623
|
+
semanticMergeReadiness = maxSemanticMergeReadiness(semanticMergeReadiness, policy.missingEvidenceReadiness);
|
|
624
|
+
}
|
|
625
|
+
issues.push({
|
|
626
|
+
lossId: loss.id,
|
|
627
|
+
kind: loss.kind,
|
|
628
|
+
policyKind: policy.kind,
|
|
629
|
+
risk: policy.risk,
|
|
630
|
+
category: policy.category,
|
|
631
|
+
readiness: missingRequiredEvidence.length ? policy.missingEvidenceReadiness : policy.minimumReadiness,
|
|
632
|
+
missingRequiredEvidence,
|
|
633
|
+
presentRequiredEvidence,
|
|
634
|
+
presentRecommendedEvidence,
|
|
635
|
+
evidenceIds: nativeImportFeatureEvidenceIds(loss, evidence, policy)
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const missingRequiredEvidence = issues.flatMap((issue) => issue.missingRequiredEvidence.map((key) => ({
|
|
640
|
+
lossId: issue.lossId,
|
|
641
|
+
kind: issue.kind,
|
|
642
|
+
policyKind: issue.policyKind,
|
|
643
|
+
evidenceKey: key
|
|
644
|
+
})));
|
|
645
|
+
return {
|
|
646
|
+
total: issues.length,
|
|
647
|
+
policyKinds: uniqueStrings(policyKinds),
|
|
648
|
+
byKind,
|
|
649
|
+
byRisk,
|
|
650
|
+
highestRisk: issues.length ? highestRisk : 'low',
|
|
651
|
+
semanticMergeReadiness,
|
|
652
|
+
missingRequiredEvidence,
|
|
653
|
+
issues,
|
|
654
|
+
reasons: nativeImportFeatureEvidenceReasons(issues)
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
476
658
|
export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
477
659
|
const normalizedLosses = normalizeNativeLossRecords(losses);
|
|
478
660
|
const bySeverity = { info: 0, warning: 0, error: 0 };
|
|
@@ -512,6 +694,9 @@ export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
|
512
694
|
reviewLossIds,
|
|
513
695
|
informationalLossIds
|
|
514
696
|
});
|
|
697
|
+
const featureEvidence = summarizeNativeImportFeatureEvidence(normalizedLosses, {
|
|
698
|
+
evidence: options.evidence
|
|
699
|
+
});
|
|
515
700
|
|
|
516
701
|
return {
|
|
517
702
|
total: normalizedLosses.length,
|
|
@@ -527,6 +712,7 @@ export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
|
527
712
|
reviewLossIds,
|
|
528
713
|
informationalLossIds,
|
|
529
714
|
failedEvidenceIds,
|
|
715
|
+
featureEvidence,
|
|
530
716
|
parser: options.parser,
|
|
531
717
|
scanKind: options.scanKind,
|
|
532
718
|
semanticStatus: options.semanticStatus
|
|
@@ -4112,6 +4298,61 @@ function nativeImportCategoryForLossKind(kind) {
|
|
|
4112
4298
|
return String(kind ?? 'opaqueNative');
|
|
4113
4299
|
}
|
|
4114
4300
|
|
|
4301
|
+
function nativeImportFeatureEvidencePolicy(kind, input = {}) {
|
|
4302
|
+
return Object.freeze({
|
|
4303
|
+
kind,
|
|
4304
|
+
category: input.category ?? nativeImportCategoryForLossKind(kind),
|
|
4305
|
+
risk: input.risk ?? 'medium',
|
|
4306
|
+
minimumReadiness: normalizeSemanticMergeReadiness(input.minimumReadiness) ?? 'needs-review',
|
|
4307
|
+
missingEvidenceReadiness: normalizeSemanticMergeReadiness(input.missingEvidenceReadiness) ?? 'needs-review',
|
|
4308
|
+
requiredEvidenceKeys: Object.freeze(uniqueStrings(input.requiredEvidenceKeys ?? [])),
|
|
4309
|
+
recommendedEvidenceKeys: Object.freeze(uniqueStrings(input.recommendedEvidenceKeys ?? [])),
|
|
4310
|
+
notes: Object.freeze(uniqueStrings(input.notes ?? []))
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
function nativeImportFeatureEvidenceHasKey(loss, evidence, key) {
|
|
4315
|
+
return nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(loss, key))
|
|
4316
|
+
|| (evidence ?? []).some((record) => nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(record, key)));
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
function nativeImportFeatureEvidenceValue(record, key) {
|
|
4320
|
+
if (!record || !key) return undefined;
|
|
4321
|
+
const candidates = [record, record.metadata].filter(Boolean);
|
|
4322
|
+
for (const candidate of candidates) {
|
|
4323
|
+
const direct = candidate[key];
|
|
4324
|
+
if (direct !== undefined) return direct;
|
|
4325
|
+
const dotted = String(key).split('.').reduce((current, part) => current?.[part], candidate);
|
|
4326
|
+
if (dotted !== undefined) return dotted;
|
|
4327
|
+
}
|
|
4328
|
+
return undefined;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
function nativeImportFeatureEvidenceValuePresent(value) {
|
|
4332
|
+
if (value === undefined || value === null) return false;
|
|
4333
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
4334
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
4335
|
+
if (typeof value === 'object') return Object.keys(value).length > 0;
|
|
4336
|
+
return true;
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
function nativeImportFeatureEvidenceIds(loss, evidence, policy) {
|
|
4340
|
+
const keys = [...policy.requiredEvidenceKeys, ...policy.recommendedEvidenceKeys];
|
|
4341
|
+
return uniqueStrings((evidence ?? [])
|
|
4342
|
+
.filter((record) => keys.some((key) => nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(record, key))))
|
|
4343
|
+
.map((record) => record.id)
|
|
4344
|
+
.filter(Boolean)
|
|
4345
|
+
.concat(loss.evidenceIds ?? []));
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
function nativeImportFeatureEvidenceReasons(issues) {
|
|
4349
|
+
return uniqueStrings((issues ?? []).flatMap((issue) => {
|
|
4350
|
+
const missing = issue.missingRequiredEvidence ?? [];
|
|
4351
|
+
if (!missing.length) return [];
|
|
4352
|
+
return [`${issue.kind} loss ${issue.lossId} is missing required evidence: ${missing.join(', ')}.`];
|
|
4353
|
+
}));
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4115
4356
|
function normalizeProjectionMatrixTargets(targets) {
|
|
4116
4357
|
return uniqueStrings((Array.isArray(targets) ? targets : [targets])
|
|
4117
4358
|
.map((target) => {
|
package/package.json
CHANGED