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,379 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
3
|
+
const boundedTextSchema = z.string().min(1).max(4_096);
|
|
4
|
+
const boundedKeySchema = z.string().min(1).max(512);
|
|
5
|
+
const unsafePathSegments = new Set(["", ".", ".."]);
|
|
6
|
+
const isSafeRelativePath = (path) => {
|
|
7
|
+
if (path.startsWith("/") || path.includes("\\"))
|
|
8
|
+
return false;
|
|
9
|
+
return path.split("/").every((part) => !unsafePathSegments.has(part));
|
|
10
|
+
};
|
|
11
|
+
const relativePathSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(4_096)
|
|
15
|
+
.refine(isSafeRelativePath, "Expected a normalized relative POSIX path without traversal");
|
|
16
|
+
/** Authority that produced one node observation or relationship. */
|
|
17
|
+
const applicationEvidenceAuthoritySchema = z.enum([
|
|
18
|
+
"artifact-bytes",
|
|
19
|
+
"ast-static-analysis",
|
|
20
|
+
"static-relationship-inference",
|
|
21
|
+
"passive-cdp-runtime",
|
|
22
|
+
"cross-layer-reconciliation",
|
|
23
|
+
"cross-version-comparison",
|
|
24
|
+
"controlled-replay",
|
|
25
|
+
"native-analysis-provider",
|
|
26
|
+
"managed-static-analysis",
|
|
27
|
+
"historical-reference",
|
|
28
|
+
"user-assertion",
|
|
29
|
+
"unknown",
|
|
30
|
+
]);
|
|
31
|
+
/** Epistemic state kept separate from the evidence source. */
|
|
32
|
+
const applicationFactStateSchema = z.enum([
|
|
33
|
+
"observed",
|
|
34
|
+
"inferred",
|
|
35
|
+
"unknown",
|
|
36
|
+
"unavailable",
|
|
37
|
+
]);
|
|
38
|
+
/** Confidence assigned without promoting inference into observation. */
|
|
39
|
+
const applicationConfidenceSchema = z.enum([
|
|
40
|
+
"exact",
|
|
41
|
+
"high",
|
|
42
|
+
"medium",
|
|
43
|
+
"low",
|
|
44
|
+
"unknown",
|
|
45
|
+
]);
|
|
46
|
+
/** Why an artifact or source location was not available to an extractor. */
|
|
47
|
+
const applicationUnavailableReasonSchema = z.enum([
|
|
48
|
+
"not-applicable",
|
|
49
|
+
"not-observed",
|
|
50
|
+
"not-approved",
|
|
51
|
+
"truncated",
|
|
52
|
+
"provider-unavailable",
|
|
53
|
+
"unresolved",
|
|
54
|
+
"unknown",
|
|
55
|
+
]);
|
|
56
|
+
const unavailableFactSchema = z.strictObject({
|
|
57
|
+
available: z.literal(false),
|
|
58
|
+
reason: applicationUnavailableReasonSchema,
|
|
59
|
+
detail: boundedTextSchema,
|
|
60
|
+
});
|
|
61
|
+
const availableArtifactSchema = z
|
|
62
|
+
.strictObject({
|
|
63
|
+
available: z.literal(true),
|
|
64
|
+
artifact_id: z.string().regex(/^art_[a-f0-9]{64}$/u),
|
|
65
|
+
sha256: digestSchema,
|
|
66
|
+
})
|
|
67
|
+
.superRefine((artifact, context) => {
|
|
68
|
+
if (artifact.artifact_id !== `art_${artifact.sha256}`)
|
|
69
|
+
context.addIssue({
|
|
70
|
+
code: "custom",
|
|
71
|
+
path: ["artifact_id"],
|
|
72
|
+
message: "Artifact identifier must commit the same SHA-256 digest",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
/** Explicit content-addressed artifact availability for one graph fact. */
|
|
76
|
+
const applicationArtifactReferenceSchema = z.discriminatedUnion("available", [
|
|
77
|
+
availableArtifactSchema,
|
|
78
|
+
unavailableFactSchema,
|
|
79
|
+
]);
|
|
80
|
+
const sourceRangeSchema = z
|
|
81
|
+
.strictObject({
|
|
82
|
+
kind: z.literal("source-range"),
|
|
83
|
+
source: boundedTextSchema,
|
|
84
|
+
start: z.strictObject({
|
|
85
|
+
line: z.number().int().min(1),
|
|
86
|
+
column: z.number().int().min(0),
|
|
87
|
+
}),
|
|
88
|
+
end: z.strictObject({
|
|
89
|
+
line: z.number().int().min(1),
|
|
90
|
+
column: z.number().int().min(0),
|
|
91
|
+
}),
|
|
92
|
+
})
|
|
93
|
+
.superRefine((location, context) => {
|
|
94
|
+
if (location.end.line < location.start.line ||
|
|
95
|
+
(location.end.line === location.start.line &&
|
|
96
|
+
location.end.column < location.start.column))
|
|
97
|
+
context.addIssue({
|
|
98
|
+
code: "custom",
|
|
99
|
+
path: ["end"],
|
|
100
|
+
message: "Source range end must not precede its start",
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
const fileOffsetRangeSchema = z
|
|
104
|
+
.strictObject({
|
|
105
|
+
kind: z.literal("file-offset-range"),
|
|
106
|
+
start: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
107
|
+
end: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
108
|
+
})
|
|
109
|
+
.superRefine((location, context) => {
|
|
110
|
+
if (location.end < location.start)
|
|
111
|
+
context.addIssue({
|
|
112
|
+
code: "custom",
|
|
113
|
+
path: ["end"],
|
|
114
|
+
message: "File offset range end must not precede its start",
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
/** A deterministic artifact, source, runtime, package, or native location. */
|
|
118
|
+
const applicationLocationSchema = z.discriminatedUnion("kind", [
|
|
119
|
+
z.strictObject({
|
|
120
|
+
kind: z.literal("artifact-path"),
|
|
121
|
+
path: relativePathSchema,
|
|
122
|
+
}),
|
|
123
|
+
sourceRangeSchema,
|
|
124
|
+
fileOffsetRangeSchema,
|
|
125
|
+
z.strictObject({
|
|
126
|
+
kind: z.literal("url"),
|
|
127
|
+
url: z.string().min(1).max(16_384),
|
|
128
|
+
}),
|
|
129
|
+
z.strictObject({
|
|
130
|
+
kind: z.literal("runtime"),
|
|
131
|
+
capture_sha256: digestSchema,
|
|
132
|
+
target_key: boundedKeySchema,
|
|
133
|
+
frame_key: boundedKeySchema.nullable(),
|
|
134
|
+
script_key: boundedKeySchema.nullable(),
|
|
135
|
+
}),
|
|
136
|
+
z.strictObject({
|
|
137
|
+
kind: z.literal("native-address"),
|
|
138
|
+
address: boundedKeySchema,
|
|
139
|
+
symbol: boundedTextSchema.nullable(),
|
|
140
|
+
}),
|
|
141
|
+
z.strictObject({
|
|
142
|
+
kind: z.literal("package-export"),
|
|
143
|
+
package_name: boundedKeySchema,
|
|
144
|
+
export_path: boundedTextSchema,
|
|
145
|
+
}),
|
|
146
|
+
]);
|
|
147
|
+
/** Explicit source-location availability for one graph fact. */
|
|
148
|
+
const applicationLocationReferenceSchema = z.discriminatedUnion("available", [
|
|
149
|
+
z.strictObject({
|
|
150
|
+
available: z.literal(true),
|
|
151
|
+
value: applicationLocationSchema,
|
|
152
|
+
}),
|
|
153
|
+
unavailableFactSchema,
|
|
154
|
+
]);
|
|
155
|
+
/** Versioned extractor coordinates attached to every graph fact. */
|
|
156
|
+
const applicationExtractorSchema = z.strictObject({
|
|
157
|
+
name: z.string().min(1).max(128),
|
|
158
|
+
version: z.string().min(1).max(128),
|
|
159
|
+
operation: z.string().min(1).max(256),
|
|
160
|
+
executable_sha256: digestSchema.nullable(),
|
|
161
|
+
});
|
|
162
|
+
const applicationLimitSchema = z.strictObject({
|
|
163
|
+
name: z.string().min(1).max(128),
|
|
164
|
+
value: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
165
|
+
unit: z.enum([
|
|
166
|
+
"items",
|
|
167
|
+
"bytes",
|
|
168
|
+
"milliseconds",
|
|
169
|
+
"depth",
|
|
170
|
+
"characters",
|
|
171
|
+
"instructions",
|
|
172
|
+
"other",
|
|
173
|
+
]),
|
|
174
|
+
});
|
|
175
|
+
/** Bounded collection coverage without treating omission as absence. */
|
|
176
|
+
export const applicationCoverageSchema = z
|
|
177
|
+
.strictObject({
|
|
178
|
+
status: z.enum(["complete", "partial", "unknown", "unavailable"]),
|
|
179
|
+
truncated: z.boolean(),
|
|
180
|
+
omitted_count: z
|
|
181
|
+
.number()
|
|
182
|
+
.int()
|
|
183
|
+
.min(0)
|
|
184
|
+
.max(Number.MAX_SAFE_INTEGER)
|
|
185
|
+
.nullable(),
|
|
186
|
+
limits: z.array(applicationLimitSchema).max(32),
|
|
187
|
+
})
|
|
188
|
+
.superRefine((coverage, context) => {
|
|
189
|
+
if (coverage.status === "complete" &&
|
|
190
|
+
(coverage.truncated || coverage.omitted_count !== 0))
|
|
191
|
+
context.addIssue({
|
|
192
|
+
code: "custom",
|
|
193
|
+
message: "Complete coverage cannot be truncated or omit items",
|
|
194
|
+
});
|
|
195
|
+
if (coverage.truncated &&
|
|
196
|
+
(coverage.status !== "partial" ||
|
|
197
|
+
coverage.limits.length === 0 ||
|
|
198
|
+
coverage.omitted_count === 0))
|
|
199
|
+
context.addIssue({
|
|
200
|
+
code: "custom",
|
|
201
|
+
message: "Truncated coverage must be partial, name a limit, and not claim zero omissions",
|
|
202
|
+
});
|
|
203
|
+
if (["unknown", "unavailable"].includes(coverage.status) &&
|
|
204
|
+
(coverage.truncated || coverage.omitted_count !== null))
|
|
205
|
+
context.addIssue({
|
|
206
|
+
code: "custom",
|
|
207
|
+
message: "Unknown or unavailable coverage cannot invent truncation counts",
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
const applicationGraphEvidenceBaseSchema = z.strictObject({
|
|
211
|
+
authority: applicationEvidenceAuthoritySchema,
|
|
212
|
+
state: applicationFactStateSchema,
|
|
213
|
+
confidence: applicationConfidenceSchema,
|
|
214
|
+
artifact: applicationArtifactReferenceSchema,
|
|
215
|
+
location: applicationLocationReferenceSchema,
|
|
216
|
+
extractor: applicationExtractorSchema,
|
|
217
|
+
coverage: applicationCoverageSchema,
|
|
218
|
+
limitations: z.array(boundedTextSchema).max(100),
|
|
219
|
+
evidence_ids: z.array(z.string().regex(/^ev_[a-f0-9]{64}$/u)).max(100),
|
|
220
|
+
});
|
|
221
|
+
const checkAuthorityState = (evidence, context) => {
|
|
222
|
+
if (evidence.authority === "static-relationship-inference" &&
|
|
223
|
+
evidence.state !== "inferred")
|
|
224
|
+
context.addIssue({
|
|
225
|
+
code: "custom",
|
|
226
|
+
path: ["state"],
|
|
227
|
+
message: "Static relationship authority must remain inferred",
|
|
228
|
+
});
|
|
229
|
+
if (evidence.authority === "cross-layer-reconciliation" &&
|
|
230
|
+
evidence.state !== "inferred")
|
|
231
|
+
context.addIssue({
|
|
232
|
+
code: "custom",
|
|
233
|
+
path: ["state"],
|
|
234
|
+
message: "Cross-layer reconciliation authority must remain inferred",
|
|
235
|
+
});
|
|
236
|
+
if (evidence.authority === "cross-version-comparison" &&
|
|
237
|
+
evidence.state !== "inferred")
|
|
238
|
+
context.addIssue({
|
|
239
|
+
code: "custom",
|
|
240
|
+
path: ["state"],
|
|
241
|
+
message: "Cross-version comparison authority must remain inferred",
|
|
242
|
+
});
|
|
243
|
+
if (evidence.authority === "unknown" &&
|
|
244
|
+
!["unknown", "unavailable"].includes(evidence.state))
|
|
245
|
+
context.addIssue({
|
|
246
|
+
code: "custom",
|
|
247
|
+
path: ["state"],
|
|
248
|
+
message: "Unknown authority cannot claim an observed fact",
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const checkStateConfidence = (evidence, context) => {
|
|
252
|
+
if (["unknown", "unavailable"].includes(evidence.state) &&
|
|
253
|
+
(evidence.confidence !== "unknown" || evidence.limitations.length === 0))
|
|
254
|
+
context.addIssue({
|
|
255
|
+
code: "custom",
|
|
256
|
+
message: "Unknown and unavailable facts require unknown confidence and an explicit limitation",
|
|
257
|
+
});
|
|
258
|
+
if (evidence.state === "observed" && evidence.confidence === "unknown")
|
|
259
|
+
context.addIssue({
|
|
260
|
+
code: "custom",
|
|
261
|
+
path: ["confidence"],
|
|
262
|
+
message: "Observed facts require a bounded non-unknown confidence",
|
|
263
|
+
});
|
|
264
|
+
if (evidence.state === "inferred" &&
|
|
265
|
+
!["high", "medium", "low"].includes(evidence.confidence))
|
|
266
|
+
context.addIssue({
|
|
267
|
+
code: "custom",
|
|
268
|
+
path: ["confidence"],
|
|
269
|
+
message: "Inferred facts require high, medium, or low confidence",
|
|
270
|
+
});
|
|
271
|
+
if (evidence.confidence === "exact" && evidence.state !== "observed")
|
|
272
|
+
context.addIssue({
|
|
273
|
+
code: "custom",
|
|
274
|
+
path: ["confidence"],
|
|
275
|
+
message: "Exact confidence is reserved for observed facts",
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
const checkTraceability = (evidence, context) => {
|
|
279
|
+
if (evidence.state === "observed" && !evidence.location.available)
|
|
280
|
+
context.addIssue({
|
|
281
|
+
code: "custom",
|
|
282
|
+
path: ["location"],
|
|
283
|
+
message: "Observed facts require an actionable location",
|
|
284
|
+
});
|
|
285
|
+
if (evidence.state === "inferred" && evidence.limitations.length === 0)
|
|
286
|
+
context.addIssue({
|
|
287
|
+
code: "custom",
|
|
288
|
+
path: ["limitations"],
|
|
289
|
+
message: "Inferred facts require an explicit limitation",
|
|
290
|
+
});
|
|
291
|
+
if (evidence.coverage.status !== "complete" &&
|
|
292
|
+
evidence.limitations.length === 0)
|
|
293
|
+
context.addIssue({
|
|
294
|
+
code: "custom",
|
|
295
|
+
path: ["limitations"],
|
|
296
|
+
message: "Non-complete coverage requires an explicit limitation",
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
const checkArtifactBacking = (evidence, context) => {
|
|
300
|
+
const artifactBacked = [
|
|
301
|
+
"artifact-bytes",
|
|
302
|
+
"ast-static-analysis",
|
|
303
|
+
"controlled-replay",
|
|
304
|
+
"native-analysis-provider",
|
|
305
|
+
"managed-static-analysis",
|
|
306
|
+
].includes(evidence.authority);
|
|
307
|
+
if (artifactBacked &&
|
|
308
|
+
evidence.state !== "unavailable" &&
|
|
309
|
+
!evidence.artifact.available)
|
|
310
|
+
context.addIssue({
|
|
311
|
+
code: "custom",
|
|
312
|
+
path: ["artifact"],
|
|
313
|
+
message: "Artifact-backed facts require a content digest",
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
/** Provenance, authority, uncertainty, and bounds for one graph fact. */
|
|
317
|
+
export const applicationGraphEvidenceSchema = applicationGraphEvidenceBaseSchema.superRefine((evidence, context) => {
|
|
318
|
+
checkAuthorityState(evidence, context);
|
|
319
|
+
checkStateConfidence(evidence, context);
|
|
320
|
+
checkTraceability(evidence, context);
|
|
321
|
+
checkArtifactBacking(evidence, context);
|
|
322
|
+
});
|
|
323
|
+
/** Stable node identifier strategies and their explicit scope. */
|
|
324
|
+
export const applicationNodeIdentitySchema = z.discriminatedUnion("strategy", [
|
|
325
|
+
z.strictObject({
|
|
326
|
+
strategy: z.literal("content-digest"),
|
|
327
|
+
stability: z.literal("global-exact"),
|
|
328
|
+
sha256: digestSchema,
|
|
329
|
+
}),
|
|
330
|
+
z.strictObject({
|
|
331
|
+
strategy: z.literal("source-map-original"),
|
|
332
|
+
stability: z.literal("source-map-exact"),
|
|
333
|
+
source_map_sha256: digestSchema,
|
|
334
|
+
original_source: boundedTextSchema,
|
|
335
|
+
source_sha256: digestSchema.nullable(),
|
|
336
|
+
}),
|
|
337
|
+
z.strictObject({
|
|
338
|
+
strategy: z.literal("canonical-path"),
|
|
339
|
+
stability: z.literal("artifact-version"),
|
|
340
|
+
artifact_sha256: digestSchema,
|
|
341
|
+
path: relativePathSchema,
|
|
342
|
+
}),
|
|
343
|
+
z.strictObject({
|
|
344
|
+
strategy: z.literal("structural-fingerprint"),
|
|
345
|
+
stability: z.literal("cross-version-inference"),
|
|
346
|
+
algorithm: z.string().min(1).max(128),
|
|
347
|
+
fingerprint_sha256: digestSchema,
|
|
348
|
+
basis: z
|
|
349
|
+
.array(z.enum([
|
|
350
|
+
"syntax-tree",
|
|
351
|
+
"control-flow",
|
|
352
|
+
"imports",
|
|
353
|
+
"exports",
|
|
354
|
+
"literals",
|
|
355
|
+
"api-usage",
|
|
356
|
+
]))
|
|
357
|
+
.min(1)
|
|
358
|
+
.max(6),
|
|
359
|
+
}),
|
|
360
|
+
z.strictObject({
|
|
361
|
+
strategy: z.literal("artifact-local-key"),
|
|
362
|
+
stability: z.literal("artifact-version"),
|
|
363
|
+
artifact_sha256: digestSchema,
|
|
364
|
+
namespace: boundedKeySchema,
|
|
365
|
+
key: boundedTextSchema,
|
|
366
|
+
}),
|
|
367
|
+
z.strictObject({
|
|
368
|
+
strategy: z.literal("runtime-instance"),
|
|
369
|
+
stability: z.literal("capture-only"),
|
|
370
|
+
capture_sha256: digestSchema,
|
|
371
|
+
runtime_key: boundedTextSchema,
|
|
372
|
+
}),
|
|
373
|
+
z.strictObject({
|
|
374
|
+
strategy: z.literal("observation-fingerprint"),
|
|
375
|
+
stability: z.literal("observation-only"),
|
|
376
|
+
observation_sha256: digestSchema,
|
|
377
|
+
scope: boundedTextSchema,
|
|
378
|
+
}),
|
|
379
|
+
]);
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { applicationEdgeInputSchema, applicationEdgeSchema, applicationNodeInputSchema, applicationNodeObservationSchema, applicationNodeSchema, javascriptApplicationGraphInputSchema, javascriptApplicationGraphRecordSchema, } from "./javascriptApplicationGraphSchemas.js";
|
|
5
|
+
/** Compare strings by Unicode code point for canonical graph ordering. */
|
|
6
|
+
export const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
7
|
+
const canonicalJson = (value) => {
|
|
8
|
+
const encoded = canonicalize(value);
|
|
9
|
+
if (encoded === undefined)
|
|
10
|
+
throw new TypeError("JavaScript Application Graph could not canonicalize data");
|
|
11
|
+
return encoded;
|
|
12
|
+
};
|
|
13
|
+
const digestCanonical = (value) => createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
14
|
+
const OBSERVATION_IDENTIFIER_STRATEGY = {
|
|
15
|
+
strategy: "semantic-content-sha256",
|
|
16
|
+
stability: "observation-exact",
|
|
17
|
+
};
|
|
18
|
+
const EDGE_IDENTIFIER_STRATEGY = {
|
|
19
|
+
strategy: "semantic-content-sha256",
|
|
20
|
+
stability: "relationship-exact",
|
|
21
|
+
};
|
|
22
|
+
const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
|
|
23
|
+
const limitKey = (limit) => canonicalJson(limit);
|
|
24
|
+
const normalizeLimits = (limits) => [...new Map(limits.map((limit) => [limitKey(limit), limit])).values()].sort((left, right) => compareCodePoints(limitKey(left), limitKey(right)));
|
|
25
|
+
const normalizeEvidence = (evidence) => ({
|
|
26
|
+
...evidence,
|
|
27
|
+
coverage: {
|
|
28
|
+
...evidence.coverage,
|
|
29
|
+
limits: normalizeLimits(evidence.coverage.limits),
|
|
30
|
+
},
|
|
31
|
+
limitations: uniqueSorted(evidence.limitations),
|
|
32
|
+
evidence_ids: uniqueSorted(evidence.evidence_ids),
|
|
33
|
+
});
|
|
34
|
+
const normalizeIdentity = (identity) => identity.strategy === "structural-fingerprint"
|
|
35
|
+
? { ...identity, basis: uniqueSorted(identity.basis) }
|
|
36
|
+
: identity;
|
|
37
|
+
const nodeSemantic = (node) => ({
|
|
38
|
+
kind: node.kind,
|
|
39
|
+
identity: node.identity,
|
|
40
|
+
});
|
|
41
|
+
const nodeId = (node) => `jag_node_${digestCanonical(nodeSemantic(node))}`;
|
|
42
|
+
const observationSemantic = (nodeIdentifier, observation) => ({ node_id: nodeIdentifier, ...observation });
|
|
43
|
+
const observationId = (nodeIdentifier, observation) => `jag_observation_${digestCanonical(observationSemantic(nodeIdentifier, observation))}`;
|
|
44
|
+
const edgeId = (edge) => `jag_edge_${digestCanonical(edge)}`;
|
|
45
|
+
const sortedUniqueIssue = (values, path, label, context) => {
|
|
46
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
47
|
+
if (compareCodePoints(values[index - 1] ?? "", values[index] ?? "") < 0)
|
|
48
|
+
continue;
|
|
49
|
+
context.addIssue({
|
|
50
|
+
code: "custom",
|
|
51
|
+
message: `${label} must be sorted and unique by Unicode code point`,
|
|
52
|
+
path: [...path, index],
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const checkEvidenceNormalization = (evidence, path, context) => {
|
|
58
|
+
sortedUniqueIssue(evidence.limitations, [...path, "limitations"], "Evidence limitations", context);
|
|
59
|
+
sortedUniqueIssue(evidence.evidence_ids, [...path, "evidence_ids"], "Evidence identifiers", context);
|
|
60
|
+
sortedUniqueIssue(evidence.coverage.limits.map(limitKey), [...path, "coverage", "limits"], "Evidence limits", context);
|
|
61
|
+
const runtimeLocation = evidence.location.available && evidence.location.value.kind === "runtime";
|
|
62
|
+
const staticAuthority = [
|
|
63
|
+
"artifact-bytes",
|
|
64
|
+
"ast-static-analysis",
|
|
65
|
+
"static-relationship-inference",
|
|
66
|
+
"native-analysis-provider",
|
|
67
|
+
].includes(evidence.authority);
|
|
68
|
+
if (runtimeLocation && staticAuthority)
|
|
69
|
+
context.addIssue({
|
|
70
|
+
code: "custom",
|
|
71
|
+
message: "Static and native authorities cannot claim a runtime location",
|
|
72
|
+
path: [...path, "location"],
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const observationMatchesIdentity = (node, observation) => {
|
|
76
|
+
const { identity } = node;
|
|
77
|
+
const { evidence } = observation;
|
|
78
|
+
if (identity.strategy === "content-digest")
|
|
79
|
+
return (evidence.state === "observed" &&
|
|
80
|
+
evidence.artifact.available &&
|
|
81
|
+
evidence.artifact.sha256 === identity.sha256);
|
|
82
|
+
if (identity.strategy === "source-map-original")
|
|
83
|
+
return (evidence.state === "observed" &&
|
|
84
|
+
evidence.artifact.available &&
|
|
85
|
+
evidence.artifact.sha256 === identity.source_map_sha256);
|
|
86
|
+
if (identity.strategy === "canonical-path")
|
|
87
|
+
return (evidence.artifact.available &&
|
|
88
|
+
evidence.artifact.sha256 === identity.artifact_sha256 &&
|
|
89
|
+
evidence.location.available &&
|
|
90
|
+
evidence.location.value.kind === "artifact-path" &&
|
|
91
|
+
evidence.location.value.path === identity.path);
|
|
92
|
+
if (identity.strategy === "artifact-local-key")
|
|
93
|
+
return (evidence.artifact.available &&
|
|
94
|
+
evidence.artifact.sha256 === identity.artifact_sha256);
|
|
95
|
+
if (identity.strategy === "runtime-instance") {
|
|
96
|
+
if (!["passive-cdp-runtime", "controlled-replay"].includes(evidence.authority) ||
|
|
97
|
+
!evidence.location.available ||
|
|
98
|
+
evidence.location.value.kind !== "runtime")
|
|
99
|
+
return false;
|
|
100
|
+
const location = evidence.location.value;
|
|
101
|
+
return (location.capture_sha256 === identity.capture_sha256 &&
|
|
102
|
+
[location.target_key, location.frame_key, location.script_key].includes(identity.runtime_key));
|
|
103
|
+
}
|
|
104
|
+
if (identity.strategy === "structural-fingerprint")
|
|
105
|
+
return evidence.state === "inferred";
|
|
106
|
+
return evidence.state === "observed";
|
|
107
|
+
};
|
|
108
|
+
const checkNode = (node, index, context) => {
|
|
109
|
+
if (node.node_id !== nodeId(node))
|
|
110
|
+
context.addIssue({
|
|
111
|
+
code: "custom",
|
|
112
|
+
message: "Node identifier does not match its kind and identity",
|
|
113
|
+
path: ["nodes", index, "node_id"],
|
|
114
|
+
});
|
|
115
|
+
if (node.identity.strategy === "structural-fingerprint" &&
|
|
116
|
+
canonicalJson(node.identity.basis) !==
|
|
117
|
+
canonicalJson(uniqueSorted(node.identity.basis)))
|
|
118
|
+
context.addIssue({
|
|
119
|
+
code: "custom",
|
|
120
|
+
message: "Structural fingerprint basis must be sorted and unique",
|
|
121
|
+
path: ["nodes", index, "identity", "basis"],
|
|
122
|
+
});
|
|
123
|
+
sortedUniqueIssue(node.observations.map(({ observation_id: id }) => id), ["nodes", index, "observations"], "Node observations", context);
|
|
124
|
+
for (const [observationIndex, observation] of node.observations.entries()) {
|
|
125
|
+
const { observation_id: identifier, ...semantic } = observation;
|
|
126
|
+
if (identifier !== observationId(node.node_id, semantic))
|
|
127
|
+
context.addIssue({
|
|
128
|
+
code: "custom",
|
|
129
|
+
message: "Observation identifier does not match its semantic content",
|
|
130
|
+
path: [
|
|
131
|
+
"nodes",
|
|
132
|
+
index,
|
|
133
|
+
"observations",
|
|
134
|
+
observationIndex,
|
|
135
|
+
"observation_id",
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
checkEvidenceNormalization(observation.evidence, ["nodes", index, "observations", observationIndex, "evidence"], context);
|
|
139
|
+
}
|
|
140
|
+
if (!node.observations.some((observation) => observationMatchesIdentity(node, observation)))
|
|
141
|
+
context.addIssue({
|
|
142
|
+
code: "custom",
|
|
143
|
+
message: "Node identity is not supported by a compatible observation",
|
|
144
|
+
path: ["nodes", index, "identity"],
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
const checkGraphInvariants = (graph, context) => {
|
|
148
|
+
sortedUniqueIssue(graph.root_node_ids, ["root_node_ids"], "Root nodes", context);
|
|
149
|
+
sortedUniqueIssue(graph.nodes.map(({ node_id: id }) => id), ["nodes"], "Nodes", context);
|
|
150
|
+
sortedUniqueIssue(graph.edges.map(({ edge_id: id }) => id), ["edges"], "Edges", context);
|
|
151
|
+
sortedUniqueIssue(graph.limitations, ["limitations"], "Graph limitations", context);
|
|
152
|
+
sortedUniqueIssue(graph.coverage.limits.map(limitKey), ["coverage", "limits"], "Graph limits", context);
|
|
153
|
+
if (graph.coverage.status !== "complete" && graph.limitations.length === 0)
|
|
154
|
+
context.addIssue({
|
|
155
|
+
code: "custom",
|
|
156
|
+
message: "Non-complete graph coverage requires an explicit limitation",
|
|
157
|
+
path: ["limitations"],
|
|
158
|
+
});
|
|
159
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.node_id, node]));
|
|
160
|
+
for (const root of graph.root_node_ids)
|
|
161
|
+
if (!nodesById.has(root))
|
|
162
|
+
context.addIssue({
|
|
163
|
+
code: "custom",
|
|
164
|
+
message: "Root identifier must name a graph node",
|
|
165
|
+
path: ["root_node_ids"],
|
|
166
|
+
});
|
|
167
|
+
for (const [index, node] of graph.nodes.entries())
|
|
168
|
+
checkNode(node, index, context);
|
|
169
|
+
for (const [index, edge] of graph.edges.entries()) {
|
|
170
|
+
const { edge_id: identifier, ...semantic } = edge;
|
|
171
|
+
if (identifier !== edgeId(semantic))
|
|
172
|
+
context.addIssue({
|
|
173
|
+
code: "custom",
|
|
174
|
+
message: "Edge identifier does not match its semantic content",
|
|
175
|
+
path: ["edges", index, "edge_id"],
|
|
176
|
+
});
|
|
177
|
+
if (!nodesById.has(edge.source_node_id) ||
|
|
178
|
+
!nodesById.has(edge.target_node_id))
|
|
179
|
+
context.addIssue({
|
|
180
|
+
code: "custom",
|
|
181
|
+
message: "Edge endpoints must name graph nodes",
|
|
182
|
+
path: ["edges", index],
|
|
183
|
+
});
|
|
184
|
+
if (edge.source_node_id === edge.target_node_id)
|
|
185
|
+
context.addIssue({
|
|
186
|
+
code: "custom",
|
|
187
|
+
message: "Application graph edges cannot be self-referential",
|
|
188
|
+
path: ["edges", index],
|
|
189
|
+
});
|
|
190
|
+
if (edge.relation === "changed_from" &&
|
|
191
|
+
nodesById.get(edge.source_node_id)?.kind !==
|
|
192
|
+
nodesById.get(edge.target_node_id)?.kind)
|
|
193
|
+
context.addIssue({
|
|
194
|
+
code: "custom",
|
|
195
|
+
message: "changed_from endpoints must have the same node kind",
|
|
196
|
+
path: ["edges", index, "relation"],
|
|
197
|
+
});
|
|
198
|
+
checkEvidenceNormalization(edge.evidence, ["edges", index, "evidence"], context);
|
|
199
|
+
}
|
|
200
|
+
const { graph_id: identifier, ...semantic } = graph;
|
|
201
|
+
if (identifier !== `jag_${digestCanonical(semantic)}`)
|
|
202
|
+
context.addIssue({
|
|
203
|
+
code: "custom",
|
|
204
|
+
message: "Graph identifier does not match its semantic content",
|
|
205
|
+
path: ["graph_id"],
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
/** Strict versioned JavaScript Application Graph with verified commitments. */
|
|
209
|
+
export const javascriptApplicationGraphSchema = javascriptApplicationGraphRecordSchema.superRefine(checkGraphInvariants);
|
|
210
|
+
/** Build one normalized application entity and derive all semantic IDs. */
|
|
211
|
+
export const createJavaScriptApplicationNode = (input) => {
|
|
212
|
+
const parsed = applicationNodeInputSchema.parse(input);
|
|
213
|
+
const identity = normalizeIdentity(parsed.identity);
|
|
214
|
+
const identifier = nodeId({ kind: parsed.kind, identity });
|
|
215
|
+
const observations = parsed.observations
|
|
216
|
+
.map((observation) => {
|
|
217
|
+
const semantic = {
|
|
218
|
+
...observation,
|
|
219
|
+
evidence: normalizeEvidence(observation.evidence),
|
|
220
|
+
identifier_strategy: OBSERVATION_IDENTIFIER_STRATEGY,
|
|
221
|
+
};
|
|
222
|
+
return applicationNodeObservationSchema.parse({
|
|
223
|
+
...semantic,
|
|
224
|
+
observation_id: observationId(identifier, semantic),
|
|
225
|
+
});
|
|
226
|
+
})
|
|
227
|
+
.sort((left, right) => compareCodePoints(left.observation_id, right.observation_id));
|
|
228
|
+
return applicationNodeSchema.parse({
|
|
229
|
+
node_id: identifier,
|
|
230
|
+
kind: parsed.kind,
|
|
231
|
+
identity,
|
|
232
|
+
observations,
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
/** Build one normalized directed relationship and derive its semantic ID. */
|
|
236
|
+
export const createJavaScriptApplicationEdge = (input) => {
|
|
237
|
+
const parsed = applicationEdgeInputSchema.parse(input);
|
|
238
|
+
const semantic = {
|
|
239
|
+
...parsed,
|
|
240
|
+
evidence: normalizeEvidence(parsed.evidence),
|
|
241
|
+
identifier_strategy: EDGE_IDENTIFIER_STRATEGY,
|
|
242
|
+
};
|
|
243
|
+
return applicationEdgeSchema.parse({
|
|
244
|
+
...semantic,
|
|
245
|
+
edge_id: edgeId(semantic),
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
/** Normalize a complete graph and derive its top-level semantic ID. */
|
|
249
|
+
export const createJavaScriptApplicationGraph = (input) => {
|
|
250
|
+
const parsed = javascriptApplicationGraphInputSchema.parse(input);
|
|
251
|
+
const semantic = {
|
|
252
|
+
...parsed,
|
|
253
|
+
root_node_ids: uniqueSorted(parsed.root_node_ids),
|
|
254
|
+
nodes: [...parsed.nodes].sort((left, right) => compareCodePoints(left.node_id, right.node_id)),
|
|
255
|
+
edges: [...parsed.edges].sort((left, right) => compareCodePoints(left.edge_id, right.edge_id)),
|
|
256
|
+
coverage: {
|
|
257
|
+
...parsed.coverage,
|
|
258
|
+
limits: normalizeLimits(parsed.coverage.limits),
|
|
259
|
+
},
|
|
260
|
+
limitations: uniqueSorted(parsed.limitations),
|
|
261
|
+
};
|
|
262
|
+
return javascriptApplicationGraphSchema.parse({
|
|
263
|
+
...semantic,
|
|
264
|
+
graph_id: `jag_${digestCanonical(semantic)}`,
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
/** Parse a stored graph, rejecting unsupported versions and stale IDs. */
|
|
268
|
+
export const parseJavaScriptApplicationGraph = (input) => {
|
|
269
|
+
const envelope = z
|
|
270
|
+
.object({ schema: z.string(), schema_version: z.number() })
|
|
271
|
+
.passthrough()
|
|
272
|
+
.safeParse(input);
|
|
273
|
+
if (envelope.success &&
|
|
274
|
+
envelope.data.schema === "JavaScriptApplicationGraph" &&
|
|
275
|
+
envelope.data.schema_version !== 1)
|
|
276
|
+
throw new TypeError(`Unsupported JavaScript Application Graph schema version: ${String(envelope.data.schema_version)}`);
|
|
277
|
+
return javascriptApplicationGraphSchema.parse(input);
|
|
278
|
+
};
|
|
279
|
+
/** Compute the byte-stable SHA-256 commitment of a verified graph. */
|
|
280
|
+
export const computeJavaScriptApplicationGraphSha256 = (input) => digestCanonical(parseJavaScriptApplicationGraph(input));
|
|
281
|
+
/** Serialize a verified graph as RFC 8785 canonical JSON. */
|
|
282
|
+
export const serializeJavaScriptApplicationGraph = (input) => canonicalJson(parseJavaScriptApplicationGraph(input));
|