Docs

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>),
}