What it is
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).
From the crate’s own README.
Read more
History
Changelog.
From the crate’s CHANGELOG.md in the harness, as of 0c5560b. 12 versions recorded, newest first. Every published version is on crates.io.
Other
Added
- A
UrlSafetyport (issue #857).checkasks whether one URL is safe to fetch — malware, social engineering, unwanted software — andcheck_manybatches the ask, verdicts in input order. Limits are checked locally before any network call bycheck_url/check_batch, which refuse withUrlSafetyError::LimitExceeded(2 048-byte URLs, batches of 500 — Safe Browsing v4's ceiling). The privacy contract is the port's first doc: the checked URL is a secret, so no implementation may log it or place it in an error string, and the one variant carrying provider text is scrubbed throughscrub_textinDisplay.CachedUrlSafetydecorates any implementation with a bounded,Clock-driven TTL cache that honours the provider's advised retention and never holds its lock across anawait. The port only: no provider adapter ships yet, so the Google Safe Browsing / Web Risk adapter issue #857 asks for is still to come.
- A
10 earlier versions 0.2.0 to 0.9.0
Added
- core SSE response helper and AI SDK UI message stream encoder (#860) (#871)
- core Speech port — transcription and streamed synthesis, with Workers AI and OpenAI-compatible adapters (#861) (#868)
- core HttpClient outbound budgets — per-upstream token buckets, Retry-After backoff, daily quota alerts (#765) (#858)
- core report per-provider mailer status in /__health (#793) (#845)
- Chunk-and-reassemble completion (issue #847).
split_text_chunkssplits a long document into chunks of at mostmax_charschars on paragraph boundaries, never inside a fenced code block (an oversized fence is the one atomic, oversized chunk), and the round trip is byte-exact;reassemble_text_chunksstitches the pieces back in order;TextModelChunkedExt::complete_chunkedruns the split → per-chunkTextModel::complete→ reassemble loop over anyTextModel, so a long rewrite no longer has to fit inside oneHttpPolicyresponse timeout. For generation-shaped jobs preferTextModel::stream(issue #859) — chunking buys its bound with one round trip per chunk. - A
Speechport (issue #861, ADR 0029).transcribe(audio in, aTranscriptwith optional word timings out) andsynthesize(text in, a streamedSynthesisout), plusvoices,capabilitiesandlimits. Limits are checked locally before any network call bycheck_transcribe/check_synthesize, which refuse withSpeechError::LimitExceeded;wav_durationreads a WAV header's length without a codec.NotConfiguredSpeechis the standing placeholder for an unwired port. - The facade gains a
sealedfeature (issue #757). It re-exports the newcratefield-module-sealedascratefield::sealed: the server half of client-side sealed blobs. It stores opaque ciphertext under a server outer wrap, erases by crypto-shredding, and has no server-side recovery path;@cratefield/sealedis the browser client. Nothing incratefield-corechanges. - The facade gains
telegramandtelegram-eventsfeatures (issue #764). They re-export the newcratefield-adapter-telegramascratefield::telegramandcratefield-module-telegramascratefield::telegram_events: the Telegram Bot API adapter, and the verified webhook, account linking and consent buttons that never move value without a passkey. Nothing incratefield-corechanges. - The facade gains a
guardrailsfeature (issue #763). It re-exports the newcratefield-module-guardrailsascratefield::guardrails: the policy engine every automated value-moving action must pass, with hard denies, allowlists, required simulation, micro-USD caps, a kill switch and audit. It denies by default and fails closed. Nothing incratefield-corechanges. Mailer::providers()names the providers behind the port (issue #793). The newMailProvider { name, configured, healthy }reports a name and two verdicts and never a credential — a health report that leaked an API key would be a secret published on an unauthenticated endpoint./__healthgains a"mailers"array beside the unchanged"mailer"key; the two answer different questions (is the port wired, does the adapter hold its key), so a Resend with no API key readsconfiguredandconfigured: false.healthyisNone— and the JSON key is then omitted — for an adapter with no probe, because "unknown" is not "unhealthy". The method has a default, so every existing adapter compiles unchanged and still lists itself.
Fixed
- auth a webview can press the magic-link confirm button; themeable hosted pages and mail; friendly validation (#840) (#841)
- A handler's
Referrer-Policy: same-originsurvives the no-store stamp (issue #840)./v1/*and token-carrying responses still getno-referrer, except when the handler itself answered withsame-origin, which also sends nothing cross-site. Underno-referrera browser sendsOrigin: nullon a page's own form POST, which a login-CSRF guard must refuse from clients that send noSec-Fetch-Site(webviews, older Safari). Any other policy a handler sets is still overwritten.
Other
- streaming completions (text, reasoning, tool-call and usage deltas) with idle/total timeouts and cancel-on-drop (#869)
- module-sealed + @cratefield/sealed: zero-knowledge sealed blobs (passkey PRF + recovery-code wraps, server outer wrap, no server recovery) (#757) (#867)
- adapter-telegram + module-telegram: verified bot webhook, account linking, rate-limited messages, signed action buttons and a notifications channel (#764) (#864)
- allowlists, caps, required simulation, hard denies and a kill switch for value-moving actions (#763) (#862)
Other
- charge amounts, currency exponents and ledger entry types (#832)
Fixed
- thirteen error-handling defects, plus make the guard-script tests survive a non-executable checkout (#828)
Fixed
- close eight concurrency races across the pool cache, auth caches, realtime rooms, waitlist confirms and tracker adapters (#823)
Added
- A provider-neutral billing lifecycle, in
billing(issue #593, ADR 0025). New pure types and functions —LifecycleEventand itsLifecycleKind,Status,SubscriptionState,LifecyclePolicy,transitionandgives_access— that map a Stripe or RevenueCat webhook onto a state and answer whether it grants access, with no I/O and no vendor payload parsing (that stays in the adapters, issues #600 and #601). Additive: no existing API changed andHARNESS_APIis untouched. - TextModel image inputs (issue #628):
Part(Text/Image),Turn::user_parts,Prompt::user_parts,Prompt::has_images,Prompt::check_images,Capability::Images,TextModelError::ImageLimit, theencode_image/encoded_image_lenhelpers, and theMAX_PROMPT_IMAGES/MAX_IMAGE_ENCODED_BYTES/MAX_PROMPT_IMAGE_ENCODED_BYTESbounds. The router andrun_tool_loopgateCapability::ImageslikeCapability::Tools. StatusOnly, a request marker asking for the status line alone. An implementation that honours it returns no body and no body-describing headers, because there is no body to describe — cheaper thanHEADwhere the endpoint is already known and only liveness is wanted. It rides as a request extension exactly asHttpPolicydoes and waives the body, not the ceilings; an implementation that does not read the extension answers the request like any other (#714).
Other
- Stripe customer portal sessions and subscription lookup (#589) (#740)
- schema-validated structured output (complete_json / complete_as) (#739)
- Venture CORS: accept browser-extension origins (chrome-extension, moz-extension, safari-web-extension) (#737)
- Inbound mail: a verify-then-parse source in core, a Resend inbound adapter, and ADR 0028 for channels (#563) (#734)
Added
Changed
- Breaking (found by cargo-semver-checks):
ports::text_model::Turnis now#[non_exhaustive]and no longer derivesEq, so it is built withTurn::user,Turn::assistant,Turn::assistant_tool_callsorTurn::tool_resultsrather than a struct literal;TextModelErroris now#[non_exhaustive], so amatchon it outside core needs a wildcard arm (#705).
- Breaking (found by cargo-semver-checks):
Added
- core breaking add Destination::Colonizer { repo } and RoutingTracker::colonizer (#676) (#687)
- core breaking name problem
typeURIs under the venture's own base (#557) (#581) Paymentscarries a Merchant of Record (ADR 0027). New optional methods, each with a default, so every existing adapter compiles unchanged andHARNESS_APIstays 1:verify_webhook_request(&HeaderMap, body)for providers that sign over several headers (Standard Webhooks; the default readsStripe-Signature);create_portal_sessionwithPortalSessionRequest/PortalSessionin issue #589's shape; andget_dispute,list_disputesandclose_disputewithDispute,DisputeStatus,DisputePhase,DisputeListRequestandDisputePagein issue #602's shape.DisputeStatus::phase()folds a provider status intoOpen/Won/Lost/Closed;Dispute::event_key()is theInboxkey for one transition. The defaults answerPaymentsError::Unsupported. (#690)Tracker::commentandTicketComment: add a note (optionally linking a URL) to an existing ticket, so a duplicate report links into the ticket it duplicates. The default impl refuses withTrackerError::Rejectednaming the tracker, so existing adapters compile unchanged;RoutingTrackerforwards it by destination. (#559)- Inbound status webhooks: the
StatusWebhooktrait,StatusUpdate,InboundStatusErrorandreceive_status, which verifies a delivery with core'sWebhookVerifierbefore the adapter ever parses it. (#559) Destination::Freshdesk { domain }andRoutingTracker::freshdesk; no adapter ships for it yet. (#559)Destination::Colonizer { repo }andRoutingTracker::colonizer; no adapter ships for it yet. (#676)TicketStateis nowSerialize/Deserialize(snake case), so a state can cross the inbound webhook wire. (#559)
Other
- Blob port: presigned GET and PUT URLs for R2 through the S3 API (SigV4) (#622) (#663)
- report metered usage to Stripe Billing Meters, idempotently (#662)
- App JWTs, cached installation tokens, and a named GitHub webhook scheme (#623) (#637)
- Webhook verification: Vercel (HMAC-SHA1) and GitLab (shared-token) schemes (#636)
- Stream request and response bodies for routes a module declares (#585) (#635)
- Usage metering: per-subject, per-period counters with an atomic check-and-increment (#588) (#634)
- CustomHostnames port in core, with a Cloudflare for SaaS adapter (#590) (#630)
- ADR 0025: provider-neutral billing lifecycle, entitlements and revenue ledger, fed by Stripe and RevenueCat (#592) (#631)
- Route policy: let one module declare a signature verifier per webhook route (#595) (#632)
- comment op, inbound status webhooks, Freshdesk destination and the Jira Cloud adapter (#559, part 1) (#582)
- VectorIndex and Embedder ports, with Cloudflare Vectorize and exact in-process adapters (#561) (#568)
- OpenAI-compatible adapter, cached-token usage on every completion, shared TextModel conformance suite (#560) (#567)
- Production readiness fails when a module declares RateLimiter and none is mounted; a missing Workers limiter binding fails closed (#562) (#569)
Changed
- Breaking — problem
typeURIs are named under the serving venture's own base.Problem::type_urinow takes the venture's base as its argument, so the next release is 0.7.0 (a breaking change is a minor bump under 0.x):typeis<public_url>/problems/<slug>— derived from the venture'spublic_url, overridable with the newVenture::problem_base(..)— andabout:blank(RFC 9457 §4.2.1) for a venture with no public URL, instead of one hard-codedhttps://factory0.ventures/problems/base for every venture.Problem::into_responserenders the context-freeabout:blankand stores the problem in the response extensions; the harness's outermost layer re-renders the body under the venture's base, so every path that serves a problem — module handlers, the tenant and readiness gates, rejections, 429s — names the venture it served for; the runtimes' pre-router refusals (native421, Worker413and unresolved-limiter503) name it through the newProblem::into_response_with_base. Clients must match on the slug — the part after/problems/,auth/…namespaces included — never on the full URI. The slug list is unchanged (docs/ERRORS.md). (#557)
A breaking change is pending here:
Destinationis an exhaustive enum and gainedFreshdeskandColonizer, so amatchover it no longer compiles unchanged; the next release of core is therefore 0.7.0, not 0.6.1.Destination::Jira { site }is documented as the bare hostname (acme.atlassian.net), not the hostname prefix: the newcratefield-adapter-jirarefuses anything else, so the Basic credential cannot be sent to another host. (#559)
0.6.0
crates.ioA breaking release:
Portis an exhaustive enum and gainedTextModel,Tracker,ClassifierandAuthsince 0.5.0, so amatchover it no longer compiles unchanged. Every published dependent is re-released against^0.6in the same round, so the crates.io set resolves on one core again (#462, #464, #558).Added
webhook_signature: a generic HMAC-SHA256 webhook verifier (WebhookVerifier) with pluggable schemes (Svix,StripeStyle,ProviderScheme), plusSignatureVerificationand a defaultedModule::signature_verification(), so aRoutePolicy::Signatureroute can be proved by awebhook_signaturescheme instead of thePaymentsport; the boot gate then checks the module's secret key (webhook_secret_readiness) in place of thePaymentsleg. (#533)
0.4.0
crates.ioA major bump because it has to be:
cargo semver-checksagainst the published 0.3.1 reports 11 major and 0 minor failures. Under 0.x that is 0.4.0. Every item under Changed below is one the tool found, not one read off the commit log.Note also that 0.3.0 and 0.3.1 were published without entries here; this one covers the surface as it stands against 0.3.1.
Changed
Database::batchis gone.batch_atomicreplaces it and has no default implementation, so everyDatabasemust provide it — the point being that a batch is all-or-nothing by contract rather than by hope. (#126)- Structs that could be built from a literal no longer can, having gained public fields:
ModuleContext(unprotected_writes_accepted,personal_data),Ports(realtime,tenants),HarnessConfig(harness_secret_revoked,harness_venture),Action(policy) andNotification(icon,url,ttl,badge,silent,loc). MessageandSqlMigrationare#[non_exhaustive], as are theKidandSignerErrorenums — so literals and exhaustive matches on them need a rest pattern. (#255)PortgainedRealtime, which shifted the discriminants ofHttpClient(9 → 10),Clock(10 → 11),IdGen(11 → 12) andDefer(12 → 13). Code casting aPortwithas isizegets a different number.PushError::Transientchanged shape, andSignerError/Kidno longer have well-defined discriminants because they now carry data.- New variants on exhaustive enums:
BlobError::TooLarge,HttpError::{ResponseTooLarge, DeadlineExceeded, BlockedDestination},SignatureError::{WrongScope, RevokedKey}. Kidno longer derivesCopy, andHmacSigneris no longerUnwindSafeorRefUnwindSafe.
Added
The public surface went from 227 items to 362. The larger pieces:
Outbox— persist-before-defer delivery, so queued work survives the request that created it, with asubjectcolumn that keeps a queued row inside export and erasure. (#128, #266)- The personal-data catalogue —
PersonalDataSet,PersonalDataCatalog,Disposition,CatalogEntry: every module declares what it holds about a person, and a table erasure cannot reach is published as its own fact rather than as "not personal data". (#244, #272, #274, #291) - Tenancy —
Tenant,TenantId,TenantConnand tenant resolution. (#32) - Sidecar boundaries — events cross inbound only, identity and contract per response rather than at cold start, and a sidecar's tables are visible to the collision check. (#61, #62, #66)
- Boot-time reconciliation, per module and per tenant. (#30)
RealtimeandBlobports, andModule::well_knownrouting.
0.2.0
Added
Module::well_known(): a module can serve routes at/.well-knownat the root (OIDCopenid-configuration,jwks.json), where discovery agents look for them.Harness::buildfails naming every module when two both provide one; nothing but/.well-knownis ever mounted at the root. (#46)cratefield_core::http::Form: anapplication/x-www-form-urlencodedextractor re-exported besideJson, with the same 64 KiB body limit and the same problem+json rejections (400 validation-failed,413 request-too-large) — for cross-siteform_postcallbacks such as Sign in with Apple. axum'sformfeature is now enabled in the workspace dependency. (#46)- Conformance kit (
cratefield-testing): a module with a well-known router is checked to mount at the root under/.well-knownand never under/v1. (#46)
One of 76 crates in the harness, all of them MIT and all of them in one repository.