rea-agents 0.2.1 → 0.4.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 +124 -20
- package/bridge/hopper_bridge.py +141 -13
- 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 +164 -14
- package/dist/application/CompositeProvider.js +73 -0
- package/dist/application/DirectAnalysis.js +27 -3
- package/dist/application/Doctor.js +35 -6
- package/dist/application/EnhancedTools.js +8 -7
- package/dist/application/EvidenceBundleCommands.js +29 -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 +84 -0
- package/dist/application/ProcessSampling.js +284 -0
- package/dist/application/RealHopperAssertions.js +105 -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 +153 -6
- 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 +154 -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 +192 -4
- 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 +14 -7
- 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/residualUnknown.js +239 -0
- package/dist/domain/staticRuntimeCorrelation.js +375 -0
- package/dist/hopper/BridgeLauncher.js +40 -3
- package/dist/hopper/HopperClient.js +14 -5
- package/dist/hopper/HopperProvider.js +57 -22
- 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 +19 -2
- package/scripts/rea.mjs +5 -1
- package/skills/rea-analysis/SKILL.md +77 -2
- /package/dist/{application/HopperToolPort.js → domain/referenceSourcePolicy.js} +0 -0
|
@@ -1,21 +1,90 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { evidenceSchema } from "../domain/evidence.js";
|
|
3
|
-
import { processCaptureSchema } from "../domain/processCapture.js";
|
|
3
|
+
import { processCaptureComparisonSchema, processCaptureSchema, } from "../domain/processCapture.js";
|
|
4
4
|
import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
|
|
5
|
-
|
|
5
|
+
import { residualUnknownSchema } from "../domain/residualUnknown.js";
|
|
6
|
+
import { functionDossierSchema } from "../domain/hopperValues.js";
|
|
7
|
+
import { demangleSwiftSchema, inspectMachoSchema, inspectPlistSchema, inspectSignatureSchema, listArchitecturesSchema, } from "../domain/nativeInspection.js";
|
|
8
|
+
import { artifactExtractionResultSchema, artifactInventoryResultSchema, } from "../domain/artifactGraph.js";
|
|
9
|
+
import { artifactComparisonResultSchema } from "../domain/artifactComparison.js";
|
|
10
|
+
import { functionComparisonResultSchema } from "../domain/functionComparison.js";
|
|
11
|
+
import { bundleComparisonResultSchema } from "../domain/bundleComparison.js";
|
|
12
|
+
import { changedBehaviorResultSchema } from "../domain/changedBehavior.js";
|
|
13
|
+
import { callPathResultSchema } from "../domain/callPath.js";
|
|
14
|
+
import { staticRuntimeCorrelationResultSchema } from "../domain/staticRuntimeCorrelation.js";
|
|
15
|
+
import { reconstructionVerificationResultSchema } from "../domain/reconstructionVerification.js";
|
|
16
|
+
const resultOf = (schema) => evidenceSchema
|
|
17
|
+
.omit({ normalized_result: true })
|
|
18
|
+
.extend({ normalized_result: schema });
|
|
6
19
|
const lifecycleResultOf = (schema) => z.object({ result: schema });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
/** Resolve a required named output schema or reject contract drift. */
|
|
21
|
+
export const requireOutputSchema = (schemas, name) => {
|
|
22
|
+
const schema = schemas[name];
|
|
23
|
+
if (schema === undefined)
|
|
24
|
+
throw new Error(`Missing output schema for ${name}`);
|
|
25
|
+
return schema;
|
|
26
|
+
};
|
|
27
|
+
const targetFormatSchema = z.enum([
|
|
28
|
+
"hopper",
|
|
29
|
+
"mach-o",
|
|
30
|
+
"elf",
|
|
31
|
+
"pe",
|
|
32
|
+
"zip",
|
|
33
|
+
"ipa",
|
|
34
|
+
"apk",
|
|
35
|
+
"asar",
|
|
36
|
+
"dmg",
|
|
37
|
+
"pkg",
|
|
38
|
+
"plist",
|
|
39
|
+
"javascript",
|
|
40
|
+
"source-map",
|
|
41
|
+
]);
|
|
42
|
+
const targetKindSchema = z.enum([
|
|
43
|
+
"executable",
|
|
44
|
+
"database",
|
|
45
|
+
"archive",
|
|
46
|
+
"artifact",
|
|
14
47
|
]);
|
|
48
|
+
const providerCapability = z.object({
|
|
49
|
+
operation: z.string(),
|
|
50
|
+
available: z.boolean(),
|
|
51
|
+
reason: z.string().nullable(),
|
|
52
|
+
input_contract_version: z.number().int().min(1),
|
|
53
|
+
output_contract_version: z.number().int().min(1),
|
|
54
|
+
pagination: z.enum(["none", "offset", "cursor"]),
|
|
55
|
+
exhaustive: z.boolean(),
|
|
56
|
+
effects: z.object({
|
|
57
|
+
mutates_artifact: z.boolean(),
|
|
58
|
+
launches_process: z.boolean(),
|
|
59
|
+
may_show_ui: z.boolean(),
|
|
60
|
+
may_access_network: z.boolean(),
|
|
61
|
+
may_write_filesystem: z.boolean(),
|
|
62
|
+
changes_permissions: z.boolean(),
|
|
63
|
+
requires_root: z.boolean(),
|
|
64
|
+
}),
|
|
65
|
+
limits: z.object({
|
|
66
|
+
max_results: z.number().int().min(0).nullable(),
|
|
67
|
+
max_payload_bytes: z.number().int().min(0).nullable(),
|
|
68
|
+
timeout_ms: z.number().int().min(0).nullable(),
|
|
69
|
+
}),
|
|
70
|
+
limitations: z.array(z.string()),
|
|
71
|
+
});
|
|
72
|
+
const sessionProvider = z.object({
|
|
73
|
+
provider: z.object({
|
|
74
|
+
id: z.string(),
|
|
75
|
+
name: z.string(),
|
|
76
|
+
version: z.string().nullable(),
|
|
77
|
+
}),
|
|
78
|
+
capabilities: z.array(providerCapability),
|
|
79
|
+
});
|
|
15
80
|
const nullableText = z.string().nullable();
|
|
16
81
|
const addressList = z.array(z.string());
|
|
17
82
|
const addressedEntry = z.object({ address: z.string(), name: z.string() });
|
|
18
83
|
const procedureIdentity = z.object({ address: z.string(), name: z.string() });
|
|
84
|
+
const localVariable = z.object({
|
|
85
|
+
description: z.string(),
|
|
86
|
+
provenance: z.literal("hopper-public-python-api"),
|
|
87
|
+
});
|
|
19
88
|
const containingProcedureResolution = z.discriminatedUnion("found", [
|
|
20
89
|
z.object({
|
|
21
90
|
query_address: z.string(),
|
|
@@ -48,22 +117,31 @@ const pageOutput = z.object({
|
|
|
48
117
|
next_offset: z.number().int().min(0).nullable(),
|
|
49
118
|
has_more: z.boolean(),
|
|
50
119
|
});
|
|
51
|
-
const
|
|
120
|
+
const searchPageOutput = pageOutput.extend({
|
|
121
|
+
items: z.array(z.object({
|
|
122
|
+
address: z.string(),
|
|
123
|
+
value: z.string(),
|
|
124
|
+
value_truncated: z.boolean(),
|
|
125
|
+
})),
|
|
126
|
+
});
|
|
127
|
+
const memoryRegionOutput = z.object({
|
|
52
128
|
name: z.string(),
|
|
53
129
|
start: z.string(),
|
|
54
130
|
end: z.string(),
|
|
131
|
+
readable: z.boolean().nullable(),
|
|
55
132
|
writable: z.boolean().nullable(),
|
|
56
133
|
executable: z.boolean().nullable(),
|
|
57
134
|
permissions: unavailable,
|
|
58
|
-
|
|
59
|
-
})
|
|
135
|
+
provenance: z.literal("hopper-public-python-api"),
|
|
136
|
+
});
|
|
137
|
+
const segmentOutput = resultOf(z.array(memoryRegionOutput.extend({ sections: z.array(memoryRegionOutput) })));
|
|
60
138
|
const procedureInfoOutput = resultOf(z.object({
|
|
61
139
|
name: z.string(),
|
|
62
140
|
entrypoint: z.string(),
|
|
63
141
|
basicblock_count: z.number().int().min(0),
|
|
64
142
|
length: z.number().min(0),
|
|
65
143
|
signature: nullableText,
|
|
66
|
-
locals: z.array(
|
|
144
|
+
locals: z.array(localVariable),
|
|
67
145
|
}));
|
|
68
146
|
const symbolDiscoveryOutput = (property) => resultOf(z.object({
|
|
69
147
|
count: z.number().int().min(0),
|
|
@@ -73,54 +151,7 @@ const graphNode = z.union([
|
|
|
73
151
|
z.object({ address: z.string(), calls: z.array(z.string()) }),
|
|
74
152
|
z.object({ address: z.string(), error: z.string() }),
|
|
75
153
|
]);
|
|
76
|
-
const
|
|
77
|
-
source_address: z.string(),
|
|
78
|
-
target_address: z.string(),
|
|
79
|
-
source_procedure: procedureIdentity.nullable(),
|
|
80
|
-
target_procedure: procedureIdentity.nullable(),
|
|
81
|
-
kind: unavailable,
|
|
82
|
-
});
|
|
83
|
-
const referencedValue = z.object({
|
|
84
|
-
address: z.string(),
|
|
85
|
-
value: z.string(),
|
|
86
|
-
source_address: z.string(),
|
|
87
|
-
});
|
|
88
|
-
const functionDossierOutput = resultOf(z.object({
|
|
89
|
-
procedure: z.object({
|
|
90
|
-
address: z.string(),
|
|
91
|
-
name: z.string(),
|
|
92
|
-
signature: nullableText,
|
|
93
|
-
locals: z.array(z.json()),
|
|
94
|
-
}),
|
|
95
|
-
pseudocode: z.object({
|
|
96
|
-
text: z.string(),
|
|
97
|
-
total_chars: z.number().int().min(0),
|
|
98
|
-
returned_chars: z.number().int().min(0),
|
|
99
|
-
truncated: z.boolean(),
|
|
100
|
-
next_offset: z.number().int().min(0).nullable(),
|
|
101
|
-
}),
|
|
102
|
-
assembly: bounded(z.string()),
|
|
103
|
-
comments: bounded(z.object({
|
|
104
|
-
address: z.string(),
|
|
105
|
-
kind: z.enum(["comment", "inline"]),
|
|
106
|
-
text: z.string(),
|
|
107
|
-
})),
|
|
108
|
-
callers: bounded(procedureIdentity),
|
|
109
|
-
callees: bounded(procedureIdentity),
|
|
110
|
-
incoming_references: bounded(referenceEdge),
|
|
111
|
-
outgoing_references: bounded(referenceEdge),
|
|
112
|
-
referenced_strings: bounded(referencedValue),
|
|
113
|
-
referenced_names: bounded(referencedValue),
|
|
114
|
-
basic_blocks: bounded(z.object({
|
|
115
|
-
start: z.string(),
|
|
116
|
-
end: z.string(),
|
|
117
|
-
successors: z.array(z.string()),
|
|
118
|
-
})),
|
|
119
|
-
instruction_scan: z.object({
|
|
120
|
-
scanned: z.number().int().min(0),
|
|
121
|
-
truncated: z.boolean(),
|
|
122
|
-
}),
|
|
123
|
-
}));
|
|
154
|
+
const functionDossierOutput = resultOf(functionDossierSchema);
|
|
124
155
|
/** Exact structured-content schemas for the direct Hopper operations. */
|
|
125
156
|
export const officialOutputSchemas = {
|
|
126
157
|
address_name: resultOf(nullableText),
|
|
@@ -158,8 +189,8 @@ export const officialOutputSchemas = {
|
|
|
158
189
|
})),
|
|
159
190
|
procedure_pseudo_code: resultOf(nullableText),
|
|
160
191
|
resolve_containing_procedure: resultOf(containingProcedureResolution),
|
|
161
|
-
search_procedures: resultOf(
|
|
162
|
-
search_strings: resultOf(
|
|
192
|
+
search_procedures: resultOf(searchPageOutput),
|
|
193
|
+
search_strings: resultOf(searchPageOutput),
|
|
163
194
|
set_address_name: resultOf(z.boolean()),
|
|
164
195
|
set_addresses_names: resultOf(z.record(z.string(), z.boolean())),
|
|
165
196
|
set_bookmark: resultOf(z.boolean()),
|
|
@@ -223,45 +254,86 @@ export const enhancedOutputSchemas = {
|
|
|
223
254
|
residual_unknowns: z.array(z.string()),
|
|
224
255
|
})),
|
|
225
256
|
};
|
|
257
|
+
/** Exact Evidence v2 schemas for provider-neutral native inspection. */
|
|
258
|
+
export const nativeOutputSchemas = {
|
|
259
|
+
inspect_macho: resultOf(inspectMachoSchema),
|
|
260
|
+
inspect_signature: resultOf(inspectSignatureSchema),
|
|
261
|
+
inspect_plist: resultOf(inspectPlistSchema),
|
|
262
|
+
list_architectures: resultOf(listArchitecturesSchema),
|
|
263
|
+
demangle_swift: resultOf(demangleSwiftSchema),
|
|
264
|
+
};
|
|
265
|
+
/** Exact Evidence v2 schemas for provider-neutral artifact graph operations. */
|
|
266
|
+
export const artifactOutputSchemas = {
|
|
267
|
+
inventory_artifact: resultOf(artifactInventoryResultSchema),
|
|
268
|
+
extract_artifact: resultOf(artifactExtractionResultSchema),
|
|
269
|
+
};
|
|
226
270
|
/** Exact structured-content schemas for target lifecycle operations. */
|
|
227
271
|
export const sessionOutputSchemas = {
|
|
228
272
|
open_binary: lifecycleResultOf(z.object({
|
|
229
273
|
path: z.string(),
|
|
230
|
-
format:
|
|
231
|
-
kind:
|
|
274
|
+
format: targetFormatSchema,
|
|
275
|
+
kind: targetKindSchema,
|
|
232
276
|
loaderArgs: z.array(z.string()),
|
|
233
277
|
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
234
278
|
architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
|
|
235
279
|
})),
|
|
236
280
|
close_binary: lifecycleResultOf(z.null()),
|
|
237
281
|
binary_session: lifecycleResultOf(z.union([
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
sessionProvider.extend({ open: z.literal(false) }),
|
|
283
|
+
sessionProvider.extend({
|
|
240
284
|
open: z.literal(true),
|
|
241
285
|
path: z.string(),
|
|
242
|
-
format:
|
|
243
|
-
kind:
|
|
286
|
+
format: targetFormatSchema,
|
|
287
|
+
kind: targetKindSchema,
|
|
244
288
|
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
245
289
|
architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
|
|
246
290
|
}),
|
|
247
291
|
])),
|
|
248
|
-
export_evidence_bundle: lifecycleResultOf(
|
|
292
|
+
export_evidence_bundle: lifecycleResultOf(z.union([
|
|
293
|
+
evidenceBundleSchema,
|
|
294
|
+
z.object({
|
|
295
|
+
path: z.string(),
|
|
296
|
+
bytes: z.number().int().min(0),
|
|
297
|
+
records: z.number().int().min(0),
|
|
298
|
+
}),
|
|
299
|
+
])),
|
|
249
300
|
import_evidence_bundle: lifecycleResultOf(z.object({
|
|
250
301
|
imported: z.number().int().min(0),
|
|
251
302
|
total: z.number().int().min(0),
|
|
252
303
|
})),
|
|
253
304
|
capture_process_scenario: lifecycleResultOf(evidenceSchema
|
|
254
|
-
.omit({
|
|
255
|
-
.extend({
|
|
256
|
-
compare_process_captures: lifecycleResultOf(evidenceSchema.omit({
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
305
|
+
.omit({ normalized_result: true })
|
|
306
|
+
.extend({ normalized_result: processCaptureSchema })),
|
|
307
|
+
compare_process_captures: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
308
|
+
normalized_result: processCaptureComparisonSchema,
|
|
309
|
+
})),
|
|
310
|
+
compare_artifacts: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
311
|
+
normalized_result: artifactComparisonResultSchema,
|
|
312
|
+
})),
|
|
313
|
+
compare_functions: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
314
|
+
normalized_result: functionComparisonResultSchema,
|
|
315
|
+
})),
|
|
316
|
+
compare_bundles: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
317
|
+
normalized_result: bundleComparisonResultSchema,
|
|
318
|
+
})),
|
|
319
|
+
find_changed_behavior: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
320
|
+
normalized_result: changedBehaviorResultSchema,
|
|
321
|
+
})),
|
|
322
|
+
build_call_path: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
323
|
+
normalized_result: callPathResultSchema,
|
|
324
|
+
})),
|
|
325
|
+
correlate_static_and_runtime: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
326
|
+
normalized_result: staticRuntimeCorrelationResultSchema,
|
|
327
|
+
})),
|
|
328
|
+
verify_reconstruction: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
|
|
329
|
+
normalized_result: reconstructionVerificationResultSchema,
|
|
330
|
+
})),
|
|
331
|
+
list_unknowns: lifecycleResultOf(z.array(residualUnknownSchema)),
|
|
332
|
+
record_unknown: lifecycleResultOf(residualUnknownSchema),
|
|
333
|
+
update_unknown: lifecycleResultOf(residualUnknownSchema),
|
|
334
|
+
verify_unknown_resolution: lifecycleResultOf(z.object({
|
|
335
|
+
valid: z.boolean(),
|
|
336
|
+
truthVerified: z.boolean(),
|
|
337
|
+
unknown: residualUnknownSchema,
|
|
266
338
|
})),
|
|
267
339
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Exact example inputs for residual-unknown lifecycle contracts. */
|
|
2
|
+
export const UNKNOWN_CONTRACT_EXAMPLES = {
|
|
3
|
+
list_unknowns: {},
|
|
4
|
+
record_unknown: {
|
|
5
|
+
approved: true,
|
|
6
|
+
question: "Does this branch require an unavailable external service?",
|
|
7
|
+
severity: "medium",
|
|
8
|
+
domain: "protocol",
|
|
9
|
+
required_authority: "controlled-replay",
|
|
10
|
+
required_confidence: "observed",
|
|
11
|
+
required_environment: null,
|
|
12
|
+
recommended_probes: [
|
|
13
|
+
{ operation: "capture_process_scenario", rationale: "Replay branch." },
|
|
14
|
+
],
|
|
15
|
+
relationships: [],
|
|
16
|
+
},
|
|
17
|
+
update_unknown: {
|
|
18
|
+
approved: true,
|
|
19
|
+
unknown_id: `unk_${"0".repeat(64)}`,
|
|
20
|
+
expected_revision: 1,
|
|
21
|
+
status: "investigating",
|
|
22
|
+
severity: "medium",
|
|
23
|
+
supporting_evidence_ids: [],
|
|
24
|
+
contradicting_evidence_ids: [],
|
|
25
|
+
required_authority: "controlled-replay",
|
|
26
|
+
required_confidence: "observed",
|
|
27
|
+
required_environment: null,
|
|
28
|
+
recommended_probes: [],
|
|
29
|
+
relationships: [],
|
|
30
|
+
resolution: null,
|
|
31
|
+
},
|
|
32
|
+
verify_unknown_resolution: { unknown_id: `unk_${"0".repeat(64)}` },
|
|
33
|
+
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {} from "./artifactGraph.js";
|
|
4
|
+
import { evidenceSchema } from "./evidence.js";
|
|
5
|
+
import { parseArtifactInventoryEvidence, } from "./artifactInventoryEvidence.js";
|
|
6
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
7
|
+
const comparisonStatusSchema = z.enum([
|
|
8
|
+
"unchanged",
|
|
9
|
+
"added",
|
|
10
|
+
"removed",
|
|
11
|
+
"changed",
|
|
12
|
+
"truncated",
|
|
13
|
+
"unknown",
|
|
14
|
+
]);
|
|
15
|
+
const changeKindSchema = z.enum(["added", "removed", "changed", "unknown"]);
|
|
16
|
+
const comparisonDimensionSchema = z.enum([
|
|
17
|
+
"content",
|
|
18
|
+
"kind",
|
|
19
|
+
"format",
|
|
20
|
+
"size",
|
|
21
|
+
"executable",
|
|
22
|
+
"relations",
|
|
23
|
+
"metadata",
|
|
24
|
+
"availability",
|
|
25
|
+
]);
|
|
26
|
+
/** Strict Evidence-backed input for deterministic artifact comparison. */
|
|
27
|
+
export const artifactComparisonInputSchema = z.object({
|
|
28
|
+
left: z.union([evidenceSchema, z.array(evidenceSchema).min(1).max(100)]),
|
|
29
|
+
right: z.union([evidenceSchema, z.array(evidenceSchema).min(1).max(100)]),
|
|
30
|
+
offset: z.number().int().min(0).default(0),
|
|
31
|
+
limit: z.number().int().min(1).max(500).default(100),
|
|
32
|
+
unknown_registry_approved: z
|
|
33
|
+
.literal(true)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Explicit approval to record incomplete or differing evidence"),
|
|
36
|
+
});
|
|
37
|
+
/** One path-classified artifact change with citations to both observations. */
|
|
38
|
+
const artifactChangeSchema = z.object({
|
|
39
|
+
classification: changeKindSchema,
|
|
40
|
+
logical_path: z.string().min(1).max(4_096),
|
|
41
|
+
dimensions: z.array(comparisonDimensionSchema).min(1).max(8),
|
|
42
|
+
left_occurrence_id: z
|
|
43
|
+
.string()
|
|
44
|
+
.regex(/^occ_[a-f0-9]{64}$/u)
|
|
45
|
+
.nullable(),
|
|
46
|
+
right_occurrence_id: z
|
|
47
|
+
.string()
|
|
48
|
+
.regex(/^occ_[a-f0-9]{64}$/u)
|
|
49
|
+
.nullable(),
|
|
50
|
+
left_artifact_id: z
|
|
51
|
+
.string()
|
|
52
|
+
.regex(/^art_[a-f0-9]{64}$/u)
|
|
53
|
+
.nullable(),
|
|
54
|
+
right_artifact_id: z
|
|
55
|
+
.string()
|
|
56
|
+
.regex(/^art_[a-f0-9]{64}$/u)
|
|
57
|
+
.nullable(),
|
|
58
|
+
evidence_links: z.array(evidenceIdSchema).min(2).max(200),
|
|
59
|
+
});
|
|
60
|
+
/** Bounded deterministic artifact comparison with an independently paged delta. */
|
|
61
|
+
export const artifactComparisonResultSchema = z.object({
|
|
62
|
+
status: comparisonStatusSchema,
|
|
63
|
+
left_manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
|
|
64
|
+
right_manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
|
|
65
|
+
summary: z.object({
|
|
66
|
+
unchanged: z.number().int().min(0),
|
|
67
|
+
added: z.number().int().min(0),
|
|
68
|
+
removed: z.number().int().min(0),
|
|
69
|
+
changed: z.number().int().min(0),
|
|
70
|
+
unknown: z.number().int().min(0),
|
|
71
|
+
}),
|
|
72
|
+
changes: z.object({
|
|
73
|
+
items: z.array(artifactChangeSchema).max(500),
|
|
74
|
+
offset: z.number().int().min(0),
|
|
75
|
+
limit: z.number().int().min(1).max(500),
|
|
76
|
+
total: z.number().int().min(0),
|
|
77
|
+
next_offset: z.number().int().min(0).nullable(),
|
|
78
|
+
}),
|
|
79
|
+
limitations: z.array(z.string()),
|
|
80
|
+
});
|
|
81
|
+
/** Compare complete inventory pages without treating missing evidence as equality. */
|
|
82
|
+
export const compareArtifacts = (leftEvidence, rightEvidence, offset, limit) => {
|
|
83
|
+
const left = parseArtifactInventoryEvidence(leftEvidence);
|
|
84
|
+
const right = parseArtifactInventoryEvidence(rightEvidence);
|
|
85
|
+
const leftComplete = left.inventory.complete;
|
|
86
|
+
const rightComplete = right.inventory.complete;
|
|
87
|
+
const leftCovered = leftComplete && left.inventory.limitations.length === 0;
|
|
88
|
+
const rightCovered = rightComplete && right.inventory.limitations.length === 0;
|
|
89
|
+
const limitations = [
|
|
90
|
+
...left.inventory.limitations.map((item) => `Left: ${item}`),
|
|
91
|
+
...right.inventory.limitations.map((item) => `Right: ${item}`),
|
|
92
|
+
...(leftComplete ? [] : ["Left artifact inventory is incomplete."]),
|
|
93
|
+
...(rightComplete ? [] : ["Right artifact inventory is incomplete."]),
|
|
94
|
+
];
|
|
95
|
+
const changes = compareOccurrences(left.inventory, right.inventory, [
|
|
96
|
+
...left.evidence.map(({ evidence_id: id }) => id),
|
|
97
|
+
...right.evidence.map(({ evidence_id: id }) => id),
|
|
98
|
+
], leftCovered && rightCovered);
|
|
99
|
+
const summary = summarize(left.inventory, right.inventory, changes, leftCovered && rightCovered);
|
|
100
|
+
const page = changes.slice(offset, offset + limit);
|
|
101
|
+
return artifactComparisonResultSchema.parse({
|
|
102
|
+
status: !leftComplete || !rightComplete
|
|
103
|
+
? "truncated"
|
|
104
|
+
: !leftCovered || !rightCovered
|
|
105
|
+
? "unknown"
|
|
106
|
+
: changes.some(({ classification }) => classification === "unknown")
|
|
107
|
+
? "unknown"
|
|
108
|
+
: changes.length === 0
|
|
109
|
+
? "unchanged"
|
|
110
|
+
: "changed",
|
|
111
|
+
left_manifest_id: left.inventory.manifest.manifest_id,
|
|
112
|
+
right_manifest_id: right.inventory.manifest.manifest_id,
|
|
113
|
+
summary,
|
|
114
|
+
changes: {
|
|
115
|
+
items: page,
|
|
116
|
+
offset,
|
|
117
|
+
limit,
|
|
118
|
+
total: changes.length,
|
|
119
|
+
next_offset: offset + page.length < changes.length ? offset + page.length : null,
|
|
120
|
+
},
|
|
121
|
+
limitations: [...new Set(limitations)].sort((a, b) => a.localeCompare(b)),
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const compareOccurrences = (left, right, evidenceLinks, complete) => {
|
|
125
|
+
const leftByPath = new Map(left.occurrences.map((item) => [item.logical_path, item]));
|
|
126
|
+
const rightByPath = new Map(right.occurrences.map((item) => [item.logical_path, item]));
|
|
127
|
+
const leftNodes = new Map(left.nodes.map((item) => [item.artifact_id, item]));
|
|
128
|
+
const rightNodes = new Map(right.nodes.map((item) => [item.artifact_id, item]));
|
|
129
|
+
const leftRelations = relationsByPath(left);
|
|
130
|
+
const rightRelations = relationsByPath(right);
|
|
131
|
+
const paths = [
|
|
132
|
+
...new Set([...leftByPath.keys(), ...rightByPath.keys()]),
|
|
133
|
+
].sort((a, b) => a.localeCompare(b, "en"));
|
|
134
|
+
const output = [];
|
|
135
|
+
for (const path of paths) {
|
|
136
|
+
const leftOccurrence = leftByPath.get(path);
|
|
137
|
+
const rightOccurrence = rightByPath.get(path);
|
|
138
|
+
const change = classifyPath({
|
|
139
|
+
path,
|
|
140
|
+
leftOccurrence,
|
|
141
|
+
rightOccurrence,
|
|
142
|
+
leftNode: nodeFor(leftOccurrence, leftNodes),
|
|
143
|
+
rightNode: nodeFor(rightOccurrence, rightNodes),
|
|
144
|
+
leftRelations: leftRelations.get(path) ?? [],
|
|
145
|
+
rightRelations: rightRelations.get(path) ?? [],
|
|
146
|
+
complete,
|
|
147
|
+
evidenceLinks,
|
|
148
|
+
});
|
|
149
|
+
if (change !== null)
|
|
150
|
+
output.push(change);
|
|
151
|
+
}
|
|
152
|
+
return output;
|
|
153
|
+
};
|
|
154
|
+
const classifyPath = (input) => {
|
|
155
|
+
const base = {
|
|
156
|
+
logical_path: input.path,
|
|
157
|
+
left_occurrence_id: input.leftOccurrence?.occurrence_id ?? null,
|
|
158
|
+
right_occurrence_id: input.rightOccurrence?.occurrence_id ?? null,
|
|
159
|
+
left_artifact_id: input.leftOccurrence?.artifact_id ?? null,
|
|
160
|
+
right_artifact_id: input.rightOccurrence?.artifact_id ?? null,
|
|
161
|
+
evidence_links: [...input.evidenceLinks],
|
|
162
|
+
};
|
|
163
|
+
if (input.leftOccurrence === undefined)
|
|
164
|
+
return {
|
|
165
|
+
...base,
|
|
166
|
+
classification: input.complete ? "added" : "unknown",
|
|
167
|
+
dimensions: [input.complete ? "content" : "availability"],
|
|
168
|
+
};
|
|
169
|
+
if (input.rightOccurrence === undefined)
|
|
170
|
+
return {
|
|
171
|
+
...base,
|
|
172
|
+
classification: input.complete ? "removed" : "unknown",
|
|
173
|
+
dimensions: [input.complete ? "content" : "availability"],
|
|
174
|
+
};
|
|
175
|
+
if (input.leftOccurrence.hash_status !== "verified" ||
|
|
176
|
+
input.rightOccurrence.hash_status !== "verified" ||
|
|
177
|
+
input.leftNode === undefined ||
|
|
178
|
+
input.rightNode === undefined)
|
|
179
|
+
return { ...base, classification: "unknown", dimensions: ["availability"] };
|
|
180
|
+
const dimensions = changedDimensions(input);
|
|
181
|
+
return dimensions.length === 0
|
|
182
|
+
? null
|
|
183
|
+
: { ...base, classification: "changed", dimensions };
|
|
184
|
+
};
|
|
185
|
+
const changedDimensions = (input) => {
|
|
186
|
+
const left = input.leftNode;
|
|
187
|
+
const right = input.rightNode;
|
|
188
|
+
if (left === undefined || right === undefined)
|
|
189
|
+
return ["availability"];
|
|
190
|
+
const checks = [
|
|
191
|
+
["content", left.sha256 !== right.sha256],
|
|
192
|
+
["kind", left.kind !== right.kind],
|
|
193
|
+
["format", left.format !== right.format],
|
|
194
|
+
["size", left.size !== right.size],
|
|
195
|
+
[
|
|
196
|
+
"executable",
|
|
197
|
+
input.leftOccurrence?.executable !== input.rightOccurrence?.executable,
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
"relations",
|
|
201
|
+
JSON.stringify(input.leftRelations) !==
|
|
202
|
+
JSON.stringify(input.rightRelations),
|
|
203
|
+
],
|
|
204
|
+
["metadata", metadataChanged(input, left, right)],
|
|
205
|
+
];
|
|
206
|
+
return checks
|
|
207
|
+
.filter(([, changed]) => changed)
|
|
208
|
+
.map(([dimension]) => dimension);
|
|
209
|
+
};
|
|
210
|
+
const metadataChanged = (input, left, right) => left.media_type !== right.media_type ||
|
|
211
|
+
left.architecture !== right.architecture ||
|
|
212
|
+
left.executable !== right.executable ||
|
|
213
|
+
left.content_state !== right.content_state ||
|
|
214
|
+
input.leftOccurrence?.entry_kind !== input.rightOccurrence?.entry_kind ||
|
|
215
|
+
input.leftOccurrence?.encrypted !== input.rightOccurrence?.encrypted ||
|
|
216
|
+
JSON.stringify(left.limitations) !== JSON.stringify(right.limitations) ||
|
|
217
|
+
JSON.stringify(input.leftOccurrence?.limitations) !==
|
|
218
|
+
JSON.stringify(input.rightOccurrence?.limitations);
|
|
219
|
+
const canonicalJson = (value) => {
|
|
220
|
+
const encoded = canonicalize(value);
|
|
221
|
+
if (encoded === undefined)
|
|
222
|
+
throw new TypeError("Artifact comparison could not canonicalize graph data");
|
|
223
|
+
return encoded;
|
|
224
|
+
};
|
|
225
|
+
const nodeFor = (occurrence, nodes) => occurrence?.artifact_id === null || occurrence?.artifact_id === undefined
|
|
226
|
+
? undefined
|
|
227
|
+
: nodes.get(occurrence.artifact_id);
|
|
228
|
+
const relationsByPath = (inventory) => {
|
|
229
|
+
const output = new Map();
|
|
230
|
+
const occurrences = new Map(inventory.occurrences.map((item) => [item.occurrence_id, item]));
|
|
231
|
+
for (const edge of inventory.edges) {
|
|
232
|
+
const path = edge.logical_path ?? ".";
|
|
233
|
+
const values = output.get(path);
|
|
234
|
+
const occurrence = occurrences.get(edge.occurrence_id);
|
|
235
|
+
const projection = {
|
|
236
|
+
parent_logical_path: occurrence?.parent_occurrence_id === null ||
|
|
237
|
+
occurrence?.parent_occurrence_id === undefined
|
|
238
|
+
? null
|
|
239
|
+
: (occurrences.get(occurrence.parent_occurrence_id)?.logical_path ??
|
|
240
|
+
null),
|
|
241
|
+
child_artifact_id: edge.child_artifact_id,
|
|
242
|
+
relation: edge.relation,
|
|
243
|
+
logical_path: edge.logical_path,
|
|
244
|
+
producer: edge.producer,
|
|
245
|
+
};
|
|
246
|
+
if (values === undefined)
|
|
247
|
+
output.set(path, [projection]);
|
|
248
|
+
else
|
|
249
|
+
values.push(projection);
|
|
250
|
+
}
|
|
251
|
+
for (const values of output.values())
|
|
252
|
+
values.sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right)));
|
|
253
|
+
return output;
|
|
254
|
+
};
|
|
255
|
+
const summarize = (left, right, changes, covered) => {
|
|
256
|
+
const counts = { added: 0, removed: 0, changed: 0, unknown: 0 };
|
|
257
|
+
for (const change of changes)
|
|
258
|
+
counts[change.classification] += 1;
|
|
259
|
+
return {
|
|
260
|
+
unchanged: covered
|
|
261
|
+
? Math.max(0, Math.min(left.occurrences.length, right.occurrences.length) -
|
|
262
|
+
counts.changed -
|
|
263
|
+
counts.unknown)
|
|
264
|
+
: 0,
|
|
265
|
+
...counts,
|
|
266
|
+
unknown: covered
|
|
267
|
+
? counts.unknown
|
|
268
|
+
: Math.max(counts.unknown, Math.max(left.manifest.occurrence_count, right.manifest.occurrence_count) -
|
|
269
|
+
counts.added -
|
|
270
|
+
counts.removed -
|
|
271
|
+
counts.changed),
|
|
272
|
+
};
|
|
273
|
+
};
|