Skip to content
CratefieldPublic

About

The open-source Rust backend harness behind Cratefield. Modules are crates, composed at compile time into one stateless Worker with its own database. MIT.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

605 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cratefield Harness. The open-source core. Modules are crates, compiled into one stateless Worker with its own database.

Status: M2 in progress Language: Rust Target: wasm32 on Cloudflare Workers Router: axum Database: D1 now, Postgres later License: MIT

parity: module suites on SQLite and Postgres (matrix)

cratefield.com · HARNESS · the open-source core


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, and the per-minor list of source breaks to fix when you move a venture forward: docs/UPGRADING.md. How crates reach crates.io: docs/RELEASING.md. Driving the venture CLI from an AI agent over MCP: docs/MCP.md. Metering an allowance per subject: docs/USAGE-METERING.md.

How a venture uses it

// src/lib.rs in a venture repo
Harness::builder()
    .venture(Venture::new("acme", "acme.example.com")
        .public_url("https://acme.example.com")
        .cors_origins(["https://acme.example.com"]))
    .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. In a generated venture the harness() ends in .expect("generated venture harness is valid"), so a bad composition fails at first compose; examples/tables-canary/tests/boots.rs composes the generated venture, which turns that into a cargo test --workspace failure.

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/>native runtime")]:::vendor
  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
Loading

Crates

All public crates are cratefield-*, MIT, plus the cratefield facade that pulls them together.

Depend on this repository by git for now, not by version. Most crates are published, but the published set cannot currently be resolved together: cratefield-core is on 0.5, and 23 of its published dependents still require ^0.4, which excludes it. Asking for core 0.5 and any runtime pulls two copies of core, whose error reads expected cratefield_core::Module, found cratefield_core::Module. Only cratefield-tables and cratefield-module-changelog are on the ^0.5 line today. Issue #464 is the release round that fixes this; after it lands, use versions.

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-anthropic TextModel over the Anthropic Messages API (Claude), through the HttpClient port — no vendor SDK, NotConfigured mode when the key is absent
cratefield-adapter-openai-compatible TextModel over the OpenAI chat-completions wire at any base URL — OpenAI, OpenRouter, Workers AI's compatible endpoint, or a local vLLM/llama.cpp/Ollama server — with token usage on every completion; Speech over the audio wire (/audio/transcriptions, /audio/speech)
cratefield-adapter-cloudflare-saas CustomHostnames over Cloudflare for SaaS custom hostnames, through the HttpClient port — a customer's own hostname served by the venture, with a NotConfigured mode and refused-hostname short-circuit
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 Database over sqlx for the native runtime (.github/workflows/parity.yml runs module suites against both SQLite and Postgres)
cratefield-import-supabase Step one of moving a Supabase project onto the harness (ADR 0026): a read-only inspection and a migration report — every item automatic, needs work or a blocker — behind fz import supabase inspect
cratefield-runtime-native The same harness as a single binary on tokio: axum on a TCP listener, Redis RateLimiter, in-process cron
cratefield-adapter-apns Push over Apple Push Notification service, HTTP/2 through the HttpClient port — no vendor SDK
cratefield-adapter-fcm Push over Firebase Cloud Messaging (HTTP v1), the same shape as APNs
cratefield-adapter-webpush Push over Web Push (RFC 8030/8188/8291): browsers and UnifiedPush
cratefield-push-auth The provider tokens the push adapters present: ES256 for APNs and VAPID, RS256 for Google service accounts
cratefield-adapter-github-app GitHub App auth: the RS256 app JWT, installation tokens (cached, single-flight, one re-mint on 401) and the user-to-server OAuth code exchange
cratefield-push-wiring Assembles the Push port from the environment: one env-variable table shared by serve(), fz push and fz doctor
cratefield-adapter-stripe Payments over the Stripe REST API
cratefield-adapter-polar Payments over Polar, a Merchant of Record: hosted checkout, usage meters through event ingestion, the customer portal, refunds, disputes and Standard Webhooks verification, through the HttpClient port
cratefield-mail-templates Branded transactional mail: one email-client-safe layout (600px tables, inline styles, dark mode, preheader, a text twin) themed with each venture's own website colours, logo and fonts, which every module that sends mail renders through
cratefield-module-cms A small content store with an editor: typed collections, versioned, in the venture's own database
cratefield-module-privacy Subject access and erasure, assembled from what every other module declares it holds
cratefield-module-notifications Push, an in-app inbox and email from one notify(), with per-account per-category preferences (NOTIFICATIONS.md)
cratefield-module-telemetry Aggregate usage counts from clients, consent-first, in the venture's own database (TELEMETRY.md)
cratefield-i18n Server-side localisation: Fluent catalogs, BCP 47 negotiation, text direction
cratefield-text-guard Keeps the facts across a model rewrite: finds the names, numbers, quotes, code, links and hashtags (plus caller-supplied spans) a rewrite must not change, and lists every one a rewrite dropped or altered
cratefield-text-diff Word-level diff (removed, added, same) in which protected ranges stay whole and come back as locked
cratefield-auth-client Verifies auth tokens in a consuming app: JWKS fetch and cache, ES256, an axum extractor
cratefield-oauth-client OAuth 2.0 over the HttpClient port: authorize URLs, code exchange, refresh, revocation, PKCE, and token sealing
cratefield-module-changelog A project's releases mirrored into the venture's own database and served over an API — the reads never call upstream
cratefield-module-webhooks Outbound webhooks: per-subject endpoints, HMAC-signed POSTs over the core outbox, dead letters and replay
cratefield-module-wallets Link EVM and Solana wallets to a signed-in account: single-use nonces, SIWE (EIP-4361) and SIWS verification, EIP-1271 contract signatures behind a port
cratefield-module-guardrails The policy engine every automated value-moving action must pass: hard denies, allowlists, required simulation, micro-USD caps, a kill switch and audit — default deny, fail closed
cratefield-module-sealed The server half of client-side sealed blobs (@cratefield/sealed is the client): opaque ciphertext at rest under a server outer wrap, crypto-shred erasure, and no server-side recovery path
cratefield-adapter-telegram TelegramBot over the Telegram Bot API, through the HttpClient port: verified secret-token webhook updates, sends under global and per-chat rate budgets, a token that never rides a log line
cratefield-module-telegram Telegram in a venture: the secret-token webhook, account linking, consent buttons that never move value without a passkey, and a module-notifications channel
cratefield-signer Signs EVM transactions, ERC-4337 UserOperations, EIP-712 data and Solana messages by key reference: guardrails decide before the provider runs, every attempt lands on an append-only audit chain, and session keys never leave the Secrets store
cratefield-adapter-turnkey The signer port's Turnkey provider: sub-organizations rooted in the user's passkey, the backend a delegated access user scoped by policy with a kill switch, signing by key reference and never exporting

Everything else in the workspace is unpublished — publish = false is what makes a crate private now, not a separate repository (ADR 0013):

Crate Role
cratefield-auth-* The auth service: auth-core plus one crate per login method (passkeys, OIDC/Google/Apple, password, magic link, Meta) and the deployable auth-worker. cratefield-auth-client, which verifies its tokens in a consuming app, is published; the service itself is not
fz-module-linkedin Private Factory Zero module: run a LinkedIn Company Page from the harness
cratefield-control-plane, cratefield-console, cratefield-accounts, cratefield-access, cratefield-catalog, cratefield-connections, cratefield-provisioning, cratefield-ui-generator The managed service: sign up, pick modules, connect Cloudflare and SSO, get a running venture
cratefield-introspect Reads a database's own catalog over the Database port (SQLite pragmas, Postgres information_schema) and answers in cratefield-tables' vocabulary — the source the dashboard's data screen renders

Ventures live in ventures/: cratefield-waitlist serves api.cratefield.com. A new venture is not copied from a template: fz init writes the manifest, fz add mounts modules, fz build generates the crate — docs/VENTURE-GUIDE.md walks the path, and examples/tables-canary is committed fz build output.

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. Built: examples/sidecar-module-template is the Worker a customer deploys, host→sidecar event delivery crosses inside wait_until (ADR 0017), and CI proves a slow sidecar forward live (wrangler dev sidecar event forward in .github/workflows/ci.yml, issue #258). docs/MOUNTING.md is the runbook.

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 (cratefield-adapter-postgres), native runtime (cratefield-runtime-native, examples/venture-native), parity suite (.github/workflows/parity.yml), data move (fz data export / fz data import) #18–#21

One epic is still specified but not scheduled: #23 multi-tenant schema — the native runtime serves one venture per process today (SECURITY.md). The other two former epics shipped: #24 embedded secrets is cratefield-secrets (envelope-encrypted over the Database port, docs/SECRETS-DESIGN.md), and #56 custom modules is the sidecar path (docs/MOUNTING.md).

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. Natively those fields are recorded on the span; on Cloudflare, where no tracing dispatcher runs, the runtime writes the same fields as one JSON line per request to Workers Logs. duration_ms is native-only — on Workers the platform's own invocation log carries the timing. Workers Logs is enabled in the checked-in wrangler.toml files ([observability] enabled = true in ventures/cratefield-waitlist/wrangler.toml and examples/venture/wrangler.toml); every response also echoes x-request-id. To pull one request's trail out of the logs, filter on the id the API returned (needs a deployed Worker and wrangler auth against your Cloudflare account, so CI cannot run it):

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-anthropic/       cratefield-adapter-anthropic
  adapter-openai-compatible/  cratefield-adapter-openai-compatible
  adapter-cloudflare-saas/ cratefield-adapter-cloudflare-saas
  adapter-sqlite/          cratefield-adapter-sqlite
  module-email-signup/     cratefield-module-email-signup
  module-waitlist/         cratefield-module-waitlist
  module-telemetry/        cratefield-module-telemetry
  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
  tables-canary/           a venture that declares its own tables, generated and committed
docs/
  ARCHITECTURE.md
  TELEMETRY.md             aggregate usage from clients, consent and opt-out
  TABLES.md                a venture declares its own tables; what it gets
  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
  llms.txt                 the build-and-deploy contract written for an agent
  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.

About

The open-source Rust backend harness behind Cratefield. Modules are crates, composed at compile time into one stateless Worker with its own database. MIT.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages