rea-agents 0.3.0 → 0.5.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 +139 -21
- package/bridge/hopper_bridge.py +156 -14
- package/dist/application/AnalysisProvider.js +12 -1
- package/dist/application/ArtifactExtraction.js +166 -0
- package/dist/application/ArtifactGraphConstruction.js +257 -0
- package/dist/application/ArtifactInventory.js +253 -0
- package/dist/application/BinarySession.js +174 -14
- package/dist/application/CompositeProvider.js +73 -0
- package/dist/application/DirectAnalysis.js +44 -3
- package/dist/application/Doctor.js +35 -6
- package/dist/application/EnhancedTools.js +10 -7
- package/dist/application/EvidenceBundleCommands.js +51 -0
- package/dist/application/EvidenceBundleFiles.js +127 -0
- package/dist/application/EvidenceLedger.js +225 -18
- package/dist/application/FilesystemSnapshot.js +124 -0
- package/dist/application/LinuxHopper.js +186 -0
- package/dist/application/LoopbackReplay.js +195 -37
- package/dist/application/ProcessHarness.js +200 -191
- package/dist/application/ProcessNormalization.js +44 -0
- package/dist/application/ProcessOwnership.js +105 -0
- package/dist/application/ProcessSampling.js +284 -0
- package/dist/application/RealHopperAssertions.js +116 -0
- package/dist/application/ReferenceSourceImport.js +182 -0
- package/dist/application/ReferenceSourceImportEntries.js +122 -0
- package/dist/application/ReferenceSourceImportPolicy.js +73 -0
- package/dist/application/ReferenceSourceImportTypes.js +18 -0
- package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
- package/dist/application/Setup.js +192 -41
- package/dist/application/Uninstall.js +130 -0
- package/dist/application/runtime.js +8 -1
- package/dist/artifacts/ArtifactPaths.js +51 -0
- package/dist/artifacts/ArtifactProvider.js +130 -0
- package/dist/artifacts/ArtifactReader.js +9 -0
- package/dist/artifacts/AsarArtifactReader.js +62 -0
- package/dist/artifacts/DirectoryArtifactReader.js +107 -0
- package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
- package/dist/artifacts/SafeOutputTree.js +199 -0
- package/dist/artifacts/StreamBytes.js +10 -0
- package/dist/artifacts/ZipArtifactReader.js +109 -0
- package/dist/cli.js +229 -21
- package/dist/cliEvidenceCommands.js +68 -0
- package/dist/cliLogging.js +21 -0
- package/dist/config.js +35 -1
- package/dist/contracts/artifactComparisonExample.js +95 -0
- package/dist/contracts/artifactToolContracts.js +84 -0
- package/dist/contracts/enhancedInputs.js +4 -0
- package/dist/contracts/functionComparisonExample.js +57 -0
- package/dist/contracts/investigationExamples.js +118 -0
- package/dist/contracts/nativeToolContracts.js +52 -0
- package/dist/contracts/processCaptureExample.js +25 -0
- package/dist/contracts/toolContractExamples.js +69 -0
- package/dist/contracts/toolContracts.js +99 -36
- package/dist/contracts/toolOutputSchemas.js +156 -82
- package/dist/contracts/unknownContractExamples.js +33 -0
- package/dist/domain/artifactComparison.js +273 -0
- package/dist/domain/artifactGraph.js +194 -0
- package/dist/domain/artifactInventoryEvidence.js +150 -0
- package/dist/domain/binaryTarget.js +55 -0
- package/dist/domain/bundleComparison.js +266 -0
- package/dist/domain/callPath.js +346 -0
- package/dist/domain/changedBehavior.js +294 -0
- package/dist/domain/errors.js +199 -5
- package/dist/domain/evidence.js +41 -10
- package/dist/domain/evidenceBundle.js +187 -6
- package/dist/domain/functionComparison.js +201 -0
- package/dist/domain/functionComparisonNormalization.js +112 -0
- package/dist/domain/functionComparisonResults.js +54 -0
- package/dist/domain/functionComparisonSchemas.js +82 -0
- package/dist/domain/functionDossierEvidence.js +171 -0
- package/dist/domain/hopperValues.js +35 -8
- package/dist/domain/nativeInspection.js +142 -0
- package/dist/domain/processCapture.js +152 -54
- package/dist/domain/processComparison.js +106 -0
- package/dist/domain/reconstructionUnknowns.js +90 -0
- package/dist/domain/reconstructionVerification.js +285 -0
- package/dist/domain/reconstructionVerificationSchemas.js +126 -0
- package/dist/domain/referenceSourceClassification.js +496 -0
- package/dist/domain/referenceSourceGraph.js +376 -0
- package/dist/domain/referenceSourceImportParsing.js +235 -0
- package/dist/domain/referenceSourcePolicy.js +1 -0
- package/dist/domain/residualUnknown.js +239 -0
- package/dist/domain/staticRuntimeCorrelation.js +375 -0
- package/dist/hopper/BridgeLauncher.js +39 -3
- package/dist/hopper/HopperClient.js +14 -5
- package/dist/hopper/HopperProvider.js +57 -22
- package/dist/hopper/protocol.js +13 -2
- package/dist/identity.js +1 -0
- package/dist/main.js +5 -1
- package/dist/native/CommandRunner.js +156 -0
- package/dist/native/NativeMacOSProvider.js +306 -0
- package/dist/native/NativeMachoInspection.js +135 -0
- package/dist/native/parsers/codesign.js +55 -0
- package/dist/native/parsers/demangle.js +26 -0
- package/dist/native/parsers/dyldInfo.js +25 -0
- package/dist/native/parsers/lipo.js +67 -0
- package/dist/native/parsers/otool.js +193 -0
- package/dist/native/parsers/plist.js +23 -0
- package/dist/reference/ReferenceSourceReader.js +73 -0
- package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
- package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
- package/dist/reference/ReferenceSourceReaderFile.js +119 -0
- package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
- package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
- package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
- package/dist/server/createServer.js +31 -5
- package/dist/server/recordDerivedEvidence.js +10 -0
- package/dist/server/registerArtifactComparisonTool.js +62 -0
- package/dist/server/registerArtifactTools.js +6 -0
- package/dist/server/registerBundleComparisonTool.js +47 -0
- package/dist/server/registerEnhancedTools.js +64 -12
- package/dist/server/registerEvidenceTools.js +36 -0
- package/dist/server/registerFunctionComparisonTool.js +68 -0
- package/dist/server/registerInvestigationTools.js +224 -0
- package/dist/server/registerNativeTools.js +6 -0
- package/dist/server/registerOfficialTools.js +47 -14
- package/dist/server/registerProcessComparisonTool.js +106 -0
- package/dist/server/registerSessionTools.js +179 -70
- package/dist/server/sessionEvidence.js +28 -0
- package/dist/server/sessionToolPolicies.js +64 -0
- package/dist/server/toolRegistrationOptions.js +7 -0
- package/dist/server/toolResult.js +8 -5
- package/install.sh +198 -0
- package/package.json +18 -1
- package/scripts/rea.mjs +5 -1
- package/skills/rea-analysis/SKILL.md +77 -2
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
5
|
+
const unknownIdSchema = z.string().regex(/^unk_[a-f0-9]{64}$/u);
|
|
6
|
+
const boundedText = z.string().trim().min(1).max(1_000);
|
|
7
|
+
const environmentRequirementSchema = z.object({
|
|
8
|
+
id: z.string().min(1).max(200).nullable(),
|
|
9
|
+
platform: z.string().min(1).max(100).nullable(),
|
|
10
|
+
architecture: z.string().min(1).max(100).nullable(),
|
|
11
|
+
isolation: z
|
|
12
|
+
.enum(["none", "process", "container", "virtual-machine"])
|
|
13
|
+
.nullable(),
|
|
14
|
+
});
|
|
15
|
+
const probeSchema = z.object({
|
|
16
|
+
operation: z.string().min(1).max(200),
|
|
17
|
+
rationale: boundedText,
|
|
18
|
+
});
|
|
19
|
+
const relationshipSchema = z.object({
|
|
20
|
+
type: z.enum(["depends-on", "related-to", "contradicts"]),
|
|
21
|
+
unknown_id: unknownIdSchema,
|
|
22
|
+
});
|
|
23
|
+
const resolutionSchema = z.discriminatedUnion("disposition", [
|
|
24
|
+
z.object({
|
|
25
|
+
disposition: z.literal("verified"),
|
|
26
|
+
rationale: boundedText,
|
|
27
|
+
evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
|
|
28
|
+
}),
|
|
29
|
+
z.object({
|
|
30
|
+
disposition: z.enum(["withdrawn", "out-of-scope"]),
|
|
31
|
+
rationale: boundedText,
|
|
32
|
+
evidence_ids: z.array(evidenceIdSchema).max(100),
|
|
33
|
+
}),
|
|
34
|
+
]);
|
|
35
|
+
/** Strict durable record for one unresolved or evidence-qualified question. */
|
|
36
|
+
const residualUnknownObjectSchema = z.object({
|
|
37
|
+
registry_version: z.literal(1),
|
|
38
|
+
unknown_id: unknownIdSchema,
|
|
39
|
+
revision: z.number().int().min(1),
|
|
40
|
+
previous_revision_digest: z
|
|
41
|
+
.string()
|
|
42
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
43
|
+
.nullable(),
|
|
44
|
+
revision_digest: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
45
|
+
scope_digest: z
|
|
46
|
+
.string()
|
|
47
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
48
|
+
.nullable(),
|
|
49
|
+
question: boundedText,
|
|
50
|
+
status: z.enum([
|
|
51
|
+
"open",
|
|
52
|
+
"investigating",
|
|
53
|
+
"blocked",
|
|
54
|
+
"contradicted",
|
|
55
|
+
"resolved",
|
|
56
|
+
]),
|
|
57
|
+
severity: z.enum(["low", "medium", "high", "critical"]),
|
|
58
|
+
domain: z.string().trim().min(1).max(100),
|
|
59
|
+
supporting_evidence_ids: z.array(evidenceIdSchema).max(100),
|
|
60
|
+
contradicting_evidence_ids: z.array(evidenceIdSchema).max(100),
|
|
61
|
+
required_authority: z
|
|
62
|
+
.enum([
|
|
63
|
+
"shipped-artifact",
|
|
64
|
+
"controlled-replay",
|
|
65
|
+
"historical-reference",
|
|
66
|
+
"external-service",
|
|
67
|
+
"analyst-inference",
|
|
68
|
+
])
|
|
69
|
+
.nullable(),
|
|
70
|
+
required_confidence: z.enum(["observed", "derived", "inferred"]),
|
|
71
|
+
required_environment: environmentRequirementSchema.nullable(),
|
|
72
|
+
recommended_probes: z.array(probeSchema).max(20),
|
|
73
|
+
relationships: z.array(relationshipSchema).max(100),
|
|
74
|
+
resolution: resolutionSchema.nullable(),
|
|
75
|
+
mutation_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
|
|
76
|
+
});
|
|
77
|
+
export const residualUnknownSchema = residualUnknownObjectSchema.superRefine((value, context) => {
|
|
78
|
+
if (computeRevisionDigest(value) !== value.revision_digest)
|
|
79
|
+
context.addIssue({
|
|
80
|
+
code: "custom",
|
|
81
|
+
message: "Residual unknown revision digest does not match",
|
|
82
|
+
path: ["revision_digest"],
|
|
83
|
+
});
|
|
84
|
+
if ((value.revision === 1) !== (value.previous_revision_digest === null))
|
|
85
|
+
context.addIssue({
|
|
86
|
+
code: "custom",
|
|
87
|
+
message: "Only the first revision has no predecessor",
|
|
88
|
+
path: ["previous_revision_digest"],
|
|
89
|
+
});
|
|
90
|
+
if ((value.status === "resolved") !== (value.resolution !== null))
|
|
91
|
+
context.addIssue({
|
|
92
|
+
code: "custom",
|
|
93
|
+
message: "Only resolved unknowns carry a resolution",
|
|
94
|
+
path: ["resolution"],
|
|
95
|
+
});
|
|
96
|
+
if (value.status === "contradicted" &&
|
|
97
|
+
value.contradicting_evidence_ids.length === 0)
|
|
98
|
+
context.addIssue({
|
|
99
|
+
code: "custom",
|
|
100
|
+
message: "Contradicted status requires contradicting evidence",
|
|
101
|
+
path: ["contradicting_evidence_ids"],
|
|
102
|
+
});
|
|
103
|
+
if (value.relationships.some((relationship) => relationship.unknown_id === value.unknown_id))
|
|
104
|
+
context.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
message: "An unknown cannot relate to itself",
|
|
107
|
+
path: ["relationships"],
|
|
108
|
+
});
|
|
109
|
+
for (const ids of [
|
|
110
|
+
value.supporting_evidence_ids,
|
|
111
|
+
value.contradicting_evidence_ids,
|
|
112
|
+
value.mutation_evidence_ids,
|
|
113
|
+
])
|
|
114
|
+
if (new Set(ids).size !== ids.length)
|
|
115
|
+
context.addIssue({
|
|
116
|
+
code: "custom",
|
|
117
|
+
message: "Evidence IDs must be unique",
|
|
118
|
+
});
|
|
119
|
+
if (value.supporting_evidence_ids.some((id) => value.contradicting_evidence_ids.includes(id)))
|
|
120
|
+
context.addIssue({
|
|
121
|
+
code: "custom",
|
|
122
|
+
message: "Evidence cannot simultaneously support and contradict",
|
|
123
|
+
});
|
|
124
|
+
const relationships = value.relationships.map((relationship) => `${relationship.type}:${relationship.unknown_id}`);
|
|
125
|
+
if (new Set(relationships).size !== relationships.length)
|
|
126
|
+
context.addIssue({
|
|
127
|
+
code: "custom",
|
|
128
|
+
message: "Relationships must be unique",
|
|
129
|
+
path: ["relationships"],
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
/** Input for deterministic creation of one residual unknown. */
|
|
133
|
+
export const recordUnknownInputSchema = residualUnknownObjectSchema
|
|
134
|
+
.omit({
|
|
135
|
+
registry_version: true,
|
|
136
|
+
unknown_id: true,
|
|
137
|
+
revision: true,
|
|
138
|
+
previous_revision_digest: true,
|
|
139
|
+
revision_digest: true,
|
|
140
|
+
scope_digest: true,
|
|
141
|
+
status: true,
|
|
142
|
+
supporting_evidence_ids: true,
|
|
143
|
+
contradicting_evidence_ids: true,
|
|
144
|
+
resolution: true,
|
|
145
|
+
mutation_evidence_ids: true,
|
|
146
|
+
})
|
|
147
|
+
.extend({
|
|
148
|
+
supporting_evidence_ids: z.array(evidenceIdSchema).max(100).default([]),
|
|
149
|
+
contradicting_evidence_ids: z.array(evidenceIdSchema).max(100).default([]),
|
|
150
|
+
approved: z.literal(true),
|
|
151
|
+
});
|
|
152
|
+
/** Explicit optimistic-concurrency update command for one unknown. */
|
|
153
|
+
export const updateUnknownInputSchema = z.object({
|
|
154
|
+
unknown_id: unknownIdSchema,
|
|
155
|
+
expected_revision: z.number().int().min(1),
|
|
156
|
+
approved: z.literal(true),
|
|
157
|
+
status: residualUnknownObjectSchema.shape.status,
|
|
158
|
+
severity: residualUnknownObjectSchema.shape.severity,
|
|
159
|
+
supporting_evidence_ids: z.array(evidenceIdSchema).max(100),
|
|
160
|
+
contradicting_evidence_ids: z.array(evidenceIdSchema).max(100),
|
|
161
|
+
required_authority: residualUnknownObjectSchema.shape.required_authority,
|
|
162
|
+
required_confidence: residualUnknownObjectSchema.shape.required_confidence,
|
|
163
|
+
required_environment: environmentRequirementSchema.nullable(),
|
|
164
|
+
recommended_probes: z.array(probeSchema).max(20),
|
|
165
|
+
relationships: z.array(relationshipSchema).max(100),
|
|
166
|
+
resolution: resolutionSchema.nullable(),
|
|
167
|
+
});
|
|
168
|
+
/** Create stable identity from immutable question and domain fields. */
|
|
169
|
+
export const createResidualUnknown = (input, mutationEvidenceId, scopeDigest) => {
|
|
170
|
+
const question = input.question.replace(/\s+/gu, " ");
|
|
171
|
+
const identity = canonicalJson({
|
|
172
|
+
domain: input.domain,
|
|
173
|
+
question,
|
|
174
|
+
scope_digest: scopeDigest,
|
|
175
|
+
});
|
|
176
|
+
const revision = {
|
|
177
|
+
registry_version: 1,
|
|
178
|
+
unknown_id: `unk_${createHash("sha256").update(identity).digest("hex")}`,
|
|
179
|
+
revision: 1,
|
|
180
|
+
previous_revision_digest: null,
|
|
181
|
+
scope_digest: scopeDigest,
|
|
182
|
+
question,
|
|
183
|
+
status: input.contradicting_evidence_ids.length > 0 ? "contradicted" : "open",
|
|
184
|
+
domain: input.domain,
|
|
185
|
+
...projectRevisionDetails(input),
|
|
186
|
+
resolution: null,
|
|
187
|
+
mutation_evidence_ids: [mutationEvidenceId],
|
|
188
|
+
};
|
|
189
|
+
return residualUnknownSchema.parse({
|
|
190
|
+
...revision,
|
|
191
|
+
revision_digest: computeRevisionDigest(revision),
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
/** Apply a complete, revision-guarded replacement while preserving identity. */
|
|
195
|
+
export const updateResidualUnknown = (current, input, mutationEvidenceId) => {
|
|
196
|
+
const revision = {
|
|
197
|
+
registry_version: 1,
|
|
198
|
+
unknown_id: current.unknown_id,
|
|
199
|
+
revision: current.revision + 1,
|
|
200
|
+
previous_revision_digest: current.revision_digest,
|
|
201
|
+
scope_digest: current.scope_digest,
|
|
202
|
+
question: current.question,
|
|
203
|
+
status: input.status,
|
|
204
|
+
domain: current.domain,
|
|
205
|
+
...projectRevisionDetails(input),
|
|
206
|
+
resolution: input.resolution,
|
|
207
|
+
mutation_evidence_ids: sortedUnique([
|
|
208
|
+
...current.mutation_evidence_ids,
|
|
209
|
+
mutationEvidenceId,
|
|
210
|
+
]),
|
|
211
|
+
};
|
|
212
|
+
return residualUnknownSchema.parse({
|
|
213
|
+
...revision,
|
|
214
|
+
revision_digest: computeRevisionDigest(revision),
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
const projectRevisionDetails = (input) => ({
|
|
218
|
+
severity: input.severity,
|
|
219
|
+
supporting_evidence_ids: sortedUnique(input.supporting_evidence_ids),
|
|
220
|
+
contradicting_evidence_ids: sortedUnique(input.contradicting_evidence_ids),
|
|
221
|
+
required_authority: input.required_authority,
|
|
222
|
+
required_confidence: input.required_confidence,
|
|
223
|
+
required_environment: input.required_environment,
|
|
224
|
+
recommended_probes: [...input.recommended_probes],
|
|
225
|
+
relationships: sortedRelationships(input.relationships),
|
|
226
|
+
});
|
|
227
|
+
const computeRevisionDigest = (value) => {
|
|
228
|
+
const { revision_digest: _revisionDigest, ...semantic } = "revision_digest" in value ? value : { ...value, revision_digest: "" };
|
|
229
|
+
void _revisionDigest;
|
|
230
|
+
return createHash("sha256").update(canonicalJson(semantic)).digest("hex");
|
|
231
|
+
};
|
|
232
|
+
const canonicalJson = (value) => {
|
|
233
|
+
const serialized = canonicalize(value);
|
|
234
|
+
if (serialized === undefined)
|
|
235
|
+
throw new TypeError("Unknown identity failed");
|
|
236
|
+
return serialized;
|
|
237
|
+
};
|
|
238
|
+
const sortedUnique = (values) => [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
239
|
+
const sortedRelationships = (values) => [...values].sort((left, right) => `${left.type}:${left.unknown_id}`.localeCompare(`${right.type}:${right.unknown_id}`));
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { artifactComparisonResultSchema } from "./artifactComparison.js";
|
|
5
|
+
import { evidenceSchema, parseEvidence } from "./evidence.js";
|
|
6
|
+
import { functionComparisonResultSchema } from "./functionComparison.js";
|
|
7
|
+
import { comparisonStatusSchema, processCaptureComparisonSchema, } from "./processCapture.js";
|
|
8
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
9
|
+
const functionDimensionSchema = z.enum([
|
|
10
|
+
"identity",
|
|
11
|
+
"pseudocode",
|
|
12
|
+
"assembly",
|
|
13
|
+
"comments",
|
|
14
|
+
"calls",
|
|
15
|
+
"references",
|
|
16
|
+
"strings_names",
|
|
17
|
+
"cfg",
|
|
18
|
+
]);
|
|
19
|
+
const runtimeDimensionSchema = z.enum([
|
|
20
|
+
"terminal",
|
|
21
|
+
"exit",
|
|
22
|
+
"filesystem",
|
|
23
|
+
"protocol",
|
|
24
|
+
"process",
|
|
25
|
+
]);
|
|
26
|
+
const expectedPatternSchema = z.enum([
|
|
27
|
+
"cochanged",
|
|
28
|
+
"static_only",
|
|
29
|
+
"runtime_only",
|
|
30
|
+
"both_unchanged",
|
|
31
|
+
]);
|
|
32
|
+
const observedPatternSchema = z.enum([
|
|
33
|
+
...expectedPatternSchema.options,
|
|
34
|
+
"unknown",
|
|
35
|
+
"truncated",
|
|
36
|
+
]);
|
|
37
|
+
const artifactSelectorSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
kind: z.literal("artifact"),
|
|
40
|
+
logical_path: z.string().min(1).max(4_096),
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
43
|
+
const functionSelectorSchema = z
|
|
44
|
+
.object({ kind: z.literal("function"), dimension: functionDimensionSchema })
|
|
45
|
+
.strict();
|
|
46
|
+
const staticSelectorSchema = z.discriminatedUnion("kind", [
|
|
47
|
+
artifactSelectorSchema,
|
|
48
|
+
functionSelectorSchema,
|
|
49
|
+
]);
|
|
50
|
+
const mappingSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
static: z
|
|
53
|
+
.object({
|
|
54
|
+
comparison_evidence_id: evidenceIdSchema,
|
|
55
|
+
selector: staticSelectorSchema,
|
|
56
|
+
})
|
|
57
|
+
.strict(),
|
|
58
|
+
runtime: z
|
|
59
|
+
.object({
|
|
60
|
+
comparison_evidence_id: evidenceIdSchema,
|
|
61
|
+
dimension: runtimeDimensionSchema,
|
|
62
|
+
})
|
|
63
|
+
.strict(),
|
|
64
|
+
side_alignment: z.enum(["left_to_left", "left_to_right"]),
|
|
65
|
+
hypothesis: z
|
|
66
|
+
.object({
|
|
67
|
+
statement: z.string().trim().min(1).max(500),
|
|
68
|
+
expected_pattern: expectedPatternSchema,
|
|
69
|
+
})
|
|
70
|
+
.strict(),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
/** Strict bounded input for explicit static/runtime hypothesis correlation. */
|
|
74
|
+
export const staticRuntimeCorrelationInputSchema = z
|
|
75
|
+
.object({
|
|
76
|
+
static_comparisons: z.array(evidenceSchema).min(1).max(100),
|
|
77
|
+
runtime_comparisons: z.array(evidenceSchema).min(1).max(100),
|
|
78
|
+
mappings: z.array(mappingSchema).min(1).max(500),
|
|
79
|
+
offset: z.number().int().min(0).default(0),
|
|
80
|
+
limit: z.number().int().min(1).max(100).default(100),
|
|
81
|
+
unknown_registry_approved: z.literal(true).optional(),
|
|
82
|
+
})
|
|
83
|
+
.strict();
|
|
84
|
+
const correlationItemSchema = z.object({
|
|
85
|
+
correlation_id: z.string().regex(/^cor_[a-f0-9]{64}$/u),
|
|
86
|
+
static: z.object({
|
|
87
|
+
comparison_evidence_id: evidenceIdSchema,
|
|
88
|
+
selector: staticSelectorSchema,
|
|
89
|
+
status: comparisonStatusSchema,
|
|
90
|
+
conclusion_kind: z.enum([
|
|
91
|
+
"derived_relationship",
|
|
92
|
+
"contradiction",
|
|
93
|
+
"unresolved_branch",
|
|
94
|
+
]),
|
|
95
|
+
}),
|
|
96
|
+
runtime: z.object({
|
|
97
|
+
comparison_evidence_id: evidenceIdSchema,
|
|
98
|
+
dimension: runtimeDimensionSchema,
|
|
99
|
+
status: comparisonStatusSchema,
|
|
100
|
+
}),
|
|
101
|
+
side_alignment: z.enum(["left_to_left", "left_to_right"]),
|
|
102
|
+
hypothesis: z.object({
|
|
103
|
+
statement: z.string().min(1).max(500),
|
|
104
|
+
expected_pattern: expectedPatternSchema,
|
|
105
|
+
}),
|
|
106
|
+
observed_pattern: observedPatternSchema,
|
|
107
|
+
classification: z.enum(["hypothesis", "contradiction", "unresolved_branch"]),
|
|
108
|
+
evidence_links: z.array(evidenceIdSchema).min(2).max(20_100),
|
|
109
|
+
limitations: z.array(z.string()),
|
|
110
|
+
});
|
|
111
|
+
/** Deterministic, paginated static/runtime correlation payload. */
|
|
112
|
+
export const staticRuntimeCorrelationResultSchema = z.object({
|
|
113
|
+
status: z.enum(["correlated", "contradicted", "unknown", "truncated"]),
|
|
114
|
+
summary: z.object({
|
|
115
|
+
hypotheses: z.number().int().min(0),
|
|
116
|
+
contradictions: z.number().int().min(0),
|
|
117
|
+
unresolved: z.number().int().min(0),
|
|
118
|
+
}),
|
|
119
|
+
correlations: z.object({
|
|
120
|
+
items: z.array(correlationItemSchema).max(100),
|
|
121
|
+
offset: z.number().int().min(0),
|
|
122
|
+
limit: z.number().int().min(1).max(100),
|
|
123
|
+
total: z.number().int().min(0),
|
|
124
|
+
next_offset: z.number().int().min(0).nullable(),
|
|
125
|
+
}),
|
|
126
|
+
evidence_links: z.array(evidenceIdSchema).min(2).max(20_100),
|
|
127
|
+
limitations: z.array(z.string()),
|
|
128
|
+
});
|
|
129
|
+
const EXPECTED = {
|
|
130
|
+
compare_artifacts: [
|
|
131
|
+
"rea.artifact-comparison/v1",
|
|
132
|
+
"rea-artifact-comparison",
|
|
133
|
+
"REA artifact comparison",
|
|
134
|
+
],
|
|
135
|
+
compare_functions: [
|
|
136
|
+
"rea.function-comparison/v1",
|
|
137
|
+
"rea-function-comparison",
|
|
138
|
+
"REA function comparison",
|
|
139
|
+
],
|
|
140
|
+
compare_process_captures: [
|
|
141
|
+
"rea.process-comparison/v1",
|
|
142
|
+
"rea-process",
|
|
143
|
+
"REA deterministic process harness",
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
/** Correlate only caller-declared static/runtime hypotheses. */
|
|
147
|
+
export const correlateStaticAndRuntime = (input) => {
|
|
148
|
+
const parsed = staticRuntimeCorrelationInputSchema.parse(input);
|
|
149
|
+
const staticEvidence = parsed.static_comparisons.map(parseStatic);
|
|
150
|
+
const runtimeEvidence = parsed.runtime_comparisons.map(parseRuntime);
|
|
151
|
+
assertUnique(staticEvidence, "static comparison Evidence");
|
|
152
|
+
assertUnique(runtimeEvidence, "runtime comparison Evidence");
|
|
153
|
+
const staticById = byId(staticEvidence);
|
|
154
|
+
const runtimeById = byId(runtimeEvidence);
|
|
155
|
+
const correlations = parsed.mappings
|
|
156
|
+
.map((mapping) => correlate(mapping, staticById, runtimeById))
|
|
157
|
+
.sort((left, right) => left.correlation_id.localeCompare(right.correlation_id, "en"));
|
|
158
|
+
if (new Set(correlations.map(({ correlation_id: id }) => id)).size !==
|
|
159
|
+
correlations.length)
|
|
160
|
+
throw new TypeError("Static/runtime correlation rejects duplicate mappings");
|
|
161
|
+
const page = correlations.slice(parsed.offset, parsed.offset + parsed.limit);
|
|
162
|
+
const links = uniqueSorted(correlations.flatMap(({ evidence_links: evidenceLinks }) => evidenceLinks));
|
|
163
|
+
if (links.length > 20_100)
|
|
164
|
+
throw new TypeError("Static/runtime Evidence closure exceeds limit");
|
|
165
|
+
const unresolved = correlations.filter(({ classification }) => classification === "unresolved_branch");
|
|
166
|
+
const contradictions = correlations.filter(({ classification }) => classification === "contradiction");
|
|
167
|
+
return staticRuntimeCorrelationResultSchema.parse({
|
|
168
|
+
status: correlations.some(({ observed_pattern: pattern }) => pattern === "truncated")
|
|
169
|
+
? "truncated"
|
|
170
|
+
: unresolved.length > 0
|
|
171
|
+
? "unknown"
|
|
172
|
+
: contradictions.length > 0
|
|
173
|
+
? "contradicted"
|
|
174
|
+
: "correlated",
|
|
175
|
+
summary: {
|
|
176
|
+
hypotheses: correlations.length - contradictions.length - unresolved.length,
|
|
177
|
+
contradictions: contradictions.length,
|
|
178
|
+
unresolved: unresolved.length,
|
|
179
|
+
},
|
|
180
|
+
correlations: {
|
|
181
|
+
items: page,
|
|
182
|
+
offset: parsed.offset,
|
|
183
|
+
limit: parsed.limit,
|
|
184
|
+
total: correlations.length,
|
|
185
|
+
next_offset: parsed.offset + page.length < correlations.length
|
|
186
|
+
? parsed.offset + page.length
|
|
187
|
+
: null,
|
|
188
|
+
},
|
|
189
|
+
evidence_links: links,
|
|
190
|
+
limitations: [
|
|
191
|
+
"Mappings are caller-declared hypotheses; consistency does not establish causality.",
|
|
192
|
+
"Only explicit mappings were evaluated; similar unmapped observations were not correlated.",
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const correlate = (mapping, staticById, runtimeById) => {
|
|
197
|
+
const staticEvidence = required(staticById, mapping.static.comparison_evidence_id, "static comparison");
|
|
198
|
+
const runtimeEvidence = required(runtimeById, mapping.runtime.comparison_evidence_id, "runtime comparison");
|
|
199
|
+
const selectedStatic = selectStatic(staticEvidence, mapping.static.selector);
|
|
200
|
+
const process = processCaptureComparisonSchema.parse(runtimeEvidence.normalized_result);
|
|
201
|
+
const runtimeStatus = process[mapping.runtime.dimension];
|
|
202
|
+
const pattern = observedPattern(selectedStatic.status, runtimeStatus);
|
|
203
|
+
const classification = pattern === "unknown" || pattern === "truncated"
|
|
204
|
+
? "unresolved_branch"
|
|
205
|
+
: pattern === mapping.hypothesis.expected_pattern
|
|
206
|
+
? "hypothesis"
|
|
207
|
+
: "contradiction";
|
|
208
|
+
const projection = {
|
|
209
|
+
static: mapping.static,
|
|
210
|
+
runtime: mapping.runtime,
|
|
211
|
+
side_alignment: mapping.side_alignment,
|
|
212
|
+
hypothesis: mapping.hypothesis,
|
|
213
|
+
};
|
|
214
|
+
return correlationItemSchema.parse({
|
|
215
|
+
correlation_id: `cor_${sha256(canonicalJson(projection))}`,
|
|
216
|
+
static: { ...mapping.static, ...selectedStatic },
|
|
217
|
+
runtime: { ...mapping.runtime, status: runtimeStatus },
|
|
218
|
+
side_alignment: mapping.side_alignment,
|
|
219
|
+
hypothesis: mapping.hypothesis,
|
|
220
|
+
observed_pattern: pattern,
|
|
221
|
+
classification,
|
|
222
|
+
evidence_links: uniqueSorted([
|
|
223
|
+
staticEvidence.evidence_id,
|
|
224
|
+
...staticEvidence.evidence_links,
|
|
225
|
+
runtimeEvidence.evidence_id,
|
|
226
|
+
...runtimeEvidence.evidence_links,
|
|
227
|
+
]),
|
|
228
|
+
limitations: uniqueSorted([
|
|
229
|
+
...staticEvidence.limitations,
|
|
230
|
+
...runtimeEvidence.limitations,
|
|
231
|
+
...process.limitations,
|
|
232
|
+
"This correlation tests an explicit hypothesis and does not establish causality.",
|
|
233
|
+
"Side alignment is caller-declared and was not independently validated against source subjects or environments.",
|
|
234
|
+
]),
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
const selectStatic = (evidence, selector) => {
|
|
238
|
+
if (selector.kind === "function") {
|
|
239
|
+
if (evidence.operation !== "compare_functions")
|
|
240
|
+
throw new TypeError("Function selector requires function comparison Evidence");
|
|
241
|
+
const result = functionComparisonResultSchema.parse(evidence.normalized_result);
|
|
242
|
+
const matches = result.dimensions.filter(({ dimension }) => dimension === selector.dimension);
|
|
243
|
+
if (matches.length !== 1)
|
|
244
|
+
throw new TypeError("Function selector must resolve exactly once");
|
|
245
|
+
return {
|
|
246
|
+
status: matches[0].status,
|
|
247
|
+
conclusion_kind: matches[0].conclusion_kind,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (evidence.operation !== "compare_artifacts")
|
|
251
|
+
throw new TypeError("Artifact selector requires artifact comparison Evidence");
|
|
252
|
+
const result = artifactComparisonResultSchema.parse(evidence.normalized_result);
|
|
253
|
+
if (result.changes.offset !== 0 ||
|
|
254
|
+
result.changes.next_offset !== null ||
|
|
255
|
+
result.changes.items.length !== result.changes.total)
|
|
256
|
+
throw new TypeError("Static/runtime correlation requires complete artifact comparison pagination");
|
|
257
|
+
const matches = result.changes.items.filter(({ logical_path: path }) => path === selector.logical_path);
|
|
258
|
+
if (matches.length !== 1)
|
|
259
|
+
throw new TypeError("Artifact selector must resolve exactly one reported delta");
|
|
260
|
+
const status = matches[0].classification;
|
|
261
|
+
return {
|
|
262
|
+
status,
|
|
263
|
+
conclusion_kind: status === "unknown" ? "unresolved_branch" : "derived_relationship",
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
const parseStatic = (input) => {
|
|
267
|
+
const evidence = parseEvidence(input);
|
|
268
|
+
if (evidence.operation !== "compare_artifacts" &&
|
|
269
|
+
evidence.operation !== "compare_functions")
|
|
270
|
+
throw new TypeError("Static/runtime correlation requires artifact or function comparison Evidence");
|
|
271
|
+
assertComparisonIdentity(evidence);
|
|
272
|
+
if (evidence.operation === "compare_artifacts") {
|
|
273
|
+
const result = artifactComparisonResultSchema.parse(evidence.normalized_result);
|
|
274
|
+
assertCompleteArtifactResult(result);
|
|
275
|
+
assertNestedLinks(evidence, result.changes.items.flatMap(({ evidence_links: links }) => links));
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const result = functionComparisonResultSchema.parse(evidence.normalized_result);
|
|
279
|
+
assertNestedLinks(evidence, result.dimensions.flatMap(({ evidence_links: links }) => links));
|
|
280
|
+
}
|
|
281
|
+
return evidence;
|
|
282
|
+
};
|
|
283
|
+
const assertNestedLinks = (evidence, nestedLinks) => {
|
|
284
|
+
const closure = new Set(evidence.evidence_links);
|
|
285
|
+
if (nestedLinks.some((evidenceId) => !closure.has(evidenceId)))
|
|
286
|
+
throw new TypeError("Comparison finding cites Evidence outside its top-level closure");
|
|
287
|
+
};
|
|
288
|
+
const parseRuntime = (input) => {
|
|
289
|
+
const evidence = parseEvidence(input);
|
|
290
|
+
if (evidence.operation !== "compare_process_captures")
|
|
291
|
+
throw new TypeError("Static/runtime correlation requires process comparison Evidence");
|
|
292
|
+
assertComparisonIdentity(evidence);
|
|
293
|
+
processCaptureComparisonSchema.parse(evidence.normalized_result);
|
|
294
|
+
return evidence;
|
|
295
|
+
};
|
|
296
|
+
const assertComparisonIdentity = (evidence) => {
|
|
297
|
+
const expected = EXPECTED[evidence.operation];
|
|
298
|
+
if (expected === undefined ||
|
|
299
|
+
evidence.predicate_type !== expected[0] ||
|
|
300
|
+
evidence.provider.id !== expected[1] ||
|
|
301
|
+
evidence.provider.name !== expected[2] ||
|
|
302
|
+
evidence.provider.version !== "1" ||
|
|
303
|
+
evidence.confidence !== "derived" ||
|
|
304
|
+
evidence.authority !== "analyst-inference" ||
|
|
305
|
+
evidence.subject !== null ||
|
|
306
|
+
evidence.evidence_links.length < 2 ||
|
|
307
|
+
new Set(evidence.evidence_links).size !== evidence.evidence_links.length)
|
|
308
|
+
throw new TypeError("Comparison Evidence operation, predicate, provider, or citations disagree");
|
|
309
|
+
const parameterLinks = comparisonParameterLinks(evidence);
|
|
310
|
+
if (!sameSet(parameterLinks, evidence.evidence_links))
|
|
311
|
+
throw new TypeError("Comparison Evidence closure disagrees with its source parameters");
|
|
312
|
+
};
|
|
313
|
+
const comparisonParameterLinks = (evidence) => {
|
|
314
|
+
if (evidence.operation === "compare_process_captures") {
|
|
315
|
+
const parameters = z
|
|
316
|
+
.object({
|
|
317
|
+
left_evidence_id: evidenceIdSchema,
|
|
318
|
+
right_evidence_id: evidenceIdSchema,
|
|
319
|
+
})
|
|
320
|
+
.passthrough()
|
|
321
|
+
.parse(evidence.parameters);
|
|
322
|
+
return [parameters.left_evidence_id, parameters.right_evidence_id];
|
|
323
|
+
}
|
|
324
|
+
const parameters = z
|
|
325
|
+
.object({
|
|
326
|
+
left_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
|
|
327
|
+
right_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
|
|
328
|
+
})
|
|
329
|
+
.passthrough()
|
|
330
|
+
.parse(evidence.parameters);
|
|
331
|
+
return [...parameters.left_evidence_ids, ...parameters.right_evidence_ids];
|
|
332
|
+
};
|
|
333
|
+
const sameSet = (left, right) => left.length === right.length &&
|
|
334
|
+
new Set(left).size === left.length &&
|
|
335
|
+
left.every((item) => right.includes(item));
|
|
336
|
+
const assertCompleteArtifactResult = (result) => {
|
|
337
|
+
if (result.changes.offset !== 0 ||
|
|
338
|
+
result.changes.next_offset !== null ||
|
|
339
|
+
result.changes.items.length !== result.changes.total)
|
|
340
|
+
throw new TypeError("Static/runtime correlation requires complete artifact comparison pagination");
|
|
341
|
+
};
|
|
342
|
+
const observedPattern = (staticStatus, runtimeStatus) => {
|
|
343
|
+
if (staticStatus === "truncated" || runtimeStatus === "truncated")
|
|
344
|
+
return "truncated";
|
|
345
|
+
if (staticStatus === "unknown" || runtimeStatus === "unknown")
|
|
346
|
+
return "unknown";
|
|
347
|
+
const staticChanged = staticStatus !== "unchanged";
|
|
348
|
+
const runtimeChanged = runtimeStatus !== "unchanged";
|
|
349
|
+
if (staticChanged && runtimeChanged)
|
|
350
|
+
return "cochanged";
|
|
351
|
+
if (staticChanged)
|
|
352
|
+
return "static_only";
|
|
353
|
+
if (runtimeChanged)
|
|
354
|
+
return "runtime_only";
|
|
355
|
+
return "both_unchanged";
|
|
356
|
+
};
|
|
357
|
+
const required = (values, id, label) => {
|
|
358
|
+
const value = values.get(id);
|
|
359
|
+
if (value === undefined)
|
|
360
|
+
throw new TypeError(`Mapping references absent ${label} Evidence`);
|
|
361
|
+
return value;
|
|
362
|
+
};
|
|
363
|
+
const byId = (evidence) => new Map(evidence.map((item) => [item.evidence_id, item]));
|
|
364
|
+
const assertUnique = (evidence, label) => {
|
|
365
|
+
if (new Set(evidence.map(({ evidence_id: id }) => id)).size !== evidence.length)
|
|
366
|
+
throw new TypeError(`Static/runtime correlation rejects duplicate ${label}`);
|
|
367
|
+
};
|
|
368
|
+
const uniqueSorted = (values) => [...new Set(values)].sort((left, right) => left.localeCompare(right, "en"));
|
|
369
|
+
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
370
|
+
const canonicalJson = (value) => {
|
|
371
|
+
const serialized = canonicalize(value);
|
|
372
|
+
if (serialized === undefined)
|
|
373
|
+
throw new TypeError("RFC 8785 canonicalization rejected correlation mapping");
|
|
374
|
+
return serialized;
|
|
375
|
+
};
|