13 KiB
Cloudflare marketplace preview
Cloudflare is the preferred hosted target. The existing Node/SQLite service remains a portable reference implementation; it is not the Worker runtime. No Cloudflare account, resources, hostname, deployment or plan has been verified by this increment. marketplace.getcodegraph.com is proposed, not configured. Do not provision a Vercel marketplace for this delivery.
Architecture and publication contract
marketplace/cloudflare/worker.ts uses Workers Web Crypto and the same portable package validator as the managed installer. The public frontend is served as Workers Static Assets; /api/* invokes the Worker. _headers preserves the browser security policy on assets, and the API applies it independently. No CORS wildcard, local-companion policy changes, native SQLite imports, Node filesystem calls, package execution or install scripts exist in the Worker bundle.
D1 holds registry identity/schema, publisher ownership, immutable releases and replay nonces. R2 holds private content-addressed objects at packages/sha256/<SHA-256>. Public buckets, direct object writes and object lifecycle deletion must remain disabled. The only public download is an existing D1 release; it verifies the actual R2 bytes before returning them. The managed installer independently revalidates identity, compatibility and integrity.
Publication validates P-256 signatures (SPKI publisher IDs match Node), timestamp, fields, paths and the full package. It then conditionally writes the immutable R2 object with a checksum and If-None-Match: *. Only after that succeeds does one transactional D1 batch claim/check ownership and insert release plus replay nonce. Database uniqueness and ownership triggers resolve concurrent publishers. A failure before D1 commit exposes no release; a response lost after commit leaves a valid immutable release, discoverable by refreshing the catalog. Retrying a committed nonce/version fails clearly. Uncommitted R2 objects can remain after failure; they are never listed or downloadable. There is deliberately no online orphan collector that could race a publication. Cleanup needs a separate maintenance procedure with writers stopped and a verified catalog snapshot.
This is staged publication, not a distributed transaction or a guarantee against an administrator deleting an object. Missing/corrupt storage fails downloads with an actionable 503. Reads use primary D1 bindings without replicas/cache. The preview caps the catalog at 1,000 releases to bound the existing array-based catalog protocol. Publication is disabled by default. An indexed D1 rate bucket permits 10 submissions per IP per minute; it mitigates abuse but is not a spending cap. Expired nonces are removed only after the signed-request validity window.
Local runtime and validation
From the repository root, with Node 22.23.2:
npm ci --no-audit --no-fund
npx tsc
npm ci --prefix marketplace/cloudflare --no-audit --no-fund
npm run build --prefix marketplace/cloudflare
npm test --prefix marketplace/cloudflare
(cd marketplace/cloudflare && node ops-test.mjs)
CLOUDFLARE_REGISTRY=1 PLAYWRIGHT_MODULE=/absolute/path/to/playwright \
node scripts/validation/extensions-compatibility.cjs
CLOUDFLARE_REGISTRY=1 PLAYWRIGHT_MODULE=/absolute/path/to/playwright \
node scripts/validation/marketplace-browser.cjs
The exact Wrangler/Miniflare/workerd versions are pinned in the separate lockfile. Current Wrangler uses Miniflare 5 alpha; the local adapter uses its documented v4-options converter and explicit resourcePersistencePath. Default test state is disposable. Production configuration points to worker.ts; only test-worker.ts accepts failure/kill controls. Tests exercise real local D1/R2 bindings, Web Crypto, HTTP and test-owned subprocess kills, not JavaScript storage mocks. Static assets in integration tests are delivered through a local binding; Wrangler dry run validates bundling and asset/binding configuration; actual platform routing still needs hosted verification.
For a retained local rehearsal (no Cloudflare account needed):
cd marketplace/cloudflare
node ops.mjs serve /absolute/lab/registry
node ops.mjs backup /absolute/lab/registry /absolute/backups/unique-snapshot
node ops.mjs restore /absolute/new-lab/registry /absolute/backups/unique-snapshot
Stop the rehearsal server before using the operator CLI on that same local state. Backup refuses missing or uninitialized source state and never silently migrates an empty source into a successful snapshot. The binding-level backup reads identity/owners/releases/nonces in one D1 batch snapshot, then copies immutable R2 bytes. A completion manifest is written last. Restore verifies checksums, manifest/package/ownership correspondence, refuses an occupied target, copies objects first and publishes metadata in one D1 batch. Partial restores remain detached and must be retried into a new destination. Backups contain public packages and metadata, not publisher private keys. Protect the independent checksum against tampering; checksums alone are not authentication. Local process-kill proof does not establish provider disk/power-loss or off-host retention guarantees.
Prepare deployment after access is verified
Required capabilities: authenticated Cloudflare account inventory, permission to deploy a preview Worker, an existing authorized D1 database and private R2 Standard bucket (or explicit approval to activate/create them), and authority for the selected hostname if attaching it. A browser login does not by itself authenticate Wrangler. Use the official wrangler whoami read; do not copy tokens into chat or inspect credential files. Do not use temporary accounts to bypass pending access.
- Inspect account/plan, D1/R2 inventory, existing Workers and
getcodegraph.comzone ownership. Confirm included allowances and whether R2 activation requires billing. No paid upgrade, card entry, R2 activation, DNS or domain change is authorized by this guide. - Copy
wrangler.jsoncto a private deployment config beside it. Replace the placeholder D1 ID and bucket name with the verified preview resources and set the verified account ID. KeepPUBLISHING_ENABLED=falseuntil storage, budget and runtime checks complete. Keep the initialworkers.devpreview; attach a custom domain only with authorization. Do not run a deploy against the checked-in placeholder config. - Apply
migrations/0001_registry.sqlto the dedicated empty preview D1 usingwrangler d1 migrations apply DB --remote --config wrangler.deploy.json. Existing registry data must never be reset. No migration/provision/deploy has been run remotely by this increment. - Check
wrangler deploy --dry-run --config wrangler.deploy.json --experimental-provision=false --experimental-auto-create=false, then deploy the reviewed preview using the same flags without--dry-runonly once resource access/authorization are verified. Automatic resource creation is disabled. The build bundles semver and shared validation; D1/R2 storage outlives Worker code replacement. - Verify
/api/health, exact deployed source, asset CSP and private bucket access. Enable publication only after measuring actual deployed CPU/memory and confirming usage controls. Publish disposable preview versions, replace the Worker code while keeping bindings, and verify unchanged identity, ownership and byte-identical downloads. Repeat browser signed publication, compatible one-Install into a separate Python project, actual graph refresh, update/disable/remove and origin/window/outage checks over the stable HTTPS origin.
The community publish API assigns official=false and rejects explicit official/publisher-identity spoof fields. The trusted Drupal operator now supplies a reviewed pinned-artifact plan/apply command, with exact registry identity, immutable publication and safe repeat operations. Apply migration 0002_official_operator.sql after 0001_registry.sql before using that command. There is no unauthenticated official/admin endpoint. Actual remote operator execution remains unverified; the local Node service's optional official seed is unchanged.
Remote backups and isolated restore
A D1 Time Travel restore alone does not restore R2. Keep immutable objects and no expiration rules, and create an independent archive containing both metadata and all referenced objects. The binding-level format/tool is a local rehearsal; remote operator steps require authenticated Wrangler and are not claimed tested against a Cloudflare account:
- Export a consistent D1 SQL snapshot:
wrangler d1 export DB --remote --config wrangler.deploy.json --output /absolute/new-backup/registry.sql. - Export the release object-key/hash/size inventory from that snapshot. With writes stopped, a D1
SELECT object_key,integrity,size FROM releasesthroughwrangler d1 execute DB --remote --json --command ...also matches it. Do not combine unrelated snapshots. Fetch each private object usingwrangler r2 object get BUCKET/KEY --remote --file /absolute/new-backup/objects/HASH --config wrangler.deploy.json. Verify SHA-256/size/package identity for every row, then write the archive manifest last. Copy the completed archive and a trusted checksum to an independent destination; define retention and recovery objectives. - Restore exclusively into a new, detached, empty D1/R2 pair with no serving Worker or writers. Verify the archive before writes; upload each verified object first with
wrangler r2 object put ... --remote --file ..., then import the metadata SQL withwrangler d1 execute DB --remote --file .... Never run restore SQL on the live database. Keep the pair detached if any import is incomplete. - Bind a separate read-only preview Worker to the restored pair and verify every listed artifact, identity and owner before cutover. Do not attach the original public domain until the operator approves a cutover. Provider backup, restore, retention and redeploy acceptance remain open until these commands actually run on verified resources.
Costs and limits: planning assumptions, not a bill estimate
Sources checked September 22, 2026: Workers pricing, Workers limits, D1 pricing, D1 limits, R2 pricing. Account-wide usage by other applications must be included.
Workers Free allows 100,000 dynamic requests/day and 10 ms CPU per invocation; static asset requests bypassing the Worker are free. Memory is 128 MB per isolate. Workers Paid starts at $5/month and includes 10 million requests and 30 million CPU-ms/month, with request/CPU overages. It is not an all-in marketplace price. No paid plan has been selected or activated.
D1 Free includes 5 million rows read/day, 100,000 rows written/day and 5 GB total storage (500 MB per database). R2 Standard includes 10 GB-month, 1 million Class A and 10 million Class B operations/month. Beyond included R2 usage, Standard storage is $0.015/GB-month, Class A $4.50/million and Class B $0.36/million; egress is free. Backups, orphan objects, repeated checks and abuse consume storage/operations too. Free allowances do not authorize billable activation or guarantee a zero bill.
Illustrative small preview, not measured demand: 100 extensions × 3 releases × 500 KiB ≈ 146 MiB of packages; seven full independent copies add ≈ 1 GiB. At 4,000 catalog queries/day scanning 300 rows plus 1,000 downloads/day, catalog reads are approximately 1.2 million rows/day; downloads add small indexed lookups. About 30,000 monthly object downloads and 300 daily backup reads remain below R2 read allowances. Twenty publications/day write tens to hundreds of metadata rows, plus rate/replay maintenance. This scenario fits request/row/storage allowances in isolation; it does not establish CPU eligibility.
The 8 MiB package contract has a 12 MiB signed JSON/base64 envelope. Package/signature verification and hashing may exceed the Free 10 ms CPU budget, and concurrent large requests may pressure 128 MB memory. Local wall time includes I/O and is not Cloudflare CPU billing evidence. Measure representative and maximum-size deployed requests before choosing a plan; if Free cannot support the contract, return the measured limit and a priced option for approval rather than silently lowering the size limit or enabling a paid plan. D1 stores only bounded metadata, avoiding its 2 MB row limit. The 1,000-release preview cap requires a catalog-pagination redesign before larger scale.
No hosted HTTPS, provider persistence, custom domain, cost ceiling, power-loss, multi-region behavior or production readiness is claimed by local runtime tests. Broader Drupal coverage/performance review remains separate; historical measured rebuild overhead is 22–30% with limited samples.