Skip to content

Commit 524d376

Browse files
authored
Run distribution startup probes (#1237)
1 parent 30e4ac9 commit 524d376

9 files changed

Lines changed: 195 additions & 13 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
"test:generic-ability-runtime-run": "tsx tests/generic-ability-runtime-run.test.ts",
115115
"test:provider-runtime-contracts": "tsx tests/provider-runtime-contracts.test.ts",
116116
"test:recipe-source-packages": "tsx tests/recipe-source-packages.test.ts",
117+
"test:distribution-startup-probes": "tsx tests/distribution-startup-probes.test.ts",
117118
"test:path-policy-parity": "tsx tests/path-policy-parity.test.ts",
118119
"test:php-path-policy-parity": "php scripts/php-path-policy-parity-smoke.php",
119120
"test:production-boundary-enforcement": "tsx tests/production-boundary-enforcement.test.ts",

packages/cli/src/commands/recipe-declared-artifacts.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { DEFAULT_CAPTURED_ARTIFACT_MAX_BYTES, STRUCTURED_ARTIFACT_SCHEMA, TYPED_
33
import { stripUndefined } from "@automattic/wp-codebox-core/internals"
44
import { appendRecipeRuntimeEvidenceFiles } from "../recipe-evidence.js"
55
import { serializeRecipeRunError, RecipeDeclaredArtifactFailureError, RecipeProbeFailureError } from "./recipe-run-output.js"
6-
import type { RecipeRunDeclaredArtifact, RecipeRunFixtureDatabase, RecipeRunProbe } from "./recipe-run-types.js"
6+
import type { RecipeRunDeclaredArtifact, RecipeRunDistributionStartupProbe, RecipeRunFixtureDatabase, RecipeRunProbe } from "./recipe-run-types.js"
77

88
const DECLARED_ARTIFACT_CAPTURE_MAX_BYTES = DEFAULT_CAPTURED_ARTIFACT_MAX_BYTES
99
const declaredArtifactContents = new WeakMap<RecipeRunDeclaredArtifact, Buffer>()
@@ -208,14 +208,19 @@ export function recipeDeclaredArtifactFailure(declaredArtifacts: RecipeRunDeclar
208208
return declaredArtifacts.some((artifact) => artifact.required && artifact.status !== "collected") ? new RecipeDeclaredArtifactFailureError(declaredArtifacts) : undefined
209209
}
210210

211-
export function recipeRuntimeEvidenceFiles(fixtureDatabases: RecipeRunFixtureDatabase[], probes: RecipeRunProbe[], declaredArtifacts: RecipeRunDeclaredArtifact[]): Array<{ filename: string; kind: string; value: unknown }> {
211+
export function recipeRuntimeEvidenceFiles(fixtureDatabases: RecipeRunFixtureDatabase[], distributionStartupProbes: RecipeRunDistributionStartupProbe[], probes: RecipeRunProbe[], declaredArtifacts: RecipeRunDeclaredArtifact[]): Array<{ filename: string; kind: string; value: unknown }> {
212212
return [
213213
...(fixtureDatabases.length > 0 ? [{ filename: "fixture-databases.json", kind: "fixture-database-results", value: { schema: "wp-codebox/fixture-database-results/v1", fixtures: fixtureDatabases } }] : []),
214+
...(distributionStartupProbes.length > 0 ? [{ filename: "distribution-startup-probes.json", kind: "distribution-startup-probe-results", value: { schema: "wp-codebox/distribution-startup-probe-results/v1", passed: !distributionStartupProbeFailure(distributionStartupProbes), probes: distributionStartupProbes } }] : []),
214215
...(probes.length > 0 ? [{ filename: "recipe-probes.json", kind: "recipe-probe-results", value: { schema: "wp-codebox/recipe-probe-results/v1", passed: !recipeProbeFailure(probes), probes } }] : []),
215216
...(declaredArtifacts.length > 0 ? [{ filename: "recipe-declared-artifacts.json", kind: "recipe-declared-artifact-results", value: { schema: "wp-codebox/recipe-declared-artifact-results/v1", passed: !recipeDeclaredArtifactFailure(declaredArtifacts), artifacts: declaredArtifacts } }] : []),
216217
]
217218
}
218219

220+
function distributionStartupProbeFailure(probes: RecipeRunDistributionStartupProbe[]): boolean {
221+
return probes.some((probe) => probe.status === "failed")
222+
}
223+
219224
export function recipeProbeFailure(probes: RecipeRunProbe[]): RecipeProbeFailureError | undefined {
220225
return probes.some((probe) => probe.status === "failed" && !probe.allowFailure) ? new RecipeProbeFailureError(probes) : undefined
221226
}

packages/cli/src/commands/recipe-run-finalizer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ interface RecipeRunCommonOutputFields {
4242
stagedFiles?: RecipeRunStagedFile[]
4343
fixtureDatabases?: RecipeRunFixtureDatabase[]
4444
siteSeeds?: RecipeRunSiteSeed[]
45+
distributionStartupProbes?: RecipeRunOutput["distributionStartupProbes"]
4546
probes?: RecipeRunProbe[]
4647
declaredArtifacts?: RecipeRunDeclaredArtifact[]
4748
phaseEvidence?: RecipePhaseEvidence[]
@@ -97,6 +98,7 @@ export function completedRecipeOutputFields(args: {
9798
stagedFiles: RecipeRunStagedFile[]
9899
fixtureDatabases: RecipeRunFixtureDatabase[]
99100
siteSeeds: RecipeRunOutput["siteSeeds"]
101+
distributionStartupProbes: NonNullable<RecipeRunOutput["distributionStartupProbes"]>
100102
probes: RecipeRunProbe[]
101103
declaredArtifacts: RecipeRunDeclaredArtifact[]
102104
phaseEvidence: RecipePhaseEvidence[]
@@ -111,6 +113,7 @@ export function completedRecipeOutputFields(args: {
111113
stagedFiles: args.stagedFiles,
112114
fixtureDatabases: args.fixtureDatabases,
113115
siteSeeds: args.siteSeeds,
116+
...(args.distributionStartupProbes.length > 0 ? { distributionStartupProbes: args.distributionStartupProbes } : {}),
114117
probes: args.probes,
115118
declaredArtifacts: args.declaredArtifacts,
116119
phaseEvidence: args.phaseEvidence,

packages/cli/src/commands/recipe-run-types.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface RecipeRunOutput {
4949
stagedFiles?: RecipeRunStagedFile[]
5050
fixtureDatabases?: RecipeRunFixtureDatabase[]
5151
siteSeeds?: RecipeRunSiteSeed[]
52+
distributionStartupProbes?: RecipeRunDistributionStartupProbe[]
5253
probes?: RecipeRunProbe[]
5354
declaredArtifacts?: RecipeRunDeclaredArtifact[]
5455
phaseEvidence?: RecipePhaseEvidence[]
@@ -149,7 +150,7 @@ export interface RecipeDiagnosticArtifactRef {
149150
sha256?: string
150151
}
151152

152-
export type RecipePhaseName = "runtime_startup" | "mount_plugins" | "activate_plugins" | "run_blueprint_steps" | "import_fixture_databases" | "run_workloads" | "run_probes" | "collect_artifacts"
153+
export type RecipePhaseName = "runtime_startup" | "mount_plugins" | "activate_plugins" | "run_blueprint_steps" | "import_fixture_databases" | "run_distribution_startup_probes" | "run_workloads" | "run_probes" | "collect_artifacts"
153154

154155
export interface RecipePhaseEvidence {
155156
schema: "wp-codebox/recipe-phase-evidence/v1"
@@ -243,6 +244,21 @@ export interface RecipeRunProbe {
243244
metadata?: Record<string, unknown>
244245
}
245246

247+
export interface RecipeRunDistributionStartupProbe {
248+
schema: "wp-codebox/distribution-startup-probe-result/v1"
249+
index: number
250+
name: string
251+
type: "http" | "browser" | "wp-cli" | "php"
252+
status: "passed" | "failed" | "skipped"
253+
command?: string
254+
args?: string[]
255+
exitCode?: number
256+
stdout?: string
257+
stderr?: string
258+
reason?: string
259+
metadata?: Record<string, unknown>
260+
}
261+
246262
export interface RecipeRunDeclaredArtifact {
247263
schema: "wp-codebox/recipe-declared-artifact-result/v1"
248264
index: number

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

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { type ArtifactBundle, type ArtifactManifestFile, type ExecutionResult, type Runtime, type WorkspaceRecipe, type WorkspaceRecipeProbe } from "@automattic/wp-codebox-core"
1+
import { type ArtifactBundle, type ArtifactManifestFile, type ExecutionResult, type Runtime, type WorkspaceRecipe, type WorkspaceRecipeDistributionStartupProbe, type WorkspaceRecipeProbe } from "@automattic/wp-codebox-core"
22
import { stripUndefined } from "@automattic/wp-codebox-core/internals"
33
import { recipeExecutionSpec, sandboxWorkspaceContract } from "../agent-sandbox.js"
44
import { executeAgentFanoutFromArgs } from "../agent-fanout.js"
55
import { recipeWorkflowSteps, type RecipeWorkflowPhase } from "../recipe-validation.js"
66
import { artifactManifestFilesByPath } from "./recipe-run-benchmark-artifacts.js"
77
import { serializeRecipeRunError } from "./recipe-run-output.js"
8-
import type { RecipeAdvisoryFailure, RecipeBrowserEvidence, RecipeBrowserEvidenceFileRef, RecipeExecutionResult, RecipeRunOptions, RecipeRunProbe } from "./recipe-run-types.js"
8+
import type { RecipeAdvisoryFailure, RecipeBrowserEvidence, RecipeBrowserEvidenceFileRef, RecipeExecutionResult, RecipeRunDistributionStartupProbe, RecipeRunOptions, RecipeRunProbe } from "./recipe-run-types.js"
99

1010
export function withRecipeExecutionPhase(execution: ExecutionResult, recipePhase: RecipeWorkflowPhase, recipeStepIndex: number, recipeCommand?: string): RecipeExecutionResult {
1111
return {
@@ -177,6 +177,46 @@ export async function runRecipeProbes(recipe: WorkspaceRecipe, recipeDirectory:
177177
return results
178178
}
179179

180+
export async function runDistributionStartupProbes(recipe: WorkspaceRecipe, runtime: Runtime, executions: RecipeExecutionResult[]): Promise<RecipeRunDistributionStartupProbe[]> {
181+
const results: RecipeRunDistributionStartupProbe[] = []
182+
for (const [index, probe] of (recipe.distribution?.startupProbes ?? []).entries()) {
183+
if (probe.type === "http" || probe.type === "browser") {
184+
results.push(stripUndefined({
185+
schema: "wp-codebox/distribution-startup-probe-result/v1" as const,
186+
index,
187+
name: probe.name,
188+
type: probe.type,
189+
status: "skipped" as const,
190+
reason: "Distribution startup probe type is planned but not executable by this runtime primitive.",
191+
metadata: probe.metadata,
192+
}))
193+
continue
194+
}
195+
196+
const execution = await executeDistributionStartupProbe(runtime, probe, index)
197+
executions.push(execution)
198+
results.push(stripUndefined({
199+
schema: "wp-codebox/distribution-startup-probe-result/v1" as const,
200+
index,
201+
name: probe.name,
202+
type: probe.type,
203+
status: execution.exitCode === 0 ? "passed" as const : "failed" as const,
204+
command: execution.command,
205+
args: execution.args,
206+
exitCode: execution.exitCode,
207+
stdout: execution.stdout,
208+
stderr: execution.stderr,
209+
metadata: probe.metadata,
210+
}))
211+
}
212+
return results
213+
}
214+
215+
export function distributionStartupProbeFailure(probes: RecipeRunDistributionStartupProbe[]): Error | undefined {
216+
const failed = probes.find((probe) => probe.status === "failed")
217+
return failed ? new Error(`Distribution startup probe "${failed.name}" failed with exit code ${failed.exitCode ?? "unknown"}.`) : undefined
218+
}
219+
180220
async function executeRecipeProbe(runtime: Runtime, probe: WorkspaceRecipeProbe, recipeDirectory: string, index: number): Promise<RecipeExecutionResult> {
181221
try {
182222
const execution = await runtime.execute(await recipeExecutionSpec(probe.step, recipeDirectory))
@@ -187,6 +227,19 @@ async function executeRecipeProbe(runtime: Runtime, probe: WorkspaceRecipeProbe,
187227
}
188228
}
189229

230+
async function executeDistributionStartupProbe(runtime: Runtime, probe: WorkspaceRecipeDistributionStartupProbe, index: number): Promise<RecipeExecutionResult> {
231+
const spec = probe.type === "wp-cli"
232+
? { command: "wordpress.wp-cli", args: [`command=${probe.command ?? ""}`] }
233+
: { command: "wordpress.run-php", args: [`code=${probe.code ?? ""}`] }
234+
try {
235+
const execution = await runtime.execute(spec)
236+
return withRecipeExecutionPhase(execution, "setup", index, `distribution.startupProbe:${probe.name}`)
237+
} catch (error) {
238+
const message = error instanceof Error ? error.message : String(error)
239+
throw new Error(`Distribution startup probe "${probe.name}" failed before producing a result: ${message}`, { cause: error })
240+
}
241+
}
242+
190243
function parseProbeJson(stdout: string): unknown | undefined {
191244
const trimmed = stdout.trim()
192245
if (!trimmed) {

0 commit comments

Comments
 (0)