ADR 0200: Hosted venture on the Cratefield harness
Status: accepted, 2026-09-29
Context
The early-access waitlist (#146) is a small public API — join with a captcha, double opt-in by mail, status, admin CSV export — that must exist before the site launches, while the engine itself (ADR 0100) is still under construction. Writing the waitlist as bespoke Workers code would put a second HTTP stack, a second mail flow and a second captcha integration into this repository, none of them the product we are building.
The cratefield ecosystem (Cratefield/harness, published on crates.io) solves exactly this class of problem: a module mounts routes into a harness, adapters bind the ports (Turnstile captcha, Resend mail), and a runtime executes the composition on a platform (Cloudflare Workers). Its own ventures/cratefield-waitlist is a working precedent of the shape.
Decision
The waitlist is a hosted venture:
ventures/keepshipping, workspace memberkeepshipping-waitlist, deployed to Cloudflare Workers atapi.keepshipping.run, backed by D1 and the Workers rate-limiter binding.The venture depends on published
cratefieldcrates from crates.io (core, module-waitlist, runtime-cloudflare, adapter-resend, adapter-turnstile, testing), pinned to minor versions in the venture's ownCargo.toml— not[workspace.dependencies], so sibling work on the engine never collides with the venture's dependency tree.This crate is composition, not protocol: the venture owns the name and domain, the product code, the site origins, the mail sender and the adapter wiring. Request handling, mail, captcha and rate limiting come from the crates.
The venture adds one piece of code the harness deliberately does not: a write-origin guard in the worker entry point. CORS headers are response decorations, not authorisation; every write must carry an
Originof the site or is refused403before the harness runs (the guard is a pure function, unit-tested insrc/origin.rs).Degradation is safe by construction: no
TURNSTILE_SECRET⇒ no captcha port ⇒ joins refused in production (fail-closed); noRESEND_API_KEY⇒ the adapter reportsNotConfigured⇒ joins are captured aspendingwithout mail.Tests mount the same module composition the worker serves (
keepshipping_waitlist::module()) against the testing kit's fakes — the deployment's refusals are exactly the tests' refusals.
Consequences
The waitlist ships now, independent of engine milestones; the site can open signups without waiting for the harness.
This repository gains an external dependency surface on the
cratefieldecosystem: version bumps there are our upgrades, reviewed like any dependency (license allowlist andcargo denycover them). It is contained to the venture crate.cargo test --workspacebuilds the venture natively; deployment (worker-build,wrangler) happens outside CI until a release pipeline wants it (issue #146's deployment tasks).The venture block (
0200–0299) is the home of future hosted-part decisions.