#[non_exhaustive]pub struct Session {Show 26 fields
pub session_id: String,
pub state: SessionState,
pub ttl_expiry: i64,
pub ttl_ms: i64,
pub started_at_unix_ms: i64,
pub resolution: Option<Vec<u8>>,
pub mode: String,
pub mode_state: Vec<u8>,
pub participants: Vec<String>,
pub seen_message_ids: HashSet<String>,
pub intent: String,
pub mode_version: String,
pub configuration_version: String,
pub policy_version: String,
pub context_id: String,
pub extensions: HashMap<String, Vec<u8>>,
pub roots: Vec<Root>,
pub initiator_sender: String,
pub participant_message_counts: HashMap<String, u32>,
pub participant_last_seen: HashMap<String, i64>,
pub policy_definition: Option<PolicyDefinition>,
pub suspended_at_ms: Option<i64>,
pub accumulated_suspended_ms: i64,
pub suspension_intervals: Vec<(i64, i64)>,
pub semantics_rev: u32,
pub max_suspend_ms: i64,
}Expand description
Session model. Fields are public for reads, but the struct is
#[non_exhaustive]: construct via Session::builder. This lets the model
gain fields without breaking every constructor in downstream crates
(pre-1.0 freeze requirement).
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.session_id: String§state: SessionState§ttl_expiry: i64§ttl_ms: i64§started_at_unix_ms: i64§resolution: Option<Vec<u8>>§mode: String§mode_state: Vec<u8>§participants: Vec<String>§seen_message_ids: HashSet<String>§intent: String§mode_version: String§configuration_version: String§policy_version: String§context_id: String§extensions: HashMap<String, Vec<u8>>§roots: Vec<Root>§initiator_sender: String§participant_message_counts: HashMap<String, u32>§participant_last_seen: HashMap<String, i64>§policy_definition: Option<PolicyDefinition>§suspended_at_ms: Option<i64>Wall-clock (session-timeline) ms at which the session was suspended, or
None when not suspended. Used to bank TTL across a suspension (§7.5).
accumulated_suspended_ms: i64Cumulative ms the session has spent suspended across all suspend/resume
cycles. Drives the MAX_SUSPEND_MS cap.
suspension_intervals: Vec<(i64, i64)>Completed (suspended_at, resumed_at) pairs on the session timeline
(ms), in the order they completed. An in-progress suspension is
deliberately not here — the pair is pushed by Session::resume,
so the vec always describes finished pauses only.
Recorded at every semantics_rev (so a session that later matters
has the history), but read only at rev >= 2, by
Session::unsuspended_deadline (RFC-MACP-0010 §5.1). Legacy
snapshots and checkpoints deserialize this as empty; see
unsuspended_deadline’s under-count invariant for why that is safe.
Bounded at every revision by MAX_SUSPENSION_CYCLES — rev >= 2
force-expires the session over the cap, rev <= 1 stops recording.
semantics_rev: u32Session-semantics revision this session was accepted under. See
CURRENT_SEMANTICS_REV. Legacy persisted sessions load as 0.
max_suspend_ms: i64Maximum-suspension cap bound at SessionStart (RFC-MACP-0001 §7.5,
RFC-MACP-0003 §2). 0 = unbound (legacy sessions and library
defaults) — the MAX_SUSPEND_MS default applies; see
Session::effective_max_suspend_ms. The kernel records the
resolved value here for new sessions.
Implementations§
Source§impl Session
impl Session
Sourcepub fn builder(
session_id: impl Into<String>,
mode: impl Into<String>,
initiator_sender: impl Into<String>,
) -> SessionBuilder
pub fn builder( session_id: impl Into<String>, mode: impl Into<String>, initiator_sender: impl Into<String>, ) -> SessionBuilder
Start building a Session. The three arguments are the fields with no
meaningful default; everything else starts from documented defaults
(see SessionBuilder) and is set with the builder’s methods.
pub fn record_participant_activity(&mut self, sender: &str, timestamp_ms: i64)
Sourcepub fn suspend(&mut self, now_ms: i64) -> Result<(), MacpError>
pub fn suspend(&mut self, now_ms: i64) -> Result<(), MacpError>
Suspend an Open session (RFC-MACP-0001 §7.5). Records the suspend time
so TTL can be banked on resume. Pure: no clock, no I/O — the caller
injects now_ms.
Sourcepub fn effective_max_suspend_ms(&self) -> i64
pub fn effective_max_suspend_ms(&self) -> i64
The suspension cap governing this session: the value bound at
SessionStart, or the MAX_SUSPEND_MS default when unbound (0).
Sourcepub fn resume(&mut self, now_ms: i64) -> Result<(), MacpError>
pub fn resume(&mut self, now_ms: i64) -> Result<(), MacpError>
Resume a Suspended session, banking the suspended duration into the
TTL deadline (ttl_expiry += now - suspended_at) and recording the
completed pause in Session::suspension_intervals.
Force-expires the session (state Expired, Err(TtlExpired)) when
either suspension cap is exceeded: cumulative duration past
MAX_SUSPEND_MS (every revision), or completed cycle count past
MAX_SUSPENSION_CYCLES (semantics_rev >= 2 only — at rev <= 1 the
cap instead stops the recording, see MAX_SUSPENSION_CYCLES). The
pair is pushed before either check, so history is recorded even on the
expiring call.
Pure: no clock, no I/O — the caller injects now_ms.
Sourcepub fn cancel(&mut self) -> Result<(), MacpError>
pub fn cancel(&mut self) -> Result<(), MacpError>
Cancel an Open or Suspended session into the terminal Cancelled
state (RFC-MACP-0001 §7.3). Returns an error if already terminal.
Sourcepub fn suspend_cap_exceeded(&self, now_ms: i64) -> bool
pub fn suspend_cap_exceeded(&self, now_ms: i64) -> bool
Whether a currently-Suspended session has exceeded MAX_SUSPEND_MS as
of now_ms (cumulative banked plus the in-progress suspension).
Sourcepub fn unsuspended_deadline(&self, from_ms: i64, duration_ms: i64) -> i64
pub fn unsuspended_deadline(&self, from_ms: i64, duration_ms: i64) -> i64
The session-timeline instant at which duration_ms of unsuspended
time has elapsed since from_ms — i.e. the earliest T with
(T - from_ms) - suspended_in[from_ms, T] >= duration_ms.
This is RFC-MACP-0010 §5.1(3)’s own formula for the synthetic implicit
accept’s timestamp (“offer acceptance time + timeout + suspended time
within the window”), evaluated on the recorded timeline required by
§5.1(1). It is deliberately not the naive
from_ms + duration_ms + banked_since(from_ms): that counts pauses
that begin after the true deadline, so it is wrong whenever a
suspend/resume pair lands between the deadline and the observation —
fully reachable, since SuspendSession/ResumeSession are RPCs that
need no session-scoped message. It would also make the timestamp
depend on when it was computed, which is unacceptable for a value
baked into permanent history.
The walk: start at from_ms with the full duration_ms remaining;
for each completed pause (s, e) starting at or after from_ms, if
the unsuspended run up to s already covers what remains, stop inside
that run; otherwise consume it and jump to e. A pause starting
exactly at the returned deadline does not extend it — the offer’s
unsuspended time had already hit the timeout at that instant.
Pure and saturating: no clock read, no I/O, no panics on overflow.
Under-count invariant. The walk’s contribution satisfies
walk_sum <= accumulated_suspended_ms - offer.suspended_ms_at_offer:
Session::suspension_intervals may under-report completed pauses
but can never over-report them. Three distinct sources produce a short
or empty vec, each permanent for the life of that log:
- A snapshot or checkpoint written before the field existed
deserializes it as empty (
#[serde(default)]) whileaccumulated_suspended_msis already positive. - Any replay — including a post-11b one — that resumes from a
pre-11b mid-session checkpoint: the fast path replays only
&log_entries[idx + 1..], so every pause that completed before that checkpoint is gone and can never be recovered, no matter how many times the log is replayed afterwards. - A
semantics_rev <= 1session that exceededMAX_SUSPENSION_CYCLES: recording stops rather than force-expiring, so pairs past the cap are dropped. This one is outside this function’s read domain by construction — the walk is only consulted atsemantics_rev >= 2, where the cap force-expires instead of dropping — so the enumeration above is exhaustive for every vec this function can actually be asked to walk.
An under-count only moves the returned deadline earlier, never later
— the safe direction, so callers may rely on deadline <= now_ms once
the scalar arithmetic has already decided the timeout elapsed.
Why a short vec cannot break replay determinism. This describes the
design this function exists to serve; the synthetic entry itself
arrives in a later phase. Replay is never to recompute a synthetic
implicit-accept timestamp — it replays the recorded synthetic entry as
data. Only the live emitter computes a deadline, exactly once, at
emission time, from the offer’s recorded offered_at_ms. So a short
vec can make a
newly emitted implicit accept land earlier than a fully-recorded one
would have, but it can never make a replayed one disagree with the
live value already baked into history — byte-identical replay is not
foreclosed.
pub fn apply_mode_response(&mut self, response: ModeResponse)
Trait Implementations§
Source§impl From<&Session> for PersistedSession
impl From<&Session> for PersistedSession
Source§fn from(session: &Session) -> PersistedSession
fn from(session: &Session) -> PersistedSession
Source§impl From<PersistedSession> for Session
impl From<PersistedSession> for Session
Source§fn from(session: PersistedSession) -> Session
fn from(session: PersistedSession) -> Session
Auto Trait Implementations§
impl Freeze for Session
impl RefUnwindSafe for Session
impl Send for Session
impl Sync for Session
impl Unpin for Session
impl UnsafeUnpin for Session
impl UnwindSafe for Session
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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