Skip to main content

OperatorSessionRecord

Struct OperatorSessionRecord 

Source
pub struct OperatorSessionRecord {
    pub sid: SessionId,
    pub token_digest: String,
    pub capability_manifest: Option<AgentProviderManifest>,
    pub joined_at_secs: u64,
    pub last_access_secs: u64,
    pub desc: Option<String>,
    pub observed: Vec<ObservedAssignment>,
    pub observed_total: u64,
}
Expand description

One persisted Operator login-flow session.

Field-for-field the durable subset of the server’s LoginSession — everything except the process-lifetime WS adapter state, which is rebuilt empty on reconnect.

§The bearer token is never stored

token_digest holds hex(SHA-256(bearer)) — the same fingerprint shape the /v1/sessions path already keys its store by, for the same reason (“the sid handed to the client is the token nonce itself (a bearer secret), so the server never uses it as a map key”; see mse_server::SessionStore). Every consumer of this record only ever compares a presented bearer (verify_bearer), so nothing downstream needs the plaintext — it exists only inside POST /v1/operators, between minting and the mint response.

Fields§

§sid: SessionId

Server-minted session id (S-<hex>).

§token_digest: String

hex(SHA-256(bearer)) of the auth token required on the WS upgrade and admin routes. Derive with Self::digest_of; compare with Self::verify_bearer. The plaintext bearer is deliberately absent (see the type doc).

§capability_manifest: Option<AgentProviderManifest>

Provider-owned effective capability manifest submitted at join.

§joined_at_secs: u64

Unix epoch seconds when POST /v1/operators minted this session.

§last_access_secs: u64

Unix epoch seconds when this session was last accessed — model §4.1’s 最終アクセス, the clock the 24h expiry (OPERATOR_SESSION_MAX_IDLE_SECS) runs from.

§Access, not activity

Self::last_activity_secs answers “when was this session last assigned something”, which is what D5 sorts the 記名 list by. This answers “when did the driver behind this session last show itself”, which is a wider set of events: attaching a WebSocket, reading its own session, being assigned a seat. A driver can be very much alive and hold no seat for a day, so expiring on activity would reap live sessions.

§Why this one is stored and its sibling is derived

last_activity_secs is a maximum over the observed ring, so it cannot go stale — every value it reads from is already persisted. An access leaves no such trace: nothing about a WS connect or a GET /v1/operators/:sid is written down anywhere else, so if this were derived there would be nothing to derive it from. It is advanced by Self::touch and written through by the server.

Additive with #[serde(default)]. A row persisted before this field existed decodes as 0, which would read as “accessed at the epoch” and expire it on sight — so every reader goes through Self::last_access_secs, which folds 0 back onto the join time.

§desc: Option<String>

The confirmed part of this session’s 記名 (model §4.2, D1): roughly 50 characters the joining AI wrote about what it is working on, fixed at join and never rewritten afterwards.

It is what the observed part cannot supply. Two drivers in the same worktree produce the same project_root / work_dir and can hold Runs of the same Blueprint; the sentence one of them wrote at join exists only in that conversation, which is what makes it an identifier (§4.2: 観測部分だけでは足りない).

None = the session joined without one. Kept as an absence rather than an empty string so a reader can tell “nothing was written” from “something was written and it was blank” — POST /v1/operators does not reject a missing desc (unlike A9 on the assignment side, D1-D5 name no 400), so the absence is a real and readable state.

D4: nothing matches on this. It is read by humans and AIs to tell sessions apart, never by the server to decide identity.

Additive with #[serde(default)] — rows persisted before the 記名 existed decode as None.

§observed: Vec<ObservedAssignment>

The observed part of this session’s 記名 (model §4.2, D2): one entry per seat this session was assigned, appended by the server at each Assign and never removed by any API.

Oldest first. Bounded by OBSERVED_CAP and de-duplicated per (run_id, slot) — see Self::record_observed.

Additive with #[serde(default)].

§observed_total: u64

How many Assigns have been recorded onto Self::observed over this session’s life, including the ones the ring has since dropped and the re-assignments folded into an existing entry.

Monotone. observed_total > observed.len() is the visible signal that the reader is looking at a window rather than the whole history.

Additive with #[serde(default)].

Implementations§

Source§

impl OperatorSessionRecord

Source

pub fn record_observed(&mut self, entry: ObservedAssignment)

Append one Assign to the observed part (D2).

Two shaping rules, both about keeping the log readable rather than about deleting anything:

  • One entry per (run_id, slot). Re-acquiring a seat this session already holds is the same fact with a newer timestamp, so the existing entry is replaced and moved to the newest position instead of accumulating a row per acquire. A driver that re-acquires after every reconnect would otherwise fill the whole window with one Run.
  • Newest OBSERVED_CAP kept. Past the cap the oldest entry is dropped.

Self::observed_total counts every call regardless, so a reader can tell that folding or dropping happened.

Source

pub fn last_activity_secs(&self) -> u64

When this session was last seen doing something — the newest ObservedAssignment::at_secs, or Self::joined_at_secs for a session that has never been assigned anything.

D5’s default ordering key. Derived rather than stored: a separate column would be a second thing to keep in step with the log, and the ring only ever drops entries older than the newest one, so the derivation cannot go stale.

Source

pub fn last_access_secs(&self) -> u64

When this session was last accessed, for the 24h expiry clock.

Reads Self::last_access_secs, with two foldings that make the value safe to compare against a horizon:

  • a 0 (a row persisted before the field existed, or a session never touched since it was minted) reads as the join time, so a fresh session is never a day old on arrival;
  • an assignment counts as an access even if nothing touched the field, so Self::last_activity_secs is folded in as well. A session being handed seats is being used, whatever else it does.
Source

pub fn touch(&mut self, now: u64) -> bool

Advance Self::last_access_secs to now, never backwards.

Monotone because the clock is not: a SystemTime that steps back (NTP correction, a suspended laptop) must not make a session look older than the last time something saw it. Returns whether the value moved, so a caller can skip a durable write that would change nothing.

Source

pub fn is_expired_at(&self, now: u64) -> bool

The 24h horizon: has this session gone OPERATOR_SESSION_MAX_IDLE_SECS without being accessed, as of now?

A pure predicate over the record. What it cannot see is whether a socket is attached right now, which is why the server’s expiry checks pair it with a connectivity read — a driver holding an idle WebSocket open is present, and reaping it would be the reaper causing the outage it exists to prevent. See mse_server::operator_ws::login’s expiry note.

Source

pub fn digest_of(bearer: &str) -> String

Digest a plaintext bearer into the at-rest shape (Self::token_digest).

Callers mint a bearer with operator_bearer_token and keep the plaintext only long enough to answer the mint request.

Source

pub fn verify_bearer(&self, bearer: &str) -> bool

Constant-time check of a presented bearer against Self::token_digest.

The comparison runs over the two digests (fixed-width hex), so it carries no timing signal about the bearer itself.

Trait Implementations§

Source§

impl Clone for OperatorSessionRecord

Source§

fn clone(&self) -> OperatorSessionRecord

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for OperatorSessionRecord

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for OperatorSessionRecord

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for OperatorSessionRecord

Source§

fn eq(&self, other: &OperatorSessionRecord) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for OperatorSessionRecord

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for OperatorSessionRecord

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> MaybeSend for T

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Serialize for T
where T: Serialize + ?Sized,

Source§

fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>

Source§

fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more