Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-0010: API changes deliberately deferred to 1.0

Status

Accepted (2026-08-24). Nothing here changes before the 1.0 release; all of it changes at 1.0 unless re-argued.

Context

An audit of the public surface against the Rust API Guidelines found two deviations whose fix is a breaking change. Pre-1.0 the family is at 0.x, where breaking is allowed but still costs every early adopter a migration. Neither deviation is dangerous enough to charge that cost now; both are wrong enough that 1.0 — the last cheap moment — should pay it. Recording the decision is the point: an unrecorded “later” is how a wart becomes permanent.

Decisions

1. PartyParseError / NumericParseError become ParsePartyError / ParseNumericError (C-WORD-ORDER). The std convention is verb-object-error (ParseIntError); ours inverts it, and it is the exact example the guideline names. At 1.0 the types are renamed with deprecated type aliases kept for one minor release.

2. Party loses its unvalidated From<&str> / From<String> impls. Today the most idiomatic spelling ("x".into()) routes to the unchecked wire-path constructor while "x".parse() validates — the two spellings a reader expects to be equivalent differ in exactly the property that matters. The impls also block adding a validating TryFrom<&str>: the std blanket impl<T, U: From<T>> TryFrom<T> for U makes TryFrom and From mutually exclusive for the same pair of types, so the checked conversion cannot even be offered while the unchecked one holds the slot. At 1.0 the From impls go, TryFrom<&str> arrives with PartyParseError, and Party::new remains the documented unchecked door for wire values.

3. TlsConfig::client_identity_pem becomes a private field behind a typed accessor. Today it is a pub field of type Option<(Vec<u8>, Vec<u8>)> — the mutual-TLS certificate and private key as an untyped pair, with “which element is the key” an undocumented invariant. The #[non_exhaustive] attribute and the with_client_identity builder already steer callers to the safe path, and Debug redacts the key, so nothing leaks today; but a pub field carrying key material, tuple-ordered, is a shape 1.0 should not freeze. At 1.0 the field goes private and the identity is read through a small typed accessor. Deferred rather than done now for the same reason as the others: it is a breaking change, and 0.x users should migrate once.

4. Update reads return the SDK’s own types; the prost ones become the escape hatch. ADR-0002 promised that raw proto types would leave the default public path, and for update reads they had not: updates_resumable yields pb::get_updates_response::Update and the JSON client returns raw JSON, so code written against one transport did not read the other, and a re-vendored proto reached callers who matched on it without a version signal (nodejumper-org/canton-rust-sdk#11). The types now exist and are additive: canton_ledger::update (Update, Transaction, Event with created, archived and exercised events, Reassignment, TopologyTransaction, OffsetCheckpoint), built from either transport, with payloads decoded by the generated type and the wire form still reachable. updates_typed_with and updates_resumable_typed_with (gRPC) and updates_typed_with (JSON) return them, and Update::from_grpc, from_grpc_point and from_json convert anything else. At 1.0 the typed reads take the plain names (updates, updates_with, updates_resumable, the point reads) and the proto- and JSON-returning ones move to *_raw, so the default path is transport-neutral and the wire types are something a caller opts into. Deferred for the reason the others are: renaming now would break every 0.x reader of updates, and they should migrate once.

Consequences

  • These changes ride the 1.0 release train, listed in its migration notes.
  • Until then the current shapes stay: churning them at 0.x would break users twice for the same lesson.