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}