Skip to main content

RunRecord

Struct RunRecord 

Source
pub struct RunRecord {
    pub id: RunId,
    pub task_id: TaskId,
    pub status: RunStatus,
    pub step_entries: Vec<StepEntry>,
    pub degradations: Vec<DegradationEntry>,
    pub operator_sid: Option<String>,
    pub current: BTreeMap<String, Assignee>,
    pub next_generation: u64,
    pub result_ref: Option<Value>,
    pub input_json: Option<String>,
    pub created_at: u64,
    pub updated_at: u64,
}
Expand description

One persisted Run row — one kick of a crate::store::task::TaskRecord.

Fields§

§id: RunId

Run identifier.

§task_id: TaskId

The Task this Run was kicked from.

§status: RunStatus

Current lifecycle status.

§step_entries: Vec<StepEntry>

Trace of dispatched steps, in append order.

§degradations: Vec<DegradationEntry>

Worker-reported degradations, in append order (GH #32). Independent channel from Self::step_entries/Self::result_ref — see DegradationEntry’s doc for the invariant. [] (the default) = no degradations reported — every pre-#32 Run is unaffected.

§operator_sid: Option<String>

Operator session id bound to this Run, if any (WS operator correlation).

This is the launch-time snapshot of who was pinned when the Run was kicked — not the live holder. The live holder is Self::current; nothing resolves a dispatch destination from this field, so the two are not competing destinations (A10).

§current: BTreeMap<String, Assignee>

The Run’s live holders, keyed by slot — the model’s Run.current (§4.3).

A slot is a Blueprint-declared Operator seat (operator_ref = Blueprint.operators[].name): a Blueprint may declare several, and each agent picks the one it dispatches through. The cardinality is therefore Run 1 : N Operator and Operator 1 : 1 Assignee (at a time), hence Run 1 : N Assignee — with A1 reading “at most one holder per slot”, which is exactly what a map expresses. An absent key is that slot’s Vacant; an empty map is a Run with no slot held at all.

R2 — a Vacant slot does not stop the Run; only a dispatch that needs that slot’s holder is affected, and dispatches through other slots are untouched. R6 — this travels with the Run row, so a restart does not drop the assignments.

Only RunStore::acquire_assignee / RunStore::vacate_assignee write it, both scoped to one slot, and both mint a fresh Assignee rather than mutating a stored one (Q3).

BTreeMap rather than HashMap: the map is serialized into a persisted column and into observation payloads, and key-sorted output keeps those bytes stable across processes. Additive with #[serde(default)] so rows serialized before the assignment axis existed decode unchanged (as an empty map = every slot Vacant).

§An empty map is written out, not skipped

This field used to carry skip_serializing_if = "BTreeMap::is_empty", so a Run holding nothing had no current key on the wire at all. That made “nobody holds anything on this Run” and “this response does not report holders” the same bytes, and §4.3 asks for the opposite (居なければ居ないと分かる — when nobody is there, it must be possible to tell that nobody is there). "current": {} says it. The per-seat form of the same answer, which also names the seats nobody holds, is [crate::handover::run_assignees].

§next_generation: u64

The Run’s generation counter — the model’s G (A4).

0 at launch. Every assignment event (Assign or Vacant) increments it by one before stamping, so the first Assign yields gen == 1; the counter therefore holds the generation of the most recent event, and the next event will use this value + 1. The bump is unconditional — re-acquiring for the SAME op still increments, because the counter counts events, not state changes.

One counter per Run, shared by every slot. An Assign to slot b advances the same G that a preceding Assign to slot a advanced, so any two holders — of the same slot or of different ones — can be ordered by gen. Per-slot counters would buy nothing and would make that comparison meaningless.

A2 (current = Assigned(a) ⟹ a.gen ≤ G) holds on two legs, not one. On the write path it holds by construction: every current value’s gen is stamped from this counter at the moment it is bumped, so an acquire can never leave a holder above it. On the way in it is checked — RunStore::create takes a caller-supplied record with both fields public, so a record that arrives already violating A2 is refused with RunStoreError::AssigneeGenerationAhead rather than stored (see RunRecord::validate_assignment_generations). Left unchecked, that record would stay violated: the next acquire stamps generation 1, below the seeded incumbent, and ordering two holders by gen — the whole reason G is Run-wide — would silently invert.

Additive with #[serde(default)] (pre-existing rows read back 0).

§result_ref: Option<Value>

The Run’s terminal result payload, set once by RunStore::set_result. None while the Run is in flight.

§input_json: Option<String>

Opaque JSON snapshot of the launch input this Run was kicked with (blueprint / init_ctx / operator injection / ttl / …). The server serializes its own launch-input struct into this string at Run creation time so an Interrupted Run can be resumed under the SAME run_id without re-deriving the input from a since-stale request body. The store treats it as an opaque blob — the schema is owned by the caller (the server crate). None = no snapshot recorded (older rows predating resume support, or a caller that never opts in); such a Run cannot be resumed. Additive with #[serde(default)] so pre-existing serialized rows deserialize unchanged.

§created_at: u64

Unix epoch seconds — creation time.

§updated_at: u64

Unix epoch seconds — last update time.

Implementations§

Source§

impl RunRecord

Source

pub fn validate_assignment_generations(&self) -> Result<(), RunStoreError>

A2 as a check: every holder in Self::current must have been stamped at or below Self::next_generation (current = Assigned(a) ⟹ a.gen ≤ G).

RunStore::create calls this on the record it is handed, and every RunStore implementation is expected to — an out-of-tree backend that skips it accepts records the two in-tree backends refuse. Nothing else needs it: acquire_assignee stamps gen from the counter it has just bumped, so no write path in this crate can produce a record this rejects.

Reports the first offending seat in Self::current’s key order, which is stable (BTreeMap), so the same bad record always names the same seat.

Trait Implementations§

Source§

impl Clone for RunRecord

Source§

fn clone(&self) -> RunRecord

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 RunRecord

Source§

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

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

impl<'de> Deserialize<'de> for RunRecord

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 JsonSchema for RunRecord

Source§

fn schema_name() -> Cow<'static, str>

The name of the generated JSON Schema. Read more
Source§

fn schema_id() -> Cow<'static, str>

Returns a string that uniquely identifies the schema produced by this type. Read more
Source§

fn json_schema(generator: &mut SchemaGenerator) -> Schema

Generates a JSON Schema for this type. Read more
Source§

fn inline_schema() -> bool

Whether JSON Schemas generated for this type should be included directly in parent schemas, rather than being re-used where possible using the $ref keyword. Read more
Source§

impl Serialize for RunRecord

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

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