Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions docs/cloudflare-provisioning-api.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Cloudflare provisioning API core
# Cloudflare provisioning API

The D1 Worker exposes a versioned control-plane API before hostname-based WordPress routing. `/v1/*` never boots or falls through to WordPress.

Expand All @@ -18,7 +18,8 @@ The Worker compares SHA-256 token digests and never accepts plaintext API creden
- `GET /v1/sites/{siteId}` requires `sites:read`.
- `POST /v1/sites/{siteId}/imports` requires `sites:import` and uses the existing bounded static-artifact request.
- `GET /v1/sites/{siteId}/operations/{operationId}` requires `operations:read`.
- `POST /v1/sites/{siteId}/administrator-claim` exchanges its one-time bearer capability for the persistent site-scoped `admin` credential after provisioning succeeds. It does not accept an API bearer token.

All responses use versioned WP Codebox provisioning schemas. Site IDs are allocated only from `WORDPRESS_SITE_CONTEXTS`; caller hostnames, DNS, and Cloudflare control APIs are not inputs. The allocation transaction reserves the same shipped D1 site identity used by legacy/operator operations, so both interfaces contend on one hostname and an existing site cannot be taken over. D1 stores allocation ownership, immutable artifact identity and import options, and API operation links. The scheduler resumes incomplete allocations for its selected site before it runs an operation; it verifies staged bytes and converges the conditional destination copy, operation, and API link without blocking publication or cron when recovery fails.

This is the API core portion of #1971. Administrator claims are intentionally a follow-up and are not represented by a route, secret, D1 field, resource field, documentation contract, or test in this PR.
`POST /v1/sites` validates `WORDPRESS_ADMIN_CLAIM_SECRET` and `WORDPRESS_ADMIN_PASSWORD` before allocation. Its create/replay response includes the deterministic pending capability while the configured root still derives the stored digest. Site reads expose only claim state, expiry, and the fixed endpoint. D1 stores capability and derived-credential digests, never either plaintext value. Scheduled allocation recovery issues a missing claim before provisioning work can run. Redemption requires the current site-scoped credential to match the digest pinned before bootstrap, then atomically consumes the capability exactly once and transfers that persistent administrator credential; it is not a one-time browser session. Rotating either root preserves the pending record but requires restoring the matching root before replay or redemption.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"smoke": "tsx scripts/run-smoke.ts",
"test:redaction": "tsx tests/redaction.test.ts",
"test:cloudflare-runtime": "node --test tests/cloudflare-d1-provisioner.test.mjs && tsx tests/cloudflare-site-context.test.ts && tsx tests/cloudflare-coordinator-site-partitioning.test.ts && tsx tests/cloudflare-d1-operation-repository.test.ts && tsx tests/cloudflare-provisioning-api.test.ts && tsx tests/cloudflare-runtime.test.ts && node ./node_modules/typescript/bin/tsc -p packages/runtime-cloudflare --noEmit",
"test:cloudflare-administrator-claim": "tsx tests/cloudflare-provisioning-api.test.ts",
"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",
Expand Down
6 changes: 6 additions & 0 deletions packages/runtime-cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ Use authenticated `GET ?phase=operator-fence-status` after acquisition to export

For lossless D1-to-Durable-Object rollback, keep D1 deployed as the active Worker, acquire its fence, and capture its coherent status. Address the retained Durable Object only with `coordinator=durable-object` on authenticated `operator-fence-*` and `operator-adopt` requests. Fence that selected coordinator first, then adopt the D1 pointer and version with its matching `fenceToken`; selected-coordinator adoption fails unless that fence is active and matches. Require its selected status envelope to be coherent and identical, then promote the Durable Object Worker before the D1 fence expires and release its fence. Its first mutation must commit at `N+1`. The Durable Object binding and historical `WordPressStateCoordinator` export remain in the D1 profile for this procedure. Non-operator requests always retain the active coordinator, and selectors on unsupported or unknown operator routes fail closed.

## Administrator Claim

`POST /v1/sites/{siteId}/administrator-claim` is intercepted before WordPress dispatch and uses only `Authorization: Bearer <capability>`. It is not an API-token endpoint. A successful provision create response includes its pending `administratorClaim` capability exactly for the create/replay window; site reads expose only its fixed endpoint URL, state, and expiry. The capability is a deterministic 256-bit HMAC derived from `WORDPRESS_ADMIN_CLAIM_SECRET` and immutable allocation identity. D1 retains capability and derived-credential digests, expiry, and lifecycle state, never plaintext values. Scheduled recovery creates a missing claim before provisioning can run, and redemption refuses to consume it when either configured root no longer matches the pinned derivation.

Configure both `WORDPRESS_ADMIN_CLAIM_SECRET` and `WORDPRESS_ADMIN_PASSWORD` before provisioning. Redemption waits for the linked provision operation to succeed, atomically consumes the claim, and returns `{ "username": "admin", "password": "..." }`, where the password is the persistent site-scoped administrator credential derived from `WORDPRESS_ADMIN_PASSWORD`. This transfers that credential; it is not a one-time browser session.

## Static Artifact Import

An authenticated `POST ?phase=operator-static-artifact-import` materializes a verified website artifact into the existing canonical site. The request is limited to 16 KiB and references an immutable JSON object at `sites/{siteId}/import-artifacts/<sha256>.json`; the Worker requires the declared R2 key, size, and SHA-256 to agree before creating work. The canonical `blocks-engine/php-transformer/site-artifact/v1` payload is limited to 4 MiB serialized, 500 safe unique files, 8 MiB per decoded file, and 32 MiB decoded in aggregate.
Expand Down
Loading
Loading