DocsOperate
#Upgrading cratefield-core
A checklist of every source-breaking change in cratefield-core since 0.5.0, one section per minor, so a venture can move its cratefield-core requirement forward and know what the compiler is going to say about it. Behaviour changes are noted only where they change what a test expects.
Two rules make most of this list shorter than it looks, and both are worth adopting before you need them:
- Prefer a constructor or a builder to a struct literal. Where core marks a struct
#[non_exhaustive]—PromptandCompletionsince 0.6,Turnsince 0.8 — the compiler forces this, and a literal stops compiling every time the struct grows a field. - Prefer
..Default::default()or a wildcard arm to an exhaustivematch. Every enum in this list that lacks#[non_exhaustive]is a match that will need editing again at the next minor.
This document covers source breaks only. For everything else — features, fixes, adapter changes — read crates/core/CHANGELOG.md, which is the complete record. For the version ranges and the contract-version rule, see COMPATIBILITY.md.
A venture jumping more than one minor works through the sections in order: 0.5 → 0.6 → 0.7 → 0.8. Several changes commonly attributed to 0.7 landed in 0.6.
#Upgrading to 0.8 (from 0.7)
- [ ]
ports::text_model::Turnis#[non_exhaustive]and no longer derivesEq. A struct literalTurn { role, content }no longer compiles outside core, and code that assertedTurn: Eq(aHashSetof turns, anassert_eq!in a#[derive(Eq)]test struct) no longer compiles either. Build turns withTurn::user,Turn::assistant,Turn::assistant_tool_callsorTurn::tool_resultsand compare them withassert_eq!on the individual fields rather than on a derivedEqbound. - [ ]
ports::text_model::TextModelErroris#[non_exhaustive]. Amatchover it outside core needs a wildcard arm. Map the wildcard to whatever a transport-shaped failure would mean for the caller — a 502, a retry, a degraded answer. - [ ]
Promptgainedtoolsandtool_choice. Not a source break on its own (Promptwas already#[non_exhaustive]and built withPrompt::new(..)plus builders), but a prompt that answers structured tool calls now carriesCompletion::tool_calls. A handler that only readsCompletion::textkeeps working; one that round-trips a conversation must carry the tool turns back. - [ ] New
Paymentsmethods, all defaulted —verify_webhook_request,create_portal_session,get_dispute,list_disputes,close_dispute(0.8.0), thenget_subscriptionandlist_subscriptions(0.8.1). Each has a default implementation, so an existing adapter compiles unchanged; all butverify_webhook_requestanswerPaymentsError::Unsupported, and it reads thestripe-signatureheader and delegates toverify_webhook. An adapter that implements any of them now has to handlePaymentsError::Unsupportedfrom its ownmatchon the enum (see the 0.7 note on that variant). - [ ]
DisputeStatusandSubscriptionStatusare new#[non_exhaustive]enums andDispute,DisputePhase,DisputeListRequest,DisputePage,Subscriptionare new types. Nothing to fix unless you read them.
#Pending, unreleased
Port::Actor, Ports::actors, Module::actor_kinds and Module::max_blob_object_bytes are merged but past the last published release, so they carry into the next core minor (issue #583). Port and Ports are exhaustive: expect one more variant and one more field. No action yet.
#Upgrading to 0.7 (from 0.6)
- [ ]
Problem::type_uritakes the venture's base.type_uri(&self) -> Stringbecametype_uri(&self, base: &str) -> String. Take the base fromVenture::problem_type_base()— the venture's explicitVenture::problem_baseoverride, else<public_url>/problems/, elseabout:blank— rather than writing a domain into an assertion:
let venture = Venture::new("acme", "acme.test").public_url("https://api.example.test");
let problem = Problem::not_found();
assert_eq!(
problem.type_uri(&venture.problem_type_base()),
"https://api.example.test/problems/not-found",
);Any assertion that read https://factory0.ventures/problems/<slug> is now wrong twice over: the base is the serving venture's, and matching the full URI at all is the mistake. Match on the slug — the part after /problems/, an auth/… namespace included — never on the URI. The slug list itself moved in this minor too (see allowance_exhausted below); ERRORS.md is the current list.
- [ ]
Problem::into_responserenders the context-freeabout:blanktype. A test that asserted a URI out of a bareinto_responsenow seesabout:blank; the harness's outermost layer re-renders the body under the venture's base. Where a runtime serves a problem from outside that layer — a native421, a Worker413, an unresolved-limiter503— use the newProblem::into_response_with_base(base). - [ ]
PortgainedVectorIndex,EmbedderandCustomHostnames.Portis exhaustive, so amatchover it stops compiling. Add the three arms, or a wildcard. - [ ]
Portsgainedvector_index,embedderandcustom_hostnames. All three areOption, andPortsis not#[non_exhaustive], so a struct literal needs all three fields. APortsbuilt by the runtime resolves them itself; only a hand-built test context needs the fields. - [ ]
tracker::DestinationgainedFreshdesk { domain }andColonizer { repo }.Destinationis exhaustive: amatchover it needs the two new arms.RoutingTrackergrewfreshdeskandcolonizerto wire them. - [ ]
surface::Actiongainedverification— theOption<SignatureVerification>a single route uses to override the module-level default. Not serialized, so the surface document's wire shape is unchanged; set it with the.verification(v)builder, or add the field to a struct literal (Noneis the default). - [ ]
WriteGuardsgainedrate_limiter_modulesandsignature_routes. Both arepubfields on a struct core builds from a module's surface (WriteGuards::collect), so a hand-builtWriteGuardsliteral needs them.signature_routesisVec<(String, String, SignatureVerification)>— module, mounted route, verifier — and adds a per-route view of which verifier a module used. - [ ]
problems::Slugsgainedallowance_exhausted.Slugsis a struct ofpubfields with a constSLUGS; a module that builds its own slug table from a literal needs the field. - [ ]
Venturegainedproblem_base: Option<String>.Ventureis not#[non_exhaustive]and every field ispub, so a venture that builds one from a literal needsproblem_base: None.Venture::newfollowed by the builders fills it. - [ ]
PaymentsErrorgainedUnsupported(&'static str). The enum is exhaustive, so amatchover it needs the new arm. It is what every defaultedPaymentsmethod returns, so an adapter that forwards a default result has to handle it rather than let it reach a handler that only knowsNotConfigured. - [ ] New defaulted trait methods, nothing to implement:
Tracker::comment(refuses withTrackerError::Rejectednaming the tracker by default),Payments::report_usage(answersPaymentsError::Unsupported),Module::streaming_routes, and the newStatusWebhooktrait served by thereceive_statusfunction. Existing adapters compile unchanged.
#Upgrading to 0.6 (from 0.5)
- [ ]
rate_limiter::Decisiongainedquota: Option<Quota>. The struct is not#[non_exhaustive]and has no constructor, so everyDecisionliteral — in a fake limiter, a test double, an adapter — needsquota: None.Nonemeans "this limiter cannot know its budget", which is true of the Cloudflare Rate Limiting binding and of a sliding window with no count.Quotaitself (limit,remaining,reset) is new and public. - [ ]
rate_limitedtakes the decision, not the pause. The exported helper went fromrate_limited(Option<Duration>)torate_limited(&Decision), so it can put theRateLimit-*headers on the response. A caller that had aDurationin hand now passes theDecisionit came from. - [ ]
ModuleContextgainedscheduled: Arc<ScheduledBudget>. Every hand-built context needs the field. Outside a scheduled invocation the budget is unbounded, which is exactly what the core itself builds:
ModuleContext {
// …the fields you already had…
scheduled: Arc::new(ScheduledBudget::unbounded()),
}Prefer Harness::module_context(module, &ports) over a literal where you can — it fills the field, and the fields it fills are the ones that cannot be guessed.
- [ ]
PortgainedAuth,TextModel,TrackerandClassifier. Exhaustive: amatchoverPortneeds four more arms. - [ ]
Portsgainedauth,tracker,text_modelandclassifier. AllOption; a literal needs all four. - [ ]
Actiongainedoutput: Option<Schema>— the JSON Schema of the body aJson-outcome action answers with.Surface::validatenow refuses an output schema that is not an object, or an output on an action whose outcome is notJson, so a surface that previously validated may not. The builders.output::<T>()and.output_schema(schema)are the way to set it. - [ ]
AudiencegainedSubject— "a credential is needed, and the route decides which rows the caller sees". Exhaustive: amatchoverAudienceneeds the arm, and it is exempt from the "under/admin/" rule inSurface::validatealongsidePublicandLink. - [ ]
RoutePolicygainedApiKey— a route gated by a developer API key rather than a captcha, a signature or a signed link. Exhaustive: amatchoverRoutePolicyneeds the arm. - [ ]
WriteGuardsgainedpayments_signature_modules,hmac_signature_modules,has_public_writesandhas_admin_routes. ASignature-guarded write no longer has one list of modules; it has the modules that verify through thePaymentsport and the modules that verify through a corewebhook_signaturescheme keyed by a module-scoped config secret. The two booleans answer whether the composition exposes any public write at all and whether it has an admin plane. - [ ]
problems::Slugsgainedunauthenticated,api_key_unauthorizedandapi_key_forbidden, andSlugsitself became an exported type. A module matching on 401s now sees two slugs where it saw none, and a 403 (api_key_forbidden) beside them. - [ ] New defaulted
Modulemethods:signature_verification(which verifier proves aSignatureroute) andmax_body_bytes. Neither needs implementing. - [ ] The
TextModelport is new in this minor (ModelTier,Prompt,Turn,Completion,TextModelError,RoutingTextModel). Nothing to migrate, but a module that called a vendor SDK directly now has a port to declare inrequires()/optional()instead.
Mirrored from docs/UPGRADING.md at b6f50cd. The harness is the source of truth: a correction goes there first.