cratefield-core 0.3.0

Factory Zero harness kernel: Module trait, Harness builder, ports, errors
Documentation

The harness

Every product needs a backend, and almost none of them should be built from scratch. This is that backend, once.

A Rust harness you compile your own backend from: pick module crates, wire adapters, ship one stateless Worker with its own database. Cloudflare D1 today, a self-hosted native binary later, with no module rewrites in between.

This repository is the open-source core, MIT, and it is complete enough to run yourself today. Cratefield is the managed service being built on top of it: builds, migrations, secrets, domains and monitoring, so you do not have to operate any of it. That service is not built yet, and the site says so on every page.

Modules only see ports. A module never touches a Cloudflare binding, an environment variable, or a vendor client. It asks for a Database, a Mailer, a Captcha. Adapters answer. That one rule is what makes the later move off Cloudflare a change of a single runtime crate.

Read docs/ARCHITECTURE.md for the full design. Decisions, including why the TypeScript attempt was thrown away, are in docs/adr. Security controls and reporting: docs/SECURITY.md. What we store and for how long: docs/PRIVACY.md. Which module version runs on which core: docs/COMPATIBILITY.md, generated and drift-checked in CI. How crates reach crates.io: docs/RELEASING.md.

How a venture uses it

// src/harness.rs in a venture repo
Harness::builder()
    .venture(Venture::new("factory0", "factory0.ventures")
        .public_url("https://factory0.ventures")
        .cors_origins(["https://factory0.ventures"]))
    .module(EmailSignup::new().double_opt_in(true))
    .module(Waitlist::new().products(["kontinuum", "undercover-rockstars"]))
    .runtime(Cloudflare::new()
        .db("DB")
        .mailer(Resend::from_env())
        .captcha(Turnstile::from_env()))
    .build()?

That is the whole composition. build() refuses a module that requires a port the runtime does not provide, two modules claiming the same table or route, or a module built against a different contract version. The venture template runs it under cargo test, so a misconfiguration fails before wrangler deploy can.

Shape

%%{init: {"theme":"base","themeVariables":{
  "background":"transparent",
  "fontFamily":"ui-monospace, SFMono-Regular, Menlo, monospace",
  "fontSize":"13px",
  "primaryColor":"#141416","primaryTextColor":"#EDEBE6","primaryBorderColor":"#3A3A3F",
  "lineColor":"#6E6E76","textColor":"#8A8A8E",
  "clusterBkg":"transparent","clusterBorder":"#3A3A3F",
  "edgeLabelBackground":"#0E0E10"
}} }%%
flowchart LR
  REQ(["HTTPS<br/>request"]):::req --> H

  subgraph V["ONE VENTURE · ONE BINARY · ONE DATABASE"]
    H["<b>Harness</b><br/>axum router<br/>/v1/&lt;module&gt;"]:::core
    M1["email-signup"]:::mod
    M2["waitlist"]:::mod
    MX["your module"]:::ghost
    P{{"<b>ports</b><br/>Database · Mailer<br/>Captcha · RateLimiter<br/>Signer · KeyValue"}}:::port
    H --> M1 & M2 & MX --> P
  end

  subgraph A["ADAPTERS · THE ONLY VENDOR-AWARE CODE"]
    DB[("D1")]:::vendor
    KV[("KV")]:::vendor
    RS["Resend"]:::vendor
    TS["Turnstile"]:::vendor
    PG[("Postgres<br/>phase 3")]:::future
  end

  P --> DB & KV & RS & TS
  P -. "runtime-native" .-> PG

  classDef req fill:#0E0E10,stroke:#4C6FFF,stroke-width:1.5px,color:#EDEBE6
  classDef core fill:#141416,stroke:#4C6FFF,stroke-width:1.5px,color:#EDEBE6
  classDef mod fill:#0E0E10,stroke:#3A3A3F,color:#EDEBE6
  classDef ghost fill:transparent,stroke:#55555A,stroke-dasharray:4 3,color:#8A8A8E
  classDef port fill:#141416,stroke:#EDEBE6,stroke-width:1.5px,color:#EDEBE6
  classDef vendor fill:#0E0E10,stroke:#3A3A3F,color:#A9A8A5
  classDef future fill:transparent,stroke:#55555A,stroke-dasharray:4 3,color:#8A8A8E

Crates

All public crates are cratefield-*, MIT, plus the cratefield facade that pulls them together. Nothing is published to crates.io yet; depend on this repository by git.

Most ventures want one line:

cratefield = { version = "0.1", features = ["cloudflare", "resend", "waitlist"] }

The individual crates stay available and are the same types; the facade is a convenience, not a layer.

These crates were factory0-* until the first release. Renaming a published crate breaks every consumer, so the rename had exactly one free moment: before anything reached crates.io. It was taken then (ADR 0011). Private modules stay fz-* and stay unpublished.

Crate Role
cratefield The facade: one dependency that re-exports the core and pulls in a runtime, adapters and modules by feature (ADR 0011, 0012). Start here
cratefield-core Module trait, Harness builder, port traits, problem+json errors, request scope, event bus, templates
cratefield-runtime-cloudflare workers-rs entry points; D1, KV, Rate Limiting and wait_until mapped to ports
cratefield-adapter-resend Mailer over the Resend REST API, with a NotConfigured mode until a sending domain is verified
cratefield-adapter-turnstile Captcha over Cloudflare Turnstile, fail-closed
cratefield-adapter-sqlite Database over rusqlite: every test, and single-node self-hosting
cratefield-module-email-signup Email signup with double opt-in, unsubscribe, admin export
cratefield-module-waitlist Per-product waitlist with confirm, position, referral codes
cratefield-secrets Envelope-encrypted secrets over the Database port, two tiers, ciphertexts bound to their row (#39)
cratefield-kms The KMS port: wrap and unwrap data keys, with a local-file provider that refuses production (ADR 0102)
cratefield-ui Renders the module surface as HTML at /ui: pages, fragments, in-process form dispatch, the cf-* styling contract (ADR 0010)
cratefield-cli Binary fz: migrations collect, doctor, modules
cratefield-testing Conformance kit every module, public or private, must pass
cratefield-adapter-postgres Phase 3. Database over sqlx for the native runtime
cratefield-runtime-native Phase 3. The same harness as a single binary on tokio

Private modules are fz-* crates in harness-private, consumed as pinned git dependencies. New ventures start from venture-backend-template. The first consumer is factory0-backend.

What a module is

A crate implementing one trait.

pub trait Module: Send + Sync + 'static {
    fn name(&self) -> &'static str;              // mounted at /v1/<name>
    fn requires(&self) -> &'static [Port];       // build fails if one is missing
    fn migrations(&self) -> Migrations;          // include_str! SQL, portable subset
    fn router(&self, ctx: ModuleContext) -> axum::Router;
    // version, optional ports, tables, events, scheduled …
}

A module is mounted one of two ways, and a caller cannot tell which. Compiled in is the default this README describes: the crate is linked into the Worker. Sidecar gives one module its own Worker, built and deployed separately and mounted at the same /v1/<name> over a Cloudflare service binding, binding the same database and secrets. It exists so a module whose source should not enter the shared artifact can still run as a real module with real ports. Designed, not built: epic #56.

Migrations are plain SQL in a subset SQLite and Postgres both accept. Queries go through sea-query, which renders for either. Confirmation and unsubscribe links are HMAC-signed tokens with key rotation, so there is no session store. Request scope travels in axum extensions, never in shared state; the conformance kit includes the concurrent-request test that proves it.

Roadmap

Milestone Contents Issues
M0 Foundation workspace tooling, core, Cloudflare runtime, Resend and Turnstile adapters, SQLite adapter, fz, testing kit #1–#9
M1 First modules email-signup, waitlist, templates, security baseline, observability #10–#14
M2 First venture live crates.io publishing, docs, contract versioning, api.factory0.ventures #15–#17
M3 Self-hosted portability Postgres adapter, native runtime, parity suite, data move #18–#21

Three epics sit outside the milestones because they are specified but not scheduled: #23 multi-tenant schema, #24 embedded secrets, and #56 custom modules without rebuilding the shared bundle.

Progress is visible in the milestones.

Observability

One structured span per request carries request_id, method, route (the matched path), module, status, duration_ms, ip_hash and ua_family — never an email address. Workers Logs is enabled in the template wrangler.toml ([observability] enabled = true); every response also echoes x-request-id. To pull one request's trail out of the logs, filter on the id the API returned:

wrangler tail --format pretty --search <request-id>

The error taxonomy (every problem slug, status and meaning) is docs/ERRORS.md, generated from cratefield-core's registry and checked in CI for drift.

Toolchain

Stable Rust pinned in rust-toolchain.toml, target wasm32-unknown-unknown, worker-build, wrangler. CI runs fmt, clippy -D warnings, test, cargo deny, and builds the example venture to wasm so a native-only dependency cannot slip into a module.

cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
(cd examples/venture && worker-build --release)

Layout

crates/
  core/                    cratefield-core
  runtime-cloudflare/      cratefield-runtime-cloudflare
  adapter-resend/          cratefield-adapter-resend
  adapter-turnstile/       cratefield-adapter-turnstile
  adapter-sqlite/          cratefield-adapter-sqlite
  module-email-signup/     cratefield-module-email-signup
  module-waitlist/         cratefield-module-waitlist
  kms/                     cratefield-kms
  secrets/                 cratefield-secrets
  ui/                      cratefield-ui
  cli/                     cratefield-cli  →  fz
  testing/                 cratefield-testing
examples/
  venture/                 smallest complete venture; CI builds it to wasm
docs/
  ARCHITECTURE.md
  KEY-ROTATION.md          rotating data keys and re-wrapping under a new master key
  MIGRATION-STREAMS.md     two repositories applying migrations to one database
  RECONCILIATION.md        boot-time reconciliation across tenant databases
  MOUNTING.md              compile a module in, or run it as a sidecar
  UI.md                    the UI surface, its markup contract, UiSpec, admin
  ui-llms.txt              the same contract written for a generator
  adr/                     0000 … 0010
tools/
  banner-render.html       source of the README banner
  render-banner.sh         regenerates it with headless Chrome

License

MIT. Built in the open for Cratefield, a Factory Zero venture.