rea-agents 1.2.0 → 1.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 (107) hide show
  1. package/README.md +95 -27
  2. package/bridge/hopper_bridge.py +172 -14
  3. package/dist/application/AnalysisSnapshotCache.js +131 -0
  4. package/dist/application/AnalysisSnapshotFiles.js +31 -0
  5. package/dist/application/ArtifactGraphConstruction.js +1 -1
  6. package/dist/application/ArtifactInventory.js +100 -24
  7. package/dist/application/AuthorizedArtifactInventory.js +16 -0
  8. package/dist/application/BinarySession.js +149 -36
  9. package/dist/application/BinarySessionPort.js +1 -0
  10. package/dist/application/BoundedJsonFiles.js +109 -0
  11. package/dist/application/CapabilityInventory.js +127 -0
  12. package/dist/application/ClientRegistrationStatus.js +69 -0
  13. package/dist/application/CrossVersionInventory.js +109 -0
  14. package/dist/application/CrossVersionInvestigation.js +358 -0
  15. package/dist/application/DirectAnalysis.js +205 -24
  16. package/dist/application/Doctor.js +104 -3
  17. package/dist/application/EvidenceBundleFiles.js +15 -108
  18. package/dist/application/EvidenceLedger.js +3 -2
  19. package/dist/application/InvestigationProviders.js +48 -0
  20. package/dist/application/InvestigationWorkspaceStore.js +214 -0
  21. package/dist/application/LinuxHopper.js +72 -48
  22. package/dist/application/PermissionAuthority.js +183 -0
  23. package/dist/application/PermissionConfiguration.js +26 -0
  24. package/dist/application/ProcessCaptureAuthority.js +2 -2
  25. package/dist/application/ProcessCaptureError.js +25 -0
  26. package/dist/application/ProcessCaptureLifecycle.js +22 -2
  27. package/dist/application/ProcessCli.js +111 -31
  28. package/dist/application/ProcessHarness.js +6 -3
  29. package/dist/application/ProgressReporter.js +30 -0
  30. package/dist/application/ProjectPermissionStore.js +110 -0
  31. package/dist/application/ReferenceSourceImportEntries.js +18 -3
  32. package/dist/application/ReferenceSourceImportTypes.js +32 -0
  33. package/dist/application/Setup.js +35 -101
  34. package/dist/application/SetupClients.js +19 -0
  35. package/dist/application/SetupInstallFailure.js +53 -0
  36. package/dist/application/SetupPlan.js +31 -0
  37. package/dist/application/SupportedClients.js +41 -0
  38. package/dist/application/Uninstall.js +34 -24
  39. package/dist/application/UnknownEvidence.js +33 -0
  40. package/dist/application/Upgrade.js +3 -1
  41. package/dist/application/runtime.js +2 -2
  42. package/dist/artifacts/ArtifactProvider.js +15 -8
  43. package/dist/catalogIdentity.js +121 -0
  44. package/dist/cli.js +76 -13
  45. package/dist/cliEvidenceCommands.js +52 -12
  46. package/dist/cliInvestigationCommands.js +137 -0
  47. package/dist/cliLogging.js +20 -3
  48. package/dist/cliOutput.js +41 -0
  49. package/dist/cliPolicyCommands.js +115 -0
  50. package/dist/config.js +102 -27
  51. package/dist/contracts/artifactToolContracts.js +14 -1
  52. package/dist/contracts/errorSchemas.js +98 -0
  53. package/dist/contracts/promptContracts.js +256 -0
  54. package/dist/contracts/sessionLifecycleInputs.js +11 -0
  55. package/dist/contracts/toolContractTypes.js +1 -0
  56. package/dist/contracts/toolContracts.js +20 -7
  57. package/dist/contracts/toolOutputSchemas.js +70 -9
  58. package/dist/domain/analysisSnapshot.js +149 -0
  59. package/dist/domain/artifactComparison.js +38 -11
  60. package/dist/domain/artifactGraph.js +25 -1
  61. package/dist/domain/artifactInventoryEvidence.js +18 -0
  62. package/dist/domain/changedBehavior.js +35 -10
  63. package/dist/domain/errors.js +384 -32
  64. package/dist/domain/evidence.js +4 -2
  65. package/dist/domain/evidenceBundle.js +28 -0
  66. package/dist/domain/hopperStartupFailure.js +55 -0
  67. package/dist/domain/investigationWorkspace.js +341 -0
  68. package/dist/domain/jsonValue.js +2 -0
  69. package/dist/domain/nativeInspection.js +3 -2
  70. package/dist/domain/permissionPolicy.js +157 -0
  71. package/dist/domain/processCapture.js +5 -4
  72. package/dist/domain/processComparison.js +6 -4
  73. package/dist/domain/reconstructionVerification.js +1 -1
  74. package/dist/domain/reconstructionVerificationSchemas.js +1 -0
  75. package/dist/domain/staticRuntimeCorrelation.js +5 -1
  76. package/dist/generatedPackageMetadata.js +9 -0
  77. package/dist/hopper/BridgeLauncher.js +51 -20
  78. package/dist/hopper/HopperClient.js +45 -3
  79. package/dist/hopper/HopperProvider.js +1 -0
  80. package/dist/identity.js +10 -3
  81. package/dist/logger.js +2 -2
  82. package/dist/main.js +139 -28
  83. package/dist/server/createServer.js +54 -5
  84. package/dist/server/mcpProgress.js +23 -0
  85. package/dist/server/promptCompletion.js +144 -0
  86. package/dist/server/registerArtifactComparisonTool.js +6 -2
  87. package/dist/server/registerBundleComparisonTool.js +6 -2
  88. package/dist/server/registerEnhancedTools.js +20 -7
  89. package/dist/server/registerEvidenceResources.js +302 -0
  90. package/dist/server/registerEvidenceTools.js +59 -8
  91. package/dist/server/registerFunctionComparisonTool.js +7 -3
  92. package/dist/server/registerInvestigationTools.js +111 -18
  93. package/dist/server/registerOfficialTools.js +25 -14
  94. package/dist/server/registerProcessComparisonTool.js +8 -10
  95. package/dist/server/registerPrompts.js +67 -0
  96. package/dist/server/registerSessionStatusTool.js +52 -0
  97. package/dist/server/registerSessionTools.js +178 -22
  98. package/dist/server/runDerivedOperation.js +35 -0
  99. package/dist/server/sessionEvidence.js +2 -8
  100. package/dist/server/sessionToolPolicies.js +1 -42
  101. package/dist/server/toolResult.js +34 -6
  102. package/dist/serverIdentity.js +70 -0
  103. package/install.sh +11 -11
  104. package/package.json +10 -6
  105. package/scripts/hopper-demo-x11.py +349 -0
  106. package/scripts/rea.mjs +6 -3
  107. package/skills/rea-analysis/SKILL.md +9 -2
@@ -0,0 +1,341 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { evidenceBundleSchema, parseEvidenceBundle, } from "./evidenceBundle.js";
5
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
6
+ const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
7
+ const artifactFormatSchema = evidenceBundleSchema.shape.artifacts.element.shape.format;
8
+ /** Bounded controls for one automatic, provider-neutral artifact comparison. */
9
+ const investigationRunOptionsSchema = z.object({
10
+ max_entries: z.number().int().min(1).max(50_000).default(10_000),
11
+ max_total_bytes: z
12
+ .number()
13
+ .int()
14
+ .min(1)
15
+ .max(Number.MAX_SAFE_INTEGER)
16
+ .default(1_073_741_824),
17
+ max_entry_bytes: z
18
+ .number()
19
+ .int()
20
+ .min(1)
21
+ .max(Number.MAX_SAFE_INTEGER)
22
+ .default(268_435_456),
23
+ max_compression_ratio: z.number().min(1).max(100_000).default(1_000),
24
+ max_depth: z.number().int().min(0).max(100).default(20),
25
+ max_path_bytes: z.number().int().min(1).max(65_535).default(4_096),
26
+ page_size: z.number().int().min(1).max(500).default(500),
27
+ change_limit: z.number().int().min(1).max(500).default(500),
28
+ });
29
+ const DEFAULT_INVESTIGATION_RUN_OPTIONS = {
30
+ max_entries: 10_000,
31
+ max_total_bytes: 1_073_741_824,
32
+ max_entry_bytes: 268_435_456,
33
+ max_compression_ratio: 1_000,
34
+ max_depth: 20,
35
+ max_path_bytes: 4_096,
36
+ page_size: 500,
37
+ change_limit: 500,
38
+ };
39
+ /** Explicit write approval and two local versions for an automatic run. */
40
+ export const crossVersionInvestigationInputSchema = z
41
+ .object({
42
+ approved: z.literal(true),
43
+ workspace_path: z.string().min(1).max(4_096),
44
+ workspace_name: z.string().trim().min(1).max(200).default("default"),
45
+ expected_workspace_revision: z.number().int().min(1).optional(),
46
+ left_path: z.string().min(1).max(4_096),
47
+ right_path: z.string().min(1).max(4_096),
48
+ integrity_policy: z.enum(["fail", "record-and-continue"]).default("fail"),
49
+ integrity_continue_approved: z.boolean().default(false),
50
+ max_integrity_mismatches: z.number().int().min(1).max(100).default(10),
51
+ options: investigationRunOptionsSchema.default(DEFAULT_INVESTIGATION_RUN_OPTIONS),
52
+ })
53
+ .superRefine((input, context) => {
54
+ if (input.integrity_policy === "record-and-continue" &&
55
+ input.integrity_continue_approved !== true)
56
+ context.addIssue({
57
+ code: "custom",
58
+ path: ["integrity_continue_approved"],
59
+ message: "record-and-continue requires explicit approval",
60
+ });
61
+ });
62
+ const targetSchema = z.object({
63
+ root_sha256: digestSchema,
64
+ graph_sha256: digestSchema,
65
+ manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
66
+ format: artifactFormatSchema,
67
+ });
68
+ const stageSchema = z.enum([
69
+ "inventory_left",
70
+ "inventory_right",
71
+ "compare_artifacts",
72
+ "find_changed_behavior",
73
+ ]);
74
+ const investigationRunBaseSchema = z.object({
75
+ schema_version: z.literal(1),
76
+ run_id: z.string().regex(/^run_[a-f0-9]{64}$/u),
77
+ request_sha256: digestSchema,
78
+ left: targetSchema,
79
+ right: targetSchema,
80
+ options: investigationRunOptionsSchema,
81
+ integrity_policy: z.enum(["fail", "record-and-continue"]),
82
+ integrity_continue_approved: z.boolean(),
83
+ max_integrity_mismatches: z.number().int().min(1).max(100),
84
+ status: z.enum(["running", "complete"]),
85
+ completed_stages: z.array(stageSchema).max(4),
86
+ left_inventory_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
87
+ right_inventory_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
88
+ comparison_evidence_id: evidenceIdSchema.nullable(),
89
+ result_evidence_id: evidenceIdSchema.nullable(),
90
+ limitations: z.array(z.string().max(4_096)).max(100),
91
+ });
92
+ export const investigationRunSchema = z
93
+ .union([
94
+ investigationRunBaseSchema.extend({
95
+ legacy_request_identity: z.literal(true),
96
+ }),
97
+ investigationRunBaseSchema,
98
+ ])
99
+ .superRefine((run, context) => {
100
+ if (!("legacy_request_identity" in run))
101
+ return;
102
+ const legacyIdentity = createLegacyInvestigationRunIdentity(run);
103
+ if (run.integrity_policy !== "fail" ||
104
+ run.integrity_continue_approved ||
105
+ run.max_integrity_mismatches !== 10 ||
106
+ run.run_id !== legacyIdentity.runId ||
107
+ run.request_sha256 !== legacyIdentity.requestSha256)
108
+ context.addIssue({
109
+ code: "custom",
110
+ path: ["legacy_request_identity"],
111
+ message: "Legacy request identity is valid only for migrated strict runs",
112
+ });
113
+ });
114
+ export const investigationRunSummarySchema = z.object({
115
+ schema_version: z.literal(1),
116
+ workspace_id: z.string().regex(/^ws_[a-f0-9]{64}$/u),
117
+ run_id: z.string().regex(/^run_[a-f0-9]{64}$/u),
118
+ left_manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
119
+ right_manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
120
+ inventory_evidence_count: z.number().int().min(2).max(200),
121
+ comparison_evidence_id: evidenceIdSchema,
122
+ limitations: z.array(z.string().max(4_096)).max(100),
123
+ });
124
+ const investigationWorkspaceSchema = z.object({
125
+ workspace_version: z.literal(1),
126
+ workspace_id: z.string().regex(/^ws_[a-f0-9]{64}$/u),
127
+ name: z.string().trim().min(1).max(200),
128
+ revision: z.number().int().min(1),
129
+ previous_revision_digest: z
130
+ .string()
131
+ .regex(/^wrev_[a-f0-9]{64}$/u)
132
+ .nullable(),
133
+ revision_digest: z.string().regex(/^wrev_[a-f0-9]{64}$/u),
134
+ bundle: evidenceBundleSchema,
135
+ runs: z.array(investigationRunSchema).max(1_000),
136
+ });
137
+ const legacyInvestigationRunSchema = investigationRunBaseSchema
138
+ .omit({
139
+ integrity_policy: true,
140
+ integrity_continue_approved: true,
141
+ max_integrity_mismatches: true,
142
+ })
143
+ .strict();
144
+ const legacyInvestigationWorkspaceSchema = investigationWorkspaceSchema
145
+ .extend({
146
+ runs: z.array(legacyInvestigationRunSchema).max(1_000),
147
+ })
148
+ .strict();
149
+ /** Derive a stable run identity from content commitments and bounded controls. */
150
+ export const createInvestigationRunIdentity = (input) => {
151
+ const requestSha256 = digestCanonical({
152
+ schema: "rea.cross-version-investigation-request/v1",
153
+ left: input.left,
154
+ right: input.right,
155
+ options: input.options,
156
+ integrity: {
157
+ policy: input.integrity_policy,
158
+ approved: input.integrity_continue_approved,
159
+ max_mismatches: input.max_integrity_mismatches,
160
+ },
161
+ });
162
+ return { runId: `run_${requestSha256}`, requestSha256 };
163
+ };
164
+ /** Build the first canonical workspace revision. */
165
+ export const createInvestigationWorkspace = (name, bundle, runs) => buildWorkspace({
166
+ workspaceId: `ws_${digestCanonical({ schema: "rea.workspace/v1", name })}`,
167
+ name,
168
+ revision: 1,
169
+ previousRevisionDigest: null,
170
+ bundle,
171
+ runs,
172
+ });
173
+ /** Create the next immutable workspace revision. */
174
+ export const reviseInvestigationWorkspace = (current, bundle, runs) => buildWorkspace({
175
+ workspaceId: current.workspace_id,
176
+ name: current.name,
177
+ revision: current.revision + 1,
178
+ previousRevisionDigest: current.revision_digest,
179
+ bundle,
180
+ runs,
181
+ });
182
+ /** Parse a workspace and verify identities, revision digest, and references. */
183
+ export const parseInvestigationWorkspace = (input) => {
184
+ if (isLegacyWorkspace(input))
185
+ return migrateLegacyWorkspace(input);
186
+ const parsed = investigationWorkspaceSchema.parse(input);
187
+ const bundle = parseEvidenceBundle(parsed.bundle);
188
+ const canonical = buildWorkspace({
189
+ workspaceId: parsed.workspace_id,
190
+ name: parsed.name,
191
+ revision: parsed.revision,
192
+ previousRevisionDigest: parsed.previous_revision_digest,
193
+ bundle,
194
+ runs: parsed.runs,
195
+ });
196
+ if (parsed.workspace_id !== workspaceIdFor(parsed.name))
197
+ throw new TypeError("Investigation workspace ID does not match its name");
198
+ if (parsed.revision_digest !== canonical.revision_digest)
199
+ throw new TypeError("Investigation workspace revision digest is invalid");
200
+ if (JSON.stringify(parsed) !== JSON.stringify(canonical))
201
+ throw new TypeError("Investigation workspace is not canonical");
202
+ validateRunReferences(canonical);
203
+ return canonical;
204
+ };
205
+ /** Encode a validated workspace as byte-stable canonical JSON. */
206
+ export const serializeInvestigationWorkspace = (workspace) => canonicalJson(parseInvestigationWorkspace(workspace));
207
+ const buildWorkspace = (input) => {
208
+ const runs = [...input.runs]
209
+ .map((run) => investigationRunSchema.parse(run))
210
+ .sort((left, right) => left.run_id.localeCompare(right.run_id));
211
+ assertUnique(runs.map(({ run_id: id }) => id), "investigation run ID");
212
+ const semantic = {
213
+ workspace_version: 1,
214
+ workspace_id: input.workspaceId,
215
+ name: input.name,
216
+ revision: input.revision,
217
+ previous_revision_digest: input.previousRevisionDigest,
218
+ bundle: parseEvidenceBundle(input.bundle),
219
+ runs,
220
+ };
221
+ return investigationWorkspaceSchema.parse({
222
+ ...semantic,
223
+ revision_digest: `wrev_${digestCanonical(semantic)}`,
224
+ });
225
+ };
226
+ const validateRunReferences = (workspace) => {
227
+ const evidenceIds = new Set(workspace.bundle.records.map(({ evidence_id: id }) => id));
228
+ for (const run of workspace.runs) {
229
+ const expectedIdentity = createInvestigationRunIdentity(run);
230
+ const legacyIdentity = createLegacyInvestigationRunIdentity(run);
231
+ if ((run.run_id !== expectedIdentity.runId ||
232
+ run.request_sha256 !== expectedIdentity.requestSha256) &&
233
+ (!("legacy_request_identity" in run) ||
234
+ run.integrity_policy !== "fail" ||
235
+ run.integrity_continue_approved ||
236
+ run.max_integrity_mismatches !== 10 ||
237
+ run.run_id !== legacyIdentity.runId ||
238
+ run.request_sha256 !== legacyIdentity.requestSha256))
239
+ throw new TypeError("Investigation run identity is invalid");
240
+ validateRunStages(run);
241
+ const references = [
242
+ ...run.left_inventory_evidence_ids,
243
+ ...run.right_inventory_evidence_ids,
244
+ ...(run.comparison_evidence_id === null
245
+ ? []
246
+ : [run.comparison_evidence_id]),
247
+ ...(run.result_evidence_id === null ? [] : [run.result_evidence_id]),
248
+ ];
249
+ if (references.some((evidenceId) => !evidenceIds.has(evidenceId)))
250
+ throw new TypeError("Investigation run references missing Evidence");
251
+ }
252
+ };
253
+ /** Derive the pre-integrity-policy run identity for strict workspace migration. */
254
+ export const createLegacyInvestigationRunIdentity = (input) => {
255
+ const requestSha256 = digestCanonical({
256
+ schema: "rea.cross-version-investigation-request/v1",
257
+ left: input.left,
258
+ right: input.right,
259
+ options: input.options,
260
+ });
261
+ return { runId: `run_${requestSha256}`, requestSha256 };
262
+ };
263
+ const isLegacyWorkspace = (input) => {
264
+ if (typeof input !== "object" || input === null || !("runs" in input))
265
+ return false;
266
+ const runs = input.runs;
267
+ return (Array.isArray(runs) &&
268
+ runs.some((run) => typeof run === "object" && run !== null && !("integrity_policy" in run)));
269
+ };
270
+ const migrateLegacyWorkspace = (input) => {
271
+ const parsed = legacyInvestigationWorkspaceSchema.parse(input);
272
+ const bundle = parseEvidenceBundle(parsed.bundle);
273
+ const runs = [...parsed.runs].sort((left, right) => left.run_id.localeCompare(right.run_id));
274
+ const semantic = {
275
+ workspace_version: 1,
276
+ workspace_id: parsed.workspace_id,
277
+ name: parsed.name,
278
+ revision: parsed.revision,
279
+ previous_revision_digest: parsed.previous_revision_digest,
280
+ bundle,
281
+ runs,
282
+ };
283
+ if (parsed.workspace_id !== workspaceIdFor(parsed.name))
284
+ throw new TypeError("Investigation workspace ID does not match its name");
285
+ if (parsed.revision_digest !== `wrev_${digestCanonical(semantic)}`)
286
+ throw new TypeError("Investigation workspace revision digest is invalid");
287
+ const canonical = legacyInvestigationWorkspaceSchema.parse({
288
+ ...semantic,
289
+ revision_digest: parsed.revision_digest,
290
+ });
291
+ if (JSON.stringify(parsed) !== JSON.stringify(canonical))
292
+ throw new TypeError("Investigation workspace is not canonical");
293
+ for (const run of runs) {
294
+ const identity = createLegacyInvestigationRunIdentity(run);
295
+ if (run.run_id !== identity.runId ||
296
+ run.request_sha256 !== identity.requestSha256)
297
+ throw new TypeError("Investigation run identity is invalid");
298
+ }
299
+ const migrated = buildWorkspace({
300
+ workspaceId: parsed.workspace_id,
301
+ name: parsed.name,
302
+ revision: parsed.revision,
303
+ previousRevisionDigest: parsed.previous_revision_digest,
304
+ bundle,
305
+ runs: runs.map((run) => ({
306
+ ...run,
307
+ legacy_request_identity: true,
308
+ integrity_policy: "fail",
309
+ integrity_continue_approved: false,
310
+ max_integrity_mismatches: 10,
311
+ })),
312
+ });
313
+ validateRunReferences(migrated);
314
+ return migrated;
315
+ };
316
+ const validateRunStages = (run) => {
317
+ const expected = [
318
+ "inventory_left",
319
+ "inventory_right",
320
+ ...(run.comparison_evidence_id === null ? [] : ["compare_artifacts"]),
321
+ ...(run.result_evidence_id === null ? [] : ["find_changed_behavior"]),
322
+ ];
323
+ if (JSON.stringify(run.completed_stages) !== JSON.stringify(expected))
324
+ throw new TypeError("Investigation run stages are inconsistent");
325
+ if (run.status === "complete" && run.result_evidence_id === null)
326
+ throw new TypeError("Completed investigation run has no result Evidence");
327
+ if (run.status === "running" && run.result_evidence_id !== null)
328
+ throw new TypeError("Running investigation run has final result Evidence");
329
+ };
330
+ const workspaceIdFor = (name) => `ws_${digestCanonical({ schema: "rea.workspace/v1", name })}`;
331
+ const digestCanonical = (value) => createHash("sha256").update(canonicalJson(value)).digest("hex");
332
+ const canonicalJson = (value) => {
333
+ const encoded = canonicalize(value);
334
+ if (encoded === undefined)
335
+ throw new TypeError("Investigation workspace could not canonicalize data");
336
+ return encoded;
337
+ };
338
+ const assertUnique = (values, description) => {
339
+ if (new Set(values).size !== values.length)
340
+ throw new TypeError(`Duplicate ${description}`);
341
+ };
@@ -1,3 +1,5 @@
1
1
  import { z } from "zod";
2
2
  /** JSON-safe value shared by domain, application, and adapter boundaries. */
3
3
  export const jsonValueSchema = z.json();
4
+ /** JSON object boundary for caller-visible parameter maps. */
5
+ export const jsonObjectSchema = z.record(z.string(), jsonValueSchema);
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { jsonValueSchema } from "./jsonValue.js";
2
3
  const hexAddress = z.string().regex(/^0x[a-fA-F0-9]+$/u);
3
4
  const coverage = (item) => z.object({
4
5
  items: z.array(item),
@@ -106,7 +107,7 @@ export const inspectSignatureSchema = z.object({
106
107
  hash_algorithms: z.array(z.string()),
107
108
  authorities: z.array(z.string()),
108
109
  designated_requirement: z.string().nullable(),
109
- entitlements: z.unknown().nullable(),
110
+ entitlements: jsonValueSchema.nullable(),
110
111
  timestamp: z.string().nullable(),
111
112
  hardened_runtime: z.boolean().nullable(),
112
113
  provenance: z.array(nativeCommandInvocationSchema),
@@ -114,7 +115,7 @@ export const inspectSignatureSchema = z.object({
114
115
  });
115
116
  export const inspectPlistSchema = z.object({
116
117
  format: z.enum(["xml", "binary", "json", "unknown"]),
117
- value: z.unknown(),
118
+ value: jsonValueSchema,
118
119
  bundle: z.object({
119
120
  identifier: z.string().nullable(),
120
121
  executable: z.string().nullable(),
@@ -0,0 +1,157 @@
1
+ import { isAbsolute, relative, resolve, sep } from "node:path";
2
+ import { err, ok } from "./result.js";
3
+ /** A proposed grant exceeds the administrator-owned maximum authority. */
4
+ export class PermissionGrantError extends Error {
5
+ reason;
6
+ missing;
7
+ _tag = "PermissionGrantError";
8
+ constructor(reason, missing) {
9
+ super(`Permission grant rejected: ${reason}`);
10
+ this.reason = reason;
11
+ this.missing = missing;
12
+ }
13
+ }
14
+ /** Create a fail-closed policy with no implicit grants. */
15
+ export const createPermissionPolicy = (ceilings, grants = []) => ({
16
+ ceilings: ceilings.map(normalizeScope),
17
+ grants: grants.map(normalizeGrant),
18
+ revoked_grant_ids: new Set(),
19
+ consumed_grant_ids: new Set(),
20
+ });
21
+ /** Add one grant only when it is a subset of its administrator ceiling. */
22
+ export const grantPermission = (policy, grant) => {
23
+ const normalized = normalizeGrant(grant);
24
+ if (normalized.lifetime === "once" && normalized.operation_identity === null)
25
+ return err(new PermissionGrantError("invalid_once_scope", {}));
26
+ const missing = missingFromScopes(normalized, policy.ceilings);
27
+ if (hasMissing(missing))
28
+ return err(new PermissionGrantError("outside_administrator_ceiling", missing));
29
+ return ok({ ...policy, grants: [...policy.grants, normalized] });
30
+ };
31
+ /** Evaluate exact requested authority against the ceiling and active grants. */
32
+ export const evaluatePermission = (policy, request, now = new Date()) => {
33
+ const normalized = normalizeRequest(request);
34
+ const outsideCeiling = missingFromScopes(normalized, policy.ceilings);
35
+ if (hasMissing(outsideCeiling))
36
+ return {
37
+ allowed: false,
38
+ reason: "outside_administrator_ceiling",
39
+ missing: outsideCeiling,
40
+ };
41
+ const candidates = policy.grants.filter((grant) => grant.capability === normalized.capability &&
42
+ !policy.revoked_grant_ids.has(grant.grant_id) &&
43
+ !policy.consumed_grant_ids.has(grant.grant_id) &&
44
+ !isExpired(grant, now) &&
45
+ (grant.lifetime !== "once" ||
46
+ grant.operation_identity === normalized.operation_identity));
47
+ const selected = candidates.find((grant) => !hasMissing(missingFromScopes(normalized, [grant])));
48
+ if (selected !== undefined)
49
+ return {
50
+ allowed: true,
51
+ grant_id: selected.grant_id,
52
+ lifetime: selected.lifetime,
53
+ };
54
+ const coveringGrant = policy.grants.find((grant) => grant.capability === normalized.capability &&
55
+ (grant.lifetime !== "once" ||
56
+ grant.operation_identity === normalized.operation_identity) &&
57
+ !hasMissing(missingFromScopes(normalized, [grant])));
58
+ return {
59
+ allowed: false,
60
+ reason: coveringGrant !== undefined && isExpired(coveringGrant, now)
61
+ ? "grant_expired"
62
+ : candidates.length === 0 &&
63
+ policy.grants.some((grant) => grant.capability === normalized.capability)
64
+ ? "grant_revoked_or_consumed"
65
+ : "grant_required",
66
+ missing: missingFromScopes(normalized, candidates),
67
+ };
68
+ };
69
+ /** Consume an allowed one-shot decision; longer-lived grants are unchanged. */
70
+ export const consumePermission = (policy, decision) => decision.lifetime === "once"
71
+ ? {
72
+ ...policy,
73
+ consumed_grant_ids: new Set([
74
+ ...policy.consumed_grant_ids,
75
+ decision.grant_id,
76
+ ]),
77
+ }
78
+ : policy;
79
+ /** Revoke one grant for every future evaluation. */
80
+ export const revokePermission = (policy, grantId) => ({
81
+ ...policy,
82
+ revoked_grant_ids: new Set([...policy.revoked_grant_ids, grantId]),
83
+ });
84
+ /** Drop connection-bound authority when an MCP or CLI session ends. */
85
+ export const clearSessionPermissions = (policy) => ({
86
+ ...policy,
87
+ grants: policy.grants.filter(({ lifetime }) => lifetime !== "once" && lifetime !== "session"),
88
+ });
89
+ /** Atomically replace administrator ceilings while retaining auditable grants. */
90
+ export const reloadPermissionCeilings = (policy, ceilings) => ({
91
+ ...policy,
92
+ ceilings: ceilings.map(normalizeScope),
93
+ });
94
+ const normalizeScope = (scope) => ({
95
+ capability: scope.capability,
96
+ roots: uniqueSorted(scope.roots.map((path) => resolve(path))),
97
+ executables: uniqueSorted(scope.executables.map((path) => resolve(path))),
98
+ environment_names: uniqueSorted(scope.environment_names),
99
+ network: scope.network,
100
+ mount: scope.mount,
101
+ });
102
+ const normalizeGrant = (grant) => ({
103
+ ...normalizeScope(grant),
104
+ grant_id: grant.grant_id,
105
+ lifetime: grant.lifetime,
106
+ operation_identity: grant.operation_identity,
107
+ expires_at: grant.expires_at,
108
+ });
109
+ const normalizeRequest = (request) => ({
110
+ ...normalizeScope(request),
111
+ operation_identity: request.operation_identity,
112
+ });
113
+ const missingFromScopes = (request, scopes) => {
114
+ const relevant = scopes.filter(({ capability }) => capability === request.capability);
115
+ const roots = request.roots.filter((path) => !relevant.some((scope) => containsAny(scope.roots, path)));
116
+ const executables = request.executables.filter((path) => !relevant.some((scope) => containsAny(scope.executables, path)));
117
+ const environmentNames = request.environment_names.filter((name) => !relevant.some((scope) => scope.environment_names.includes(name)));
118
+ const network = request.network === "none" ||
119
+ relevant.some((scope) => networkRank(scope.network) >= networkRank(request.network))
120
+ ? undefined
121
+ : request.network;
122
+ const mount = !request.mount || relevant.some((scope) => scope.mount)
123
+ ? undefined
124
+ : true;
125
+ return {
126
+ ...(roots.length === 0 ? {} : { roots }),
127
+ ...(executables.length === 0 ? {} : { executables }),
128
+ ...(environmentNames.length === 0
129
+ ? {}
130
+ : { environment_names: environmentNames }),
131
+ ...(network === undefined ? {} : { network }),
132
+ ...(mount === undefined ? {} : { mount }),
133
+ };
134
+ };
135
+ const HOST_PATH_OPERATIONS = { relative, isAbsolute, sep };
136
+ /** Test whether a candidate is inside a root using one platform path flavor. */
137
+ export const isPathContained = (root, candidate, operations = HOST_PATH_OPERATIONS) => {
138
+ const relation = operations.relative(root, candidate);
139
+ return (relation === "" ||
140
+ (relation !== ".." &&
141
+ !relation.startsWith(`..${operations.sep}`) &&
142
+ !operations.isAbsolute(relation)));
143
+ };
144
+ const containsAny = (roots, candidate) => roots.some((root) => isPathContained(root, candidate));
145
+ const networkRank = (network) => {
146
+ switch (network) {
147
+ case "none":
148
+ return 0;
149
+ case "loopback":
150
+ return 1;
151
+ case "external":
152
+ return 2;
153
+ }
154
+ };
155
+ const isExpired = (grant, now) => grant.expires_at !== null && Date.parse(grant.expires_at) <= now.getTime();
156
+ const hasMissing = (missing) => Object.keys(missing).length > 0;
157
+ const uniqueSorted = (values) => [...new Set(values)].sort((left, right) => left.localeCompare(right));
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { jsonValueSchema } from "./jsonValue.js";
2
3
  import { LEGACY_PROCESS_CAPTURE_MESSAGE, normalizationSchema, } from "./processScenario.js";
3
4
  export * from "./processScenario.js";
4
5
  const fileStateSchema = z.object({
@@ -31,10 +32,10 @@ export const processCaptureSchema = z.object({
31
32
  pty_backend: z.literal("node-pty"),
32
33
  started_at: z.iso.datetime(),
33
34
  completed_at: z.iso.datetime(),
34
- scenario: z.record(z.string(), z.unknown()),
35
- comparison_contract: z.record(z.string(), z.unknown()),
36
- shim_plan: z.array(z.unknown()),
37
- replay_plan: z.record(z.string(), z.unknown()),
35
+ scenario: z.record(z.string(), jsonValueSchema),
36
+ comparison_contract: z.record(z.string(), jsonValueSchema),
37
+ shim_plan: z.array(jsonValueSchema),
38
+ replay_plan: z.record(z.string(), jsonValueSchema),
38
39
  full_scenario_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
39
40
  comparison_contract_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
40
41
  executable_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { validateProcessCapture } from "./processCapture.js";
3
+ import { jsonValueSchema } from "./jsonValue.js";
3
4
  /** Comparison classification that never equates incomplete evidence. */
4
5
  export const comparisonStatusSchema = z.enum([
5
6
  "unchanged",
@@ -8,6 +9,7 @@ export const comparisonStatusSchema = z.enum([
8
9
  "changed",
9
10
  "truncated",
10
11
  "unknown",
12
+ "contradiction",
11
13
  ]);
12
14
  /** Pure normalized comparison between two captures. */
13
15
  export const processCaptureComparisonSchema = z
@@ -37,8 +39,8 @@ export const processCaptureComparisonSchema = z
37
39
  index: z.number().int().nonnegative(),
38
40
  left_at_ms: z.number().int().nonnegative().nullable(),
39
41
  right_at_ms: z.number().int().nonnegative().nullable(),
40
- left: z.unknown().nullable(),
41
- right: z.unknown().nullable(),
42
+ left: jsonValueSchema.nullable(),
43
+ right: jsonValueSchema.nullable(),
42
44
  }),
43
45
  ]),
44
46
  limitations: z.array(z.string()),
@@ -127,8 +129,8 @@ const firstCollectionDivergence = (dimension, left, right) => {
127
129
  index,
128
130
  left_at_ms: eventTime(leftValue),
129
131
  right_at_ms: eventTime(rightValue),
130
- left: leftValue,
131
- right: rightValue,
132
+ left: jsonValueSchema.parse(leftValue),
133
+ right: jsonValueSchema.parse(rightValue),
132
134
  };
133
135
  }
134
136
  return null;
@@ -261,7 +261,7 @@ const authorityLimitations = (claim, sources, sides) => {
261
261
  const classify = (observed, limitations, unknowns) => {
262
262
  if (limitations.length > 0)
263
263
  return "unknown";
264
- if (["changed", "added", "removed"].includes(observed))
264
+ if (["changed", "added", "removed", "contradiction"].includes(observed))
265
265
  return "fail";
266
266
  if (observed === "unknown" || observed === "truncated" || unknowns.length > 0)
267
267
  return "unknown";
@@ -86,6 +86,7 @@ export const reconstructionClaimResultSchema = z.object({
86
86
  "changed",
87
87
  "truncated",
88
88
  "unknown",
89
+ "contradiction",
89
90
  ]),
90
91
  comparison_evidence_id: evidenceIdSchema,
91
92
  left_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
@@ -260,7 +260,11 @@ const selectStatic = (evidence, selector) => {
260
260
  const status = matches[0].classification;
261
261
  return {
262
262
  status,
263
- conclusion_kind: status === "unknown" ? "unresolved_branch" : "derived_relationship",
263
+ conclusion_kind: status === "contradiction"
264
+ ? "contradiction"
265
+ : status === "unknown"
266
+ ? "unresolved_branch"
267
+ : "derived_relationship",
264
268
  };
265
269
  };
266
270
  const parseStatic = (input) => {
@@ -0,0 +1,9 @@
1
+ /** Generated from package.json and package-lock.json; do not edit. */
2
+ export const PACKAGE_METADATA = {
3
+ name: "rea-agents",
4
+ version: "1.4.0",
5
+ serverSdkVersion: "2.0.0-beta.4",
6
+ clientSdkVersion: "2.0.0-beta.4",
7
+ coreSdkVersion: "2.0.0-beta.4",
8
+ skillVersion: "12",
9
+ };