pub struct HandoffMode { /* private fields */ }Implementations§
Source§impl HandoffMode
impl HandoffMode
Sourcepub fn new(evaluator: Arc<dyn PolicyEvaluator>) -> Self
pub fn new(evaluator: Arc<dyn PolicyEvaluator>) -> Self
Construct the mode with an injected governance policy evaluator.
Trait Implementations§
Source§impl Mode for HandoffMode
impl Mode for HandoffMode
Source§fn validate_client_envelope(
&self,
session: &Session,
env: &Envelope,
) -> Result<(), MacpError>
fn validate_client_envelope( &self, session: &Session, env: &Envelope, ) -> Result<(), MacpError>
RFC-MACP-0010 §5.1(3): a client-submitted HandoffAccept carrying
implicit = true MUST be rejected, and the synthetic accept’s
message_id namespace is reserved.
Gated to semantics_rev >= 2 so rev <= 1 wire behavior is
byte-identical: a legacy session’s client could send an
implicit-accept:-prefixed id and be accepted, and its history must
stay replayable under the semantics it was accepted with.
Two rules, in this order:
- any
message_idin the reserved namespace ->InvalidEnvelope(checked first, and for every message type: the squat works throughSessionStart,CommitmentandHandoffContexttoo); HandoffAcceptwhose payload decodes withimplicit = true->InvalidPayload— the same codehandle_messagereturns for the same envelope today, so the rev-2 error surface does not shift.
Rule 2 is what the mode itself will stop being able to enforce once
the runtime synthesizes implicit accepts: at rev >= 2 a well-formed
implicit accept with the deterministic message_id is exactly what
dispatch must accept on replay, and the mode cannot tell client
provenance from runtime provenance. This boundary can.
Source§fn due_synthetic_envelope(
&self,
session: &Session,
now_ms: i64,
) -> Option<Envelope>
fn due_synthetic_envelope( &self, session: &Session, now_ms: i64, ) -> Option<Envelope>
RFC-MACP-0010 §5.1(2)-(3): the synthetic implicit HandoffAccept, due
once the outstanding offer’s implicit_accept_timeout_ms has elapsed in
unsuspended session time.
Gated to semantics_rev >= 2. Rev <= 1 sessions get None forever and
keep resolving through the interim in-Commitment path, so their
histories replay to the outcome they were accepted with.
§Decision vs. timestamp — two different functions, on purpose
The decision (“has the timeout elapsed?”) is the scalar
implicit_accept_elapsed_ms, which is exact for this purpose:
elapsed(T) >= timeout iff T >= D, because every pause banked since
the offer lies inside [offered_at, T].
Only the timestamp needs the interval walk
Session::unsuspended_deadline. §5.1(3) fixes it at “offer acceptance
time + timeout + suspended time within the window”, which is not
the naive offered_at + timeout + banked_since_offer: that form counts
pauses beginning after the deadline — fully reachable, since
SuspendSession/ResumeSession are RPCs and need no session-scoped
message — and it would make a permanently-recorded timestamp depend on
when it happened to be observed.
The two are consistent in the safe direction: an under-recorded
suspension_intervals vec (see unsuspended_deadline’s under-count
invariant) can only move D earlier, never later, so the
debug_assert!(D <= now_ms) below holds even on a legacy rev-2
snapshot.
§Cost
One mode_state decode per call for handoff sessions with a bound
timeout, and an allocation only when an envelope is actually due. A
cached “next deadline” flag on the session was considered and rejected
as premature: it would need a writer on the resume path, which is not
mode-dispatched at all.
fn on_session_start( &self, session: &Session, _env: &Envelope, ) -> Result<ModeResponse, MacpError>
fn on_message( &self, session: &Session, env: &Envelope, ) -> Result<ModeResponse, MacpError>
Source§fn on_message_at(
&self,
session: &Session,
env: &Envelope,
ctx: &MessageContext,
) -> Result<ModeResponse, MacpError>
fn on_message_at( &self, session: &Session, env: &Envelope, ctx: &MessageContext, ) -> Result<ModeResponse, MacpError>
on_message plus the runtime’s
macp_core::mode::MessageContext (acceptance clock). Defaulted to plain
on_message so most modes ignore it; modes that need a trustworthy
time source (Handoff) override this instead of reading the forgeable
Envelope.timestamp_unix_ms. The runtime and replay always call this,
with the same clock value that the log entry records.