refactor(app): split session and CLI workflows
This commit is contained in:
@@ -22,6 +22,7 @@ import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import type { ExecutionOptions } from "./AnalysisProvider.js";
|
||||
import type { PermissionAuthority } from "./PermissionAuthority.js";
|
||||
import { replayPermissionRequest } from "./JavaScriptReplayPermission.js";
|
||||
import {
|
||||
digestBytes,
|
||||
prepareReplayPlan,
|
||||
@@ -65,6 +66,74 @@ export const runControlledReplayValidated = async (
|
||||
input: z.output<typeof controlledReplayInputSchema>,
|
||||
options: ExecutionOptions = {},
|
||||
): Promise<Result<JsonValue, AnalysisError>> => {
|
||||
const authorized = await authorizeReplay(dependencies, input, options.signal);
|
||||
if (!authorized.ok) return authorized;
|
||||
|
||||
const prepared = await prepareValidatedReplay(
|
||||
dependencies,
|
||||
input,
|
||||
options.signal,
|
||||
);
|
||||
if (!prepared.ok) return prepared;
|
||||
|
||||
if (input.mode === "plan")
|
||||
return ok(
|
||||
asJson({
|
||||
phase: "plan",
|
||||
plan: prepared.value.publicPlan,
|
||||
source_evidence: [],
|
||||
evidence: null,
|
||||
}),
|
||||
);
|
||||
|
||||
if (input.plan_digest !== prepared.value.publicPlan.plan_digest)
|
||||
return err(
|
||||
new ReplayPlanStaleError(
|
||||
input.plan_digest ?? "",
|
||||
prepared.value.publicPlan.plan_digest,
|
||||
),
|
||||
);
|
||||
|
||||
const exportContext = await authorizeReproducerExport(
|
||||
dependencies.authority,
|
||||
input,
|
||||
prepared.value,
|
||||
);
|
||||
if (!exportContext.ok) return exportContext;
|
||||
|
||||
const executed = await executeValidatedReplay(
|
||||
dependencies,
|
||||
prepared.value,
|
||||
exportContext.value,
|
||||
options,
|
||||
);
|
||||
if (!executed.ok) return executed;
|
||||
|
||||
try {
|
||||
return ok(
|
||||
buildReplayOutput(
|
||||
prepared.value,
|
||||
executed.value.executed,
|
||||
executed.value.sourceEvidence,
|
||||
),
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
return err(
|
||||
new AnalysisProtocolError(
|
||||
cause instanceof z.ZodError
|
||||
? "Controlled replay produced an invalid bounded result"
|
||||
: "Controlled replay could not establish or clean up its sandbox",
|
||||
{ cause },
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const authorizeReplay = async (
|
||||
dependencies: JavaScriptReplayDependencies,
|
||||
input: z.output<typeof controlledReplayInputSchema>,
|
||||
signal: AbortSignal | undefined,
|
||||
): Promise<Result<true, AnalysisError>> => {
|
||||
if (!dependencies.policy.enabled)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
@@ -81,9 +150,9 @@ export const runControlledReplayValidated = async (
|
||||
"JavaScript replay permission policy is not configured",
|
||||
),
|
||||
);
|
||||
if (isAborted(options.signal))
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
const request = permissionRequest(input, dependencies.policy);
|
||||
if (isAborted(signal)) return err(new AnalysisCancelledError(OPERATION));
|
||||
|
||||
const request = replayPermissionRequest(input, dependencies.policy);
|
||||
const authorized =
|
||||
input.mode === "plan"
|
||||
? await dependencies.authority.explain(request, "read")
|
||||
@@ -96,12 +165,20 @@ export const runControlledReplayValidated = async (
|
||||
cause: authorized.error,
|
||||
}),
|
||||
);
|
||||
let prepared;
|
||||
return ok(true);
|
||||
};
|
||||
|
||||
const prepareValidatedReplay = async (
|
||||
dependencies: JavaScriptReplayDependencies,
|
||||
input: z.output<typeof controlledReplayInputSchema>,
|
||||
signal: AbortSignal | undefined,
|
||||
): Promise<
|
||||
Result<Awaited<ReturnType<typeof prepareReplayPlan>>, AnalysisError>
|
||||
> => {
|
||||
if (isAborted(signal)) return err(new AnalysisCancelledError(OPERATION));
|
||||
try {
|
||||
prepared = await prepareReplayPlan(
|
||||
input,
|
||||
dependencies.policy,
|
||||
dependencies.host,
|
||||
return ok(
|
||||
await prepareReplayPlan(input, dependencies.policy, dependencies.host),
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
return err(
|
||||
@@ -112,60 +189,78 @@ export const runControlledReplayValidated = async (
|
||||
),
|
||||
);
|
||||
}
|
||||
if (isAborted(options.signal))
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
if (input.mode === "plan")
|
||||
return ok(
|
||||
asJson({
|
||||
phase: "plan",
|
||||
plan: prepared.publicPlan,
|
||||
source_evidence: [],
|
||||
evidence: null,
|
||||
}),
|
||||
);
|
||||
if (input.plan_digest !== prepared.publicPlan.plan_digest)
|
||||
};
|
||||
|
||||
interface ReproducerExportContext {
|
||||
readonly path: string;
|
||||
readonly includeSources: boolean;
|
||||
}
|
||||
|
||||
const authorizeReproducerExport = async (
|
||||
authority: PermissionAuthority | undefined,
|
||||
input: z.output<typeof controlledReplayInputSchema>,
|
||||
prepared: Awaited<ReturnType<typeof prepareReplayPlan>>,
|
||||
): Promise<Result<ReproducerExportContext | undefined, AnalysisError>> => {
|
||||
if (input.reproducer_export === undefined) return ok(undefined);
|
||||
if (input.reproducer_export.approved !== true)
|
||||
return err(new AnalysisInputError(`${OPERATION}:reproducer_export`));
|
||||
if (authority === undefined)
|
||||
return err(
|
||||
new ReplayPlanStaleError(
|
||||
input.plan_digest ?? "",
|
||||
prepared.publicPlan.plan_digest,
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
"rea-javascript-replay",
|
||||
OPERATION,
|
||||
"JavaScript replay permission policy is not configured",
|
||||
),
|
||||
);
|
||||
let canonicalExportPath: string | undefined;
|
||||
if (input.reproducer_export !== undefined) {
|
||||
if (input.reproducer_export.approved !== true)
|
||||
return err(new AnalysisInputError(`${OPERATION}:reproducer_export`));
|
||||
const exportAuthorized = await dependencies.authority.authorize(
|
||||
{
|
||||
capability: "evidence_write",
|
||||
roots: [input.reproducer_export.path],
|
||||
executables: [],
|
||||
environment_names: [],
|
||||
network: "none",
|
||||
mount: false,
|
||||
operation_identity: `${OPERATION}:reproducer:${prepared.publicPlan.plan_digest}`,
|
||||
},
|
||||
"write",
|
||||
const exportAuthorized = await authority.authorize(
|
||||
{
|
||||
capability: "evidence_write",
|
||||
roots: [input.reproducer_export.path],
|
||||
executables: [],
|
||||
environment_names: [],
|
||||
network: "none",
|
||||
mount: false,
|
||||
operation_identity: `${OPERATION}:reproducer:${prepared.publicPlan.plan_digest}`,
|
||||
},
|
||||
"write",
|
||||
);
|
||||
if (!exportAuthorized.ok)
|
||||
return err(
|
||||
exportAuthorized.error instanceof PermissionRequiredError
|
||||
? exportAuthorized.error
|
||||
: new AnalysisProtocolError(exportAuthorized.error.message, {
|
||||
cause: exportAuthorized.error,
|
||||
}),
|
||||
);
|
||||
if (!exportAuthorized.ok)
|
||||
return err(
|
||||
exportAuthorized.error instanceof PermissionRequiredError
|
||||
? exportAuthorized.error
|
||||
: new AnalysisProtocolError(exportAuthorized.error.message, {
|
||||
cause: exportAuthorized.error,
|
||||
}),
|
||||
);
|
||||
canonicalExportPath = exportAuthorized.value.request.roots[0];
|
||||
if (canonicalExportPath !== input.reproducer_export.path)
|
||||
return err(
|
||||
new AnalysisInputError(`${OPERATION}:reproducer_export:path`, {
|
||||
cause: new TypeError(
|
||||
`Use the canonical reproducer path: ${canonicalExportPath ?? "unavailable"}`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (isAborted(options.signal))
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
const canonicalExportPath = exportAuthorized.value.request.roots[0];
|
||||
if (canonicalExportPath !== input.reproducer_export.path)
|
||||
return err(
|
||||
new AnalysisInputError(`${OPERATION}:reproducer_export:path`, {
|
||||
cause: new TypeError(
|
||||
`Use the canonical reproducer path: ${canonicalExportPath ?? "unavailable"}`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
return ok({
|
||||
path: canonicalExportPath,
|
||||
includeSources: input.reproducer_export.include_sources === true,
|
||||
});
|
||||
};
|
||||
|
||||
const executeValidatedReplay = async (
|
||||
dependencies: JavaScriptReplayDependencies,
|
||||
prepared: Awaited<ReturnType<typeof prepareReplayPlan>>,
|
||||
exportContext: ReproducerExportContext | undefined,
|
||||
options: ExecutionOptions,
|
||||
): Promise<
|
||||
Result<
|
||||
{
|
||||
readonly executed: z.infer<typeof replayExecutionResultSchema>;
|
||||
readonly sourceEvidence: ReturnType<typeof createReplayEvidence>[];
|
||||
},
|
||||
AnalysisError
|
||||
>
|
||||
> => {
|
||||
await options.progress?.report({
|
||||
phase: OPERATION,
|
||||
completed: 0,
|
||||
@@ -174,6 +269,7 @@ export const runControlledReplayValidated = async (
|
||||
});
|
||||
if (isAborted(options.signal))
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
|
||||
try {
|
||||
let executed = replayExecutionResultSchema.parse(
|
||||
await dependencies.runner.execute(
|
||||
@@ -191,132 +287,9 @@ export const runControlledReplayValidated = async (
|
||||
});
|
||||
if (executed.termination === "cancelled")
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
if (
|
||||
input.reproducer_export !== undefined &&
|
||||
canonicalExportPath !== undefined &&
|
||||
executed.cleanup.state === "complete"
|
||||
) {
|
||||
const manifest = {
|
||||
schema_version: 1,
|
||||
plan: prepared.publicPlan,
|
||||
result: executed,
|
||||
sources: input.reproducer_export.include_sources
|
||||
? {
|
||||
left: prepared.leftSources,
|
||||
...(prepared.rightSources === undefined
|
||||
? {}
|
||||
: { right: prepared.rightSources }),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
const encoded = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
|
||||
try {
|
||||
await writeFileAtomic(canonicalExportPath, encoded, {
|
||||
mode: 0o600,
|
||||
});
|
||||
executed = replayExecutionResultSchema.parse({
|
||||
...executed,
|
||||
reproducer: {
|
||||
state: "written",
|
||||
path: canonicalExportPath,
|
||||
sha256: digestBytes(encoded),
|
||||
},
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
executed = replayExecutionResultSchema.parse({
|
||||
...executed,
|
||||
limitations: [
|
||||
...executed.limitations,
|
||||
"Replay completed, but the separately approved reproducer export failed.",
|
||||
],
|
||||
reproducer: {
|
||||
state: "failed",
|
||||
path: canonicalExportPath,
|
||||
error:
|
||||
cause instanceof Error
|
||||
? cause.message
|
||||
: "unknown reproducer export failure",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
const leftCount = prepared.publicPlan.cases.length;
|
||||
const sourceEvidence = [
|
||||
createReplayEvidence(
|
||||
prepared,
|
||||
executed,
|
||||
"left",
|
||||
executed.outcomes.slice(0, leftCount),
|
||||
),
|
||||
...(prepared.publicPlan.right === undefined
|
||||
? []
|
||||
: [
|
||||
createReplayEvidence(
|
||||
prepared,
|
||||
executed,
|
||||
"right",
|
||||
executed.outcomes.slice(leftCount),
|
||||
),
|
||||
]),
|
||||
];
|
||||
const subject = prepared.publicPlan.left.modules[0];
|
||||
const evidence = createEvidence(
|
||||
subject === undefined
|
||||
? undefined
|
||||
: {
|
||||
path: subject.canonical_path,
|
||||
sha256: subject.sha256,
|
||||
format: "javascript",
|
||||
},
|
||||
{
|
||||
id: "rea-javascript-replay",
|
||||
name: "REA isolated JavaScript replay",
|
||||
version: "1",
|
||||
},
|
||||
{
|
||||
predicateType: "javascript-controlled-replay",
|
||||
operation: OPERATION,
|
||||
parameters: {
|
||||
replay_plan: jsonValueSchema.parse(
|
||||
JSON.parse(JSON.stringify(prepared.publicPlan)),
|
||||
),
|
||||
},
|
||||
result: jsonValueSchema.parse(JSON.parse(JSON.stringify(executed))),
|
||||
rawResult: jsonValueSchema.parse(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
cases: prepared.publicPlan.cases,
|
||||
outcomes: executed.outcomes,
|
||||
stderr: executed.stderr,
|
||||
}),
|
||||
),
|
||||
),
|
||||
confidence: executed.comparison === undefined ? "observed" : "derived",
|
||||
authority: "controlled-replay",
|
||||
environment: {
|
||||
id: prepared.publicPlan.plan_digest,
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
isolation: "container",
|
||||
},
|
||||
limitations: executed.limitations,
|
||||
locations: prepared.publicPlan.left.modules.map((module) => ({
|
||||
kind: "artifact-path" as const,
|
||||
path: module.canonical_path,
|
||||
})),
|
||||
evidenceLinks: sourceEvidence.map(({ evidence_id: id }) => id),
|
||||
},
|
||||
);
|
||||
return ok(
|
||||
asJson({
|
||||
phase: "execute",
|
||||
plan: null,
|
||||
source_evidence: sourceEvidence.map((item) =>
|
||||
replayEvidenceSchema.parse(item),
|
||||
),
|
||||
evidence: replayEvidenceSchema.parse(evidence),
|
||||
}),
|
||||
);
|
||||
executed = await applyReproducerExport(executed, exportContext, prepared);
|
||||
const sourceEvidence = buildSourceEvidence(prepared, executed);
|
||||
return ok({ executed, sourceEvidence });
|
||||
} catch (cause: unknown) {
|
||||
return err(
|
||||
new AnalysisProtocolError(
|
||||
@@ -329,6 +302,147 @@ export const runControlledReplayValidated = async (
|
||||
}
|
||||
};
|
||||
|
||||
const applyReproducerExport = async (
|
||||
executed: z.infer<typeof replayExecutionResultSchema>,
|
||||
exportContext: ReproducerExportContext | undefined,
|
||||
prepared: Awaited<ReturnType<typeof prepareReplayPlan>>,
|
||||
): Promise<z.infer<typeof replayExecutionResultSchema>> => {
|
||||
if (exportContext === undefined || executed.cleanup.state !== "complete")
|
||||
return executed;
|
||||
|
||||
const manifest = {
|
||||
schema_version: 1,
|
||||
plan: prepared.publicPlan,
|
||||
result: executed,
|
||||
sources: exportContext.includeSources
|
||||
? {
|
||||
left: prepared.leftSources,
|
||||
...(prepared.rightSources === undefined
|
||||
? {}
|
||||
: { right: prepared.rightSources }),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
const encoded = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
|
||||
try {
|
||||
await writeFileAtomic(exportContext.path, encoded, {
|
||||
mode: 0o600,
|
||||
});
|
||||
return replayExecutionResultSchema.parse({
|
||||
...executed,
|
||||
reproducer: {
|
||||
state: "written",
|
||||
path: exportContext.path,
|
||||
sha256: digestBytes(encoded),
|
||||
},
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
return replayExecutionResultSchema.parse({
|
||||
...executed,
|
||||
limitations: [
|
||||
...executed.limitations,
|
||||
"Replay completed, but the separately approved reproducer export failed.",
|
||||
],
|
||||
reproducer: {
|
||||
state: "failed",
|
||||
path: exportContext.path,
|
||||
error:
|
||||
cause instanceof Error
|
||||
? cause.message
|
||||
: "unknown reproducer export failure",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const buildSourceEvidence = (
|
||||
prepared: Awaited<ReturnType<typeof prepareReplayPlan>>,
|
||||
executed: z.infer<typeof replayExecutionResultSchema>,
|
||||
): ReturnType<typeof createReplayEvidence>[] => {
|
||||
const leftCount = prepared.publicPlan.cases.length;
|
||||
return [
|
||||
createReplayEvidence(
|
||||
prepared,
|
||||
executed,
|
||||
"left",
|
||||
executed.outcomes.slice(0, leftCount),
|
||||
),
|
||||
...(prepared.publicPlan.right === undefined
|
||||
? []
|
||||
: [
|
||||
createReplayEvidence(
|
||||
prepared,
|
||||
executed,
|
||||
"right",
|
||||
executed.outcomes.slice(leftCount),
|
||||
),
|
||||
]),
|
||||
];
|
||||
};
|
||||
|
||||
const buildReplayOutput = (
|
||||
prepared: Awaited<ReturnType<typeof prepareReplayPlan>>,
|
||||
executed: z.infer<typeof replayExecutionResultSchema>,
|
||||
sourceEvidence: ReturnType<typeof createReplayEvidence>[],
|
||||
): JsonValue => {
|
||||
const subject = prepared.publicPlan.left.modules[0];
|
||||
const evidence = createEvidence(
|
||||
subject === undefined
|
||||
? undefined
|
||||
: {
|
||||
path: subject.canonical_path,
|
||||
sha256: subject.sha256,
|
||||
format: "javascript",
|
||||
},
|
||||
{
|
||||
id: "rea-javascript-replay",
|
||||
name: "REA isolated JavaScript replay",
|
||||
version: "1",
|
||||
},
|
||||
{
|
||||
predicateType: "javascript-controlled-replay",
|
||||
operation: OPERATION,
|
||||
parameters: {
|
||||
replay_plan: jsonValueSchema.parse(
|
||||
JSON.parse(JSON.stringify(prepared.publicPlan)),
|
||||
),
|
||||
},
|
||||
result: jsonValueSchema.parse(JSON.parse(JSON.stringify(executed))),
|
||||
rawResult: jsonValueSchema.parse(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
cases: prepared.publicPlan.cases,
|
||||
outcomes: executed.outcomes,
|
||||
stderr: executed.stderr,
|
||||
}),
|
||||
),
|
||||
),
|
||||
confidence: executed.comparison === undefined ? "observed" : "derived",
|
||||
authority: "controlled-replay",
|
||||
environment: {
|
||||
id: prepared.publicPlan.plan_digest,
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
isolation: "container",
|
||||
},
|
||||
limitations: executed.limitations,
|
||||
locations: prepared.publicPlan.left.modules.map((module) => ({
|
||||
kind: "artifact-path" as const,
|
||||
path: module.canonical_path,
|
||||
})),
|
||||
evidenceLinks: sourceEvidence.map(({ evidence_id: id }) => id),
|
||||
},
|
||||
);
|
||||
return asJson({
|
||||
phase: "execute",
|
||||
plan: null,
|
||||
source_evidence: sourceEvidence.map((item) =>
|
||||
replayEvidenceSchema.parse(item),
|
||||
),
|
||||
evidence: replayEvidenceSchema.parse(evidence),
|
||||
});
|
||||
};
|
||||
|
||||
const createReplayEvidence = (
|
||||
prepared: Awaited<ReturnType<typeof prepareReplayPlan>>,
|
||||
executed: z.infer<typeof replayExecutionResultSchema>,
|
||||
@@ -395,24 +509,3 @@ const asJson = (value: unknown): JsonValue =>
|
||||
JSON.stringify(controlledReplayOutputSchema.parse(value)),
|
||||
) as unknown,
|
||||
);
|
||||
|
||||
const permissionRequest = (
|
||||
input: z.infer<typeof controlledReplayInputSchema>,
|
||||
policy: JavaScriptReplayPolicy,
|
||||
) => ({
|
||||
capability: "javascript_replay" as const,
|
||||
roots: [...input.left.modules, ...(input.right?.modules ?? [])].map(
|
||||
({ path }) => path,
|
||||
),
|
||||
executables: [
|
||||
policy.nodePath,
|
||||
policy.bubblewrapPath,
|
||||
policy.systemdRunPath,
|
||||
policy.systemctlPath,
|
||||
policy.shellPath,
|
||||
],
|
||||
environment_names: [],
|
||||
network: "none" as const,
|
||||
mount: true,
|
||||
operation_identity: `${OPERATION}:${input.plan_digest ?? "plan"}`,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user