412 lines
11 KiB
TypeScript
412 lines
11 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { evidenceEnvelopeSchema } from "./evidence.js";
|
|
|
|
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
const absolutePathSchema = z.string().startsWith("/").max(4096);
|
|
const aliasSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(256)
|
|
.regex(/^[A-Za-z0-9_./:@-]+$/u);
|
|
|
|
const replayModuleSchema = z
|
|
.object({
|
|
alias: aliasSchema,
|
|
path: absolutePathSchema,
|
|
format: z.enum(["esm", "commonjs-factory"]),
|
|
role: z.enum(["module", "stub"]).default("module"),
|
|
dependencies: z
|
|
.record(z.string().min(1).max(256), aliasSchema)
|
|
.refine((value) => Object.keys(value).length <= 128, {
|
|
message: "Replay modules may declare at most 128 dependencies",
|
|
})
|
|
.default({}),
|
|
})
|
|
.strict();
|
|
|
|
const replaySideSchema = z
|
|
.object({
|
|
modules: z.array(replayModuleSchema).min(1).max(128),
|
|
entry_alias: aliasSchema,
|
|
entry_export: z.string().min(1).max(256).default("default"),
|
|
})
|
|
.strict();
|
|
|
|
const explicitCaseSchema = z
|
|
.object({
|
|
case_id: z.string().min(1).max(128),
|
|
arguments: z.array(z.json()).max(16),
|
|
})
|
|
.strict();
|
|
|
|
const generatorSchema = z
|
|
.object({
|
|
preset: z.enum([
|
|
"parser-boundaries",
|
|
"sanitizer-boundaries",
|
|
"clipboard-boundaries",
|
|
]),
|
|
seed: z.number().int().min(0).max(0xffff_ffff),
|
|
count: z.number().int().min(1).max(64),
|
|
})
|
|
.strict();
|
|
|
|
const replayLimitsSchema = z
|
|
.object({
|
|
wall_time_ms: z.number().int().min(100).max(10_000).default(3_000),
|
|
memory_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(16 * 1024 * 1024)
|
|
.max(512 * 1024 * 1024)
|
|
.default(128 * 1024 * 1024),
|
|
tasks: z.number().int().min(1).max(32).default(8),
|
|
cpu_quota_percent: z.number().int().min(1).max(100).default(50),
|
|
tmpfs_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(1024 * 1024)
|
|
.max(64 * 1024 * 1024)
|
|
.default(16 * 1024 * 1024),
|
|
module_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(16 * 1024 * 1024)
|
|
.default(4 * 1024 * 1024),
|
|
input_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(1024 * 1024)
|
|
.default(256 * 1024),
|
|
protocol_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(1024 * 1024)
|
|
.max(64 * 1024 * 1024)
|
|
.default(16 * 1024 * 1024),
|
|
output_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(4 * 1024 * 1024)
|
|
.default(512 * 1024),
|
|
stderr_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(256 * 1024)
|
|
.default(32 * 1024),
|
|
result_depth: z.number().int().min(1).max(64).default(16),
|
|
result_nodes: z.number().int().min(1).max(100_000).default(10_000),
|
|
})
|
|
.strict();
|
|
|
|
const determinismSchema = z
|
|
.object({
|
|
clock_iso: z.iso.datetime().default("2000-01-01T00:00:00.000Z"),
|
|
random_seed: z.number().int().min(0).max(0xffff_ffff).default(0),
|
|
locale: z.literal("en-US").default("en-US"),
|
|
timezone: z.literal("UTC").default("UTC"),
|
|
platform: z.literal("linux").default("linux"),
|
|
})
|
|
.strict();
|
|
|
|
const controlledReplayInputShape = {
|
|
left: replaySideSchema,
|
|
right: replaySideSchema.optional(),
|
|
cases: z.array(explicitCaseSchema).max(128).default([]),
|
|
generator: generatorSchema.optional(),
|
|
determinism: determinismSchema.default({
|
|
clock_iso: "2000-01-01T00:00:00.000Z",
|
|
random_seed: 0,
|
|
locale: "en-US",
|
|
timezone: "UTC",
|
|
platform: "linux",
|
|
}),
|
|
limits: replayLimitsSchema.default({
|
|
wall_time_ms: 3_000,
|
|
memory_bytes: 128 * 1024 * 1024,
|
|
tasks: 8,
|
|
cpu_quota_percent: 50,
|
|
tmpfs_bytes: 16 * 1024 * 1024,
|
|
module_bytes: 4 * 1024 * 1024,
|
|
input_bytes: 256 * 1024,
|
|
protocol_bytes: 16 * 1024 * 1024,
|
|
output_bytes: 512 * 1024,
|
|
stderr_bytes: 32 * 1024,
|
|
result_depth: 16,
|
|
result_nodes: 10_000,
|
|
}),
|
|
reproducer_export: z
|
|
.strictObject({
|
|
path: absolutePathSchema,
|
|
approved: z.literal(true),
|
|
include_sources: z.boolean().default(false),
|
|
})
|
|
.optional(),
|
|
} as const;
|
|
|
|
/** Parse a replay request that can only construct a plan. */
|
|
export const controlledReplayPlanInputSchema = z.strictObject({
|
|
...controlledReplayInputShape,
|
|
mode: z.literal("plan"),
|
|
});
|
|
|
|
/** Parse a replay request carrying literal approval for one exact plan. */
|
|
export const controlledReplayExecutionInputSchema = z.strictObject({
|
|
...controlledReplayInputShape,
|
|
mode: z.literal("execute"),
|
|
approved: z.literal(true),
|
|
plan_digest: digestSchema,
|
|
});
|
|
|
|
/** Parse a controlled replay request into its legal plan or execution state. */
|
|
export const controlledReplayInputSchema = z
|
|
.discriminatedUnion("mode", [
|
|
controlledReplayPlanInputSchema,
|
|
controlledReplayExecutionInputSchema,
|
|
])
|
|
.superRefine((value, context) => {
|
|
if (value.cases.length === 0 && value.generator === undefined)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["cases"],
|
|
message: "At least one explicit or generated case is required",
|
|
});
|
|
if (value.cases.length + (value.generator?.count ?? 0) > 128)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["generator", "count"],
|
|
message: "Explicit and generated replay cases must total at most 128",
|
|
});
|
|
});
|
|
|
|
const moduleCommitmentSchema = z
|
|
.object({
|
|
alias: aliasSchema,
|
|
canonical_path: z.string().min(1),
|
|
format: z.enum(["esm", "commonjs-factory"]),
|
|
role: z.enum(["module", "stub"]),
|
|
byte_count: z.number().int().min(0),
|
|
sha256: digestSchema,
|
|
dependencies: z.record(z.string(), aliasSchema),
|
|
})
|
|
.strict();
|
|
|
|
const runtimeIdentitySchema = z
|
|
.object({
|
|
path: z.string().min(1),
|
|
version: z.string().min(1),
|
|
sha256: digestSchema,
|
|
})
|
|
.strict();
|
|
|
|
const runtimeFileIdentitySchema = z
|
|
.object({
|
|
source_path: z.string().min(1),
|
|
destination_path: z.string().min(1),
|
|
sha256: digestSchema,
|
|
})
|
|
.strict();
|
|
|
|
const replayPlanSchema = z
|
|
.object({
|
|
plan_digest: digestSchema,
|
|
policy_version: z.literal("linux-bwrap-systemd-v1"),
|
|
policy_sha256: digestSchema,
|
|
network: z.literal("none"),
|
|
filesystem: z
|
|
.object({
|
|
host_writes: z.literal(false),
|
|
private_tmpfs_bytes: z.number().int().nonnegative(),
|
|
})
|
|
.strict(),
|
|
runtime: z
|
|
.object({
|
|
executable: runtimeIdentitySchema,
|
|
worker: runtimeIdentitySchema,
|
|
read_only_files: z.array(runtimeFileIdentitySchema).min(1),
|
|
})
|
|
.strict(),
|
|
sandbox: z
|
|
.object({
|
|
bubblewrap: runtimeIdentitySchema,
|
|
systemd_run: runtimeIdentitySchema,
|
|
systemctl: runtimeIdentitySchema,
|
|
shell: runtimeIdentitySchema,
|
|
seccomp_sha256: digestSchema,
|
|
})
|
|
.strict(),
|
|
left: z
|
|
.object({
|
|
modules: z.array(moduleCommitmentSchema),
|
|
entry_alias: aliasSchema,
|
|
entry_export: z.string(),
|
|
})
|
|
.strict(),
|
|
right: z
|
|
.object({
|
|
modules: z.array(moduleCommitmentSchema),
|
|
entry_alias: aliasSchema,
|
|
entry_export: z.string(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
cases: z.array(
|
|
z
|
|
.object({
|
|
case_id: z.string(),
|
|
arguments: z.array(z.json()),
|
|
sha256: digestSchema,
|
|
})
|
|
.strict(),
|
|
),
|
|
determinism: determinismSchema,
|
|
limits: replayLimitsSchema,
|
|
effects: z.array(z.string()),
|
|
reproducer_export: z
|
|
.object({
|
|
path: z.string().min(1),
|
|
include_sources: z.boolean(),
|
|
authority: z.literal("evidence_write"),
|
|
})
|
|
.strict()
|
|
.nullable(),
|
|
})
|
|
.strict();
|
|
|
|
const replayOutcomeFacts = {
|
|
case_id: z.string(),
|
|
input_sha256: digestSchema,
|
|
} as const;
|
|
const replayExceptionSchema = z
|
|
.object({
|
|
name: z.string(),
|
|
message: z.string(),
|
|
stack: z.string().nullable(),
|
|
})
|
|
.strict();
|
|
const replayOutcomeSchema = z.discriminatedUnion("outcome", [
|
|
z.strictObject({
|
|
...replayOutcomeFacts,
|
|
outcome: z.literal("return"),
|
|
value: z.json(),
|
|
output_sha256: digestSchema,
|
|
truncated: z.literal(false),
|
|
}),
|
|
z.strictObject({
|
|
...replayOutcomeFacts,
|
|
outcome: z.enum(["exception", "serialization_error", "denied"]),
|
|
exception: z.object(replayExceptionSchema.shape).strict(),
|
|
output_sha256: digestSchema,
|
|
truncated: z.literal(false),
|
|
}),
|
|
z.strictObject({
|
|
...replayOutcomeFacts,
|
|
outcome: z.enum(["timeout", "oom", "crash", "cancelled"]),
|
|
output_sha256: z.null(),
|
|
truncated: z.literal(false),
|
|
}),
|
|
z.strictObject({
|
|
...replayOutcomeFacts,
|
|
outcome: z.literal("protocol_error"),
|
|
output_sha256: z.null(),
|
|
truncated: z.literal(true),
|
|
}),
|
|
]);
|
|
|
|
export const replayExecutionResultSchema = z
|
|
.object({
|
|
plan_digest: digestSchema,
|
|
outcomes: z.array(replayOutcomeSchema),
|
|
comparison: z
|
|
.array(
|
|
z
|
|
.object({
|
|
case_id: z.string(),
|
|
status: z.enum(["equal", "changed", "unknown"]),
|
|
left_index: z.number().int(),
|
|
right_index: z.number().int(),
|
|
})
|
|
.strict(),
|
|
)
|
|
.optional(),
|
|
stderr: z.string(),
|
|
termination: z.enum([
|
|
"completed",
|
|
"timeout",
|
|
"oom",
|
|
"crash",
|
|
"cancelled",
|
|
"protocol_error",
|
|
]),
|
|
cleanup: z
|
|
.object({
|
|
state: z.enum(["complete", "incomplete"]),
|
|
residual_resources: z.array(z.string()),
|
|
})
|
|
.strict(),
|
|
limitations: z.array(z.string()),
|
|
reproducer: z
|
|
.discriminatedUnion("state", [
|
|
z
|
|
.object({
|
|
state: z.literal("written"),
|
|
path: z.string(),
|
|
sha256: digestSchema,
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
state: z.literal("failed"),
|
|
path: z.string(),
|
|
error: z.string(),
|
|
})
|
|
.strict(),
|
|
])
|
|
.nullable(),
|
|
})
|
|
.strict();
|
|
|
|
export const replayEvidenceSchema = evidenceEnvelopeSchema
|
|
.omit({ normalized_result: true })
|
|
.extend({ normalized_result: replayExecutionResultSchema });
|
|
|
|
/** A replay plan result with no claimed runtime observation. */
|
|
export const controlledReplayPlanOutputSchema = z.strictObject({
|
|
phase: z.literal("plan"),
|
|
plan: replayPlanSchema,
|
|
source_evidence: z
|
|
.array(replayEvidenceSchema)
|
|
.length(0)
|
|
.brand<"NoReplayEvidence">(),
|
|
evidence: z.null(),
|
|
});
|
|
|
|
/** An executed replay result with its required source and aggregate Evidence. */
|
|
export const controlledReplayExecutionOutputSchema = z.strictObject({
|
|
phase: z.literal("execute"),
|
|
plan: z.null(),
|
|
source_evidence: z.array(replayEvidenceSchema).min(1),
|
|
evidence: replayEvidenceSchema,
|
|
});
|
|
|
|
/** Parse replay output into its legal plan or execution state. */
|
|
export const controlledReplayOutputSchema = z.discriminatedUnion("phase", [
|
|
controlledReplayPlanOutputSchema,
|
|
controlledReplayExecutionOutputSchema,
|
|
]);
|
|
|
|
export type ControlledReplayInput = z.infer<typeof controlledReplayInputSchema>;
|
|
export type ControlledReplayExecutionInput = z.infer<
|
|
typeof controlledReplayExecutionInputSchema
|
|
>;
|
|
export type ControlledReplayExecutionOutput = z.infer<
|
|
typeof controlledReplayExecutionOutputSchema
|
|
>;
|
|
export type ReplayPlan = z.infer<typeof replayPlanSchema>;
|
|
export type ReplayExecutionResult = z.infer<typeof replayExecutionResultSchema>;
|