Files
buzz/crates/buzz-workflow/src/error.rs
T
2693e0db1f Make workflow run history authoritative in Desktop (#5780)
## Summary

- persist stable workflow run `error_code` values separately from human
diagnostics
- expose NIP-98 authenticated, channel-authorized run history and
approval reads with stable keyset pagination
- connect Desktop to those authoritative reads and return the
relay-created run ID on trigger
- show truthful loading, failure, and pending-trace states, and do not
render approval actions from non-actionable stored hashes

## Validation

- pre-push `branch-skew`, `desktop-typecheck`, `desktop-test`,
`rust-tests`, `desktop-tauri-checks`, and `desktop-check` all passed on
`a097dbe5f`
- Desktop tests: 4,761 passed, 0 failed
- `cargo check -p buzz-relay`
- `git diff --check`

## Remaining gate

This does not claim a relay-backed Playwright workflow journey. The
browser relay bridge still routes workflow invokes through in-memory
handlers; that production-shaped acceptance gate remains follow-up work
before Workflows can leave preview.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz>
2026-08-13 12:56:37 -07:00

115 lines
3.7 KiB
Rust

//! Workflow error types.
use thiserror::Error;
/// Partial execution progress captured when a workflow step fails mid-run.
///
/// This allows callers to persist whatever trace was accumulated before the
/// error, rather than losing it when the in-memory `Vec` is dropped.
#[derive(Debug, Default)]
pub struct PartialProgress {
/// Index of the step that failed (0-based).
pub step_index: usize,
/// Trace entries for steps completed/skipped before the failure.
pub trace: Vec<serde_json::Value>,
}
/// Errors produced by the workflow engine.
#[derive(Debug, Error)]
pub enum WorkflowError {
/// The workflow YAML/JSON could not be parsed.
#[error("invalid YAML: {0}")]
InvalidYaml(#[from] serde_yaml::Error),
/// The workflow definition violates a semantic invariant.
#[error("invalid definition: {0}")]
InvalidDefinition(String),
/// An `if:` condition expression could not be evaluated.
#[error("condition evaluation error: {0}")]
ConditionError(String),
/// A template variable substitution failed.
#[error("template error: {0}")]
TemplateError(String),
/// A step exceeded its configured timeout.
#[error("step '{step_id}' timed out after {timeout_secs}s")]
StepTimeout {
/// The ID of the step that timed out.
step_id: String,
/// The timeout limit in seconds.
timeout_secs: u64,
},
/// An outbound webhook call failed.
#[error("webhook error: {0}")]
WebhookError(String),
/// The engine's concurrency limit was reached.
#[error("capacity exceeded")]
CapacityExceeded,
/// A database operation failed.
#[error("database error: {0}")]
Database(String),
/// The workflow's owner is not currently authorized to run it (removed
/// from the channel, insufficient role for the definition's actions, or
/// the authority lookup failed — all deny, fail-closed).
#[error("unauthorized: {0}")]
Unauthorized(String),
/// The action is defined but not yet implemented.
#[error("action not implemented: {0}")]
NotImplemented(String),
}
impl WorkflowError {
/// Stable run-level classification. Diagnostics remain in `Display` output.
pub const fn code(&self) -> &'static str {
match self {
Self::InvalidYaml(_) => "invalid_yaml",
Self::InvalidDefinition(_) => "invalid_definition",
Self::ConditionError(_) => "condition_evaluation_failed",
Self::TemplateError(_) => "template_resolution_failed",
Self::StepTimeout { .. } => "step_timeout",
Self::WebhookError(_) => "webhook_failed",
Self::CapacityExceeded => "capacity_exceeded",
Self::Database(_) => "database_error",
Self::Unauthorized(_) => "owner_unauthorized",
Self::NotImplemented(_) => "action_not_implemented",
}
}
}
impl From<buzz_db::error::DbError> for WorkflowError {
fn from(e: buzz_db::error::DbError) -> Self {
WorkflowError::Database(e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::WorkflowError;
#[test]
fn workflow_error_codes_are_stable_and_separate_from_diagnostics() {
let timeout = WorkflowError::StepTimeout {
step_id: "notify".to_owned(),
timeout_secs: 30,
};
assert_eq!(timeout.code(), "step_timeout");
assert!(timeout.to_string().contains("notify"));
let webhook = WorkflowError::WebhookError("secret-bearing detail".to_owned());
assert_eq!(webhook.code(), "webhook_failed");
assert!(!webhook.code().contains("secret-bearing detail"));
assert_eq!(
WorkflowError::NotImplemented("SendDm".to_owned()).code(),
"action_not_implemented"
);
}
}