Skip to content

Commit 6dfeea1

Browse files
authored
Expose editor actions to fuzz sequences (#1860)
* Add editor runtime fuzz actions * Preserve editor fuzz failure artifacts * Persist editor fuzz failure artifacts * Serialize durable fuzz case refs
1 parent f3bfe3d commit 6dfeea1

17 files changed

Lines changed: 459 additions & 67 deletions

packages/runtime-core/src/artifact-layout.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdir, writeFile } from "node:fs/promises"
1+
import { copyFile, mkdir, writeFile } from "node:fs/promises"
22
import { dirname, isAbsolute, relative, resolve } from "node:path"
33

44
import { artifactManifestFile, refreshArtifactManifestFileSha256s, type ArtifactManifest, type ArtifactManifestFile, type ArtifactManifestFileOptions, type ArtifactViewerMetadata } from "./artifact-manifest.js"
@@ -73,6 +73,12 @@ export class ArtifactBundleWriter {
7373
await write(this.path(path))
7474
}
7575

76+
async importFile(path: string, sourcePath: string, manifest: Omit<ManifestedArtifactFileInput, "path">): Promise<void> {
77+
await this.writeGenerated(path, manifest, async (destinationPath) => {
78+
await copyFile(sourcePath, destinationPath)
79+
})
80+
}
81+
7682
async writeManifest<T extends ArtifactManifest>(manifest: T): Promise<T> {
7783
this.artifacts.add({ path: this.manifestPath, kind: "manifest", contentType: "application/json" })
7884
manifest.files = this.artifacts.files()

packages/runtime-core/src/fuzz-suite-contracts.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ export const RUNTIME_BACKED_FUZZ_SUITE_RUNNER_CAPABILITIES: FuzzSuiteRunnerCapab
252252
"runtime-action:random_walk",
253253
"runtime-action:crud_operation",
254254
"runtime-action:db_operation",
255+
"runtime-action:editor_actions",
255256
"runtime-action:editor_open",
257+
"runtime-action:editor_validate_blocks",
256258
"runtime-action:page",
257259
"runtime-action:php",
258260
"runtime-action:rest_request",
@@ -279,8 +281,8 @@ export const RUNTIME_BACKED_FUZZ_SUITE_RUNNER_CAPABILITIES: FuzzSuiteRunnerCapab
279281
],
280282
targetKinds: ["ability", "command", "http", "rest", "runtime", "runtime-action"],
281283
operationKinds: ["read", "crud", "mutation-isolation", "delete-boundary"],
282-
runtimeActionTypes: ["admin_page", "browser", "browser_probe", "crud_operation", "db_operation", "editor_open", "page", "php", "random_walk", "rest_request", "sequence", "wp_cli", "wordpress_hook", "wordpress_cron_event"],
283-
commands: ["wp-codebox.checkpoint-create", "wp-codebox.checkpoint-list", "wp-codebox.checkpoint-restore", "wordpress.ability", "wordpress.browser-actions", "wordpress.browser-page-load", "wordpress.browser-probe", "wordpress.cache-churn-observation", "wordpress.collect-workload-result", "wordpress.crud-operation", "wordpress.db-operation", "wordpress.editor-open", "wordpress.execution-surfaces", "wordpress.fuzz-admin-pages", "wordpress.fuzz-plugin-module-state", "wordpress.http-request", "wordpress.inventory-plugin-module-options-tables", "wordpress.invoke-cron-event", "wordpress.invoke-hook", "wordpress.invoke-wp-cli", "wordpress.rest-performance-observation", "wordpress.rest-request", "wordpress.run-php", "wordpress.run-workload", "wordpress.server-page-load", "wordpress.simulated-admin-page-load", "wordpress.simulated-frontend-page-load", "wordpress.wp-cli"],
284+
runtimeActionTypes: ["admin_page", "browser", "browser_probe", "crud_operation", "db_operation", "editor_actions", "editor_open", "editor_validate_blocks", "page", "php", "random_walk", "rest_request", "sequence", "wp_cli", "wordpress_hook", "wordpress_cron_event"],
285+
commands: ["wp-codebox.checkpoint-create", "wp-codebox.checkpoint-list", "wp-codebox.checkpoint-restore", "wordpress.ability", "wordpress.browser-actions", "wordpress.browser-page-load", "wordpress.browser-probe", "wordpress.cache-churn-observation", "wordpress.collect-workload-result", "wordpress.crud-operation", "wordpress.db-operation", "wordpress.editor-actions", "wordpress.editor-open", "wordpress.editor-validate-blocks", "wordpress.execution-surfaces", "wordpress.fuzz-admin-pages", "wordpress.fuzz-plugin-module-state", "wordpress.http-request", "wordpress.inventory-plugin-module-options-tables", "wordpress.invoke-cron-event", "wordpress.invoke-hook", "wordpress.invoke-wp-cli", "wordpress.rest-performance-observation", "wordpress.rest-request", "wordpress.run-php", "wordpress.run-workload", "wordpress.server-page-load", "wordpress.simulated-admin-page-load", "wordpress.simulated-frontend-page-load", "wordpress.wp-cli"],
284286
unsupportedRequiredCapabilities: [],
285287
}
286288

@@ -312,8 +314,8 @@ export const WORDPRESS_FUZZ_RUNTIME_CONTRACT: WordPressFuzzRuntimeContract = {
312314
id: "runtime-browser-admin",
313315
label: "Runtime browser, admin page, editor, and page coverage",
314316
targetKinds: ["runtime", "runtime-action"],
315-
runtimeActionTypes: ["admin_page", "browser", "browser_probe", "editor_open", "page", "random_walk", "sequence"],
316-
commands: ["wordpress.browser-actions", "wordpress.browser-page-load", "wordpress.browser-probe", "wordpress.editor-open", "wordpress.fuzz-admin-pages", "wordpress.page", "wordpress.simulated-admin-page-load", "wordpress.simulated-frontend-page-load"],
317+
runtimeActionTypes: ["admin_page", "browser", "browser_probe", "editor_actions", "editor_open", "editor_validate_blocks", "page", "random_walk", "sequence"],
318+
commands: ["wordpress.browser-actions", "wordpress.browser-page-load", "wordpress.browser-probe", "wordpress.editor-actions", "wordpress.editor-open", "wordpress.editor-validate-blocks", "wordpress.fuzz-admin-pages", "wordpress.page", "wordpress.simulated-admin-page-load", "wordpress.simulated-frontend-page-load"],
317319
mutationIntents: ["read", "write"],
318320
supported: true,
319321
},

packages/runtime-core/src/fuzz-suite-runner.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { stripUndefined } from "./object-utils.js"
22
import { planBrowserRandomWalk } from "./browser-interaction.js"
33
import { FUZZ_RUNNER_CAPABILITIES_SCHEMA, RUNTIME_BACKED_FUZZ_SUITE_RUNNER_CAPABILITIES, fuzzRunnerCapabilitiesContract, fuzzSuiteCaseResetPolicy, fuzzSuiteRequiredRunnerCapabilities, fuzzSuiteResetPolicyDiagnostics, fuzzSuiteResultEnvelope, unsupportedRequiredFuzzRunnerCapabilities, type FuzzSuiteArtifactRef, type FuzzSuiteCase, type FuzzSuiteCaseResetResult, type FuzzSuiteCaseResult, type FuzzSuiteContract, type FuzzSuiteDiagnostic, type FuzzSuiteResetPolicy, type FuzzSuiteRunnerCapabilities, type FuzzSuiteTargetRef } from "./fuzz-suite-contracts.js"
44
import { DELETE_BOUNDARY_ARTIFACT_KIND, DELETE_BOUNDARY_ARTIFACT_SCHEMA, MUTATION_ISOLATION_ARTIFACT_KIND, MUTATION_ISOLATION_ARTIFACT_SCHEMA, isRestMutationMethod } from "./mutation-isolation-contracts.js"
5-
import type { RuntimeAction, RuntimeActionObservation } from "./runtime-action-adapter.js"
5+
import { RuntimeActionExecutionError, type RuntimeAction, type RuntimeActionObservation } from "./runtime-action-adapter.js"
66
import type { ExecutionResult, ExecutionSpec, RuntimeCommandDiagnosticsCaptureSpec, RuntimeEpisodeTraceRef, WorkspaceRecipeStep } from "./runtime-contracts.js"
77
import { WORDPRESS_CRUD_OPERATION_SCHEMA, normalizeWordPressCrudOperation } from "./wordpress-crud-contracts.js"
88
import { WORDPRESS_DB_OPERATION_SCHEMA, normalizeWordPressDbOperation } from "./wordpress-db-contracts.js"
@@ -249,7 +249,7 @@ export async function runFuzzSuite(suite: FuzzSuiteContract, options: FuzzSuiteR
249249
} catch (error) {
250250
const diagnostic: FuzzSuiteDiagnostic = { severity: "error", code: "fuzz_suite_runtime_action_sequence_execution_error", caseId: fuzzCase.id, target, message: error instanceof Error ? error.message : String(error) }
251251
diagnostics.push(diagnostic)
252-
cases.push({ id: fuzzCase.id, status: "error", success: false, target, reset, diagnostics: [diagnostic], metadata: stripUndefined({ replay: replayMetadata, adapter: { ...plan.metadata, adapterKind: "runtime-action", actionType: "sequence", executorKind: "episode" } }) })
252+
cases.push({ id: fuzzCase.id, status: "error", success: false, target, reset, diagnostics: [diagnostic], artifactRefs: fuzzSuiteRuntimeActionErrorArtifactRefs(error), metadata: stripUndefined({ replay: replayMetadata, adapter: { ...plan.metadata, adapterKind: "runtime-action", actionType: "sequence", executorKind: "episode" } }) })
253253
}
254254
continue
255255
}
@@ -304,6 +304,7 @@ export async function runFuzzSuite(suite: FuzzSuiteContract, options: FuzzSuiteR
304304
target,
305305
reset,
306306
diagnostics: [diagnostic],
307+
artifactRefs: fuzzSuiteRuntimeActionErrorArtifactRefs(error),
307308
metadata: stripUndefined({ replay: replayMetadata, adapter: { ...plan.metadata, adapterKind: "runtime-action", actionType: runtimeAction.action.type, executorKind: "episode" } }),
308309
})
309310
}
@@ -965,6 +966,25 @@ function runtimeActionFuzzSuiteTargetAdapter(): FuzzSuiteTargetAdapter {
965966
}
966967
}
967968

969+
if (input.payload.type === "editor_actions") {
970+
if (!Array.isArray(input.payload.steps)) {
971+
return unsupportedInputAdapterResolution(fuzzCase, target, "Expected editor_actions runtime-action input steps array.", { adapterKind: "runtime-action", actionType: input.payload.type })
972+
}
973+
return {
974+
status: "supported",
975+
spec: stripUndefined({ command: "wordpress.editor-actions", args: runtimeEditorActionsArgs(input.payload), timeoutMs: runtimeActionTimeoutMs(input.payload, input.timeoutMs) }) as ExecutionSpec,
976+
metadata: { adapterKind: "runtime-action", actionType: input.payload.type, mappedCommand: "wordpress.editor-actions" },
977+
}
978+
}
979+
980+
if (input.payload.type === "editor_validate_blocks") {
981+
return {
982+
status: "supported",
983+
spec: stripUndefined({ command: "wordpress.editor-validate-blocks", args: runtimeEditorValidateBlocksArgs(input.payload), timeoutMs: runtimeActionTimeoutMs(input.payload, input.timeoutMs) }) as ExecutionSpec,
984+
metadata: { adapterKind: "runtime-action", actionType: input.payload.type, mappedCommand: "wordpress.editor-validate-blocks" },
985+
}
986+
}
987+
968988
if (input.payload.type === "admin_page" || input.payload.type === "page") {
969989
const command = input.payload.type === "admin_page" ? "wordpress.simulated-admin-page-load" : "wordpress.simulated-frontend-page-load"
970990
return {
@@ -1168,6 +1188,8 @@ function fuzzSuiteRuntimeActionMutationClassification(action: RuntimeAction): {
11681188
if (action.type === "browser") return { mutates: ["click", "fill", "press", "select"].includes(action.operation), metadata: { actionType: action.type, operation: action.operation } }
11691189
if (action.type === "admin_page" || action.type === "page") return { mutates: false, metadata: { actionType: action.type } }
11701190
if (action.type === "editor_open") return { mutates: false, metadata: { actionType: action.type } }
1191+
if (action.type === "editor_actions") return { mutates: action.steps.some((step) => step.kind === "savePost"), metadata: { actionType: action.type, steps: action.steps.length } }
1192+
if (action.type === "editor_validate_blocks") return { mutates: false, metadata: { actionType: action.type } }
11711193
return { mutates: false, metadata: { actionType: action.type } }
11721194
}
11731195

@@ -1357,6 +1379,24 @@ function runtimeEditorOpenArgs(input: Record<string, unknown>): string[] {
13571379
].filter((arg): arg is string => Boolean(arg))
13581380
}
13591381

1382+
function runtimeEditorActionsArgs(input: Record<string, unknown>): string[] {
1383+
return [
1384+
...runtimeEditorOpenArgs(input),
1385+
`steps-json=${JSON.stringify(input.steps)}`,
1386+
durationMsArg("wait-timeout", input.wait_timeout_ms ?? input.waitTimeoutMs),
1387+
durationMsArg("step-timeout", input.step_timeout_ms ?? input.stepTimeoutMs),
1388+
].filter((arg): arg is string => Boolean(arg))
1389+
}
1390+
1391+
function runtimeEditorValidateBlocksArgs(input: Record<string, unknown>): string[] {
1392+
return [
1393+
optionalStringArg("content", input.content),
1394+
optionalStringArg("content-file", input.content_file ?? input.contentFile),
1395+
...runtimeEditorOpenArgs(input),
1396+
optionalStringArg("validation-provider", input.validation_provider ?? input.validationProvider),
1397+
].filter((arg): arg is string => Boolean(arg))
1398+
}
1399+
13601400
function durationMsArg(name: string, value: unknown): string | undefined {
13611401
return typeof value === "number" && Number.isFinite(value) ? `${name}=${value}ms` : undefined
13621402
}
@@ -1401,7 +1441,13 @@ function fuzzSuiteExecutionArtifactRefs(execution: ExecutionResult): FuzzSuiteAr
14011441
}
14021442

14031443
function fuzzSuiteRuntimeActionArtifactRefs(observation: RuntimeActionObservation): FuzzSuiteArtifactRef[] | undefined {
1404-
const refs = (observation.artifactRefs ?? []).map(fuzzSuiteArtifactRefFromTrace).filter((ref): ref is FuzzSuiteArtifactRef => Boolean(ref))
1444+
const refs = [...(observation.artifactRefs ?? []), ...(observation.step?.execution.artifactRefs ?? []), ...(observation.step?.execution.result?.artifactRefs ?? [])].map(fuzzSuiteArtifactRefFromTrace).filter((ref): ref is FuzzSuiteArtifactRef => Boolean(ref))
1445+
return refs.length > 0 ? refs : undefined
1446+
}
1447+
1448+
function fuzzSuiteRuntimeActionErrorArtifactRefs(error: unknown): FuzzSuiteArtifactRef[] | undefined {
1449+
if (!(error instanceof RuntimeActionExecutionError)) return undefined
1450+
const refs = error.artifactRefs.map(fuzzSuiteArtifactRefFromTrace).filter((ref): ref is FuzzSuiteArtifactRef => Boolean(ref))
14051451
return refs.length > 0 ? refs : undefined
14061452
}
14071453

@@ -1459,8 +1505,9 @@ function fuzzSuiteArtifactRefFromTrace(ref: RuntimeEpisodeTraceRef): FuzzSuiteAr
14591505
return stripUndefined({
14601506
path,
14611507
kind: ref.kind,
1508+
contentType: ref.contentType,
14621509
sha256: ref.digest?.algorithm === "sha256" ? ref.digest.value : undefined,
1463-
metadata: stripUndefined({ id: ref.id, artifactId: ref.artifactId, digest: ref.digest }),
1510+
metadata: stripUndefined({ id: ref.id, artifactId: ref.artifactId, digest: ref.digest, sourcePath: ref.sourcePath }),
14641511
})
14651512
}
14661513

packages/runtime-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export * from "./recipe-source-packages.js"
5959
export * from "./mount-primitives.js"
6060
export * from "./benchmark-contracts.js"
6161
export * from "./runtime-episode.js"
62+
export * from "./runtime-action-adapter.js"
6263
export * from "./runtime-reference.js"
6364
export * from "./transfer-proof.js"
6465
export * from "./host-tool-registry.js"

packages/runtime-core/src/public.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export * from "./runtime-contract-manifest.js"
6666
export * from "./runtime-command-result.js"
6767
export * from "./runtime-contracts.js"
6868
export * from "./runtime-episode.js"
69+
export * from "./runtime-action-adapter.js"
6970
export * from "./runtime-neutral-contracts.js"
7071
export * from "./runtime-overlay-bundle.js"
7172
export * from "./runtime-overlay-descriptors.js"

0 commit comments

Comments
 (0)