pub struct Runtime {
pub storage: Arc<dyn StorageBackend>,
pub registry: Arc<SessionRegistry>,
pub log_store: Arc<LogStore>,
/* private fields */
}Fields§
§storage: Arc<dyn StorageBackend>§registry: Arc<SessionRegistry>§log_store: Arc<LogStore>Implementations§
Source§impl Runtime
impl Runtime
pub fn new( storage: Arc<dyn StorageBackend>, registry: Arc<SessionRegistry>, log_store: Arc<LogStore>, ) -> Self
pub fn with_mode_registry( storage: Arc<dyn StorageBackend>, registry: Arc<SessionRegistry>, log_store: Arc<LogStore>, mode_registry: Arc<ModeRegistry>, ) -> Self
pub fn with_registries( storage: Arc<dyn StorageBackend>, registry: Arc<SessionRegistry>, log_store: Arc<LogStore>, mode_registry: Arc<ModeRegistry>, policy_registry: Arc<PolicyRegistry>, ) -> Self
Sourcepub fn registered_mode_names(&self) -> Vec<String>
pub fn registered_mode_names(&self) -> Vec<String>
Returns all mode names the runtime can handle (standards-track + extensions). Used by Initialize and GetManifest to advertise full capability.
Sourcepub fn standard_mode_descriptors(&self) -> Vec<ModeDescriptor>
pub fn standard_mode_descriptors(&self) -> Vec<ModeDescriptor>
Returns only standards-track mode descriptors for ListModes.
Sourcepub fn extension_mode_descriptors(&self) -> Vec<ModeDescriptor>
pub fn extension_mode_descriptors(&self) -> Vec<ModeDescriptor>
Returns only extension mode descriptors for ListExtModes.
pub fn register_extension( &self, descriptor: ModeDescriptor, ) -> Result<(), String>
pub fn unregister_extension(&self, mode: &str) -> Result<(), String>
pub fn promote_mode( &self, mode: &str, new_name: Option<&str>, ) -> Result<String, String>
pub fn subscribe_mode_changes(&self) -> Receiver<()>
pub fn mode_registry(&self) -> &Arc<ModeRegistry> ⓘ
pub fn register_policy( &self, definition: PolicyDefinition, ) -> Result<(), String>
pub fn unregister_policy(&self, policy_id: &str) -> Result<(), String>
pub fn get_policy(&self, policy_id: &str) -> Option<PolicyDefinition>
pub fn list_policies(&self, mode_filter: Option<&str>) -> Vec<PolicyDefinition>
pub fn subscribe_policy_changes(&self) -> Receiver<()>
pub fn policy_registry(&self) -> &Arc<PolicyRegistry> ⓘ
pub fn metrics(&self) -> &Arc<RuntimeMetrics> ⓘ
pub fn subscribe_session_stream(&self, session_id: &str) -> Receiver<Envelope>
pub fn subscribe_signals(&self) -> Receiver<Envelope>
pub fn subscribe_session_lifecycle(&self) -> Receiver<SessionLifecycleEvent>
Sourcepub async fn get_session_envelopes_after(
&self,
session_id: &str,
after_sequence: u64,
) -> Result<Vec<Envelope>, u64>
pub async fn get_session_envelopes_after( &self, session_id: &str, after_sequence: u64, ) -> Result<Vec<Envelope>, u64>
RFC-MACP-0006 §3.2: Replay accepted envelopes from the session log for
passive subscribe, strictly after after_sequence (1-based accepted
ordinal, exclusive; 0 = from the start). Err(base) when the
requested range was discarded by log compaction — the caller must
surface an explicit error, not silently skip missing history.
pub async fn process( &self, env: &Envelope, max_open_sessions: Option<usize>, ) -> Result<ProcessResult, MacpError>
pub async fn get_session_checked(&self, session_id: &str) -> Option<Session>
Sourcepub async fn cancel_session(
&self,
session_id: &str,
reason: &str,
cancelled_by: &str,
) -> Result<ProcessResult, MacpError>
pub async fn cancel_session( &self, session_id: &str, reason: &str, cancelled_by: &str, ) -> Result<ProcessResult, MacpError>
Cancel a session. The cancelled_by parameter MUST be the authenticated
sender of the CancelSession RPC (RFC-MACP-0001 Section 7.3: CancelSession
is a Core control-plane message; mode authorization does not apply).
Sourcepub async fn suspend_session(
&self,
session_id: &str,
reason: &str,
suspended_by: &str,
) -> Result<ProcessResult, MacpError>
pub async fn suspend_session( &self, session_id: &str, reason: &str, suspended_by: &str, ) -> Result<ProcessResult, MacpError>
Suspend an Open session (RFC-MACP-0001 §7.5). Appends a SessionSuspend
annotation, transitions Open -> Suspended, and emits a lifecycle event.
The session’s TTL is banked and restored on resume.
Sourcepub async fn resume_session(
&self,
session_id: &str,
reason: &str,
resumed_by: &str,
) -> Result<ProcessResult, MacpError>
pub async fn resume_session( &self, session_id: &str, reason: &str, resumed_by: &str, ) -> Result<ProcessResult, MacpError>
Resume a Suspended session (RFC-MACP-0001 §7.5), banking the suspended
duration into the TTL deadline. If the MAX_SUSPEND_MS cap is exceeded,
the session is force-expired instead.
Sourcepub async fn cleanup_expired_sessions(&self)
pub async fn cleanup_expired_sessions(&self)
Expire all sessions that have exceeded their TTL. Called by the background cleanup task to proactively transition stale sessions without waiting for the next incoming message.
Sourcepub async fn sweep_due_synthetic_accepts(&self) -> usize
pub async fn sweep_due_synthetic_accepts(&self) -> usize
Emit every synthetic envelope that has become due, across all open sessions — RFC-MACP-0010 §5.1(2)’s eager observation of the implicit-accept deadline.
Lazy observation (process_message -> synthesize_due_accept) is the
MUST and ships on its own: it guarantees no message is ever evaluated
against a stale offer. This is the SHOULD on top of it — without it a
session where nobody speaks again keeps an accepted offer out of history
indefinitely, and GetSession / StreamSession show an offer that the
protocol says was accepted at D. Called from the background
maintenance loop in src/main.rs, so MACP_CLEANUP_INTERVAL_SECS is
the latency bound on the observation (never on the recorded timestamp,
which is D whichever path emits — see below).
§Not a variant of cleanup_expired_sessions
Different predicate (a mode-computed deadline inside mode_state, not
ttl_expiry) and a different product: an EntryKind::Incoming entry
that consumes an accepted ordinal and is published to StreamSession,
where TtlExpired is Internal and is published to neither. It is
deliberately ordered after cleanup_expired_sessions in that loop so
a TTL-expired session is already non-Open when the sweep reaches it —
which is exactly the precedence the lazy path gives (Precheck::Expired
returns before synthesize_due_accept is ever called), so the two paths
cannot disagree about a session whose TTL and implicit-accept deadline
both passed unobserved.
§Locking
The registry map lock is held only for the snapshot of (id, Arc) pairs
and is released before any session mutex is taken or any I/O happens —
the lock-ordering contract on SessionRegistry. The snapshot fixes the
set, not the state.
§Why no snapshot-to-append race can leave an orphan entry
A session can resolve, cancel or expire between the snapshot and the
moment this loop reaches it, and the Arc keeps it alive (and writable)
regardless. Nothing here reads state at snapshot time: every decision is
made under the session mutex, which is the same mutex every writer —
process_message, cancel_session, suspend_session, resume_session,
cleanup_expired_sessions — holds across its own validate-append-commit.
So the state != Open re-check inside synthesize_due_accept observes
the session as the last writer left it, and a session that terminated
after the snapshot is skipped. Two further guards make it belt and
braces: the mode returns None once the offer’s disposition is no
longer Offered (so an explicit HandoffAccept that won the race
disarms the synthesis), and the deterministic message_id is already in
seen_message_ids after any emission. Eviction cannot orphan one
either: evict_stale_sessions and gc_disk_sessions only ever drop
terminal sessions, which fail the Open check.
§Cost
One mode_state decode per open session whose mode implements the hook
and whose policy binds a timeout, per tick; everything else short-circuits
before decoding (Mode::due_synthetic_envelope’s own cost note).
Returns the number of synthetic entries appended.
Sourcepub async fn gc_disk_sessions(&self, retention_secs: u64) -> usize
pub async fn gc_disk_sessions(&self, retention_secs: u64) -> usize
Delete terminal sessions’ durable data older than retention_secs
(opt-in via MACP_SESSION_DISK_RETENTION_SECS). Before this existed,
storage.delete_session had no callers at all: disk grew without
bound and every restart reloaded every session ever completed.
Enumerates STORAGE (not memory — eviction may already have dropped the
registry entry), deletes the session’s snapshot+log, and clears any
in-memory remnants. Returns the number of sessions deleted.
Sourcepub async fn evict_stale_sessions(&self, retention_secs: u64)
pub async fn evict_stale_sessions(&self, retention_secs: u64)
Evict resolved/expired sessions older than retention_secs from
memory: the registry entry, the in-memory log cache, AND the stream
broadcast channel (all three previously grew for the process lifetime;
the log cache and stream bus were never evicted at all). Sessions
remain queryable from durable storage after eviction.
Auto Trait Implementations§
impl !RefUnwindSafe for Runtime
impl !UnwindSafe for Runtime
impl Freeze for Runtime
impl Send for Runtime
impl Sync for Runtime
impl Unpin for Runtime
impl UnsafeUnpin for Runtime
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> 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