pub struct ConversationHandle<S: ResumeStore> { /* private fields */ }Expand description
One enrolled participant in one conversation: the transport-agnostic entry to the conversation patterns (design D4 — nothing in this type or its methods names a transport; the embedded transport arrives later behind this same handle).
A handle is single-owner and its pattern calls take &mut self; run one
handle per participant, one participant per thread. The handle is Send
so it may be moved into a thread after construction.
Implementations§
Source§impl<S: ResumeStore> ConversationHandle<S>
impl<S: ResumeStore> ConversationHandle<S>
Sourcepub fn open(
attachment: &BusAttachment,
store: S,
) -> Result<(Self, JoinGrant), AttachError>
pub fn open( attachment: &BusAttachment, store: S, ) -> Result<(Self, JoinGrant), AttachError>
Opens a NEW conversation: mints its identity and enrolls this participant as its first member. Creation is implicit at first enrollment (F-0c assertion 1 — the substrate offers no explicit create and no create-versus-join testimony; the minting policy is design §2.4’s, carried until a server-issued binding authority exists).
§Errors
Returns typed endpoint, refusal, transport, or answer-window failures.
Sourcepub fn join(
attachment: &BusAttachment,
conversation: ConversationId,
store: S,
) -> Result<(Self, JoinGrant), AttachError>
pub fn join( attachment: &BusAttachment, conversation: ConversationId, store: S, ) -> Result<(Self, JoinGrant), AttachError>
Joins an existing conversation (or implicitly creates it — the
substrate cannot distinguish; see open). Joining is
membership-forward: history before the join is neither delivered nor
ackable (the F-0c late-joiner finding).
§Errors
Returns typed endpoint, refusal, transport, or answer-window failures.
Sourcepub fn resume(
attachment: &BusAttachment,
grant: &JoinGrant,
canonical_state: &[u8],
store: S,
) -> Result<(Self, JoinGrant), AttachError>
pub fn resume( attachment: &BusAttachment, grant: &JoinGrant, canonical_state: &[u8], store: S, ) -> Result<(Self, JoinGrant), AttachError>
Resumes a previously joined participant: restores the aggregate from the caller’s persisted canonical state, then re-attaches with the grant’s credential. Resume is restore-then-reattach, never restore alone; the credential ROTATES and the returned grant carries the new one — persist it (F-0c §R4, proven at published 0.3.0).
On success the substrate replays the unacked obligation window from
the committed cursor — including records admitted while this
participant was dead — which arrives through the normal typed
surfaces (next_event and friends).
§Errors
Returns typed endpoint, resume-state, refusal, transport, or answer-window failures.
Source§impl<S: ResumeStore> ConversationHandle<S>
impl<S: ResumeStore> ConversationHandle<S>
Sourcepub fn publish_event<E: Serialize>(
&mut self,
body: &E,
) -> Result<PublishReceipt, PublishError>
pub fn publish_event<E: Serialize>( &mut self, body: &E, ) -> Result<PublishReceipt, PublishError>
Publishes a typed subscription event into the conversation. Schema-invalid values are refused typed BEFORE the wire (F-3a R1). The receipt is the correlated commit answer — delivery evidence for the record’s admission, NOT evidence any subscriber consumed it (finding G1’s discipline; the stream carries news, never obligations).
§Errors
Returns typed publish failures.
Sourcepub fn next_event<E: DeserializeOwned>(
&mut self,
wait: Duration,
) -> Result<Option<SubscriptionItem<E>>, CallError>
pub fn next_event<E: DeserializeOwned>( &mut self, wait: Duration, ) -> Result<Option<SubscriptionItem<E>>, CallError>
Waits up to wait for the next subscription item. Ok(None) is a
benign quiet wait — never an error, never a protocol outcome (the
assertion-8 pin at this crate’s surface). Items carry the delivery
guarantee documented on SubscriptionItem: ordered, contiguous,
sender-excluded within a live session; membership-forward at join;
typed gap surface with resume as the documented resync path.
The effective wait granularity is the substrate’s IO quantum (hardcoded 5 s at published liminal 0.3.0 — upstream ASK-2): a wait smaller than one quantum may still block for one quantum.
§Errors
Returns a typed connection fate.
Sourcepub fn commit_cursor(
&mut self,
through: ConversationSeq,
) -> Result<CursorCommit, PublishError>
pub fn commit_cursor( &mut self, through: ConversationSeq, ) -> Result<CursorCommit, PublishError>
Commits the caller-owned consumption cursor through through,
cumulatively. Acking is explicit and caller-owned — this crate never
acks on the caller’s behalf, because the committed cursor is the
caller’s lever on resume replay volume (F-0c §R4).
§Errors
Returns typed publish-path failures; the ack answers themselves
(committed / no-op / gap / regression) are the typed
CursorCommit outcome, not errors.
Source§impl<S: ResumeStore> ConversationHandle<S>
impl<S: ResumeStore> ConversationHandle<S>
Sourcepub fn leave(&mut self) -> Result<LeaveOutcome, LeaveError>
pub fn leave(&mut self) -> Result<LeaveOutcome, LeaveError>
Leaves the conversation: terminal, explicit, idempotent (R1). The
commit retires this participant PERMANENTLY — a later resume of
the lineage is refused typed (ParticipantUnknown, probe finding
3), and every further operation on this handle fails typed.
Calling again on the same handle returns
LeaveOutcome::AlreadyLeft without wire traffic (finding 2).
§Errors
Returns typed refusal, transport, or answer-window failures; the already-left answers are outcomes, not errors.
Sourcepub fn leave_resumed(
attachment: &BusAttachment,
grant: &JoinGrant,
canonical_state: &[u8],
store: S,
) -> Result<LeaveOutcome, LeaveError>
pub fn leave_resumed( attachment: &BusAttachment, grant: &JoinGrant, canonical_state: &[u8], store: S, ) -> Result<LeaveOutcome, LeaveError>
Completes (or performs) a leave from the caller’s persisted lineage
WITHOUT re-attaching: restore the aggregate from canonical_state,
then submit the lineage’s durable leave token over the unbound
connection. Total by findings 4 and 5: an already-left lineage
receives the original commit’s echo; a never-left lineage commits —
the presented credential material is the authority, and no attach
leg exists to be refused. This is the crash-window path: a caller
that issued leave and died before observing the commit re-drives it
here and gets the same typed outcome.
§Errors
Returns typed endpoint, resume-state, refusal, transport, or answer-window failures.
Source§impl<S: ResumeStore> ConversationHandle<S>
impl<S: ResumeStore> ConversationHandle<S>
Sourcepub fn publish<P: Serialize>(
&mut self,
id: PublicationId,
body: &P,
) -> Result<PublishReceipt, PublishError>
pub fn publish<P: Serialize>( &mut self, id: PublicationId, body: &P, ) -> Result<PublishReceipt, PublishError>
Publishes one typed publication under the caller-supplied identity. Schema-invalid values are refused typed BEFORE the wire. The receipt is the admission witness (A5): the substrate owes the publication once to every epoch live in its membership linearization at this receipt — publisher death after it does not retract the broadcast, and zero live epochs mean this validation plus this one admission outcome were the entire cost.
PUBLISHER OBLIGATION (the honest contract at the pinned substrate,
A4 superseded 2026-07-23): a PublicationId identifies ONE
publication — never republish an id, with the same or different
bytes. The substrate refuses neither on a live binding (admission
tokens are per-attempt identity there, not durable idempotency
keys), so a reused id admits a NEW record and every active epoch
observes it again under the same id. Stronger admission idempotency
is upstream ask ASK-6, non-gating.
§Errors
Returns typed publish failures.
Sourcepub fn next_publication<P: DeserializeOwned>(
&mut self,
wait: Duration,
) -> Result<Option<PublicationItem<P>>, CallError>
pub fn next_publication<P: DeserializeOwned>( &mut self, wait: Duration, ) -> Result<Option<PublicationItem<P>>, CallError>
Waits up to wait for the next pub/sub item. Ok(None) is a benign
quiet wait — never an error, never a protocol outcome (the
assertion-8 pin). Items carry the guarantee documented on
PublicationItem; duplicate presentation of an admitted
publication is suppressed by the O(1) last-presented-sequence
comparison, with every suppression counted
(duplicate_publications), never silent.
The effective wait granularity is the substrate’s IO quantum (hardcoded 5 s at the pinned release — upstream ASK-2): a wait smaller than one quantum may still block for one quantum.
§Errors
Returns a typed connection fate.
Source§impl<S: ResumeStore> ConversationHandle<S>
impl<S: ResumeStore> ConversationHandle<S>
Sourcepub fn request<Q: Serialize, R: DeserializeOwned>(
&mut self,
body: &Q,
deadline: Duration,
) -> Result<RequestOutcome<R>, CallError>
pub fn request<Q: Serialize, R: DeserializeOwned>( &mut self, body: &Q, deadline: Duration, ) -> Result<RequestOutcome<R>, CallError>
Sends a typed request and waits for exactly one outcome from the closed set: the correlated schema-validated reply, the caller’s deadline elapsing, or a responder failure (F-3a R2).
The deadline is caller-supplied per call — no default exists — and it
is the ONLY thing that ends the request: elapsed IO quanta while
waiting are benign re-arms and never change the outcome (constraint
3; the receive-cancel discipline). Correlation is by typed
correlation identity, never delivery order (constraint 4). A reply
arriving after the deadline surfaces as a typed late-reply anomaly on
drain_anomalies; a second reply to the
same correlation surfaces as a duplicate-reply anomaly; the first
reply’s delivery is unaffected (R6).
§Errors
Returns typed publish failures (schema-invalid refused before the
wire; substrate refusals; transport loss), a typed
schema-invalid-reply error, or a typed connection fate. A deadline
elapse is NOT an error — it is the RequestOutcome::DeadlineElapsed
outcome.
Sourcepub fn next_request<Q: DeserializeOwned>(
&mut self,
wait: Duration,
) -> Result<Option<InboundRequest<Q>>, CallError>
pub fn next_request<Q: DeserializeOwned>( &mut self, wait: Duration, ) -> Result<Option<InboundRequest<Q>>, CallError>
Waits up to wait for the next inbound request. Ok(None) is a
benign quiet wait — never an error, never a protocol outcome (the
assertion-8 pin at this crate’s surface). A schema-invalid request
surfaces typed as InboundRequest::SchemaInvalid, never dropped.
The effective wait granularity is the substrate’s IO quantum (hardcoded 5 s at published liminal 0.3.0 — upstream ASK-2): a wait smaller than one quantum may still block for one quantum.
§Errors
Returns a typed connection fate.
Sourcepub fn reply<R: Serialize>(
&mut self,
correlation: CorrelationId,
body: &R,
) -> Result<PublishReceipt, PublishError>
pub fn reply<R: Serialize>( &mut self, correlation: CorrelationId, body: &R, ) -> Result<PublishReceipt, PublishError>
Publishes the typed reply to a received request, correlated to the request’s exchange. Schema-invalid replies are refused typed BEFORE the wire (F-3a R1).
§Errors
Returns typed publish failures.
Source§impl<S: ResumeStore> ConversationHandle<S>
impl<S: ResumeStore> ConversationHandle<S>
Sourcepub const fn conversation(&self) -> ConversationId
pub const fn conversation(&self) -> ConversationId
The conversation this handle is enrolled in.
Sourcepub const fn participant(&self) -> ParticipantRef
pub const fn participant(&self) -> ParticipantRef
This participant’s substrate-minted identity.
Sourcepub const fn grant(&self) -> &JoinGrant
pub const fn grant(&self) -> &JoinGrant
The current join grant: identity, attach credential, and generation. The credential rotates on every resume; callers persisting the grant must persist the CURRENT one.
Sourcepub const fn attached(&self) -> bool
pub const fn attached(&self) -> bool
Whether the handle’s connection is still serviceable. Once false,
every operation fails typed; the recovery path is
resume from the caller’s persisted state.
Sourcepub fn drain_anomalies(&mut self) -> Vec<Anomaly>
pub fn drain_anomalies(&mut self) -> Vec<Anomaly>
Drains the queued typed anomalies (R2’s anomaly home, queue half). Counters are unaffected by draining.
Sourcepub const fn anomaly_counters(&self) -> AnomalyCounters
pub const fn anomaly_counters(&self) -> AnomalyCounters
The named, readable anomaly counters (R2’s anomaly home, counter half).
Trait Implementations§
Auto Trait Implementations§
impl<S> !Freeze for ConversationHandle<S>
impl<S> !RefUnwindSafe for ConversationHandle<S>
impl<S> !UnwindSafe for ConversationHandle<S>
impl<S> Send for ConversationHandle<S>
impl<S> Sync for ConversationHandle<S>
impl<S> Unpin for ConversationHandle<S>where
S: Unpin,
impl<S> UnsafeUnpin for ConversationHandle<S>where
S: UnsafeUnpin,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request