use serde::{Deserialize, Serialize};
use super::ids::{ChangeId, ProposalId, WorkEdgeId, WorkNodeId};
use super::model::{
AcceptanceRequirement, CompatProjectionState, EvidenceRef, IdempotencyKey, NodeState,
OperationBinding, Provenance, Ts, WorkActivityEvent, WorkEdge, WorkNode,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
pub enum WorkGraphChange {
AddNode {
node: WorkNode,
},
UpdateNode {
id: WorkNodeId,
patch: WorkNodePatch,
},
AddEdge {
edge: WorkEdge,
},
RemoveEdge {
id: WorkEdgeId,
},
BindOperation {
node: WorkNodeId,
binding: OperationBinding,
},
ReconcileOperation {
node: WorkNodeId,
obs: OperationObservation,
},
AttachEvidence {
node: WorkNodeId,
evidence: EvidenceRef,
},
ProposePlanDiff {
proposal: WorkGraphProposal,
},
WithdrawPlanDiff {
proposal_id: ProposalId,
},
AcceptPlanDiff {
proposal_id: ProposalId,
approval: ApprovalRef,
},
Supersede {
old: WorkNodeId,
replacement: WorkNodeId,
},
ReplaceCompatProjection {
compat: CompatProjectionState,
},
SetImportDigest {
digest: String,
},
RecordActivity {
event: WorkActivityEvent,
},
}
impl WorkGraphChange {
#[must_use]
pub fn kind_name(&self) -> &'static str {
match self {
WorkGraphChange::AddNode { .. } => "add_node",
WorkGraphChange::UpdateNode { .. } => "update_node",
WorkGraphChange::AddEdge { .. } => "add_edge",
WorkGraphChange::RemoveEdge { .. } => "remove_edge",
WorkGraphChange::BindOperation { .. } => "bind_operation",
WorkGraphChange::ReconcileOperation { .. } => "reconcile_operation",
WorkGraphChange::AttachEvidence { .. } => "attach_evidence",
WorkGraphChange::ProposePlanDiff { .. } => "propose_plan_diff",
WorkGraphChange::WithdrawPlanDiff { .. } => "withdraw_plan_diff",
WorkGraphChange::AcceptPlanDiff { .. } => "accept_plan_diff",
WorkGraphChange::Supersede { .. } => "supersede",
WorkGraphChange::ReplaceCompatProjection { .. } => "replace_compat_projection",
WorkGraphChange::SetImportDigest { .. } => "set_import_digest",
WorkGraphChange::RecordActivity { .. } => "record_activity",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct WorkNodePatch {
pub title: Option<String>,
pub state: Option<NodeState>,
pub acceptance: Option<Vec<AcceptanceRequirement>>,
pub provenance: Option<Provenance>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkGraphProposal {
pub id: ProposalId,
#[serde(default)]
pub added_nodes: Vec<WorkNode>,
#[serde(default)]
pub added_edges: Vec<WorkEdge>,
#[serde(default)]
pub updated_nodes: Vec<ProposedNodeUpdate>,
#[serde(default)]
pub removed_nodes: Vec<WorkNodeId>,
#[serde(default)]
pub removed_edges: Vec<WorkEdgeId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub replacement_compat: Option<CompatProjectionState>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProposedNodeUpdate {
pub id: WorkNodeId,
pub patch: WorkNodePatch,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApprovalRef {
pub reference: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OwnerState {
Initializing,
Running,
Waiting,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CancelOutcome {
Requested,
Acknowledged,
Forced,
AlreadyFinished,
NotFound,
StaleUnknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationObservation {
OwnerReported {
state: OwnerState,
seq: u64,
at: Ts,
#[serde(default, skip_serializing_if = "Option::is_none")]
output: Option<EvidenceRef>,
},
OwnerMissing {
checked_at: Ts,
},
CancelUpdate {
outcome: CancelOutcome,
at: Ts,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ObservationSummary {
pub owner_state: OwnerState,
pub seq: u64,
pub observed_at: Ts,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output: Option<EvidenceRef>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChangeCtx {
pub session_id: String,
pub now: Ts,
pub idempotency_key: Option<IdempotencyKey>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChangeReceipt {
pub change_id: ChangeId,
pub revision: u64,
pub summary: String,
pub applied_at: Ts,
pub idempotency_key: Option<IdempotencyKey>,
pub no_op: bool,
}
impl ChangeReceipt {
#[must_use]
pub fn of(change: &WorkGraphChange, revision: u64, ctx: &ChangeCtx) -> Self {
let kind = change.kind_name();
ChangeReceipt {
change_id: ChangeId::derive(&ctx.session_id, &format!("change:{revision}:{kind}")),
revision,
summary: kind.to_string(),
applied_at: ctx.now,
idempotency_key: ctx.idempotency_key.clone(),
no_op: false,
}
}
}