rea-agents 1.7.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 +38 -31
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/Doctor.js +56 -6
- 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 +38 -13
- package/dist/application/InvestigationProviders.js +30 -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 +7 -1
- package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
- package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
- package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
- 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/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/ProcessSampling.js +15 -11
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/runtime.js +2 -0
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +5 -1
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +4 -1
- package/dist/cli.js +388 -0
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliCommandNames.js +13 -0
- package/dist/cliElectronCommands.js +103 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/config.js +83 -0
- 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/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/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +47 -45
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +46 -31
- package/dist/doctorRuntime.js +42 -1
- package/dist/domain/artifactComparison.js +3 -4
- 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 +67 -1
- package/dist/domain/functionComparisonSchemas.js +3 -8
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
- package/dist/domain/javascriptApplicationGraph.js +2 -1
- package/dist/domain/javascriptApplicationGraphSchemas.js +7 -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/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 +31 -43
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
- package/dist/domain/javascriptStaticAnalysisState.js +6 -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/GhidraProvider.js +6 -0
- package/dist/main.js +14 -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 +52 -0
- 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 +58 -13
- 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 +8 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +51 -86
- 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 +12 -4
- package/scripts/rea.mjs +1 -1
- package/skills/rea-analysis/SKILL.md +59 -3
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { compareCodePoints, createJavaScriptApplicationEdge, } from "./javascriptApplicationGraph.js";
|
|
2
|
+
import { digestCanonical, } from "./javascriptRuntimeReconciliationParsing.js";
|
|
3
|
+
import { mappedRuntimePaths, } from "./javascriptRuntimeStaticCandidates.js";
|
|
4
|
+
/** Match runtime entities conservatively and emit only inferred observed_as edges. */
|
|
5
|
+
export const reconcileRuntimeEntities = (input) => {
|
|
6
|
+
const index = indexCandidates(input.candidates);
|
|
7
|
+
const evaluated = input.entities.map((entity) => reconcileEntity(entity, index, input.layers));
|
|
8
|
+
const retained = evaluated
|
|
9
|
+
.sort((left, right) => compareCodePoints(left.item.reconciliation_id, right.item.reconciliation_id))
|
|
10
|
+
.slice(0, input.maximumItems);
|
|
11
|
+
return {
|
|
12
|
+
items: retained.map(({ item }) => item),
|
|
13
|
+
edges: retained.flatMap(({ edge }) => (edge === null ? [] : [edge])),
|
|
14
|
+
omittedItems: evaluated.length - retained.length,
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const reconcileEntity = (entity, index, layers) => {
|
|
18
|
+
const locations = candidatesWithLocation(entity, index, layers);
|
|
19
|
+
if (entity.sourceSha256 !== null) {
|
|
20
|
+
const content = index.byCategoryDigest.get(categoryDigestKey(entity.staticKind, entity.sourceSha256)) ?? [];
|
|
21
|
+
const locatedKeys = new Set(locations.map(({ candidate }) => candidateKey(candidate)));
|
|
22
|
+
const combined = content.filter((candidate) => locatedKeys.has(candidateKey(candidate)));
|
|
23
|
+
if (combined.length === 1)
|
|
24
|
+
return matched(entity, combined[0], {
|
|
25
|
+
basis: "content-and-location",
|
|
26
|
+
confidence: "high",
|
|
27
|
+
reason: "content-and-location-match",
|
|
28
|
+
});
|
|
29
|
+
if (content.length === 1) {
|
|
30
|
+
const candidate = content[0];
|
|
31
|
+
const digestBasis = candidate.digests.find(({ sha256 }) => sha256 === entity.sourceSha256)?.basis;
|
|
32
|
+
return matched(entity, candidate, {
|
|
33
|
+
basis: digestBasis ?? "content-sha256",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
reason: "unique-content-match",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (content.length > 1)
|
|
39
|
+
return unresolved(entity, {
|
|
40
|
+
status: "ambiguous",
|
|
41
|
+
basis: "none",
|
|
42
|
+
confidence: "unknown",
|
|
43
|
+
reason: "ambiguous-static-candidates",
|
|
44
|
+
candidates: content,
|
|
45
|
+
});
|
|
46
|
+
if (locations.length > 0)
|
|
47
|
+
return unresolved(entity, {
|
|
48
|
+
status: "unmatched",
|
|
49
|
+
basis: strongestLocationBasis(locations),
|
|
50
|
+
confidence: "high",
|
|
51
|
+
reason: "captured-content-disagrees-with-static-location",
|
|
52
|
+
candidates: locations.map(({ candidate }) => candidate),
|
|
53
|
+
});
|
|
54
|
+
return unresolved(entity, {
|
|
55
|
+
status: "unmatched",
|
|
56
|
+
basis: "none",
|
|
57
|
+
confidence: "high",
|
|
58
|
+
reason: "no-static-content-match",
|
|
59
|
+
candidates: [],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (locations.length === 1) {
|
|
63
|
+
const located = locations[0];
|
|
64
|
+
return matched(entity, located.candidate, {
|
|
65
|
+
basis: located.mapping.basis,
|
|
66
|
+
confidence: "medium",
|
|
67
|
+
reason: "unique-location-match",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (locations.length > 1)
|
|
71
|
+
return unresolved(entity, {
|
|
72
|
+
status: "ambiguous",
|
|
73
|
+
basis: strongestLocationBasis(locations),
|
|
74
|
+
confidence: "unknown",
|
|
75
|
+
reason: "ambiguous-static-candidates",
|
|
76
|
+
candidates: locations.map(({ candidate }) => candidate),
|
|
77
|
+
});
|
|
78
|
+
const mapped = layers.some((layer) => mappedRuntimePaths(layer, entity).length > 0);
|
|
79
|
+
return unresolved(entity, {
|
|
80
|
+
status: mapped ? "unmatched" : "unknown",
|
|
81
|
+
basis: "none",
|
|
82
|
+
confidence: "unknown",
|
|
83
|
+
reason: mapped
|
|
84
|
+
? "no-static-content-match"
|
|
85
|
+
: "no-authorized-location-mapping",
|
|
86
|
+
candidates: [],
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const candidatesWithLocation = (entity, index, layers) => {
|
|
90
|
+
const located = new Map();
|
|
91
|
+
for (const layer of layers)
|
|
92
|
+
for (const mapping of mappedRuntimePaths(layer, entity))
|
|
93
|
+
for (const candidate of index.byLayerCategoryPath.get(layerCategoryPathKey(layer.layerId, entity.staticKind, mapping.path)) ?? []) {
|
|
94
|
+
const key = candidateKey(candidate);
|
|
95
|
+
if (!located.has(key))
|
|
96
|
+
located.set(key, { candidate, mapping });
|
|
97
|
+
}
|
|
98
|
+
return [...located.values()].sort((left, right) => compareCodePoints(candidateKey(left.candidate), candidateKey(right.candidate)));
|
|
99
|
+
};
|
|
100
|
+
const indexCandidates = (candidates) => {
|
|
101
|
+
const byCategoryDigest = new Map();
|
|
102
|
+
const byLayerCategoryPath = new Map();
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
for (const { sha256 } of candidate.digests)
|
|
105
|
+
appendIndex(byCategoryDigest, categoryDigestKey(candidate.category, sha256), candidate);
|
|
106
|
+
for (const path of candidate.paths)
|
|
107
|
+
appendIndex(byLayerCategoryPath, layerCategoryPathKey(candidate.layer.layerId, candidate.category, path), candidate);
|
|
108
|
+
}
|
|
109
|
+
return { byCategoryDigest, byLayerCategoryPath };
|
|
110
|
+
};
|
|
111
|
+
const appendIndex = (index, key, candidate) => {
|
|
112
|
+
const values = index.get(key);
|
|
113
|
+
if (values === undefined)
|
|
114
|
+
index.set(key, [candidate]);
|
|
115
|
+
else
|
|
116
|
+
values.push(candidate);
|
|
117
|
+
};
|
|
118
|
+
const categoryDigestKey = (category, digest) => `${category}\0${digest}`;
|
|
119
|
+
const layerCategoryPathKey = (layerId, category, path) => `${layerId}\0${category}\0${path}`;
|
|
120
|
+
const matched = (entity, candidate, input) => {
|
|
121
|
+
const item = createItem(entity, {
|
|
122
|
+
layer: candidate.layer,
|
|
123
|
+
staticNodeId: candidate.node.node_id,
|
|
124
|
+
status: "matched",
|
|
125
|
+
basis: input.basis,
|
|
126
|
+
confidence: input.confidence,
|
|
127
|
+
reason: input.reason,
|
|
128
|
+
candidates: [candidate],
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
item,
|
|
132
|
+
edge: createJavaScriptApplicationEdge({
|
|
133
|
+
source_node_id: candidate.node.node_id,
|
|
134
|
+
target_node_id: entity.node.node_id,
|
|
135
|
+
relation: "observed_as",
|
|
136
|
+
properties: {
|
|
137
|
+
reconciliation_id: item.reconciliation_id,
|
|
138
|
+
basis: input.basis,
|
|
139
|
+
static_layer_id: candidate.layer.layerId,
|
|
140
|
+
},
|
|
141
|
+
evidence: reconciliationEvidence(entity, candidate.layer, input.basis, input.confidence),
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const unresolved = (entity, input) => ({
|
|
146
|
+
item: createItem(entity, {
|
|
147
|
+
layer: null,
|
|
148
|
+
staticNodeId: null,
|
|
149
|
+
status: input.status,
|
|
150
|
+
basis: input.basis,
|
|
151
|
+
confidence: input.confidence,
|
|
152
|
+
reason: input.reason,
|
|
153
|
+
candidates: input.candidates,
|
|
154
|
+
}),
|
|
155
|
+
edge: null,
|
|
156
|
+
});
|
|
157
|
+
const createItem = (entity, input) => {
|
|
158
|
+
const candidateReferences = uniqueCandidateReferences(input.candidates);
|
|
159
|
+
const semantic = {
|
|
160
|
+
entity_kind: entity.kind,
|
|
161
|
+
runtime_evidence_id: entity.capture.evidence.evidence_id,
|
|
162
|
+
runtime_node_id: entity.node.node_id,
|
|
163
|
+
static_layer_id: input.layer?.layerId ?? null,
|
|
164
|
+
static_node_id: input.staticNodeId,
|
|
165
|
+
status: input.status,
|
|
166
|
+
basis: input.basis,
|
|
167
|
+
confidence: input.confidence,
|
|
168
|
+
reason: input.reason,
|
|
169
|
+
candidate_static_count: candidateReferences.count,
|
|
170
|
+
candidate_static_nodes: candidateReferences.items,
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
reconciliation_id: `jrr_item_${digestCanonical(semantic)}`,
|
|
174
|
+
...semantic,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
const reconciliationEvidence = (entity, layer, basis, confidence) => ({
|
|
178
|
+
authority: "cross-layer-reconciliation",
|
|
179
|
+
state: "inferred",
|
|
180
|
+
confidence: confidence === "medium" ? "medium" : "high",
|
|
181
|
+
artifact: entity.sourceSha256 === null
|
|
182
|
+
? {
|
|
183
|
+
available: false,
|
|
184
|
+
reason: "not-observed",
|
|
185
|
+
detail: "Runtime source bytes were not captured for this mapping.",
|
|
186
|
+
}
|
|
187
|
+
: {
|
|
188
|
+
available: true,
|
|
189
|
+
artifact_id: `art_${entity.sourceSha256}`,
|
|
190
|
+
sha256: entity.sourceSha256,
|
|
191
|
+
},
|
|
192
|
+
location: entity.node.observations[0].evidence.location,
|
|
193
|
+
extractor: {
|
|
194
|
+
name: "rea-javascript-runtime-reconciliation",
|
|
195
|
+
version: "1",
|
|
196
|
+
operation: "reconcile_javascript_runtime",
|
|
197
|
+
executable_sha256: null,
|
|
198
|
+
},
|
|
199
|
+
coverage: layer.graph.coverage.status === "complete" &&
|
|
200
|
+
entity.capture.scriptsCompleteWithinScope
|
|
201
|
+
? { status: "complete", truncated: false, omitted_count: 0, limits: [] }
|
|
202
|
+
: {
|
|
203
|
+
status: "partial",
|
|
204
|
+
truncated: false,
|
|
205
|
+
omitted_count: null,
|
|
206
|
+
limits: [],
|
|
207
|
+
},
|
|
208
|
+
limitations: [
|
|
209
|
+
basis === "content-sha256" ||
|
|
210
|
+
basis === "module-source-sha256" ||
|
|
211
|
+
basis === "content-and-location"
|
|
212
|
+
? "Equal content digests establish byte identity, not execution causality."
|
|
213
|
+
: "Location correspondence is inferred from an artifact root or caller-declared mapping.",
|
|
214
|
+
"Passive script parsing or target presence does not prove feature execution.",
|
|
215
|
+
],
|
|
216
|
+
evidence_ids: [
|
|
217
|
+
layer.evidence.evidence_id,
|
|
218
|
+
entity.capture.evidence.evidence_id,
|
|
219
|
+
].sort(),
|
|
220
|
+
});
|
|
221
|
+
const strongestLocationBasis = (values) => {
|
|
222
|
+
const bases = new Set(values.map(({ mapping }) => mapping.basis));
|
|
223
|
+
if (bases.has("artifact-path"))
|
|
224
|
+
return "artifact-path";
|
|
225
|
+
if (bases.has("operator-file-mapping"))
|
|
226
|
+
return "operator-file-mapping";
|
|
227
|
+
return "operator-url-mapping";
|
|
228
|
+
};
|
|
229
|
+
const candidateKey = (candidate) => `${candidate.layer.layerId}:${candidate.node.node_id}`;
|
|
230
|
+
const uniqueCandidateReferences = (candidates) => {
|
|
231
|
+
const seen = new Set();
|
|
232
|
+
let count = 0;
|
|
233
|
+
const items = [];
|
|
234
|
+
for (const { layer, node } of candidates) {
|
|
235
|
+
const key = `${layer.layerId}:${node.node_id}`;
|
|
236
|
+
if (seen.has(key))
|
|
237
|
+
continue;
|
|
238
|
+
seen.add(key);
|
|
239
|
+
count += 1;
|
|
240
|
+
if (items.length < 1_000)
|
|
241
|
+
items.push({
|
|
242
|
+
static_layer_id: layer.layerId,
|
|
243
|
+
static_node_id: node.node_id,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return { count, items };
|
|
247
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { browserAllowedOriginsSchema, browserEndpointSchema, webPageInspectionSchema, } from "./browserObservation.js";
|
|
5
|
+
import { electronFileRootsSchema, electronPageInspectionSchema, } from "./electronObservation.js";
|
|
6
|
+
import { parseEvidence } from "./evidence.js";
|
|
7
|
+
import { javascriptApplicationAnalysisResultSchema, } from "./javascriptApplicationAnalysis.js";
|
|
8
|
+
import { compareCodePoints, } from "./javascriptApplicationGraph.js";
|
|
9
|
+
/** Parse and semantically bind every static analysis Evidence layer. */
|
|
10
|
+
export const parseStaticLayers = (layers) => layers
|
|
11
|
+
.map((layer) => parseStaticLayer(layer))
|
|
12
|
+
.sort((left, right) => compareCodePoints(left.layerId, right.layerId));
|
|
13
|
+
/** Parse only supported passive web/Electron inspection Evidence. */
|
|
14
|
+
export const parseRuntimeCaptures = (observations) => observations
|
|
15
|
+
.map((observation) => parseRuntimeCapture(observation))
|
|
16
|
+
.sort((left, right) => compareCodePoints(left.evidence.evidence_id, right.evidence.evidence_id));
|
|
17
|
+
export const digestCanonical = (value) => {
|
|
18
|
+
const encoded = canonicalize(value);
|
|
19
|
+
if (encoded === undefined)
|
|
20
|
+
throw new TypeError("Runtime reconciliation could not canonicalize input");
|
|
21
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
22
|
+
};
|
|
23
|
+
const parseStaticLayer = (layer) => {
|
|
24
|
+
const evidence = parseEvidence(layer.analysis);
|
|
25
|
+
assertEvidenceIdentity(evidence, {
|
|
26
|
+
operation: "analyze_javascript_application",
|
|
27
|
+
predicate: "rea.javascript-application-analysis/v1",
|
|
28
|
+
providerId: "rea-javascript-application",
|
|
29
|
+
providerName: "REA JavaScript application analyzer",
|
|
30
|
+
providerVersion: "1",
|
|
31
|
+
authority: "shipped-artifact",
|
|
32
|
+
confidence: "derived",
|
|
33
|
+
});
|
|
34
|
+
const result = javascriptApplicationAnalysisResultSchema.parse(evidence.normalized_result);
|
|
35
|
+
if (evidence.subject === null ||
|
|
36
|
+
evidence.subject.digest.sha256 !== result.root_artifact_sha256 ||
|
|
37
|
+
evidence.subject.format !== result.format ||
|
|
38
|
+
evidence.subject.local_path !== result.input_path)
|
|
39
|
+
throw new TypeError("JavaScript application Evidence subject disagrees with its result");
|
|
40
|
+
const parameters = z
|
|
41
|
+
.object({
|
|
42
|
+
approved: z.literal(true),
|
|
43
|
+
source_map_read_approved: z.boolean(),
|
|
44
|
+
})
|
|
45
|
+
.passthrough()
|
|
46
|
+
.parse(evidence.parameters);
|
|
47
|
+
return {
|
|
48
|
+
layerId: `jrl_${digestCanonical({
|
|
49
|
+
role: layer.role,
|
|
50
|
+
evidence_id: evidence.evidence_id,
|
|
51
|
+
runtime_mappings: layer.runtime_mappings,
|
|
52
|
+
})}`,
|
|
53
|
+
role: layer.role,
|
|
54
|
+
evidence,
|
|
55
|
+
result,
|
|
56
|
+
graph: result.graph,
|
|
57
|
+
runtimeMappings: layer.runtime_mappings,
|
|
58
|
+
sourceMapReadApproved: parameters.source_map_read_approved,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const parseRuntimeCapture = (input) => {
|
|
62
|
+
const evidence = parseEvidence(input);
|
|
63
|
+
if (evidence.operation === "inspect_web_page") {
|
|
64
|
+
assertEvidenceIdentity(evidence, {
|
|
65
|
+
operation: "inspect_web_page",
|
|
66
|
+
predicate: "rea.web-page-inspection/v2",
|
|
67
|
+
providerId: "rea-cdp-browser",
|
|
68
|
+
providerName: "REA Chrome DevTools Protocol observation provider",
|
|
69
|
+
providerVersion: "2",
|
|
70
|
+
authority: "external-service",
|
|
71
|
+
confidence: "observed",
|
|
72
|
+
});
|
|
73
|
+
const inspection = webPageInspectionSchema.parse(evidence.normalized_result);
|
|
74
|
+
assertRuntimeParameters(evidence, {
|
|
75
|
+
kind: "browser",
|
|
76
|
+
targetId: inspection.target.target_id,
|
|
77
|
+
targetOrigin: inspection.target.origin,
|
|
78
|
+
sourceIncluded: inspection.scripts.items.some(({ source }) => source.included),
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
kind: "browser",
|
|
82
|
+
evidence,
|
|
83
|
+
inspection,
|
|
84
|
+
captureSha256: digestCanonical(inspection),
|
|
85
|
+
scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (evidence.operation === "inspect_electron_page") {
|
|
89
|
+
assertEvidenceIdentity(evidence, {
|
|
90
|
+
operation: "inspect_electron_page",
|
|
91
|
+
predicate: "rea.electron-page-inspection/v1",
|
|
92
|
+
providerId: "rea-cdp-electron",
|
|
93
|
+
providerName: "REA Electron file-page CDP observation provider",
|
|
94
|
+
providerVersion: "1",
|
|
95
|
+
authority: "external-service",
|
|
96
|
+
confidence: "observed",
|
|
97
|
+
});
|
|
98
|
+
const inspection = electronPageInspectionSchema.parse(evidence.normalized_result);
|
|
99
|
+
assertRuntimeParameters(evidence, {
|
|
100
|
+
kind: "electron",
|
|
101
|
+
targetId: inspection.target.target_id,
|
|
102
|
+
sourceIncluded: inspection.scripts.items.some(({ source }) => source.included),
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
kind: "electron",
|
|
106
|
+
evidence,
|
|
107
|
+
inspection,
|
|
108
|
+
captureSha256: digestCanonical(inspection),
|
|
109
|
+
scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
throw new TypeError("Runtime reconciliation requires inspect_web_page or inspect_electron_page Evidence");
|
|
113
|
+
};
|
|
114
|
+
const assertRuntimeParameters = (evidence, expected) => {
|
|
115
|
+
const common = {
|
|
116
|
+
target_id: z.string().trim().min(1).max(256),
|
|
117
|
+
cdp_endpoint: browserEndpointSchema,
|
|
118
|
+
include_script_sources: z.boolean(),
|
|
119
|
+
};
|
|
120
|
+
if (expected.kind === "browser") {
|
|
121
|
+
const parameters = z
|
|
122
|
+
.object({ ...common, allowed_origins: browserAllowedOriginsSchema })
|
|
123
|
+
.passthrough()
|
|
124
|
+
.parse(evidence.parameters);
|
|
125
|
+
if (parameters.target_id !== expected.targetId)
|
|
126
|
+
throw new TypeError("Runtime Evidence target disagrees with its captured result");
|
|
127
|
+
if (!parameters.allowed_origins.includes(expected.targetOrigin))
|
|
128
|
+
throw new TypeError("Browser Evidence target is outside its recorded origin scope");
|
|
129
|
+
if (expected.sourceIncluded && !parameters.include_script_sources)
|
|
130
|
+
throw new TypeError("Runtime Evidence contains source without source-capture approval");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const parameters = z
|
|
134
|
+
.object({
|
|
135
|
+
...common,
|
|
136
|
+
allowed_file_roots: electronFileRootsSchema,
|
|
137
|
+
source_capture_approved: z.boolean(),
|
|
138
|
+
})
|
|
139
|
+
.passthrough()
|
|
140
|
+
.parse(evidence.parameters);
|
|
141
|
+
if (parameters.target_id !== expected.targetId)
|
|
142
|
+
throw new TypeError("Runtime Evidence target disagrees with its captured result");
|
|
143
|
+
if (expected.sourceIncluded &&
|
|
144
|
+
(!parameters.include_script_sources ||
|
|
145
|
+
parameters.source_capture_approved !== true))
|
|
146
|
+
throw new TypeError("Runtime Evidence contains source without source-capture approval");
|
|
147
|
+
};
|
|
148
|
+
const scriptsComplete = (completeness) => !completeness.truncated_sections.includes("scripts") &&
|
|
149
|
+
!completeness.unavailable_sections.includes("scripts") &&
|
|
150
|
+
completeness.dropped_events.scripts === 0;
|
|
151
|
+
const assertEvidenceIdentity = (evidence, expected) => {
|
|
152
|
+
if (evidence.operation !== expected.operation ||
|
|
153
|
+
evidence.predicate_type !== expected.predicate ||
|
|
154
|
+
evidence.provider.id !== expected.providerId ||
|
|
155
|
+
evidence.provider.name !== expected.providerName ||
|
|
156
|
+
evidence.provider.version !== expected.providerVersion ||
|
|
157
|
+
evidence.authority !== expected.authority ||
|
|
158
|
+
evidence.confidence !== expected.confidence)
|
|
159
|
+
throw new TypeError(`Evidence does not match the supported ${expected.operation} contract`);
|
|
160
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { compareCodePoints, } from "./javascriptApplicationGraph.js";
|
|
2
|
+
import { digestCanonical, } from "./javascriptRuntimeReconciliationParsing.js";
|
|
3
|
+
import { javascriptRuntimeReconciliationResultSchema, } from "./javascriptRuntimeReconciliationSchemas.js";
|
|
4
|
+
/** Build and verify the deterministic caller-visible reconciliation result. */
|
|
5
|
+
export const createJavaScriptRuntimeReconciliationResult = (input) => {
|
|
6
|
+
const completion = completionFlags(input);
|
|
7
|
+
const semantic = {
|
|
8
|
+
schema_version: 1,
|
|
9
|
+
static_layers: staticLayerSummaries(input.layers),
|
|
10
|
+
runtime_captures: captureSummaries(input.captures, input.runtime),
|
|
11
|
+
graph: input.graph,
|
|
12
|
+
summary: reconciliationSummary(input),
|
|
13
|
+
reconciliations: input.matching.items,
|
|
14
|
+
static_load_states: input.loadStates.states,
|
|
15
|
+
source_map_authority: sourceMapAuthority(input),
|
|
16
|
+
coverage: reconciliationCoverage(input, completion),
|
|
17
|
+
evidence_links: uniqueSorted([
|
|
18
|
+
...input.layers.map(({ evidence }) => evidence.evidence_id),
|
|
19
|
+
...input.captures.map(({ evidence }) => evidence.evidence_id),
|
|
20
|
+
]),
|
|
21
|
+
limitations: reconciliationLimitations(completion),
|
|
22
|
+
};
|
|
23
|
+
return javascriptRuntimeReconciliationResultSchema.parse({
|
|
24
|
+
reconciliation_id: `jrr_${digestCanonical(semantic)}`,
|
|
25
|
+
...semantic,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const staticLayerSummaries = (layers) => layers.map((layer) => ({
|
|
29
|
+
layer_id: layer.layerId,
|
|
30
|
+
role: layer.role,
|
|
31
|
+
evidence_id: layer.evidence.evidence_id,
|
|
32
|
+
graph_id: layer.graph.graph_id,
|
|
33
|
+
root_artifact_sha256: layer.result.root_artifact_sha256,
|
|
34
|
+
input_path: layer.result.input_path,
|
|
35
|
+
format: layer.result.format,
|
|
36
|
+
runtime_mappings: [...layer.runtimeMappings],
|
|
37
|
+
}));
|
|
38
|
+
const captureSummaries = (captures, runtime) => captures.map((capture) => {
|
|
39
|
+
const targetNode = runtime.targetNodeByEvidenceId.get(capture.evidence.evidence_id);
|
|
40
|
+
if (targetNode === undefined)
|
|
41
|
+
throw new TypeError("Runtime projection omitted a required target node");
|
|
42
|
+
return {
|
|
43
|
+
evidence_id: capture.evidence.evidence_id,
|
|
44
|
+
capture_sha256: capture.captureSha256,
|
|
45
|
+
kind: capture.kind,
|
|
46
|
+
target_node_id: targetNode.node_id,
|
|
47
|
+
target_key: capture.inspection.target.target_id,
|
|
48
|
+
target_location: capture.kind === "browser"
|
|
49
|
+
? capture.inspection.target.url.slice(0, 4_096)
|
|
50
|
+
: capture.inspection.target.file_path.slice(0, 4_096),
|
|
51
|
+
frames: capture.inspection.frames.length,
|
|
52
|
+
scripts: capture.inspection.scripts.items.length,
|
|
53
|
+
workers: capture.inspection.workers.length,
|
|
54
|
+
scripts_complete_within_scope: capture.scriptsCompleteWithinScope,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
const reconciliationSummary = (input) => ({
|
|
58
|
+
runtime_targets: countBy(input.runtime.entities, "kind", "target"),
|
|
59
|
+
runtime_frames: countBy(input.runtime.entities, "kind", "frame"),
|
|
60
|
+
runtime_scripts: countBy(input.runtime.entities, "kind", "script"),
|
|
61
|
+
runtime_workers: countBy(input.runtime.entities, "kind", "worker"),
|
|
62
|
+
matched: countBy(input.matching.items, "status", "matched"),
|
|
63
|
+
ambiguous: countBy(input.matching.items, "status", "ambiguous"),
|
|
64
|
+
unmatched: countBy(input.matching.items, "status", "unmatched"),
|
|
65
|
+
unknown: countBy(input.matching.items, "status", "unknown"),
|
|
66
|
+
static_loaded: countBy(input.loadStates.states, "status", "loaded"),
|
|
67
|
+
static_resident: countBy(input.loadStates.states, "status", "resident-in-loaded-asset"),
|
|
68
|
+
static_not_observed: countBy(input.loadStates.states, "status", "not-observed-in-capture"),
|
|
69
|
+
static_unknown: countBy(input.loadStates.states, "status", "unknown"),
|
|
70
|
+
});
|
|
71
|
+
const sourceMapAuthority = (input) => ({
|
|
72
|
+
used_for_primary_matching: false,
|
|
73
|
+
static_layers_with_read_approval: input.layers.filter(({ sourceMapReadApproved }) => sourceMapReadApproved).length,
|
|
74
|
+
runtime_script_declarations: input.captures.reduce((total, capture) => total +
|
|
75
|
+
(capture.kind === "browser"
|
|
76
|
+
? capture.inspection.scripts.items.filter(({ source_map_url: url }) => url !== null).length
|
|
77
|
+
: 0), 0),
|
|
78
|
+
limitation: "Source-map declarations and approved original-source reads retain separate static authority and are not used as primary runtime byte matches.",
|
|
79
|
+
});
|
|
80
|
+
const reconciliationCoverage = (input, completion) => ({
|
|
81
|
+
status: completion.outputTruncated || completion.inputTruncated
|
|
82
|
+
? "truncated"
|
|
83
|
+
: completion.inputPartial
|
|
84
|
+
? "partial"
|
|
85
|
+
: "complete-within-inputs",
|
|
86
|
+
truncated: completion.outputTruncated || completion.inputTruncated,
|
|
87
|
+
omitted_runtime_entities: input.runtime.omittedEntities,
|
|
88
|
+
omitted_reconciliation_items: input.matching.omittedItems,
|
|
89
|
+
omitted_static_load_states: input.loadStates.omittedStates,
|
|
90
|
+
omitted_graph_items: input.omittedGraphItems,
|
|
91
|
+
});
|
|
92
|
+
const completionFlags = (input) => ({
|
|
93
|
+
outputTruncated: input.runtime.omittedEntities > 0 ||
|
|
94
|
+
input.matching.omittedItems > 0 ||
|
|
95
|
+
input.loadStates.omittedStates > 0 ||
|
|
96
|
+
input.omittedGraphItems > 0,
|
|
97
|
+
inputTruncated: input.layers.some(({ graph }) => graph.coverage.truncated) ||
|
|
98
|
+
input.captures.some(({ inspection }) => inspection.completeness.conditions.includes("truncated")),
|
|
99
|
+
inputPartial: input.layers.some(({ graph }) => graph.coverage.status !== "complete") ||
|
|
100
|
+
input.captures.some(({ inspection }) => inspection.completeness.status !== "complete_within_window"),
|
|
101
|
+
});
|
|
102
|
+
const reconciliationLimitations = (completion) => uniqueSorted([
|
|
103
|
+
"Only caller-supplied, semantically verified Evidence records were reconciled.",
|
|
104
|
+
"Digest equality establishes byte identity but not code reachability, module initialization, or feature execution.",
|
|
105
|
+
"A static module resident in an observed bundle is not reported as executed.",
|
|
106
|
+
"Not-observed means absent from the bounded captures in scope, never globally unloaded.",
|
|
107
|
+
"Caller-declared file and URL mappings are inference inputs and do not expand CDP origin or filesystem-root authority.",
|
|
108
|
+
"Source-map authority remains separate from passive runtime authority.",
|
|
109
|
+
...(completion.inputPartial
|
|
110
|
+
? ["One or more static graphs or passive captures were incomplete."]
|
|
111
|
+
: []),
|
|
112
|
+
...(completion.outputTruncated
|
|
113
|
+
? [
|
|
114
|
+
"Caller limits omitted runtime entities or reconciliation classifications.",
|
|
115
|
+
]
|
|
116
|
+
: []),
|
|
117
|
+
]);
|
|
118
|
+
const countBy = (items, key, value) => items.filter((item) => item[key] === value).length;
|
|
119
|
+
const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
|