1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Focused wire mappings for nested engine error families.
use aion::EngineError;
use aion::durability::DurabilityError;
use aion_proto::WireError;
use aion_store::StoreError;
pub(super) fn invalid_state_wire(reason: &str) -> WireError {
WireError::invalid_state_with_type("InvalidState", reason.to_owned())
}
pub(super) fn backend_wire(error_type: &'static str, source: &EngineError) -> WireError {
WireError::backend_with_type(error_type, source.to_string())
}
pub(super) fn contract_refusal_wire(error_type: &'static str, source: &EngineError) -> WireError {
WireError::invalid_state(source.to_string()).with_error_type(error_type)
}
/// Wire mapping for a declared-contract boundary refusal — a start input or a
/// signal payload that did not satisfy the type the package declared.
///
/// These are the CALLER's to fix and cost the target run nothing: the refusal
/// is returned before anything is recorded and before any arrival is consumed.
/// So they are invalid input (HTTP 400), never a backend fault and never a
/// state conflict.
pub(super) fn declared_contract_wire(error_type: &'static str, source: &EngineError) -> WireError {
WireError::invalid_input(source.to_string()).with_error_type(error_type)
}
pub(super) const fn store_error_type(source: &StoreError) -> &'static str {
match source {
StoreError::SequenceConflict { .. } => "SequenceConflict",
StoreError::NotFound { .. } => "NotFound",
StoreError::NotOwner { .. } => "NotOwner",
StoreError::Backend(_) => "Backend",
StoreError::Serialization(_) => "Serialization",
}
}
pub(super) fn durability_wire(durability: &DurabilityError, source: &EngineError) -> WireError {
match durability {
DurabilityError::Store(store) => super::wire_from_store(store),
DurabilityError::NonDeterminism(_)
| DurabilityError::HistoryShape { .. }
| DurabilityError::SearchAttribute(_) => {
WireError::backend_with_type("Durability", source.to_string())
}
// Same wire name as [`aion::EngineError::EngineTaskEpochClosed`] (see
// `error.rs`'s engine mapping), because a caller is being told about one
// condition — this engine has begun closing — and which internal seam
// raised it is not theirs to act on. The two Rust variants stay separate
// for a reason that lives on the Rust side, not on the wire: the engine
// variant carries a documented single-construction-site invariant.
//
// 🔴 WHY THIS DISCRIMINANT IS DROPPED WHEN A SIBLING CHANGE INSISTS ON
// KEEPING ONE. `runtime/nif_timer_fire.rs` introduces `RefusedAppend`
// expressly so a refusal's cause CANNOT be dropped — its own comment
// says collapsing one "let one sentence be raised for several cases".
// Landing both in one changeset looks like arguing both sides, so the
// rule that separates them is stated here rather than left to be
// inferred.
//
// **A discriminant travels when it changes what the recipient should
// DO.** `RefusedAppend` decides which SENTENCE an operator reads, and
// the three cases it separates carry three different remedies: wait for
// workflow code to reissue, check the run's status, or nothing at all.
// Collapse them and the operator is sent to the wrong one. Here the two
// variants share a single condition and a single remedy — this engine
// is closing; the run is untouched; a successor picks it up — so the
// seam of origin changes nothing a caller can act on, and splitting the
// label would only fragment the trace an operator searches. Keeping a
// distinction that cannot alter a decision is not caution; it is a
// second name for one fact, which is the drift this codebase treats as
// a defect in its own right.
//
// Reached only if a durable write refused by the epoch is ever surfaced
// through a transport operation. Today the one construction site is the
// continue-as-new NIF, whose error returns into workflow code rather
// than into a response — but this mapping is total over the type, so it
// decides rather than assuming that stays true.
DurabilityError::EngineTaskEpochClosed { .. } => {
WireError::backend_with_type("EngineTaskEpochClosed", source.to_string())
}
}
}
/// Trace and wire discriminator for live-query dispatch failures.
pub(super) fn query_error_type(source: &aion::QueryError) -> &'static str {
match source {
aion::QueryError::UnknownQuery(_) => "UnknownQuery",
aion::QueryError::Timeout => "QueryTimeout",
aion::QueryError::NotRunning(_) => "QueryNotRunning",
aion::QueryError::Unknown(_) => "QueryUnknownWorkflow",
aion::QueryError::ReplyDropped => "QueryReplyDropped",
aion::QueryError::HandlerFailed { .. } => "QueryFailed",
aion::QueryError::InvalidArguments { .. } => "QueryInvalidArguments",
aion::QueryError::Engine(_) => "QueryEngine",
}
}
/// Wire mapping for live-query dispatch failures.
pub(super) fn query_wire(query: &aion::QueryError, source: &EngineError) -> WireError {
match query {
aion::QueryError::UnknownQuery(_) => WireError::unknown_query(source.to_string()),
aion::QueryError::Timeout => WireError::query_timeout(source.to_string()),
aion::QueryError::NotRunning(_) | aion::QueryError::ReplyDropped => {
WireError::not_running_with_type(query_error_type(query), source.to_string())
}
aion::QueryError::Unknown(_) => {
WireError::not_found_with_type(query_error_type(query), source.to_string())
}
aion::QueryError::HandlerFailed { .. } => {
WireError::query_failed(source.to_string()).with_error_type(query_error_type(query))
}
// The caller's own arguments were malformed: a request defect, not a
// handler failure and not an engine fault. `invalid_input` is the
// code that tells the caller to fix what it sent.
aion::QueryError::InvalidArguments { .. } => {
WireError::invalid_input(source.to_string()).with_error_type(query_error_type(query))
}
aion::QueryError::Engine(_) => {
WireError::backend_with_type(query_error_type(query), source.to_string())
}
}
}