Skip to content

Commit 2ebd0c0

Browse files
committed
Add public provisioning local gate
1 parent 5a4cce5 commit 2ebd0c0

2 files changed

Lines changed: 81 additions & 7 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"cloudflare:dry-run:d1": "npm exec -- wrangler deploy --dry-run --config packages/runtime-cloudflare/wrangler.d1.jsonc",
9191
"cloudflare:local-gate": "node scripts/cloudflare-local-gate.mjs",
9292
"cloudflare:local-gate:d1": "node scripts/cloudflare-local-gate.mjs --coordinator=d1",
93+
"cloudflare:local-gate:provisioning": "node scripts/cloudflare-local-gate.mjs --coordinator=d1 --public-provisioning",
9394
"postinstall": "node scripts/apply-development-patches.mjs",
9495
"generate:cloudflare-wordpress-runtime-corpus": "tsx scripts/generate-cloudflare-wordpress-runtime-corpus.ts",
9596
"provision:cloudflare-wordpress-runtime-corpus": "node scripts/provision-cloudflare-wordpress-runtime-corpus.mjs",

scripts/cloudflare-local-gate.mjs

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ const origin = `http://127.0.0.1:${port}`
1111
const password = "cloudflare-runtime-test-password"
1212
const authSecret = "cloudflare-runtime-test-auth-secret"
1313
const operatorToken = "cloudflare-runtime-test-operator-token"
14+
const apiToken = "cloudflare-runtime-test-api-token"
15+
const administratorClaimSecret = "cloudflare-runtime-test-administrator-claim-secret"
1416
const coordinator = process.argv.includes("--coordinator=d1") ? "d1" : "durable-object"
17+
const publicProvisioning = process.argv.includes("--public-provisioning")
1518
const wranglerConfig = coordinator === "d1" ? "packages/runtime-cloudflare/wrangler.d1.jsonc" : "packages/runtime-cloudflare/wrangler.jsonc"
1619
const stateDirectory = await mkdtemp(join(tmpdir(), "wp-codebox-cloudflare-gate-"))
1720
const cookies = []
@@ -29,6 +32,10 @@ try {
2932
await run("npm", ["run", "provision:cloudflare-wordpress-runtime-corpus", "--", "--local", "--persist-to", stateDirectory])
3033
const staticArtifactImport = await provisionStaticArtifact()
3134
await startWorker()
35+
if (publicProvisioning) {
36+
await assertPublicProvisioning(staticArtifactImport)
37+
console.log("Cloudflare public provisioning gate passed: authenticated artifact staging, idempotent site allocation, scheduled import, cold-restart site read, administrator claim, login, and editable native blocks.")
38+
} else {
3239
await assertFullBootProbe()
3340
await assertWordPressCronDisabled()
3441
await assertConcurrentMutations()
@@ -113,6 +120,7 @@ try {
113120
if (conflicting.status !== "conflict" || conflictStateAfter.version !== duplicateStateBefore.version || conflictStateAfter.pointer?.revision !== duplicateStateBefore.pointer?.revision) throw new Error("Conflicting static artifact replay changed canonical state.")
114121
await assertTwoSiteIsolation()
115122
console.log(`Cloudflare local runtime gate passed with ${coordinator} coordination: canonical full-boot probe, explanatory homepage, complete block styles, coordinator-free R2 publication reads, login, dashboard, post editor, concurrent canonical mutations, authenticated REST post and media creation, plugin ZIP installation and activation, direct R2 upload serving, frontend/admin/editor assets, cold-restart persistence, and bounded durable scheduled callback execution.`)
123+
}
116124
} finally {
117125
await stopWorker()
118126
await rm(stateDirectory, { recursive: true, force: true })
@@ -152,15 +160,79 @@ async function provisionStaticArtifact() {
152160
const serialized = JSON.stringify(artifact)
153161
const sha256 = createHash("sha256").update(serialized).digest("hex")
154162
const key = `sites/default/import-artifacts/${sha256}.json`
155-
const path = join(stateDirectory, "static-site-artifact.json")
156-
await writeFile(path, serialized)
157-
await run("npm", ["exec", "--", "wrangler", "r2", "object", "put", `wp-codebox-runtime-chubes/${key}`, "--file", path, "--local", "--persist-to", stateDirectory])
158-
return {
163+
if (!publicProvisioning) {
164+
const path = join(stateDirectory, "static-site-artifact.json")
165+
await writeFile(path, serialized)
166+
await run("npm", ["exec", "--", "wrangler", "r2", "object", "put", `wp-codebox-runtime-chubes/${key}`, "--file", path, "--local", "--persist-to", stateDirectory])
167+
}
168+
const request = {
159169
schema: "wp-codebox/cloudflare-static-artifact-import-request/v1",
160170
idempotencyKey: `cloudflare-static-artifact-${sha256}`,
161171
artifact: { r2Key: key, sha256, size: Buffer.byteLength(serialized) },
162172
import: { slug: "verified-artifact-site", name: "Verified Artifact Site", siteTitle: "Verified Artifact Site" },
163173
}
174+
return publicProvisioning ? { ...request, serialized } : request
175+
}
176+
177+
async function assertPublicProvisioning(input) {
178+
const authorization = { authorization: `Bearer ${apiToken}` }
179+
const stagedResponse = await fetch(`${origin}/v1/artifacts/${input.artifact.sha256}`, {
180+
method: "PUT",
181+
headers: { ...authorization, "content-type": "application/json" },
182+
body: input.serialized,
183+
})
184+
const stagedBody = await stagedResponse.text()
185+
if (stagedResponse.status !== 200) throw new Error(`Public artifact staging failed: ${stagedResponse.status} ${stagedBody}.`)
186+
const staged = JSON.parse(stagedBody)
187+
if (staged.schema !== "wp-codebox/provisioning-artifact/v1" || staged.artifact?.sha256 !== input.artifact.sha256 || staged.artifact?.size !== input.artifact.size
188+
|| staged.artifact?.r2Key !== `sites/provisioning/import-artifacts/${input.artifact.sha256}.json`) {
189+
throw new Error(`Public artifact staging returned an invalid reference: ${stagedBody}.`)
190+
}
191+
192+
const idempotencyKey = `cloudflare-public-provisioning-${input.artifact.sha256}`
193+
const requestBody = JSON.stringify({ schema: "wp-codebox/provisioning-create-request/v1", idempotencyKey, artifact: staged.artifact, import: input.import })
194+
const createSite = () => fetch(`${origin}/v1/sites`, { method: "POST", headers: { ...authorization, "content-type": "application/json", "idempotency-key": idempotencyKey }, body: requestBody })
195+
const createdResponse = await createSite()
196+
const createdBody = await createdResponse.text()
197+
if (createdResponse.status !== 202) throw new Error(`Public site creation failed: ${createdResponse.status} ${createdBody}.`)
198+
const created = JSON.parse(createdBody)
199+
if (created.schema !== "wp-codebox/provisioning-site/v1" || created.site?.id !== "default" || typeof created.site.operation !== "string"
200+
|| typeof created.site.administratorClaim?.token !== "string") throw new Error(`Public site creation returned an invalid resource: ${createdBody}.`)
201+
const replayResponse = await createSite()
202+
const replayBody = await replayResponse.text()
203+
if (replayResponse.status !== 202 || replayBody !== createdBody) throw new Error(`Public site creation replay did not converge: ${replayResponse.status} ${replayBody}.`)
204+
205+
let operation
206+
for (let tick = 0; tick < 8; tick++) {
207+
const response = await fetch(new URL(created.site.operation, origin), { headers: authorization })
208+
const body = await response.text()
209+
if (!response.ok) throw new Error(`Public provisioning operation read failed: ${response.status} ${body}.`)
210+
operation = JSON.parse(body).operation
211+
if (operation?.state === "succeeded") break
212+
if (operation?.state === "failed") throw new Error(`Public provisioning operation failed: ${body}.`)
213+
await runScheduledCron()
214+
}
215+
const imported = operation?.receipt?.ssiResult
216+
if (operation?.state !== "succeeded" || imported?.status !== "imported" || imported.staticSiteImporterVersion !== "1.3.4" || !imported.themeSlug
217+
|| !imported.pages || Object.keys(imported.pages).length !== 2 || Object.values(imported.quality ?? {}).some((count) => count !== 0)
218+
|| operation.receipt?.publication?.status !== "none") {
219+
throw new Error(`Public provisioning did not produce a terminal import receipt: ${JSON.stringify(operation)}.`)
220+
}
221+
222+
await stopWorker()
223+
await startWorker()
224+
const siteResponse = await fetch(`${origin}/v1/sites/${created.site.id}`, { headers: authorization })
225+
const siteBody = await siteResponse.text()
226+
const site = JSON.parse(siteBody)
227+
if (!siteResponse.ok || site.site?.status !== "ready" || site.site?.administratorClaim?.state !== "pending" || siteBody.includes(created.site.administratorClaim.token)) {
228+
throw new Error(`Cold-restart public site read returned invalid state: ${siteResponse.status} ${siteBody}.`)
229+
}
230+
const claimResponse = await fetch(new URL(created.site.administratorClaim.url, origin), { method: "POST", headers: { authorization: `Bearer ${created.site.administratorClaim.token}` } })
231+
const claimBody = await claimResponse.text()
232+
const claim = JSON.parse(claimBody)
233+
if (!claimResponse.ok || claim.credential?.username !== "admin" || claim.credential?.password !== password) throw new Error(`Administrator claim failed: ${claimResponse.status} ${claimBody}.`)
234+
const adminHtml = await login(claim.credential.password)
235+
await assertImportedArtifactPages(adminHtml, imported)
164236
}
165237

166238
async function assertTwoSiteIsolation() {
@@ -238,7 +310,8 @@ async function assertTwoSiteIsolation() {
238310

239311
async function startWorker() {
240312
output = ""
241-
child = spawn("npm", ["exec", "--", "wrangler", "dev", "--test-scheduled", "--config", wranglerConfig, "--port", String(port), "--persist-to", stateDirectory, "--var", `WORDPRESS_ADMIN_PASSWORD:${password}`, "--var", `WORDPRESS_AUTH_SECRET:${authSecret}`, "--var", `WORDPRESS_OPERATOR_TOKEN:${operatorToken}`, "--var", `WORDPRESS_SITE_CONTEXTS:${JSON.stringify(siteContexts)}`], {
313+
const apiTokens = [{ id: "local-gate", principal: "local-gate", digest: createHash("sha256").update(apiToken).digest("hex"), scopes: ["sites:create", "sites:read", "sites:import", "operations:read"], expiresAt: "2099-01-01T00:00:00.000Z", maxSites: 1 }]
314+
child = spawn("npm", ["exec", "--", "wrangler", "dev", "--test-scheduled", "--config", wranglerConfig, "--port", String(port), "--persist-to", stateDirectory, "--var", `WORDPRESS_ADMIN_PASSWORD:${password}`, "--var", `WORDPRESS_ADMIN_CLAIM_SECRET:${administratorClaimSecret}`, "--var", `WORDPRESS_AUTH_SECRET:${authSecret}`, "--var", `WORDPRESS_OPERATOR_TOKEN:${operatorToken}`, "--var", `WORDPRESS_API_TOKENS:${JSON.stringify(apiTokens)}`, "--var", `WORDPRESS_SITE_CONTEXTS:${JSON.stringify(siteContexts)}`], {
242315
cwd: process.cwd(),
243316
// The host PAC resolves these public archive hosts through an unavailable local proxy.
244317
env: { ...process.env, NO_PROXY: "wordpress.org,github.com,codeload.github.com", no_proxy: "wordpress.org,github.com,codeload.github.com" },
@@ -277,9 +350,9 @@ async function assertLoginForm() {
277350
if (!/<form[^>]+id=["']loginform["']/i.test(html)) throw new Error("wp-login.php did not return the login form.")
278351
}
279352

280-
async function login() {
353+
async function login(candidatePassword = password) {
281354
await assertLoginForm()
282-
const form = new URLSearchParams({ log: "admin", pwd: password, redirect_to: `${origin}/wp-admin/`, testcookie: "1", "wp-submit": "Log In" })
355+
const form = new URLSearchParams({ log: "admin", pwd: candidatePassword, redirect_to: `${origin}/wp-admin/`, testcookie: "1", "wp-submit": "Log In" })
283356
const response = await request(`${origin}/wp-login.php`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form, redirect: "manual" })
284357
if (![301, 302].includes(response.status)) throw new Error(`Expected login redirect, received ${response.status}: ${await response.text()}`)
285358
if (response.headers.get("x-wp-codebox-publication") === "queued" || response.headers.has("x-wp-codebox-publication-revision")) throw new Error("Login must not enqueue or promote publication work.")

0 commit comments

Comments
 (0)