Skip to content
Draft
78 changes: 78 additions & 0 deletions docs/native-php-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Native PHP Runtime Adapter

`runtime.backend: "wordpress-native"` selects the native adapter without changing
backend-neutral recipe command IDs. The adapter never substitutes host PHP,
ambient credentials, or production state when a contained driver is unavailable.

## Built-in Driver

`wordpress-native` now uses the built-in Docker driver when no `backendPackage` is
declared. It creates an isolated internal Docker network, a disposable MariaDB
sidecar on tmpfs, and a digest-pinned WordPress/PHP Apache container. Apache prefork
is configured with two persistent workers and PHP OPcache has timestamp validation
configuration. Docker is a hard requirement: the adapter fails closed rather than
using host PHP or host credentials.

The driver creates a random fixture-only database/admin secret for every run. It
never imports a host browser profile. `wordpress.browser-actions` captures a
machine-readable local network record, and `wordpress.bench` emits cold startup,
warm no-op PHP, and dynamic HTTP timings marked as local evidence, not production
RUM. The native artifact bundle contains `files/native/browser-network.json` and
`files/native/commands.json` alongside `native-runtime-provenance.json`.

## Optional Driver Contract

The CLI can load an adapter-owned contained driver from `runtime.backendPackage` when
the backend is `wordpress-native`. Its package uses `kind: "native"` and exports
`createNativeRuntimeDriver()`. The driver must report a digest-pinned container
image, PHP version and SAPI, persistent enabled OPcache evidence, at least two HTTP
workers, and a disposable managed-runtime-service database. Startup rejects
incomplete evidence and destroys a partially created runtime.

Before accepting commands, the driver persists
`wp-codebox/native-runtime-provenance/v1` in the run artifact directory and returns
its path and SHA-256. The evidence identifies the backend, PHP/SAPI, image digest,
OPcache configuration/status, worker model, managed database integration, and that
measurements are local representative evidence rather than production RUM. The
driver owns process and container lifecycle; `destroy()` is single-flight and the
adapter terminalizes the runtime even when driver cleanup reports an error.

Browser commands remain backend-neutral. A native driver must expose the local
preview and authenticated fixture workflow through the existing browser-action
contract; it must not use caller browser credentials.

`wordpress.browser-actions` must declare `auth=wordpress-admin` or
`auth=storage-state`. Native drivers accept only runtime-generated fixture state;
they must not import a caller profile or ambient browser credentials.

## HTTP Concurrency

The driver records its resolved worker count and worker model in
`provenance.httpConcurrency`. This is local representative evidence, not
production RUM. Benchmark workloads should record cold startup, warm no-op PHP,
and a dynamic WordPress request for both `wordpress-playground` and
`wordpress-native` using `wordpress.bench`.

The native provenance contract records that all three cases are covered. Cold
startup crosses a worker/process boundary, warm no-op PHP retains the shared
OPcache, and the dynamic request exercises WordPress routing. These are local
representative measurements, not production RUM.

## Example optional selection

```json
{
"runtime": {
"backend": "wordpress-native",
"backendPackage": {
"kind": "native",
"source": "./contained-native-driver"
}
}
}
```

The optional native package is responsible for translating the existing backend-neutral
`wordpress.*` commands, including browser actions and fixture authentication, into
its contained runtime. It must not read host PHP configuration, browser profiles,
ambient credentials, or production state.
14 changes: 14 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
"types": "./packages/runtime-playground/dist/public.d.ts",
"import": "./packages/runtime-playground/dist/public.js"
},
"./native": {
"types": "./packages/runtime-native/dist/index.d.ts",
"import": "./packages/runtime-native/dist/index.js"
},
"./cli": {
"types": "./packages/cli/dist/index.d.ts",
"import": "./packages/cli/dist/index.js"
Expand All @@ -75,6 +79,8 @@
"files": [
"packages/runtime-core/dist",
"packages/runtime-core/package.json",
"packages/runtime-native/dist",
"packages/runtime-native/package.json",
"packages/runtime-playground/dist",
"packages/runtime-playground/package.json",
"packages/cli/dist",
Expand All @@ -88,8 +94,8 @@
"LICENSE"
],
"scripts": {
"build": "node ./node_modules/typescript/bin/tsc -b packages/runtime-core packages/runtime-playground packages/cli && node scripts/ensure-cli-bin-executable.mjs && tsx scripts/write-cli-build-provenance.ts",
"build:release": "node ./node_modules/typescript/bin/tsc -b --force packages/runtime-core packages/runtime-playground packages/cli && node scripts/ensure-cli-bin-executable.mjs && tsx scripts/write-cli-build-provenance.ts",
"build": "node ./node_modules/typescript/bin/tsc -b --force packages/runtime-core packages/runtime-playground packages/runtime-native packages/cli && node scripts/ensure-cli-bin-executable.mjs && tsx scripts/write-cli-build-provenance.ts",
"build:release": "node ./node_modules/typescript/bin/tsc -b --force packages/runtime-core packages/runtime-playground packages/runtime-native packages/cli && node scripts/ensure-cli-bin-executable.mjs && tsx scripts/write-cli-build-provenance.ts",
"cloudflare:build": "npm --prefix packages/runtime-cloudflare run build",
"cloudflare:check": "npm --prefix packages/runtime-cloudflare run check",
"cloudflare:package-dry-run": "npm --prefix packages/runtime-cloudflare run package:dry-run",
Expand Down Expand Up @@ -156,6 +162,8 @@
"test:runtime-command-artifact-bounds": "tsx tests/runtime-command-artifact-bounds.test.ts",
"test:native-agent-task-interruption": "node tests/execute-native-agent-task-interruption.test.mjs",
"test:native-agent-task-playground-e2e": "tsx tests/execute-native-agent-task-playground-e2e.test.ts",
"test:native-runtime": "tsx tests/native-runtime.test.ts && tsx scripts/backend-package-adapter-registry-smoke.ts",
"test:native-docker-runtime": "npm run build && tsx tests/native-docker-runtime.integration.test.ts",
"test:bench-command-step-behavior": "tsx tests/bench-command-step-behavior.test.ts",
"test:external-http-load-integration": "npm run build && tsx tests/external-http-load.integration.test.ts",
"test:generic-primitives": "npm run test:artifact-path-primitives && npm run test:browser-callback-materialization-contracts && npm run test:source-package-compiler-primitives && npm run test:bench-command-step-behavior && npm run test:generic-ability-runtime-run",
Expand All @@ -181,6 +189,7 @@
"workspaces": [
"packages/cli",
"packages/runtime-core",
"packages/runtime-native",
"packages/runtime-playground",
"packages/wordpress-plugin"
],
Expand All @@ -198,6 +207,7 @@
"dependencies": {
"@automattic/wp-codebox-cli": "file:packages/cli",
"@automattic/wp-codebox-core": "file:packages/runtime-core",
"@automattic/wp-codebox-native": "file:packages/runtime-native",
"@automattic/wp-codebox-playground": "file:packages/runtime-playground",
"@php-wasm/node-8-3": "file:runtime-overlays/php-wasm-node-8-3",
"@php-wasm/node-8-4": "3.1.46",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"dependencies": {
"@automattic/wp-codebox-core": "file:../runtime-core",
"@automattic/wp-codebox-native": "file:../runtime-native",
"@automattic/wp-codebox-playground": "file:../runtime-playground"
}
}
25 changes: 24 additions & 1 deletion packages/cli/src/recipe-backend-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readFile, stat } from "node:fs/promises"
import { basename, dirname, join, resolve } from "node:path"
import { pathToFileURL } from "node:url"
import { normalizeRuntimeBackendKind, type RuntimeBackendFactoryContext, type RuntimeBackendKind, type WorkspaceRecipe, type WorkspaceRecipeRuntimeBackendPackage } from "@automattic/wp-codebox-core"
import type { NativeRuntimeDriverFactory } from "@automattic/wp-codebox-native"

export interface RuntimeBackendPackageProvenance {
schema: "wp-codebox/runtime-backend-package/v1"
Expand Down Expand Up @@ -94,7 +95,25 @@ const playgroundRuntimeBackendPackageAdapter: RuntimeBackendPackageAdapter = {
},
}

const runtimeBackendPackageAdapterRegistry = new RuntimeBackendPackageAdapterRegistry([playgroundRuntimeBackendPackageAdapter])
const nativeRuntimeBackendPackageAdapter: RuntimeBackendPackageAdapter = {
backendKind: "wordpress-native",
prepare(loadedPackage) {
const { backendPackage, entrypoint, module } = loadedPackage
if (backendPackage.kind !== "native") {
throw backendPackageError(backendPackage, `Unsupported native WordPress runtime backend package kind: ${backendPackage.kind}`)
}
if (!isNativeRuntimeDriverFactory(module)) {
throw backendPackageError(backendPackage, `Runtime backend package entrypoint must export createNativeRuntimeDriver(): ${entrypoint}`)
}

return {
runtimeBackendContext: { nativeRuntimeDriver: (module as NativeRuntimeDriverFactory).createNativeRuntimeDriver() },
diagnostics: [{ status: "passed", message: "Entrypoint exports createNativeRuntimeDriver" }],
}
},
}

const runtimeBackendPackageAdapterRegistry = new RuntimeBackendPackageAdapterRegistry([playgroundRuntimeBackendPackageAdapter, nativeRuntimeBackendPackageAdapter])

export class RecipeRuntimeBackendPackageError extends Error {
readonly code = "recipe-runtime-backend-package-invalid"
Expand Down Expand Up @@ -223,6 +242,10 @@ function isPlaygroundCliModule(value: unknown): value is RuntimeCliEntrypointMod
return Boolean(value && typeof value === "object" && "runCLI" in value && typeof (value as { runCLI?: unknown }).runCLI === "function")
}

function isNativeRuntimeDriverFactory(value: unknown): value is NativeRuntimeDriverFactory {
return Boolean(value && typeof value === "object" && "createNativeRuntimeDriver" in value && typeof (value as { createNativeRuntimeDriver?: unknown }).createNativeRuntimeDriver === "function")
}

function backendPackageError(backendPackage: WorkspaceRecipeRuntimeBackendPackage, message: string): RecipeRuntimeBackendPackageError {
return new RecipeRuntimeBackendPackageError(message, backendPackage, [{ status: "failed", message }])
}
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/runtime-backends.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createRuntimeBackendRegistry, runtimeBackendRecipeAliases, type RuntimeBackend, type RuntimeBackendFactoryContext, type RuntimeBackendKind, type RuntimeBackendRecipePolicy } from "@automattic/wp-codebox-core"
import type { CommandDefinition } from "@automattic/wp-codebox-core/contracts"
import { playgroundRuntimeBackendProvider } from "@automattic/wp-codebox-playground"
import { nativeRuntimeBackendProvider } from "@automattic/wp-codebox-native"

const cliRuntimeBackendRegistry = createRuntimeBackendRegistry([playgroundRuntimeBackendProvider])
const cliRuntimeBackendRegistry = createRuntimeBackendRegistry([playgroundRuntimeBackendProvider, nativeRuntimeBackendProvider])

export function listCliRuntimeBackendKinds(): RuntimeBackendKind[] {
return cliRuntimeBackendRegistry.list().flatMap((kind) => [kind, ...runtimeBackendRecipeAliases(kind)])
Expand Down
1 change: 1 addition & 0 deletions packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
},
"references": [
{ "path": "../runtime-core" },
{ "path": "../runtime-native" },
{ "path": "../runtime-playground" }
],
"include": ["src/**/*.ts"]
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime-core/src/runtime-backend-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const WORDPRESS_RUNTIME_BACKEND_ALIAS = "wordpress" as const
*/
export interface RuntimeBackendFactoryContext {
readonly cliModule?: unknown
/** Adapter-owned process/container driver; core keeps this implementation-neutral. */
readonly nativeRuntimeDriver?: unknown
}

export interface RuntimeBackendRecipePolicy {
Expand Down
17 changes: 17 additions & 0 deletions packages/runtime-native/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@automattic/wp-codebox-native",
"version": "0.26.8",
"description": "Contained native PHP runtime adapter for WP Codebox.",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": ["dist"],
"scripts": { "build": "tsc -b" },
"dependencies": { "@automattic/wp-codebox-core": "file:../runtime-core" }
}
Loading
Loading