Skip to main content

bb_runtime/engine/
step.rs

1//! `EngineStep` - observable output of `Engine::poll` per
2//! `docs/ENGINE.md` §10 + `docs/internal/IMPLEMENTATION_PLAN.md` //! lines 751-753.
3
4use crate::bus::{OpError, WireReceiveErrorKind};
5use crate::envelope::WireEnvelope;
6use crate::framework::BlockReason;
7use crate::ids::{CommandId, ExecId, NodeSiteId, OpRef, PeerId};
8
9/// One step of work the engine performed during a poll cycle.
10/// `Engine::poll` returns `Vec<EngineStep>` capturing every event
11/// the host can observe.
12#[derive(Clone, Debug)]
13pub enum EngineStep {
14    /// An Op completed successfully + wrote `sites_written` slots.
15    OpCompleted {
16        /// The completed Op.
17        op_ref: OpRef,
18        /// The execution it belonged to.
19        exec_id: ExecId,
20        /// Output sites the Op wrote.
21        sites_written: Vec<NodeSiteId>,
22    },
23
24    /// An Op suspended on a `CommandId` awaiting completion.
25    AsyncSuspended {
26        /// The suspended Op.
27        op_ref: OpRef,
28        /// The execution it belonged to.
29        exec_id: ExecId,
30        /// The CommandId the Op returned.
31        cmd_id: CommandId,
32    },
33
34    /// An outbound envelope is ready to ship.
35    SendEnvelope(WireEnvelope),
36
37    /// An app-facing event was published. Carries the topic name
38    /// (the `function.output` value name, or the `AppEmit` topic for
39    /// mid-cycle emissions) plus the serialized value bytes. When the
40    /// emitter is a `Notify`-style call with no payload, `value_bytes`
41    /// is empty.
42    AppEvent {
43        /// Module that emitted the event.
44        module_name: String,
45        /// Topic name - typically a `function.output` name for
46        /// top-level surface, or an explicit topic for `AppEmit`.
47        topic: String,
48        /// Serialized payload - the slot value at the emission site,
49        /// encoded via the type's `WireType::to_wire_bytes` path.
50        /// Empty for marker-only notifications.
51        value_bytes: Vec<u8>,
52    },
53
54    /// A lifecycle phase fired. fills in the per-phase
55    /// payload.
56    LifecycleFired {
57        /// Phase name (e.g. `"Bootstrap"`, `"PreShutdown"`).
58        phase: String,
59    },
60
61    /// The single bootstrap FunctionCall the engine seeded at install
62    /// completion drained to quiescence. Body ops fire on the same
63    /// poll cycle once this step is emitted. Bootstrap is a one-shot
64    /// per Node lifetime; a restored Node does not re-emit (its
65    /// bootstrap pass already ran pre-snapshot).
66    BootstrapComplete,
67
68    /// At least one bootstrap-phase op returned `DispatchResult::Async`
69    /// and the engine is waiting on its completion before activating
70    /// body ops. The host drives the completion via the ingress and
71    /// re-invokes `Node::run_bootstrap`.
72    WaitingOnBootstrap,
73
74    /// An Op failed. The error is also published on the bus as
75    /// `InfraEvent::OpFailure`.
76    OpFailed {
77        /// The failed Op.
78        op_ref: OpRef,
79        /// The execution it belonged to.
80        exec_id: ExecId,
81        /// The failure detail.
82        error: OpError,
83    },
84
85    /// `cycle_op_budget` was hit during a `poll()`. The engine
86    /// yielded mid-cascade; the host should poll again to drain
87    /// the remaining frontier. Emitted at most once per poll.
88    CycleBudgetExceeded {
89        /// Number of op-invocations the cycle issued before
90        /// yielding (== `cycle_op_budget`).
91        ops_invoked: usize,
92    },
93
94    /// `max_outbound_queue` was hit since the previous poll;
95    /// `count` envelopes were FIFO-dropped to make room. Emitted
96    /// at most once per poll.
97    OutboundDropped {
98        /// Number of envelopes dropped since the last poll.
99        count: usize,
100    },
101
102    /// An inbound wire envelope's payload could not be decoded.
103    /// The envelope's slot fill was dropped; this step lets the
104    /// host observe the drop. Carries the same context as the
105    /// matching `InfraEvent::WireDecodeFailure` on the bus.
106    WireDecodeFailed {
107        /// Wire-type hash the envelope advertised (0 if the
108        /// failure occurred before the hash could be read).
109        hash: u64,
110        /// Length of the offending payload, in bytes.
111        payload_size: usize,
112        /// Human-readable failure detail.
113        detail: String,
114    },
115
116    /// An inbound wire fill failed the typed-decode step. Mirrors
117    /// the bus's [`crate::bus::InfraEvent::WireReceiveError`] so
118    /// the host poll() caller observes the per-fill failure
119    /// without subscribing to the bus. Other fills in the same
120    /// envelope still deliver (partial-delivery semantics).
121    WireReceiveFailed {
122        /// Sender of the failing envelope, if known.
123        src_peer: Option<PeerId>,
124        /// Position of the failing fill within the envelope
125        /// (0-based).
126        fill_index: u32,
127        /// The `type_hash` the sender stamped on the fill.
128        actual_hash: u64,
129        /// Length of the offending payload, in bytes.
130        payload_size: usize,
131        /// Which failure mode fired.
132        kind: WireReceiveErrorKind,
133    },
134
135    /// A registered in-flight request entry was evicted by the
136    /// engine's per-poll `RequestTracker::drain_stale` sweep because
137    /// its per-entry TTL elapsed without a matching response. The
138    /// originator's local DAG continuation parked behind
139    /// `parked_op` (if `Some`) is failed with "chain timeout" via
140    /// the same path async-suspension completions take.
141    WireTimeout {
142        /// The chain correlation token that timed out.
143        wire_req_id: u64,
144        /// Destination site the request was dispatched to.
145        target_site: crate::ids::NodeSiteId,
146        /// Engine-clock timestamp when the originating Send fired.
147        started_at_ns: u64,
148        /// `CommandId` of the originator's parked local op, if the
149        /// request was registered with one.
150        parked_op: Option<crate::ids::CommandId>,
151    },
152
153    /// An inbound envelope from `peer` was dropped by the
154    /// [`crate::framework::PeerGovernor`] before any slot was
155    /// written. path -
156    /// the "first contact with IP" check the user flagged.
157    PeerBlocked {
158        /// The peer whose envelope was rejected.
159        peer: PeerId,
160        /// Why the envelope was rejected.
161        reason: BlockReason,
162    },
163
164    /// A peer crossed below the failure threshold and is now
165    /// marked down. Emitted at most once per transition.
166    PeerDown {
167        /// The peer that went down.
168        peer: PeerId,
169    },
170
171    /// A peer recovered after a failure streak.
172    PeerUp {
173        /// The peer that came back up.
174        peer: PeerId,
175    },
176
177    /// `wire::Send` could not resolve its destination peer's
178    /// addresses against the framework's
179    /// [`crate::framework::AddressBook`]. Either the peer is
180    /// unknown, its address list is empty, or the Send op's `peer`
181    /// input didn't carry a valid `PeerId`. The Send op produces
182    /// no envelope; the host application reacts via this event.
183    /// Mirrors `InfraEvent::PeerResolveFailure` on the bus -
184    /// telemetry-tap parity with the     /// `PeerBlocked`/`PeerDown`/`PeerUp` family.
185    PeerResolveFailed {
186        /// The peer whose addresses could not be resolved. `None`
187        /// when the Send op had no parseable `peer` input.
188        peer: Option<PeerId>,
189        /// The Send op that failed to resolve.
190        op_ref: OpRef,
191        /// Execution this Send belonged to.
192        exec_id: ExecId,
193    },
194}