rea-agents 2.3.0 → 2.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 +2 -2
- package/dist/application/ArtifactExtraction.js +5 -1
- package/dist/application/ArtifactGraphConstruction.js +3 -15
- package/dist/application/ArtifactInventory/classify.js +5 -1
- package/dist/application/ArtifactInventory/reader.js +2 -0
- package/dist/application/BinarySessionStatus.js +1 -0
- package/dist/application/CapabilityInventory.js +11 -3
- package/dist/application/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureLifecycle.js +1 -0
- package/dist/application/ProcessCheckpoints.js +5 -0
- package/dist/application/ProcessHarness.js +45 -34
- package/dist/application/ProcessNormalization.js +6 -0
- package/dist/application/ProcessReactiveCoordinator.js +10 -3
- package/dist/application/ProcessReactiveEffects.js +9 -1
- package/dist/application/ProcessReactiveHarness.js +85 -0
- package/dist/application/ProcessReactiveObservations.js +101 -16
- package/dist/contracts/toolOutputSchemaPrimitives.js +3 -0
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/binaryTarget.js +3 -6
- package/dist/domain/evidence.js +2 -0
- package/dist/domain/processCapture.js +64 -0
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +2 -0
- package/dist/domain/processObservation.js +15 -6
- package/dist/domain/processReactiveRuntime.js +21 -10
- package/dist/domain/processReactiveTransition.js +8 -6
- package/dist/domain/processScenario.js +21 -0
- package/dist/domain/zipPackageFormat.js +20 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/native/NativeMacOSProvider.js +1 -0
- package/package.json +1 -1
- package/skills/reverse-engineer-anything/SKILL.md +3 -2
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -27,6 +27,8 @@ export const targetFormatSchema = z.enum([
|
|
|
27
27
|
"zip",
|
|
28
28
|
"ipa",
|
|
29
29
|
"apk",
|
|
30
|
+
"msix",
|
|
31
|
+
"appx",
|
|
30
32
|
"asar",
|
|
31
33
|
"dmg",
|
|
32
34
|
"pkg",
|
|
@@ -44,6 +46,7 @@ const providerCapability = z.object({
|
|
|
44
46
|
operation: z.string(),
|
|
45
47
|
available: z.boolean(),
|
|
46
48
|
reason: z.string().nullable(),
|
|
49
|
+
availability_code: z.enum(PROVIDER_REJECTION_CODES).nullable(),
|
|
47
50
|
input_contract_version: z.number().int().min(1),
|
|
48
51
|
output_contract_version: z.number().int().min(1),
|
|
49
52
|
pagination: z.enum(["none", "offset", "cursor"]),
|
|
@@ -6,6 +6,7 @@ import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { BinaryTargetError } from "./errors.js";
|
|
8
8
|
import { err, ok } from "./result.js";
|
|
9
|
+
import { zipPackageFormatForPath } from "./zipPackageFormat.js";
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
/**
|
|
11
12
|
* Resolve and classify a readable local target before a provider is selected.
|
|
@@ -71,11 +72,7 @@ const detectArtifactFormat = async (path, handle) => {
|
|
|
71
72
|
magic[1] === 0x4b &&
|
|
72
73
|
[0x03, 0x05, 0x07].includes(magic[2] ?? -1) &&
|
|
73
74
|
[0x04, 0x06, 0x08].includes(magic[3] ?? -1)) {
|
|
74
|
-
return lower
|
|
75
|
-
? "ipa"
|
|
76
|
-
: lower.endsWith(".apk")
|
|
77
|
-
? "apk"
|
|
78
|
-
: "zip";
|
|
75
|
+
return zipPackageFormatForPath(lower) ?? "zip";
|
|
79
76
|
}
|
|
80
77
|
const named = namedArtifactFormat(lower);
|
|
81
78
|
if (named !== undefined)
|
|
@@ -104,7 +101,7 @@ const namedArtifactFormat = (lowerPath) => {
|
|
|
104
101
|
return "source-map";
|
|
105
102
|
return /\.(?:m?js|cjs)$/u.test(lowerPath) ? "javascript" : undefined;
|
|
106
103
|
};
|
|
107
|
-
const isArchiveFormat = (format) => ["zip", "ipa", "apk", "asar", "dmg", "pkg"].includes(format);
|
|
104
|
+
const isArchiveFormat = (format) => ["zip", "ipa", "apk", "msix", "appx", "asar", "dmg", "pkg"].includes(format);
|
|
108
105
|
const sha256Handle = async (handle) => {
|
|
109
106
|
const hash = createHash("sha256");
|
|
110
107
|
const chunk = Buffer.allocUnsafe(64 * 1024);
|
package/dist/domain/evidence.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonValueSchema } from "./jsonValue.js";
|
|
3
3
|
import { LEGACY_PROCESS_CAPTURE_MESSAGE, normalizationSchema, } from "./processScenario.js";
|
|
4
|
+
import { PROCESS_REACTIVE_LIMITS } from "./processReactiveScenario.js";
|
|
4
5
|
export * from "./processScenario.js";
|
|
5
6
|
/** Capture collection whose members participate in global observation order. */
|
|
6
7
|
export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [
|
|
@@ -145,6 +146,69 @@ export const processCaptureSchema = z.object({
|
|
|
145
146
|
sensitive_aliases: z.array(z.string().min(1).max(64)).max(32),
|
|
146
147
|
}))
|
|
147
148
|
.default([]),
|
|
149
|
+
reactive_run: z
|
|
150
|
+
.object({
|
|
151
|
+
status: z.enum(["running", "finished"]),
|
|
152
|
+
outcome: z
|
|
153
|
+
.enum([
|
|
154
|
+
"passed",
|
|
155
|
+
"predicate_timeout",
|
|
156
|
+
"scenario_deadline",
|
|
157
|
+
"ambiguous_match",
|
|
158
|
+
"action_rejected",
|
|
159
|
+
"target_lost",
|
|
160
|
+
"capture_incomplete",
|
|
161
|
+
"cancelled",
|
|
162
|
+
"cleanup_failed",
|
|
163
|
+
])
|
|
164
|
+
.nullable(),
|
|
165
|
+
active_state: z.string().min(1).max(64),
|
|
166
|
+
controls: z
|
|
167
|
+
.array(z.object({
|
|
168
|
+
sequence: z.number().int().nonnegative(),
|
|
169
|
+
kind: z.enum([
|
|
170
|
+
"state_deadline",
|
|
171
|
+
"scenario_deadline",
|
|
172
|
+
"target_lost",
|
|
173
|
+
"cancelled",
|
|
174
|
+
"cleanup_failed",
|
|
175
|
+
]),
|
|
176
|
+
after_capture_order: z.number().int().min(-1),
|
|
177
|
+
}))
|
|
178
|
+
.default([]),
|
|
179
|
+
transitions: z
|
|
180
|
+
.array(z.object({
|
|
181
|
+
sequence: z.number().int().nonnegative(),
|
|
182
|
+
transition_id: z.string().min(1).max(64),
|
|
183
|
+
state_before: z.string().min(1).max(64),
|
|
184
|
+
state_after: z.string().min(1).max(64).nullable(),
|
|
185
|
+
outcome: z
|
|
186
|
+
.enum([
|
|
187
|
+
"passed",
|
|
188
|
+
"predicate_timeout",
|
|
189
|
+
"scenario_deadline",
|
|
190
|
+
"ambiguous_match",
|
|
191
|
+
"action_rejected",
|
|
192
|
+
"target_lost",
|
|
193
|
+
"capture_incomplete",
|
|
194
|
+
"cancelled",
|
|
195
|
+
"cleanup_failed",
|
|
196
|
+
])
|
|
197
|
+
.nullable(),
|
|
198
|
+
trigger_event_ids: z
|
|
199
|
+
.array(z.string().min(1))
|
|
200
|
+
.max(PROCESS_REACTIVE_LIMITS.predicates),
|
|
201
|
+
action_event_ids: z
|
|
202
|
+
.array(z.string().min(1))
|
|
203
|
+
.max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
|
|
204
|
+
action_types: z
|
|
205
|
+
.array(z.enum(["send_input", "resize", "send_signal", "checkpoint"]))
|
|
206
|
+
.max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
|
|
207
|
+
}))
|
|
208
|
+
.max(PROCESS_REACTIVE_LIMITS.transitions),
|
|
209
|
+
})
|
|
210
|
+
.nullable()
|
|
211
|
+
.default(null),
|
|
148
212
|
event_journal: z
|
|
149
213
|
.array(z.object({
|
|
150
214
|
capture_order: z.number().int().nonnegative(),
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { projectProcessObservation, } from "./processObservation.js";
|
|
3
|
+
import { commitProcessReactiveProposal, createProcessReactiveSnapshot, reduceProcessReactiveScenario, } from "./processReactiveRuntime.js";
|
|
4
|
+
import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
|
|
5
|
+
const admittedCollection = (collection) => [
|
|
6
|
+
"frames",
|
|
7
|
+
"interaction_events",
|
|
8
|
+
"process_samples",
|
|
9
|
+
"filesystem_checkpoints",
|
|
10
|
+
"shim_events",
|
|
11
|
+
"protocol_events",
|
|
12
|
+
].includes(collection);
|
|
13
|
+
const reactiveObservations = (capture) => (capture.event_journal ?? []).flatMap((location) => {
|
|
14
|
+
if (!admittedCollection(location.collection))
|
|
15
|
+
return [];
|
|
16
|
+
const observation = projectProcessObservation(capture, location);
|
|
17
|
+
return observation === null ? [] : [observation];
|
|
18
|
+
});
|
|
19
|
+
const validateTransitionDeclaration = (transition, next, context) => {
|
|
20
|
+
const { index, scenario, require } = context;
|
|
21
|
+
const path = `reactive_run.transitions[${String(index)}]`;
|
|
22
|
+
const declared = scenario.states
|
|
23
|
+
.find(({ id }) => id === transition.state_before)
|
|
24
|
+
?.on.find(({ id }) => id === transition.transition_id);
|
|
25
|
+
require(declared !==
|
|
26
|
+
undefined, path, "reactive transition is not declared by its recorded source state");
|
|
27
|
+
if (declared !== undefined) {
|
|
28
|
+
const stateAfter = declared.target.kind === "goto" ? declared.target.state : null;
|
|
29
|
+
const outcome = declared.target.kind === "finish" ? declared.target.outcome : null;
|
|
30
|
+
require(transition.state_after === stateAfter &&
|
|
31
|
+
transition.outcome ===
|
|
32
|
+
outcome, path, "reactive transition target or outcome differs from the committed graph");
|
|
33
|
+
require(canonicalize(transition.action_types) ===
|
|
34
|
+
canonicalize(declared.actions.map(({ type }) => type)), `${path}.action_types`, "reactive transition actions differ from the committed graph");
|
|
35
|
+
}
|
|
36
|
+
require(transition.sequence ===
|
|
37
|
+
index, `${path}.sequence`, "reactive transition sequence must be contiguous from zero");
|
|
38
|
+
if (next !== undefined)
|
|
39
|
+
require(transition.state_after ===
|
|
40
|
+
next.state_before, `reactive_run.transitions[${String(index + 1)}].state_before`, "reactive transition states must be contiguous");
|
|
41
|
+
};
|
|
42
|
+
const validateTransitionDeclarations = (run, scenario, require) => {
|
|
43
|
+
require(run.transitions[0]?.state_before === scenario.initial_state ||
|
|
44
|
+
run.transitions.length ===
|
|
45
|
+
0, "reactive_run.transitions[0].state_before", "reactive transition journal must start at the declared initial state");
|
|
46
|
+
for (const [index, transition] of run.transitions.entries())
|
|
47
|
+
validateTransitionDeclaration(transition, run.transitions[index + 1], {
|
|
48
|
+
index,
|
|
49
|
+
scenario,
|
|
50
|
+
require,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const commitRecordedEffects = (options) => commitProcessReactiveProposal(options.scenario, options.proposal, options.recordedFailure === undefined
|
|
54
|
+
? options.proposal.effects.map((_, index) => {
|
|
55
|
+
const eventId = options.expected?.action_event_ids[index];
|
|
56
|
+
const observation = eventId === undefined
|
|
57
|
+
? undefined
|
|
58
|
+
: options.observations.get(eventId);
|
|
59
|
+
return observation === undefined
|
|
60
|
+
? { status: "rejected" }
|
|
61
|
+
: { status: "succeeded", observation };
|
|
62
|
+
})
|
|
63
|
+
: [
|
|
64
|
+
options.recordedFailure === "target_lost"
|
|
65
|
+
? { status: "target_lost" }
|
|
66
|
+
: { status: "rejected" },
|
|
67
|
+
], { committedSensitiveInputs: true });
|
|
68
|
+
const replayTransitions = (scenario, run, observations, require) => {
|
|
69
|
+
const observationsById = new Map(observations.map((observation) => [observation.event_id, observation]));
|
|
70
|
+
let snapshot = createProcessReactiveSnapshot(scenario);
|
|
71
|
+
let transitionIndex = 0;
|
|
72
|
+
const advance = (input) => {
|
|
73
|
+
let decision = reduceProcessReactiveScenario(scenario, snapshot, input);
|
|
74
|
+
if (decision.kind === "proposal") {
|
|
75
|
+
const proposal = decision;
|
|
76
|
+
decision = commitRecordedEffects({
|
|
77
|
+
scenario,
|
|
78
|
+
proposal: decision,
|
|
79
|
+
expected: run.transitions[transitionIndex],
|
|
80
|
+
observations: observationsById,
|
|
81
|
+
recordedFailure: run.transitions[transitionIndex] === undefined &&
|
|
82
|
+
decision.effects.length > 0 &&
|
|
83
|
+
(run.outcome === "action_rejected" || run.outcome === "target_lost")
|
|
84
|
+
? run.outcome
|
|
85
|
+
: undefined,
|
|
86
|
+
});
|
|
87
|
+
require(decision.kind !== "finished" ||
|
|
88
|
+
decision.outcome !== "action_rejected" ||
|
|
89
|
+
run.outcome ===
|
|
90
|
+
"action_rejected", `reactive_run.transitions[${String(transitionIndex)}].action_event_ids`, `recorded action observations do not match proposed effects: ${canonicalize({
|
|
91
|
+
effects: proposal.effects,
|
|
92
|
+
observations: run.transitions[transitionIndex]?.action_event_ids.map((eventId) => observationsById.get(eventId)),
|
|
93
|
+
})}`);
|
|
94
|
+
}
|
|
95
|
+
if (decision.kind === "transition") {
|
|
96
|
+
const expected = run.transitions[transitionIndex];
|
|
97
|
+
require(expected !== undefined &&
|
|
98
|
+
canonicalize(decision.record) ===
|
|
99
|
+
canonicalize(expected), `reactive_run.transitions[${String(transitionIndex)}]`, "reactive transition differs from deterministic journal replay");
|
|
100
|
+
transitionIndex += 1;
|
|
101
|
+
}
|
|
102
|
+
snapshot = decision.snapshot;
|
|
103
|
+
};
|
|
104
|
+
const advanceControl = (control, index) => {
|
|
105
|
+
require(snapshot.status !== "finished" ||
|
|
106
|
+
control.kind ===
|
|
107
|
+
"cleanup_failed", `reactive_run.controls[${String(index)}]`, "reactive control cannot follow a finished run");
|
|
108
|
+
advance(control.kind === "state_deadline"
|
|
109
|
+
? {
|
|
110
|
+
kind: control.kind,
|
|
111
|
+
state_id: snapshot.active_state,
|
|
112
|
+
state_entry_capture_order: snapshot.state_entry_capture_order,
|
|
113
|
+
}
|
|
114
|
+
: { kind: control.kind });
|
|
115
|
+
};
|
|
116
|
+
let controlIndex = 0;
|
|
117
|
+
for (const observation of observations) {
|
|
118
|
+
while ((run.controls[controlIndex]?.after_capture_order ??
|
|
119
|
+
Number.MAX_SAFE_INTEGER) < observation.capture_order) {
|
|
120
|
+
const control = run.controls[controlIndex];
|
|
121
|
+
advanceControl(control, controlIndex);
|
|
122
|
+
controlIndex += 1;
|
|
123
|
+
}
|
|
124
|
+
advance({ kind: "observation", observation });
|
|
125
|
+
}
|
|
126
|
+
for (; controlIndex < run.controls.length; controlIndex += 1) {
|
|
127
|
+
const control = run.controls[controlIndex];
|
|
128
|
+
advanceControl(control, controlIndex);
|
|
129
|
+
}
|
|
130
|
+
require(transitionIndex ===
|
|
131
|
+
run.transitions
|
|
132
|
+
.length, "reactive_run.transitions", `reactive transition journal is not reproduced by deterministic replay (replayed ${String(transitionIndex)} of ${String(run.transitions.length)}; outcome ${snapshot.outcome})`);
|
|
133
|
+
return snapshot;
|
|
134
|
+
};
|
|
135
|
+
const validateControls = (run, observations, require) => {
|
|
136
|
+
const admittedOrders = new Set([
|
|
137
|
+
-1,
|
|
138
|
+
...observations.map(({ capture_order }) => capture_order),
|
|
139
|
+
]);
|
|
140
|
+
for (const [index, control] of run.controls.entries()) {
|
|
141
|
+
require(control.sequence ===
|
|
142
|
+
index, `reactive_run.controls[${String(index)}].sequence`, "reactive control sequence must be contiguous");
|
|
143
|
+
require(admittedOrders.has(control.after_capture_order), `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoff must identify the last admitted observation");
|
|
144
|
+
require(index === 0 ||
|
|
145
|
+
control.after_capture_order >=
|
|
146
|
+
run.controls[index - 1]
|
|
147
|
+
.after_capture_order, `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoffs must be ordered");
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/** Validate one capture's reactive journal by replaying the production reducer. */
|
|
151
|
+
export const validateProcessCaptureReactiveRun = (capture, require) => {
|
|
152
|
+
const declared = capture.manifest.scenario["reactive"];
|
|
153
|
+
const run = capture.reactive_run;
|
|
154
|
+
const declaredReactive = declared !== undefined && declared !== null;
|
|
155
|
+
require(declaredReactive ===
|
|
156
|
+
(run !==
|
|
157
|
+
null), "reactive_run", "reactive result presence must match the committed scenario");
|
|
158
|
+
if (run === null)
|
|
159
|
+
return;
|
|
160
|
+
const parsed = processReactiveScenarioSchema.safeParse(declared);
|
|
161
|
+
require(parsed.success, "manifest.scenario.reactive", "committed reactive scenario does not satisfy its declared schema");
|
|
162
|
+
if (!parsed.success)
|
|
163
|
+
return;
|
|
164
|
+
require(run.status === "finished" &&
|
|
165
|
+
run.outcome !==
|
|
166
|
+
null, "reactive_run.outcome", "completed capture requires a finished reactive outcome");
|
|
167
|
+
validateTransitionDeclarations(run, parsed.data, require);
|
|
168
|
+
const observations = reactiveObservations(capture);
|
|
169
|
+
validateControls(run, observations, require);
|
|
170
|
+
const snapshot = replayTransitions(parsed.data, run, observations, require);
|
|
171
|
+
require(snapshot.outcome ===
|
|
172
|
+
run.outcome, "reactive_run.outcome", "reactive outcome differs from deterministic journal replay");
|
|
173
|
+
require(snapshot.active_state ===
|
|
174
|
+
run.active_state, "reactive_run.active_state", "reactive active state differs from deterministic journal replay");
|
|
175
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateProcessCaptureReactiveRun } from "./processCaptureReactiveValidation.js";
|
|
1
2
|
import { replayMachineSchema } from "./replayMachine.js";
|
|
2
3
|
import { digestProcessCommitment } from "./processScenario.js";
|
|
3
4
|
const orderedTimestamps = (values) => values.every((value, index) => index === 0 || value.at_ms >= values[index - 1].at_ms);
|
|
@@ -209,5 +210,6 @@ export const validateProcessCapture = (capture) => {
|
|
|
209
210
|
validateLifecycle(capture, require);
|
|
210
211
|
validateShimEvents(capture, require);
|
|
211
212
|
validateReplayEvents(capture, require);
|
|
213
|
+
validateProcessCaptureReactiveRun(capture, require);
|
|
212
214
|
return issues;
|
|
213
215
|
};
|
|
@@ -60,13 +60,22 @@ const atMilliseconds = (value) => {
|
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
};
|
|
63
|
-
|
|
63
|
+
/** Derive the stable capture-local identity for one normalized observation. */
|
|
64
|
+
export const processObservationSubject = (source, value) => {
|
|
64
65
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
65
66
|
return null;
|
|
66
|
-
if (source === "process" && typeof value
|
|
67
|
-
return `process:${String(value
|
|
68
|
-
if (source === "filesystem" &&
|
|
69
|
-
|
|
67
|
+
if (source === "process" && "pid" in value && typeof value.pid === "number")
|
|
68
|
+
return `process:${String(value.pid)}`;
|
|
69
|
+
if (source === "filesystem" &&
|
|
70
|
+
"name" in value &&
|
|
71
|
+
typeof value.name === "string")
|
|
72
|
+
return `checkpoint:${value.name}`;
|
|
73
|
+
if (source === "shim" &&
|
|
74
|
+
"command" in value &&
|
|
75
|
+
typeof value.command === "string")
|
|
76
|
+
return "route_index" in value && typeof value.route_index === "number"
|
|
77
|
+
? `shim:${value.command}:route:${String(value.route_index)}`
|
|
78
|
+
: `shim:${value.command}:unmatched`;
|
|
70
79
|
return null;
|
|
71
80
|
};
|
|
72
81
|
const projectedRecord = (capture, location) => {
|
|
@@ -133,7 +142,7 @@ const projectedRecord = (capture, location) => {
|
|
|
133
142
|
return {
|
|
134
143
|
source: projected.source,
|
|
135
144
|
captured_at_ms: atMilliseconds(payload),
|
|
136
|
-
subject_id:
|
|
145
|
+
subject_id: processObservationSubject(projected.source, payload),
|
|
137
146
|
payload,
|
|
138
147
|
};
|
|
139
148
|
};
|
|
@@ -2,6 +2,22 @@ import { matchProcessReactiveTrigger } from "./processReactiveMatching.js";
|
|
|
2
2
|
import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
|
|
3
3
|
export { commitProcessReactiveProposal } from "./processReactiveTransition.js";
|
|
4
4
|
const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
|
|
5
|
+
const observeInput = (snapshot, observation) => {
|
|
6
|
+
const duplicate = snapshot.observations.find(({ event_id }) => event_id === observation.event_id);
|
|
7
|
+
if (duplicate !== undefined)
|
|
8
|
+
return { kind: "invalid" };
|
|
9
|
+
const prior = snapshot.observations.at(-1);
|
|
10
|
+
if ((prior !== undefined && observation.capture_order <= prior.capture_order) ||
|
|
11
|
+
snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
|
|
12
|
+
return { kind: "invalid" };
|
|
13
|
+
return {
|
|
14
|
+
kind: "new",
|
|
15
|
+
snapshot: {
|
|
16
|
+
...snapshot,
|
|
17
|
+
observations: [...snapshot.observations, observation],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
5
21
|
/** Create the initial immutable reducer state for one validated scenario. */
|
|
6
22
|
export const createProcessReactiveSnapshot = (scenario) => ({
|
|
7
23
|
status: "running",
|
|
@@ -35,6 +51,8 @@ const reduceControlInput = (snapshot, input) => {
|
|
|
35
51
|
: { kind: "waiting", snapshot, evaluations: [] };
|
|
36
52
|
if (input.kind === "scenario_deadline")
|
|
37
53
|
return finishedDecision(snapshot, "scenario_deadline");
|
|
54
|
+
if (input.kind === "target_lost")
|
|
55
|
+
return finishedDecision(snapshot, "target_lost");
|
|
38
56
|
return finishedDecision(snapshot, "cancelled");
|
|
39
57
|
};
|
|
40
58
|
/** Advance one validated scenario using only an observation or explicit deadline. */
|
|
@@ -44,17 +62,10 @@ export const reduceProcessReactiveScenario = (scenario, snapshot, input) => {
|
|
|
44
62
|
return controlDecision;
|
|
45
63
|
if (input.kind !== "observation")
|
|
46
64
|
return finishedDecision(snapshot, "capture_incomplete");
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
input.observation.capture_order <= prior.capture_order) ||
|
|
50
|
-
snapshot.observations.some(({ event_id }) => event_id === input.observation.event_id))
|
|
51
|
-
return finishedDecision(snapshot, "capture_incomplete");
|
|
52
|
-
if (snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
|
|
65
|
+
const admitted = observeInput(snapshot, input.observation);
|
|
66
|
+
if (admitted.kind === "invalid")
|
|
53
67
|
return finishedDecision(snapshot, "capture_incomplete");
|
|
54
|
-
const observed =
|
|
55
|
-
...snapshot,
|
|
56
|
-
observations: [...snapshot.observations, input.observation],
|
|
57
|
-
};
|
|
68
|
+
const observed = admitted.snapshot;
|
|
58
69
|
const state = scenario.states.find(({ id }) => id === observed.active_state);
|
|
59
70
|
if (state === undefined)
|
|
60
71
|
return finishedDecision(observed, "target_lost");
|
|
@@ -75,7 +75,7 @@ const applyProcessReactiveTransition = (input) => {
|
|
|
75
75
|
record,
|
|
76
76
|
};
|
|
77
77
|
};
|
|
78
|
-
const effectObservationMatchesAction = (action, observation) => {
|
|
78
|
+
const effectObservationMatchesAction = (action, observation, committedSensitiveInputs) => {
|
|
79
79
|
const payload = typeof observation.payload === "object" &&
|
|
80
80
|
observation.payload !== null &&
|
|
81
81
|
!Array.isArray(observation.payload)
|
|
@@ -98,14 +98,16 @@ const effectObservationMatchesAction = (action, observation) => {
|
|
|
98
98
|
if (action.type === "send_input")
|
|
99
99
|
return (payload["type"] === "input" &&
|
|
100
100
|
payload["data"] ===
|
|
101
|
-
(action.sensitive
|
|
102
|
-
? `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`
|
|
103
|
-
: action.data));
|
|
101
|
+
expectedInputEvidence(action.data, action.sensitive, committedSensitiveInputs));
|
|
104
102
|
if (action.type === "resize")
|
|
105
103
|
return (payload["type"] === "resize" &&
|
|
106
104
|
payload["data"] === `${String(action.columns)}x${String(action.rows)}`);
|
|
107
105
|
return payload["type"] === "signal" && payload["data"] === action.signal;
|
|
108
106
|
};
|
|
107
|
+
const expectedInputEvidence = (data, sensitive, committedSensitiveInput) => !sensitive ||
|
|
108
|
+
(committedSensitiveInput && /^<redacted-input:\d+-bytes>$/.test(data))
|
|
109
|
+
? data
|
|
110
|
+
: `<redacted-input:${String(Buffer.byteLength(data))}-bytes>`;
|
|
109
111
|
const failedProposal = (proposed, outcome) => ({
|
|
110
112
|
kind: "finished",
|
|
111
113
|
outcome,
|
|
@@ -113,7 +115,7 @@ const failedProposal = (proposed, outcome) => ({
|
|
|
113
115
|
snapshot: { ...proposed.snapshot, status: "finished", outcome },
|
|
114
116
|
});
|
|
115
117
|
/** Commit a proposal only after the host reports every effect as successful. */
|
|
116
|
-
export const commitProcessReactiveProposal = (scenario, proposed, results) => {
|
|
118
|
+
export const commitProcessReactiveProposal = (scenario, proposed, results, options = {}) => {
|
|
117
119
|
if (results.some(({ status }) => status === "target_lost"))
|
|
118
120
|
return failedProposal(proposed, "target_lost");
|
|
119
121
|
const state = scenario.states.find(({ id }) => id === proposed.snapshot.active_state);
|
|
@@ -137,7 +139,7 @@ export const commitProcessReactiveProposal = (scenario, proposed, results) => {
|
|
|
137
139
|
const action = transition.actions[index];
|
|
138
140
|
const effectObservation = result.observation;
|
|
139
141
|
if (action === undefined ||
|
|
140
|
-
!effectObservationMatchesAction(action, effectObservation) ||
|
|
142
|
+
!effectObservationMatchesAction(action, effectObservation, options.committedSensitiveInputs ?? false) ||
|
|
141
143
|
!Number.isSafeInteger(effectObservation.capture_order) ||
|
|
142
144
|
effectObservation.capture_order <= previousOrder ||
|
|
143
145
|
knownEventIds.has(effectObservation.event_id))
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
|
|
5
6
|
import { replayMachineSchema } from "./replayMachine.js";
|
|
6
7
|
const positiveBudget = z.number().int().positive();
|
|
7
8
|
const timedEventBase = { at_ms: z.number().int().nonnegative() };
|
|
@@ -44,6 +45,23 @@ export const digestProcessCommitment = (value) => {
|
|
|
44
45
|
throw new TypeError("Process capture commitment is not canonical JSON");
|
|
45
46
|
return createHash("sha256").update(serialized).digest("hex");
|
|
46
47
|
};
|
|
48
|
+
const reactiveScenarioCommitment = (scenario) => scenario === null
|
|
49
|
+
? null
|
|
50
|
+
: {
|
|
51
|
+
...scenario,
|
|
52
|
+
states: scenario.states.map((state) => ({
|
|
53
|
+
...state,
|
|
54
|
+
on: state.on.map((transition) => ({
|
|
55
|
+
...transition,
|
|
56
|
+
actions: transition.actions.map((action) => action.type === "send_input" && action.sensitive
|
|
57
|
+
? {
|
|
58
|
+
...action,
|
|
59
|
+
data: `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`,
|
|
60
|
+
}
|
|
61
|
+
: action),
|
|
62
|
+
})),
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
47
65
|
/** Build the secret-safe scenario projection committed by Process Capture v4. */
|
|
48
66
|
export const processScenarioCommitment = (scenario, executableSha256) => ({
|
|
49
67
|
...scenario,
|
|
@@ -51,6 +69,7 @@ export const processScenarioCommitment = (scenario, executableSha256) => ({
|
|
|
51
69
|
name,
|
|
52
70
|
scenario.secret_aliases.includes(name) ? "<redacted-secret>" : value,
|
|
53
71
|
])),
|
|
72
|
+
reactive: reactiveScenarioCommitment(scenario.reactive),
|
|
54
73
|
unknown_registry_approved: scenario.unknown_registry_approved === true,
|
|
55
74
|
executable_sha256: executableSha256 ?? null,
|
|
56
75
|
});
|
|
@@ -71,6 +90,7 @@ export const processComparisonContract = (scenario) => ({
|
|
|
71
90
|
normalization: scenario.normalization,
|
|
72
91
|
command_shims: scenario.command_shims,
|
|
73
92
|
replay: scenario.replay,
|
|
93
|
+
reactive: reactiveScenarioCommitment(scenario.reactive),
|
|
74
94
|
});
|
|
75
95
|
/**
|
|
76
96
|
* Boundary schema for one explicitly approved, bounded process experiment.
|
|
@@ -258,6 +278,7 @@ export const processScenarioSchema = z
|
|
|
258
278
|
websocket_messages: [],
|
|
259
279
|
websocket_connections: [],
|
|
260
280
|
}),
|
|
281
|
+
reactive: processReactiveScenarioSchema.nullable().default(null),
|
|
261
282
|
})
|
|
262
283
|
.strict()
|
|
263
284
|
.superRefine((scenario, context) => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a ZIP-backed package by its complete lower-cased path suffix.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately extension-only. Callers must separately verify ZIP
|
|
5
|
+
* magic before trusting the classification for a root input.
|
|
6
|
+
*/
|
|
7
|
+
export const zipPackageFormatForPath = (path) => {
|
|
8
|
+
const lower = path.toLowerCase();
|
|
9
|
+
if (lower.endsWith(".zip"))
|
|
10
|
+
return "zip";
|
|
11
|
+
if (lower.endsWith(".ipa"))
|
|
12
|
+
return "ipa";
|
|
13
|
+
if (lower.endsWith(".apk") || lower.endsWith(".aab"))
|
|
14
|
+
return "apk";
|
|
15
|
+
if (lower.endsWith(".msix") || lower.endsWith(".msixbundle"))
|
|
16
|
+
return "msix";
|
|
17
|
+
if (lower.endsWith(".appx") || lower.endsWith(".appxbundle"))
|
|
18
|
+
return "appx";
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Generated from package.json and package-lock.json; do not edit. */
|
|
2
2
|
export const PACKAGE_METADATA = {
|
|
3
3
|
name: "rea-agents",
|
|
4
|
-
version: "2.
|
|
4
|
+
version: "2.4.0", // x-release-please-version
|
|
5
5
|
serverSdkVersion: "2.0.0-beta.4",
|
|
6
6
|
clientSdkVersion: "2.0.0-beta.4",
|
|
7
7
|
coreSdkVersion: "2.0.0-beta.4",
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, an
|
|
|
4
4
|
metadata:
|
|
5
5
|
version: "23"
|
|
6
6
|
tool_count: 98
|
|
7
|
-
catalog_digest: "
|
|
7
|
+
catalog_digest: "aa413204625f3aeb62732d0f732b9b8b24832eea4520214b1e6bd204e599bcd6"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# REA
|
|
@@ -22,7 +22,8 @@ Choose the first tool from the target the user supplied. Do not call
|
|
|
22
22
|
|
|
23
23
|
- ASAR or extracted JavaScript/Electron tree:
|
|
24
24
|
`analyze_javascript_application`.
|
|
25
|
-
- Archive, application package, ZIP/APK/IPA, or DMG:
|
|
25
|
+
- Archive, application package, ZIP/APK/IPA/MSIX/AppX, or DMG:
|
|
26
|
+
`inventory_artifact`.
|
|
26
27
|
- Managed PE/CLI assembly: `inspect_managed_artifact`.
|
|
27
28
|
- User-owned browser page already open: `list_browser_targets`.
|
|
28
29
|
- User-owned Electron runtime already open: `list_electron_targets`.
|
|
@@ -24,8 +24,8 @@ parser and must not become an implicit setup dependency.
|
|
|
24
24
|
## Packages and extraction
|
|
25
25
|
|
|
26
26
|
Call `inventory_artifact` before extraction for application bundles, archives,
|
|
27
|
-
ZIP/APK/IPA, ASAR, or DMG inputs. Continue deterministic occurrence
|
|
28
|
-
the returned offset only when needed. Cite graph manifest IDs.
|
|
27
|
+
ZIP/APK/IPA/MSIX/AppX, ASAR, or DMG inputs. Continue deterministic occurrence
|
|
28
|
+
pages from the returned offset only when needed. Cite graph manifest IDs.
|
|
29
29
|
|
|
30
30
|
`extract_artifact` requires explicit approval, an absent absolute output root,
|
|
31
31
|
and selected occurrence IDs. Never extract every entry implicitly. Symlinks and
|