Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
2dac41a
Persist functional WordPress state in Cloudflare
chubes4 Jul 19, 2026
7159cf0
Prebuild canonical Cloudflare WordPress state
chubes4 Jul 19, 2026
c590c4a
Keep PHP outside the state coordinator
chubes4 Jul 19, 2026
8138eb1
Bound the Cloudflare WordPress runtime
chubes4 Jul 19, 2026
36a6c1e
Stream WordPress browser assets outside PHP
chubes4 Jul 19, 2026
c00988e
Provision the WordPress runtime corpus in R2
chubes4 Jul 19, 2026
962acac
Buffer the bounded R2 runtime artifact
chubes4 Jul 19, 2026
fac127a
Extract the runtime corpus inside PHP
chubes4 Jul 19, 2026
bea4645
Add bounded MDI init diagnostics
chubes4 Jul 19, 2026
b9233ed
Split widget initialization diagnostics
chubes4 Jul 19, 2026
3847b72
Bisect MDI widget settings registration
chubes4 Jul 19, 2026
a23366b
Compare MDI widget option visibility
chubes4 Jul 19, 2026
1969510
Probe complete canonical WordPress state
chubes4 Jul 19, 2026
ddb1f4c
Probe remaining widget registrations
chubes4 Jul 19, 2026
2ec7efe
Remove diagnostic hooks through WordPress APIs
chubes4 Jul 19, 2026
6d95c3b
Isolate canonical cron scheduling
chubes4 Jul 19, 2026
8fd3a40
Skip disabled cron registration in Cloudflare
chubes4 Jul 19, 2026
4803823
Remove disabled cron at init time
chubes4 Jul 19, 2026
7d439cb
Patch disabled cron before canonical init
chubes4 Jul 19, 2026
2be2d25
Disable browser-request cron scheduling
chubes4 Jul 19, 2026
ba72370
Probe canonical lifecycle boundaries
chubes4 Jul 19, 2026
e909a01
Bisect canonical wp-loaded callbacks
chubes4 Jul 19, 2026
f284345
Add short canonical lifecycle probes
chubes4 Jul 19, 2026
09a9bff
Add direct wp-loaded probe aliases
chubes4 Jul 19, 2026
8c1b797
Probe rewrite rule persistence
chubes4 Jul 19, 2026
42032cb
Keep rewrite cache out of canonical state
chubes4 Jul 19, 2026
b247295
Parallelize canonical R2 persistence
chubes4 Jul 19, 2026
9a7ce51
Add authenticated canonical state reset
chubes4 Jul 19, 2026
721ce3b
Persist canonical MDI change sets
chubes4 Jul 19, 2026
1215172
Document canonical lifecycle probes
chubes4 Jul 19, 2026
1f08ffe
Honor coordinator lease retry timing
chubes4 Jul 20, 2026
c4fbc71
Add bounded Cloudflare editor probes
chubes4 Jul 20, 2026
a56125c
Probe MDI auto-draft persistence
chubes4 Jul 20, 2026
9c01418
Bundle upstream auto-draft persistence fix
chubes4 Jul 20, 2026
d1a830f
Package constrained MDI runtime dependencies
chubes4 Jul 20, 2026
17127d2
Instrument Cloudflare editor memory phases
chubes4 Jul 20, 2026
bc245c3
Tighten editor insertion memory marker
chubes4 Jul 20, 2026
0654274
Split editor post-insert memory phases
chubes4 Jul 20, 2026
47306bb
Instrument block editor REST preload memory
chubes4 Jul 20, 2026
cedb6f3
Probe block editor global styles lookup
chubes4 Jul 20, 2026
4035a44
Instrument block editor settings memory
chubes4 Jul 20, 2026
2b69b49
Instrument core editor settings phases
chubes4 Jul 20, 2026
1c6a2e7
Split global stylesheet memory phases
chubes4 Jul 20, 2026
20a3869
Compare editor custom and theme styles
chubes4 Jul 20, 2026
5b11009
Fast-path custom CSS theme JSON output
chubes4 Jul 20, 2026
d5a266d
Anchor custom CSS fast path uniquely
chubes4 Jul 20, 2026
0ecd321
Seed WordPress custom CSS absence cache
chubes4 Jul 20, 2026
2e4948d
Log canonical bootstrap stages
chubes4 Jul 20, 2026
b7cc8ae
Persist canonical bootstrap stage evidence
chubes4 Jul 20, 2026
a5a5cb4
Seed custom CSS lookup cache canonically
chubes4 Jul 20, 2026
1133786
Split canonical bootstrap setup phases
chubes4 Jul 20, 2026
a88aba6
Instrument block editor output phases
chubes4 Jul 20, 2026
34ca13a
Instrument editor footer scripts
chubes4 Jul 20, 2026
7ffdeb7
Persist mutation stage evidence
chubes4 Jul 20, 2026
29c502c
Recycle PHP at canonical request boundaries
chubes4 Jul 20, 2026
aa15411
Add MDI persistence isolation probes
chubes4 Jul 20, 2026
a53ab9b
Split MDI write lifecycle probes
chubes4 Jul 20, 2026
b64a809
Report MDI dirty flush inventory
chubes4 Jul 20, 2026
34e1452
Isolate MDI table flush probes
chubes4 Jul 20, 2026
cc5a6b2
Isolate MDI options index maintenance
chubes4 Jul 20, 2026
3771dc9
Isolate MDI options fetch
chubes4 Jul 20, 2026
2a621c0
Isolate MDI dirty option writes
chubes4 Jul 20, 2026
2ab517a
Isolate MDI option mutation tracking
chubes4 Jul 20, 2026
d42ba01
Probe shared MDI temp paths
chubes4 Jul 20, 2026
6abd2b3
Pin bounded MDI option persistence
chubes4 Jul 20, 2026
9c7bbab
Revert "Persist mutation stage evidence"
chubes4 Jul 20, 2026
01a552a
Revert "Persist canonical bootstrap stage evidence"
chubes4 Jul 20, 2026
5355fbd
Revert "Log canonical bootstrap stages"
chubes4 Jul 20, 2026
78f2809
Revert "Probe MDI auto-draft persistence"
chubes4 Jul 20, 2026
0ca5b64
Revert "Probe canonical lifecycle boundaries"
chubes4 Jul 20, 2026
0e854ef
Revert "Probe complete canonical WordPress state"
chubes4 Jul 20, 2026
9b66992
Revert "Add bounded MDI init diagnostics"
chubes4 Jul 20, 2026
c423cda
Remove Cloudflare investigation diagnostics
chubes4 Jul 20, 2026
7e0e642
Preserve generic hook removal behavior
chubes4 Jul 20, 2026
486bdf8
Restore hook snapshot iteration
chubes4 Jul 20, 2026
21f1354
Add canonical Cloudflare runtime homepage
chubes4 Jul 21, 2026
f1ec68b
Repair Cloudflare WordPress corpus dev rig
chubes4 Jul 21, 2026
05b9ba1
Merge remote-tracking branch 'origin/main' into fix/1838-cloudflare-b…
chubes4 Jul 21, 2026
fa788ba
Merge remote-tracking branch 'origin/main' into fix/1838-cloudflare-b…
chubes4 Jul 21, 2026
e9ab6bb
Fix Cloudflare site styles and warm reads
chubes4 Jul 21, 2026
1e9e86e
Merge remote-tracking branch 'origin/main' into fix/1838-cloudflare-b…
chubes4 Jul 21, 2026
55fba5c
Add canonical Cloudflare state restore
chubes4 Jul 21, 2026
16e324f
Cache public pages by canonical revision
chubes4 Jul 21, 2026
f3963f7
Merge remote-tracking branch 'origin/main' into fix/1838-cloudflare-b…
chubes4 Jul 21, 2026
5abafae
Serve cached Cloudflare pages before runtime boot
chubes4 Jul 21, 2026
b0fcead
Declare PHPUnit config in readonly fixture
chubes4 Jul 21, 2026
0f144aa
Persist Cloudflare WordPress media in R2
chubes4 Jul 21, 2026
1d8d764
Merge remote-tracking branch 'origin/main' into fix/1838-cloudflare-b…
chubes4 Jul 21, 2026
b90b8b4
Serve WordPress assets from R2 ranges
chubes4 Jul 21, 2026
cee31d5
Load Cloudflare runtime dependencies from R2
chubes4 Jul 21, 2026
d11d4ed
Exercise canonical Cloudflare boot probe
chubes4 Jul 21, 2026
16d87b5
Persist mutable WordPress content in R2
chubes4 Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,118 changes: 541 additions & 577 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
"build": "node ./node_modules/typescript/bin/tsc -b packages/runtime-core packages/runtime-playground packages/cli && node scripts/ensure-cli-bin-executable.mjs",
"cloudflare:dry-run": "npm exec -- wrangler deploy --dry-run --config packages/runtime-cloudflare/wrangler.jsonc",
"cloudflare:local-gate": "node scripts/cloudflare-local-gate.mjs",
"postinstall": "patch-package",
"generate:cloudflare-wordpress-runtime-corpus": "tsx scripts/generate-cloudflare-wordpress-runtime-corpus.ts",
"provision:cloudflare-wordpress-runtime-corpus": "node scripts/provision-cloudflare-wordpress-runtime-corpus.mjs",
"prepare": "npm run build",
"package:wordpress-plugin": "tsx scripts/build-wordpress-plugin-zip.ts",
"release:package": "tsx scripts/package-release-artifact.ts",
Expand All @@ -95,9 +98,12 @@
"generate:browser-fanout-aggregation-runtime": "tsx scripts/generate-browser-fanout-aggregation-runtime.ts",
"generate:fanout-aggregation-contract": "tsx scripts/generate-fanout-aggregation-contract-fixture.ts",
"generate:cloudflare-mdi-runtime-bundle": "node scripts/build-cloudflare-mdi-runtime-bundle.mjs",
"generate:cloudflare-canonical-mdi-seed": "npm run generate:cloudflare-mdi-runtime-bundle && php scripts/build-cloudflare-canonical-mdi-seed.php",
"smoke": "tsx scripts/run-smoke.ts",
"test:redaction": "tsx tests/redaction.test.ts",
"test:cloudflare-runtime": "tsx tests/cloudflare-runtime.test.ts && node ./node_modules/typescript/bin/tsc -p packages/runtime-cloudflare --noEmit",
"test:cloudflare-wordpress-auth": "tsx tests/cloudflare-wordpress-auth.test.ts",
"test:cloudflare-wordpress-archive-corpus": "tsx tests/cloudflare-wordpress-archive-corpus.test.ts",
"test:agent-task-contracts": "tsx tests/agent-task-contracts.test.ts && tsx tests/agent-task-canonical-evidence.test.ts && npm run test:agent-task-workflow-interface && npm run test:runtime-sources-materialization && tsx tests/agent-task-reusable-workflow.test.ts",
"test:agent-task-workflow-interface": "tsx tests/run-agent-task-reusable-workflow-interface.test.ts",
"test:agent-task-runtime-package-staging": "tsx tests/agent-task-runtime-package-staging.test.ts",
Expand Down Expand Up @@ -198,7 +204,6 @@
"test:php-artifact-import-idempotency": "php scripts/php-artifact-import-idempotency-smoke.php",
"test:php-browser-runtime-local-package": "php scripts/php-browser-runtime-local-package-smoke.php",
"test:php-browser-runtime-agent-substrate": "php scripts/php-browser-runtime-agent-substrate-smoke.php",
"test:php-browser-preview-only-session": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-preview-only-session-smoke.php",
"test:mcp-client-configs": "tsx tests/mcp-client-configs.test.ts",
"test:runtime-tool-policy": "tsx tests/runtime-tool-policy.test.ts",
"test:primitive-contract-parity": "tsx tests/primitive-contract-parity.test.ts",
Expand Down Expand Up @@ -264,6 +269,7 @@
"devDependencies": {
"@cloudflare/workers-types": "^5.20260718.1",
"@types/node": "^24.0.0",
"patch-package": "^8.0.1",
"tsx": "^4.20.0",
"wrangler": "^4.112.0"
},
Expand Down Expand Up @@ -464,6 +470,8 @@
"yargs-parser": "^21.1.1"
},
"overrides": {
"@php-wasm/stream-compression": "3.1.45",
"@php-wasm/util": "3.1.46",
"ajv": "^8.20.0",
"express": "^4.22.2",
"qs": "^6.15.2",
Expand Down
35 changes: 24 additions & 11 deletions packages/runtime-cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
# Cloudflare Runtime Gate

This additive integration is the first acceptance gate for [wp-codebox#1838](https://github.com/Automattic/wp-codebox/issues/1838). It compiles the current PHP 8.5 Asyncify Wasm asset for workerd, boots WordPress through Playground with the current SQLite integration release, executes PHP, and returns a Codebox runtime-command-result envelope containing the stable `wp-codebox/cloudflare-runtime-health/v1` health payload.
This candidate integration for [wp-codebox#1838](https://github.com/Automattic/wp-codebox/issues/1838) runs WordPress through PHP-WASM and Playground in Cloudflare Workers.

The Worker owns Cloudflare transport, PHP-WASM execution, and the caller-owned disposable SQLite cache. [MDI PR #126](https://github.com/Automattic/markdown-database-integration/pull/126), pinned at `94b9f875ffb8402d5e8eb726893a12324e20f45c`, supplies the constrained public primary runtime: normal MDI driver SQL writes are explicitly flushed to deterministic relative Markdown/JSON paths. R2 stores immutable canonical revisions and the current pointer; the Durable Object owns only the persisted lease, base-revision validation, and CAS pointer promotion. SQLite is never uploaded. This preserves cold reconstruction from canonical R2 files and concurrent mutation serialization without expanding MDI's storage-only boundary.
## Runtime Architecture

Existing evidence covers full WordPress initialization and canonical R2 revision behavior. This update changes the source relationship from ad hoc writes to MDI's public constrained runtime, adds source-level bundle and mutation guards, and supports local packaging verification. It does not claim a new remote deployment.
The entry Worker executes PHP-WASM and WordPress. The named `WordPressStateCoordinator` Durable Object remains lightweight: it serializes a bounded lease and atomically promotes the current canonical R2 pointer with token, base-revision, and version checks. It never imports or instantiates PHP-WASM. State reads query the coordinator directly; every request that can observe canonical WordPress state acquires a lease first.

On cold start, the entry Worker uses the acquired pointer to rebuild PHP-WASM's disposable SQLite index from canonical MDI Markdown and JSON files. A missing pointer materializes the packaged canonical MDI seed and boots one PHP-WASM primary runtime. The build-time PHP CLI generator creates that archive from `wordpress-install-seed.sqlite` through MDI's public `bootstrap_existing_cache()` API, validates its pinned MDI revision and input digest, and never packages SQLite. The runtime updates `siteurl` and `home` through WordPress APIs using the request origin and sets the admin password from `WORDPRESS_ADMIN_PASSWORD`; only WordPress's password hash is canonical. Bootstrap persists and CAS-promotes this mutation before serving the next request.

Canonical browser, health, and mutation boots require the separately managed `WORDPRESS_AUTH_SECRET` Worker secret. The entry Worker derives the eight WordPress auth keys and salts from that secret with a versioned, site-scoped (`default`) SHA-256 domain separator before `wp-load.php`. It never logs, persists, or returns the secret or derived values. Configure it independently from the bootstrap password with `wrangler secret put WORDPRESS_AUTH_SECRET --config packages/runtime-cloudflare/wrangler.jsonc`; rotating `WORDPRESS_ADMIN_PASSWORD` does not rotate authentication salts or invalidate sessions.

After each mutating HTTP request, the entry runtime invokes MDI's explicit request-boundary flush, collects canonical files, stores immutable content-addressed R2 objects and a revision manifest, then commits the new pointer through the held lease. GET, HEAD, and asset requests release without promotion. Failed requests abort their leases; stale leases recover by token/version/expiry checks. The entry isolate can cache one runtime only for the exact acquired pointer revision and exits it after promotion or when another isolate advances the pointer. It does not persist SQLite. Existing manifests are reused when canonical file hashes have not changed.

The same revision transaction persists bounded user-managed files under `wp-content/plugins`, `themes`, `languages`, and `mu-plugins`. Runtime-owned MDI, SQLite integration, and Codebox adapter files remain reconstructable artifacts and are excluded. Unchanged bundled-theme files are omitted by release hash, while modified files become canonical overrides. Public canonical plugin and theme assets serve directly from revision-addressed R2 cache entries before the immutable release corpus. Nonce-protected `wp-admin` GET actions such as plugin activation are classified as mutations so their filesystem and MDI option changes commit atomically. Existing revisions without `wpContent` remain valid.

The Worker forwards browser cookies directly to Playground and disables Playground's internal cookie store, preventing an empty per-isolate store from replacing a valid browser session after cold restart.

The bundled MDI source is pinned to immutable commit `2a8ee7f6a46e1d64b4606f1ee3c97e14032dc96c` from [MDI PR #139](https://github.com/Automattic/markdown-database-integration/pull/139). The bundle generator, worker provenance, and source-contract test use the same revision.

WordPress server files, browser assets, and pinned runtime dependencies are separate deployment artifacts, never Worker-module imports. `generate:cloudflare-wordpress-runtime-corpus` emits a deterministic server ZIP containing only `isWordPressRuntimeFile()` entries, one concatenated browser-asset blob containing the complete supported `wp-admin`, `wp-includes`, and bundled-theme surface, and the pinned SQLite integration ZIP. Checked-in manifests record content-addressed R2 keys, hashes, budgets, provenance, and each browser asset's exact byte range. Cold boot validates and streams the server ZIP into PHP MEMFS while loading SQLite integration from R2. Browser requests use one bounded R2 range read, validate the selected bytes, and populate an immutable artifact-versioned Worker cache. Runtime requests never fetch WordPress or SQLite integration release archives from third-party origins.

Canonical Cloudflare boots patch only the assembled PHP MEMFS copy of `/wordpress/wp-settings.php` after the R2 WordPress corpus is materialized and before WordPress executes. The disabled-cron scheduling policy requires exactly one canonical `do_action( 'init' );` needle and fails closed otherwise. When `DISABLE_WP_CRON` is true, it removes only core's `wp_cron`, `wp_schedule_delete_old_privacy_export_files`, and `wp_schedule_update_checks` callbacks through public `remove_action()` before executing the original `init` call once. This prevents browser requests from recreating cron work that cannot execute in the PHP-WASM/MDI shutdown path. Durable cron execution belongs to a separate explicit queue or scheduled-Worker contract.
## Verification

1. Install dependencies with `npm ci`.
2. Run `npm run build`; expect the existing Codebox packages to compile.
3. Run `npm run test:cloudflare-runtime`; expect the deterministic health response to contain the Codebox command-result schema and boot/execution evidence.
4. Run `npm run cloudflare:dry-run`; expect Wrangler to compile the Worker and report the Worker bundle size. This creates no Cloudflare resources.
5. Run `npm run cloudflare:local-gate`; expect two HTTP 200 health envelopes from local workerd and automatic server cleanup. This validates packaging and the real Worker boot path.
6. After configuring a Cloudflare account and an explicit deployment target, run `npm exec -- wrangler deploy --config packages/runtime-cloudflare/wrangler.jsonc`; request the deployed URL and expect `marker: "wp-codebox-cloudflare-runtime-health"`, WordPress and PHP versions, and completed initialization/execution evidence.
7. Remove the deployed Worker with `npm exec -- wrangler delete --config packages/runtime-cloudflare/wrangler.jsonc` when the remote gate is no longer needed.
1. Run `npm run generate:cloudflare-canonical-mdi-seed` and `npm run generate:cloudflare-wordpress-runtime-corpus` to regenerate deterministic runtime artifacts and manifests.
2. Run `npm run provision:cloudflare-wordpress-runtime-corpus -- --local --persist-to <directory>` to verify and upload all exact content-addressed artifacts into isolated local R2 storage. For an authorized deployment, run the provisioner with `--remote` and require every upload to succeed before deploying the Worker that imports their manifests.
3. Run `npm run test:cloudflare-runtime` for routing, canonical-state, artifact validation, source contract, and TypeScript coverage.
4. Run `npm run cloudflare:dry-run` to compile the Worker without creating Cloudflare resources.
5. Run `npm run cloudflare:local-gate` for isolated local workerd evidence. It generates and provisions all artifacts before workerd starts, then injects stable test-only admin-password and auth-secret values, uploads and activates a real plugin ZIP, verifies that plugin's REST route before and after Worker restart, and covers login, authenticated REST publication, media, public rendering, representative frontend/admin/editor assets, PHP diagnostics, session recovery, and a fresh login after restart.

The remote request in step 6 is the memory and boot acceptance gate. Cloudflare Workers enforces the 128 MB isolate limit remotely; a local workerd result does not establish that limit.
This document describes local candidate verification only. It does not claim remote deployment.
Loading
Loading