pub struct Runtime { /* private fields */ }Expand description
The runtime.
Implementations§
Source§impl Runtime
impl Runtime
Sourcepub async fn attention(
&self,
at: Timestamp,
page: usize,
) -> Result<Attention, RuntimeError>
pub async fn attention( &self, at: Timestamp, page: usize, ) -> Result<Attention, RuntimeError>
Does anything on this plane need a person right now, and which thing.
What counts is not a judgement made here twice. It comes from the
run vocabulary: failed, exhausted, withheld and quarantined all
leave a run open, and three of them name a party who can answer — a
raised ceiling, a lifted withdrawal, a person deciding a doubt. failed
is the one a resume may clear alone, so it is listed only where the run
stands on landed work no compensation undid. Beside those sit
the backlogs that already have a listing and a verb that empties them.
The clock is the caller’s — the same decision the task listing and
the push sweep make — so a fixed instant makes this testable and a
deployment owns its cadence. page bounds every query, and a condition
says whether its page filled rather than pretending to a total.
§Errors
If a store this plane does hold is unreachable: a backlog that cannot be read is not an empty one.
Source§impl Runtime
impl Runtime
Sourcepub async fn run_batch(
&self,
id: BatchId,
spec: &BatchSpec,
) -> Result<BatchReport, RuntimeError>
pub async fn run_batch( &self, id: BatchId, spec: &BatchSpec, ) -> Result<BatchReport, RuntimeError>
Start a batch, or continue one.
Passing an existing BatchId resumes it: processing picks up after the
stored cursor, and any item that was reserved but never finished is
replayed under its original run id rather than started again.
Returns when the source is exhausted or max_items is reached. A
failing item does not stop the batch — that is the failure isolation a
batch exists for, and a caller who wants the opposite should be running
one run, not many.
What does stop it is an admission that never happened: a halt, a tenant ceiling, a store outage. Those hold for every item behind the one they hit, and they are returned as the error rather than written over the item, so the item is still pending when the next pass runs.
Sourcepub async fn batch_report(
&self,
id: BatchId,
) -> Result<BatchReport, RuntimeError>
pub async fn batch_report( &self, id: BatchId, ) -> Result<BatchReport, RuntimeError>
The batch’s current tally, without processing anything.
Source§impl Runtime
impl Runtime
Sourcepub fn builder_on<B: FullBackend + 'static>(store: Arc<B>) -> RuntimeBuilder
pub fn builder_on<B: FullBackend + 'static>(store: Arc<B>) -> RuntimeBuilder
A builder with the whole case layer wired to one backend.
The one-call form of the seven-cast litany: journal, cases, tasks,
events, timers, memory and quotas all backed by store, which is what a
deployment on a single RedbStore or PostgresStore means anyway. The
à-la-carte methods remain — cases and
friends override an individual store afterwards, and
builder still starts from the journal alone for a
plane that wants nothing else.
Blob storage is deliberately not included: bytes routinely live in a
different system than rows, so blobs stays
an explicit decision.
Sourcepub fn builder_with(stores: Stores) -> RuntimeBuilder
pub fn builder_with(stores: Stores) -> RuntimeBuilder
A builder with the whole case layer wired from handles.
What builder_on is in terms of: the seven casts live
in one place, so a deployment that chose its backend at run time — an
operator naming either a file or a connection string — wires exactly what
a deployment on one concrete type wires, rather than a hand-written
approximation of it that drifts the day an eighth store is added.
pub fn builder(store: Arc<dyn JournalStore>) -> RuntimeBuilder
Sourcepub fn events(&self) -> Option<&Arc<dyn EventStore>>
pub fn events(&self) -> Option<&Arc<dyn EventStore>>
The inbound-event store, if this runtime has one.
Sourcepub const fn budget(&self) -> &Budget
pub const fn budget(&self) -> &Budget
The ceilings every run under this runtime starts with.
Readable because “what is this plane allowed to spend” is a question an operator asks of a running system, and answering it by re-reading the config that should have been applied is how a misapplied budget stays invisible.
Sourcepub async fn drill(&self, at: Timestamp) -> Result<DrillReport, RuntimeError>
pub async fn drill(&self, at: Timestamp) -> Result<DrillReport, RuntimeError>
Run the live half of the case-layer drill with this plane’s own stores.
The wiring is the point of this method existing: the drill’s value is
holding each case’s references against the stores the plane actually
runs with, and an embedder assembling drill::Stores by hand could
hand it a different blob store than the one their runs write to —
a drill that passes over the wrong bucket. Stores this runtime does
not have are reported as unchecked by the drill itself, which is a
different and better answer than an error: a plane with no blob store
has no bytes to lose.
The clock is the caller’s, the same decision
attention and the push sweep make. A rehearsal’s
instant is a fact about the outside world rather than a journaled
observation, and taking it here would be a fourth ambient-clock escape
inside the deterministic zone for a value the caller already has. A
fixed instant also makes the recorded verdict testable.
§Errors
If this runtime has no case store — the drill walks cases, so there is nothing to drill — or if the case layer cannot be enumerated.
Sourcepub async fn last_drill(&self) -> Result<Option<DrillRecord>, RuntimeError>
pub async fn last_drill(&self) -> Result<Option<DrillRecord>, RuntimeError>
The last recovery rehearsal this plane ran, or None if it never has.
The half that makes drill evidence rather than a command: an audit
asks when did you last rehearse and did it pass, and that answer has
to come from the plane rather than from the thing that scheduled it.
§Errors
RuntimeError::PlanContract when this plane has no case store — the
same condition that makes a drill impossible in the first place — and
RuntimeError for a store failure.
Sourcepub async fn retain(
&self,
older_than: Timestamp,
at: Timestamp,
reason: &str,
) -> Result<RetentionReport, RuntimeError>
pub async fn retain( &self, older_than: Timestamp, at: Timestamp, reason: &str, ) -> Result<RetentionReport, RuntimeError>
Erase every closed case opened before older_than. The retention verb.
The wiring is the point, exactly as it is for drill:
erasure derives blob addresses and key scopes from the tenant and the
case, so a caller assembling the stores by hand could tombstone
addresses no run ever wrote and report a clean pass over nothing.
reason lands on every tombstone and on each key destruction, so a
later read says expired, on this date, for this reason rather than
missing — the distinction the drill’s three-way verdict turns on.
at is a parameter rather than a clock read, for the reason the
sweeper’s is: a pass that read the clock could not be tested against a
year of ageing cases.
Read RetentionReport::not_erasable
before concluding anything. Without a key ring this expires blob
tombstones in the live store and journal payloads stay verbatim,
which is the residual the erasure page names and the one an erasure
request actually turns on.
§Errors
If this runtime has no case store, or the case layer cannot be walked. A store that fails on one case is a report entry, not an error.
Sourcepub fn timers(&self) -> Option<&Arc<dyn TimerStore>>
pub fn timers(&self) -> Option<&Arc<dyn TimerStore>>
The timer store, if this runtime has one.
pub fn batches(&self) -> Option<&Arc<dyn BatchStore>>
pub fn push(&self) -> Option<&Arc<dyn PushStore>>
Sourcepub async fn rearm_push(
&self,
task: RunId,
id: &str,
) -> Result<bool, RuntimeError>
pub async fn rearm_push( &self, task: RunId, id: &str, ) -> Result<bool, RuntimeError>
Re-arm a webhook registration a delivery worker gave up on.
A parked registration is not delivered and not deleted: it keeps its cursor, so re-arming resumes at the first record the receiver never acknowledged rather than at the head of the run. That is the whole reason parking exists, and the reason this verb is on the plane — a backlog nobody can empty is a warning line in yesterday’s logs.
The clock is read here rather than taken from the caller, unlike the delivery worker’s: a worker’s schedule has to be assertable in a test, and an operator asking for now means now.
Returns whether a parked registration was found. false is an answer —
already live, or never existed — and is told rather than swallowed,
because an operator left waiting for a sweep with nothing to do has no
way to tell the difference.
§Errors
RuntimeError::PlanContract if this plane has no push store, and
RuntimeError::Store if it is unreachable.
Sourcepub async fn request_cancel(
&self,
run: RunId,
actor: &Operator,
reason: &str,
) -> Result<bool, RuntimeError>
pub async fn request_cancel( &self, run: RunId, actor: &Operator, reason: &str, ) -> Result<bool, RuntimeError>
Ask a run to stop, and drive the stop if nothing else will.
The request is durable before this returns, so an operator who gets an acknowledgement has one whether or not the run was reachable. What happens next depends on where the run is:
- Suspended — nothing is executing, so this resumes the run itself. It observes the request at its first step boundary and unwinds.
- Running here or elsewhere — the owner observes the request at its next step boundary. Nothing is interrupted mid-effect, deliberately: stopping between “announced” and “recorded” manufactures the in-doubt case the effect protocol exists to avoid.
- Quarantined — refused with
RuntimeError::CannotUnwind: the unwind a stop promises is what a quarantine forbids, and reopening or abandoning it are the verbs that apply. - Concluded and closed (succeeded, abandoned, cancelled) — refused
with
RuntimeError::AlreadyConcludedand nothing is recorded; a run somebody already stopped answersfalse. A failed or exhausted run is resumable, and stopping it unwinds its work. - Held by no provider on this plane — an operator’s terminal: the request is recorded and not driven, and the plane that holds the run’s agent observes it on its next resume.
Returns whether this call recorded the request. A second caller gets
false: the first asker stays on the record, because “who intervened”
must not be rewritten by a retry.
§Errors
RuntimeError::PlanContract for a blank reason,
RuntimeError::CannotUnwind for a quarantined run,
RuntimeError::AlreadyConcluded for a closed one, and
RuntimeError if the store is unreachable or resuming a suspended run
fails.
Sourcepub async fn decide_quarantine(
&self,
run: RunId,
decider: &Operator,
reason: &str,
decision: QuarantineDecision,
) -> Result<RunOutcome, RuntimeError>
pub async fn decide_quarantine( &self, run: RunId, decider: &Operator, reason: &str, decision: QuarantineDecision, ) -> Result<RunOutcome, RuntimeError>
Answer a quarantine, and let the runtime decide the run again.
A quarantine is this runtime saying it does not know, and the one conclusion it cannot reach past on its own. Everything else that stops a run has a party who can move it — a resume for a crash, a raised ceiling for an exhaustion, a reviewer for a suspension. This is the party for this one.
§What this does and does not decide
The person decides whether the run may be judged again, or that it
never will be. They do not decide the outcome. A
Reopen hands the run back
to the executor, which re-derives its verdict from a history that now
holds whatever the person established with
reconcile_effect — and quarantines it again,
on the record, if the answer was wrong or incomplete. An
Abandon closes it where it
stands, unwinding nothing.
The decision is journaled before it is acted on, so a crash between the two leaves the instruction standing rather than lost: the next resume finds it and finishes the job.
Returns the outcome of the pass this drove — so an operator who reopens a run learns immediately whether it got any further, rather than having to go and look.
§Errors
RuntimeError::NotQuarantined if the run’s last conclusion is
anything else, RuntimeError::LeaseHeld if somebody is executing it,
and RuntimeError for a store failure or a refused resume.
§Panics
Never: the expect covers a lease this function has just acquired.
Sourcepub async fn record_quarantine_decision(
&self,
run: RunId,
decider: &Operator,
reason: &str,
decision: QuarantineDecision,
) -> Result<(), RuntimeError>
pub async fn record_quarantine_decision( &self, run: RunId, decider: &Operator, reason: &str, decision: QuarantineDecision, ) -> Result<(), RuntimeError>
Answer a quarantine without driving the run.
decide_quarantine records the decision and
then hands the run to the executor, which is what an operator on a
running plane wants: they learn immediately whether a reopened run got
any further.
A terminal holding a store cannot do the second half. It has the journal and not the agent — a declarative run’s program is a manifest this process was not given — so driving the run would mean refusing the verb for want of an argument the operator may not have during an incident.
That is a supported state rather than a lesser one. The decision is journaled before it is acted on precisely so a crash between the two leaves the instruction standing, and this is the same position reached deliberately: the next resume finds the record and finishes the job — so what the caller must not do is report the run as moved.
§Errors
As decide_quarantine, minus everything the
resume could have raised.
Sourcepub async fn reconcile_effect(
&self,
run: RunId,
effect: EffectKey,
assertion: Assertion,
asserted_by: &Operator,
note: &str,
) -> Result<(), RuntimeError>
pub async fn reconcile_effect( &self, run: RunId, effect: EffectKey, assertion: Assertion, asserted_by: &Operator, note: &str, ) -> Result<(), RuntimeError>
Supply, out of band, the outcome of an effect the journal cannot decide.
The manual twin of Effect::reconcile,
and it produces the same record for the same reason: an operator who
looks the charge up in the provider’s console is answering exactly the
question a probe asks, so the answer belongs in the place a probe’s
answer goes, where replay reads it back and nothing re-derives it.
What separates the two is on the record too. A probe’s verdict is attributed to the run; this one names the person, because “the provider told us” and “somebody asserted it” are different evidence.
It supplies a missing fact and never replaces a recorded one. Only
an effect undecided_effects names
may be answered. Without that rule an operator could talk a run out of
compensating work that is standing in the world, and the journal would
show an orderly reconciliation while it happened.
This changes no status by itself: the run stays quarantined until
decide_quarantine hands it back. Several
doubts can be answered before the run is judged again, and answering one
of three is not a decision.
§Errors
RuntimeError::NotUndecided if that effect’s outcome is already on
the record, RuntimeError::LeaseHeld if somebody is executing the
run, and RuntimeError for a store failure.
Sourcepub async fn undecided(
&self,
run: RunId,
) -> Result<Vec<Undecided>, RuntimeError>
pub async fn undecided( &self, run: RunId, ) -> Result<Vec<Undecided>, RuntimeError>
Every mutating effect this run’s journal cannot decide the outcome of.
What an operator holding a quarantine has to go and look up, and the
keys they quote back to reconcile_effect.
§Errors
If the store is unreachable.
Sourcepub async fn set_halt(
&self,
scope: &HaltScope,
by: &Operator,
at: Timestamp,
reason: &str,
) -> Result<(), RuntimeError>
pub async fn set_halt( &self, scope: &HaltScope, by: &Operator, at: Timestamp, reason: &str, ) -> Result<(), RuntimeError>
Stop this tenant from starting new work, or let it start again.
The emergency stop. Some(reason) halts, None lifts, and the reason is
required because the next person to look will be somebody else, possibly
at three in the morning, and why is the whole question.
What it stops, precisely. New admissions, across every instance, because the flag is in the store rather than in this process — a switch that stops only the instance it was thrown on is the in-process-counter failure arriving during an incident. Refusals are their own error, not a ceiling: a ceiling means not right now and invites a retry, which is exactly what somebody pulling this switch is trying to stop.
What a workload-scoped halt does not stop, deliberately. Runs already executing, and suspended runs resuming. Those are existing work, and refusing to let them continue would strand them mid-saga with reversals unrun — turning an incident into a second one. To stop work in flight, cancel it: that unwinds what it did and records who asked. This is the front door, not a power cut, and saying so is the difference between a control an operator can reason about and one they discover the shape of during an outage.
How wide it reaches is the HaltScope,
and the scopes divide on which axis they name rather than on how
broad they are. Three name a workload — the tenant, every revision of
one declared agent, or one exact reviewed digest — and stop admission.
The fourth names an authority, any principal on the delegation
chain a run was admitted under, and is the one that reaches work
already running:
there the incident is the credential, so a run carrying on under it is
the harm rather than a side effect, and it pauses at its next step
boundary with its completed work standing
(withdraws_from is the
predicate, so the distinction is checked rather than remembered).
Scopes are independent rows — lifting a narrow halt leaves a broad one
standing. An ungoverned run, with no manifest, is stopped only by a
tenant halt; there is nothing narrower to key it on.
at is the caller’s, never a clock read here: every lifecycle instant in
this crate is supplied for the reason a hold’s is — a pass that reads its
own clock cannot be tested against an ageing plane, and I1 forbids the
runtime an ambient clock outside the effect protocol.
Requires a quota store; without one there is nowhere durable to keep the flag, and an emergency stop that a restart forgets is not one.
§Errors
If no quota store is wired, or the store is unreachable.
Sourcepub async fn lift_halt(
&self,
scope: &HaltScope,
by: &Operator,
at: Timestamp,
) -> Result<Option<ControlLifted>, RuntimeError>
pub async fn lift_halt( &self, scope: &HaltScope, by: &Operator, at: Timestamp, ) -> Result<Option<ControlLifted>, RuntimeError>
Lift one, and let work at that scope start again — recording who did.
Answers the run holding the lift record and whether this call removed
the row, or None when nothing was standing at scope: an operator who
lifts a scope nobody halted is told so rather than told done, and
nothing is written.
The record comes first, then the row goes. The lift is written as
the sole HaltLifted record
of a sealed run of its own, carrying by, at and the whole stop it
ended; only then is the register’s row removed. The journal and the
register share no transaction, so a lift that cannot be recorded does
not happen, and a removal that fails after the record is written is an
error naming the record’s run — the halt still stands, the register is
the answer to what is in force, and lifting again writes a second
record. The record attests the instruction and the row as read, not
that the removal succeeded.
Only the row the record names goes. The removal is
QuotaStore::lift_halt_if
with that row, so a halt re-thrown between the read and the removal is
left standing and the call fails naming the record’s run. A row another
lift already removed answers removed: false.
QuotaStore::lift_halt is the
raw register verb; a direct call to it is unrecorded.
§Errors
If no quota store is wired, the store is unreachable, or the record
cannot be written; RuntimeError::ControlStands if the row cannot be
removed after the record was written, or was re-thrown in between.
Sourcepub async fn release_hold(
&self,
case: CaseId,
by: &Operator,
at: Timestamp,
) -> Result<Option<ControlLifted>, RuntimeError>
pub async fn release_hold( &self, case: CaseId, by: &Operator, at: Timestamp, ) -> Result<Option<ControlLifted>, RuntimeError>
Release the legal hold on case — recording who did.
The hold’s counterpart of lift_halt, in the same
order for the same reason: the
HoldReleased record is
written, stamped with case, before the row goes. It names the releaser
and the hold’s placing operator and instant, and not the hold’s reason,
so the erasure the release permits leaves its authorization readable.
None when the matter is not held, with nothing written; removed: false when another release removed the row first.
Only the hold the record names goes. The removal is
CaseStore::release_hold_if
with that hold, so a hold placed again between the read and the removal
is left standing and the call fails naming the record’s run.
CaseStore::release_hold is the
raw register verb; a direct call to it is unrecorded.
§Errors
StoreError::NotFound if the case
does not exist; RuntimeError::ControlStands if the row cannot be
removed after the record was written, or was placed again in between;
otherwise if no case store is
wired, the store is unreachable, or the record cannot be written.
Sourcepub async fn lifted_halts(
&self,
limit: usize,
) -> Result<Vec<Record>, RuntimeError>
pub async fn lifted_halts( &self, limit: usize, ) -> Result<Vec<Record>, RuntimeError>
Every recorded halt lift, newest first: the one record of each run.
§Errors
If the journal is unreachable, or a lift run does not hold a lift record.
Sourcepub async fn released_holds(
&self,
limit: usize,
) -> Result<Vec<Record>, RuntimeError>
pub async fn released_holds( &self, limit: usize, ) -> Result<Vec<Record>, RuntimeError>
Every recorded hold release, newest first: the one record of each run.
§Errors
If the journal is unreachable, or a release run does not hold a release record.
Sourcepub async fn reservations(
&self,
limit: usize,
) -> Result<Vec<Held>, RuntimeError>
pub async fn reservations( &self, limit: usize, ) -> Result<Vec<Held>, RuntimeError>
Every run holding spend against this tenant’s periods, and how much.
The runs a QuotaError::SpentOut
refusal counts as reserved. A run holds its reservation until it
concludes, so the ones that stopped waiting on a person — quarantined,
exhausted, withheld, failed — hold it until that person acts;
attention names those.
§Errors
If no quota store is wired, or it is unreachable.
Sourcepub async fn halts(&self) -> Result<Vec<Halt>, RuntimeError>
pub async fn halts(&self) -> Result<Vec<Halt>, RuntimeError>
Every standing emergency stop on this tenant, and why.
The operator’s question — what is stopped right now? — which a per-scope lookup cannot answer without already knowing what to ask about.
§Errors
If no quota store is wired, or the store is unreachable.
Sourcepub async fn running_runs(
&self,
limit: usize,
) -> Result<Vec<LiveRun>, RuntimeError>
pub async fn running_runs( &self, limit: usize, ) -> Result<Vec<LiveRun>, RuntimeError>
Which runs are executing right now, and under whose authority.
runs_by_outcome
answers from the journal’s index, which only concluded runs are in; the
live set is the tenant’s admission slots.
The join is the point, not the ids. An incident is rarely cancel this run but a revoked credential or a bad revision, so each entry carries the declared agent and the delegation subject it was admitted under.
A slot whose lease has lapsed is marked stranded rather than omitted:
the recovery sweep resumes those, and cancelling one undoes work that was
about to continue.
Bounded by limit, and reads a page of each run’s header records to
attribute it — an operator question rather than a hot path.
§Errors
If no quota store is wired, or either store is unreachable.
Sourcepub async fn waiting_runs(
&self,
limit: usize,
) -> Result<Vec<WaitingRun>, RuntimeError>
pub async fn waiting_runs( &self, limit: usize, ) -> Result<Vec<WaitingRun>, RuntimeError>
The runs that are waiting, and what each one waits for.
The third listing, beside concluded runs and running ones: a suspended run has no outcome and holds no admission slot, so neither of the others names it. Re-arming these is the recovery runbook’s last step, and until this existed that step named a verb with no argument an operator could obtain.
Soonest due first, bounded by limit. Straight through to the store’s
maintained index — no page of records is read, because what a run waits
for is on the suspension itself.
§Errors
If the store is unreachable.
Sourcepub async fn drain(&self, grace: Duration) -> DrainReport
pub async fn drain(&self, grace: Duration) -> DrainReport
Stop this instance taking on work, then wait for what it is already doing.
A process killed mid-step leaves an announced effect with no terminal
record, so its declared Recovery has to
decide whether it happened — the right answer to an accident, and an
expensive one to schedule on every deploy.
Admission closes first: every later start is
refused
RuntimeError::Draining, leaving
no lease, no slot and no journal. Then this waits up to grace for the
runs it put on a background task to reach a journaled resting point.
Closing first is what makes the answer worth having — a wait alone
reports about an instant — and it is also the only signal a long-lived
read gets, so an open A2A subscription ends rather than holding a
graceful shutdown for as long as the run it watches.
Run it beside a server’s own graceful shutdown, not after it —
tokio::join! of the two. Serialising deadlocks one on the other in
whichever order you pick: a run awaited inside a request handler is
finished by the server, and an open subscription is ended by this.
Three things it deliberately does not do. It does not wait for runs on
somebody else’s task; those belong to that caller’s own shutdown. It
does not gate resumes or an agent commissioning another one — both
continue work this plane already owns, and a commission is a step of a
run this is waiting for. And it does not hand back the leases of runs it
did not finish: a release says takeover is safe, and for a run still
inside perform the next owner would re-perform the call beside the one
in flight. Left to expire, the lease is the signal the recovery sweep
reads as an instance died holding this run — which is what a
drained-out run is.
Idempotent: draining an instance that is already draining waits again.
Sourcepub fn is_draining(&self) -> bool
pub fn is_draining(&self) -> bool
Whether this instance has stopped admitting runs.
For an embedder whose server keeps its listener open while it drains and
therefore needs something to answer a readiness probe with. The shipped
server needs none: it closes its listeners first, so a probe that
connects already fails — which is why this crate’s HTTP surface has no
/health route to flip. See crate::api on why it has none at all.
Sourcepub async fn cancellation(
&self,
run: RunId,
) -> Result<Option<Cancellation>, RuntimeError>
pub async fn cancellation( &self, run: RunId, ) -> Result<Option<Cancellation>, RuntimeError>
The cancellation recorded against a run, if one was.
Reads the journal rather than a flag: a cancellation is a record, so this answers the same thing after a restart and on another instance.
§Errors
If the store is unreachable.
Sourcepub fn policy(&self) -> Option<&Arc<dyn PolicyEngine>>
pub fn policy(&self) -> Option<&Arc<dyn PolicyEngine>>
The policy engine, if this runtime has one.
Exposed because a surface that faces strangers has to be able to ask whether one exists at all. Inside the process the caller is the embedder’s own code and an absent engine is a choice; on a socket it is a hole, and the HTTP surface refuses to start without one.
Sourcepub async fn record_break_glass(
&self,
actor: &Operator,
roles: &[String],
reason: &str,
) -> Result<RunId, RuntimeError>
pub async fn record_break_glass( &self, actor: &Operator, roles: &[String], reason: &str, ) -> Result<RunId, RuntimeError>
Record an operator deliberately crossing into this tenant, and return the run that holds the record.
Every other tenancy control keeps a cross-tenant read from being reached by accident. This is the designed exception, and the rule for it is that an exception without a record is indistinguishable from the breach it is meant to be. So the access is written into this tenant’s journal — the one whose data is about to be reached — in a sealed run of its own, exactly as a sweep writes its decisions. It therefore inherits the hash chain, the per-record signature and the Merkle inclusion, and the offline audit tool reports it without being taught that break-glass exists.
The record is written before any data is served, and a failure to write it is a failure to access. That direction is the whole control: the alternative — serve first, record best-effort — is a break-glass that works exactly as well when its own evidence is lost.
§Errors
If reason is blank, or if the record cannot be written. An
unexplained exception is the thing this record exists to prevent, so it
is refused rather than stored empty.
Sourcepub fn journal(&self) -> &Arc<dyn JournalStore> ⓘ
pub fn journal(&self) -> &Arc<dyn JournalStore> ⓘ
The journal, for reads the runtime does not mediate.
This is also the embedder’s resumable output stream:
JournalStore::read is a seq-cursored read over a run’s records, so
polling from the last sequence seen is a durable, reconnect-safe
progress feed that any instance can serve — the exact mechanism the
A2A streaming surface is built on. There is deliberately no curated
event type between an embedder and the records: a third vocabulary
beside the journal’s and the wire’s would be a second truth wearing
ergonomics, and the records are already the history the stream must
agree with.
Sourcepub async fn case_of(&self, run: RunId) -> Result<Option<CaseId>, RuntimeError>
pub async fn case_of(&self, run: RunId) -> Result<Option<CaseId>, RuntimeError>
Which case a run belongs to, or None if it belongs to none.
Read from the journal, not from a column beside it. The binding is stamped on the run’s own records at admission, so answering from there is answering from the plan of record — a case-store column saying the same thing would be a second copy of one fact, and the two could disagree about a run the case layer never saw.
It is the first question an operator surface asks, which is why it is a
method rather than a documented one-liner: every caller was otherwise
going to reach for the first record and read body.case off it, and a
caller who reached for the last one instead would still be right today
and wrong the moment a run is admitted before its case is known.
§Errors
If the journal cannot be read. An unknown run is Ok(None) rather than
an error: no such run and a run in no case are both honest answers
to this question, and neither is a fault.
pub fn store(&self) -> &Arc<dyn JournalStore> ⓘ
Sourcepub fn owner_id(&self) -> &str
pub fn owner_id(&self) -> &str
This process instance’s identity, as it appears in run leases.
Not the agent’s name: several instances of one agent are normal. See
RuntimeBuilder::owner.
Public because “which instance holds this run” is a question an operator asks of a stuck system, and the answer is otherwise only in a store row.
Sourcepub fn start(
&self,
target: impl Into<Target>,
input: Tainted<Value>,
) -> RunRequest<'_, Unkeyed>
pub fn start( &self, target: impl Into<Target>, input: Tainted<Value>, ) -> RunRequest<'_, Unkeyed>
Start a run: a capability a skill answers, or a plan.
The one door. What the run is bound to — a case, a caller’s chain, a
pinned declaration — goes in RunRequest::terms; whether it runs at
most once goes in RunRequest::once, which changes what the answer
is; and the terminal says whether to wait for it.
let outcome = rt.start("order.settle", input.clone()).run().await?;
let admission = rt
.start("order.settle", input)
.terms(RunTerms::default().correlated("order", &[]))
.once("billing:msg-7")
.run()
.await?;The input arrives labelled, and that is the whole of the decision.
Tainted::trusted is what an operator
writes for a literal, a constant or a configuration value they vouch for;
anything that came from outside — an inbound event, a queue message, a
counterparty’s payload — keeps the label it arrived with. A bare value
admitted as trusted would satisfy require_trusted fields with
attacker-chosen data and give the egress ceiling nothing to join with.
Sourcepub async fn recorded_outcome(
&self,
run: RunId,
) -> Result<Option<RunOutcome>, RuntimeError>
pub async fn recorded_outcome( &self, run: RunId, ) -> Result<Option<RunOutcome>, RuntimeError>
What a run has recorded, without resuming it.
None while the run is still working — it has records but has reached
neither a conclusion nor a suspension.
No lease is taken and nothing is appended. The original is frequently live when its redelivery lands, and a read that acquired a lease would fence a healthy run mid-step in order to report on it.
The last record decides, never any record: a run that suspended, was resumed and carried on has a suspension in its history and is not suspended now. Same rule as the A2A status projection, so the two surfaces cannot disagree about one run.
§Errors
If the journal cannot be read.
Sourcepub async fn replay(
&self,
run: RunId,
mode: Mode,
) -> Result<RunOutcome, RuntimeError>
pub async fn replay( &self, run: RunId, mode: Mode, ) -> Result<RunOutcome, RuntimeError>
Re-execute a recorded run from its journal.
Mode::Strictverifies determinism: every effect must match, and the run must not want any effect the journal lacks.Mode::Resumerecovers a crashed run: history is replayed, then execution continues live from wherever the record ends.
Either way no external effect is performed for anything already in the journal. That is the whole point — a resumed run does not re-issue the invoice it already issued.
Sourcepub async fn verify(&self, run: RunId) -> Result<Verdict, RuntimeError>
pub async fn verify(&self, run: RunId) -> Result<Verdict, RuntimeError>
Replay a recorded run strictly and say what it found.
replay with Mode::Strict, answered as a
Verdict rather than a status: verified when every
recorded effect was asked for again and the run reached the ending it
recorded, whatever that ending was; diverged at the first effect this
build asked for differently; cannot replay when the run cannot be
re-derived here, for a reason that is not the declaration. Both
revisions are named on every answer — the one the run was admitted
under and the one this plane holds by that name.
Writes nothing, takes no lease and dispatches nothing, as a strict replay never does. It is the question an author asks of an edited manifest: replayed under the edit, this run’s history either reaches nothing it changed or names the first effect it did.
§Errors
If the run does not exist or its journal does not verify, or on a store failure — never for a divergence or a reason the verdict names.
Source§impl Runtime
Inbound event delivery.
impl Runtime
Inbound event delivery.
Sourcepub async fn deliver(
&self,
event: &InboundEvent,
) -> Result<Delivery, RuntimeError>
pub async fn deliver( &self, event: &InboundEvent, ) -> Result<Delivery, RuntimeError>
Deliver an inbound event, resuming whichever run was waiting for it.
§Ordering
The event is stored before anyone looks for a waiter. That ordering is the whole reason this works: a message can arrive before its run reaches the wait, and one that is matched-then-discarded leaves that run waiting forever for something that already happened.
A Delivery::Buffered result is therefore normal and not an error —
it means “held until someone asks”. Only the sweep
(EventStore::sweep_unclaimed)
decides an event is genuinely unroutable, because that is a claim about
the future rather than about this instant.
§Errors
RuntimeError::ReservedEventKind for a kind in the agentplane.
namespace, which only this plane mints; RuntimeError for a store
failure or a refused resume.
Sourcepub async fn deliver_to(
&self,
run: RunId,
event: &InboundEvent,
) -> Result<Delivery, RuntimeError>
pub async fn deliver_to( &self, run: RunId, event: &InboundEvent, ) -> Result<Delivery, RuntimeError>
Deliver an inbound event to exactly run.
This is the task-addressed counterpart to Runtime::deliver. It is
used by protocols such as A2A where a follow-up carries a concrete task
id. Correlation alone is insufficient there: two tasks may wait on the
same business key, and resuming the oldest would violate the request.
The event store atomically inserts and claims the event for this run. A run that is not waiting leaves no buffered event behind for another run.
§Errors
As deliver, and RuntimeError::PlanContract when
run is not waiting for this input. Addressing a run by id is no way
around the reserved namespace: a run waiting on a human task is waiting
for exactly the kind this refuses.
Sourcepub async fn sweep_events(&self, grace: Duration) -> Result<usize, RuntimeError>
pub async fn sweep_events(&self, grace: Duration) -> Result<usize, RuntimeError>
Retire events that nobody claimed within grace.
A non-empty dead-letter list means a correlation key is wrong somewhere:
the message arrived, was held, and no run ever asked for it. That is the
failure which otherwise presents as a process silently never completing,
so it is worth alerting on rather than logging.
grace is a std::time::Duration for the reason
StepCtx::deadline’s warn_before
is: a negative grace window is meaningless, and the signed type could
express it — a cutoff moved forward of now, retiring events that had
not yet had their chance. It is also the Duration the caller has.
Source§impl Runtime
impl Runtime
Sourcepub async fn sweep(
&self,
now: Timestamp,
event_grace: Duration,
) -> Result<SweepReport, RuntimeError>
pub async fn sweep( &self, now: Timestamp, event_grace: Duration, ) -> Result<SweepReport, RuntimeError>
Run one sweep.
Idempotent: a transition already applied is not applied twice, so calling this on a timer — or twice by accident, or from two instances — is safe.
now is passed in rather than read so the caller controls the clock.
That keeps the sweeper testable at all, and lets a simulation drive it
through a year in milliseconds.
Sourcepub async fn census(&self, now: Timestamp) -> Result<Census, StoreError>
pub async fn census(&self, now: Timestamp) -> Result<Census, StoreError>
Read the gauges: how much is open right now.
Queried from the stores rather than accumulated, because a counter incremented on open and decremented on close drifts permanently the first time a process dies between the state change and the emission — and it drifts plausibly, which is worse than drifting obviously.
Stores that were never configured contribute zero rather than an error: a plane with no human tasks has no open tasks, and refusing to report the gauges it does have would be the silent failure this exists to prevent.
Sourcepub async fn fire_timers(
&self,
now: Timestamp,
) -> Result<WokenRuns, RuntimeError>
pub async fn fire_timers( &self, now: Timestamp, ) -> Result<WokenRuns, RuntimeError>
Wake the runs whose instant has arrived.
The shape mirrors event delivery exactly, and for the same reasons: claim atomically so two sweepers cannot wake one run twice, record the wake-up as the sleeping effect’s result, then let replay do the rest. The resumed run reads the timer back like any other completed effect, so none of the suspension machinery exists twice.
One run’s failure does not block the runs behind it: a batch that stops at the first error hands a single unresumable run a veto over every later wake in the tick — and the failed run heals anyway: its wake was recorded under a lease this pass acquired and never released, so the recovery pass finds it once that lease lapses. The failure is counted rather than returned, because a caller that got a count and an error would have neither.
Sourcepub async fn decide_task(
&self,
id: TaskId,
decision: &Decision,
roles: &[String],
) -> Result<Delivery, RuntimeError>
pub async fn decide_task( &self, id: TaskId, decision: &Decision, roles: &[String], ) -> Result<Delivery, RuntimeError>
Complete a task on behalf of a person, enforcing eligibility first.
The claim is checked before the decision is recorded: an approval from somebody who was not permitted to give it is worse than no approval, because it looks like one.
§Only a person’s answer
Decided::OnExpiry is refused here.
It is the sweeper’s to apply, from the policy the task declared, and it
is the one decision with nobody to claim against: four-eyes has no
actor to exclude and eligibility has no name to check, so every control
this function exists to run would pass by having nothing to test. A
caller reaching this door with one is either applying an expiry from
outside the sweeper or dressing a person’s answer as the policy’s.
Sourcepub async fn decide_task_at(
&self,
id: TaskId,
decision: &Decision,
roles: &[String],
expected: Option<Digest>,
) -> Result<Delivery, RuntimeError>
pub async fn decide_task_at( &self, id: TaskId, decision: &Decision, roles: &[String], expected: Option<Digest>, ) -> Result<Delivery, RuntimeError>
decide_task, refused unless the task row is
still the version expected names.
expected is Justification::digest
of the row as the caller read it. A row that changed since — before the
claim, or between the read and the claim — is refused as
RuntimeError::TaskChanged: nothing is recorded, and a claim this
call took is released. It proves the caller held the current version
of the row, not what any screen displayed. None compares nothing.
Trait Implementations§
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
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,
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