pub struct OperatorSessionRecord {
pub sid: SessionId,
pub token_digest: String,
pub capability_manifest: Option<AgentProviderManifest>,
pub joined_at_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: SessionIdServer-minted session id (S-<hex>).
token_digest: Stringhex(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: u64Unix epoch seconds when POST /v1/operators minted this session.
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: u64How 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
impl OperatorSessionRecord
Sourcepub fn record_observed(&mut self, entry: ObservedAssignment)
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_CAPkept. 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.
Sourcepub fn last_activity_secs(&self) -> u64
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.
Sourcepub fn digest_of(bearer: &str) -> String
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.
Sourcepub fn verify_bearer(&self, bearer: &str) -> bool
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
impl Clone for OperatorSessionRecord
Source§fn clone(&self) -> OperatorSessionRecord
fn clone(&self) -> OperatorSessionRecord
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for OperatorSessionRecord
impl Debug for OperatorSessionRecord
Source§impl<'de> Deserialize<'de> for OperatorSessionRecord
impl<'de> Deserialize<'de> for OperatorSessionRecord
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for OperatorSessionRecord
impl PartialEq for OperatorSessionRecord
Source§impl Serialize for OperatorSessionRecord
impl Serialize for OperatorSessionRecord
impl StructuralPartialEq for OperatorSessionRecord
Auto Trait Implementations§
impl Freeze for OperatorSessionRecord
impl RefUnwindSafe for OperatorSessionRecord
impl Send for OperatorSessionRecord
impl Sync for OperatorSessionRecord
impl Unpin for OperatorSessionRecord
impl UnsafeUnpin for OperatorSessionRecord
impl UnwindSafe for OperatorSessionRecord
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> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
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> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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