use onemessagebus::{Message, Registry, SchemaId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::event::PipelineKind;
type Object = Map<String, Value>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum Status {
Pending,
Ready,
Running,
Waiting,
Blocked,
Parked,
Cancelled,
Done,
#[serde(rename = "complete-but-draft")]
CompleteDraft,
Failed,
Skipped,
}
impl From<crate::graph::NodeStatus> for Status {
fn from(status: crate::graph::NodeStatus) -> Self {
use crate::graph::NodeStatus as S;
match status {
S::Pending => Self::Pending,
S::Ready => Self::Ready,
S::Running => Self::Running,
S::Waiting => Self::Waiting,
S::Blocked => Self::Blocked,
S::Parked => Self::Parked,
S::Cancelled => Self::Cancelled,
S::Done => Self::Done,
S::CompleteDraft => Self::CompleteDraft,
S::Failed => Self::Failed,
S::Skipped => Self::Skipped,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum LandingWord {
Landed,
Unlanded,
}
impl From<crate::graph::Landing> for LandingWord {
fn from(landing: crate::graph::Landing) -> Self {
match landing {
crate::graph::Landing::Landed => Self::Landed,
crate::graph::Landing::Unlanded => Self::Unlanded,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub(crate) struct AuthorWord(String);
impl From<crate::channel::Author> for AuthorWord {
fn from(author: crate::channel::Author) -> Self {
Self(author.as_str().to_owned())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ReleaseStyleWord {
Automated,
HumanStep,
}
impl From<onevcs::releases::ReleaseStyle> for ReleaseStyleWord {
fn from(style: onevcs::releases::ReleaseStyle) -> Self {
match style {
onevcs::releases::ReleaseStyle::Automated => Self::Automated,
onevcs::releases::ReleaseStyle::HumanStep => Self::HumanStep,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub(crate) enum DeliveryWord {
Live,
Next,
}
impl From<crate::edits::Delivery> for DeliveryWord {
fn from(delivery: crate::edits::Delivery) -> Self {
match delivery {
crate::edits::Delivery::Live => Self::Live,
crate::edits::Delivery::Deferred => Self::Next,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub(crate) enum AnswerWord {
Match,
Mismatch,
Unread,
}
impl From<&crate::criteria::Answer> for AnswerWord {
fn from(answer: &crate::criteria::Answer) -> Self {
match answer {
crate::criteria::Answer::Match => Self::Match,
crate::criteria::Answer::Mismatch { .. } => Self::Mismatch,
crate::criteria::Answer::Unread { .. } => Self::Unread,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum EndingWord {
DispatchFailed,
SchemaRefused,
NoBody,
}
impl From<&crate::lifecycle::Undrafted> for EndingWord {
fn from(undrafted: &crate::lifecycle::Undrafted) -> Self {
match undrafted {
crate::lifecycle::Undrafted::Dispatch(_) => Self::DispatchFailed,
crate::lifecycle::Undrafted::SchemaRefused => Self::SchemaRefused,
crate::lifecycle::Undrafted::Bodyless => Self::NoBody,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ReachedWord {
Queued,
Worker,
Supervisor,
JudgedWith,
Carried,
}
impl From<&crate::note::Reached> for ReachedWord {
fn from(reached: &crate::note::Reached) -> Self {
use crate::note::Reached as R;
match reached {
R::Queued => Self::Queued,
R::Worker => Self::Worker,
R::Supervisor => Self::Supervisor,
R::JudgedWith { .. } => Self::JudgedWith,
R::Carried => Self::Carried,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum EvidenceWord {
DeliveredOrigin,
OpeningTask,
InstructionText,
AnsweringTurn,
}
impl From<crate::note::Evidence> for EvidenceWord {
fn from(evidence: crate::note::Evidence) -> Self {
use crate::note::Evidence as E;
match evidence {
E::DeliveredOrigin => Self::DeliveredOrigin,
E::OpeningTask => Self::OpeningTask,
E::InstructionText => Self::InstructionText,
E::AnsweringTurn => Self::AnsweringTurn,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct RunStarted {
pub(crate) plan: Object,
pub(crate) graph: Option<String>,
pub(crate) dir: String,
pub(crate) heartbeat_interval: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct ConcurrentAcknowledged {
pub(crate) shared_identities: Vec<String>,
pub(crate) runs: ConcurrentRuns,
pub(crate) holders: Vec<Holder>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct ConcurrentRuns {
pub(crate) launching: String,
pub(crate) holding_sessions: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct Holder {
pub(crate) session: String,
pub(crate) owner_pid: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NodeReady {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NodeDispatched {
pub(crate) attempt: u64,
#[serde(default)]
pub(crate) persona: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) attempts: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) notes_spent: Option<Vec<Object>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) notes_carried: Option<Vec<Object>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum RequeueReasonWord {
WorkspaceExhausted,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NodeRequeued {
pub(crate) reason: RequeueReasonWord,
pub(crate) detail: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) branch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) attempt: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NodeSettled {
pub(crate) status: Status,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) detail: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) branch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) change_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) cause: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) head: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) landing: Option<LandingWord>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) completed_steps: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct EditCommitted {
pub(crate) author: AuthorWord,
pub(crate) command: Object,
pub(crate) operations: Vec<Object>,
pub(crate) operation_kinds: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct CommandAccepted {
pub(crate) author: AuthorWord,
pub(crate) command: Object,
pub(crate) operations: Vec<Object>,
pub(crate) operation_kinds: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct EditRejected {
pub(crate) author: AuthorWord,
pub(crate) command: Object,
pub(crate) reason: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct PlannerSurfaceQueued {
pub(crate) kind: String,
pub(crate) message: String,
pub(crate) source: String,
pub(crate) blocking: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct PlannerSurfaced {
pub(crate) kind: String,
pub(crate) message: String,
pub(crate) source: String,
pub(crate) blocking: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) queued_at: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct PlannerReplied {
pub(crate) author: AuthorWord,
#[serde(default)]
pub(crate) completion: Option<bool>,
#[serde(default)]
pub(crate) reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct HumanAttested {
#[serde(rename = "ref")]
pub(crate) reference: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct DriverAdopted {
pub(crate) adoption: u64,
pub(crate) pid: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) abandoned: Option<Vec<Abandoned>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct Abandoned {
pub(crate) node: String,
pub(crate) session: String,
pub(crate) branch: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct RunStopped {
pub(crate) owner: String,
pub(crate) forced: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) teardown: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct DispatchStopped {
pub(crate) pid: u32,
pub(crate) interrupt: InterruptWord,
pub(crate) detail: String,
pub(crate) ended: DispatchEndingWord,
pub(crate) waited_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum InterruptWord {
Delivered,
NoTurn,
Failed,
NotAsked,
}
impl From<crate::shutdown::Answer> for InterruptWord {
fn from(answer: crate::shutdown::Answer) -> Self {
use crate::shutdown::Answer as A;
match answer {
A::Delivered => Self::Delivered,
A::NoTurn => Self::NoTurn,
A::Failed => Self::Failed,
A::NotAsked => Self::NotAsked,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum DispatchEndingWord {
Graceful,
Killed,
StillRunning,
}
impl From<crate::shutdown::DispatchEnding> for DispatchEndingWord {
fn from(ending: crate::shutdown::DispatchEnding) -> Self {
use crate::shutdown::DispatchEnding as E;
match ending {
E::Graceful => Self::Graceful,
E::Killed => Self::Killed,
E::StillRunning => Self::StillRunning,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct HostShutdown {
pub(crate) scope: ScopeWord,
pub(crate) owner: String,
pub(crate) forced: bool,
pub(crate) grace_seconds: u64,
pub(crate) dispatches: u32,
pub(crate) graceful: u32,
pub(crate) killed: u32,
pub(crate) teardown: TeardownWord,
pub(crate) root: String,
pub(crate) branches: Vec<BranchPreserved>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum TeardownWord {
Signalled,
NothingToStop,
IdentityDeclined,
NotAttempted,
PartlySignalled,
Refused,
Elsewhere,
}
impl From<crate::journal::StopTeardown> for TeardownWord {
fn from(teardown: crate::journal::StopTeardown) -> Self {
use crate::journal::StopTeardown as T;
match teardown {
T::Signalled => Self::Signalled,
T::NothingToStop => Self::NothingToStop,
T::IdentityDeclined => Self::IdentityDeclined,
T::NotAttempted => Self::NotAttempted,
T::PartlySignalled => Self::PartlySignalled,
T::Refused => Self::Refused,
T::Elsewhere => Self::Elsewhere,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ScopeWord {
Run,
Mine,
Host,
}
impl From<&crate::shutdown::ShutdownScope> for ScopeWord {
fn from(scope: &crate::shutdown::ShutdownScope) -> Self {
use crate::shutdown::ShutdownScope as S;
match scope {
S::Run(_) => Self::Run,
S::Mine => Self::Mine,
S::Host => Self::Host,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct BranchPreserved {
pub(crate) identity: String,
pub(crate) branch: String,
pub(crate) result: PreservedWord,
pub(crate) remote: Option<String>,
pub(crate) commit: Option<String>,
pub(crate) detail: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum PreservedWord {
Pushed,
AlreadyOnOrigin,
NoRemote,
Refused,
}
impl From<crate::shutdown::Preserved> for PreservedWord {
fn from(preserved: crate::shutdown::Preserved) -> Self {
use crate::shutdown::Preserved as P;
match preserved {
P::Pushed => Self::Pushed,
P::AlreadyOnOrigin => Self::AlreadyOnOrigin,
P::NoRemote => Self::NoRemote,
P::Refused => Self::Refused,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct QuietWorker {
pub(crate) quiet_for_seconds: u64,
pub(crate) threshold_seconds: u64,
pub(crate) persona: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NodeHeld {
pub(crate) reasons: Vec<Object>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NodeUnheld {
pub(crate) released: Vec<Object>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct DecisionPending {
pub(crate) reference: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) unblocks: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct DecisionCleared {
pub(crate) reference: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) released: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct CrossDagSatisfied {
pub(crate) dependency: String,
pub(crate) last_seq: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct UpstreamModified {
pub(crate) dependency: String,
pub(crate) captured_last_seq: u64,
pub(crate) observed_last_seq: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct CompletionRequested {
pub(crate) reason: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct ReleaseWait {
pub(crate) node: String,
pub(crate) awaiting: Vec<Object>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct ReleaseArrived {
pub(crate) node: String,
pub(crate) dep: String,
pub(crate) identity: String,
pub(crate) version: String,
#[serde(default)]
pub(crate) target: Option<String>,
#[serde(default)]
pub(crate) style: Option<ReleaseStyleWord>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct ReleaseAdopted {
pub(crate) node: String,
pub(crate) delivery: DeliveryWord,
pub(crate) versions: Vec<AdoptedVersion>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct AdoptedVersion {
pub(crate) identity: String,
pub(crate) target: String,
pub(crate) version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) dep: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) branch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) commit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) instructions: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct CriterionChecked {
pub(crate) criterion: String,
pub(crate) file: String,
pub(crate) expected: String,
pub(crate) answer: AnswerWord,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) holds: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct BodyNotDrafted {
pub(crate) ending: EndingWord,
pub(crate) detail: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct NoteShown {
pub(crate) addressee: crate::note::Addressee,
pub(crate) text: String,
pub(crate) reached: ReachedWord,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) criterion: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) completion_reason: Option<String>,
pub(crate) party: crate::note::Party,
pub(crate) turn: u64,
pub(crate) evidence: EvidenceWord,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum HookWord {
Success,
Failure,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum HookReasonWord {
Nodes,
Unfinished,
Stopped,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct UnsettledNode {
pub(crate) id: String,
pub(crate) status: Status,
pub(crate) outcome: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct HookReason {
pub(crate) kind: HookReasonWord,
pub(crate) nodes: Vec<UnsettledNode>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct RunHookFired {
pub(crate) hook: HookWord,
pub(crate) command: String,
pub(crate) reason: Option<HookReason>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum HookEndingWord {
Succeeded,
Failed,
CouldNotStart,
TimedOut,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct RunHookFinished {
pub(crate) hook: HookWord,
pub(crate) exit: Option<i32>,
pub(crate) ending: HookEndingWord,
pub(crate) log: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum WithheldSettlementWord {
AwaitingPlanner,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct RunHookWithheld {
pub(crate) settlement: WithheldSettlementWord,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct PoolMaintenance {
pub(crate) started_at: String,
pub(crate) identities: Vec<MaintainedIdentity>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub(crate) struct MaintainedIdentity {
pub(crate) identity: String,
pub(crate) every: String,
#[serde(flatten)]
pub(crate) answer: MaintainedAnswer,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub(crate) enum MaintainedAnswer {
Outcome {
outcome: Value,
},
Error {
error: String,
},
}
macro_rules! payload_messages {
($($payload:ident => $wire:literal;)*) => {
$(
impl Message for $payload {
const SCHEMA: SchemaId =
SchemaId::literal("agent", concat!("pipeline.", $wire), 2);
}
)*
pub(crate) const fn schema_of(kind: PipelineKind) -> SchemaId {
match kind {
$(PipelineKind::$payload => <$payload as Message>::SCHEMA,)*
}
}
pub(crate) fn registry() -> Registry {
let mut registry = onemessagebus_agent::registry();
$(
registry
.register::<$payload>()
.expect(concat!("the ", $wire, " payload schema registers"));
)*
for kind in crate::event::PIPELINE_KINDS {
debug_assert!(
registry.schema(&schema_of(*kind)).is_some(),
"{kind} has no payload document in the registry"
);
}
registry
}
};
}
payload_messages! {
RunStarted => "run-started";
ConcurrentAcknowledged => "concurrent-acknowledged";
NodeReady => "node-ready";
NodeDispatched => "node-dispatched";
NodeSettled => "node-settled";
NodeRequeued => "node-requeued";
EditCommitted => "edit-committed";
CommandAccepted => "command-accepted";
EditRejected => "edit-rejected";
PlannerSurfaceQueued => "planner-surface-queued";
PlannerSurfaced => "planner-surfaced";
PlannerReplied => "planner-replied";
HumanAttested => "human-attested";
DriverAdopted => "driver-adopted";
RunStopped => "run-stopped";
QuietWorker => "quiet-worker";
NodeHeld => "node-held";
NodeUnheld => "node-unheld";
DecisionPending => "decision-pending";
DecisionCleared => "decision-cleared";
CrossDagSatisfied => "cross-dag-satisfied";
UpstreamModified => "upstream-modified";
CompletionRequested => "completion-requested";
ReleaseWait => "release-wait";
ReleaseArrived => "release-arrived";
ReleaseAdopted => "release-adopted";
CriterionChecked => "criterion-checked";
BodyNotDrafted => "body-not-drafted";
NoteShown => "note-shown";
RunHookFired => "run-hook-fired";
RunHookFinished => "run-hook-finished";
RunHookWithheld => "run-hook-withheld";
PoolMaintenance => "pool-maintenance";
DispatchStopped => "dispatch-stopped";
HostShutdown => "host-shutdown";
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{registry, ENVELOPE_VERSION, PIPELINE_KINDS};
use onemessagebus::CheckError;
#[test]
fn every_kind_this_crate_emits_is_a_registered_bus_message() {
let mut seen = std::collections::BTreeSet::new();
for kind in PIPELINE_KINDS {
let id = schema_of(*kind);
assert_eq!(
id.to_string(),
format!("agent.pipeline.{kind}@{ENVELOPE_VERSION}")
);
assert!(
registry().schema(&id).is_some(),
"{id} is not in the registry this crate constructs"
);
assert!(seen.insert(id.clone()), "{id} is claimed by two kinds");
}
assert_eq!(
registry()
.ids()
.iter()
.filter(|id| id.family().starts_with("agent.pipeline."))
.count(),
PIPELINE_KINDS.len(),
"the registry holds a pipeline payload no kind names"
);
}
#[test]
fn a_payload_violating_its_document_is_refused_naming_the_id_and_the_pointer() {
let id = schema_of(PipelineKind::NodeSettled);
let admitted = serde_json::json!({"status": "done", "outcome": "task-completed"});
registry()
.check(&id, &admitted)
.expect("a settlement this crate writes is admitted");
let refused =
|payload: serde_json::Value, pointer: &str| match registry().check(&id, &payload) {
Err(CheckError::Violation(violation)) => {
assert_eq!(violation.id, id);
assert_eq!(violation.pointer, pointer, "{violation}");
let said = CheckError::Violation(violation).to_string();
assert!(
said.contains("agent.pipeline.node-settled@2") && said.contains(pointer),
"the refusal does not name the id and the pointer: {said}"
);
}
other => panic!("{payload} was not refused as a violation: {other:?}"),
};
refused(serde_json::json!({"status": 5}), "/status");
refused(
serde_json::json!({"status": "done", "completed_steps": "implement"}),
"/completed_steps",
);
refused(serde_json::json!({"outcome": "task-completed"}), "");
}
const RECORDED: &str = include_str!("../tests/recorded/pipeline-kinds.jsonl");
#[test]
fn every_kinds_recorded_envelope_validates_against_its_registered_document() {
let recorded: Vec<crate::event::Envelope> = RECORDED
.lines()
.map(|line| serde_json::from_str(line).expect("a recorded envelope reads"))
.collect();
for envelope in &recorded {
let kind = PipelineKind::from_wire(&envelope.kind)
.unwrap_or_else(|| panic!("{} is not a kind this crate emits", envelope.kind));
assert_eq!(
envelope.v, ENVELOPE_VERSION,
"{kind} was recorded at another version"
);
let payload = serde_json::Value::Object(envelope.payload.clone());
registry()
.check(&schema_of(kind), &payload)
.unwrap_or_else(|refusal| panic!("the recorded {kind} is refused: {refusal}"));
}
for kind in PIPELINE_KINDS {
let id = schema_of(*kind);
let envelope = recorded
.iter()
.find(|envelope| PipelineKind::from_wire(&envelope.kind) == Some(*kind))
.unwrap_or_else(|| panic!("no recorded envelope of {kind}"));
let document = registry().schema(&id).expect("registered");
let Some(required) = document["required"]
.as_array()
.and_then(|keys| keys.first())
else {
continue;
};
let key = required.as_str().expect("a required key is a name");
let mut violating = envelope.payload.clone();
violating.remove(key);
match registry().check(&id, &serde_json::Value::Object(violating)) {
Err(CheckError::Violation(violation)) => {
assert_eq!(violation.id, id);
assert_eq!(violation.pointer, "", "{violation}");
assert!(
violation.to_string().contains(&id.to_string()),
"{violation}"
);
}
other => panic!("{kind} without `{key}` was not refused: {other:?}"),
}
}
}
#[test]
fn every_payload_word_is_its_owners_own_spelling() {
fn word<T: Serialize>(value: &T) -> String {
serde_json::to_value(value)
.expect("a word serializes")
.as_str()
.expect("a word is text")
.to_owned()
}
use crate::graph::NodeStatus as S;
for status in [
S::Pending,
S::Ready,
S::Running,
S::Waiting,
S::Blocked,
S::Parked,
S::Cancelled,
S::Done,
S::CompleteDraft,
S::Failed,
S::Skipped,
] {
assert_eq!(word(&Status::from(status)), status.as_str());
}
for landing in [
crate::graph::Landing::Landed,
crate::graph::Landing::Unlanded,
] {
assert_eq!(word(&LandingWord::from(landing)), landing.as_str());
}
for author in [
crate::channel::Author::planner(),
crate::channel::Author::from("watcher"),
] {
assert_eq!(word(&AuthorWord::from(author.clone())), author.as_str());
assert_eq!(word(&AuthorWord::from(author.clone())), word(&author));
}
for ending in [
crate::shutdown::DispatchEnding::Graceful,
crate::shutdown::DispatchEnding::Killed,
crate::shutdown::DispatchEnding::StillRunning,
] {
assert_eq!(word(&DispatchEndingWord::from(ending)), ending.as_str());
}
for preserved in [
crate::shutdown::Preserved::Pushed,
crate::shutdown::Preserved::AlreadyOnOrigin,
crate::shutdown::Preserved::NoRemote,
crate::shutdown::Preserved::Refused,
] {
assert_eq!(word(&PreservedWord::from(preserved)), preserved.as_str());
}
for answer in [
crate::shutdown::Answer::Delivered,
crate::shutdown::Answer::NoTurn,
crate::shutdown::Answer::Failed,
crate::shutdown::Answer::NotAsked,
] {
assert_eq!(word(&InterruptWord::from(answer)), answer.as_str());
}
for teardown in [
crate::journal::StopTeardown::Signalled,
crate::journal::StopTeardown::NothingToStop,
crate::journal::StopTeardown::IdentityDeclined,
crate::journal::StopTeardown::NotAttempted,
crate::journal::StopTeardown::PartlySignalled,
crate::journal::StopTeardown::Refused,
crate::journal::StopTeardown::Elsewhere,
] {
assert_eq!(word(&TeardownWord::from(teardown)), teardown.word());
assert_eq!(word(&TeardownWord::from(teardown)), word(&teardown));
}
for scope in [
crate::shutdown::ShutdownScope::Run(String::new()),
crate::shutdown::ShutdownScope::Mine,
crate::shutdown::ShutdownScope::Host,
] {
assert_eq!(word(&ScopeWord::from(&scope)), scope.as_str());
}
for style in [
onevcs::releases::ReleaseStyle::Automated,
onevcs::releases::ReleaseStyle::HumanStep,
] {
assert_eq!(word(&ReleaseStyleWord::from(style)), style.as_str());
}
for delivery in [
crate::edits::Delivery::Live,
crate::edits::Delivery::Deferred,
] {
assert_eq!(
word(&DeliveryWord::from(delivery)),
crate::engine::adoption_delivery(delivery)
);
}
for answer in [
crate::criteria::Answer::Match,
crate::criteria::Answer::Mismatch {
holds: String::new(),
},
crate::criteria::Answer::Unread {
reason: String::new(),
},
] {
assert_eq!(word(&AnswerWord::from(&answer)), answer.as_str());
}
for undrafted in [
crate::lifecycle::Undrafted::Dispatch(String::new()),
crate::lifecycle::Undrafted::SchemaRefused,
crate::lifecycle::Undrafted::Bodyless,
] {
assert_eq!(word(&EndingWord::from(&undrafted)), undrafted.ending());
}
use crate::note::Reached as R;
for reached in [
R::Queued,
R::Worker,
R::Supervisor,
R::JudgedWith {
completion_reason: String::new(),
},
R::Carried,
] {
let tagged = serde_json::to_value(&reached).expect("a delivery serializes");
assert_eq!(
serde_json::json!(word(&ReachedWord::from(&reached))),
tagged["reached"]
);
}
assert_eq!(
word(&WithheldSettlementWord::AwaitingPlanner),
crate::hooks::PAUSED
);
use crate::note::Evidence as E;
for evidence in [
E::DeliveredOrigin,
E::OpeningTask,
E::InstructionText,
E::AnsweringTurn,
] {
assert_eq!(word(&EvidenceWord::from(evidence)), word(&evidence));
}
}
}