Skip to content

Commit 2f58c6c

Browse files
authored
Fix recipe-backed fuzz suite resets (#1878)
1 parent 6457dca commit 2f58c6c

3 files changed

Lines changed: 64 additions & 2 deletions

File tree

packages/cli/src/commands/recipe-run-workflow-evidence.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises"
33
import { resolve } from "node:path"
44
import { commandArgValue, parseCommandJson, parseCommandJsonObject, RUNTIME_BACKED_FUZZ_SUITE_RUNNER_CAPABILITIES, runFuzzSuite, runtimeCheckpointUnsupportedDiagnostic, type ArtifactBundle, type ArtifactManifestFile, type ExecutionResult, type FuzzSuiteContract, type Runtime, type RuntimeCheckpointFailureDiagnostic, type RuntimeCheckpointOperation, type RuntimeCheckpointResult, type WorkspaceRecipe, type WorkspaceRecipeDistributionSetupArtifact, type WorkspaceRecipeDistributionStartupProbe, type WorkspaceRecipeProbe } from "@automattic/wp-codebox-core"
55
import { stripUndefined } from "@automattic/wp-codebox-core/internals"
6+
import { createWordPressFuzzSuiteResetExecutor } from "@automattic/wp-codebox-playground/public"
67
import { correlateObservedHostsToExternalServiceBoundaries, recipeExternalServiceBoundarySummaries } from "../recipe-external-services.js"
78
import { recipeExecutionSpec, sandboxWorkspaceContract } from "../agent-sandbox.js"
89
import { executeAgentFanoutFromArgs } from "../agent-fanout.js"
@@ -523,6 +524,7 @@ async function executeRunFuzzSuiteRecipeCommand(runtime: Runtime, args: string[]
523524
const result = await runFuzzSuite(suite, {
524525
runnerCapabilities: RUNTIME_BACKED_FUZZ_SUITE_RUNNER_CAPABILITIES,
525526
executor: async (spec) => executeRecipeWorkflowStep(runtime, { phase: "steps", index: 0, step: workflowStepFromExecutionSpec(spec) }, recipeDirectory, sandboxWorkspace, undefined, undefined, inputMountPathMap),
527+
resetExecutor: createWordPressFuzzSuiteResetExecutor(recipeRuntimeFuzzSuiteResetEpisode(runtime, recipeDirectory, sandboxWorkspace, inputMountPathMap)),
526528
runtimeWorkloadExecutor: async ({ suite, workload, case: fuzzCase }) => {
527529
const workloadJson = JSON.stringify(workload)
528530
const execution = await executeWordPressRunWorkloadJsonRecipeCommand(runtime, [`workload-json=${workloadJson}`], recipeDirectory, sandboxWorkspace, suite, fuzzCase, inputMountPathMap)
@@ -552,6 +554,37 @@ async function executeRunFuzzSuiteRecipeCommand(runtime: Runtime, args: string[]
552554
}
553555
}
554556

557+
function recipeRuntimeFuzzSuiteResetEpisode(runtime: Runtime, recipeDirectory: string, sandboxWorkspace: ReturnType<typeof sandboxWorkspaceContract> | undefined, inputMountPathMap: readonly InputMountPathMapping[]) {
558+
let index = 0
559+
return {
560+
async step(action: { command: string; args?: string[]; metadata?: Record<string, unknown> }) {
561+
index += 1
562+
const id = `recipe-fuzz-reset-${index}`
563+
const execution = await executeRecipeWorkflowStep(runtime, {
564+
phase: "steps",
565+
index,
566+
step: { command: action.command, args: action.args, metadata: action.metadata },
567+
}, recipeDirectory, sandboxWorkspace, undefined, undefined, inputMountPathMap)
568+
return {
569+
id,
570+
index,
571+
action: {
572+
schema: "wp-codebox/runtime-episode-action/v1" as const,
573+
id,
574+
kind: "command" as const,
575+
command: action.command,
576+
args: action.args ?? [],
577+
metadata: action.metadata,
578+
digest: { algorithm: "sha256" as const, value: createHash("sha256").update(JSON.stringify(action)).digest("hex") },
579+
},
580+
actionRef: { kind: "action", id },
581+
execution,
582+
executionRef: { kind: "execution", id: execution.id },
583+
}
584+
},
585+
}
586+
}
587+
555588
async function fuzzSuiteFromRecipeCommandArgs(args: string[], recipeDirectory: string): Promise<FuzzSuiteContract> {
556589
const inline = commandArgValue(args, "input-json") ?? commandArgValue(args, "suite-json")
557590
if (inline) {

packages/runtime-playground/src/public.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ export interface WordPressFuzzSuiteExecutionOptions extends Omit<FuzzSuiteRunOpt
213213
artifactBundles?: Array<Pick<ArtifactBundle, "id" | "directory">>
214214
artifactStorage?: RuntimeArtifactStorageInput | RuntimeArtifactStorageDescriptor
215215
}
216-
type WordPressFuzzSuiteResetEpisode = Pick<RuntimeEpisode, "reset" | "step"> & Partial<Pick<RuntimeEpisode, "restoreSnapshot">>
216+
type WordPressFuzzSuiteResetEpisode = Pick<RuntimeEpisode, "step"> & Partial<Pick<RuntimeEpisode, "restoreSnapshot">>
217+
type WordPressFuzzSuiteExecutionEpisode = WordPressFuzzSuiteResetEpisode & Pick<RuntimeEpisode, "reset">
217218

218219
export interface WordPressFuzzSuiteResetExecutorOptions {
219220
artifactBundles?: Array<Pick<ArtifactBundle, "id" | "directory">>
@@ -1217,7 +1218,7 @@ export function createWordPressFuzzSuiteResetExecutor(episode: WordPressFuzzSuit
12171218
}
12181219

12191220
export async function executeWordPressFuzzSuite(
1220-
episode: WordPressFuzzSuiteResetEpisode,
1221+
episode: WordPressFuzzSuiteExecutionEpisode,
12211222
suite: FuzzSuiteContract,
12221223
options: WordPressFuzzSuiteExecutionOptions = {},
12231224
): Promise<FuzzSuiteResultEnvelope> {

tests/nested-fuzz-suite-recipe-command.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,17 @@ const recipe: WorkspaceRecipe = {
2828
assertWorkspaceRecipeJsonSchema(recipe, { recipeCommandIds: ["wp-codebox/run-fuzz-suite", "wordpress.run-php"] })
2929

3030
const executed: ExecutionSpec[] = []
31+
let checkpointCreates = 0
32+
let checkpointRestores = 0
3133
const runtime = {
34+
async createCheckpoint({ name }: { name: string }) {
35+
checkpointCreates += 1
36+
return { schema: "wp-codebox/runtime-checkpoint-result/v1" as const, status: "created", operation: "create" as const, checkpoint: { name, snapshotId: `snapshot-${name}`, createdAt: "2026-01-01T00:00:00.000Z" } }
37+
},
38+
async restoreCheckpoint(name: string) {
39+
checkpointRestores += 1
40+
return { schema: "wp-codebox/runtime-checkpoint-result/v1" as const, status: "restored", operation: "restore" as const, checkpoint: { name, snapshotId: `snapshot-${name}`, createdAt: "2026-01-01T00:00:00.000Z", restoredAt: "2026-01-01T00:00:01.000Z" } }
41+
},
3242
async execute(spec: ExecutionSpec): Promise<ExecutionResult> {
3343
executed.push(spec)
3444
if (spec.command === "wordpress.run-php" && (spec.args ?? []).some((arg) => arg.includes("emit-rest-db-profile"))) {
@@ -104,6 +114,24 @@ assert.equal(result.schema, "wp-codebox/fuzz-suite-result/v1")
104114
assert.equal(result.status, "passed")
105115
assert.deepEqual(executed.map((spec) => spec.command), ["wordpress.run-php"])
106116

117+
executed.length = 0
118+
const checkpointSuite = fuzzSuiteContract({
119+
id: "nested-checkpoint-suite",
120+
resetPolicy: { mode: "checkpoint-per-case", checkpointName: "nested-baseline" },
121+
cases: [
122+
{ id: "checkpoint-case-one", target: { kind: "command", id: "wordpress.run-php", entrypoint: "wordpress.run-php" }, input: { args: ["code=echo 'one';"] } },
123+
{ id: "checkpoint-case-two", target: { kind: "command", id: "wordpress.run-php", entrypoint: "wordpress.run-php" }, input: { args: ["code=echo 'two';"] } },
124+
],
125+
})
126+
const checkpointExecution = await executeRecipeWorkflowStep(runtime, { phase: "steps", index: 0, step: { command: "wp-codebox/run-fuzz-suite", args: [`input-json=${JSON.stringify(checkpointSuite)}`] } }, process.cwd())
127+
const checkpointResult = JSON.parse(checkpointExecution.stdout)
128+
assert.equal(checkpointExecution.exitCode, 0)
129+
assert.equal(checkpointResult.status, "passed")
130+
assert.equal(checkpointCreates, 1)
131+
assert.equal(checkpointRestores, 2)
132+
assert.deepEqual(checkpointResult.cases.map((fuzzCase: { id: string; reset: { mode: string; status: string } }) => [fuzzCase.id, fuzzCase.reset.mode, fuzzCase.reset.status]), [["checkpoint-case-one", "checkpoint-per-case", "passed"], ["checkpoint-case-two", "checkpoint-per-case", "passed"]])
133+
assert.deepEqual(executed.map((spec) => spec.command), ["wordpress.run-php", "wordpress.run-php"])
134+
107135
const suiteAliasDir = await mkdtemp(join(tmpdir(), "wp-codebox-nested-fuzz-suite-alias-"))
108136
await writeFile(join(suiteAliasDir, "suite.json"), JSON.stringify(suite), "utf8")
109137
const suiteAliasRecipe: WorkspaceRecipe = {

0 commit comments

Comments
 (0)