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,139 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { applicationCoverageSchema, applicationGraphEvidenceSchema, applicationNodeIdentitySchema, } from "./javascriptApplicationEvidenceSchemas.js";
|
|
3
|
+
import { isJsonWithinLimits } from "./jsonLimits.js";
|
|
4
|
+
import { jsonValueSchema } from "./jsonValue.js";
|
|
5
|
+
const boundedTextSchema = z.string().min(1).max(4_096);
|
|
6
|
+
/** Node kinds admitted by JavaScript Application Graph v1. */
|
|
7
|
+
export const JAVASCRIPT_APPLICATION_NODE_KINDS = [
|
|
8
|
+
"package",
|
|
9
|
+
"installer",
|
|
10
|
+
"artifact",
|
|
11
|
+
"asar-entry",
|
|
12
|
+
"electron-main",
|
|
13
|
+
"electron-preload",
|
|
14
|
+
"electron-renderer",
|
|
15
|
+
"electron-utility",
|
|
16
|
+
"javascript-asset",
|
|
17
|
+
"javascript-chunk",
|
|
18
|
+
"javascript-module",
|
|
19
|
+
"source-map",
|
|
20
|
+
"source-module",
|
|
21
|
+
"browser-window",
|
|
22
|
+
"frame",
|
|
23
|
+
"target",
|
|
24
|
+
"context-bridge-api",
|
|
25
|
+
"ipc-channel",
|
|
26
|
+
"ipc-handler",
|
|
27
|
+
"worker",
|
|
28
|
+
"service-worker",
|
|
29
|
+
"endpoint",
|
|
30
|
+
"storage",
|
|
31
|
+
"native-addon",
|
|
32
|
+
"native-export",
|
|
33
|
+
"managed-assembly",
|
|
34
|
+
"managed-module",
|
|
35
|
+
"managed-type",
|
|
36
|
+
"managed-method",
|
|
37
|
+
"managed-field",
|
|
38
|
+
"managed-pinvoke-import",
|
|
39
|
+
"managed-native-implementation",
|
|
40
|
+
"runtime-script-instance",
|
|
41
|
+
"unknown",
|
|
42
|
+
];
|
|
43
|
+
/** Provider-neutral application entity categories. */
|
|
44
|
+
const applicationNodeKindSchema = z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS);
|
|
45
|
+
/** Directed relations admitted by JavaScript Application Graph v1. */
|
|
46
|
+
export const JAVASCRIPT_APPLICATION_RELATIONS = [
|
|
47
|
+
"contains",
|
|
48
|
+
"loads",
|
|
49
|
+
"imports",
|
|
50
|
+
"maps_to",
|
|
51
|
+
"exposes",
|
|
52
|
+
"sends",
|
|
53
|
+
"invokes",
|
|
54
|
+
"handles",
|
|
55
|
+
"calls",
|
|
56
|
+
"persists_to",
|
|
57
|
+
"observed_as",
|
|
58
|
+
"changed_from",
|
|
59
|
+
];
|
|
60
|
+
/** Provider-neutral relation categories. */
|
|
61
|
+
const applicationRelationSchema = z.enum(JAVASCRIPT_APPLICATION_RELATIONS);
|
|
62
|
+
const applicationPropertiesSchema = z
|
|
63
|
+
.record(z.string().min(1).max(128), jsonValueSchema)
|
|
64
|
+
.superRefine((properties, context) => {
|
|
65
|
+
if (Object.keys(properties).length > 64)
|
|
66
|
+
context.addIssue({
|
|
67
|
+
code: "custom",
|
|
68
|
+
message: "Application graph properties exceed 64 keys",
|
|
69
|
+
});
|
|
70
|
+
if (!isJsonWithinLimits(properties, {
|
|
71
|
+
maxDepth: 6,
|
|
72
|
+
maxNodes: 512,
|
|
73
|
+
maxStringLength: 4_096,
|
|
74
|
+
}))
|
|
75
|
+
context.addIssue({
|
|
76
|
+
code: "custom",
|
|
77
|
+
message: "Application graph properties exceed structural limits",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
/** One node observation before its semantic identifier is derived. */
|
|
81
|
+
const applicationNodeObservationInputSchema = z.strictObject({
|
|
82
|
+
label: z.string().min(1).max(1_024).nullable(),
|
|
83
|
+
properties: applicationPropertiesSchema,
|
|
84
|
+
evidence: applicationGraphEvidenceSchema,
|
|
85
|
+
});
|
|
86
|
+
/** One immutable, evidence-bearing observation attached to an entity. */
|
|
87
|
+
export const applicationNodeObservationSchema = applicationNodeObservationInputSchema.extend({
|
|
88
|
+
observation_id: z.string().regex(/^jag_observation_[a-f0-9]{64}$/u),
|
|
89
|
+
identifier_strategy: z.strictObject({
|
|
90
|
+
strategy: z.literal("semantic-content-sha256"),
|
|
91
|
+
stability: z.literal("observation-exact"),
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
/** One application entity before its stable identifier is derived. */
|
|
95
|
+
export const applicationNodeInputSchema = z.strictObject({
|
|
96
|
+
kind: applicationNodeKindSchema,
|
|
97
|
+
identity: applicationNodeIdentitySchema,
|
|
98
|
+
observations: z.array(applicationNodeObservationInputSchema).min(1).max(64),
|
|
99
|
+
});
|
|
100
|
+
/** One stable application entity with one or more bounded observations. */
|
|
101
|
+
export const applicationNodeSchema = z.strictObject({
|
|
102
|
+
node_id: z.string().regex(/^jag_node_[a-f0-9]{64}$/u),
|
|
103
|
+
kind: applicationNodeKindSchema,
|
|
104
|
+
identity: applicationNodeIdentitySchema,
|
|
105
|
+
observations: z.array(applicationNodeObservationSchema).min(1).max(64),
|
|
106
|
+
});
|
|
107
|
+
/** One directed relationship before its semantic identifier is derived. */
|
|
108
|
+
export const applicationEdgeInputSchema = z.strictObject({
|
|
109
|
+
source_node_id: z.string().regex(/^jag_node_[a-f0-9]{64}$/u),
|
|
110
|
+
target_node_id: z.string().regex(/^jag_node_[a-f0-9]{64}$/u),
|
|
111
|
+
relation: applicationRelationSchema,
|
|
112
|
+
properties: applicationPropertiesSchema,
|
|
113
|
+
evidence: applicationGraphEvidenceSchema,
|
|
114
|
+
});
|
|
115
|
+
/** One directed, evidence-bearing application relationship. */
|
|
116
|
+
export const applicationEdgeSchema = applicationEdgeInputSchema.extend({
|
|
117
|
+
edge_id: z.string().regex(/^jag_edge_[a-f0-9]{64}$/u),
|
|
118
|
+
identifier_strategy: z.strictObject({
|
|
119
|
+
strategy: z.literal("semantic-content-sha256"),
|
|
120
|
+
stability: z.literal("relationship-exact"),
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
/** Graph content before its top-level semantic identifier is derived. */
|
|
124
|
+
export const javascriptApplicationGraphInputSchema = z.strictObject({
|
|
125
|
+
schema: z.literal("JavaScriptApplicationGraph"),
|
|
126
|
+
schema_version: z.literal(1),
|
|
127
|
+
root_node_ids: z
|
|
128
|
+
.array(z.string().regex(/^jag_node_[a-f0-9]{64}$/u))
|
|
129
|
+
.min(1)
|
|
130
|
+
.max(1_000),
|
|
131
|
+
nodes: z.array(applicationNodeSchema).min(1).max(100_000),
|
|
132
|
+
edges: z.array(applicationEdgeSchema).max(200_000),
|
|
133
|
+
coverage: applicationCoverageSchema,
|
|
134
|
+
limitations: z.array(boundedTextSchema).max(1_000),
|
|
135
|
+
});
|
|
136
|
+
/** Strict stored shape for JavaScript Application Graph v1. */
|
|
137
|
+
export const javascriptApplicationGraphRecordSchema = javascriptApplicationGraphInputSchema.extend({
|
|
138
|
+
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
139
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { buildJavaScriptApplicationChangeGraph } from "./javascriptApplicationChangeGraph.js";
|
|
4
|
+
import { applicationVersionComparisonResultSchema, } from "./javascriptApplicationVersionComparisonSchemas.js";
|
|
5
|
+
import { compareCodePoints } from "./javascriptApplicationGraph.js";
|
|
6
|
+
import { classifyJavaScriptApplicationVersions } from "./javascriptApplicationVersionItems.js";
|
|
7
|
+
import { matchJavaScriptApplicationVersions } from "./javascriptApplicationVersionKeys.js";
|
|
8
|
+
/** Compare application entities with ordered unique-only identity tiers. */
|
|
9
|
+
export const compareJavaScriptApplicationVersions = (input) => {
|
|
10
|
+
const matching = matchJavaScriptApplicationVersions(input.left.graph.nodes, input.right.graph.nodes);
|
|
11
|
+
const projection = classifyJavaScriptApplicationVersions(matching, {
|
|
12
|
+
leftGraph: input.left.graph,
|
|
13
|
+
rightGraph: input.right.graph,
|
|
14
|
+
leftEvidenceId: input.left.evidenceId,
|
|
15
|
+
rightEvidenceId: input.right.evidenceId,
|
|
16
|
+
leftNativeEvidence: input.leftNativeEvidence,
|
|
17
|
+
rightNativeEvidence: input.rightNativeEvidence,
|
|
18
|
+
maxCandidates: input.limits.max_candidate_nodes,
|
|
19
|
+
});
|
|
20
|
+
const items = projection.items.slice(0, input.limits.max_comparison_items);
|
|
21
|
+
const omittedComparisonItems = projection.items.length - items.length;
|
|
22
|
+
const changeGraph = buildJavaScriptApplicationChangeGraph({
|
|
23
|
+
left: input.left.graph,
|
|
24
|
+
right: input.right.graph,
|
|
25
|
+
leftEvidenceId: input.left.evidenceId,
|
|
26
|
+
rightEvidenceId: input.right.evidenceId,
|
|
27
|
+
items,
|
|
28
|
+
omittedComparisonItems,
|
|
29
|
+
limits: input.limits,
|
|
30
|
+
});
|
|
31
|
+
const evidenceLinks = uniqueSorted(projection.items.flatMap(({ evidence_links: links }) => links));
|
|
32
|
+
const omissions = {
|
|
33
|
+
omitted_comparison_items: omittedComparisonItems,
|
|
34
|
+
omitted_candidate_references: projection.omittedCandidateReferences,
|
|
35
|
+
omitted_graph_nodes: changeGraph.omittedNodes,
|
|
36
|
+
omitted_graph_edges: changeGraph.omittedEdges,
|
|
37
|
+
omitted_graph_observations: changeGraph.omittedObservations,
|
|
38
|
+
};
|
|
39
|
+
const semantic = {
|
|
40
|
+
schema_version: 1,
|
|
41
|
+
left: {
|
|
42
|
+
evidence_id: input.left.evidenceId,
|
|
43
|
+
graph_id: input.left.graph.graph_id,
|
|
44
|
+
root_artifact_sha256: input.left.rootArtifactSha256,
|
|
45
|
+
},
|
|
46
|
+
right: {
|
|
47
|
+
evidence_id: input.right.evidenceId,
|
|
48
|
+
graph_id: input.right.graph.graph_id,
|
|
49
|
+
root_artifact_sha256: input.right.rootArtifactSha256,
|
|
50
|
+
},
|
|
51
|
+
summary: summary(projection.items),
|
|
52
|
+
matching: matchingSummary(projection.items),
|
|
53
|
+
items,
|
|
54
|
+
graph: changeGraph.graph,
|
|
55
|
+
coverage: {
|
|
56
|
+
status: comparisonCoverageStatus(input, omissions),
|
|
57
|
+
left_graph_status: input.left.graph.coverage.status,
|
|
58
|
+
right_graph_status: input.right.graph.coverage.status,
|
|
59
|
+
...omissions,
|
|
60
|
+
},
|
|
61
|
+
evidence_links: evidenceLinks,
|
|
62
|
+
limitations: comparisonLimitations(input, omissions),
|
|
63
|
+
};
|
|
64
|
+
return applicationVersionComparisonResultSchema.parse({
|
|
65
|
+
...semantic,
|
|
66
|
+
comparison_id: `javc_${digestCanonical(semantic)}`,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const summary = (items) => ({
|
|
70
|
+
unchanged: countStatus(items, "unchanged"),
|
|
71
|
+
added: countStatus(items, "added"),
|
|
72
|
+
removed: countStatus(items, "removed"),
|
|
73
|
+
changed: countStatus(items, "changed"),
|
|
74
|
+
unknown: countStatus(items, "unknown"),
|
|
75
|
+
});
|
|
76
|
+
const countStatus = (items, status) => items.filter((item) => item.status === status).length;
|
|
77
|
+
const matchingSummary = (items) => ({
|
|
78
|
+
exact_node_identity: countBasis(items, "exact-node-identity"),
|
|
79
|
+
exact_content_digest: countBasis(items, "exact-content-digest"),
|
|
80
|
+
exact_module_source_digest: countBasis(items, "exact-module-source-digest"),
|
|
81
|
+
source_map_identity: countBasis(items, "source-map-identity"),
|
|
82
|
+
structural_fingerprint: countBasis(items, "structural-fingerprint"),
|
|
83
|
+
semantic_key: countBasis(items, "semantic-key"),
|
|
84
|
+
ambiguous: items.filter(({ match }) => match.status === "ambiguous").length,
|
|
85
|
+
unmatched: items.filter(({ match }) => match.status === "unmatched").length,
|
|
86
|
+
});
|
|
87
|
+
const countBasis = (items, basis) => items.filter(({ match }) => match.basis === basis).length;
|
|
88
|
+
const comparisonCoverageStatus = (input, omissions) => Object.values(omissions).some((value) => value > 0)
|
|
89
|
+
? "truncated"
|
|
90
|
+
: input.left.graph.coverage.status === "complete" &&
|
|
91
|
+
input.right.graph.coverage.status === "complete"
|
|
92
|
+
? "complete-within-inputs"
|
|
93
|
+
: "partial";
|
|
94
|
+
const comparisonLimitations = (input, omissions) => uniqueSorted([
|
|
95
|
+
"Application comparison never uses bundler module ordinals as persistent identity and performs no fuzzy pairing.",
|
|
96
|
+
"Exact digest equality proves byte identity only; source-map, structural-fingerprint, and semantic-key matches retain inferred confidence.",
|
|
97
|
+
"Minified names and chunk locations are not assumed stable across versions.",
|
|
98
|
+
"The comparison reads retained graphs and Evidence only; it does not execute either application.",
|
|
99
|
+
"Native Evidence is linked only by exact subject digest; provider snapshot reuse remains target/provider/profile exact.",
|
|
100
|
+
...(input.left.graph.coverage.status === "complete" &&
|
|
101
|
+
input.right.graph.coverage.status === "complete"
|
|
102
|
+
? []
|
|
103
|
+
: [
|
|
104
|
+
"At least one source graph is incomplete; unmatched entities on the opposite side remain unknown rather than added or removed.",
|
|
105
|
+
]),
|
|
106
|
+
...(Object.values(omissions).some((value) => value > 0)
|
|
107
|
+
? ["Comparison output was truncated at explicit caller limits."]
|
|
108
|
+
: []),
|
|
109
|
+
]);
|
|
110
|
+
const digestCanonical = (value) => {
|
|
111
|
+
const encoded = canonicalize(value);
|
|
112
|
+
if (encoded === undefined)
|
|
113
|
+
throw new TypeError("Application version comparison could not canonicalize data");
|
|
114
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
115
|
+
};
|
|
116
|
+
const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { evidenceSchema } from "./evidence.js";
|
|
3
|
+
import { javascriptApplicationGraphSchema } from "./javascriptApplicationGraph.js";
|
|
4
|
+
import { JAVASCRIPT_APPLICATION_NODE_KINDS } from "./javascriptApplicationGraphSchemas.js";
|
|
5
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
6
|
+
const nodeIdSchema = z.string().regex(/^jag_node_[a-f0-9]{64}$/u);
|
|
7
|
+
const boundedTextSchema = z.string().min(1).max(4_096);
|
|
8
|
+
const comparisonLimitsSchema = z.strictObject({
|
|
9
|
+
max_comparison_items: z.number().int().min(1).max(50_000).default(20_000),
|
|
10
|
+
max_candidate_nodes: z.number().int().min(1).max(1_000).default(100),
|
|
11
|
+
max_graph_nodes: z.number().int().min(1).max(100_000).default(20_000),
|
|
12
|
+
max_graph_edges: z.number().int().min(1).max(200_000).default(40_000),
|
|
13
|
+
});
|
|
14
|
+
/** Two authenticated application versions and deterministic output bounds. */
|
|
15
|
+
export const compareApplicationVersionsInputSchema = z
|
|
16
|
+
.strictObject({
|
|
17
|
+
left: evidenceSchema,
|
|
18
|
+
right: evidenceSchema,
|
|
19
|
+
left_native_observations: z.array(evidenceSchema).max(64).default([]),
|
|
20
|
+
right_native_observations: z.array(evidenceSchema).max(64).default([]),
|
|
21
|
+
limits: comparisonLimitsSchema.default({
|
|
22
|
+
max_comparison_items: 20_000,
|
|
23
|
+
max_candidate_nodes: 100,
|
|
24
|
+
max_graph_nodes: 20_000,
|
|
25
|
+
max_graph_edges: 40_000,
|
|
26
|
+
}),
|
|
27
|
+
unknown_registry_approved: z.literal(true).optional(),
|
|
28
|
+
})
|
|
29
|
+
.superRefine((input, context) => {
|
|
30
|
+
if (input.left.evidence_id === input.right.evidence_id)
|
|
31
|
+
context.addIssue({
|
|
32
|
+
code: "custom",
|
|
33
|
+
path: ["right"],
|
|
34
|
+
message: "Application version Evidence must be distinct",
|
|
35
|
+
});
|
|
36
|
+
for (const side of [
|
|
37
|
+
"left_native_observations",
|
|
38
|
+
"right_native_observations",
|
|
39
|
+
]) {
|
|
40
|
+
const ids = input[side].map(({ evidence_id: id }) => id);
|
|
41
|
+
if (new Set(ids).size !== ids.length)
|
|
42
|
+
context.addIssue({
|
|
43
|
+
code: "custom",
|
|
44
|
+
path: [side],
|
|
45
|
+
message: "Native observations must be unique on each side",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const applicationVersionMatchBasisSchema = z.enum([
|
|
50
|
+
"exact-node-identity",
|
|
51
|
+
"exact-content-digest",
|
|
52
|
+
"exact-module-source-digest",
|
|
53
|
+
"source-map-identity",
|
|
54
|
+
"structural-fingerprint",
|
|
55
|
+
"semantic-key",
|
|
56
|
+
"none",
|
|
57
|
+
]);
|
|
58
|
+
const comparisonItemSchema = z.strictObject({
|
|
59
|
+
item_id: z.string().regex(/^javc_item_[a-f0-9]{64}$/u),
|
|
60
|
+
status: z.enum(["unchanged", "added", "removed", "changed", "unknown"]),
|
|
61
|
+
node_kind: z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS),
|
|
62
|
+
left_node_id: nodeIdSchema.nullable(),
|
|
63
|
+
right_node_id: nodeIdSchema.nullable(),
|
|
64
|
+
match: z.strictObject({
|
|
65
|
+
status: z.enum(["matched", "unmatched", "ambiguous"]),
|
|
66
|
+
basis: applicationVersionMatchBasisSchema,
|
|
67
|
+
confidence: z.enum(["exact", "high", "medium", "unknown"]),
|
|
68
|
+
candidate_left_node_ids: z.array(nodeIdSchema).max(1_000),
|
|
69
|
+
candidate_right_node_ids: z.array(nodeIdSchema).max(1_000),
|
|
70
|
+
}),
|
|
71
|
+
dimensions: z
|
|
72
|
+
.array(z.enum([
|
|
73
|
+
"content",
|
|
74
|
+
"location",
|
|
75
|
+
"properties",
|
|
76
|
+
"relationships",
|
|
77
|
+
"availability",
|
|
78
|
+
"coverage",
|
|
79
|
+
]))
|
|
80
|
+
.max(6),
|
|
81
|
+
evidence_links: z.array(evidenceIdSchema).min(2).max(130),
|
|
82
|
+
limitations: z.array(boundedTextSchema).max(100),
|
|
83
|
+
});
|
|
84
|
+
/** Tiered module/entity matching plus a bounded cross-version change graph. */
|
|
85
|
+
export const applicationVersionComparisonResultSchema = z.strictObject({
|
|
86
|
+
schema_version: z.literal(1),
|
|
87
|
+
comparison_id: z.string().regex(/^javc_[a-f0-9]{64}$/u),
|
|
88
|
+
left: z.strictObject({
|
|
89
|
+
evidence_id: evidenceIdSchema,
|
|
90
|
+
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
91
|
+
root_artifact_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
92
|
+
}),
|
|
93
|
+
right: z.strictObject({
|
|
94
|
+
evidence_id: evidenceIdSchema,
|
|
95
|
+
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
96
|
+
root_artifact_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
97
|
+
}),
|
|
98
|
+
summary: z.strictObject({
|
|
99
|
+
unchanged: z.number().int().min(0),
|
|
100
|
+
added: z.number().int().min(0),
|
|
101
|
+
removed: z.number().int().min(0),
|
|
102
|
+
changed: z.number().int().min(0),
|
|
103
|
+
unknown: z.number().int().min(0),
|
|
104
|
+
}),
|
|
105
|
+
matching: z.strictObject({
|
|
106
|
+
exact_node_identity: z.number().int().min(0),
|
|
107
|
+
exact_content_digest: z.number().int().min(0),
|
|
108
|
+
exact_module_source_digest: z.number().int().min(0),
|
|
109
|
+
source_map_identity: z.number().int().min(0),
|
|
110
|
+
structural_fingerprint: z.number().int().min(0),
|
|
111
|
+
semantic_key: z.number().int().min(0),
|
|
112
|
+
ambiguous: z.number().int().min(0),
|
|
113
|
+
unmatched: z.number().int().min(0),
|
|
114
|
+
}),
|
|
115
|
+
items: z.array(comparisonItemSchema).max(50_000),
|
|
116
|
+
graph: javascriptApplicationGraphSchema,
|
|
117
|
+
coverage: z.strictObject({
|
|
118
|
+
status: z.enum(["complete-within-inputs", "partial", "truncated"]),
|
|
119
|
+
left_graph_status: z.enum([
|
|
120
|
+
"complete",
|
|
121
|
+
"partial",
|
|
122
|
+
"unknown",
|
|
123
|
+
"unavailable",
|
|
124
|
+
]),
|
|
125
|
+
right_graph_status: z.enum([
|
|
126
|
+
"complete",
|
|
127
|
+
"partial",
|
|
128
|
+
"unknown",
|
|
129
|
+
"unavailable",
|
|
130
|
+
]),
|
|
131
|
+
omitted_comparison_items: z.number().int().min(0),
|
|
132
|
+
omitted_candidate_references: z.number().int().min(0),
|
|
133
|
+
omitted_graph_nodes: z.number().int().min(0),
|
|
134
|
+
omitted_graph_edges: z.number().int().min(0),
|
|
135
|
+
omitted_graph_observations: z.number().int().min(0),
|
|
136
|
+
}),
|
|
137
|
+
evidence_links: z.array(evidenceIdSchema).min(2).max(130),
|
|
138
|
+
limitations: z.array(boundedTextSchema).max(1_000),
|
|
139
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { compareCodePoints, } from "./javascriptApplicationGraph.js";
|
|
4
|
+
/** Classify matched, unmatched, and ambiguous nodes without inventing absence. */
|
|
5
|
+
export const classifyJavaScriptApplicationVersions = (matching, context) => {
|
|
6
|
+
const pairByLeft = new Map(matching.pairs.map(({ left, right }) => [
|
|
7
|
+
left.node_id,
|
|
8
|
+
`pair:${left.node_id}:${right.node_id}`,
|
|
9
|
+
]));
|
|
10
|
+
const pairByRight = new Map(matching.pairs.map(({ left, right }) => [
|
|
11
|
+
right.node_id,
|
|
12
|
+
`pair:${left.node_id}:${right.node_id}`,
|
|
13
|
+
]));
|
|
14
|
+
const fullContext = { ...context, pairByLeft, pairByRight };
|
|
15
|
+
let omittedCandidateReferences = 0;
|
|
16
|
+
const leftItems = matching.unmatchedLeft.map((node) => {
|
|
17
|
+
const candidates = matching.candidatesForLeft.get(node.node_id) ?? [];
|
|
18
|
+
omittedCandidateReferences += Math.max(0, candidates.length - context.maxCandidates);
|
|
19
|
+
return unmatchedItem(node, "left", candidates.slice(0, context.maxCandidates), fullContext);
|
|
20
|
+
});
|
|
21
|
+
const rightItems = matching.unmatchedRight.map((node) => {
|
|
22
|
+
const candidates = matching.candidatesForRight.get(node.node_id) ?? [];
|
|
23
|
+
omittedCandidateReferences += Math.max(0, candidates.length - context.maxCandidates);
|
|
24
|
+
return unmatchedItem(node, "right", candidates.slice(0, context.maxCandidates), fullContext);
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
items: [
|
|
28
|
+
...matching.pairs.map((pair) => matchedItem(pair, fullContext)),
|
|
29
|
+
...leftItems,
|
|
30
|
+
...rightItems,
|
|
31
|
+
].sort((left, right) => compareCodePoints(left.item_id, right.item_id)),
|
|
32
|
+
omittedCandidateReferences,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const matchedItem = (pair, context) => {
|
|
36
|
+
const dimensions = changedDimensions(pair, context);
|
|
37
|
+
const complete = graphsComplete(context);
|
|
38
|
+
const observedChange = dimensions.some((dimension) => ["content", "location", "properties"].includes(dimension));
|
|
39
|
+
const status = observedChange || (complete && dimensions.length > 0)
|
|
40
|
+
? "changed"
|
|
41
|
+
: complete
|
|
42
|
+
? "unchanged"
|
|
43
|
+
: "unknown";
|
|
44
|
+
const finalDimensions = status === "unknown" && !dimensions.includes("coverage")
|
|
45
|
+
? [...dimensions, "coverage"]
|
|
46
|
+
: dimensions;
|
|
47
|
+
const semantic = {
|
|
48
|
+
status,
|
|
49
|
+
node_kind: pair.left.kind,
|
|
50
|
+
left_node_id: pair.left.node_id,
|
|
51
|
+
right_node_id: pair.right.node_id,
|
|
52
|
+
match: {
|
|
53
|
+
status: "matched",
|
|
54
|
+
basis: pair.basis,
|
|
55
|
+
confidence: pair.confidence,
|
|
56
|
+
candidate_left_node_ids: [],
|
|
57
|
+
candidate_right_node_ids: [],
|
|
58
|
+
},
|
|
59
|
+
dimensions: finalDimensions,
|
|
60
|
+
evidence_links: itemEvidenceLinks(pair.left, pair.right, context),
|
|
61
|
+
limitations: pairLimitations(pair, status),
|
|
62
|
+
};
|
|
63
|
+
return itemWithId(semantic);
|
|
64
|
+
};
|
|
65
|
+
const unmatchedItem = (node, side, candidates, context) => {
|
|
66
|
+
const ambiguous = candidates.length > 0;
|
|
67
|
+
const oppositeComplete = (side === "left" ? context.rightGraph : context.leftGraph).coverage
|
|
68
|
+
.status === "complete";
|
|
69
|
+
const status = ambiguous
|
|
70
|
+
? "unknown"
|
|
71
|
+
: oppositeComplete
|
|
72
|
+
? side === "left"
|
|
73
|
+
? "removed"
|
|
74
|
+
: "added"
|
|
75
|
+
: "unknown";
|
|
76
|
+
const semantic = {
|
|
77
|
+
status,
|
|
78
|
+
node_kind: node.kind,
|
|
79
|
+
left_node_id: side === "left" ? node.node_id : null,
|
|
80
|
+
right_node_id: side === "right" ? node.node_id : null,
|
|
81
|
+
match: {
|
|
82
|
+
status: ambiguous ? "ambiguous" : "unmatched",
|
|
83
|
+
basis: "none",
|
|
84
|
+
confidence: "unknown",
|
|
85
|
+
candidate_left_node_ids: side === "right" ? [...candidates] : [],
|
|
86
|
+
candidate_right_node_ids: side === "left" ? [...candidates] : [],
|
|
87
|
+
},
|
|
88
|
+
dimensions: [
|
|
89
|
+
"availability",
|
|
90
|
+
...(status === "unknown" && !ambiguous ? ["coverage"] : []),
|
|
91
|
+
],
|
|
92
|
+
evidence_links: itemEvidenceLinks(side === "left" ? node : undefined, side === "right" ? node : undefined, context),
|
|
93
|
+
limitations: [
|
|
94
|
+
...(ambiguous
|
|
95
|
+
? [
|
|
96
|
+
"Multiple nodes share the same permitted match key; no cross-version pair was selected.",
|
|
97
|
+
]
|
|
98
|
+
: []),
|
|
99
|
+
...(status === "unknown" && !ambiguous
|
|
100
|
+
? [
|
|
101
|
+
"The opposite graph is incomplete, so an unmatched node cannot be classified as added or removed.",
|
|
102
|
+
]
|
|
103
|
+
: []),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
return itemWithId(semantic);
|
|
107
|
+
};
|
|
108
|
+
const changedDimensions = (pair, context) => {
|
|
109
|
+
const dimensions = [];
|
|
110
|
+
const leftContent = contentIdentity(pair.left);
|
|
111
|
+
const rightContent = contentIdentity(pair.right);
|
|
112
|
+
if (leftContent !== null &&
|
|
113
|
+
rightContent !== null &&
|
|
114
|
+
leftContent !== rightContent)
|
|
115
|
+
dimensions.push("content");
|
|
116
|
+
if (canonical(locations(pair.left)) !== canonical(locations(pair.right)))
|
|
117
|
+
dimensions.push("location");
|
|
118
|
+
if (canonical(properties(pair.left)) !== canonical(properties(pair.right)))
|
|
119
|
+
dimensions.push("properties");
|
|
120
|
+
if (relationshipSignature(pair.left, "left", context) !==
|
|
121
|
+
relationshipSignature(pair.right, "right", context))
|
|
122
|
+
dimensions.push("relationships");
|
|
123
|
+
return dimensions;
|
|
124
|
+
};
|
|
125
|
+
const contentIdentity = (node) => {
|
|
126
|
+
if (node.identity.strategy === "content-digest")
|
|
127
|
+
return `content:${node.identity.sha256}`;
|
|
128
|
+
if (node.kind === "javascript-module")
|
|
129
|
+
return stringProperty(node, "source_sha256");
|
|
130
|
+
if (node.kind === "source-module" &&
|
|
131
|
+
node.identity.strategy === "source-map-original")
|
|
132
|
+
return node.identity.source_sha256;
|
|
133
|
+
return null;
|
|
134
|
+
};
|
|
135
|
+
const locations = (node) => node.observations
|
|
136
|
+
.map(({ evidence }) => evidence.location.available ? evidence.location.value : evidence.location)
|
|
137
|
+
.sort((left, right) => compareCodePoints(canonical(left), canonical(right)));
|
|
138
|
+
const properties = (node) => node.observations
|
|
139
|
+
.map(({ label, properties: values }) => ({ label, properties: values }))
|
|
140
|
+
.sort((left, right) => compareCodePoints(canonical(left), canonical(right)));
|
|
141
|
+
const relationshipSignature = (node, side, context) => {
|
|
142
|
+
const graph = side === "left" ? context.leftGraph : context.rightGraph;
|
|
143
|
+
const paired = side === "left" ? context.pairByLeft : context.pairByRight;
|
|
144
|
+
const nodeById = new Map(graph.nodes.map((value) => [value.node_id, value]));
|
|
145
|
+
const signatures = graph.edges.flatMap((edge) => {
|
|
146
|
+
if (edge.source_node_id === node.node_id)
|
|
147
|
+
return [
|
|
148
|
+
edgeSignature("out", edge.relation, edge.target_node_id, paired, nodeById),
|
|
149
|
+
];
|
|
150
|
+
if (edge.target_node_id === node.node_id)
|
|
151
|
+
return [
|
|
152
|
+
edgeSignature("in", edge.relation, edge.source_node_id, paired, nodeById),
|
|
153
|
+
];
|
|
154
|
+
return [];
|
|
155
|
+
});
|
|
156
|
+
return canonical(signatures.sort(compareCodePoints));
|
|
157
|
+
};
|
|
158
|
+
const edgeSignature = (direction, relation, neighborId, paired, nodeById) => {
|
|
159
|
+
const mapped = paired.get(neighborId);
|
|
160
|
+
const neighbor = nodeById.get(neighborId);
|
|
161
|
+
return `${direction}\0${relation}\0${mapped ?? `unmatched:${neighbor?.kind ?? "unknown"}`}`;
|
|
162
|
+
};
|
|
163
|
+
const itemEvidenceLinks = (left, right, context) => [
|
|
164
|
+
...new Set([
|
|
165
|
+
context.leftEvidenceId,
|
|
166
|
+
context.rightEvidenceId,
|
|
167
|
+
...nativeLinks(left, context.leftNativeEvidence),
|
|
168
|
+
...nativeLinks(right, context.rightNativeEvidence),
|
|
169
|
+
]),
|
|
170
|
+
].sort(compareCodePoints);
|
|
171
|
+
const nativeLinks = (node, evidence) => {
|
|
172
|
+
const digest = node === undefined
|
|
173
|
+
? null
|
|
174
|
+
: contentIdentity(node)?.replace(/^content:/u, "");
|
|
175
|
+
return digest === null
|
|
176
|
+
? []
|
|
177
|
+
: evidence
|
|
178
|
+
.filter(({ subject }) => subject?.digest.sha256 === digest)
|
|
179
|
+
.map(({ evidence_id: id }) => id);
|
|
180
|
+
};
|
|
181
|
+
const pairLimitations = (pair, status) => [
|
|
182
|
+
...(pair.basis === "structural-fingerprint"
|
|
183
|
+
? [
|
|
184
|
+
"Structural fingerprint equality is rename-resistant inference; it is not byte identity or proof of semantic equivalence.",
|
|
185
|
+
]
|
|
186
|
+
: []),
|
|
187
|
+
...(pair.basis === "source-map-identity"
|
|
188
|
+
? [
|
|
189
|
+
"Source-map original names identify a candidate source lineage; changed or unavailable source content remains explicit.",
|
|
190
|
+
]
|
|
191
|
+
: []),
|
|
192
|
+
...(pair.basis === "semantic-key"
|
|
193
|
+
? [
|
|
194
|
+
"Semantic-key pairing is a deterministic inference and is never promoted to exact identity.",
|
|
195
|
+
]
|
|
196
|
+
: []),
|
|
197
|
+
...(status === "unknown"
|
|
198
|
+
? ["Incomplete graph coverage prevents a complete node-level conclusion."]
|
|
199
|
+
: []),
|
|
200
|
+
];
|
|
201
|
+
const itemWithId = (semantic) => ({
|
|
202
|
+
...semantic,
|
|
203
|
+
item_id: `javc_item_${digestCanonical(semantic)}`,
|
|
204
|
+
});
|
|
205
|
+
const stringProperty = (node, key) => {
|
|
206
|
+
const values = [
|
|
207
|
+
...new Set(node.observations
|
|
208
|
+
.map(({ properties: values }) => values[key])
|
|
209
|
+
.filter((value) => typeof value === "string")),
|
|
210
|
+
];
|
|
211
|
+
return values.length === 1 ? (values[0] ?? null) : null;
|
|
212
|
+
};
|
|
213
|
+
const graphsComplete = (context) => context.leftGraph.coverage.status === "complete" &&
|
|
214
|
+
context.rightGraph.coverage.status === "complete";
|
|
215
|
+
const canonical = (value) => {
|
|
216
|
+
const encoded = canonicalize(value);
|
|
217
|
+
if (encoded === undefined)
|
|
218
|
+
throw new TypeError("Application version item could not canonicalize data");
|
|
219
|
+
return encoded;
|
|
220
|
+
};
|
|
221
|
+
const digestCanonical = (value) => createHash("sha256").update(canonical(value)).digest("hex");
|