Skip to content

Commit 7d439cb

Browse files
committed
Patch disabled cron before canonical init
1 parent 4803823 commit 7d439cb

3 files changed

Lines changed: 31 additions & 30 deletions

File tree

packages/runtime-cloudflare/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The bundled MDI source is pinned to immutable commit `1870fb41279e7eb5946e506c9c
1818

1919
The WordPress server corpus is a separate deployment artifact, never a Worker-module import. `generate:cloudflare-wordpress-runtime-corpus` uses bounded ZIP Range decoding against the pinned WordPress release, retains only `isWordPressRuntimeFile()` entries, and emits a deterministic ZIP under `artifacts/` plus the checked-in `assets/wordpress-runtime-artifact.json` provenance manifest. The immutable R2 key is derived from the corpus ZIP SHA-256. Cold boot fetches that exact object through `WORDPRESS_STATE_BUCKET`, validates the manifest and archive/file hashes and budgets, then streams it into PHP MEMFS. Static browser assets continue to use lazy exact archive ranges and Worker cache.
2020

21-
Canonical Cloudflare boots materialize a named mu-plugin that removes only core's `wp_cron` `init` callback when the runtime policy defines `DISABLE_WP_CRON` as true. Cron spawning is explicitly disabled by this runtime policy because the PHP-WASM/MDI shutdown callback path is not supported. Update and privacy scheduling callbacks remain registered; an explicit durable cron execution contract is separate future scope.
21+
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 patch requires exactly one canonical `do_action( 'init' );` needle, fails closed otherwise, removes only core's `wp_cron` callback through public `remove_action()` when `DISABLE_WP_CRON` is true, then executes the original `init` call once. Cron spawning is explicitly disabled by this runtime policy because the PHP-WASM/MDI shutdown callback path is not supported. Update and privacy scheduling callbacks remain registered; an explicit durable cron execution contract is separate future scope.
2222

2323
## MDI Init Diagnostics
2424

packages/runtime-cloudflare/src/worker.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ async function bootWordPressRuntime(
10031003
siteUrl = SITE_URL,
10041004
authConstants: Partial<Record<WordPressAuthConstant, string>> = {},
10051005
runtimeBucket?: R2Bucket,
1006-
canonicalCronAdapter = false,
1006+
shouldPatchCanonicalCronAtInit = false,
10071007
): Promise<{ php: PHP; requestHandler: PHPRequestHandler; wordpressVersion: string }> {
10081008
const requestHandler = await bootWordPressAndRequestHandler({
10091009
createPhpRuntime,
@@ -1035,7 +1035,7 @@ async function bootWordPressRuntime(
10351035
materializeRuntimeFiles(php, MARKDOWN_ROOT, markdownFiles)
10361036
if (markdownIndexSeed) php.writeFile(MARKDOWN_RESOLVED_INDEX_PATH, markdownIndexSeed)
10371037
}
1038-
if (canonicalCronAdapter) materializeCanonicalCronAdapter(php)
1038+
if (shouldPatchCanonicalCronAtInit) patchCanonicalCronAtInit(php)
10391039
} : undefined,
10401040
beforeDatabaseSetup: databaseSeed ? (php: PHP) => {
10411041
php.mkdir("/wordpress/wp-content/database")
@@ -1063,24 +1063,19 @@ function materializeRuntimeFiles(php: PHP, root: string, files: RuntimeFile[]):
10631063
}
10641064
}
10651065

1066-
function materializeCanonicalCronAdapter(php: PHP): void {
1067-
const path = "/wordpress/wp-content/mu-plugins/wp-codebox-canonical-cron-policy.php"
1068-
php.mkdir(path.slice(0, path.lastIndexOf("/")))
1069-
php.writeFile(path, new TextEncoder().encode(`<?php
1070-
/**
1071-
* Plugin Name: WP Codebox Canonical Cron Policy
1072-
*/
1073-
1074-
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
1075-
add_action(
1076-
'init',
1077-
static function (): void {
1078-
remove_action( 'init', 'wp_cron' );
1079-
},
1080-
PHP_INT_MIN
1081-
);
1082-
}
1083-
`))
1066+
function patchCanonicalCronAtInit(php: PHP): void {
1067+
const settingsPath = "/wordpress/wp-settings.php"
1068+
const needle = "do_action( 'init' );"
1069+
const settings = new TextDecoder().decode(php.readFileAsBuffer(settingsPath))
1070+
const firstNeedle = settings.indexOf(needle)
1071+
if (firstNeedle === -1 || firstNeedle !== settings.lastIndexOf(needle)) {
1072+
throw new Error("WordPress canonical cron patch needle was not uniquely found.")
1073+
}
1074+
const replacement = `if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
1075+
remove_action( 'init', 'wp_cron' );
1076+
}
1077+
${needle}`
1078+
php.writeFile(settingsPath, new TextEncoder().encode(`${settings.slice(0, firstNeedle)}${replacement}${settings.slice(firstNeedle + needle.length)}`))
10841079
}
10851080

10861081
function collectRuntimeFiles(php: PHP, root: string): RuntimeFile[] {

tests/cloudflare-runtime.test.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,16 +313,22 @@ test("Cloudflare MDI lifecycle diagnostics use the complete packaged seed withou
313313
assert.match(worker, /function canonicalChangedPathCounts/)
314314
})
315315

316-
test("Cloudflare canonical runtime materializes a conditional cron policy without removing other scheduling callbacks", async () => {
316+
test("Cloudflare canonical runtime patches the unique init call in MEMFS without removing other scheduling callbacks", async () => {
317317
const worker = await readFile(new URL("../packages/runtime-cloudflare/src/worker.ts", import.meta.url), "utf8")
318-
const adapter = worker.match(/function materializeCanonicalCronAdapter\(php: PHP\): void \{[\s\S]*?\n\}/)?.[0]
319-
320-
assert.ok(adapter, "The canonical cron policy materializer is present.")
321-
assert.match(adapter, /wp-content\/mu-plugins\/wp-codebox-canonical-cron-policy\.php/)
322-
assert.match(adapter, /defined\( 'DISABLE_WP_CRON' \) && DISABLE_WP_CRON/)
323-
assert.match(adapter, /add_action\([\s\S]*'init'[\s\S]*remove_action\( 'init', 'wp_cron' \)[\s\S]*PHP_INT_MIN/)
324-
assert.doesNotMatch(adapter, /wp_schedule_update_checks|wp_schedule_delete_old_privacy_export_files|\$GLOBALS\['wp_filter'\]/)
325-
assert.match(worker, /runtimeBucket\?: R2Bucket,\n canonicalCronAdapter = false,/)
318+
const patcher = worker.slice(worker.indexOf("function patchCanonicalCronAtInit"), worker.indexOf("\nfunction collectRuntimeFiles"))
319+
320+
assert.ok(patcher.startsWith("function patchCanonicalCronAtInit"), "The canonical cron init patcher is present.")
321+
assert.match(patcher, /const settingsPath = "\/wordpress\/wp-settings\.php"/)
322+
assert.match(patcher, /php\.readFileAsBuffer\(settingsPath\)/)
323+
assert.match(patcher, /firstNeedle === -1 \|\| firstNeedle !== settings\.lastIndexOf\(needle\)/)
324+
assert.match(patcher, /throw new Error\("WordPress canonical cron patch needle was not uniquely found\."\)/)
325+
assert.match(patcher, /defined\( 'DISABLE_WP_CRON' \) && DISABLE_WP_CRON/)
326+
assert.match(patcher, /remove_action\( 'init', 'wp_cron' \)/)
327+
assert.equal((patcher.match(/do_action\( 'init' \);/g) ?? []).length, 1)
328+
assert.match(patcher, /php\.writeFile\(settingsPath/)
329+
assert.doesNotMatch(patcher, /mu-plugins|add_action\(|wp_schedule_update_checks|wp_schedule_delete_old_privacy_export_files|\$GLOBALS\['wp_filter'\]/)
330+
assert.doesNotMatch(worker, /materializeCanonicalCronAdapter|wp-codebox-canonical-cron-policy/)
331+
assert.match(worker, /runtimeBucket\?: R2Bucket,\n shouldPatchCanonicalCronAtInit = false,/)
326332
assert.match(worker, /authConstants, bucket, true\), pointer/)
327333
assert.match(worker, /env\.WORDPRESS_STATE_BUCKET, true\)/)
328334
assert.match(worker, /canonical-probe\.invalid", \{\}, bucket, true\)/)

0 commit comments

Comments
 (0)