Skip to main content

ledgence_orchestration_api/
lib.rs

1//! Portable contracts for task orchestration, resumable workflows and worker delivery.
2//!
3//! Submission, leases, durable settlement, completion callbacks, workflow events,
4//! child tasks and retention describe transactional operations. They provide no
5//! persistence or transport by themselves. Adapters must apply the full operation
6//! before acknowledging it and preserve replay and ownership semantics.
7//!
8//! See the [delivery contract](https://github.com/Ledgence/ledgence/blob/main/docs/delivery-contract.md)
9//! and [workflow contract](https://github.com/Ledgence/ledgence/blob/main/docs/workflows.md).
10
11pub use ledgence_worker_api::TraceContext;
12
13mod acquisition;
14mod completion;
15mod delivery;
16mod discovery;
17mod dispatch;
18mod observation;
19mod retention;
20mod storage;
21mod submission;
22mod workflow;
23mod workflow_children;
24mod workflow_events;
25pub use acquisition::*;
26pub use completion::*;
27pub use delivery::*;
28pub use discovery::*;
29pub use dispatch::*;
30pub use observation::*;
31pub use retention::*;
32pub use storage::*;
33pub use submission::*;
34pub use workflow::*;
35pub use workflow_children::*;
36pub use workflow_events::*;
37
38use ledgence_worker_api::{Error, ErrorKind};
39use serde::{Deserialize, Serialize};
40use std::{fmt, future::Future, pin::Pin};
41
42/// Expected operation rejection or an adapter failure. Backend errors must not
43/// be translated into successful empty acquisitions or lost ownership.
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(tag = "code", content = "message", rename_all = "snake_case")]
46pub enum ContractError {
47    InvalidInput(String),
48    /// Integrated acquisition rejected because the queue requires external
49    /// dispatch. This exact operation granted no authority and committed no
50    /// consumer-cursor mutation. Existing completed sequences must replay before
51    /// checking the route. Only a timely confirmed response permits stopping
52    /// reconciliation; a transport timeout remains an unknown outcome.
53    ExternalDispatchRequired,
54    /// A queue receive positively returned invalid transport data, before any
55    /// targeted claim or cursor mutation was issued. This stops new broker
56    /// admission without acknowledging the record. Adapters must not use this
57    /// for timeouts, network failures, or errors from claim/acknowledgment calls.
58    InvalidQueueDelivery(String),
59    Conflict,
60    OwnershipLost,
61    UnknownSession,
62    SessionExpired,
63    ObsoleteOperation,
64    OutOfOrder,
65    Busy,
66    NotFound,
67    Unavailable(String),
68}
69impl From<Error> for ContractError {
70    fn from(error: Error) -> Self {
71        Self::InvalidInput(error.to_string())
72    }
73}
74impl fmt::Display for ContractError {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "{self:?}")
77    }
78}
79impl std::error::Error for ContractError {}
80pub type Result<T> = std::result::Result<T, ContractError>;
81pub type ContractFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
82
83/// Fixed-delay retry policy, including the initial attempt in `max_attempts`.
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct RetryPolicy {
87    pub max_attempts: u32,
88    pub retry_delay_ms: u64,
89}
90impl Default for RetryPolicy {
91    fn default() -> Self {
92        Self {
93            max_attempts: 3,
94            retry_delay_ms: 5_000,
95        }
96    }
97}
98impl RetryPolicy {
99    pub fn validate(&self) -> ledgence_worker_api::Result<()> {
100        if !(1..=1_000).contains(&self.max_attempts) || self.retry_delay_ms > 86_400_000 {
101            return Err(Error::new(
102                ErrorKind::InvalidInput,
103                "retry policy exceeds supported limits",
104            ));
105        }
106        Ok(())
107    }
108}
109
110/// Initial server limits. All times are milliseconds; none is a concurrency knob.
111pub const LEASE_DURATION_MS: u64 = 60_000;
112pub const RENEW_INTERVAL_MS: u64 = 15_000;
113pub const LEASE_SAFETY_MARGIN_MS: u64 = 5_000;
114pub const LONG_POLL_WAIT_MS: u64 = 20_000;
115pub const CONTROL_REQUEST_TIMEOUT_MS: u64 = 30_000;
116pub const CLEANUP_GRACE_MS: u64 = 30_000;
117pub const SESSION_VALIDITY_MS: u64 = 86_400_000;
118pub const TERMINAL_RETENTION_MS: u64 = 90 * 86_400_000;
119pub const SETTLEMENT_MAX_BYTES: usize = 8 * 1024 * 1024;
120
121/// Identifier/reference text stored in indexed platform columns.
122pub fn validate_text(value: &str, maximum: usize) -> Result<()> {
123    if value.is_empty()
124        || value.len() > maximum
125        || value.chars().any(|character| {
126            let code = u32::from(character);
127            character.is_control() || (0xfdd0..=0xfdef).contains(&code) || (code & 0xfffe) == 0xfffe
128        })
129    {
130        return Err(ContractError::InvalidInput(
131            "invalid platform identifier/reference".into(),
132        ));
133    }
134    Ok(())
135}