runner-actor
Source: crates/lvcs-action/src/runner/mod.rs.
/// A change job's id: `HASH([frontier id])`, the frontier it prepares.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct JobId(#[serde(with = "lvcs_doc::cbor::fixed")] pub [u8; 32]);
/// The runner actor's events, generated by its sml table: its mailbox and
/// journal type (decision 286). None carries a
/// credential value.
///
/// Its events, each the payload type its variant is named for:
/// - [`RunnerConfig`]: First, and again at every caps render and every move of
/// `lvcs.toml`'s approved heads: what its participant's grants ask it
/// to serve.
/// - [`Probed`]: The sandbox probe the runner asked for (`RunnerOutput::Probe`)
/// ended.
/// - [`Visible`]: Change: main's visible frontier moved: sent before the `Merged` a
/// merge causes, since a trigger opens a job on the visible frontier as
/// last sent, and when a render of one of main's `file` documents moved
/// it. Each starts a quiet period.
/// - [`Merged`]: Change: a bundle merged into main.
/// - [`Tick`]: Change: a [`RunnerOutput::Wake`] fired.
/// - [`Prepared`]: Change: the executor prepared a job: each change action's inputs.
/// - [`Ran`]: Change: the executor ran one change action of a job.
/// - [`Derived`]: Change: the executor ran a job's generator (answering
/// [`RunnerOutput::Derive`]).
/// - [`GeneratedSeen`]: Change: a generation counted (row 180): the shell forwards each
/// inputs the channels actor names newly counted, shown or not
/// (`ChannelsOutput::Counted`).
/// - [`GenerationDropped`]: Change: the shell couldn't run job `job`'s generator or record its
/// generation (no grant, a quota reached, an executor error): the
/// inputs count as never run, and the job ends with nothing attested.
/// - [`Candidates`]: Gated: an action channel's pending run changed
/// (`ChannelsOutput::Candidates`).
/// - [`Outcomes`]: Gated: an action channel newly holds an outcome of each of some
/// runs (`ChannelsOutput::Outcomes`).
/// - [`Presence`]: Gated: a runner's presence on an action channel's document topic
/// arrived.
/// - [`PresenceTick`]: Gated: the shell's tick, every `RUNNER_PRESENCE_SECS`.
/// - [`Staged`]: Gated: the executor staged a run.
/// - [`Finished`]: Gated: the executor ran a run.
/// - [`Released`]: Gated: a run's build trees are gone, and the executor is free of
/// it.
pub use machine::RunnerEvents;
/// What its participant's grants ask a runner to serve, and that
/// participant (decisions 206 and 245).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunnerConfig {
/// The participant this actor runs as.
pub me: ParticipantKey,
/// What its grants ask for.
pub wants: Wants,
/// Every toolchain an action at `lvcs.toml`'s approved heads names
/// (decision 245), which the sandbox probe covers besides `rust`.
#[serde(with = "crate::config::toolchains")]
pub toolchains: BTreeSet<Toolchain>,
}
/// What the participant's live grants, each naming a channel with
/// `Attest(<channel>)` exactly, ask a runner to serve, given the actions at
/// `lvcs.toml`'s approved heads: a pure function of both, computed by the
/// shell (decision 297). `change` when a grant attests a change action's
/// channel, the generator's included; `gated`, each gated action's
/// channel a grant attests. Any set of channels may be asked for at once;
/// none is asked of the owner, whose capabilities need no grant.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Wants {
/// The change channels to serve, when a grant attests one.
pub change: Option<ChangeWants>,
/// The gated actions' channels grants attest.
pub gated: GatedConfig,
}
/// What a runner of change channels runs, before the sandbox probe gives
/// its toolchain hash (decision 297): every change channel at
/// `lvcs.toml`'s approved heads with the ids of the change actions that
/// move it (the generator's aside), the change channels the grants attest
/// (`served`, never empty, the generator's included when attested), the
/// quiet period `[runner]` resolves to, and the generator.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChangeWants {
/// Every change channel but the generator's, with its actions' ids.
pub channels: BTreeMap<ChannelName, BTreeSet<ActionId>>,
/// The change channels the grants attest.
pub served: BTreeSet<ChannelName>,
/// The quiet period, in milliseconds.
pub quiet_ms: u64,
/// The generator, when an action has `writes`.
pub generator: Option<ChangeGenerator>,
}
/// The generator's id and its channel, which its generations go in.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChangeGenerator {
/// The generator's id.
pub id: ActionId,
/// Its channel, its name.
pub channel: ChannelName,
}
/// How the sandbox probe ended.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Probed {
/// The sandbox runs here, with this toolchain hash.
Ready(#[serde(with = "lvcs_doc::cbor::fixed")] [u8; 32]),
/// It can't run here, and why.
Unavailable(String),
}
/// A change runner's configuration, fixed for its incarnation (decision
/// 127): its [`ChangeWants`]' channels, quiet period, and generator when it
/// first served a change channel, with the toolchain hash the sandbox's
/// toolchain probes gave (decision 245). Which of these channels are
/// served follows each `Config` (decision 297), as gated channels do.
///
/// `channels` is every change channel but the generator's with its
/// actions' ids, `quiet_ms` how long main's visible frontier must be
/// unchanged before a job opens on it, and `generator` the action with
/// `writes` and its channel, which the shell names in every generation
/// record; the runner derives only while it serves the generator's
/// channel.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChangeConfig {
/// Every change channel but the generator's, with its actions' ids.
pub channels: BTreeMap<ChannelName, BTreeSet<ActionId>>,
/// The toolchain hash.
#[serde(with = "lvcs_doc::cbor::fixed")]
pub toolchain_hash: [u8; 32],
/// The quiet period, in milliseconds.
pub quiet_ms: u64,
/// The generator, when an action has `writes`.
pub generator: Option<ChangeGenerator>,
}
/// The gated actions a runner serves: each gated action's channel its
/// participant's live grants attest, with the id of the gated action that
/// moves it at `lvcs.toml`'s approved heads.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GatedConfig {
/// Each served channel's gated action.
pub served: BTreeMap<ChannelName, ActionId>,
}
/// Main's visible frontier as a delta since the last (the first is full).
///
/// `changed` holds the documents whose heads changed, with their heads,
/// and `removed` the documents that left it; the shell sent it at its Unix
/// time `now_ms`, in milliseconds.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Visible {
/// The documents whose heads changed, with their heads.
#[serde(with = "lvcs_doc::cbor::head_map")]
pub changed: BTreeMap<String, Vec<ChangeHash>>,
/// The documents that left it.
pub removed: Vec<String>,
/// When the shell sent it.
pub now_ms: i64,
}
/// Bundle `bundle` merged into main.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Merged {
/// The bundle that merged.
pub bundle: BundleId,
}
/// A `Wake` fired, at the shell's Unix time `now_ms`, in milliseconds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tick {
/// When the shell sent it.
pub now_ms: i64,
}
/// Job `job` is materialized, with each change action's `InputsHash`; an
/// action left out couldn't be prepared, and ends the job with nothing
/// attested.
///
/// With a generator, `generator` is its `InputsHash` at the job's frontier
/// (row 180), and each action's inputs were computed over the counted
/// generation's bytes when one exists for it; a job prepared without it
/// ends with nothing attested.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Prepared {
/// The job.
pub job: JobId,
/// Each prepared change action's `InputsHash`.
pub inputs: BTreeMap<ActionId, InputsHash>,
/// The generator's `InputsHash`, with a generator.
pub generator: Option<InputsHash>,
/// Whether `generator` has a counted generation, shown or not, as the
/// shell last heard from the channels actor (`ChannelsOutput::Counted`
/// and `Uncounted`) when it passed this event on: a runner whose
/// toolchain hash differs from its peers' counts other inputs than the
/// shown generation's (row 180).
pub counted: bool,
}
/// Change action `action` of job `job` ran, and how it ended.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Ran {
/// The job.
pub job: JobId,
/// The action that ran.
pub action: ActionId,
/// How it ended.
pub outcome: RunOutcome,
}
/// How a change action's run ended (decision 230).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RunOutcome {
/// It exited 0 within its timeout.
Passed,
/// It exited non-zero within its timeout, or couldn't run.
Failed,
/// The sandbox killed it at its timeout (exit status 124): never
/// reusable, and, like a failure, it holds only its own channel.
TimedOut,
}
/// What a generator run came to (row 180): each output address's SHA-256
/// digest, or why it failed.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeriveOutcome {
/// The run's derived outputs, each address with its bytes' digest.
Outputs(#[serde(with = "digest_map")] BTreeMap<String, [u8; 32]>),
/// The run failed.
Failed(DeriveFailure),
}
/// Job `job`'s generator ran on the inputs `inputs`, with `outcome`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Derived {
/// The job.
pub job: JobId,
/// The generator's `InputsHash`.
pub inputs: InputsHash,
/// What the run came to.
pub outcome: DeriveOutcome,
/// Whether `inputs` has a counted generation, as the shell last heard
/// when it passed this event on (as [`Prepared::counted`]).
pub counted: bool,
}
/// Job `job`'s generator run on `inputs` was not recorded.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenerationDropped {
/// The job.
pub job: JobId,
/// The generator's `InputsHash`.
pub inputs: InputsHash,
}
/// The generator's inputs `inputs` newly have a counted generation, shown
/// or not (`ChannelsOutput::Counted`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct GeneratedSeen {
/// The counted generation's `InputsHash`.
pub inputs: InputsHash,
}
/// Action channel `channel`'s pending run is now `run`, or none.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Candidates {
/// The channel.
pub channel: ChannelName,
/// Its pending run.
pub run: Option<PendingRun>,
}
/// Action channel `channel` newly holds an outcome, passed or failed, of
/// each run in `runs`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Outcomes {
/// The channel.
pub channel: ChannelName,
/// The runs.
pub runs: Vec<RunKey>,
}
/// Runner `agent`'s presence on channel `channel` arrived.
///
/// It came on action channel `channel`'s document topic, at the shell's
/// Unix time `now_ms`, in milliseconds: its arrival time, never the
/// sender's `ts` (decision 142). The shell sends only a presence from a
/// holder of `Attest(channel)`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Presence {
/// The runner.
pub agent: ParticipantKey,
/// The channel.
pub channel: ChannelName,
/// When it arrived.
pub now_ms: i64,
}
/// The shell's presence tick, at its Unix time `now_ms`, in milliseconds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PresenceTick {
/// When the shell sent it.
pub now_ms: i64,
}
/// Run `run` is staged: its directory is materialized.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Staged {
/// The run.
pub run: RunKey,
}
/// Run `run` ended: `passed` when its command exited 0 in time.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Finished {
/// The run.
pub run: RunKey,
/// Whether it passed.
pub passed: bool,
}
/// Run `run`'s build trees are gone, and it holds the executor no more.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Released {
/// The run.
pub run: RunKey,
}
/// What the runner asks the shell to do. None carries a credential value.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RunnerOutput {
/// Probe the sandbox once, on a blocking thread (answered by
/// `Probed`), for `rust` and these toolchains.
Probe {
/// The toolchains besides `rust`.
#[serde(with = "crate::config::toolchains")]
toolchains: BTreeSet<Toolchain>,
},
/// What `status` names of this runner moved.
Serving(Serving),
/// The runner serves change channels from now on, with this
/// configuration: the shell starts its change side. A runner that
/// served change channels before in this incarnation names the
/// configuration it first served them with.
StartChange(ChangeConfig),
/// The runner serves no change channel any more: its participant's
/// grants attest none (decision 249). The shell drops its change side:
/// a job step under way is discarded when it returns, never told to the
/// runner or attested, and its build trees are removed then.
StopChange,
/// The runner serves these gated actions from now on, none included:
/// the shell's gated side follows.
ServeGated(GatedConfig),
/// Change: materialize `frontier` as job `job` and compute the inputs of
/// `actions` (answered by `Prepared`).
Prepare {
/// The job.
job: JobId,
/// The frontier to materialize.
frontier: Frontier,
/// The change actions whose inputs to compute.
actions: BTreeSet<ActionId>,
},
/// Change: run change action `action` of job `job` on `frontier`, whose
/// prepared `InputsHash` is `inputs` (answered by `Ran`).
Run {
/// The job.
job: JobId,
/// The job's frontier.
frontier: FrontierId,
/// The action to run.
action: ActionId,
/// The action's prepared `InputsHash`.
inputs: InputsHash,
},
/// Change: change action `action` of job `job` is reused, not run: its
/// prepared `InputsHash` equals this runner's own last pass of it. The
/// shell records its `run` span with `outcome` `reused` (decision
/// 134). Emitted once per reused action, in `ActionId` order, when the
/// job is prepared, so before any of the job's `Run`s and `Attest`s.
Reused {
/// The job.
job: JobId,
/// The action reused.
action: ActionId,
},
/// Change: every change action of served channel `channel` passed on
/// job `job`'s `frontier`, or was reused (decision 297): the shell
/// signs an attestation under the runner's key and device and puts it
/// in `channel`. The job runs on; its `Ended` ends it.
Attest {
/// The job.
job: JobId,
/// The channel attested.
channel: ChannelName,
/// The frontier attested.
frontier: Frontier,
/// Each of the channel's actions' prepared `InputsHash`.
actions: BTreeMap<ActionId, InputsHash>,
/// The runner's toolchain hash.
#[serde(with = "lvcs_doc::cbor::fixed")]
toolchain_hash: [u8; 32],
},
/// Change: job `job` ended: every action it planned ran or was
/// reused, after the `Attest` of each channel all of whose actions
/// passed; or a `Prepared` left an action out, or its generation was
/// dropped or superseded, with nothing attested. The shell gives the
/// executor to the next job and removes the job's build trees.
Ended {
/// The job.
job: JobId,
},
/// Change: send a `Tick` at or after the shell's Unix time `at_ms`, in
/// milliseconds (`CoordOutput::Wake`'s pattern): the end of the quiet
/// period the last `Visible` began.
Wake {
/// The end of the quiet period.
at_ms: i64,
},
/// Change: run job `job`'s generator on `frontier` through the change executor
/// and sandbox (answered by `Derived`): its inputs have no counted
/// generation, and this runner never ran them.
Derive {
/// The job.
job: JobId,
/// The frontier to derive from.
frontier: Frontier,
},
/// Change: record job `job`'s generator run on `inputs` at `frontier`: write
/// its outputs (whole-file edits flagged `Mark`, under the runner's
/// `write` grant) and sign its generation record, or sign the failure.
Generate {
/// The job.
job: JobId,
/// The generator's `InputsHash`.
inputs: InputsHash,
/// The frontier it ran on.
frontier: Frontier,
/// What the run came to.
outcome: DeriveOutcome,
},
/// Gated: the channels served moved: the shell publishes this runner's
/// presence on each one's document topic every `RUNNER_PRESENCE_SECS`.
Announce {
/// The channels served, sorted.
channels: Vec<ChannelName>,
},
/// Gated: materialize `frontier`'s files under gated action `action`'s
/// `paths` as run `run` (answered by `Staged`); `trigger` says what
/// started it, its `LVCS_TRIGGER` (decision 329).
Stage {
/// The run.
run: RunKey,
/// The frontier it runs on.
frontier: FrontierId,
/// The gated action.
action: ActionId,
/// What started it: `channel` when it serves its frontier's trigger
/// origin ([`trigger_origin`]), `manual` when it serves a run
/// request.
#[serde(with = "run_trigger")]
trigger: RunTrigger,
},
/// Gated: run `run` of gated action `action` on `frontier` was
/// cancelled while under way, since its channel's grant ended (decision
/// 249, revised) or a newer pending run of its channel superseded it
/// (decision 306): the shell kills its step now, if one is out, and its
/// answer (`Staged` or `Finished`) still comes back, which `Cancelled`
/// then ends.
Kill {
/// The run.
run: RunKey,
/// The frontier it ran on.
frontier: FrontierId,
/// The gated action.
action: ActionId,
},
/// Gated: run `run` was cancelled while under way, since its channel's
/// grant ended (decision 249), and the executor's answer for it
/// (`Staged` or `Finished`) is back: the shell ends it, recording no
/// outcome, and answers `Released` once the executor is free of it.
Cancelled {
/// The run.
run: RunKey,
},
/// Gated: launch gated action `action`'s steps as run `run` (answered
/// by `Finished`).
Launch {
/// The run.
run: RunKey,
/// The gated action.
action: ActionId,
},
/// Gated: run `run` of gated action `action`, of `frontier`, ended: the
/// shell signs an `Outcome` with the runner and device keys and puts it
/// in the action's channel.
Outcome {
/// The run.
run: RunKey,
/// The frontier it ran on.
frontier: FrontierId,
/// The gated action.
action: ActionId,
/// Whether it passed.
passed: bool,
},
}
/// What a runner serves, as `status` names it.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Serving {
/// No grant names a channel there is an action for.
#[default]
Nothing,
/// These channels, change actions' and gated actions' alike.
Channels(Vec<ChannelName>),
/// These channels, once the sandbox probe answers.
Probing(Vec<ChannelName>),
/// These channels, which it runs nothing for: the sandbox can't run
/// here, for this reason.
Unavailable(Vec<ChannelName>, String),
/// Nothing: its participant's grants no longer attest these channels
/// it served (decision 249).
Revoked(Vec<ChannelName>),
}