fix(process): clarify Windows capture support and recovery (#798)

This commit is contained in:
N0zoM1z0
2026-10-07 04:20:53 +08:00
committed by GitHub
parent 911d580634
commit 50787b2faf
8 changed files with 105 additions and 11 deletions
+13 -6
View File
@@ -631,9 +631,9 @@ Process Capture records behavior and is not a security sandbox.
Capture a scenario or compare two saved Process Capture Evidence records:
```bash
rea capture-process ./scenario.json > authority.json
rea capture-process ./reconstruction.json > reconstruction.json
rea compare-process-captures authority.json reconstruction.json
rea capture-process ./authority-scenario.json --json > authority-capture.json
rea capture-process ./reconstruction-scenario.json --json > reconstruction-capture.json
rea compare-process-captures authority-capture.json reconstruction-capture.json --json
```
The comparison reports each observed dimension separately and identifies the
@@ -641,9 +641,16 @@ first terminal, interaction, exit, filesystem, or process divergence.
See [Process Capture](docs/process-capture.md) for scenario fields, limits, and
evidence boundaries.
REA installs a prebuilt PTY backend for supported macOS, Linux, and Windows
architectures. If the capability check reports that the backend is unavailable,
reinstall REA for the current platform and architecture.
Process Capture currently runs on Linux and macOS with a working native PTY
backend. Native Windows capture remains unavailable until the PTY adapter can
verify descendant cleanup; installing or reinstalling its Windows PTY binary
does not enable capture. For Linux commands, run Linux REA inside WSL.
Comparing existing capture Evidence remains available on Windows.
On supported capture hosts, a missing or incompatible PTY binary can be fixed
by reinstalling REA for the current platform, architecture, and Node.js version
with optional dependencies enabled. Use separate scenario and output files;
`--json` produces the JSON consumed by the comparison command.
ASAR inventory verifies Electron integrity metadata for both archive entries
and `.asar.unpacked` companion files. Integrity failures identify the logical
+20
View File
@@ -12,6 +12,24 @@ sandbox. Filesystem observation paths select what REA snapshots; they do not
restrict what the process can read or write. The inherited environment is not
recorded, so its influence may remain unknown.
## Host support
Capturing a new scenario currently requires Linux or macOS and a working
native PTY backend. Native Windows capture is unavailable because the PTY
adapter does not yet verify descendant cleanup. The native Job Object controls
used by other REA providers do not establish PTY capture support. Reinstalling
the Windows PTY binary does not enable this workflow.
For Linux commands, use Linux REA inside WSL. Adapt the scenario to that host;
this does not establish capture of a native Windows process tree. Comparing
existing capture Evidence through the CLI or MCP remains available on Windows
and does not launch a PTY or the captured target.
On supported capture hosts, a missing or incompatible native PTY binary has a
different recovery: reinstall REA for the active platform, architecture, and
Node.js version with optional dependencies enabled. Capability diagnostics
distinguish this from the Windows capture-adapter limitation.
## Capture a command
Write a JSON scenario and pass its path to the CLI:
@@ -22,6 +40,8 @@ rea capture-process ./scenario.json --json > capture.json
`--json` is required when saving input for JSON consumers; the default terminal
format is TOON. The file contains the complete capture Evidence record.
Choose an output file distinct from the scenario input: shell redirection opens
and truncates the output before REA reads the scenario.
For example:
+1 -1
View File
@@ -13,7 +13,7 @@ export const processCaptureOwnershipUnavailableReason = (
platform: NodeJS.Platform,
): string | undefined =>
platform === "win32"
? "Windows process-tree ownership and cleanup are unavailable; REA cannot verify that descendants have stopped."
? "Windows PTY process capture is unavailable because this adapter does not yet verify descendant cleanup. Use Linux (including WSL for Linux commands) or macOS. Reinstalling the PTY backend does not enable native Windows capture."
: undefined;
/** Dependencies that make the native PTY probe's host boundary testable. */
+13 -2
View File
@@ -5,6 +5,7 @@ import { captureProcessScenario } from "./ProcessHarness.js";
import { settleProcessCaptureJournal } from "./ProcessCaptureLifecycle.js";
import { processCaptureSchema } from "../domain/processCapture.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
it("rejects legacy replay output instead of silently discarding it", () => {
expect(
@@ -48,9 +49,19 @@ it("fails closed on Windows before resolving or launching scenario paths", async
const result = await captureProcessScenario(scenario, undefined, "win32");
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected Windows ownership refusal");
expect(result.error).toBeInstanceOf(AnalysisCapabilityUnavailableError);
if (!(result.error instanceof AnalysisCapabilityUnavailableError))
throw new Error("expected a capability-unavailable outcome");
expect(result.error).toMatchObject({
operation: "capture_process_scenario",
reason: expect.stringContaining("Windows process-tree ownership"),
reason: expect.stringContaining("Windows PTY process capture"),
});
expect(projectAnalysisError(result.error)).toMatchObject({
code: "capability_unavailable",
category: "unsupported_provider",
message: expect.stringContaining("does not yet verify descendant cleanup"),
details: {
operation: "capture_process_scenario",
reason: result.error.reason,
},
});
});
+1
View File
@@ -409,6 +409,7 @@ export const captureProcessScenario = async (
"rea-process",
"capture_process_scenario",
ownershipReason,
{ userMessage: ownershipReason },
),
);
try {
+7 -1
View File
@@ -52,13 +52,19 @@ export class AnalysisOutputError extends AnalysisError {
/** Selected provider cannot execute a declared analysis operation. */
export class AnalysisCapabilityUnavailableError extends AnalysisError {
readonly _tag = "AnalysisCapabilityUnavailableError";
override readonly userMessage: string | undefined;
constructor(
readonly providerId: string,
readonly operation: string,
readonly reason: string,
options?: ErrorOptions & { readonly userMessage?: string },
) {
super(`Provider ${providerId} cannot execute ${operation}: ${reason}`);
super(
`Provider ${providerId} cannot execute ${operation}: ${reason}`,
options,
);
this.userMessage = options?.userMessage;
}
}
@@ -201,6 +201,8 @@ describe("analysis error projection: caller contract", () => {
]);
expect(projected[1]).toMatchObject({
category: "unsupported_provider",
message:
"This analysis is unavailable for the current target. Choose another analysis or target.",
details: {
provider_id: "fixture",
operation: "overview",
@@ -213,6 +215,31 @@ describe("analysis error projection: caller contract", () => {
expect(JSON.stringify(projected)).not.toContain("secret-token");
});
it("uses explicit capability recovery while retaining the constraint", () => {
const projected = projectAnalysisError(
new AnalysisCapabilityUnavailableError(
"fixture",
"capture",
"host constraint",
{
userMessage: "Use the supported host workflow.",
cause: new Error("private internal cause"),
},
),
);
expect(projected).toMatchObject({
code: "capability_unavailable",
category: "unsupported_provider",
message: "Use the supported host workflow.",
details: {
provider_id: "fixture",
operation: "capture",
reason: "host constraint",
},
});
expect(JSON.stringify(projected)).not.toContain("private internal cause");
});
it("preserves exact artifact-integrity coordinates", () => {
expect(
projectAnalysisError(
@@ -54,7 +54,29 @@ describe("process capture capability diagnostics", () => {
it("keeps the ownership limitation separate from native PTY support", () => {
expect(processCaptureOwnershipUnavailableReason("win32")).toContain(
"process-tree ownership",
"Windows PTY process capture",
);
});
it("explains Windows capture admission before loading or spawning a PTY", async () => {
let loaded = false;
const capability = await probeProcessCaptureCapability({
platform: "win32",
loadPty: async () => {
loaded = true;
throw new Error("Windows admission must precede PTY loading");
},
});
expect(loaded).toBe(false);
expect(capability).toMatchObject({
available: false,
reason: expect.stringContaining("does not yet verify descendant cleanup"),
});
if (capability.available)
throw new Error("expected Windows capture refusal");
expect(capability.reason).toContain("WSL for Linux commands");
expect(capability.reason).toContain(
"Reinstalling the PTY backend does not enable native Windows capture",
);
});
});