Skip to main content

aion_server/worker/
liminal_task_delivery.rs

1//! The liminal arm of the delivery seam: push the dispatch out on the worker's
2//! existing connection and block for its correlated reply.
3//!
4//! # Why this arm is the one that needed a seam
5//!
6//! Before #52 there was no way to reach it from the worker dispatcher at all: a
7//! worker with no gRPC stream sender was deregistered on the premise that its
8//! presence could only be a leak. This module is the same delivery sequence the
9//! outbox arm has always run, lifted so both callers reach one implementation.
10//!
11//! # What it does NOT own any more
12//!
13//! Two responsibilities deliberately stayed with the caller, and moving them
14//! here would break the invariants they exist to keep:
15//!
16//! - **The completion token.** One per pass, minted before the candidate walk
17//!   and revoked once when the pass places nothing. Minting per delivery would
18//!   put a second authorization beside the one a worker may still be holding for
19//!   the same attempt.
20//! - **The abandonment condition.** See
21//!   [`DeliveryIntent`](super::delivery_intent::DeliveryIntent): one of its terms
22//!   is unanswerable from inside a transport.
23
24use std::sync::Arc;
25
26use async_trait::async_trait;
27
28use aion_core::{ActivityId, RunId, WorkflowId};
29use aion_proto::ProtoActivityTask;
30
31use super::delivery_intent::SharedDeliveryIntent;
32use super::intervention::{AttemptKey, AttemptOwnerIndex};
33use super::liminal_transport::{AttemptOwnerGuard, DispatchRequest, LiminalCompletionSource};
34use super::registry::{WorkerDelivery, WorkerHandle};
35use super::task_delivery::{DeliveryAccepted, LivenessTracking, TaskDelivery, WorkerTaskDelivery};
36
37/// Delivers by pushing the dispatch out on the worker's liminal connection and
38/// blocking for the correlated reply.
39///
40/// The reply **is** the activity's completion, so this arm re-enters it through
41/// the same completion path the gRPC transport's out-of-band completion uses.
42pub struct LiminalTaskDelivery {
43    completion: Arc<LiminalCompletionSource>,
44    /// NOI-6 `attempt -> owning-worker` back-index. `None` (every non-agent
45    /// deployment) skips the binding, exactly as the outbox arm does —
46    /// intervention is then simply never offered.
47    attempt_owners: Option<AttemptOwnerIndex>,
48    /// The liveness tracker and registry this arm retires a completed dispatch
49    /// from, when the dispatcher above it tracks its dispatches.
50    ///
51    /// The liminal delivery is the ONLY seam that can do this. Unlike gRPC —
52    /// where the frame is queued, `Delivered` is returned, and the result comes
53    /// back later on the worker's stream for the session loop to untrack — this
54    /// arm waits for the reply inline and routes the completion itself. There is
55    /// no later moment and no other holder: if this does not retire the entry,
56    /// nothing does, and the dispatch stays counted against the worker's
57    /// capacity for the life of the registration.
58    ///
59    /// `None` where the dispatcher above tracks nothing, which is every
60    /// in-process façade and any test whose subject is not capacity.
61    completion_tracking: Option<CompletionTracking>,
62}
63
64/// What the liminal arm needs to retire a completed dispatch from the liveness
65/// tracker: the tracker itself and the registry whose capacity count it feeds.
66#[derive(Clone)]
67struct CompletionTracking {
68    heartbeat_tracker: crate::worker::heartbeat::HeartbeatTracker,
69    registry: crate::worker::ConnectedWorkerRegistry,
70}
71
72impl std::fmt::Debug for LiminalTaskDelivery {
73    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        formatter
75            .debug_struct("LiminalTaskDelivery")
76            .field("attempt_owners", &self.attempt_owners.is_some())
77            .finish_non_exhaustive()
78    }
79}
80
81impl LiminalTaskDelivery {
82    /// Build the liminal delivery arm over the completion sink both transports
83    /// share.
84    #[must_use]
85    pub fn new(completion: Arc<LiminalCompletionSource>) -> Self {
86        Self {
87            completion,
88            attempt_owners: None,
89            completion_tracking: None,
90        }
91    }
92
93    /// Retire a completed dispatch from the liveness tracker when the reply
94    /// lands, so this transport's completion removes its tracked entry exactly
95    /// as the gRPC session loop's result arm does.
96    ///
97    /// Required wherever the dispatcher above this arm tracks its dispatches
98    /// (`ActivityDispatcher::with_heartbeat_tracker`). Without it the entry made
99    /// before the push is never removed on this transport, and the worker's
100    /// advertised capacity is consumed permanently, one slot per delivery.
101    #[must_use]
102    pub fn with_completion_tracking(
103        mut self,
104        heartbeat_tracker: crate::worker::heartbeat::HeartbeatTracker,
105        registry: crate::worker::ConnectedWorkerRegistry,
106    ) -> Self {
107        self.completion_tracking = Some(CompletionTracking {
108            heartbeat_tracker,
109            registry,
110        });
111        self
112    }
113
114    /// Install the NOI-6 attempt-owner back-index, so a dispatched attempt binds
115    /// its owning worker before the push and the intervention router can resolve
116    /// the current owner of a live attempt.
117    #[must_use]
118    pub fn with_attempt_owners(mut self, attempt_owners: AttemptOwnerIndex) -> Self {
119        self.attempt_owners = Some(attempt_owners);
120        self
121    }
122}
123
124/// Untracks a liminal dispatch if this delivery is ABANDONED before its
125/// completion is routed.
126///
127/// The liminal arm waits for the worker's reply inline, so its future can be
128/// cancelled mid-wait — a caller that withdraws, or a dispatcher whose runtime is
129/// dropped out from under it during a failover. A cancelled future does not
130/// return, so neither the delivery's own completion path nor the dispatcher's
131/// non-`Delivered` arm ever runs, and the entry tracked before the push would
132/// simply stay: a slot the worker never gets back, on a registry that in a
133/// multi-server deployment can outlive the dispatcher that made the entry.
134///
135/// Disarmed the moment the completion has retired the entry itself, so the
136/// normal path does not untrack twice (it would be idempotent by key anyway —
137/// this only avoids the pointless second call).
138struct AbandonedDispatchGuard<'a> {
139    tracking: &'a CompletionTracking,
140    worker_id: crate::worker::WorkerId,
141    workflow_id: &'a WorkflowId,
142    activity_id: &'a ActivityId,
143    armed: bool,
144}
145
146impl AbandonedDispatchGuard<'_> {
147    /// The completion retired the entry; this guard has nothing left to do.
148    fn disarm(&mut self) {
149        self.armed = false;
150    }
151}
152
153impl Drop for AbandonedDispatchGuard<'_> {
154    fn drop(&mut self) {
155        if !self.armed {
156            return;
157        }
158        let retired = crate::worker::bridge::clear_completed_task_tracking(
159            &self.tracking.heartbeat_tracker,
160            &self.tracking.registry,
161            self.worker_id,
162            self.workflow_id,
163            self.activity_id,
164        );
165        if retired {
166            tracing::warn!(
167                worker_id = ?self.worker_id,
168                workflow_id = %self.workflow_id,
169                activity_id = %self.activity_id,
170                "liminal delivery was abandoned before the worker replied; its tracked slot has \
171                 been returned and the row re-drives"
172            );
173        }
174    }
175}
176
177/// The identity a delivery needs, resolved from the wire task **before** any
178/// owner binding is taken.
179///
180/// 🔴 This type exists to make the ordering structural rather than positional.
181/// The owner binding is keyed on the run, so a binding taken before the run is
182/// resolved would let an intervention aimed at one continue-as-new generation
183/// resolve another generation's worker.
184///
185/// # Exactly how strong this is, measured rather than asserted
186///
187/// The binding is taken by [`Resolved::bind_owner`], a method **on the resolved
188/// identity**, so it cannot be called before that identity exists: moving the
189/// call above the resolution does not compile. That is the mutation this guards
190/// against, and it has been run.
191///
192/// It is **not** proof against a deliberate bypass. [`AttemptKey::new`] is a
193/// public constructor over four plain components, so an edit that calls
194/// [`AttemptOwnerGuard::bind`] directly with a placeholder run compiles and
195/// binds. An earlier version of this comment claimed the key "can only be built
196/// from a `Resolved`", which was false — the mutation that proved it survived
197/// both this structure and the test below.
198///
199/// The bypass is also, for the same reason, invisible at runtime: the guard
200/// releases on drop, so a binding taken before a refusal is gone again before
201/// any caller can look. What defends the property is that the natural edit does
202/// not compile and the deliberate one is a different API call, which review can
203/// see.
204struct Resolved {
205    workflow_id: WorkflowId,
206    run_id: RunId,
207    activity_id: ActivityId,
208    attempt: u32,
209}
210
211impl Resolved {
212    /// Resolve the task's identity, refusing a task with no run.
213    ///
214    /// The refusal is the pre-existing one — an activity without a run cannot be
215    /// dispatched at all, because an unfenced external effect has no generation
216    /// to belong to.
217    fn from_task(task: &ProtoActivityTask) -> Result<Self, &'static str> {
218        let workflow_id = task
219            .workflow_id
220            .clone()
221            .ok_or("task carries no workflow id")?
222            .try_into()
223            .map_err(|_| "task carries a malformed workflow id")?;
224        let run_id: RunId = task
225            .run_id
226            .clone()
227            .ok_or("activity run id is missing; refusing unfenced external effect")?
228            .try_into()
229            .map_err(|_| "task carries a malformed run id")?;
230        // Infallible: `ProtoActivityId` is the sequence position, and
231        // `ActivityId` is a newtype over it. Only its ABSENCE can be refused.
232        let activity_id: ActivityId = task
233            .activity_id
234            .ok_or("task carries no activity id")?
235            .into();
236        Ok(Self {
237            workflow_id,
238            run_id,
239            activity_id,
240            attempt: task.attempt,
241        })
242    }
243
244    /// The intervention key for this attempt, carrying the **resolved** run.
245    fn attempt_key(&self) -> AttemptKey {
246        AttemptKey::new(
247            self.workflow_id.clone(),
248            self.run_id.clone(),
249            self.activity_id.clone(),
250            self.attempt,
251        )
252    }
253
254    /// Bind this attempt's owner, returning the guard that releases it.
255    ///
256    /// 🔴 A method on the **resolved** identity on purpose. The binding is keyed
257    /// on the run, so taking it before the run is resolved is the defect; making
258    /// it a method on `Resolved` means the call cannot be moved above the
259    /// resolution — there is no receiver for it there, and the lift does not
260    /// compile. See this type's docs for what that does and does not prove.
261    fn bind_owner(
262        &self,
263        owners: Option<&AttemptOwnerIndex>,
264        worker: super::registry::WorkerId,
265    ) -> Option<AttemptOwnerGuard> {
266        owners.map(|owners| AttemptOwnerGuard::bind(owners.clone(), self.attempt_key(), worker))
267    }
268
269    /// The outbox ordinal this activity id was derived from.
270    ///
271    /// [`ActivityId`] is a newtype over the scheduling sequence position, so
272    /// `from_sequence_position` is exactly invertible and the ordinal the wire
273    /// needs round-trips without the row being present.
274    fn ordinal(&self) -> u64 {
275        self.activity_id.sequence_position()
276    }
277}
278
279/// Build the liminal wire request from the already-built task.
280///
281/// Every field is carried from the task except `heartbeat_window_ms`, which the
282/// task does not have — see [`LivenessTracking`] for why that assignment is a named
283/// value rather than a bare zero.
284/// The [`TaskDelivery`] a typed liminal push or reply failure becomes, BY
285/// CLASS. An unservable frame (proved larger than the connection's whole
286/// outbound buffer) is the terminal [`Undeliverable::Unservable`]; every other
287/// failure stays the alive-worker [`Undeliverable::DeliveryFailed`] this arm
288/// has always reported. The class must survive this boundary: the candidate
289/// walk and the outbox dispatcher read the TYPE, and a refusal flattened to a
290/// string here was re-pushed once per attempt in the budget, recording one
291/// lease per push for a step that could never run.
292fn undelivered_by(error: &crate::error::ServerError) -> TaskDelivery {
293    if error.is_worker_dispatch_unservable() {
294        TaskDelivery::unservable(format!("liminal dispatch is unservable: {error}"))
295    } else {
296        TaskDelivery::failed(format!("liminal dispatch failed: {error}"))
297    }
298}
299
300fn request_for_task(task: &ProtoActivityTask, resolved: &Resolved) -> DispatchRequest {
301    DispatchRequest {
302        activity_type: task.activity_type.clone(),
303        workflow_id: resolved.workflow_id.clone(),
304        ordinal: resolved.ordinal(),
305        run_id: Some(resolved.run_id.clone()),
306        completion_token: task.completion_token.clone(),
307        idempotency_key: task.idempotency_key.clone(),
308        input: task
309            .input
310            .as_ref()
311            .map(|payload| payload.bytes.clone())
312            .unwrap_or_default(),
313        attempt: resolved.attempt,
314        labels: task
315            .labels
316            .iter()
317            .map(|(k, v)| (k.clone(), v.clone()))
318            .collect(),
319        heartbeat_window_ms: LivenessTracking::NotTrackedPerTask.heartbeat_window_ms(),
320    }
321}
322
323#[async_trait]
324impl WorkerTaskDelivery for LiminalTaskDelivery {
325    async fn deliver(
326        &self,
327        worker: &WorkerHandle,
328        task: &ProtoActivityTask,
329        intent: &SharedDeliveryIntent,
330        accepted: &dyn DeliveryAccepted,
331    ) -> TaskDelivery {
332        // Identity is resolved FIRST — before the transport is even consulted —
333        // and the binding below is a method on what this produces, so it cannot
334        // be moved above this line.
335        //
336        // Ahead of the transport check deliberately. A task with no run is
337        // malformed **regardless of which worker it was aimed at**, so refusing
338        // it with "not delivered over liminal" would name the wrong defect and
339        // send an operator after the wrong remedy.
340        let resolved = match Resolved::from_task(task) {
341            Ok(resolved) => resolved,
342            Err(reason) => return TaskDelivery::failed(reason),
343        };
344
345        let delivery = match worker.delivery() {
346            WorkerDelivery::Liminal(delivery) => delivery.clone(),
347            WorkerDelivery::Grpc(_) => {
348                // A caller chose the wrong transport for the worker it selected.
349                // The worker is alive and correctly registered, so its
350                // registration stands — this is #52's defect refused at its
351                // mirror site.
352                return TaskDelivery::failed(
353                    "selected worker is not delivered over liminal; the liminal transport cannot \
354                     reach it",
355                );
356            }
357        };
358
359        // NOI-6: bind this attempt's owner BEFORE the push, so an intervention
360        // that races the dispatch resolves the worker. The guard releases on
361        // every exit path (reply, error, panic) so the index never keeps a
362        // finished attempt.
363        let _owner_guard = resolved.bind_owner(self.attempt_owners.as_ref(), worker.id());
364
365        let request = request_for_task(task, &resolved);
366        // The push and the wait are two steps on purpose: the moment the push
367        // is acknowledged the worker HOLDS the attempt, and the lease is
368        // recorded THERE — the reply this arm then waits for IS the completion,
369        // so a lease recorded after the wait would land after the terminal it
370        // exists to precede (WA-010 R3). Both steps are blocking, thread-based
371        // liminal calls; each runs off the async runtime so a long-running
372        // activity cannot starve a runtime worker. The caller's intent is
373        // re-asked at every poll boundary of the wait, which is why it arrives
374        // behind an `Arc`.
375        // ARMED BEFORE THE PUSH. The tracker entry already exists (the caller
376        // tracked before handing the task here), so its cleanup obligation
377        // exists from this line — and the push itself is the FIRST await this
378        // future can be cancelled at. A cancelled future runs no arm of any
379        // match below it, so a guard armed after the push left exactly one
380        // uncovered window: a cancellation while the push was in flight
381        // leaked the tracked entry. The push-failure returns disarm explicitly:
382        // nothing was delivered, and the caller's non-`Delivered` arm untracks.
383        // The reply-wait `None`/`Err`, the join error, and the completion-record
384        // failure do NOT disarm — on those the guard fires on drop AND the
385        // caller untracks, and that double untrack is safe only because
386        // `TaskTracker::complete_task` frees a slot solely for the removal
387        // that found the entry (`was_tracked`); the second finds nothing and
388        // decrements nothing. That idempotence is load-bearing here: an
389        // unconditional decrement in `complete_task` would re-open an
390        // over-push through this path.
391        let mut abandoned =
392            self.completion_tracking
393                .as_ref()
394                .map(|tracking| AbandonedDispatchGuard {
395                    tracking,
396                    worker_id: worker.id(),
397                    workflow_id: &resolved.workflow_id,
398                    activity_id: &resolved.activity_id,
399                    armed: true,
400                });
401        let push_delivery = delivery.clone();
402        let pushed =
403            tokio::task::spawn_blocking(move || push_delivery.push_dispatch(&request)).await;
404        let awaiter = match pushed {
405            Ok(Ok(awaiter)) => awaiter,
406            Ok(Err(error)) => {
407                if let Some(guard) = abandoned.as_mut() {
408                    guard.disarm();
409                }
410                return undelivered_by(&error);
411            }
412            Err(error) => {
413                if let Some(guard) = abandoned.as_mut() {
414                    guard.disarm();
415                }
416                return TaskDelivery::failed(format!("dispatch task join failed: {error}"));
417            }
418        };
419        accepted.accepted().await;
420        let waiting_intent = Arc::clone(intent);
421        let dispatched = tokio::task::spawn_blocking(move || {
422            super::liminal_transport::receive_bridge_reply(&awaiter, || {
423                waiting_intent.still_wanted()
424            })
425        })
426        .await;
427
428        let response = match dispatched {
429            Ok(Ok(Some(response))) => response,
430            Ok(Ok(None)) => {
431                // The caller withdrew while the delivery waited. The worker is
432                // alive and correctly registered; only this pass gave up, and a
433                // late reply is discarded by the fences.
434                return TaskDelivery::failed("delivery wait abandoned before worker reply");
435            }
436            Ok(Err(error)) => return undelivered_by(&error),
437            Err(error) => {
438                return TaskDelivery::failed(format!("dispatch task join failed: {error}"));
439            }
440        };
441
442        // Re-enter the worker's result through the SAME completion path the gRPC
443        // transport uses; terminal dedup applies unchanged.
444        //
445        // 🔴 A failure HERE is the one outcome whose name reads narrower than its
446        // meaning: the worker took the task, executed it, and replied — and the
447        // reply did not record. `Delivered` would be wrong (the caller would
448        // settle the work without its result) and `WorkerUnreachable` would be
449        // wrong twice over (the worker is demonstrably alive, and deregistering
450        // it would destroy a healthy registration). `DeliveryFailed` carries the
451        // right obligations — registration stands, the caller withdraws its
452        // token — which is what the type encodes and what the outbox arm has
453        // always done here.
454        if let Err(error) = self.completion.deliver(&response) {
455            return TaskDelivery::failed(format!(
456                "worker replied but the completion could not be recorded: {error}"
457            ));
458        }
459        // THE COMPLETION RETIRES THE TRACKED ENTRY, here and nowhere else.
460        //
461        // This transport routes the completion itself, inline, before it returns
462        // — so unlike gRPC there is no later stream frame for a session loop to
463        // untrack on. The dispatcher above tracks before the push (the bridge's
464        // `track_then_send` ordering); this is the matching half. Skipping it
465        // leaves a phantom entry that nothing nominates, nothing logs, and
466        // nothing removes: the worker's capacity shrinks by one per delivery and
467        // `in_flight_count` never returns to zero.
468        //
469        // Idempotent by key — `clear_completed_task_tracking` reports whether it
470        // actually retired anything — so if some other holder ever sees the same
471        // completion this is a no-op rather than a second decrement.
472        if let Some(tracking) = &self.completion_tracking {
473            let _ = crate::worker::bridge::clear_completed_task_tracking(
474                &tracking.heartbeat_tracker,
475                &tracking.registry,
476                worker.id(),
477                &resolved.workflow_id,
478                &resolved.activity_id,
479            );
480        }
481        if let Some(guard) = abandoned.as_mut() {
482            guard.disarm();
483        }
484        TaskDelivery::Delivered
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use crate::worker::registry::RegistrationOptions;
491
492    /// A delivery that records whether its accept point was reached, so a
493    /// refusal can be pinned as never having handed the attempt over.
494    #[derive(Default)]
495    struct NeverAccepted {
496        reached: std::sync::atomic::AtomicBool,
497    }
498
499    #[async_trait::async_trait]
500    impl super::DeliveryAccepted for NeverAccepted {
501        async fn accepted(&self) {
502            self.reached
503                .store(true, std::sync::atomic::Ordering::SeqCst);
504        }
505    }
506    use std::sync::Arc;
507
508    use aion_core::{ActivityId, InterventionCapabilities, RunId, WorkflowId};
509    use aion_proto::{ProtoActivityId, ProtoActivityTask, ProtoWorkflowId};
510    use uuid::Uuid;
511
512    use crate::error::ServerError;
513    use crate::worker::bridge::OutboxDeliveryCallback;
514    use crate::worker::delivery_intent::{AlwaysWanted, SharedDeliveryIntent};
515    use crate::worker::intervention::AttemptOwnerIndex;
516    use crate::worker::liminal_transport::LiminalCompletionSource;
517    use crate::worker::registry::{ConnectedWorkerRegistry, WorkerDelivery};
518    use crate::worker::task_delivery::{TaskDelivery, WorkerTaskDelivery};
519
520    use super::{LiminalTaskDelivery, Resolved};
521
522    /// The completion sink is never reached by these tests — every one of them
523    /// refuses before a worker is pushed to — so both methods report "no live
524    /// run" if they are ever called, rather than pretending to succeed.
525    struct NoopCallback;
526
527    impl OutboxDeliveryCallback for NoopCallback {
528        fn deliver_completion(
529            &self,
530            _workflow_id: &WorkflowId,
531            _activity_id: &ActivityId,
532            _run_id: Option<&RunId>,
533            _result: String,
534        ) -> Result<bool, ServerError> {
535            Ok(false)
536        }
537        fn deliver_failure(
538            &self,
539            _workflow_id: &WorkflowId,
540            _activity_id: &ActivityId,
541            _run_id: Option<&RunId>,
542            _reason: String,
543        ) -> Result<bool, ServerError> {
544            Ok(false)
545        }
546    }
547
548    const WORKFLOW: u128 = 0x51;
549
550    /// A task carrying everything a delivery needs EXCEPT a run id.
551    fn task_without_a_run() -> ProtoActivityTask {
552        ProtoActivityTask {
553            workflow_id: Some(ProtoWorkflowId::from(WorkflowId::new(Uuid::from_u128(
554                WORKFLOW,
555            )))),
556            activity_id: Some(ProtoActivityId::from(ActivityId::from_sequence_position(3))),
557            activity_type: String::from("agent"),
558            input: None,
559            attempt: 1,
560            labels: std::collections::HashMap::new(),
561            // The whole point of this fixture.
562            run_id: None,
563            completion_token: String::from("token"),
564            idempotency_key: String::from("key"),
565        }
566    }
567
568    fn liminal_delivery(owners: &AttemptOwnerIndex) -> LiminalTaskDelivery {
569        LiminalTaskDelivery::new(Arc::new(LiminalCompletionSource::new(Arc::new(
570            NoopCallback,
571        ))))
572        .with_attempt_owners(owners.clone())
573    }
574
575    /// 🔴 NAMED PROPERTY: the run is resolved BEFORE any attempt owner is bound.
576    ///
577    /// # What breaks without it
578    ///
579    /// The owner binding is keyed on the run. A binding taken before the run is
580    /// resolved would let an intervention aimed at one continue-as-new
581    /// generation resolve a DIFFERENT generation's worker — an operator
582    /// stopping run A reaching the worker executing run B.
583    ///
584    /// # 🔴 What this test does and does NOT witness — measured by mutation
585    ///
586    /// It witnesses the **refusal**: a run-less task is rejected, is diagnosed
587    /// by its missing run rather than by the transport, does not deregister the
588    /// worker, and leaves no owner binding behind.
589    ///
590    /// It does **not** witness the ordering, and an earlier version of this
591    /// comment claimed it did. The mutation — lifting the bind above the
592    /// resolution with a placeholder run — was run, and **this test stayed
593    /// green**, because [`AttemptOwnerGuard`] releases on drop: a binding taken
594    /// before a refusal is already gone by the time any caller can look. There
595    /// is no observation window on this path, and there cannot be one.
596    ///
597    /// The ordering is defended structurally instead: the bind is a method on
598    /// [`Resolved`], so the natural lift does not compile. That claim was also
599    /// checked by mutation rather than asserted — see [`Resolved`] for exactly
600    /// how far it goes, including the bypass it does not stop.
601    #[tokio::test]
602    async fn run_resolution_precedes_attempt_owner_binding()
603    -> Result<(), Box<dyn std::error::Error>> {
604        let owners = AttemptOwnerIndex::new();
605        let delivery = liminal_delivery(&owners);
606
607        // A real registration yields a real WorkerId; no id here is fabricated.
608        let registry = ConnectedWorkerRegistry::default();
609        let (sender, _receiver) = tokio::sync::mpsc::channel(1);
610        let types = [String::from("agent")];
611        let registration = registry.register_delivery(
612            [String::from("default")],
613            String::from("default"),
614            None,
615            types.iter(),
616            WorkerDelivery::Grpc(sender),
617            RegistrationOptions::identified(
618                "agent-1",
619                crate::worker::UNBOUNDED_SENDER_WORKER_CONCURRENCY,
620            )
621            .with_intervention_capabilities(InterventionCapabilities::none()),
622        )?;
623        let worker_id = registration
624            .worker_id()
625            .ok_or("a registration must assign a worker id")?;
626        let worker = registry
627            .worker_by_id(worker_id)?
628            .ok_or("the worker just registered must be readable")?;
629
630        let intent: SharedDeliveryIntent = Arc::new(AlwaysWanted);
631        let accepted = NeverAccepted::default();
632        let outcome = delivery
633            .deliver(&worker, &task_without_a_run(), &intent, &accepted)
634            .await;
635        assert!(
636            !accepted.reached.load(std::sync::atomic::Ordering::SeqCst),
637            "a task refused by its missing run must never reach the accept point"
638        );
639
640        match outcome {
641            TaskDelivery::Delivered => {
642                return Err("a task with no run must never be delivered".into());
643            }
644            TaskDelivery::Undeliverable(undeliverable) => {
645                // The refusal names the RUN, not the transport: identity is
646                // resolved before the transport is consulted, so a run-less task
647                // is diagnosed as run-less whichever worker it was aimed at.
648                assert!(
649                    undeliverable.reason().contains("run id is missing"),
650                    "a run-less task must be refused BY ITS MISSING RUN, not by the transport; \
651                     got: {}",
652                    undeliverable.reason()
653                );
654                assert!(
655                    !undeliverable.deregisters_worker(),
656                    "a malformed task is not evidence that the worker is gone"
657                );
658            }
659        }
660
661        // 🔴 THE PROPERTY, as far as this test can witness it: nothing is bound
662        // when the run refuses. Note what this does NOT say — an earlier
663        // version of this comment claimed nothing COULD be bound because an
664        // `AttemptKey` is only constructible from a resolved identity, and that
665        // was false. `AttemptKey::new` is public over four plain components.
666        // See this test's docs and [`Resolved`] for what actually holds.
667        let bound = owners.attempts_for_workflow(&WorkflowId::new(Uuid::from_u128(WORKFLOW)));
668        assert!(
669            bound.is_empty(),
670            "the attempt-owner index must be untouched when the run refused; a binding here \
671             means the bind was lifted above the run resolution, and an intervention could \
672             resolve the wrong generation's worker. Found: {bound:?}"
673        );
674        Ok(())
675    }
676
677    /// A task carrying everything a delivery needs, run included.
678    fn task_with_a_run() -> ProtoActivityTask {
679        ProtoActivityTask {
680            run_id: Some(aion_proto::ProtoRunId::from(RunId::new(Uuid::from_u128(
681                0x52,
682            )))),
683            ..task_without_a_run()
684        }
685    }
686
687    /// 🔴 NAMED PROPERTY: the abandonment guard is armed BEFORE the push, so a
688    /// delivery cancelled while its push is still in flight returns the slot
689    /// the caller tracked for it.
690    ///
691    /// # What breaks without it
692    ///
693    /// A guard constructed after the push leaves one uncovered window — the
694    /// push's own `await`, the FIRST point this future can be cancelled at. A
695    /// dispatcher that withdraws there (a shutdown, a caller that stops
696    /// wanting the work) runs no arm below the push, so nothing untracks the
697    /// entry made before it: the worker's advertised capacity is down one slot
698    /// permanently, on a registry that in a multi-server deployment outlives
699    /// the dispatcher that made the entry.
700    ///
701    /// # How the window is held open, deterministically
702    ///
703    /// The push runs on `spawn_blocking`. The runtime here has a blocking pool
704    /// of exactly ONE thread, and that thread is occupied by a closure parked
705    /// on a gate the test holds shut — so the push's blocking task is provably
706    /// still queued, not merely likely to be, when the delivery future is
707    /// polled once and dropped. The worker is a liminal handle over a
708    /// supervisor with no connection behind it: had the push ever run it
709    /// would have FAILED, and the failure arm disarms the guard and leaves the
710    /// slot to the caller — which is exactly why the push must not be allowed
711    /// to run for this test to say anything. The gate is opened afterwards so
712    /// the runtime shuts down cleanly.
713    ///
714    /// Measured by mutation: moving the guard's construction below the push
715    /// (a full revert of the fix) leaves the slot held and this test red.
716    #[test]
717    fn a_delivery_abandoned_during_its_push_returns_the_tracked_slot()
718    -> Result<(), Box<dyn std::error::Error>> {
719        use std::time::{Duration, Instant};
720
721        use crate::worker::envelope::CompletionToken;
722        use crate::worker::heartbeat::{HeartbeatTracker, InFlightActivity};
723        use crate::worker::liminal_transport::LiminalWorkerDelivery;
724
725        let runtime = tokio::runtime::Builder::new_current_thread()
726            .enable_all()
727            .max_blocking_threads(1)
728            .build()?;
729        runtime.block_on(async {
730            // Occupy the only blocking thread until the gate opens.
731            let (open_gate, gate) = std::sync::mpsc::channel::<()>();
732            let occupant = tokio::task::spawn_blocking(move || gate.recv());
733
734            let registry = ConnectedWorkerRegistry::default();
735            let tracker = HeartbeatTracker::new(Duration::from_secs(30));
736            let supervisor = liminal_server::server::connection::ConnectionSupervisor::new()?;
737            let types = [String::from("agent")];
738            let registration = registry.register_delivery(
739                [String::from("default")],
740                String::from("default"),
741                None,
742                types.iter(),
743                WorkerDelivery::Liminal(LiminalWorkerDelivery::new(supervisor, 7)),
744                RegistrationOptions::identified("agent-1", 1)
745                    .with_intervention_capabilities(InterventionCapabilities::none()),
746            )?;
747            let worker_id = registration
748                .worker_id()
749                .ok_or("a registration must assign a worker id")?;
750            let worker = registry
751                .worker_by_id(worker_id)?
752                .ok_or("the worker just registered must be readable")?;
753
754            // The caller's half of the contract: tracked BEFORE the delivery is
755            // handed the task, holding the worker's one slot.
756            let workflow_id = WorkflowId::new(Uuid::from_u128(WORKFLOW));
757            let activity_id = ActivityId::from_sequence_position(3);
758            tracker.track_task(
759                worker_id,
760                InFlightActivity {
761                    workflow_id: workflow_id.clone(),
762                    activity_id: activity_id.clone(),
763                    attempt: 1,
764                    completion_token: CompletionToken::for_test(),
765                },
766                Instant::now(),
767                &registry,
768                None,
769            )?;
770            assert_eq!(
771                registry.in_flight_for_worker(worker_id)?,
772                1,
773                "precondition: the tracked dispatch holds the worker's slot"
774            );
775
776            let owners = AttemptOwnerIndex::new();
777            let delivery = liminal_delivery(&owners)
778                .with_completion_tracking(tracker.clone(), registry.clone());
779            let intent: SharedDeliveryIntent = Arc::new(AlwaysWanted);
780            let accepted = NeverAccepted::default();
781            let task = task_with_a_run();
782            {
783                // Owned on the heap so the `drop` below is the real one: a
784                // stack-pinned future is only released at the end of its scope.
785                let mut future = Box::pin(delivery.deliver(&worker, &task, &intent, &accepted));
786                let first_poll = futures::poll!(future.as_mut());
787                assert!(
788                    first_poll.is_pending(),
789                    "the push is queued behind the occupied blocking thread, so the first poll \
790                     must suspend AT the push; a ready outcome means the window this test \
791                     holds open was not held"
792                );
793                // The dispatcher withdraws while the push is in flight.
794                drop(future);
795            }
796            assert!(
797                !accepted.reached.load(std::sync::atomic::Ordering::SeqCst),
798                "a delivery abandoned during its push never reached the accept point"
799            );
800
801            // 🔴 THE PROPERTY: the slot is back, and the entry is gone.
802            assert_eq!(
803                registry.in_flight_for_worker(worker_id)?,
804                0,
805                "a delivery abandoned during its push must return the tracked slot; a slot \
806                 still held means the guard was armed AFTER the push and the cancellation \
807                 window is open again"
808            );
809            assert!(
810                !tracker.complete_task(worker_id, &workflow_id, &activity_id, &registry)?,
811                "the guard retired the entry itself; the caller's later untrack must find \
812                 nothing left to retire"
813            );
814
815            // Let the queued push run to its (failing) end and the occupant exit,
816            // so runtime shutdown does not wait on a parked blocking thread.
817            open_gate.send(())?;
818            occupant.await??;
819            Ok::<(), Box<dyn std::error::Error>>(())
820        })
821    }
822
823    /// The property's SECOND witness, and the one that outlives a refactor of
824    /// the call site: identity resolution refuses a run-less task, so no
825    /// `Resolved` is produced — and [`Resolved::bind_owner`], the only binding
826    /// path the delivery takes, is a method on it.
827    ///
828    /// 🔴 That is a claim about the CALL SITE, not about the key type. An
829    /// earlier version said an `AttemptKey` "cannot be built without" a
830    /// `Resolved`; it can, and the mutation that proved it survived. What this
831    /// witnesses is that resolution refuses first.
832    ///
833    /// Kept beside the behavioural test deliberately. The refusal and the
834    /// structure are different claims, and a change that removes one should
835    /// still meet the other.
836    #[test]
837    fn identity_resolution_refuses_a_task_with_no_run() -> Result<(), Box<dyn std::error::Error>> {
838        let Err(refusal) = Resolved::from_task(&task_without_a_run()) else {
839            return Err("a task with no run must not resolve".into());
840        };
841        assert!(
842            refusal.contains("run id is missing"),
843            "the refusal must name the run so an operator is sent to the right remedy: {refusal}"
844        );
845        Ok(())
846    }
847}