rea-agents 1.6.0 → 2.0.0
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 +157 -66
- package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
- package/bridge/hopper_bridge.py +13 -3
- package/dist/application/AbortablePromise.js +25 -0
- package/dist/application/AnalysisProvider.js +3 -0
- package/dist/application/AnalysisProviderEvaluation.js +60 -0
- package/dist/application/AnalysisProviderRegistry.js +228 -0
- package/dist/application/AnalysisSnapshotCache.js +41 -21
- package/dist/application/AnalysisSnapshotFiles.js +5 -6
- package/dist/application/BinarySession.js +218 -101
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +110 -8
- package/dist/application/ElectronBoundaryAnalysis.js +96 -0
- package/dist/application/ElectronBoundaryGraph.js +9 -0
- package/dist/application/ElectronBoundaryGraphContext.js +62 -0
- package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
- package/dist/application/ElectronBoundaryGraphNative.js +86 -0
- package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
- package/dist/application/EnhancedTools.js +39 -14
- package/dist/application/InvestigationProviders.js +41 -0
- package/dist/application/JavaScriptApplicationEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
- package/dist/application/JavaScriptApplicationService.js +74 -0
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +305 -0
- package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
- package/dist/application/JavaScriptArtifactFiles.js +231 -0
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
- package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
- package/dist/application/JavaScriptReplayPlanning.js +201 -0
- package/dist/application/JavaScriptReplayService.js +266 -0
- package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
- package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
- package/dist/application/MacHopper.js +20 -4
- package/dist/application/ManagedApplicationGraphService.js +55 -0
- package/dist/application/ManagedMemberComparisonService.js +122 -0
- package/dist/application/ManagedNativeVerificationService.js +39 -0
- package/dist/application/ManagedReconstructionService.js +58 -0
- package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
- package/dist/application/PermissionAuthority.js +1 -1
- package/dist/application/ProcessCaptureLifecycle.js +1 -1
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessSampling.js +16 -12
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +84 -60
- package/dist/application/SetupClients.js +1 -1
- package/dist/application/SetupPlan.js +9 -2
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Upgrade.js +1 -1
- package/dist/application/runtime.js +11 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +4 -2
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +9 -3
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpEndpoint.js +14 -1
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +6 -42
- package/dist/cli.js +446 -34
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliBrowserAdvancedCommands.js +5 -4
- package/dist/cliBrowserCommands.js +5 -4
- package/dist/cliCommandNames.js +56 -0
- package/dist/cliElectronCommands.js +106 -3
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +2 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/cliPolicyCommands.js +2 -1
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +101 -2
- package/dist/contracts/applicationToolContracts.js +91 -0
- package/dist/contracts/artifactToolContracts.js +5 -9
- package/dist/contracts/browserToolContracts.js +14 -62
- package/dist/contracts/electronToolContracts.js +45 -19
- package/dist/contracts/enhancedInputs.js +2 -2
- package/dist/contracts/errorSchemas.js +1 -0
- package/dist/contracts/investigationExamples.js +0 -6
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
- package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
- package/dist/contracts/managedToolContracts.js +153 -0
- package/dist/contracts/managedWorkflowExamples.js +346 -0
- package/dist/contracts/managedWorkflowToolContracts.js +203 -0
- package/dist/contracts/nativeToolContracts.js +2 -6
- package/dist/contracts/promptContracts.js +18 -2
- package/dist/contracts/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +69 -65
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +152 -42
- package/dist/doctorRuntime.js +47 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactComparison.js +3 -4
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- package/dist/domain/browserObservation.js +3 -0
- package/dist/domain/bundleComparison.js +4 -4
- package/dist/domain/electronObservation.js +13 -0
- package/dist/domain/electronStaticAnalysis.js +9 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
- package/dist/domain/electronStaticAnalysisIpc.js +171 -0
- package/dist/domain/electronStaticAnalysisNative.js +164 -0
- package/dist/domain/electronStaticAnalysisTypes.js +1 -0
- package/dist/domain/electronStaticAnalysisValues.js +103 -0
- package/dist/domain/errors.js +148 -7
- package/dist/domain/evidence.js +44 -5
- package/dist/domain/evidenceBundle.js +9 -7
- package/dist/domain/functionComparison.js +9 -3
- package/dist/domain/functionComparisonNormalization.js +37 -0
- package/dist/domain/functionComparisonSchemas.js +3 -8
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/investigationWorkspace.js +2 -1
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
- package/dist/domain/javascriptApplicationGraph.js +282 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
- package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionItems.js +221 -0
- package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
- package/dist/domain/javascriptAstFingerprint.js +143 -0
- package/dist/domain/javascriptFeatureSeed.js +140 -0
- package/dist/domain/javascriptFeatureTrace.js +232 -0
- package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
- package/dist/domain/javascriptFeatureTraversal.js +88 -0
- package/dist/domain/javascriptNativeHandoff.js +85 -0
- package/dist/domain/javascriptReplay.js +361 -0
- package/dist/domain/javascriptRuntimeLoadState.js +106 -0
- package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
- package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
- package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
- package/dist/domain/javascriptStaticAnalysis.js +346 -0
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
- package/dist/domain/javascriptStaticAnalysisState.js +23 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/managedApplicationGraph.js +556 -0
- package/dist/domain/managedArtifact.js +444 -0
- package/dist/domain/managedMemberComparison.js +565 -0
- package/dist/domain/managedNativeVerification.js +408 -0
- package/dist/domain/managedReconstruction.js +183 -0
- package/dist/domain/managedRuntimeCorrelation.js +175 -0
- package/dist/domain/reconstructionVerificationSchemas.js +1 -3
- package/dist/dotnet/ManagedArtifactInspector.js +318 -0
- package/dist/dotnet/ManagedMemberInspector.js +887 -0
- package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
- package/dist/dotnet/ManagedMetadataInventory.js +368 -0
- package/dist/dotnet/ManagedMetadataLayout.js +322 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
- package/dist/dotnet/ManagedPeReader.js +157 -0
- package/dist/dotnet/ManagedReaderFailure.js +10 -0
- package/dist/dotnet/ManagedStaticProvider.js +203 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
- package/dist/ghidra/GhidraClient.js +400 -0
- package/dist/ghidra/GhidraClientTypes.js +1 -0
- package/dist/ghidra/GhidraDefaults.js +20 -0
- package/dist/ghidra/GhidraDiagnostics.js +43 -0
- package/dist/ghidra/GhidraDoctor.js +59 -0
- package/dist/ghidra/GhidraFunctionValues.js +264 -0
- package/dist/ghidra/GhidraInstallation.js +239 -0
- package/dist/ghidra/GhidraInventoryValues.js +217 -0
- package/dist/ghidra/GhidraLauncher.js +139 -0
- package/dist/ghidra/GhidraProvider.js +366 -0
- package/dist/ghidra/GhidraRequestQueue.js +98 -0
- package/dist/ghidra/GhidraResponseBuffer.js +31 -0
- package/dist/ghidra/GhidraResponseRouter.js +24 -0
- package/dist/ghidra/GhidraSessionError.js +39 -0
- package/dist/ghidra/GhidraSessionValues.js +62 -0
- package/dist/ghidra/GhidraSocketConnection.js +46 -0
- package/dist/ghidra/protocol.js +69 -0
- package/dist/hopper/BridgeLauncher.js +19 -54
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +154 -162
- package/dist/hopper/HopperProvider.js +99 -7
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +14 -0
- package/dist/native/NativeMacOSProvider.js +3 -1
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -0
- package/dist/replay/JavaScriptReplayWorker.js +286 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
- package/dist/replay/LinuxRuntimeClosure.js +46 -0
- package/dist/replay/LinuxSeccompPolicy.js +40 -0
- package/dist/replay/ReplayWorkerProtocol.js +55 -0
- package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
- package/dist/server/createServer.js +53 -0
- package/dist/server/promptCompletion.js +9 -3
- package/dist/server/registerApplicationTools.js +115 -0
- package/dist/server/registerArtifactComparisonTool.js +16 -9
- package/dist/server/registerBrowserTools.js +33 -8
- package/dist/server/registerBundleComparisonTool.js +17 -20
- package/dist/server/registerElectronTools.js +45 -3
- package/dist/server/registerEnhancedTools.js +67 -19
- package/dist/server/registerEvidenceTools.js +5 -1
- package/dist/server/registerFunctionComparisonTool.js +14 -7
- package/dist/server/registerInvestigationTools.js +21 -17
- package/dist/server/registerManagedTools.js +6 -0
- package/dist/server/registerManagedWorkflowTools.js +277 -0
- package/dist/server/registerOfficialTools.js +11 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +57 -87
- package/dist/server/sessionEvidence.js +14 -19
- package/dist/server/toolInputValidation.js +77 -0
- package/dist/server/toolRegistrationOptions.js +79 -2
- package/dist/server/toolResult.js +25 -17
- package/package.json +17 -7
- package/scripts/rea.mjs +36 -2
- package/skills/rea-analysis/SKILL.md +59 -3
- /package/dist/{application → process}/ProcessOwnership.js +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { compareCodePoints } from "./javascriptApplicationGraph.js";
|
|
2
|
+
const matchTiers = () => [
|
|
3
|
+
{ basis: "exact-content-digest", confidence: "exact", key: contentDigestKey },
|
|
4
|
+
{
|
|
5
|
+
basis: "exact-module-source-digest",
|
|
6
|
+
confidence: "exact",
|
|
7
|
+
key: moduleSourceDigestKey,
|
|
8
|
+
},
|
|
9
|
+
{ basis: "exact-node-identity", confidence: "exact", key: nodeIdentityKey },
|
|
10
|
+
{ basis: "source-map-identity", confidence: "high", key: sourceMapKey },
|
|
11
|
+
{
|
|
12
|
+
basis: "structural-fingerprint",
|
|
13
|
+
confidence: "medium",
|
|
14
|
+
key: structuralFingerprintKey,
|
|
15
|
+
},
|
|
16
|
+
{ basis: "semantic-key", confidence: "medium", key: semanticKey },
|
|
17
|
+
];
|
|
18
|
+
/** Apply ordered, unique-only matching tiers without fuzzy pairing. */
|
|
19
|
+
export const matchJavaScriptApplicationVersions = (leftNodes, rightNodes) => {
|
|
20
|
+
const left = new Map(leftNodes.map((node) => [node.node_id, node]));
|
|
21
|
+
const right = new Map(rightNodes.map((node) => [node.node_id, node]));
|
|
22
|
+
const leftCandidates = new Map();
|
|
23
|
+
const rightCandidates = new Map();
|
|
24
|
+
const pairs = [];
|
|
25
|
+
for (const tier of matchTiers())
|
|
26
|
+
applyTier(left, right, pairs, leftCandidates, rightCandidates, tier);
|
|
27
|
+
const remainingLeft = sortedNodes(left.values());
|
|
28
|
+
const remainingRight = sortedNodes(right.values());
|
|
29
|
+
const leftIds = new Set(remainingLeft.map(({ node_id: id }) => id));
|
|
30
|
+
const rightIds = new Set(remainingRight.map(({ node_id: id }) => id));
|
|
31
|
+
return {
|
|
32
|
+
pairs: pairs.sort((a, b) => compareCodePoints(`${a.left.node_id}\0${a.right.node_id}`, `${b.left.node_id}\0${b.right.node_id}`)),
|
|
33
|
+
unmatchedLeft: remainingLeft,
|
|
34
|
+
unmatchedRight: remainingRight,
|
|
35
|
+
candidatesForLeft: filteredCandidates(leftCandidates, rightIds),
|
|
36
|
+
candidatesForRight: filteredCandidates(rightCandidates, leftIds),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const applyTier = (left, right, pairs, leftCandidates, rightCandidates, tier) => {
|
|
40
|
+
const leftByKey = nodesByKey(left.values(), tier.key);
|
|
41
|
+
const rightByKey = nodesByKey(right.values(), tier.key);
|
|
42
|
+
for (const key of [...leftByKey.keys()].sort(compareCodePoints)) {
|
|
43
|
+
const leftGroup = leftByKey.get(key) ?? [];
|
|
44
|
+
const rightGroup = rightByKey.get(key) ?? [];
|
|
45
|
+
if (rightGroup.length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
recordCandidates(leftGroup, rightGroup, leftCandidates, rightCandidates);
|
|
48
|
+
if (leftGroup.length !== 1 || rightGroup.length !== 1)
|
|
49
|
+
continue;
|
|
50
|
+
const leftNode = leftGroup[0];
|
|
51
|
+
const rightNode = rightGroup[0];
|
|
52
|
+
if (leftNode === undefined || rightNode === undefined)
|
|
53
|
+
continue;
|
|
54
|
+
pairs.push({ left: leftNode, right: rightNode, ...tier });
|
|
55
|
+
left.delete(leftNode.node_id);
|
|
56
|
+
right.delete(rightNode.node_id);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const nodesByKey = (nodes, keyFor) => {
|
|
60
|
+
const output = new Map();
|
|
61
|
+
for (const node of nodes) {
|
|
62
|
+
const key = keyFor(node);
|
|
63
|
+
if (key === null)
|
|
64
|
+
continue;
|
|
65
|
+
output.set(key, [...(output.get(key) ?? []), node]);
|
|
66
|
+
}
|
|
67
|
+
return output;
|
|
68
|
+
};
|
|
69
|
+
const recordCandidates = (left, right, leftCandidates, rightCandidates) => {
|
|
70
|
+
for (const leftNode of left) {
|
|
71
|
+
const candidates = leftCandidates.get(leftNode.node_id) ?? new Set();
|
|
72
|
+
for (const rightNode of right)
|
|
73
|
+
candidates.add(rightNode.node_id);
|
|
74
|
+
leftCandidates.set(leftNode.node_id, candidates);
|
|
75
|
+
}
|
|
76
|
+
for (const rightNode of right) {
|
|
77
|
+
const candidates = rightCandidates.get(rightNode.node_id) ?? new Set();
|
|
78
|
+
for (const leftNode of left)
|
|
79
|
+
candidates.add(leftNode.node_id);
|
|
80
|
+
rightCandidates.set(rightNode.node_id, candidates);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const filteredCandidates = (candidates, retained) => new Map([...candidates.entries()].flatMap(([nodeId, values]) => {
|
|
84
|
+
const filtered = [...values]
|
|
85
|
+
.filter((value) => retained.has(value))
|
|
86
|
+
.sort(compareCodePoints);
|
|
87
|
+
return filtered.length === 0 ? [] : [[nodeId, filtered]];
|
|
88
|
+
}));
|
|
89
|
+
const nodeIdentityKey = (node) => `node\0${node.kind}\0${node.node_id}`;
|
|
90
|
+
const contentDigestKey = (node) => node.identity.strategy === "content-digest"
|
|
91
|
+
? `content\0${node.kind}\0${node.identity.sha256}`
|
|
92
|
+
: null;
|
|
93
|
+
const moduleSourceDigestKey = (node) => {
|
|
94
|
+
if (node.kind !== "javascript-module")
|
|
95
|
+
return null;
|
|
96
|
+
const value = uniqueStringProperty(node, "source_sha256");
|
|
97
|
+
return value === null ? null : `module-source\0${node.kind}\0${value}`;
|
|
98
|
+
};
|
|
99
|
+
const sourceMapKey = (node) => node.kind === "source-module" &&
|
|
100
|
+
node.identity.strategy === "source-map-original"
|
|
101
|
+
? `source-map\0${node.identity.original_source}`
|
|
102
|
+
: null;
|
|
103
|
+
const structuralFingerprintKey = (node) => {
|
|
104
|
+
if (node.kind !== "javascript-module")
|
|
105
|
+
return null;
|
|
106
|
+
const digest = uniqueStringProperty(node, "structural_fingerprint_sha256");
|
|
107
|
+
const algorithm = uniqueStringProperty(node, "structural_fingerprint_algorithm");
|
|
108
|
+
const status = uniqueStringProperty(node, "structural_fingerprint_status");
|
|
109
|
+
return digest === null || algorithm === null || status !== "complete"
|
|
110
|
+
? null
|
|
111
|
+
: `structural\0${node.kind}\0${algorithm}\0${digest}`;
|
|
112
|
+
};
|
|
113
|
+
const semanticKey = (node) => {
|
|
114
|
+
if ([
|
|
115
|
+
"javascript-module",
|
|
116
|
+
"javascript-chunk",
|
|
117
|
+
"runtime-script-instance",
|
|
118
|
+
"unknown",
|
|
119
|
+
].includes(node.kind))
|
|
120
|
+
return null;
|
|
121
|
+
const properties = firstProperties(node);
|
|
122
|
+
const value = semanticValue(node, properties);
|
|
123
|
+
return value === null ? null : `semantic\0${node.kind}\0${value}`;
|
|
124
|
+
};
|
|
125
|
+
const semanticValue = (node, properties) => {
|
|
126
|
+
if (node.kind === "endpoint")
|
|
127
|
+
return joined(properties.endpoint_kind, properties.value);
|
|
128
|
+
if (node.kind === "storage")
|
|
129
|
+
return joined(properties.storage_kind, properties.name ?? properties.mechanism);
|
|
130
|
+
if (node.kind === "ipc-channel")
|
|
131
|
+
return properties.resolution === "dynamic"
|
|
132
|
+
? null
|
|
133
|
+
: (stringValue(properties.channel) ?? firstLabel(node));
|
|
134
|
+
if (node.kind === "context-bridge-api")
|
|
135
|
+
return firstLabel(node) ?? stringValue(properties.api_name);
|
|
136
|
+
if (node.kind === "native-export")
|
|
137
|
+
return (joined(properties.specifier, stringList(properties.requested_members).join(",")) ?? firstLabel(node));
|
|
138
|
+
if ([
|
|
139
|
+
"electron-main",
|
|
140
|
+
"electron-preload",
|
|
141
|
+
"electron-renderer",
|
|
142
|
+
"electron-utility",
|
|
143
|
+
].includes(node.kind))
|
|
144
|
+
return joined(properties.declared_path, properties.resolved_path);
|
|
145
|
+
if (node.kind === "package")
|
|
146
|
+
return stringValue(properties.name);
|
|
147
|
+
return canonicalPath(node) ?? stringValue(properties.path);
|
|
148
|
+
};
|
|
149
|
+
const canonicalPath = (node) => node.identity.strategy === "canonical-path" ? node.identity.path : null;
|
|
150
|
+
const firstProperties = (node) => node.observations[0]?.properties ?? {};
|
|
151
|
+
const firstLabel = (node) => node.observations.find(({ label }) => label !== null)?.label ?? null;
|
|
152
|
+
const uniqueStringProperty = (node, name) => {
|
|
153
|
+
const values = [
|
|
154
|
+
...new Set(node.observations
|
|
155
|
+
.map(({ properties }) => properties[name])
|
|
156
|
+
.filter((value) => typeof value === "string")),
|
|
157
|
+
];
|
|
158
|
+
return values.length === 1 ? (values[0] ?? null) : null;
|
|
159
|
+
};
|
|
160
|
+
const joined = (...values) => {
|
|
161
|
+
const strings = values.map(stringValue);
|
|
162
|
+
return strings.some((value) => value === null) ? null : strings.join("\0");
|
|
163
|
+
};
|
|
164
|
+
const stringValue = (value) => typeof value === "string" && value !== "" ? value : null;
|
|
165
|
+
const stringList = (value) => Array.isArray(value)
|
|
166
|
+
? value
|
|
167
|
+
.filter((item) => typeof item === "string")
|
|
168
|
+
.sort(compareCodePoints)
|
|
169
|
+
: [];
|
|
170
|
+
const sortedNodes = (nodes) => [...nodes].sort((left, right) => compareCodePoints(left.node_id, right.node_id));
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
/** Derive a rename-resistant, bounded syntax fingerprint without code execution. */
|
|
4
|
+
export const fingerprintJavaScriptAst = (node, maximumTokens) => {
|
|
5
|
+
const tokens = [];
|
|
6
|
+
let truncated = false;
|
|
7
|
+
t.traverseFast(node, (current) => {
|
|
8
|
+
if (tokens.length >= maximumTokens) {
|
|
9
|
+
truncated = true;
|
|
10
|
+
return t.traverseFast.stop;
|
|
11
|
+
}
|
|
12
|
+
tokens.push(...semanticTokens(current));
|
|
13
|
+
if (tokens.length > maximumTokens) {
|
|
14
|
+
tokens.length = maximumTokens;
|
|
15
|
+
truncated = true;
|
|
16
|
+
return t.traverseFast.stop;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
sha256: createHash("sha256").update(JSON.stringify(tokens)).digest("hex"),
|
|
22
|
+
truncated,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** Collect statically declared CommonJS/bundler export names from one factory. */
|
|
26
|
+
export const collectJavaScriptExports = (node, maximumExports) => {
|
|
27
|
+
const exports = new Set();
|
|
28
|
+
let truncated = false;
|
|
29
|
+
t.traverseFast(node, (current) => {
|
|
30
|
+
if (exports.size >= maximumExports) {
|
|
31
|
+
truncated = true;
|
|
32
|
+
return t.traverseFast.stop;
|
|
33
|
+
}
|
|
34
|
+
if (t.isAssignmentExpression(current))
|
|
35
|
+
collectAssignmentExports(current.left, current.right, exports, maximumExports);
|
|
36
|
+
if (t.isCallExpression(current))
|
|
37
|
+
collectCallExports(current, exports, maximumExports);
|
|
38
|
+
return undefined;
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
values: [...exports].sort(compareCodePoints),
|
|
42
|
+
truncated,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const semanticTokens = (node) => {
|
|
46
|
+
const tokens = [`node:${node.type}`];
|
|
47
|
+
if (t.isStringLiteral(node))
|
|
48
|
+
tokens.push(`string:${bounded(node.value)}`);
|
|
49
|
+
else if (t.isNumericLiteral(node))
|
|
50
|
+
tokens.push(`number:${String(node.value)}`);
|
|
51
|
+
else if (t.isBooleanLiteral(node))
|
|
52
|
+
tokens.push(`boolean:${String(node.value)}`);
|
|
53
|
+
else if (t.isRegExpLiteral(node))
|
|
54
|
+
tokens.push(`regexp:${bounded(node.pattern)}/${node.flags}`);
|
|
55
|
+
else if (t.isBinaryExpression(node) || t.isLogicalExpression(node))
|
|
56
|
+
tokens.push(`operator:${node.operator}`);
|
|
57
|
+
else if (t.isUnaryExpression(node) || t.isUpdateExpression(node))
|
|
58
|
+
tokens.push(`operator:${node.operator}`);
|
|
59
|
+
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
60
|
+
const property = propertyName(node.property);
|
|
61
|
+
if (property !== "")
|
|
62
|
+
tokens.push(`property:${bounded(property)}`);
|
|
63
|
+
}
|
|
64
|
+
if ((t.isObjectProperty(node) || t.isObjectMethod(node)) && !node.computed) {
|
|
65
|
+
const key = propertyName(node.key);
|
|
66
|
+
if (key !== "")
|
|
67
|
+
tokens.push(`key:${bounded(key)}`);
|
|
68
|
+
}
|
|
69
|
+
return tokens;
|
|
70
|
+
};
|
|
71
|
+
const collectAssignmentExports = (left, right, output, maximum) => {
|
|
72
|
+
const path = memberPath(left);
|
|
73
|
+
if (path === "exports" || path === "module.exports") {
|
|
74
|
+
if (t.isObjectExpression(right))
|
|
75
|
+
collectObjectKeys(right, output, maximum);
|
|
76
|
+
else
|
|
77
|
+
addExport(output, "default", maximum);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (path.startsWith("exports."))
|
|
81
|
+
addExport(output, path.slice("exports.".length), maximum);
|
|
82
|
+
if (path.startsWith("module.exports."))
|
|
83
|
+
addExport(output, path.slice("module.exports.".length), maximum);
|
|
84
|
+
};
|
|
85
|
+
const collectCallExports = (call, output, maximum) => {
|
|
86
|
+
const callee = memberPath(call.callee);
|
|
87
|
+
if (callee === "Object.defineProperty") {
|
|
88
|
+
const target = memberPath(call.arguments[0]);
|
|
89
|
+
const name = stringValue(call.arguments[1]);
|
|
90
|
+
if ((target === "exports" || target === "module.exports") && name)
|
|
91
|
+
addExport(output, name, maximum);
|
|
92
|
+
}
|
|
93
|
+
if (!callee.endsWith(".d"))
|
|
94
|
+
return;
|
|
95
|
+
const target = memberPath(call.arguments[0]);
|
|
96
|
+
const declarations = call.arguments[1];
|
|
97
|
+
if ((target === "exports" || target === "module.exports") &&
|
|
98
|
+
t.isObjectExpression(declarations))
|
|
99
|
+
collectObjectKeys(declarations, output, maximum);
|
|
100
|
+
};
|
|
101
|
+
const collectObjectKeys = (object, output, maximum) => {
|
|
102
|
+
for (const property of object.properties) {
|
|
103
|
+
if (output.size >= maximum)
|
|
104
|
+
return;
|
|
105
|
+
if (!t.isObjectProperty(property) && !t.isObjectMethod(property))
|
|
106
|
+
continue;
|
|
107
|
+
const name = propertyName(property.key);
|
|
108
|
+
if (name !== "")
|
|
109
|
+
addExport(output, name, maximum);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const addExport = (output, name, maximum) => {
|
|
113
|
+
if (output.size < maximum)
|
|
114
|
+
output.add(name.slice(0, 4_096));
|
|
115
|
+
};
|
|
116
|
+
const memberPath = (node, depth = 0) => {
|
|
117
|
+
if (depth >= 128)
|
|
118
|
+
return "[deep]";
|
|
119
|
+
if (node === undefined || node === null)
|
|
120
|
+
return "";
|
|
121
|
+
if (t.isIdentifier(node) || t.isPrivateName(node))
|
|
122
|
+
return t.isIdentifier(node) ? node.name : "";
|
|
123
|
+
if (t.isThisExpression(node))
|
|
124
|
+
return "this";
|
|
125
|
+
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
126
|
+
const object = t.isNode(node.object)
|
|
127
|
+
? memberPath(node.object, depth + 1)
|
|
128
|
+
: "";
|
|
129
|
+
const property = propertyName(node.property);
|
|
130
|
+
return object === "" ? property : `${object}.${property}`;
|
|
131
|
+
}
|
|
132
|
+
return "";
|
|
133
|
+
};
|
|
134
|
+
const propertyName = (node) => {
|
|
135
|
+
if (t.isIdentifier(node))
|
|
136
|
+
return node.name;
|
|
137
|
+
if (t.isStringLiteral(node) || t.isNumericLiteral(node))
|
|
138
|
+
return String(node.value);
|
|
139
|
+
return "";
|
|
140
|
+
};
|
|
141
|
+
const stringValue = (node) => t.isStringLiteral(node) ? node.value : undefined;
|
|
142
|
+
const bounded = (value) => value.slice(0, 1_024);
|
|
143
|
+
const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { compareCodePoints } from "./javascriptApplicationGraph.js";
|
|
2
|
+
/** Locate every graph node matching a typed literal seed without regex execution. */
|
|
3
|
+
export const findApplicationFeatureSeeds = (nodes, seed) => nodes
|
|
4
|
+
.flatMap((node) => seedMatch(node, seed))
|
|
5
|
+
.sort((left, right) => compareCodePoints(`${left.node_id}\0${left.basis}\0${left.field}`, `${right.node_id}\0${right.basis}\0${right.field}`));
|
|
6
|
+
const seedMatch = (node, seed) => {
|
|
7
|
+
if (seed.kind === "node-id")
|
|
8
|
+
return node.node_id === seed.value
|
|
9
|
+
? [
|
|
10
|
+
{
|
|
11
|
+
node_id: node.node_id,
|
|
12
|
+
kind: node.kind,
|
|
13
|
+
basis: "node-id",
|
|
14
|
+
field: "node_id",
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
: [];
|
|
18
|
+
if (!kindMatches(node, seed.kind))
|
|
19
|
+
return [];
|
|
20
|
+
const mode = seed.match ?? (seed.kind === "string" ? "contains" : "exact");
|
|
21
|
+
const query = normalized(seed.value, seed.case_sensitive);
|
|
22
|
+
const match = candidateFields(node, seed.kind).find((candidate) => {
|
|
23
|
+
const value = normalized(candidate.value, seed.case_sensitive);
|
|
24
|
+
return mode === "exact" ? value === query : value.includes(query);
|
|
25
|
+
});
|
|
26
|
+
return match === undefined
|
|
27
|
+
? []
|
|
28
|
+
: [
|
|
29
|
+
{
|
|
30
|
+
node_id: node.node_id,
|
|
31
|
+
kind: node.kind,
|
|
32
|
+
basis: match.basis,
|
|
33
|
+
field: match.field,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
};
|
|
37
|
+
const kindMatches = (node, kind) => {
|
|
38
|
+
if (kind === "string")
|
|
39
|
+
return true;
|
|
40
|
+
if (kind === "route")
|
|
41
|
+
return (node.kind === "endpoint" &&
|
|
42
|
+
node.observations.some(({ properties }) => properties.endpoint_kind === "route"));
|
|
43
|
+
if (kind === "api")
|
|
44
|
+
return node.kind === "context-bridge-api";
|
|
45
|
+
if (kind === "channel")
|
|
46
|
+
return node.kind === "ipc-channel";
|
|
47
|
+
if (kind === "module")
|
|
48
|
+
return ["javascript-asset", "javascript-module", "source-module"].includes(node.kind);
|
|
49
|
+
return node.kind === "native-export";
|
|
50
|
+
};
|
|
51
|
+
const candidateFields = (node, seedKind) => {
|
|
52
|
+
const fields = [
|
|
53
|
+
...identityFields(node),
|
|
54
|
+
...node.observations.flatMap((observation, index) => [
|
|
55
|
+
...(observation.label === null
|
|
56
|
+
? []
|
|
57
|
+
: [
|
|
58
|
+
{
|
|
59
|
+
basis: "label",
|
|
60
|
+
field: `observations[${String(index)}].label`,
|
|
61
|
+
value: observation.label,
|
|
62
|
+
},
|
|
63
|
+
]),
|
|
64
|
+
...propertyFields(observation.properties, `observations[${String(index)}].properties`),
|
|
65
|
+
]),
|
|
66
|
+
];
|
|
67
|
+
if (seedKind === "string")
|
|
68
|
+
return fields;
|
|
69
|
+
const names = relevantFields[seedKind];
|
|
70
|
+
return fields.filter(({ field }) => names.some((name) => field === "identity" || field.endsWith(name)));
|
|
71
|
+
};
|
|
72
|
+
const relevantFields = {
|
|
73
|
+
route: [".label", ".value", ".path"],
|
|
74
|
+
api: [".label", ".api_name", ".key", ".methods", ".members"],
|
|
75
|
+
channel: [".label", ".channel", ".key"],
|
|
76
|
+
module: [
|
|
77
|
+
".label",
|
|
78
|
+
".module_key",
|
|
79
|
+
".path",
|
|
80
|
+
".source",
|
|
81
|
+
".original_source",
|
|
82
|
+
".exports",
|
|
83
|
+
],
|
|
84
|
+
"native-export": [".label", ".key", ".requested_members", ".members"],
|
|
85
|
+
};
|
|
86
|
+
const identityFields = (node) => {
|
|
87
|
+
const identity = node.identity;
|
|
88
|
+
if (identity.strategy === "content-digest")
|
|
89
|
+
return [{ basis: "identity", field: "identity", value: identity.sha256 }];
|
|
90
|
+
if (identity.strategy === "source-map-original")
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
basis: "identity",
|
|
94
|
+
field: "identity.original_source",
|
|
95
|
+
value: identity.original_source,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
if (identity.strategy === "canonical-path")
|
|
99
|
+
return [
|
|
100
|
+
{ basis: "identity", field: "identity.path", value: identity.path },
|
|
101
|
+
];
|
|
102
|
+
if (identity.strategy === "artifact-local-key")
|
|
103
|
+
return [{ basis: "identity", field: "identity.key", value: identity.key }];
|
|
104
|
+
if (identity.strategy === "runtime-instance")
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
basis: "identity",
|
|
108
|
+
field: "identity.runtime_key",
|
|
109
|
+
value: identity.runtime_key,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
return [];
|
|
113
|
+
};
|
|
114
|
+
const propertyFields = (value, prefix) => {
|
|
115
|
+
const output = [];
|
|
116
|
+
const visit = (current, path, depth) => {
|
|
117
|
+
if (output.length >= 512 || depth > 6)
|
|
118
|
+
return;
|
|
119
|
+
if (typeof current === "string") {
|
|
120
|
+
output.push({ basis: "property", field: path, value: current });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(current)) {
|
|
124
|
+
for (const [index, item] of current.entries())
|
|
125
|
+
visit(item, `${path}[${String(index)}]`, depth + 1);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (current !== null && typeof current === "object")
|
|
129
|
+
for (const key of Object.keys(current).sort(compareCodePoints))
|
|
130
|
+
visit(current[key] ?? null, `${path}.${key}`, depth + 1);
|
|
131
|
+
};
|
|
132
|
+
visit(value, prefix, 0);
|
|
133
|
+
return output;
|
|
134
|
+
};
|
|
135
|
+
const normalized = (value, caseSensitive) => {
|
|
136
|
+
const normalizedValue = value.normalize("NFKC");
|
|
137
|
+
return caseSensitive
|
|
138
|
+
? normalizedValue
|
|
139
|
+
: normalizedValue.toLocaleLowerCase("en-US");
|
|
140
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { compareCodePoints, createJavaScriptApplicationGraph, } from "./javascriptApplicationGraph.js";
|
|
4
|
+
import { applicationFeatureTraceResultSchema, } from "./javascriptFeatureTraceSchemas.js";
|
|
5
|
+
import { findApplicationFeatureSeeds, } from "./javascriptFeatureSeed.js";
|
|
6
|
+
import { traverseApplicationFeature, } from "./javascriptFeatureTraversal.js";
|
|
7
|
+
import { buildJavaScriptNativeHandoffs } from "./javascriptNativeHandoff.js";
|
|
8
|
+
/** Trace a literal feature through a bounded, authority-preserving JAG. */
|
|
9
|
+
export const traceApplicationFeature = (input) => {
|
|
10
|
+
const allSeedMatches = findApplicationFeatureSeeds(input.graph.nodes, input.seed);
|
|
11
|
+
const seedMatches = allSeedMatches.slice(0, input.limits.max_seed_matches);
|
|
12
|
+
if (seedMatches.length === 0)
|
|
13
|
+
return noMatchResult(input);
|
|
14
|
+
const traversal = traverseApplicationFeature(input.graph, seedMatches.map(({ node_id: id }) => id), input.direction, input.limits);
|
|
15
|
+
const graph = traceGraph(input, seedMatches, traversal, allSeedMatches.length);
|
|
16
|
+
const allPaths = terminalPaths(traversal, seedMatches);
|
|
17
|
+
const paths = allPaths.slice(0, input.limits.max_paths);
|
|
18
|
+
const nativeHandoffs = buildJavaScriptNativeHandoffs(traversal.nodes, traversal.edges, input.nativeEvidence);
|
|
19
|
+
const evidenceLinks = uniqueSorted([
|
|
20
|
+
input.sourceEvidenceId,
|
|
21
|
+
...nativeHandoffs.flatMap(({ evidence_ids: ids }) => ids),
|
|
22
|
+
]);
|
|
23
|
+
const omissions = traceOmissions(input, traversal, allSeedMatches.length, allPaths.length);
|
|
24
|
+
const semantic = {
|
|
25
|
+
schema_version: 1,
|
|
26
|
+
source_evidence_id: input.sourceEvidenceId,
|
|
27
|
+
source_graph_id: input.graph.graph_id,
|
|
28
|
+
seed: input.seed,
|
|
29
|
+
direction: input.direction,
|
|
30
|
+
seed_matches: seedMatches,
|
|
31
|
+
graph,
|
|
32
|
+
paths,
|
|
33
|
+
native_handoffs: nativeHandoffs,
|
|
34
|
+
summary: {
|
|
35
|
+
matched_seeds: seedMatches.length,
|
|
36
|
+
traced_nodes: traversal.nodes.length,
|
|
37
|
+
traced_edges: traversal.edges.length,
|
|
38
|
+
terminal_paths: paths.length,
|
|
39
|
+
native_handoffs: nativeHandoffs.length,
|
|
40
|
+
...factSummary(traversal.nodes, traversal.edges),
|
|
41
|
+
},
|
|
42
|
+
coverage: {
|
|
43
|
+
status: traceCoverageStatus(input.graph, omissions),
|
|
44
|
+
source_graph_status: input.graph.coverage.status,
|
|
45
|
+
scanned_nodes: input.graph.nodes.length,
|
|
46
|
+
total_seed_matches: allSeedMatches.length,
|
|
47
|
+
...omissions,
|
|
48
|
+
frontier_node_ids: traversal.frontierNodeIds,
|
|
49
|
+
},
|
|
50
|
+
evidence_links: evidenceLinks,
|
|
51
|
+
limitations: traceLimitations(input.graph, omissions),
|
|
52
|
+
};
|
|
53
|
+
return applicationFeatureTraceResultSchema.parse({
|
|
54
|
+
...semantic,
|
|
55
|
+
trace_id: `jatr_${digestCanonical(semantic)}`,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const noMatchResult = (input) => {
|
|
59
|
+
const semantic = {
|
|
60
|
+
schema_version: 1,
|
|
61
|
+
source_evidence_id: input.sourceEvidenceId,
|
|
62
|
+
source_graph_id: input.graph.graph_id,
|
|
63
|
+
seed: input.seed,
|
|
64
|
+
direction: input.direction,
|
|
65
|
+
seed_matches: [],
|
|
66
|
+
graph: null,
|
|
67
|
+
paths: [],
|
|
68
|
+
native_handoffs: [],
|
|
69
|
+
summary: {
|
|
70
|
+
matched_seeds: 0,
|
|
71
|
+
traced_nodes: 0,
|
|
72
|
+
traced_edges: 0,
|
|
73
|
+
terminal_paths: 0,
|
|
74
|
+
native_handoffs: 0,
|
|
75
|
+
observed_facts: 0,
|
|
76
|
+
inferred_facts: 0,
|
|
77
|
+
unknown_facts: 0,
|
|
78
|
+
unavailable_facts: 0,
|
|
79
|
+
},
|
|
80
|
+
coverage: {
|
|
81
|
+
status: "no-match",
|
|
82
|
+
source_graph_status: input.graph.coverage.status,
|
|
83
|
+
scanned_nodes: input.graph.nodes.length,
|
|
84
|
+
total_seed_matches: 0,
|
|
85
|
+
omitted_seed_matches: 0,
|
|
86
|
+
omitted_nodes: 0,
|
|
87
|
+
omitted_edges: 0,
|
|
88
|
+
omitted_paths: 0,
|
|
89
|
+
frontier_node_ids: [],
|
|
90
|
+
},
|
|
91
|
+
evidence_links: [input.sourceEvidenceId],
|
|
92
|
+
limitations: uniqueSorted([
|
|
93
|
+
"No graph entity matched the literal seed; this is not evidence that the feature is absent.",
|
|
94
|
+
...sourceCoverageLimitations(input.graph),
|
|
95
|
+
]),
|
|
96
|
+
};
|
|
97
|
+
return applicationFeatureTraceResultSchema.parse({
|
|
98
|
+
...semantic,
|
|
99
|
+
trace_id: `jatr_${digestCanonical(semantic)}`,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const traceGraph = (input, seedMatches, traversal, totalSeedMatches) => {
|
|
103
|
+
const omitted = traversal.omittedNodeCount +
|
|
104
|
+
traversal.omittedEdgeCount +
|
|
105
|
+
(totalSeedMatches - seedMatches.length);
|
|
106
|
+
return createJavaScriptApplicationGraph({
|
|
107
|
+
schema: "JavaScriptApplicationGraph",
|
|
108
|
+
schema_version: 1,
|
|
109
|
+
root_node_ids: uniqueSorted(seedMatches.map(({ node_id: id }) => id)),
|
|
110
|
+
nodes: traversal.nodes,
|
|
111
|
+
edges: traversal.edges,
|
|
112
|
+
coverage: traceGraphCoverage(input.graph, input.limits, omitted),
|
|
113
|
+
limitations: uniqueSorted([
|
|
114
|
+
...input.graph.limitations,
|
|
115
|
+
"Trace edges retain their source authority; graph connectivity does not prove runtime execution or reachability.",
|
|
116
|
+
...(omitted === 0
|
|
117
|
+
? []
|
|
118
|
+
: ["Trace output stopped at explicit caller bounds."]),
|
|
119
|
+
]),
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const traceGraphCoverage = (source, limits, omitted) => {
|
|
123
|
+
if (omitted > 0)
|
|
124
|
+
return {
|
|
125
|
+
status: "partial",
|
|
126
|
+
truncated: true,
|
|
127
|
+
omitted_count: omitted,
|
|
128
|
+
limits: [
|
|
129
|
+
{
|
|
130
|
+
name: "max-seed-matches",
|
|
131
|
+
value: limits.max_seed_matches,
|
|
132
|
+
unit: "items",
|
|
133
|
+
},
|
|
134
|
+
{ name: "max-depth", value: limits.max_depth, unit: "depth" },
|
|
135
|
+
{ name: "max-nodes", value: limits.max_nodes, unit: "items" },
|
|
136
|
+
{ name: "max-edges", value: limits.max_edges, unit: "items" },
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
return source.coverage.status === "complete"
|
|
140
|
+
? { status: "complete", truncated: false, omitted_count: 0, limits: [] }
|
|
141
|
+
: {
|
|
142
|
+
status: source.coverage.status,
|
|
143
|
+
truncated: source.coverage.truncated,
|
|
144
|
+
omitted_count: source.coverage.omitted_count,
|
|
145
|
+
limits: source.coverage.limits,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
const terminalPaths = (traversal, seeds) => {
|
|
149
|
+
const seedIds = new Set(seeds.map(({ node_id: id }) => id));
|
|
150
|
+
const edgeById = new Map(traversal.edges.map((edge) => [edge.edge_id, edge]));
|
|
151
|
+
return traversal.nodes
|
|
152
|
+
.filter((node) => isTerminal(node) &&
|
|
153
|
+
(!seedIds.has(node.node_id) || traversal.nodes.length === 1))
|
|
154
|
+
.map((node) => pathTo(node, traversal.predecessors, edgeById))
|
|
155
|
+
.filter((path) => path !== null)
|
|
156
|
+
.sort((left, right) => compareCodePoints(left.path_id, right.path_id));
|
|
157
|
+
};
|
|
158
|
+
const pathTo = (node, predecessors, edgeById) => {
|
|
159
|
+
const nodeIds = [node.node_id];
|
|
160
|
+
const edgeIds = [];
|
|
161
|
+
let current = node.node_id;
|
|
162
|
+
while (predecessors.has(current)) {
|
|
163
|
+
const predecessor = predecessors.get(current);
|
|
164
|
+
if (predecessor === undefined)
|
|
165
|
+
break;
|
|
166
|
+
nodeIds.push(predecessor.previousNodeId);
|
|
167
|
+
edgeIds.push(predecessor.edgeId);
|
|
168
|
+
current = predecessor.previousNodeId;
|
|
169
|
+
}
|
|
170
|
+
nodeIds.reverse();
|
|
171
|
+
edgeIds.reverse();
|
|
172
|
+
const edges = edgeIds
|
|
173
|
+
.map((edgeId) => edgeById.get(edgeId))
|
|
174
|
+
.filter((edge) => edge !== undefined);
|
|
175
|
+
if (edges.length !== edgeIds.length)
|
|
176
|
+
return null;
|
|
177
|
+
const semantic = {
|
|
178
|
+
start_node_id: nodeIds[0] ?? node.node_id,
|
|
179
|
+
end_node_id: node.node_id,
|
|
180
|
+
end_kind: node.kind,
|
|
181
|
+
node_ids: nodeIds,
|
|
182
|
+
edge_ids: edgeIds,
|
|
183
|
+
authorities: uniqueSorted(edges.map(({ evidence }) => evidence.authority)),
|
|
184
|
+
contains_inference: edges.some(({ evidence }) => evidence.state === "inferred"),
|
|
185
|
+
};
|
|
186
|
+
return { ...semantic, path_id: `jatp_${digestCanonical(semantic)}` };
|
|
187
|
+
};
|
|
188
|
+
const isTerminal = (node) => ["endpoint", "storage", "native-addon", "native-export", "unknown"].includes(node.kind);
|
|
189
|
+
const factSummary = (nodes, edges) => {
|
|
190
|
+
const states = [
|
|
191
|
+
...nodes.flatMap(({ observations }) => observations.map(({ evidence }) => evidence.state)),
|
|
192
|
+
...edges.map(({ evidence }) => evidence.state),
|
|
193
|
+
];
|
|
194
|
+
return {
|
|
195
|
+
observed_facts: states.filter((state) => state === "observed").length,
|
|
196
|
+
inferred_facts: states.filter((state) => state === "inferred").length,
|
|
197
|
+
unknown_facts: states.filter((state) => state === "unknown").length,
|
|
198
|
+
unavailable_facts: states.filter((state) => state === "unavailable").length,
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
const traceOmissions = (input, traversal, seedMatches, paths) => ({
|
|
202
|
+
omitted_seed_matches: Math.max(0, seedMatches - input.limits.max_seed_matches),
|
|
203
|
+
omitted_nodes: traversal.omittedNodeCount,
|
|
204
|
+
omitted_edges: traversal.omittedEdgeCount,
|
|
205
|
+
omitted_paths: Math.max(0, paths - input.limits.max_paths),
|
|
206
|
+
});
|
|
207
|
+
const traceCoverageStatus = (graph, omissions) => Object.values(omissions).some((value) => value > 0)
|
|
208
|
+
? "truncated"
|
|
209
|
+
: graph.coverage.status === "complete"
|
|
210
|
+
? "complete-within-source"
|
|
211
|
+
: "partial";
|
|
212
|
+
const traceLimitations = (graph, omissions) => uniqueSorted([
|
|
213
|
+
"A trace reports graph relationships, not proof that code executed or that a feature is reachable in every state.",
|
|
214
|
+
"Static, native, passive-runtime, inferred, and unknown facts retain their original authority in the returned graph.",
|
|
215
|
+
"Native handoffs never open a binary or invoke a provider automatically; snapshot reuse remains provider/profile/target exact.",
|
|
216
|
+
...sourceCoverageLimitations(graph),
|
|
217
|
+
...(Object.values(omissions).some((value) => value > 0)
|
|
218
|
+
? ["Trace results were truncated at explicit caller limits."]
|
|
219
|
+
: []),
|
|
220
|
+
]);
|
|
221
|
+
const sourceCoverageLimitations = (graph) => graph.coverage.status === "complete"
|
|
222
|
+
? []
|
|
223
|
+
: [
|
|
224
|
+
"The source application graph is incomplete; unmatched seeds and frontiers remain unknown.",
|
|
225
|
+
];
|
|
226
|
+
const digestCanonical = (value) => {
|
|
227
|
+
const encoded = canonicalize(value);
|
|
228
|
+
if (encoded === undefined)
|
|
229
|
+
throw new TypeError("Feature trace could not canonicalize data");
|
|
230
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
231
|
+
};
|
|
232
|
+
const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
|