Skip to main content

Crate cratefield_core

Crate cratefield_core 

Source
Expand description

cratefield-core is the runtime-agnostic kernel of the Factory Zero harness: the Module contract, the Harness builder, port traits, RFC 9457 problem+json errors, the request Scope, an in-process EventBus and a TemplateRegistry (ADR 0001, 0002, 0007).

Core depends only on http, axum (default features off), serde, tracing, sea-query and pure-Rust crypto. It must never depend on worker, wasm-bindgen, tokio, reqwest, sqlx or rusqlite, and never touches std::fs or std::net — CI enforces this with a cargo tree check and the wasm build of examples/venture.

cratefield-core — The kernel.

cratefield-core on crates.io cratefield-core documentation MIT

§cratefield-core

The runtime-agnostic kernel of the Cratefield harness: the Module contract, the Harness builder, the port traits, RFC 9457 problem+json errors, the request Scope, an in-process EventBus, and the template registry (ADRs 0001, 0002, 0007).

Core depends only on http, axum (default features off), serde, tracing, sea-query and pure-Rust crypto. It must never depend on worker, wasm-bindgen, tokio, reqwest, sqlx or rusqlite — a CI job fails the build if one appears. That is what lets the same modules run on Cloudflare Workers (wasm) and, in phase 3, as a native binary.

§The shape

Harness::builder()
    .venture(Venture::new("acme", "acme.example.com").cors_origins([..]))
    .module(EmailSignup::new().double_opt_in(true))     // any Module impl
    .templates(EmailSignup::default_templates())        // + overrides
    .runtime(Cloudflare::new().db("DB"))                // any Runtime impl
    .build()?                                            // all problems at once

build() refuses a module that requires a port the runtime does not provide, two modules claiming the same table or route prefix, a module built against a different contract version (HARNESS_API), or a template override naming an unknown module — and it reports every problem in one error, not the first. router(ports) then mounts each module under /v1/<name>, adds GET /__health / GET /__ready, the request-scope middleware (request id + Scope in extensions, ADR 0007), CORS from venture.cors_origins, a 64 KiB body limit and /v1/* security headers.

§What lives here

PieceWhat it is
Module / ModuleContextthe module contract; see docs/MODULE-AUTHORING.md
Harness / HarnessBuildercomposition, validation, router assembly
ports::*Database, Mailer, Captcha, RateLimiter, Signer, KeyValue, HttpClient, Clock, IdGen, Defer — Send + Sync trait objects
Problem / problems!RFC 9457 errors with a stable slug taxonomy (generated into docs/ERRORS.md)
Scopeper-request id + span + wait_until, an axum extractor, never shared state
EventBusin-process "<module>.<event>" handlers, run in the emitting request’s scope
TemplateRegistrymail templates with per-venture overrides
HmacSignersigned tokens for confirm/unsubscribe links with key rotation (ADR 0006)
admin / csv / email / rate-limit helpersconstant-time admin auth, formula-injection-safe CSV, email normalization

§Writing a module

Start with docs/MODULE-AUTHORING.md — it builds a complete module, cratefield-module-hello, step by step.

§Testing a module

cratefield-testing is the conformance kit: fake ports, an in-memory SQLite Database, and request helpers over the real axum router, no network. Every module — public or private — passes it.


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

Re-exports§

pub use axum;

Modules§

billing
Provider-neutral billing lifecycle (ADR 0025, issue #593).
sigv4
AWS Signature Version 4 for the Blob port (issue #622).

Structs§

Accounting
The price sheet and the ledger every call is recorded to — what the router, the shadow classifier and the corpus runner are all given, so no classifier call through them goes unrecorded.
Action
One route the module serves, described for a renderer.
ActorHandlers
The handlers a venture registered, by kind. Built from the HarnessBuilder::actor registrations so an in-process actor host can dispatch to them.
ActorWrites
The writes one handler call staged, applied by ActorStore::commit.
AdapterId
The name a venture wires a classifier adapter under — the key prices, thresholds and agreement evidence are kept by. The port does not name its adapters, so the wiring does.
AgreementAt
The slice of a kind’s evidence the cheap adapter would have been trusted on at some thresholds.
AgreementLog
Accumulates an AgreementReport one compared answer at a time.
AgreementReport
Agreement between two named adapters, per question kind.
Answer
One answer: the value decided, the probability mass per label, and the probability of the value that came back.
ApiKeyPrincipal
A verified key’s answer to “who is calling, and what may they do?”.
ApiKeys
A store of API keys over the Database port; the owning app ships create_table_sql as a migration. Timestamps are RFC 3339 TEXT from the Clock port, the house shape (cratefield_core::SendCooldown documents why lexicographic order on that format is chronological order).
BlobMeta
What a store knows about an object without reading its bytes (issue #586): the metadata a Blob::head call returns, and one row of a BlobPage.
BlobObject
A stored object: its bytes and the content type to serve it with.
BlobPage
One page of a Blob::list (issue #586). cursor is None on the last page and otherwise an opaque token to hand straight back to the next list call — the module never parses it.
BlobStream
A streamed object body (issue #586): what Blob::get_stream returns instead of buffering the bytes. size and content_type are known before a single chunk is read, so a handler can set response headers from them.
BoundedHttpClient
Wraps any HttpClient so the port’s bounds hold on every runtime: the deadline is enforced through the Clock port (the only timer core is allowed to use), and the response is refused — by declared length before anything else, then by actual size — when it exceeds the HttpPolicy cap.
Brand
Venture branding for mail templates (issue #12). Defaults are text-only: factory-zero orange accent, no logo, no footer line.
CalibrationPoint
The cheap adapter’s confidence and margin on one compared question, and how it went — the evidence a Thresholds is checked against. Only the cheap side is kept: its confidence is what routing reads, and it is not comparable with the expensive one’s.
CallRecord
One classifier call as the ledger receives it.
CaptchaBinding
What a captcha adapter is actually bound to (issue #133). The harness reads this at build time to decide whether a production venture’s HumanForm routes can really be verified — providing the port is not the same as configuring it.
CatalogEntry
One declaration, and the module that owns the table.
Charge
A created charge/payment-intent and its status as Stripe reported it.
CheckoutRequest
A one-time hosted checkout (Stripe Checkout in payment mode).
CheckoutSession
The hosted page to send the browser to, and the session id to reconcile on.
ClassifierProfile
What a caller can learn about the adapter answering, short of its identity: the family its confidence comes from, and how much state it will carry before it truncates.
Column
A column of a View::Table.
Completion
A completed completion: the text, the model that answered (a provider identifier, for the log line), and the token usage.
ConfigError
Accumulates every configuration problem so Harness::build can report them together instead of one at a time.
ConnectAccountLink
The Connect account id (persist it) and the hosted onboarding URL.
ConnectAccountLinkRequest
Onboards a Connect account (a coach) and returns a hosted onboarding link.
Consumed
A successful consume: the amount was counted.
CorpusItem
One labelled corpus item: a state, the questions asked of it in one batch, and the gold answer for whichever ids have one (a missing id is ungraded).
Cost
The pico-USD cost of one call: a u64, saturating near $18.4 million, which no single classification spends.
Credential
The API credential the destination’s tracker expects — the customer company’s token, decrypted from cratefield-secrets immediately before the call and never stored anywhere else.
CustomHostname
A hostname as the provider sees it: its id, its state, its certificate and the DNS records the customer must publish.
Decision
Disagreement
One question the two adapters answered differently. Values only, never the state: a report is safe to paste into a ticket.
Dispute
A dispute against one payment (issue #602’s shape). payment_ref is the id RefundRequest::payment_ref takes for the same provider (a Stripe payment-intent, a Polar order), so a venture can tie the dispute to what it sold; charge_ref is the provider’s underlying charge or payment id.
DisputeListRequest
Which disputes Payments::list_disputes returns.
DisputePage
One page of disputes, newest first, and the cursor for the next page.
DrainOptions
How many items a drain_within may process in one tick, under what lease, and what one item costs the budget.
DrainReport
What one drain_within pass did.
Embeddings
One answered embedding request: one vector per input text, in input order, plus the model that answered and the input tokens it billed.
EmptyConfig
A configuration that always returns None (tests, offline builds).
EventBus
Registry of handlers, built once by Harness::build from every module’s events(). Cheap to clone (two Arcs).
ExactVectorIndex
The native, in-process VectorIndex: exact (brute-force) cosine ranking over an in-memory map — the true top-k, ties broken by id ascending, so the same corpus and query always answer in the same order. For development, tests, and small single-process corpora: data is not persisted, and a restart starts empty.
Exhausted
A refused consume: the allowance could not carry the amount, so nothing was counted and no work may proceed. An amount larger than the whole allowance is refused as exhausted from the start — on an empty meter, used is 0.
Filed
The result of a file that the tracker accepted: its id in the external tracker (persist it — Tracker::status is keyed by it) and the ticket’s human-readable URL, where the tracker has one.
Form
A form (application/x-www-form-urlencoded) extractor whose rejections are problem+json with the same shape as Json’s: body reads fail through the shared 413 slug (the size limit), everything else is a 400 validation problem. Needed for cross-site form_post callbacks (issue #46).
Github
GitHub’s scheme (X-Hub-Signature-256), the shape GitHub App and repository webhook deliveries carry: one header, sha256=<hex>, over the raw body alone. A named scheme so ventures declaring a GitHub delivery do not re-type the equivalent ProviderScheme fields.
Gitlab
GitLab’s scheme: header X-Gitlab-Token carrying the shared secret verbatim. Exactly SharedTokenScheme with that header fixed.
Harness
A built harness: immutable after build().
HarnessBuilder
Builder: .venture(..), .module(..), .runtime(..), .template(..), then .build().
HarnessConfig
The harness-level keys, parsed once from the environment Config (issue #3): HARNESS_SECRET (required, ≥ 32 bytes), HARNESS_SECRET_PREVIOUS (optional), ADMIN_TOKEN (optional, ≥ 32 bytes when set — issue #437), ENV (development|staging|production, default development), and the issue #137 key-ring keys: HARNESS_SECRET_REVOKED (optional, comma-separated key ids whose tokens must be refused immediately) and HARNESS_VENTURE (optional label binding tokens to this venture, so one venture’s links cannot verify in another that shares its secret by mistake).
HmacSigner
HMAC-SHA256 signer over a KeyRing (ADR 0006, ADR 0014).
HostnameClaim
A hostname a customer wants served, and how they will prove they own it.
HttpPolicy
Per-operation outbound bounds, attached to a request as an extension and resolved by every implementation with HttpPolicy::of_request. Both fields clamp to the port maxima: a caller may only tighten.
ImplicitTenant
The resolver a deployment with no control database uses: every host is the one venture, always active.
InMemoryLedger
A CostLedger that keeps every record in memory, oldest first. Process-local — one isolate’s view on Workers — which suits a measurement window and is not a billing system.
InboundMessage
One inbound message, read out of a verified provider delivery.
Inbox
A dedup ledger over the Database port. Construct it with the table the owning module declares (e.g. "<module>_inbox").
IssuedKey
A newly minted key: token is the only time the plaintext exists — show it once, store nothing; prefix is public and safe to display.
Json
A Json extractor and response whose rejections and serializations are problem+json (architecture section 6). Deserialization failures become a 400 validation-failed problem listing the field.
KeyRing
A bounded set of token-signing keys with explicit states (issue #137).
KindAgreement
Agreement on one QuestionKind.
LedgerTotals
Totals over a set of records.
LineItem
One line on a checkout: a name shown to the buyer and its price.
LocKeys
Native localisation keys. The strings are looked up in the app’s own catalogue by the operating system, so a notification can be localised without the server knowing the device’s language. Reserved here and mapped by the adapters; rendering is the i18n child’s job.
MapConfig
A Config backed by a map (tests, fz doctor with process env).
Member
A connected participant, identified by the venture after it verified the upgrade request’s bearer token (through the Signer or the auth client). A RoomHandler only ever sees this verified id, never a raw token.
Message
An outbound mail. text is always sent alongside html.
Migrations
The module’s migrations, per dialect. postgres differs from sqlite only where the SQL truly differs (ADR 0004).
ModuleConfig
Typed view over a Config for one module: prefixes every key with the module name in SCREAMING_SNAKE and parses values with defaults (issue #3).
ModuleContext
Everything a module’s router needs: its declared ports, the typed config, the bus, the templates and the venture identity.
ModuleSurface
One module’s entry in the composed document.
Money
An amount in a currency’s minor units (cents), the way Stripe takes and reports money. currency is a lowercase ISO-4217 code ("usd").
Months
How many whole months one Period::Anchored period spans. Must be non-zero; the arithmetic clamps a zero to one.
NoopDefer
Drops deferred futures with a warning. Used when no runtime defer is available; tests that assert on deferred work supply their own.
Notification
One notification, in transport-neutral terms. data is the custom key-value payload the app reads; collapse_id coalesces notifications the user has not seen yet.
Outbox
A durable work queue over the Database port. Construct it with the table the owning module declares (e.g. "<module>_outbox").
OutboxRecord
A leased outbox record handed to a drainer.
PartReceipt
One part that was uploaded, as S3 and R2 report it: its number and the ETag the store assigned (issue #586). A module collects these from Blob::upload_part and passes them, in ascending part order, to Blob::complete_multipart.
Payload
The signed payload: { purpose, subject, exp?, kid }.
PendingUpload
A multipart upload the module started but has not completed or aborted (issue #586): what Blob::list_multipart_uploads reports, so a module can find and abort the ones it abandoned.
PeriodWindow
A half-open period [start, end), both instants UTC.
PersonalDataCatalog
Every personal-data declaration in a composition, with the module that made it.
PersonalDataSet
One table’s worth of personal data, declared by the module that owns it.
PortalSession
The hosted portal URL to send the customer to. Short-lived: create one per visit, never store it.
PortalSessionRequest
A hosted customer-portal session (issue #589): the page where a customer changes plan, updates the payment method, reads invoices and cancels. customer_ref is the provider customer the adapter is configured to name (a provider id, or the venture’s own id where the adapter maps it).
Ports
The per-request bundle of resolved port implementations plus the typed config the runtime built from environment/secrets.
PresignedPut
A presigned upload: the URL to PUT to, the method, and the headers the client must send exactly — the signature covers each signed header, so a different value is refused by the store (issue #622).
Price
What one token costs on one adapter, in pico-USD, per side. Pricing is config, pasted off the vendor’s page through Price::per_million_tokens.
PriceSheet
What each adapter costs, wired once where the adapters are wired.
Problem
An API error, serialized as application/problem+json.
ProblemDef
Definition of one problem slug.
Prompt
One completion request: the tier asked for, the conversation so far, and what the caller will accept back.
ProviderScheme
A provider-specific scheme, configured rather than coded. Entries are separated by commas or spaces, so a provider may send several — for instance during a secret rotation.
QuestionKind
The id a question is asked under — the key of the map Classifier::ask takes. A venture asks the same kind of question under the same id, so agreement is measured and routing is decided per id: a classifier reliable on one kind is not therefore reliable on the next.
Quota
The budget a key spends against, when the limiter knows one (issue #538). A Workers Rate Limiting binding does not; a D1-backed fixed window does. limit is the budget per window, remaining what is left of the current one, and reset how long until it turns over — the three numbers rate_limited puts on the RateLimit-* headers.
RandomError
Why an entropy draw failed: no key material existed yet, so the message carries only the platform’s own words.
RedactingVisitor
A tracing field visitor that records (name, redacted value) pairs into a map. Runtimes use it in their formatters; tests use it to prove the redaction rules.
Refund
A created refund.
RefundRequest
A refund of a prior payment: the whole amount when amount is None, else a partial refund.
Rendered
A rendered mail, ready for Message.
RenderedSurface
A document serialized once, with the strong ETag clients revalidate against. Built at Harness::build for both the public and the admin variant.
RequestStream
A streamed request body (issue #585). Extracted by a handler on a route its module declared in Module::streaming_routes; the router has already put it in the request extensions and emptied the buffered body.
RequestSummary
The per-request span’s fields as plain data, attached to every response’s extensions by the request-id layer so a runtime with no tracing dispatcher — Cloudflare — can still log them. route is the matched pattern, never the raw path ("" when nothing matched). There is no duration_ms: wasm32 has no monotonic clock to measure it with.
ResponseStream
A streamed response body (issue #585).
RingKey
One ring entry. Deliberately not Debug: the ring’s debug view (KeyRing::states) shows ids and states but never key material (issue #135’s scrubbing rule applies to key bytes too).
Routed
One answer, with who gave it and why.
RoutingClassifier
Sends each question to the cheap adapter when its kind has earned it and to the expensive adapter otherwise; see RoutingPolicy.
RoutingPolicy
How RoutingClassifier decides. Defaults to RoutingMode::Off: a cheaper adapter is earned by measurement, never assumed.
RoutingPush
Dispatches by Recipient variant to the adapter a venture configured for that transport, so venture code holds one Arc<dyn Push> and never matches on the transport itself (ADR 0015).
RoutingTextModel
Dispatches by Prompt::tier to the adapter a venture configured for that tier, so venture code holds one Arc<dyn TextModel> and never matches on the tier or the vendor itself (ADR 0002). The router lives above the adapters, in core — the same place RoutingPush does, and for the same reason (ADR 0015).
RoutingTracker
Dispatches by Destination variant to the adapter a venture configured for that tracker, so venture code holds one Arc<dyn Tracker> and never matches on the destination itself.
Row
One result row: ordered (column name, value) pairs.
Rows
A small owned row model. No engine types leak past this point.
ScheduledBudget
What one module may spend in one scheduled invocation.
ScheduledLimits
The limits one scheduled invocation may spend in total: how long it may run, and how many subrequest-shaped steps it may make. None is unbounded, which is the only value the native runtime needs — UNBOUNDED.
ScheduledSplit
Divides one invocation’s ScheduledLimits across its modules, in order, with every module’s unspent share rolling forward to the ones after it.
Scope
Per-request scope, inserted into extensions by the request-id layer (issue #2). Handlers receive it through the Scope extractor; there is no ambient “current request”.
ScopedActors
Wraps an Actors so a module only reaches the kinds it declared in Module::actor_kinds, the actor equivalent of the table-ownership rule. When the module has a Clock wired, every call is also bounded by ACTOR_CALL_TIMEOUT; with no clock there is nothing to measure the wait with, so the call is made straight through and the host still serializes it. The harness applies this in Ports::view_for.
ScopedBlob
Wraps a Blob so every key is prefixed with <module>/ and no key can escape it. The harness applies this in Ports::view_for, so a module sees a store scoped to itself — the blob equivalent of the table-ownership check.
SendCooldown
A cooldown ledger over the Database port. Construct it with the table the owning module declares (e.g. "<module>_send_cooldown"); the owning module ships create_table_sql as a migration.
ShadowClassifier
Serves the expensive adapter’s answers, unchanged, and asks the cheap one the same questions to measure agreement. The cheap adapter cannot change or fail a result: its errors are logged, recorded and counted as skipped.
SharedTokenScheme
A shared-token scheme: the header carries the secret itself, in the clear, and a value equal to the secret is what proves the delivery — there is no signature over the body, so body is ignored entirely. The comparison is the verifier’s constant-time one (the same helper the HMAC path uses); a missing, empty or non-UTF-8 token refuses. Nothing here is bound to time, so a captured token replays: claim each delivery’s event id through the Inbox ledger.
SidecarMount
One mounted sidecar.
SidecarMounts
The mount table, parsed from configuration.
SignedDelivery
What a SignatureScheme read out of one delivery: the bytes the provider signed, the candidate signatures to compare, the timestamp the replay tolerance runs on.
Slugs
Every slug core emits. Keep sorted by status.
SqlMigration
One migration step, embedded with include_str! from crates/<module>/migrations/<dialect>/NNNN_name.sql (issue #8).
Statement
A rendered SQL statement: (sql, values) with ? placeholders, produced by rendering a sea-query query for a dialect. Modules build queries with sea-query and render through the helpers on this type (or let adapters do it); adapters bind values positionally.
StatusOnly
Marks a request whose caller wants only the status line. An implementation that honours it returns a response with no body and no body-describing headers (Content-Length, Content-Encoding, Transfer-Encoding), because there is no body here to describe.
StatusUpdate
A ticket-state change a tracker reported on its inbound status webhook: the same shape Tracker::status answers with, pushed instead of pulled. Ok(None) from a parse means a verified event that carries no status change (a comment, a ping) — not an error.
StepUsage
The usage of one model call inside a tool loop (issue #665): the same three token fields Completion reports, plus how many tool calls that step’s completion asked for.
StreamRoute
One route a module serves in streaming mode (issue #585). path is relative to the module’s /v1/<name> mount and uses axum’s pattern syntax: a literal segment, {param} for exactly one segment, or {*rest} for the remaining one-or-more (/upload, /files/{id}, /files/{*rest}).
StripeStyle
Stripe’s scheme, the shape adapter-stripe verifies: one header of t=<unix>,v1=<hex>[,v1=<hex>…], payload {t}.{body}. The header name is configurable because other providers use the same layout under their own name.
Subject
The person a verified credential speaks for.
SubjectVia
One hop to the subject, through a table that holds the id requests are made with.
Subscription
A recurring subscription as the provider reports it (issue #589). customer_ref names the customer the way the adapter is configured to name customers (a provider id, or the venture’s own id where the adapter maps it); the fields mirror what a billing module reconciles against, never an entitlement.
SubscriptionCheckoutRequest
A recurring hosted checkout (Stripe Checkout in subscription mode) against a Stripe Price the venture configured (e.g. $15/mo with a trial).
Surface
What a module declares from Module::surface.
SurfaceDocument
The document GET /__surface serves: composed at Harness::build, one entry per module in mount order, modules with an empty surface omitted.
Svix
Svix’s scheme — the one Resend, Clerk and a growing list of providers sign with. Headers svix-id / svix-timestamp / svix-signature (the webhook-* aliases are accepted too); payload {id}.{timestamp}.{body}; signature header a space-separated list of <version>,<base64> pairs of which only v1 is read — how an endpoint survives a secret rotation.
SystemClock
Real wall clock (time crate). Its timeout runs futures to completion — tests that need a real timeout supply their own clock.
TemplateRegistry
Immutable registry: venture overrides are inserted after module defaults at Harness::build, so they win on id collision.
Tenant
The tenant a request belongs to. Immutable, and built only by core’s resolution layer from a registry row — never from a header, a path segment, or module code.
TenantConn
A database handle bound to the request’s tenant.
TenantId
A tenant’s key in the control database’s registry.
TenantSummary
One row of the registry, as this port reports it.
Thresholds
When the cheap adapter’s answer is good enough to serve: at least min_confidence and a top-two margin of at least min_margin, under the Calibration these numbers were tuned for.
TicketComment
A note added to an existing ticket — e.g. linking a duplicate report to the ticket it duplicates. Adapters map the fields their tracker has and document what they drop; body_markdown is the only content every tracker takes.
TicketDraft
A ticket to file, in tracker-neutral terms: every adapter maps the fields its protocol has and documents what it drops. title and body_markdown are the only content every tracker takes; labels and environment are requests an adapter may flatten into the body, a label list, or nothing.
TicketStatus
A ticket’s current state and, where the tracker renders one, its URL.
TokenPolicy
A per-purpose lifetime ceiling (ADR 0014, issue #137).
Tokens
The tokens one call spent, and where the count came from.
ToolBudget
How far a tool loop may run (issue #665): a hard cap on model calls, and an optional cap on the tokens those calls may spend.
ToolCall
One tool call the model asked for: which call it is, which tool, and the arguments the model produced (issue #665). The arguments are exactly what the model sent — run_tool_loop validates them against the tool’s schema before any executor sees them.
ToolLoopOutcome
What a finished tool loop produced (issue #665).
ToolResult
The result of running one ToolCall, fed back to the model as context (issue #665). A tool-level failure is a ToolResult::error, not a failed loop: the model is given the error text and may try again.
ToolSpec
A tool the model is offered: a name, the instruction a model reads when deciding whether to call it, and the JSON Schema of the arguments object the call must carry (issue #665).
TransferCharge
A destination charge with an application fee: the buyer is charged amount, application_fee is kept by the platform, and the remainder is transferred to destination_account (the coach’s Connect account).
Turn
One message of the conversation a Prompt carries.
UiContext
What a UI renderer gets from the harness (ADR 0010). Built by Harness::router for every router it assembles.
UlidIdGen
Real ULID generator (monotonic per process).
Unconfigured
The verifier a deployment gets when it asked for one from the environment and the environment did not have it.
UploadId
Identifies one multipart upload in flight (issue #586). Opaque: an adapter mints it in Blob::create_multipart and a module only ever hands it back to the other multipart methods.
Usage
A usage-meter table over the Database port: durable per (subject, meter, period) counters. Construct it with the table the module declares (e.g. "<module>_usage") and ship create_table_sql as its migration. subject is who the allowance belongs to; meter is which resource is counted, so one table holds every meter.
UsageReport
A metered usage amount to report to the billing provider (Stripe Billing Meters). meter_event_name names a meter the venture configured; customer_ref is the provider customer id the meter maps the event to; value is the amount counted, in the meter’s own unit; identifier is the caller-chosen exactly-once key; timestamp is when the usage happened.
UsageReported
The outcome of a report_usage: the identifier that was reported and whether the provider said it had already been reported. A duplicate is success, not failure — the window was counted exactly once — so a caller completes the work either way.
Validation
One DNS record the customer has to publish for validation — a TXT pre-validation record (DCV), or a CNAME pointing the name here.
VectorFilter
Equality on metadata: a record matches when its metadata holds every key with an equal value; values compare as JSON, so "1" and 1 differ. The default matches all. Hosted indexes may filter only on keys pre-registered as metadata indexes; the exact index filters on all.
VectorMatch
One nearest neighbour: the record’s id, its cosine similarity to the query (higher is closer), and its metadata.
VectorNamespace
The search space one call sees: one tenant’s slice of the index.
VectorRecord
One stored vector: its id, its embedding, and the metadata a filter can select on — carried back on every VectorMatch.
Venture
Identity and CORS configuration for the venture this harness serves.
VentureSurface
The venture identity a renderer needs.
Vercel
Vercel’s scheme: header x-vercel-signature, a lowercase hex HMAC-SHA1 over the raw body — no prefix, no timestamp header. The digest is SHA-1 but the construction is HMAC, so SHA-1’s collisions do not apply (see the module docs); with no timestamp, replay defence is the Inbox dedup ledger’s job.
Verdict
A captcha verification verdict. ok: false with a reason from the provider’s error-codes; transport-level unavailability surfaces as ok: false, reason: "unavailable" unless the adapter is configured fail-open (staging only).
WebhookEvent
A webhook event the adapter has verified (signature + timestamp) before returning. kind is Stripe’s event type ("checkout.session.completed"); data is the event’s data.object for the module to interpret.
WebhookVerifier
The signature core every scheme shares: compute what a matching delivery must carry — an HMAC (SHA-256 or SHA-1) over the scheme’s signed payload, or, for a token scheme, the secret itself — compare it against every candidate in constant time with no early exit, and hold the delivery to its timestamp.
WriteGuards
What the composed modules demand of the runtime, module by module.

Enums§

ActorError
Actor failures.
AnswerValue
The decided value of one Question, shaped to match it.
ApiKeyError
Why a key operation failed; in every variant, nothing was written.
ApiKeyMode
Which environment a key belongs to: sandbox or production money.
Audience
Who an action is for. Drives the public/admin split of /__surface and, in the renderer, which pages need the admin session.
AuthError
Why a caller could not be identified.
BlobError
Blob store failures.
Calibration
Which family of numbers an adapter’s probabilities come from.
CallOutcome
How a recorded call ended. A failed call is recorded too: a cheap adapter that fails often is not cheap.
CallRole
Which side of a routing decision a recorded call was on.
Caller
Who the request is from.
Capability
Something a TextModel adapter may or may not be able to do beyond a plain completion. A port whose shape grows over time needs a way for the router to ask “can the model behind this tier actually do this” before a caller wastes a request on one that cannot.
CaptchaError
CertificateStatus
The edge certificate’s state — separate from ProviderStatus, since a hostname can be validated while its certificate is still issuing.
ClassifierError
Classification failures.
Consumption
The outcome of one consume: matched on directly.
ControlLevel
How severe a forwarded control event is (issue #441).
CustomHostnameError
Custom-hostname failures. Every variant that carries the provider’s own text is scrubbed in Display (issue #235), the same way MailError’s is; Debug stays raw for tests.
DataKind
The category a row falls into, in the vocabulary a privacy page and an App Store questionnaire both use.
DbError
Database failures, sanitized for logs and problem details.
Destination
Where a ticket is filed. One variant per supported tracker, because the eleven do not share a shape: a GitHub destination is an owner and repo, a webhook is a URL, and Intercom and HubSpot address nothing but the credential — the workspace/portal the token belongs to is the destination, so those variants carry no fields at all.
Digest
The hash an HMAC scheme runs on. SHA-256 everywhere it is not otherwise named; SHA-1 only where a provider signs with it (Vercel).
DispatchError
Disposition
What erasure does to these rows.
DisputePhase
The coarse dispute lifecycle a venture acts on: flag an account while a dispute is Open, restore it when the dispute is Won or Closed without a chargeback, and act on a Lost one (revoke what the payment funded).
DisputeStatus
Where a dispute (a chargeback, or the inquiry before one) stands, in the shape issue #602 set out, plus the two states a Merchant of Record reports around it. The provider’s own spelling is kept on Dispute::provider_status; DisputeStatus::phase is the coarse lifecycle a venture reacts to.
DnsRecordType
The DNS record type a Validation names.
EmbedError
Embedding failures. EmbedError::Provider carries the provider’s own words, so it is scrubbed in Display (issue #235); Debug stays raw.
ErasureStep
One step of a tenant’s erasure, in the order docs/TENANT-ONBOARDING.md §2 gives it.
GroundTruth
Which adapter the ground truth sided with when the two disagreed.
HostnameRefusal
Why a hostname was refused before it ever reached the provider.
HttpError
ImageLimit
Which image bound a Prompt crossed (issue #628): the count, one image’s encoded size, or the whole prompt’s. Carries only sizes and indices — never user content — so it is safe in a log line as-is.
ImageMediaType
The media type of an image a Part carries (issue #628): the closed set the providers accept, so a caller picks one rather than handing the provider a bare string to validate. #[non_exhaustive], so a match carries a wildcard.
InboundMailError
An inbound-mail failure. Signature carries no provider text — WebhookVerifier::verify answers a boolean, fail closed with no detail to leak — and Malformed scrubs the adapter’s own description in Display, because a payload can quote the message and its sender.
InboundStatusError
A status-webhook failure. The Signature variant carries no provider text: WebhookVerifier::verify answers a boolean — fail closed with no detail to leak — and the port keeps that property rather than inventing prose a log could leak. Malformed carries the adapter’s own description, scrubbed in Display the way TrackerError::Rejected scrubs the tracker’s words.
KeyState
A key’s role in the ring (ADR 0014, issue #137).
Kid
Which key in the crate::KeyRing a token was signed with. Tokens name their key so rotation never breaks links in flight.
KvError
MailError
Mailer failures, mapped by the adapter from provider responses.
ModelTier
Which class of model a completion asks for — a tier, never a vendor or a model name. A venture maps each tier onto a provider in its own wiring, and can move drafting from one vendor to another without touching a module.
OriginError
Why a string is not an origin this harness will name.
Outcome
What the browser should do with a successful response.
Part
One piece of a Turn: text, or an image with its media type and raw bytes (issue #628). A parts-bearing turn carries them in order; #[non_exhaustive], so a part is built with Part::text and Part::image rather than a struct literal.
PaymentsError
Payment failures. NotConfigured lets a venture build and run without Stripe (the port reports it rather than erroring); the rest map an upstream failure. SignatureInvalid is separated so a webhook handler answers 400 and never processes an unverified event.
Period
How a period is cut. Every variant converts to UTC first, so an instant in any offset names the same window as its UTC spelling.
Platform
The transport a Recipient is reached over, which is not a statement about the device: a UnifiedPush endpoint is Platform::Web on an Android phone (ADR 0015).
Port
Every port a module can declare in requires() / optional() (architecture section 4).
Priority
How urgently the notification should be delivered. Maps to APNs priority 10 (deliver now, may wake the device) and 5 (deliver to save power); to android.priority HIGH/NORMAL; to the Web Push Urgency header high/normal.
Processed
What the handler made of one record.
ProviderStatus
The hostname’s own state at the provider.
PushError
Push failures. Unregistered is separated because the caller must act on it — the recipient is dead and should be pruned — where the others are transient or a bad request.
PushOutcome
The result of a send that the provider accepted.
Question
One question asked of state. Three shapes, because a classifier is asked three kinds of thing: pick one of a named set (Question::Choice), place on an ordered scale (Question::Score), or answer yes or no (Question::Noul).
RateLimit
Outcome of consulting the limiter for one request.
RateLimitError
RateLimitFailure
What a limiter transport error means for the request (issue #133). Every call site picks one deliberately; it used to be an implicit “allow” buried in each module’s copy of the limiter loop.
RealtimeError
Realtime failures.
Recipient
Where a notification is sent. One variant per push transport, because the three do not share a shape: APNs and FCM take an opaque token, Web Push takes a subscription of three parts (RFC 8291).
Resolution
What a deployment’s registry says about a host.
Role
Who is speaking in a Turn. A prompt is a conversation, and a provider needs to know which side each part came from.
RoutePolicy
How a request to a declared route proves it is legitimate.
RouteReason
Why a question went where it went.
RoutingError
Refusals from RoutingClassifier::new: a policy that cannot mean what it says for these adapters is rejected at wiring time, not at the first question.
RoutingMode
Whether routing happens at all.
SendOutcome
Result of a send attempt.
Severity
How urgently the ticket should be attended to. Adapters map this onto whatever their tracker has — a GitHub label, a Jira priority, a Slack Notification-Alert — and document what they drop.
SignatureEncoding
How a provider encodes signature bytes in its header.
SignatureError
Failures surfaced by verify beyond “the token is simply invalid”, which is reported as None.
SignatureVerification
Which verifier proves a module’s RoutePolicy::Signature deliveries (issue #533): the Payments port — the default every existing module was built against — or the core webhook_signature HMAC scheme keyed by the module’s own config secret. The production gate demands whichever the module named, never both and never neither.
SignerError
Errors from constructing an HmacSigner or a KeyRing.
StreamError
Why a RequestStream ended early.
SubscriptionStatus
Where a recurring subscription stands (issue #589), in the provider’s own vocabulary — Stripe’s subscription statuses today, plus Other for one this version does not know. This is the provider’s word, not an entitlement: a billing module reads it and decides what access it grants (ADR 0025).
TemplateError
Tenancy
Which of the two tenancy shapes served a request.
TenantDbError
Why a tenant’s database could not be handed over. Deliberately carries the tenant id and nothing else — no DSN, no driver message.
TenantLifecycleError
Why a registry write did not happen.
TenantStatus
A tenant database’s reconciliation status.
TextModelError
Completion failures.
TicketState
A ticket’s state as the tracker last reported it. Adapters map their tracker’s workflow states onto these five, and onto TicketState::Unknown anything they cannot name — they never invent a sixth state, and a caller that needs the tracker’s own raw state name asks the tracker’s API by external_id.
TokenSource
Where a token count came from.
ToolChoice
How the model should choose among a prompt’s Prompt::tools: freely, not at all, mandatorily, or pinned to one named tool (issue #665).
ToolLoopError
Why a tool loop stopped without a usable answer (issue #665).
TrackerError
Tracker failures.
ValidationMethod
How the provider proves the customer controls the hostname: over HTTP (the provider fetches a token) or a TXT record the customer publishes. Cloudflare for SaaS defaults to HTTP.
VectorIndexError
Vector-index failures. Provider-text variants are scrubbed in Display (issue #235); Debug stays raw for tests.
VentureEnv
Deployment environment. Mirrors the ENV config key; Production drives the mandatory-captcha rule (architecture section 11).
View
How actions compose into something to render.

Constants§

ABOUT_BLANK
The type URI of a problem that has no URI of its own to name (RFC 9457 §4.2.1): a venture with no public URL, or a response rendered outside any venture’s context. The slug stays in the body’s title and in logs; callers branch on it where one exists.
ACTOR_CALL_TIMEOUT
How long one Actors::call may take before the caller gives up. The host keeps serializing the actor; the caller just stops waiting.
ALLOW_UNLIMITED_PUBLIC_ROUTES
Config key holding an operator’s explicit, recorded acceptance that this deployment serves its public writes and admin routes with no resolved rate limiter (issue #437). Same contract as ALLOW_UNPROTECTED_WRITES: the value is the reason, and a blank one does not count; the boot gate records it once, wherever the operator ships logs. It exists because the limiter was opt-in — a runtime that failed to resolve its binding degraded to no limiter and served — and because the escape from that must be as deliberate as the gap it accepts. Resolve the binding and delete the key.
ALLOW_UNPROTECTED_WRITES
Config key holding an operator’s explicit, recorded acceptance that this deployment serves guarded routes it cannot fully protect (issue #143).
CARD_DATA
Card-data fragments the harness never stores. With a normal Stripe integration the card details go straight to Stripe (Checkout, Elements, the SDKs) and never reach a backend, so a column or secret shaped like a card number, verification code or full expiry is a mistake. Deliberately the specific forms and not bare pan, track or expiry, which collide with legitimate columns — an audio pan, a music track, a session_expiry (issue #44).
CONFIRM_TOKEN_MAX_TTL_SECS
Seven days: the ADR 0006 confirmation default, expressed as a policy ceiling rather than a caller habit.
DEFAULT_MAX_STATE_CHARS
Chars of state a conservative adapter will send before it truncates: roughly 4 chars per token against the 32k-token window Workers AI documents, which leaves room in that window for the questions and the answer. An adapter whose provider has a different budget reports its own limit through ClassifierProfile::max_state_chars — this is the default, not a law.
DEFAULT_MAX_TOKENS
The token ceiling a Prompt starts with: enough for a drafted reply, small enough that a forgotten .max_tokens(..) cannot turn into a run away bill. Explicit in the type so “how long can the answer get” is a field a caller can read, not an adapter’s private default.
DEFAULT_PRESIGN_TTL
The presign lifetime a caller gets when it does not choose one: one hour — long enough for a browser to fetch or upload, short enough that a leaked URL ages out (issue #622).
DEFAULT_RESPONSE_TIMEOUT
Deadline applied to an outbound call that asks for none. Longer than the strictest in-tree self-bound (Turnstile’s 5s verify), shorter than anything that could pin a request.
DEFAULT_TOKEN_MAX_TTL_SECS
The default ceiling for any purpose not listed explicitly. An unknown purpose can never mint a non-expiring token by omission.
DEFAULT_TOLERANCE_SECS
How far a delivery’s timestamp may be from now, in seconds, either way — Svix’s and Stripe’s own tolerance, and the WebhookVerifier default. The signature stays valid forever; the timestamp is what stops replays.
FORMULA_PREFIXES
Characters that make a cell a formula when it starts with one of them.
GATEWAY_ADMIN_PURPOSE
The purpose the host mints only after its own require_admin has passed for an admin path (issue #131). It is what lets a sidecar re-materialize its own admin token: a token proving nothing more than “this came through the host” must not open the admin plane, or any captured forwarded stamp would be an admin credential for its whole lifetime.
GATEWAY_PURPOSE
The token purpose bound into the gateway MAC. A token minted for any other purpose — a confirm link, an admin session — cannot pass verification, and vice versa (issue #137’s rule, applied to #131).
GATEWAY_TOKEN_TTL_SECS
Gateway token lifetime: a forwarded request completes in one hop, so a minute is generous. Short TTLs shrink the replay window a stolen token buys an attacker (issue #131).
HARNESS_API
Contract version shared by core and every module. Harness::build rejects modules whose harness_api differs. Bumped only on breaking contract changes; cratefield-core’s major follows it.
HARNESS_ONE_WORKER
Truthy declares “this venture deploys as one Worker”: mounting a sidecar then contradicts the shape, and SidecarMounts::from_config rejects the mount table rather than starting a deployment that cannot reach it (issue #131).
HARNESS_SIDECARS
Config key holding the mount table, a JSON object of {"<module name>": "<service binding>"}.
HINT_KEYWORDS
The x-cf-* extension keywords the renderer understands on a field schema. Anything else under x-cf- is ignored, never an error, so a module can target a newer renderer than the one that serves it.
IMPLICIT_TENANT
The registry id a deployment without a registry resolves to.
MAX_ACTOR_KEY_BYTES
The longest an actor key or a storage key may be. Both are bounded by the same constant, the way they are addressed ("<kind>:<key>").
MAX_ACTOR_KIND_BYTES
The longest an actor kind may be. Kind names are [a-z0-9_-], so "<kind>:<key>" splits unambiguously.
MAX_ACTOR_MESSAGE_BYTES
Hard ceiling on one message sent to an actor.
MAX_ACTOR_REPLY_BYTES
Hard ceiling on one reply an actor returns.
MAX_ACTOR_VALUE_BYTES
Hard ceiling on one stored value. An actor holds small coordination state, not a store: past this bound the state belongs in the database, reached through Ports::db.
MAX_BLOB_BYTES
Hard ceiling on one stored object (issue #136). The port exists for small media — a coach’s voice clip, an avatar — and every caller reaches it through ScopedBlob or an adapter that enforces this bound before allocating or writing. Ten MiB is well past any legit clip at conversational bitrates and far below what threatens a self-hosted process or an isolate that buffers the bytes.
MAX_BODY_BYTES
Default request body limit for /v1/* JSON endpoints.
MAX_CONCURRENT_REQUESTS
Per-process budget for concurrent outbound requests. The native runtime shares one connection pool across the tenant it serves; this is what stops one request’s fan-out (say a cron fanning out emails) from monopolising the pool and the response-buffer memory behind it.
MAX_EMAIL_BYTES
The maximum accepted address length in bytes (RFC 5321 “forward-path”).
MAX_EXPORT_ROWS
Maximum rows one admin-export page may carry (issue #136). Exports are paged (limit/offset query params) and clamped to this: an unbounded SELECT * of a subscriber table into one in-memory String is an outage a hostile sign-up wave can trigger, and the bound is what a Worker isolate budgets for one response. x-cf-export-more: true tells the operator a next page exists.
MAX_IMAGE_ENCODED_BYTES
The most base64 bytes one image may encode to (issue #628): 3.75 MiB, the vendors’ per-image ceiling. encoded_image_len gives a raw image’s encoded length; Prompt::check_images refuses any over it.
MAX_KID_NAME
The longest accepted Kid::Named wire name. Key ids ride inside every token; the bound keeps a hostile payload from claiming arbitrary storage.
MAX_LARGE_BLOB_BYTES
The harness-wide absolute cap on the large-object limit a module may declare (issue #586). Five GiB is a harness-chosen ceiling — large enough to stream a video for a person who uploaded one, and well under R2’s ~5 TiB single-object maximum — so no module can declare its way past a bound the harness is willing to enforce.
MAX_LIST_LIMIT
The most objects one Blob::list call may return (issue #586): R2’s list cap. ScopedBlob clamps a caller’s limit into 1 ..= MAX_LIST_LIMIT before any adapter sees it.
MAX_LOCAL_BYTES
The maximum local-part length in bytes (RFC 5321).
MAX_MULTIPART_PARTS
The most parts one multipart upload may have (issue #586): S3’s and R2’s own limit. Blob::complete_multipart refuses more, and part numbers are 1 ..= MAX_MULTIPART_PARTS.
MAX_NAMESPACE_BYTES
The longest namespace name accepted, in bytes: Cloudflare Vectorize’s limit, and so the port’s.
MAX_PRESIGN_TTL
The longest presign lifetime a store accepts: seven days, the S3 maximum. ScopedBlob refuses a longer one before any adapter sees it (issue #622).
MAX_PROMPT_IMAGES
The most images one Prompt may carry (issue #628): four, inside the vendors’ own per-request range. Prompt::check_images refuses more.
MAX_PROMPT_IMAGE_ENCODED_BYTES
The most base64 bytes all of a prompt’s images may encode to together (issue #628): 4 MiB, the same MAX_RESPONSE_BYTES ceiling the HttpClient port puts on a response body. Prompt::check_images refuses a total over it.
MAX_RESPONSE_BYTES
Hard ceiling on a response body this port may return: an adapter’s largest legitimate upstream reply (JWKS documents, Stripe objects, LinkedIn people payloads) is orders of magnitude below it, and it is far below anything that threatens isolate memory. Callers may lower it per operation, never raise it.
MAX_RESPONSE_TIMEOUT
Ceiling on a caller-supplied deadline. A 30 s Worker request cannot responsibly spend more than half of it waiting on one upstream.
MAX_SIDECAR_ACTIONS
Largest action list a merged sidecar surface may declare (issue #131).
MAX_SIDECAR_SURFACE_BYTES
Largest sidecar surface document the host will merge (issue #131). A whole module’s declaration is a few kilobytes; anything approaching this cap is hostile or accidental, and parsing it costs the host.
MAX_SIDECAR_VIEWS
Largest view list a merged sidecar surface may declare (issue #131).
MIN_MULTIPART_PART_BYTES
The smallest a non-final multipart part may be (issue #586): R2’s rule. Every part except the last one listed must be at least this, and R2 additionally requires all non-final parts to be the same size; ScopedBlob leaves that to the adapter, which alone sees the parts as they are written.
MIN_SECRET_BYTES
Minimum secret length. HARNESS_SECRET must be at least 32 bytes.
SIDECAR_GATEWAY_SECRET
Shared secret between the host and a sidecar that enforces the gateway (issue #131). The host mints short-lived per-request tokens with it; the sidecar verifies them on /v1/* and /__surface. It is its own secret — HARNESS_SECRET stays venture-local (ADR 0009) — and must appear on both ends of a mounted pair.
SIDECAR_REQUIRE_GATEWAY
Sidecar-side switch: truthy means every /v1/* and /__surface request must carry a valid gateway token minted with SIDECAR_GATEWAY_SECRET. Requiring it without the secret is a configuration error and fails closed, not open (issue #131).
SLUGS
STATUS_TOKEN_MAX_TTL_SECS
The status-token policy (issue #137): a waitlist position is not secret, but a URL that never expires is a URL forever in logs, forwards and bookmarks. Ninety days, renewed on every confirm hit.
SURFACE_API
Contract version of the surface document, independent of HARNESS_API: a renderer or the control plane checks it before reading the document.
UNSUBSCRIBE_ACTION
The one action with a deliberately non-expiring ceiling, justified in ADR 0014: unsubscribe must always be reachable, and the longevity the signed link used to buy from the global key ring is now also carried by the per-subscription opaque token in module-email-signup, which outlives any key rotation because it lives in the subscriber’s row.
X_HARNESS_API
Contract version stamped on every harness response, checked by the host on every forwarded response. Stamping beats a cold-start handshake because an isolate outlives a sidecar redeploy (ADR 0009).
X_HARNESS_GATEWAY
Gateway token header, minted by the host per forwarded request and verified by a sidecar that set SIDECAR_REQUIRE_GATEWAY (issue #131).
X_HARNESS_MODULE
Module name stamped alongside X_HARNESS_API.
X_REQUEST_ID
x-request-id: accepted from the client when it matches ^[A-Za-z0-9_-]{8,128}$, otherwise generated as a ULID. Always set on the response (architecture section 6).

Traits§

ActorContext
The operations an ActorHandler performs on its actor while handling a message or an alarm. Writes are staged and committed together by the run_actor_message / run_actor_alarm runners.
ActorHandler
The protocol for one kind of actor. A module implements it and registers it with HarnessBuilder::actor; the host owns the serialization and calls these.
ActorStore
The backing store for one actor instance. An adapter implements this per instance; core owns the atomicity, so an implementation need only make commit apply its whole set or none of it.
Actors
An actor host: it owns serialization and durable storage for every actor instance on the runtime. Implemented by the runtime adapters (a Durable Object namespace on Cloudflare, an in-process registry natively, a fake in tests); a module only ever reaches a ScopedActors.
Auth
Turns a request’s headers into the caller they speak for.
Blob
A blob store. Keys are module-prefixed; the harness wraps this in a ScopedBlob per module so a module cannot name another’s objects.
Captcha
Classifier
Answers a set of typed questions about a state, over whichever classifier the venture wired. The sibling of TextModel: a module asks “which of these is it, and how sure are you” and holds Arc<dyn Classifier> without learning who answered.
Clock
Config
Read-only key/value configuration, resolved per runtime from environment variables and secrets (Workers Env) or the process environment.
CostLedger
Sinks what classifier calls cost.
CustomHostnames
Claims, reads and releases custom hostnames at whichever provider the venture wired.
Database
Execute statements against the venture database. Implementations: D1 (Workers), rusqlite (tests, self-hosted), Postgres (phase 3).
Defer
Dispatcher
Embedder
Embeds texts, over whichever provider the venture wired: exactly one vector per input text, in input order, so a caller can zip them.
HttpClient
IdGen
InboundMailSource
A vendor’s inbound-mail webhook, implemented per vendor by adapter crates: how to verify a delivery’s signature and how to read a message out of the verified body. The inbound sibling of StatusWebhook and the mirror of Mailer — it hands a parsed InboundMessage to a module, where Mailer takes one from it.
KeyValue
Mailer
Module
A Factory Zero module. Object-safe; composed as Arc<dyn Module>.
Payments
Moves money for a venture. Stripe today; the trait names only Stripe identifiers and hosted URLs, never card data.
Push
Sends notifications to a device, over whichever transport the Recipient names. An adapter serves one transport and returns PushError::unsupported_recipient for the rest; RoutingPush fans a mixed set of recipients out across the adapters a venture configured.
RandomBytes
The CSPRNG ApiKeys draws key material from. Injected like every environment effect (ADR 0002) — core carries no entropy source, and the deployment (or a test) supplies its own. An app crate declaring getrandom (the workspace-wide leaf pattern) writes:
RateLimiter
Realtime
Poke a room from outside a socket — a module’s HTTP handler (e.g. an admin ends a session) or a cron. The socket lifecycle itself is the runtime’s job (it calls RoomHandler); this is the part a module holds via Ports.
ResolveTenant
How a deployment turns a request’s host into a tenant.
RoomContext
The operations a RoomHandler performs on its room while handling an event. The handler holds no sockets of its own — it reaches them through this context — so a Durable Object can hibernate between events.
RoomHandler
The protocol for one kind of room. A module implements it; the runtime owns the sockets and calls these as events arrive.
Runtime
A runtime resolves environment bindings into Ports and declares statically which ports it can provide, so Harness::build can reject a module that requires something the runtime will never hand it (ADR 0002). Reference implementation: cratefield-runtime-cloudflare.
SignatureScheme
How a provider puts its signature material on the wire. Implement this for a provider none of the built-in schemes speak; the verification core is WebhookVerifier’s either way. Fail closed: None for a delivery whose headers cannot be read in full, never a guess.
Signer
Produces and verifies base64url(json).base64url(mac) tokens where the MAC is computed over the encoded payload string, so a token has exactly one valid encoding (ADR 0006).
StatusWebhook
A tracker’s inbound status webhook, implemented per vendor by adapter crates: how to verify a delivery’s signature and how to read a state change out of the verified body.
SurfaceSource
Where the current surface comes from (issue #76). With no sidecar mounted this is the document composed at build; with sidecars, each call fetches every mounted sidecar’s /__surface (public part) and merges it in, so a sidecar redeploy is seen on the next request (ADR 0009). An unreachable sidecar contributes nothing and is logged.
Template
One template. data is the caller’s JSON payload; locale is the requested locale tag (en, de, …) used by the implementation for its own variants.
TenantDatabases
How a deployment turns a resolved tenant into a database handle.
TenantLifecycle
The write half of the tenant registry: create a tenant, read the registry, and walk a tenant through its erasure.
TenantRouting
A deployment’s tenant plane: a host becomes a tenant, and a tenant becomes a database handle.
TextModel
Completes a prompt, over whichever provider the venture wired for the Prompt::tier it was asked for. An adapter serves any tier it is configured for (typically one); RoutingTextModel dispatches between the adapters a venture configured.
TextModelExt
Structured output on any TextModel (issue #580). Blanket-implemented for every TextModel, so an adapter cannot override — and cannot skip — the schema check, the validation, or the repair retry; import the trait to call the methods.
ToolExecutor
Runs one tool call and reports its result (issue #665).
Tracker
Files tickets into a tracker and reports their status.
TryFromValue
Conversion from a sea-query SeaValue for typed row access.
UiMount
A renderer the venture mounts at /ui with HarnessBuilder::ui (ADR 0010). Core defines the seam; cratefield-ui is the implementation, kept out of core so a venture without a UI carries no maud.
VectorIndex
Stores and ranks embedding vectors, within the namespace every call names — the sibling of Embedder, which makes the vectors this one keeps and searches. Hosted adapters (Vectorize) are eventually consistent — a write may take seconds to become queryable — while ExactVectorIndex is immediate.

Functions§

allowance_exhausted
A 429 usage/allowance-exhausted problem (issue #588): a metered allowance is spent for the current period. The body carries extension members a caller acts on — the meter, the limit, the used total and the period_end as RFC 3339 — and Retry-After is the delta-seconds to that reset, the same header rate_limited sets.
answer_margin
How narrowly the answer won: the top probability minus the runner-up, never negative.
assert_migration_set
Rejects a malformed migration set at compile time.
bearer_token
Extracts the bearer token from Authorization: Bearer <token>.
blob_error_from_stream
Maps a mid-stream failure to a BlobError (issue #586): a ceiling breach is BlobError::TooLarge, a transport failure is BlobError::Operation with the detail for logs.
captcha_effective
Whether the runtime’s Captcha port can really verify a token in production: the port is provided and, when the adapter reports, hostname-bound and not fail-open. An adapter that does not report (Captcha::binding = None) counts as effective — presence is all the harness can know about a third-party implementation; the adapter contract carries the duty to fail closed per request.
card_data_hit
The first card-data fragment text contains (case-insensitive), or None. Keeps card numbers, verification codes and full expiry out of migrations and secret names — with a normal Stripe integration none of them should exist (issue #44).
check_actor_call
Validates the (kind, key, message) one call carries: the kind, the key and the message size. Shared by both runners, the scoped view and the Cloudflare host, so all of them refuse a call the same way — the message bound is checked here rather than only inside a handler.
check_blob_size
Rejects a write whose payload exceeds MAX_BLOB_BYTES (issue #136). Every Blob::put implementation MUST call this before touching storage; ScopedBlob (the module-facing wrapper) already does, so an adapter’s own call is the second, defence-in-depth gate for runtimes-wired stores and direct users.
check_hostname
Validation helper, called by adapters before any provider call: normalises hostname (trimmed, lowercased, one trailing root dot stripped) or says why it cannot serve.
check_part_number
Rejects a multipart part number outside 1 ..= MAX_MULTIPART_PARTS (issue #586) before any store sees it.
check_rate_limit
Shared limiter loop: every key must pass, and a transport error is resolved by on_failure at the call site rather than per-module convention. No limiter configured (None) allows the request: composition chose to run without one.
client_ip
The client IP for rate limiting, from the headers.
constant_time_eq
Constant-time equality of two secrets, via fixed-length digests so timing does not leak the configured token’s length.
created_tables
Every table sql leaves behind, in the order it creates them.
csv_escape
Escapes one CSV field: formula-guard, then RFC 4180 quoting.
csv_row
Escapes and joins one CSV row, with a trailing newline.
declared_content_length
The declared Content-Length of a response, when it parses as a non-negative size. Implementations check this before touching the body, so an oversized declared length is refused without allocating for it (issue #136).
deployed_env
The environment this deployment runs in (issue #143).
encode_image
The standard, padded base64 encoding of an image’s raw bytes (issue #628) — the form every provider expects for inline image data. Here so an adapter needs no base64 dependency of its own.
encoded_image_len
The length of encode_image’s output for raw_len raw bytes (issue #628): 4 * ceil(raw_len / 3), padding included. Pure arithmetic, so Prompt::check_images can size an image without encoding it — to refuse an over-limit image before the allocation, not after.
env_disagreement
Whether the deployment’s environment contradicts the compiled one (issue #143). Not an error by itself — the stricter answer wins — but it means the build-time gate ran against the wrong environment, so the caller re-checks and says so.
forward_control_event
Forwards a one-line boot-time control event to the installed sink at its own level (issue #441); a no-op when none is installed (native, tests).
harness_api_mismatch
The message Harness::build and fz doctor report for a module whose Module::harness_api differs from core’s: it names the module, the module crate’s version, the API it targets, and the cratefield-core crate with its version and API (issue #17).
hint_field
Sets one x-cf-* (or any) keyword on a field of an object schema after derivation, for hints that only exist at runtime: a select whose options are the configured product list. Unknown fields are ignored so a rename in the body type cannot panic at build.
invalid_email_problem
A 400 validation-failed problem for a rejected address.
is_email_field
Whether a field is expected to carry an email address.
is_idempotent_sql
True when the SQL carries an idempotence guard, so re-running it after a crash is safe. Required of every migration marked transactional: false (RECONCILIATION.md §4).
is_plain_identifier
An unquoted SQL identifier: ASCII letters, digits and underscore, starting with a letter or underscore, at most 63 bytes — the shortest limit among the dialects the harness targets (Postgres truncates there; SQLite does not care).
is_secret_field
Whether a field name marks a secret: matches (?i)secret|token|key|authorization|password.
is_valid
A conservative validator: non-empty, one @, sane lengths, an ASCII local part from the unreserved set, and a dot-separated alphanumeric domain with no empty or hyphen-edge labels.
limit_stream
Wraps a byte-chunk stream so that the chunk which would push the total past max_bytes is replaced by StreamError::TooLarge (it is not delivered), the source is dropped, and every later poll answers None (issue #586). The tool an adapter reaches for to implement Blob::put_stream and Blob::upload_part without buffering, and the same enforcement, one layer down, that ScopedBlob applies.
lint_card_data
Card-data column or table names found in sql, as (fragment, why) pairs. Comments and string literals are ignored, so documenting the rule does not trip it.
lint_portable_sql
Returns (token, explanation) pairs found in sql.
measure_agreement
Asks both adapters every corpus item — one batch per item, cheap first, expensive only when cheap answered — and reports agreement per kind. Every call is recorded to accounting as CallRole::Shadow. An adapter error skips the item’s questions rather than aborting the run: a failure rate is evidence too.
migration_checksum
The sha256 of a migration’s SQL, lowercase hex. Recorded in harness_migrations when the migration is applied, so a later run can tell “already applied” from “applied, then edited” — the rule forward-only migrations rest on, enforced by the database rather than by a lockfile in one repository (issues #28, #34).
migration_edited
The message a migration whose recorded checksum no longer matches gets. Shared so both engines say the same thing.
migration_missing_guard
The message a non-transactional migration without an idempotence guard gets. Shared so both engines say the same thing.
migration_tables
Every table a module’s migrations leave behind, on either dialect.
normalize_email
Trim, Unicode NFC normalise, then lowercase. Idempotent.
origin_of
The RFC 6454 origin of url: scheme, host and non-default port, ASCII serialized.
payments_effective
Whether the runtime can verify webhook signatures for the RoutePolicy::Signature-guarded routes whose modules verify through Payments (the default): signature verification is the adapter’s per-request duty (verify_webhook + Inbox), and which secret it checks against is deploy configuration. A module that declared SignatureVerification::Hmac is not this function’s business — its production requirement is its secret key, checked by webhook_secret_readiness.
problem_registry
Every core slug definition, for tests and docs.
production_readiness
The production abuse-control gate (issue #133), as collected error strings (the ConfigError convention: report every problem at once). Empty for anything but Production.
rate_limit_keys
The rate-limit keys for one request: always ip:<ip> (or ip:unknown when no address is visible), plus email:<normalized> when an address is known. The limiter is consulted per key, in order.
rate_limited
A 429 rate-limited problem (architecture section 6). Retry-After: <seconds> comes from the Decision’s own pause, falling back to the quota’s reset when the limiter reports a window but no pause; a known quota also rides the IETF draft RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers (delta-seconds), so a well-behaved client can pace itself without probing (issue #538). A decision with no quota at all — a Workers Rate Limiting binding, or a failure resolved at the call site — is a bare 429 + Retry-After, exactly as before.
rate_limiter_effective
Whether the runtime has a limiter it can actually consult (issue #437).
receive_mail
Verifies a webhook delivery against source’s verifier, and only then parses it: parse never sees unverified bytes. The parameters are WebhookVerifier::verify’s — the endpoint’s secret, the request’s headers, the raw body bytes and now_unix for the replay tolerance.
receive_status
Verifies a webhook delivery against hook’s verifier, and only then parses it: parse never sees unverified bytes. The parameter shapes are WebhookVerifier::verify’s — the endpoint’s per-tenant secret, the request’s headers, the raw body bytes (before anything parsed them) and the current now_unix for the replay tolerance, where the vendor’s scheme carries a timestamp.
redacted_value
How one recorded field is stored: [redacted] for secrets, the hash for emails, the value passed through scrub_text otherwise (issue #135: a secret inside a generic uri/message/error value is invisible to the field-name rules alone).
remaining_erasure
The erasure steps still outstanding for a tenant in status.
request_id_is_valid
The character class and length bounds of an accepted request id.
require_admin
Checks Authorization: Bearer <ADMIN_TOKEN> for one admin request.
require_api_key
The per-request gate, the API-key sibling of cratefield_core::require_admin: reads Authorization: Bearer <token>, verifies it against keys, and demands scope. A missing header, malformed token, unknown prefix, revoked key or wrong secret all answer 401 api-key-unauthorized, one uniform problem so a probe cannot tell which held; a valid key without scope answers 403 api-key-forbidden; a store failure answers 503 not-ready (generic detail — the error itself is only logged).
retry_after
Retry-After (RFC 9110 §10.2.3) as a duration, so a caller can honour what a provider asked for.
run_actor_alarm
Runs a fired alarm. The staged context starts with the alarm cancelled, so a handler that does not re-arm leaves none set; a handler error commits nothing and returns the error, and the host retries.
run_actor_message
Runs one message: validates the kind, key and message size, runs the handler against a staged context, checks the reply size, and only then commits the staged writes. A handler error or an oversized reply commits nothing.
run_tool_loop
Runs prompt to completion through model, running any tool calls with executor, within budget (issue #665).
schema_for
Generates the schema for T the way every action does: draft 2020-12, definitions inlined so a renderer never has to resolve $ref.
scrub_request_url
Cuts one known request URL back to its origin everywhere it appears in message, and redacts that request’s bare target as well (issues #228, #229).
scrub_text
Scrubs secrets out of a free-text value (issue #135).
set_error_forwarder
Installs a process-wide forwarder for internal-error diagnostics (architecture section 11).
set_log_pseudonym_key
Installs the key that subject_hash uses to pseudonymise email values in logs (issue #135). A bare SHA-256 of a low-entropy email is dictionary-reversible, so a runtime derives this from HARNESS_SECRET and installs it once at startup; subject_hash then emits HMAC(key, email) instead. Unset (tests, or a runtime whose HARNESS_SECRET failed validation) makes subject_hash emit a fixed all-zero 000000000000 placeholder — fail-closed; no digest of the address ever reaches a sink. Boot-time infrastructure, not request state (ADR 0007); first-install-wins.
signer_effective
Whether the runtime can issue and verify the artifacts a RoutePolicy::SignedLink route rests on (issue #143).
stated_reason
The operator’s reason, if they actually gave one.
subject_hash
The redacted form of an email-ish value: a 12-hex pseudonym, no @ ever reaching the logs. When a pseudonym key is installed it is HMAC-SHA256(key, domain ‖ value) (a keyed pseudonym, resistant to dictionary reversal); when it is not, a fixed all-zero placeholder — correlation is lost, but a bare digest never leaks (issue #135, fail-closed).
svix_secret_key
An Svix endpoint secret’s key bytes: base64, with or without the whsec_ prefix Svix writes it with. None when it does not decode.
timeout
Typed wrapper over Clock::timeout_any. None means the clock abandoned the future after after.
ttl_secs
A Notification::ttl as the whole seconds every push protocol takes.
undeclared_tables
The tables a module owns and has said nothing about: the gap this type exists to close, named so a test can fail on it.
unlimited_public_routes_override
The operator’s recorded reason for serving public writes and admin routes unlimited, if they set one (issue #437).
unlisted_tables
The tables a module’s own migrations create and its Module::tables never mentions: the hole the declaration rule cannot see (issue #272).
unprotected_writes_override
The operator’s recorded reason for serving guarded routes unprotected, if they set one (issue #143).
validate_actor_key
Rejects an actor key or storage key that is empty or longer than MAX_ACTOR_KEY_BYTES.
validate_actor_kind
Rejects an actor kind that is not 1..=MAX_ACTOR_KIND_BYTES of [a-z0-9_-], so "<kind>:<key>" is unambiguous.
validate_questions
Checks a question set before an adapter renders it, so a malformed set is ClassifierError::Rejected and never a panic anywhere: every adapter calls this first, and every caller knows the shapes it hands the provider are whole.
validation_error
The reason an address is rejected, for problem details.
values_agree
Whether two answer values are the same answer.
verify_human_form
The shared CAPTCHA gate for HumanForm handlers (issue #133): the single place a module asks “is this form submission a human?”.
webhook_secret_readiness
The boot-time leg for HMAC-verified signature modules (issue #533): in production, each one’s configured secret key must be present and non-blank, or the module’s handler refuses every delivery. The errors name the module and the config key, never the value, and there is no waiver: an operator who wants the endpoint unverified removes the route, which is a build-time decision. Run by the boot gate (Harness::router’s readiness re-check), which holds the deployment config; the build-time gate has none, so it skips this leg — the same split as the overrides there.

Type Aliases§

AnyError
Error type for handler and scheduled-work results.
BoxFuture
An owned dynamically typed Future for use in cases where you can’t statically type your result or need to add some indirection.
BoxStream
A type-erased, Send stream of body chunks: the shape a runtime bridges to the wire, and what ResponseStream::take hands one. Same definition as futures_util::stream::BoxStream, written here because core depends on futures-core only.
EventHandler
A registered handler: receives the emitting request’s scope and the payload.
EventName
Event names are "<module>.<event>", e.g. waitlist.confirmed.