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.
- package/README.md +95 -27
- package/bridge/hopper_bridge.py +172 -14
- package/dist/application/AnalysisSnapshotCache.js +131 -0
- package/dist/application/AnalysisSnapshotFiles.js +31 -0
- package/dist/application/ArtifactGraphConstruction.js +1 -1
- package/dist/application/ArtifactInventory.js +100 -24
- package/dist/application/AuthorizedArtifactInventory.js +16 -0
- package/dist/application/BinarySession.js +149 -36
- package/dist/application/BinarySessionPort.js +1 -0
- package/dist/application/BoundedJsonFiles.js +109 -0
- package/dist/application/CapabilityInventory.js +127 -0
- package/dist/application/ClientRegistrationStatus.js +69 -0
- package/dist/application/CrossVersionInventory.js +109 -0
- package/dist/application/CrossVersionInvestigation.js +358 -0
- package/dist/application/DirectAnalysis.js +205 -24
- package/dist/application/Doctor.js +104 -3
- package/dist/application/EvidenceBundleFiles.js +15 -108
- package/dist/application/EvidenceLedger.js +3 -2
- package/dist/application/InvestigationProviders.js +48 -0
- package/dist/application/InvestigationWorkspaceStore.js +214 -0
- package/dist/application/LinuxHopper.js +72 -48
- package/dist/application/PermissionAuthority.js +183 -0
- package/dist/application/PermissionConfiguration.js +26 -0
- package/dist/application/ProcessCaptureAuthority.js +2 -2
- package/dist/application/ProcessCaptureError.js +25 -0
- package/dist/application/ProcessCaptureLifecycle.js +22 -2
- package/dist/application/ProcessCli.js +111 -31
- package/dist/application/ProcessHarness.js +6 -3
- package/dist/application/ProgressReporter.js +30 -0
- package/dist/application/ProjectPermissionStore.js +110 -0
- package/dist/application/ReferenceSourceImportEntries.js +18 -3
- package/dist/application/ReferenceSourceImportTypes.js +32 -0
- package/dist/application/Setup.js +35 -101
- package/dist/application/SetupClients.js +19 -0
- package/dist/application/SetupInstallFailure.js +53 -0
- package/dist/application/SetupPlan.js +31 -0
- package/dist/application/SupportedClients.js +41 -0
- package/dist/application/Uninstall.js +34 -24
- package/dist/application/UnknownEvidence.js +33 -0
- package/dist/application/Upgrade.js +3 -1
- package/dist/application/runtime.js +2 -2
- package/dist/artifacts/ArtifactProvider.js +15 -8
- package/dist/catalogIdentity.js +121 -0
- package/dist/cli.js +76 -13
- package/dist/cliEvidenceCommands.js +52 -12
- package/dist/cliInvestigationCommands.js +137 -0
- package/dist/cliLogging.js +20 -3
- package/dist/cliOutput.js +41 -0
- package/dist/cliPolicyCommands.js +115 -0
- package/dist/config.js +102 -27
- package/dist/contracts/artifactToolContracts.js +14 -1
- package/dist/contracts/errorSchemas.js +98 -0
- package/dist/contracts/promptContracts.js +256 -0
- package/dist/contracts/sessionLifecycleInputs.js +11 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +20 -7
- package/dist/contracts/toolOutputSchemas.js +70 -9
- package/dist/domain/analysisSnapshot.js +149 -0
- package/dist/domain/artifactComparison.js +38 -11
- package/dist/domain/artifactGraph.js +25 -1
- package/dist/domain/artifactInventoryEvidence.js +18 -0
- package/dist/domain/changedBehavior.js +35 -10
- package/dist/domain/errors.js +384 -32
- package/dist/domain/evidence.js +4 -2
- package/dist/domain/evidenceBundle.js +28 -0
- package/dist/domain/hopperStartupFailure.js +55 -0
- package/dist/domain/investigationWorkspace.js +341 -0
- package/dist/domain/jsonValue.js +2 -0
- package/dist/domain/nativeInspection.js +3 -2
- package/dist/domain/permissionPolicy.js +157 -0
- package/dist/domain/processCapture.js +5 -4
- package/dist/domain/processComparison.js +6 -4
- package/dist/domain/reconstructionVerification.js +1 -1
- package/dist/domain/reconstructionVerificationSchemas.js +1 -0
- package/dist/domain/staticRuntimeCorrelation.js +5 -1
- package/dist/generatedPackageMetadata.js +9 -0
- package/dist/hopper/BridgeLauncher.js +51 -20
- package/dist/hopper/HopperClient.js +45 -3
- package/dist/hopper/HopperProvider.js +1 -0
- package/dist/identity.js +10 -3
- package/dist/logger.js +2 -2
- package/dist/main.js +139 -28
- package/dist/server/createServer.js +54 -5
- package/dist/server/mcpProgress.js +23 -0
- package/dist/server/promptCompletion.js +144 -0
- package/dist/server/registerArtifactComparisonTool.js +6 -2
- package/dist/server/registerBundleComparisonTool.js +6 -2
- package/dist/server/registerEnhancedTools.js +20 -7
- package/dist/server/registerEvidenceResources.js +302 -0
- package/dist/server/registerEvidenceTools.js +59 -8
- package/dist/server/registerFunctionComparisonTool.js +7 -3
- package/dist/server/registerInvestigationTools.js +111 -18
- package/dist/server/registerOfficialTools.js +25 -14
- package/dist/server/registerProcessComparisonTool.js +8 -10
- package/dist/server/registerPrompts.js +67 -0
- package/dist/server/registerSessionStatusTool.js +52 -0
- package/dist/server/registerSessionTools.js +178 -22
- package/dist/server/runDerivedOperation.js +35 -0
- package/dist/server/sessionEvidence.js +2 -8
- package/dist/server/sessionToolPolicies.js +1 -42
- package/dist/server/toolResult.js +34 -6
- package/dist/serverIdentity.js +70 -0
- package/install.sh +11 -11
- package/package.json +10 -6
- package/scripts/hopper-demo-x11.py +349 -0
- package/scripts/rea.mjs +6 -3
- 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
|
+
};
|
package/dist/domain/jsonValue.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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(),
|
|
35
|
-
comparison_contract: z.record(z.string(),
|
|
36
|
-
shim_plan: z.array(
|
|
37
|
-
replay_plan: z.record(z.string(),
|
|
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:
|
|
41
|
-
right:
|
|
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";
|
|
@@ -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 === "
|
|
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
|
+
};
|