fix(electron): attribute unresolved paths to selected inputs

Resolve each caller-selected Electron path once at the application boundary, collect field-specific failures with original causes, and pass canonical paths directly to the provider. Update direct provider consumers and route the real verifier through the shared application workflow.

Validation: public CLI/MCP invalid-path acceptance, canonicalization boundary, provider tests, and cancellation/cleanup checks. Live Electron launch was not verified. This does not address the separate Playwright launch-rejection crash in #2269.

Fixes #2272
This commit is contained in:
morluto committed 2026-10-13 04:12:40 +08:00
1 parent 049132ab43
commit b7169c8b74
8 files changed
+186 -92

No files matched your search

@@ -1,18 +1,11 @@
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
import type { ProviderIdentity } from "../AnalysisProvider.js";
import type { CanonicalElectronActiveObservationInput } from "./ElectronActiveObservationPort.js";
import type { Evidence, EvidenceObservation } from "../../domain/evidence.js";
import { createEvidence } from "../../domain/evidence.js";
import type {
ElectronActiveObservationInput,
ElectronActiveObservationResult,
} from "../../domain/javascript/electronActiveObservation.js";
import type { ElectronActiveObservationResult } from "../../domain/javascript/electronActiveObservation.js";
import { jsonObjectSchema, jsonValueSchema } from "../../domain/jsonValue.js";
type CanonicalElectronActiveObservationInput =
ElectronActiveObservationInput & {
readonly application_root: string;
};
/** Commit the selected Electron experiment and its observed result. */
export const createElectronActiveEvidence = (
input: CanonicalElectronActiveObservationInput,
@@ -9,11 +9,15 @@ import type {
} from "../../domain/javascript/electronActiveObservation.js";
import type { Result } from "../../domain/result.js";
/** Caller selections resolved once before entering the runtime adapter. */
export type CanonicalElectronActiveObservationInput =
ElectronActiveObservationInput & { readonly application_root: string };
/** Provider-neutral boundary for owned Electron runtime experiments. */
export interface ElectronActiveObservationPort {
identity(): ProviderIdentity;
capture(
input: ElectronActiveObservationInput,
input: CanonicalElectronActiveObservationInput,
options?: ExecutionOptions,
): Promise<Result<ElectronActiveObservationResult, AnalysisError>>;
}
@@ -2,57 +2,80 @@ import { dirname } from "node:path";
import { realpath } from "node:fs/promises";
import type { ExecutionOptions } from "../AnalysisProvider.js";
import type { ElectronActiveObservationPort } from "./ElectronActiveObservationPort.js";
import type {
CanonicalElectronActiveObservationInput,
ElectronActiveObservationPort,
} from "./ElectronActiveObservationPort.js";
import {
AnalysisCapabilityUnavailableError,
AnalysisInputError,
type AnalysisInputIssue,
} from "../../domain/analysisErrorCore.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type { Evidence } from "../../domain/evidence.js";
import type { ElectronActiveObservationInput } from "../../domain/javascript/electronActiveObservation.js";
import { err, type Result } from "../../domain/result.js";
import { err, ok, type Result } from "../../domain/result.js";
import { createElectronActiveEvidence } from "./ElectronActiveEvidence.js";
const OPERATION = "capture_electron_scenario" as const;
const PROVIDER_ID = "rea-playwright-electron-active";
type CanonicalElectronActiveObservationInput =
ElectronActiveObservationInput & {
readonly application_root: string;
};
const resolveInputPath = async (
field: "executable_path" | "application_path" | "application_root",
selected: string,
): Promise<
Result<
string,
{ readonly issue: AnalysisInputIssue; readonly cause: unknown }
>
> => {
try {
return ok(await realpath(selected));
} catch (cause: unknown) {
return err({
issue: {
path: [field],
reason: "invalid_value",
message: `Cannot resolve ${field} ${selected}: ${cause instanceof Error ? cause.message : String(cause)}`,
},
cause,
});
}
};
const canonicalizeInput = async (
input: ElectronActiveObservationInput,
): Promise<Result<CanonicalElectronActiveObservationInput, AnalysisError>> => {
try {
const [executablePath, applicationPath] = await Promise.all([
realpath(input.executable_path),
realpath(input.application_path),
]);
const applicationRoot =
input.application_root === undefined
? dirname(applicationPath)
: await realpath(input.application_root);
return {
ok: true,
value: {
...input,
executable_path: executablePath,
application_path: applicationPath,
application_root: applicationRoot,
},
};
} catch (cause: unknown) {
const [executable, application, root] = await Promise.all([
resolveInputPath("executable_path", input.executable_path),
resolveInputPath("application_path", input.application_path),
input.application_root === undefined
? Promise.resolve(ok(undefined))
: resolveInputPath("application_root", input.application_root),
]);
if (!executable.ok || !application.ok || !root.ok) {
const failures = [executable, application, root].flatMap((result) =>
result.ok ? [] : [result.error],
);
return err(
new AnalysisInputError(OPERATION, { cause }, [
new AnalysisInputError(
OPERATION,
{
path: ["application_path"],
reason: "invalid_format",
message:
"Electron executable, application, and root must resolve to existing paths",
cause: new AggregateError(
failures.map(({ cause }) => cause),
"Electron paths could not be resolved",
),
},
]),
failures.map(({ issue }) => issue),
),
);
}
return ok({
...input,
executable_path: executable.value,
application_path: application.value,
application_root: root.value ?? dirname(application.value),
});
};
/** Canonicalize request paths and execute one provider-owned Electron run. */