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.
Files changed (122) hide show
  1. package/README.md +124 -20
  2. package/bridge/hopper_bridge.py +141 -13
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +164 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +27 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +8 -7
  12. package/dist/application/EvidenceBundleCommands.js +29 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +84 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +105 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +153 -6
  41. package/dist/config.js +35 -1
  42. package/dist/contracts/artifactComparisonExample.js +95 -0
  43. package/dist/contracts/artifactToolContracts.js +84 -0
  44. package/dist/contracts/enhancedInputs.js +4 -0
  45. package/dist/contracts/functionComparisonExample.js +57 -0
  46. package/dist/contracts/investigationExamples.js +118 -0
  47. package/dist/contracts/nativeToolContracts.js +52 -0
  48. package/dist/contracts/processCaptureExample.js +25 -0
  49. package/dist/contracts/toolContractExamples.js +69 -0
  50. package/dist/contracts/toolContracts.js +99 -36
  51. package/dist/contracts/toolOutputSchemas.js +154 -82
  52. package/dist/contracts/unknownContractExamples.js +33 -0
  53. package/dist/domain/artifactComparison.js +273 -0
  54. package/dist/domain/artifactGraph.js +194 -0
  55. package/dist/domain/artifactInventoryEvidence.js +150 -0
  56. package/dist/domain/binaryTarget.js +55 -0
  57. package/dist/domain/bundleComparison.js +266 -0
  58. package/dist/domain/callPath.js +346 -0
  59. package/dist/domain/changedBehavior.js +294 -0
  60. package/dist/domain/errors.js +192 -4
  61. package/dist/domain/evidence.js +41 -10
  62. package/dist/domain/evidenceBundle.js +187 -6
  63. package/dist/domain/functionComparison.js +201 -0
  64. package/dist/domain/functionComparisonNormalization.js +112 -0
  65. package/dist/domain/functionComparisonResults.js +54 -0
  66. package/dist/domain/functionComparisonSchemas.js +82 -0
  67. package/dist/domain/functionDossierEvidence.js +171 -0
  68. package/dist/domain/hopperValues.js +14 -7
  69. package/dist/domain/nativeInspection.js +142 -0
  70. package/dist/domain/processCapture.js +152 -54
  71. package/dist/domain/processComparison.js +106 -0
  72. package/dist/domain/reconstructionUnknowns.js +90 -0
  73. package/dist/domain/reconstructionVerification.js +285 -0
  74. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  75. package/dist/domain/referenceSourceClassification.js +496 -0
  76. package/dist/domain/referenceSourceGraph.js +376 -0
  77. package/dist/domain/referenceSourceImportParsing.js +235 -0
  78. package/dist/domain/residualUnknown.js +239 -0
  79. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  80. package/dist/hopper/BridgeLauncher.js +40 -3
  81. package/dist/hopper/HopperClient.js +14 -5
  82. package/dist/hopper/HopperProvider.js +57 -22
  83. package/dist/identity.js +1 -0
  84. package/dist/main.js +5 -1
  85. package/dist/native/CommandRunner.js +156 -0
  86. package/dist/native/NativeMacOSProvider.js +306 -0
  87. package/dist/native/NativeMachoInspection.js +135 -0
  88. package/dist/native/parsers/codesign.js +55 -0
  89. package/dist/native/parsers/demangle.js +26 -0
  90. package/dist/native/parsers/dyldInfo.js +25 -0
  91. package/dist/native/parsers/lipo.js +67 -0
  92. package/dist/native/parsers/otool.js +193 -0
  93. package/dist/native/parsers/plist.js +23 -0
  94. package/dist/reference/ReferenceSourceReader.js +73 -0
  95. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  96. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  97. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  98. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  99. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  100. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  101. package/dist/server/createServer.js +31 -5
  102. package/dist/server/recordDerivedEvidence.js +10 -0
  103. package/dist/server/registerArtifactComparisonTool.js +62 -0
  104. package/dist/server/registerArtifactTools.js +6 -0
  105. package/dist/server/registerBundleComparisonTool.js +47 -0
  106. package/dist/server/registerEnhancedTools.js +64 -12
  107. package/dist/server/registerEvidenceTools.js +36 -0
  108. package/dist/server/registerFunctionComparisonTool.js +68 -0
  109. package/dist/server/registerInvestigationTools.js +224 -0
  110. package/dist/server/registerNativeTools.js +6 -0
  111. package/dist/server/registerOfficialTools.js +47 -14
  112. package/dist/server/registerProcessComparisonTool.js +106 -0
  113. package/dist/server/registerSessionTools.js +179 -70
  114. package/dist/server/sessionEvidence.js +28 -0
  115. package/dist/server/sessionToolPolicies.js +64 -0
  116. package/dist/server/toolRegistrationOptions.js +7 -0
  117. package/dist/server/toolResult.js +8 -5
  118. package/install.sh +198 -0
  119. package/package.json +19 -2
  120. package/scripts/rea.mjs +5 -1
  121. package/skills/rea-analysis/SKILL.md +77 -2
  122. /package/dist/{application/HopperToolPort.js → domain/referenceSourcePolicy.js} +0 -0
@@ -8,9 +8,124 @@ export class HopperError extends AnalysisError {
8
8
  export class AnalysisProtocolError extends AnalysisError {
9
9
  _tag = "AnalysisProtocolError";
10
10
  }
11
- /** An evidence bundle or bounded session ledger rejected caller-controlled data. */
12
- export class EvidenceLedgerError extends AnalysisError {
13
- _tag = "EvidenceLedgerError";
11
+ /** Caller input failed provider-neutral application parsing. */
12
+ export class AnalysisInputError extends AnalysisError {
13
+ operation;
14
+ _tag = "AnalysisInputError";
15
+ constructor(operation, options) {
16
+ super(`Invalid analysis input for ${operation}`, options);
17
+ this.operation = operation;
18
+ }
19
+ }
20
+ /** Provider output failed provider-neutral application parsing. */
21
+ export class AnalysisOutputError extends AnalysisError {
22
+ operation;
23
+ reason;
24
+ _tag = "AnalysisOutputError";
25
+ constructor(operation, reason, options) {
26
+ super(`Invalid analysis output for ${operation}: ${reason}`, options);
27
+ this.operation = operation;
28
+ this.reason = reason;
29
+ }
30
+ }
31
+ /** Selected provider cannot execute a declared analysis operation. */
32
+ export class AnalysisCapabilityUnavailableError extends AnalysisError {
33
+ providerId;
34
+ operation;
35
+ reason;
36
+ _tag = "AnalysisCapabilityUnavailableError";
37
+ constructor(providerId, operation, reason) {
38
+ super(`Provider ${providerId} cannot execute ${operation}: ${reason}`);
39
+ this.providerId = providerId;
40
+ this.operation = operation;
41
+ this.reason = reason;
42
+ }
43
+ }
44
+ /** Caller cancellation won before provider-neutral work completed. */
45
+ export class AnalysisCancelledError extends AnalysisError {
46
+ operation;
47
+ _tag = "AnalysisCancelledError";
48
+ constructor(operation) {
49
+ super(`Analysis operation was cancelled: ${operation}`);
50
+ this.operation = operation;
51
+ }
52
+ }
53
+ /** Provider-neutral operation exceeded its declared execution deadline. */
54
+ export class AnalysisTimeoutError extends AnalysisError {
55
+ operation;
56
+ timeoutMs;
57
+ _tag = "AnalysisTimeoutError";
58
+ constructor(operation, timeoutMs) {
59
+ super(`Analysis operation timed out after ${String(timeoutMs)}ms: ${operation}`);
60
+ this.operation = operation;
61
+ this.timeoutMs = timeoutMs;
62
+ }
63
+ }
64
+ /** No configured provider can satisfy a requested operation. */
65
+ export class ProviderSelectionError extends AnalysisError {
66
+ operation;
67
+ _tag = "ProviderSelectionError";
68
+ constructor(operation) {
69
+ super(`No configured provider can execute ${operation}`);
70
+ this.operation = operation;
71
+ }
72
+ }
73
+ /** A provider adapter failed outside its more precise typed variants. */
74
+ export class ProviderAdapterError extends AnalysisError {
75
+ providerId;
76
+ operation;
77
+ _tag = "ProviderAdapterError";
78
+ constructor(providerId, operation, options) {
79
+ super(`Provider ${providerId} adapter failed during ${operation}`, options);
80
+ this.providerId = providerId;
81
+ this.operation = operation;
82
+ }
83
+ }
84
+ /** Artifact inventory or extraction failed a typed safety boundary. */
85
+ export class ArtifactOperationError extends AnalysisError {
86
+ operation;
87
+ reason;
88
+ _tag = "ArtifactOperationError";
89
+ constructor(operation, reason) {
90
+ super(`Artifact ${operation} failed: ${reason}`);
91
+ this.operation = operation;
92
+ this.reason = reason;
93
+ }
94
+ }
95
+ /** Evidence identity, schema, or bundle manifests failed integrity checks. */
96
+ export class EvidenceIntegrityError extends AnalysisError {
97
+ _tag = "EvidenceIntegrityError";
98
+ }
99
+ /** A bounded evidence ledger cannot accept more records or serialized bytes. */
100
+ export class EvidenceLimitError extends AnalysisError {
101
+ limit;
102
+ maximum;
103
+ _tag = "EvidenceLimitError";
104
+ constructor(limit, maximum) {
105
+ super(`Evidence ledger ${limit} limit exceeded (${String(maximum)})`);
106
+ this.limit = limit;
107
+ this.maximum = maximum;
108
+ }
109
+ }
110
+ /** Evidence bundle filesystem access failed within the configured policy. */
111
+ export class EvidenceFileError extends AnalysisError {
112
+ operation;
113
+ reason;
114
+ _tag = "EvidenceFileError";
115
+ constructor(operation, reason, options) {
116
+ super(`Evidence bundle ${operation} failed: ${reason}`, options);
117
+ this.operation = operation;
118
+ this.reason = reason;
119
+ }
120
+ }
121
+ /** Residual-unknown mutation failed a lifecycle, reference, or CAS invariant. */
122
+ export class UnknownRegistryError extends AnalysisError {
123
+ reason;
124
+ _tag = "UnknownRegistryError";
125
+ constructor(reason, options) {
126
+ super(`Residual unknown registry mutation failed: ${reason}`, options);
127
+ this.reason = reason;
128
+ }
14
129
  }
15
130
  /** Hopper did not respond within the configured operation deadline. */
16
131
  export class HopperTimeoutError extends HopperError {
@@ -75,7 +190,80 @@ export class BinaryTargetError extends AnalysisError {
75
190
  path;
76
191
  _tag = "BinaryTargetError";
77
192
  constructor(path, reason, options) {
78
- super(`Cannot open app: ${reason}: ${path}`, options);
193
+ super(`Cannot open artifact: ${reason}`, options);
79
194
  this.path = path;
80
195
  }
81
196
  }
197
+ /** Project expected failures into exhaustive, secret-safe caller fields. */
198
+ export const projectAnalysisError = (error) => {
199
+ assertKnownTag(error._tag);
200
+ return {
201
+ tag: error._tag,
202
+ message: error.message,
203
+ details: safeDetails(error),
204
+ };
205
+ };
206
+ const safeDetails = (error) => {
207
+ if (error instanceof AnalysisInputError)
208
+ return { operation: error.operation };
209
+ if (error instanceof AnalysisOutputError)
210
+ return { operation: error.operation, reason: error.reason };
211
+ if (error instanceof AnalysisCapabilityUnavailableError)
212
+ return {
213
+ providerId: error.providerId,
214
+ operation: error.operation,
215
+ reason: error.reason,
216
+ };
217
+ if (error instanceof AnalysisCancelledError)
218
+ return { operation: error.operation };
219
+ if (error instanceof AnalysisTimeoutError)
220
+ return { operation: error.operation, timeoutMs: error.timeoutMs };
221
+ if (error instanceof ProviderSelectionError)
222
+ return { operation: error.operation };
223
+ if (error instanceof ProviderAdapterError)
224
+ return { providerId: error.providerId, operation: error.operation };
225
+ if (error instanceof ArtifactOperationError)
226
+ return { operation: error.operation, reason: error.reason };
227
+ if (error instanceof EvidenceLimitError)
228
+ return { limit: error.limit, maximum: error.maximum };
229
+ if (error instanceof EvidenceFileError)
230
+ return { operation: error.operation, reason: error.reason };
231
+ if (error instanceof UnknownRegistryError)
232
+ return { reason: error.reason };
233
+ if (error instanceof HopperTimeoutError)
234
+ return { timeoutMs: error.timeoutMs };
235
+ if (error instanceof HopperRemoteError)
236
+ return { code: error.code, safeMessage: error.safeMessage };
237
+ if (error instanceof HopperProcessError)
238
+ return { exitCode: error.exitCode };
239
+ return {};
240
+ };
241
+ const KNOWN_ERROR_TAGS = {
242
+ AnalysisProtocolError: true,
243
+ AnalysisInputError: true,
244
+ AnalysisOutputError: true,
245
+ AnalysisCapabilityUnavailableError: true,
246
+ AnalysisCancelledError: true,
247
+ AnalysisTimeoutError: true,
248
+ ProviderSelectionError: true,
249
+ ProviderAdapterError: true,
250
+ ArtifactOperationError: true,
251
+ ProcessCaptureError: true,
252
+ EvidenceIntegrityError: true,
253
+ EvidenceLimitError: true,
254
+ EvidenceFileError: true,
255
+ UnknownRegistryError: true,
256
+ HopperTimeoutError: true,
257
+ HopperCancelledError: true,
258
+ HopperProtocolError: true,
259
+ HopperRemoteError: true,
260
+ HopperProcessError: true,
261
+ HopperStartError: true,
262
+ ConfigurationError: true,
263
+ NoBinaryOpenError: true,
264
+ BinaryTargetError: true,
265
+ };
266
+ const assertKnownTag = (tag) => {
267
+ if (KNOWN_ERROR_TAGS[tag] !== true)
268
+ throw new TypeError("Unknown analysis error tag");
269
+ };
@@ -11,11 +11,32 @@ const providerSchema = z.object({
11
11
  const subjectSchema = z.object({
12
12
  name: z.string().min(1),
13
13
  digest: z.object({ sha256: digestSchema }),
14
- format: z.enum(["hopper", "mach-o", "elf", "pe"]),
14
+ format: z.enum([
15
+ "hopper",
16
+ "mach-o",
17
+ "elf",
18
+ "pe",
19
+ "zip",
20
+ "ipa",
21
+ "apk",
22
+ "asar",
23
+ "dmg",
24
+ "pkg",
25
+ "plist",
26
+ "javascript",
27
+ "source-map",
28
+ "directory",
29
+ "file",
30
+ "unknown",
31
+ "mach-o-universal",
32
+ "javascript-bundle",
33
+ "entitlements",
34
+ ]),
15
35
  architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
16
36
  local_path: z.string(),
17
37
  });
18
- const locationSchema = z.discriminatedUnion("kind", [
38
+ /** Source location attached to an evidence observation. */
39
+ const evidenceLocationSchema = z.discriminatedUnion("kind", [
19
40
  z.object({ kind: z.literal("address"), address: z.string().min(1) }),
20
41
  z.object({
21
42
  kind: z.literal("address-range"),
@@ -23,6 +44,12 @@ const locationSchema = z.discriminatedUnion("kind", [
23
44
  end: z.string().min(1),
24
45
  }),
25
46
  z.object({ kind: z.literal("artifact-path"), path: z.string().min(1) }),
47
+ z.object({ kind: z.literal("file-offset"), offset: z.number().int().min(0) }),
48
+ z.object({
49
+ kind: z.literal("file-offset-range"),
50
+ start: z.number().int().min(0),
51
+ end: z.number().int().min(0),
52
+ }),
26
53
  ]);
27
54
  const evidenceAuthoritySchema = z.enum([
28
55
  "shipped-artifact",
@@ -46,13 +73,13 @@ export const evidenceSchema = z.object({
46
73
  predicate_type: z.string().min(1),
47
74
  operation: z.string().min(1),
48
75
  parameters: z.record(z.string(), jsonValueSchema),
49
- result: jsonValueSchema,
50
- raw_payload_sha256: digestSchema.nullable(),
76
+ raw_result: jsonValueSchema.nullable(),
77
+ normalized_result: jsonValueSchema,
51
78
  confidence: z.enum(["observed", "derived", "inferred"]),
52
79
  authority: evidenceAuthoritySchema,
53
80
  environment: executionEnvironmentSchema.nullable(),
54
81
  limitations: z.array(z.string()),
55
- locations: z.array(locationSchema),
82
+ locations: z.array(evidenceLocationSchema),
56
83
  evidence_links: z.array(z.string().regex(/^ev_[a-f0-9]{64}$/u)),
57
84
  });
58
85
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
@@ -76,7 +103,8 @@ const semanticProjection = (evidence) => ({
76
103
  predicate_type: evidence.predicate_type,
77
104
  operation: evidence.operation,
78
105
  parameters: evidence.parameters,
79
- result: evidence.result,
106
+ raw_result: evidence.raw_result,
107
+ normalized_result: evidence.normalized_result,
80
108
  confidence: evidence.confidence,
81
109
  authority: evidence.authority,
82
110
  environment: evidence.environment,
@@ -88,6 +116,11 @@ const semanticProjection = (evidence) => ({
88
116
  const computeEvidenceId = (evidence) => `ev_${sha256(canonicalJson(semanticProjection(evidence)))}`;
89
117
  /** Parse evidence and reject a syntactically valid but tampered semantic ID. */
90
118
  export const parseEvidence = (input) => {
119
+ if (typeof input === "object" &&
120
+ input !== null &&
121
+ "schema_version" in input &&
122
+ input.schema_version !== 2)
123
+ throw new TypeError(`Unsupported evidence schema_version ${String(input.schema_version)}; Evidence v1 is not accepted. Produce Evidence v2.`);
91
124
  const evidence = evidenceSchema.parse(input);
92
125
  const { evidence_id: evidenceId, ...withoutId } = evidence;
93
126
  if (computeEvidenceId(withoutId) !== evidenceId)
@@ -122,7 +155,8 @@ export const createEvidence = (target, provider, observation) => {
122
155
  predicate_type: observation.predicateType ?? "rea.analysis/v2",
123
156
  operation: observation.operation,
124
157
  parameters: observation.parameters,
125
- result: observation.result,
158
+ raw_result: observation.rawResult ?? null,
159
+ normalized_result: observation.result,
126
160
  confidence: observation.confidence ?? "observed",
127
161
  authority: observation.authority ?? "shipped-artifact",
128
162
  environment: observation.environment ?? null,
@@ -139,8 +173,5 @@ export const createEvidence = (target, provider, observation) => {
139
173
  ...semantic,
140
174
  evidence_id: `ev_${sha256(canonicalJson(semantic))}`,
141
175
  subject,
142
- raw_payload_sha256: observation.redactedRawPayload === undefined
143
- ? null
144
- : sha256(canonicalJson(observation.redactedRawPayload)),
145
176
  });
146
177
  };
@@ -1,11 +1,192 @@
1
1
  import { z } from "zod";
2
- import { evidenceSchema } from "./evidence.js";
2
+ import canonicalize from "canonicalize";
3
+ import { evidenceSchema, parseEvidence } from "./evidence.js";
4
+ import { residualUnknownSchema, } from "./residualUnknown.js";
5
+ const artifactManifestSchema = evidenceSchema.shape.subject.unwrap().pick({
6
+ digest: true,
7
+ format: true,
8
+ architecture: true,
9
+ });
10
+ const providerManifestSchema = evidenceSchema.shape.provider;
11
+ const environmentManifestSchema = evidenceSchema.shape.environment.unwrap();
12
+ const scenarioManifestSchema = z.object({
13
+ evidence_id: evidenceSchema.shape.evidence_id,
14
+ operation: z.string().min(1),
15
+ authority: z.literal("controlled-replay"),
16
+ });
17
+ const captureManifestSchema = z.object({
18
+ evidence_id: evidenceSchema.shape.evidence_id,
19
+ predicate_type: z.string().min(1),
20
+ });
3
21
  export const evidenceBundleSchema = z.object({
4
- bundle_version: z.literal(1),
22
+ bundle_version: z.literal(2),
23
+ artifacts: z.array(artifactManifestSchema),
24
+ providers: z.array(providerManifestSchema),
25
+ environments: z.array(environmentManifestSchema),
26
+ scenarios: z.array(scenarioManifestSchema),
27
+ captures: z.array(captureManifestSchema),
28
+ unknowns: z.array(residualUnknownSchema),
5
29
  records: z.array(evidenceSchema),
6
30
  });
7
31
  /** Project records into a deterministic bundle whose order has no semantics. */
8
- export const createEvidenceBundle = (records) => ({
9
- bundle_version: 1,
10
- records: [...records].sort((left, right) => left.evidence_id.localeCompare(right.evidence_id)),
11
- });
32
+ export const createEvidenceBundle = (records, unknowns = []) => {
33
+ const sortedRecords = [...records].sort((left, right) => left.evidence_id.localeCompare(right.evidence_id));
34
+ return {
35
+ bundle_version: 2,
36
+ artifacts: uniqueSorted(sortedRecords.flatMap(({ subject }) => subject === null
37
+ ? []
38
+ : [
39
+ {
40
+ digest: subject.digest,
41
+ format: subject.format,
42
+ architecture: subject.architecture,
43
+ },
44
+ ])),
45
+ providers: uniqueSorted(sortedRecords.map(({ provider }) => provider)),
46
+ environments: uniqueSorted(sortedRecords.flatMap(({ environment }) => environment === null ? [] : [environment])),
47
+ scenarios: sortedRecords.flatMap((evidence) => evidence.authority === "controlled-replay"
48
+ ? [
49
+ {
50
+ evidence_id: evidence.evidence_id,
51
+ operation: evidence.operation,
52
+ authority: evidence.authority,
53
+ },
54
+ ]
55
+ : []),
56
+ captures: sortedRecords.flatMap((evidence) => evidence.predicate_type.startsWith("rea.process-capture/")
57
+ ? [
58
+ {
59
+ evidence_id: evidence.evidence_id,
60
+ predicate_type: evidence.predicate_type,
61
+ },
62
+ ]
63
+ : []),
64
+ unknowns: [...unknowns].sort((left, right) => left.unknown_id.localeCompare(right.unknown_id) ||
65
+ left.revision - right.revision),
66
+ records: sortedRecords,
67
+ };
68
+ };
69
+ /** Parse records, verify semantic IDs, and reject inconsistent manifests. */
70
+ export const parseEvidenceBundle = (input) => {
71
+ const parsed = evidenceBundleSchema.parse(input);
72
+ const recordIds = parsed.records.map(({ evidence_id: id }) => id);
73
+ if (new Set(recordIds).size !== recordIds.length)
74
+ throw new TypeError("Evidence bundle contains duplicate record IDs");
75
+ validateUnknownGraph(parsed.unknowns, parsed.records);
76
+ const canonical = createEvidenceBundle(parsed.records.map(parseEvidence), parsed.unknowns);
77
+ if (JSON.stringify(parsed) !== JSON.stringify(canonical))
78
+ throw new TypeError("Evidence bundle manifests are not canonical");
79
+ return canonical;
80
+ };
81
+ const validateUnknownGraph = (unknowns, records) => {
82
+ const evidenceById = new Map(records.map((record) => [record.evidence_id, record]));
83
+ const histories = new Map();
84
+ for (const unknown of unknowns) {
85
+ const history = histories.get(unknown.unknown_id) ?? [];
86
+ history.push(unknown);
87
+ histories.set(unknown.unknown_id, history);
88
+ }
89
+ const unknownById = new Map();
90
+ for (const [id, unordered] of histories) {
91
+ const history = [...unordered].sort((left, right) => left.revision - right.revision);
92
+ for (const [index, revision] of history.entries()) {
93
+ if (revision.revision !== index + 1)
94
+ throw new TypeError(`Residual unknown ${id} revision history has a gap`);
95
+ if (index > 0 &&
96
+ revision.previous_revision_digest !==
97
+ history[index - 1]?.revision_digest)
98
+ throw new TypeError(`Residual unknown ${id} revision chain is broken`);
99
+ }
100
+ const head = history.at(-1);
101
+ if (head === undefined)
102
+ throw new TypeError("Residual unknown revision history is empty");
103
+ unknownById.set(id, head);
104
+ }
105
+ for (const unknown of unknowns) {
106
+ const referencedEvidence = [
107
+ ...unknown.supporting_evidence_ids,
108
+ ...unknown.contradicting_evidence_ids,
109
+ ...unknown.mutation_evidence_ids,
110
+ ...(unknown.resolution?.evidence_ids ?? []),
111
+ ];
112
+ for (const evidenceId of referencedEvidence)
113
+ if (!evidenceById.has(evidenceId))
114
+ throw new TypeError(`Residual unknown references missing evidence ${evidenceId}`);
115
+ for (const relationship of unknown.relationships)
116
+ if (!unknownById.has(relationship.unknown_id))
117
+ throw new TypeError(`Residual unknown references missing unknown ${relationship.unknown_id}`);
118
+ if (unknown.resolution?.disposition === "verified")
119
+ validateVerifiedResolution(unknown, evidenceById);
120
+ }
121
+ rejectDependencyCycles(unknownById);
122
+ };
123
+ const validateVerifiedResolution = (unknown, evidenceById) => {
124
+ if (unknown.contradicting_evidence_ids.length > 0)
125
+ throw new TypeError(`Residual unknown ${unknown.unknown_id} retains contradicting evidence`);
126
+ const qualifies = unknown.resolution?.evidence_ids.some((evidenceId) => {
127
+ const evidence = evidenceById.get(evidenceId);
128
+ return (unknown.supporting_evidence_ids.includes(evidenceId) &&
129
+ evidence !== undefined &&
130
+ evidence.predicate_type !== "rea.residual-unknown-mutation/v1" &&
131
+ evidenceQualifies(unknown, evidence));
132
+ });
133
+ if (qualifies !== true)
134
+ throw new TypeError(`Residual unknown ${unknown.unknown_id} has no qualifying resolution evidence`);
135
+ };
136
+ const evidenceQualifies = (unknown, evidence) => {
137
+ if (unknown.required_authority !== null &&
138
+ evidence.authority !== unknown.required_authority)
139
+ return false;
140
+ const confidenceRank = { inferred: 1, derived: 2, observed: 3 };
141
+ if (confidenceRank[evidence.confidence] <
142
+ confidenceRank[unknown.required_confidence])
143
+ return false;
144
+ const requirement = unknown.required_environment;
145
+ if (requirement === null)
146
+ return true;
147
+ if (evidence.environment === null)
148
+ return false;
149
+ return ((requirement.id === null || requirement.id === evidence.environment.id) &&
150
+ (requirement.platform === null ||
151
+ requirement.platform === evidence.environment.platform) &&
152
+ (requirement.architecture === null ||
153
+ requirement.architecture === evidence.environment.architecture) &&
154
+ (requirement.isolation === null ||
155
+ requirement.isolation === evidence.environment.isolation));
156
+ };
157
+ const rejectDependencyCycles = (unknownById) => {
158
+ const visited = new Set();
159
+ const active = new Set();
160
+ const visit = (id) => {
161
+ if (active.has(id))
162
+ throw new TypeError("Residual unknown dependency graph contains a cycle");
163
+ if (visited.has(id))
164
+ return;
165
+ active.add(id);
166
+ const unknown = unknownById.get(id);
167
+ if (unknown === undefined)
168
+ throw new TypeError("Residual unknown dependency graph is inconsistent");
169
+ for (const relationship of unknown.relationships)
170
+ if (relationship.type === "depends-on")
171
+ visit(relationship.unknown_id);
172
+ active.delete(id);
173
+ visited.add(id);
174
+ };
175
+ for (const id of unknownById.keys())
176
+ visit(id);
177
+ };
178
+ /** Encode a validated bundle as byte-stable RFC 8785 canonical JSON. */
179
+ export const serializeEvidenceBundle = (bundle) => {
180
+ const serialized = canonicalize(parseEvidenceBundle(bundle));
181
+ if (serialized === undefined)
182
+ throw new TypeError("Evidence bundle canonicalization failed");
183
+ return serialized;
184
+ };
185
+ const uniqueSorted = (values) => {
186
+ const unique = new Map();
187
+ for (const value of values)
188
+ unique.set(JSON.stringify(value), value);
189
+ return [...unique.entries()]
190
+ .sort(([left], [right]) => left.localeCompare(right))
191
+ .map(([, value]) => value);
192
+ };