Skip to main content

gate4agent_handle/
provider_runtime.rs

1use gate4agent_kernel::{
2    BackendIngress, KernelProviderError, ProviderRuntimeCommandOutcome, ProviderRuntimeTransition,
3};
4use gate4agent_tool_protocol::{
5    CapabilityEffect, CapabilityEffectEnvelope, CapabilityObservation,
6    CapabilityObservationEnvelope, CapabilityRequestKey, CapabilityResult, InvocationCancelReason,
7    ObservationIgnoredReason, ProviderBindingId, ProviderBoundCapabilityEffectEnvelope,
8    ProviderRuntimeCommand, ProviderRuntimeEnvelope, ProviderRuntimeSnapshot, ResourceScopeId,
9    ToolCapabilityId, ToolFailure, ToolOperationId, ToolProviderId,
10};
11use gate4agent_types::{AgentInstanceId, SessionGeneration};
12use std::collections::BTreeMap;
13use std::fmt;
14use std::ops::AddAssign;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TryRecvError, TrySendError};
17use std::sync::{Arc, Mutex, Weak};
18use thiserror::Error;
19
20pub const MAX_PROVIDER_RUNTIMES: usize = 64;
21pub const MAX_PROVIDER_EFFECT_CAPACITY: usize = 1_024;
22
23#[derive(Clone)]
24/// In-process authority that reserves provider binding identities and attaches
25/// one local executor per provider.
26pub struct ProviderRuntimeAuthorityHandle {
27    state: Arc<ProviderAuthorityState>,
28}
29
30/// Non-cloneable receive side for one exact provider binding.
31pub struct ProviderRuntimeHandle {
32    state: Arc<ProviderBindingState>,
33    work_rx: Receiver<ProviderWork>,
34    observation_outcome_rx: Receiver<ProviderObservationOutcome>,
35}
36
37#[derive(Clone)]
38/// Cloneable completion sender for worker tasks spawned by one runtime.
39pub struct ProviderCompletionHandle {
40    state: Arc<ProviderBindingState>,
41}
42
43#[derive(Clone)]
44/// Local cancellation signal for either a whole binding or one invocation.
45///
46/// `is_cancelled` does not acknowledge physical subprocess or browser
47/// termination; concrete provider supervisors own that stronger contract.
48pub struct ProviderCancellationToken {
49    state: Weak<ProviderBindingState>,
50    operation: Option<Arc<ProviderOperationState>>,
51}
52
53/// Bounded executor work emitted after canonical kernel reduction.
54pub enum ProviderWork {
55    Invoke(ProviderInvocation),
56    Cancel(ProviderCancellation),
57}
58
59/// Opaque invocation ticket whose correlation identity cannot be rewritten by
60/// the provider adapter.
61pub struct ProviderInvocation {
62    state: Weak<ProviderBindingState>,
63    operation: Arc<ProviderOperationState>,
64    binding_id: ProviderBindingId,
65    effect: CapabilityEffectEnvelope,
66    submitted: bool,
67}
68
69/// Exact cancellation notice for an invocation previously issued by the same
70/// runtime binding.
71pub struct ProviderCancellation {
72    binding_id: ProviderBindingId,
73    effect: CapabilityEffectEnvelope,
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
77/// Correlated receipt for a provider observation already reduced by the
78/// canonical kernel.
79pub struct ProviderObservationOutcome {
80    pub sequence: u64,
81    pub operation_id: ToolOperationId,
82    pub request_key: CapabilityRequestKey,
83    pub status: ProviderObservationStatus,
84}
85
86#[derive(Clone, Debug, Eq, PartialEq)]
87/// Canonical disposition of a provider observation.
88pub enum ProviderObservationStatus {
89    Applied,
90    Ignored { reason: ObservationIgnoredReason },
91    Rejected(KernelProviderError),
92    ContractViolation,
93    RuntimeClosed,
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97/// Public lifecycle projection for one provider runtime binding.
98pub enum ProviderRuntimeState {
99    Attaching,
100    Active,
101    Closing,
102    Closed,
103}
104
105#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
106pub struct ProviderEffectPublishReport {
107    pub delivered: usize,
108    pub queue_full: usize,
109    pub disconnected: usize,
110    pub unbound: usize,
111}
112
113#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
114pub enum ProviderRuntimeError {
115    #[error("provider runtime registry is full")]
116    CapacityExceeded,
117    #[error("provider already has a runtime binding")]
118    AlreadyBound,
119    #[error("provider runtime counter '{counter}' is exhausted")]
120    CounterExhausted { counter: &'static str },
121    #[error("invocation belongs to another provider runtime binding")]
122    ForeignInvocation,
123    #[error("provider runtime binding is not active")]
124    Inactive,
125    #[error("provider invocation already submitted a terminal observation")]
126    AlreadyCompleted,
127    #[error("provider observation is invalid")]
128    InvalidObservation,
129    #[error("provider runtime ingress is full")]
130    Full,
131    #[error("provider runtime ingress is disconnected")]
132    Disconnected,
133}
134
135pub(crate) struct ProviderRuntimePort {
136    state: Arc<ProviderAuthorityState>,
137}
138
139struct ProviderAuthorityState {
140    ingress_tx: SyncSender<BackendIngress>,
141    inner: Mutex<ProviderAuthorityInner>,
142}
143
144struct ProviderAuthorityInner {
145    closed: bool,
146    next_sequence: u64,
147    sequence_exhausted: bool,
148    bindings: BTreeMap<ToolProviderId, Arc<ProviderBindingState>>,
149    foreign_bindings: BTreeMap<ToolProviderId, ProviderBindingId>,
150}
151
152struct ProviderBindingState {
153    provider_id: ToolProviderId,
154    binding_id: ProviderBindingId,
155    effect_capacity: usize,
156    authority: Weak<ProviderAuthorityState>,
157    lifecycle: Mutex<ProviderBindingLifecycle>,
158    work_tx: Mutex<Option<SyncSender<ProviderWork>>>,
159    observation_outcome_tx: Mutex<Option<SyncSender<ProviderObservationOutcome>>>,
160    pending_observations: Mutex<BTreeMap<u64, PendingObservation>>,
161    operations: Mutex<BTreeMap<ToolOperationId, Arc<ProviderOperationState>>>,
162    cancelled: AtomicBool,
163}
164
165struct ProviderOperationState {
166    request_key: CapabilityRequestKey,
167    instance_id: AgentInstanceId,
168    generation: SessionGeneration,
169    deadline_tick: u64,
170    cancelled: AtomicBool,
171    settled: AtomicBool,
172}
173
174struct PendingObservation {
175    operation_id: ToolOperationId,
176    request_key: CapabilityRequestKey,
177}
178
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180enum ProviderBindingLifecycle {
181    AttachSent { sequence: u64 },
182    Active,
183    Quiescing,
184    ClosingRetry { attach_sequence: Option<u64> },
185    DetachSent { sequence: u64 },
186    Closed,
187}
188
189impl ProviderOperationState {
190    fn from_effect(effect: &CapabilityEffectEnvelope) -> Self {
191        Self {
192            request_key: effect.request_key.clone(),
193            instance_id: effect.instance_id,
194            generation: effect.generation,
195            deadline_tick: effect.deadline_tick,
196            cancelled: AtomicBool::new(false),
197            settled: AtomicBool::new(false),
198        }
199    }
200
201    fn matches(&self, effect: &CapabilityEffectEnvelope) -> bool {
202        self.request_key == effect.request_key
203            && self.instance_id == effect.instance_id
204            && self.generation == effect.generation
205            && self.deadline_tick == effect.deadline_tick
206    }
207}
208
209pub(crate) fn provider_runtime(
210    ingress_tx: SyncSender<BackendIngress>,
211) -> (ProviderRuntimeAuthorityHandle, ProviderRuntimePort) {
212    let state = Arc::new(ProviderAuthorityState {
213        ingress_tx,
214        inner: Mutex::new(ProviderAuthorityInner {
215            closed: false,
216            next_sequence: 1,
217            sequence_exhausted: false,
218            bindings: BTreeMap::new(),
219            foreign_bindings: BTreeMap::new(),
220        }),
221    });
222    (
223        ProviderRuntimeAuthorityHandle {
224            state: Arc::clone(&state),
225        },
226        ProviderRuntimePort { state },
227    )
228}
229
230impl ProviderRuntimeAuthorityHandle {
231    pub fn bind_provider(
232        &self,
233        provider_id: ToolProviderId,
234        effect_capacity: usize,
235    ) -> Result<ProviderRuntimeHandle, ProviderRuntimeError> {
236        let mut authority = lock(&self.state.inner);
237        if authority.closed {
238            return Err(ProviderRuntimeError::Disconnected);
239        }
240        if authority.sequence_exhausted {
241            return Err(ProviderRuntimeError::CounterExhausted {
242                counter: "provider-runtime-sequence",
243            });
244        }
245        if authority.bindings.len() >= MAX_PROVIDER_RUNTIMES {
246            return Err(ProviderRuntimeError::CapacityExceeded);
247        }
248        if authority.bindings.contains_key(&provider_id)
249            || authority.foreign_bindings.contains_key(&provider_id)
250        {
251            return Err(ProviderRuntimeError::AlreadyBound);
252        }
253        let sequence = authority.next_sequence;
254        let binding_id = ProviderBindingId(sequence);
255        let effect_capacity = bounded_capacity(effect_capacity, MAX_PROVIDER_EFFECT_CAPACITY);
256        let (work_tx, work_rx) = sync_channel(effect_capacity);
257        let (observation_outcome_tx, observation_outcome_rx) = sync_channel(effect_capacity);
258        let state = Arc::new(ProviderBindingState {
259            provider_id: provider_id.clone(),
260            binding_id,
261            effect_capacity,
262            authority: Arc::downgrade(&self.state),
263            lifecycle: Mutex::new(ProviderBindingLifecycle::AttachSent { sequence }),
264            work_tx: Mutex::new(Some(work_tx)),
265            observation_outcome_tx: Mutex::new(Some(observation_outcome_tx)),
266            pending_observations: Mutex::new(BTreeMap::new()),
267            operations: Mutex::new(BTreeMap::new()),
268            cancelled: AtomicBool::new(false),
269        });
270        let envelope = ProviderRuntimeEnvelope {
271            sequence,
272            command: ProviderRuntimeCommand::Attach {
273                binding_id,
274                provider_id: provider_id.clone(),
275            },
276        };
277        if let Err(error) = send_ingress(&self.state.ingress_tx, envelope) {
278            if error == ProviderRuntimeError::Disconnected {
279                let bindings = close_authority_locked(&mut authority);
280                drop(authority);
281                retire_binding(&state);
282                for binding in bindings {
283                    retire_binding(&binding);
284                }
285            }
286            return Err(error);
287        }
288        commit_sequence(&mut authority, sequence);
289        authority.bindings.insert(provider_id, Arc::clone(&state));
290        Ok(ProviderRuntimeHandle {
291            state,
292            work_rx,
293            observation_outcome_rx,
294        })
295    }
296
297    pub fn active_binding_count(&self) -> usize {
298        lock(&self.state.inner)
299            .bindings
300            .values()
301            .filter(|state| matches!(*lock(&state.lifecycle), ProviderBindingLifecycle::Active))
302            .count()
303    }
304
305    pub fn sequence_exhausted(&self) -> bool {
306        lock(&self.state.inner).sequence_exhausted
307    }
308}
309
310impl ProviderRuntimeHandle {
311    pub fn provider_id(&self) -> &ToolProviderId {
312        &self.state.provider_id
313    }
314
315    pub fn binding_id(&self) -> ProviderBindingId {
316        self.state.binding_id
317    }
318
319    /// Maximum number of provider work items and unconsumed observation
320    /// outcomes admitted by this exact binding.
321    pub fn effect_capacity(&self) -> usize {
322        self.state.effect_capacity
323    }
324
325    pub fn state(&self) -> ProviderRuntimeState {
326        public_lifecycle(*lock(&self.state.lifecycle))
327    }
328
329    pub fn completion_handle(&self) -> ProviderCompletionHandle {
330        ProviderCompletionHandle {
331            state: Arc::clone(&self.state),
332        }
333    }
334
335    pub fn cancellation_token(&self) -> ProviderCancellationToken {
336        ProviderCancellationToken {
337            state: Arc::downgrade(&self.state),
338            operation: None,
339        }
340    }
341
342    pub fn try_recv(&self) -> Result<ProviderWork, TryRecvError> {
343        let lifecycle = lock(&self.state.lifecycle);
344        if !matches!(
345            *lifecycle,
346            ProviderBindingLifecycle::Active | ProviderBindingLifecycle::Quiescing
347        ) {
348            return Err(TryRecvError::Disconnected);
349        }
350        self.work_rx.try_recv()
351    }
352
353    pub fn try_recv_observation_outcome(&self) -> Result<ProviderObservationOutcome, TryRecvError> {
354        self.observation_outcome_rx.try_recv()
355    }
356
357    /// Hides this binding from new local client dispatch while preserving its
358    /// receive and completion lanes for already admitted work.
359    ///
360    /// A supervisor must retain every native owner until physical exit and
361    /// call [`ProviderRuntimeHandle::close`] only after teardown. Quiescing is
362    /// a local admission fence; canonical detach still flows through the
363    /// kernel.
364    pub fn begin_quiesce(&self) -> Result<(), ProviderRuntimeError> {
365        begin_quiesce(&self.state)
366    }
367
368    /// Fences work immediately and non-blockingly requests canonical detach.
369    /// A full ingress leaves the detach retryable in `Closing` state.
370    pub fn close(&self) -> Result<u64, ProviderRuntimeError> {
371        request_close(&self.state)
372    }
373
374    pub fn try_complete(
375        &self,
376        invocation: &mut ProviderInvocation,
377        observation: &CapabilityObservation,
378    ) -> Result<u64, ProviderRuntimeError> {
379        self.completion_handle()
380            .try_complete(invocation, observation)
381    }
382
383    pub fn try_succeed(
384        &self,
385        invocation: &mut ProviderInvocation,
386        result: &CapabilityResult,
387    ) -> Result<u64, ProviderRuntimeError> {
388        self.completion_handle().try_succeed(invocation, result)
389    }
390
391    pub fn try_fail(
392        &self,
393        invocation: &mut ProviderInvocation,
394        failure: &ToolFailure,
395    ) -> Result<u64, ProviderRuntimeError> {
396        self.completion_handle().try_fail(invocation, failure)
397    }
398}
399
400impl Drop for ProviderRuntimeHandle {
401    fn drop(&mut self) {
402        let _ = request_close(&self.state);
403    }
404}
405
406impl ProviderCompletionHandle {
407    /// Enqueues a terminal observation; the returned sequence is not an
408    /// acknowledgement. Read `ProviderObservationOutcome` for canonical
409    /// applied/ignored/rejected disposition.
410    pub fn try_complete(
411        &self,
412        invocation: &mut ProviderInvocation,
413        observation: &CapabilityObservation,
414    ) -> Result<u64, ProviderRuntimeError> {
415        if invocation.submitted {
416            return Err(ProviderRuntimeError::AlreadyCompleted);
417        }
418        observation
419            .validate()
420            .map_err(|_| ProviderRuntimeError::InvalidObservation)?;
421        let Some(invocation_state) = invocation.state.upgrade() else {
422            return Err(ProviderRuntimeError::Inactive);
423        };
424        if !Arc::ptr_eq(&self.state, &invocation_state)
425            || invocation.binding_id != self.state.binding_id
426        {
427            return Err(ProviderRuntimeError::ForeignInvocation);
428        }
429        let sequence =
430            submit_observation(&self.state, invocation.effect.clone(), observation.clone())?;
431        invocation.submitted = true;
432        Ok(sequence)
433    }
434
435    pub fn try_succeed(
436        &self,
437        invocation: &mut ProviderInvocation,
438        result: &CapabilityResult,
439    ) -> Result<u64, ProviderRuntimeError> {
440        self.try_complete(
441            invocation,
442            &CapabilityObservation::Succeeded {
443                result: result.clone(),
444            },
445        )
446    }
447
448    pub fn try_fail(
449        &self,
450        invocation: &mut ProviderInvocation,
451        failure: &ToolFailure,
452    ) -> Result<u64, ProviderRuntimeError> {
453        self.try_complete(
454            invocation,
455            &CapabilityObservation::Failed {
456                failure: failure.clone(),
457            },
458        )
459    }
460}
461
462impl ProviderInvocation {
463    pub fn binding_id(&self) -> ProviderBindingId {
464        self.binding_id
465    }
466
467    pub fn operation_id(&self) -> ToolOperationId {
468        self.effect.operation_id
469    }
470
471    pub fn request_key(&self) -> &CapabilityRequestKey {
472        &self.effect.request_key
473    }
474
475    pub fn capability_id(&self) -> &ToolCapabilityId {
476        match &self.effect.effect {
477            CapabilityEffect::Invoke { capability_id, .. } => capability_id,
478            CapabilityEffect::Cancel { .. } => unreachable!("invoke ticket contains invoke effect"),
479        }
480    }
481
482    pub fn deadline_tick(&self) -> u64 {
483        self.effect.deadline_tick
484    }
485
486    pub fn resource_scope_id(&self) -> &ResourceScopeId {
487        match &self.effect.effect {
488            CapabilityEffect::Invoke {
489                resource_scope_id, ..
490            } => resource_scope_id,
491            CapabilityEffect::Cancel { .. } => unreachable!("invoke ticket contains invoke effect"),
492        }
493    }
494
495    pub fn cancellation_token(&self) -> ProviderCancellationToken {
496        ProviderCancellationToken {
497            state: self.state.clone(),
498            operation: Some(Arc::clone(&self.operation)),
499        }
500    }
501
502    pub fn payload(&self) -> &[u8] {
503        match &self.effect.effect {
504            CapabilityEffect::Invoke { payload, .. } => payload,
505            CapabilityEffect::Cancel { .. } => unreachable!("invoke ticket contains invoke effect"),
506        }
507    }
508}
509
510impl ProviderCancellationToken {
511    pub fn is_cancelled(&self) -> bool {
512        self.operation
513            .as_ref()
514            .is_some_and(|operation| operation.cancelled.load(Ordering::Acquire))
515            || self
516                .state
517                .upgrade()
518                .is_none_or(|state| state.cancelled.load(Ordering::Acquire))
519    }
520}
521
522impl fmt::Debug for ProviderInvocation {
523    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
524        formatter
525            .debug_struct("ProviderInvocation")
526            .field("binding_id", &self.binding_id)
527            .field("effect", &self.effect)
528            .field("submitted", &self.submitted)
529            .finish()
530    }
531}
532
533impl ProviderCancellation {
534    pub fn binding_id(&self) -> ProviderBindingId {
535        self.binding_id
536    }
537
538    pub fn operation_id(&self) -> ToolOperationId {
539        self.effect.operation_id
540    }
541
542    pub fn request_key(&self) -> &CapabilityRequestKey {
543        &self.effect.request_key
544    }
545
546    pub fn reason(&self) -> InvocationCancelReason {
547        match self.effect.effect {
548            CapabilityEffect::Cancel { reason } => reason,
549            CapabilityEffect::Invoke { .. } => unreachable!("cancel ticket contains cancel effect"),
550        }
551    }
552}
553
554impl fmt::Debug for ProviderCancellation {
555    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
556        formatter
557            .debug_struct("ProviderCancellation")
558            .field("binding_id", &self.binding_id)
559            .field("effect", &self.effect)
560            .finish()
561    }
562}
563
564impl fmt::Debug for ProviderWork {
565    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
566        match self {
567            Self::Invoke(invocation) => invocation.fmt(formatter),
568            Self::Cancel(cancellation) => cancellation.fmt(formatter),
569        }
570    }
571}
572
573impl ProviderRuntimePort {
574    pub(crate) fn authority_handle(&self) -> ProviderRuntimeAuthorityHandle {
575        ProviderRuntimeAuthorityHandle {
576            state: Arc::clone(&self.state),
577        }
578    }
579
580    pub(crate) fn flush_closing_bindings(&self) {
581        let bindings = lock(&self.state.inner)
582            .bindings
583            .values()
584            .cloned()
585            .collect::<Vec<_>>();
586        for binding in bindings {
587            if matches!(
588                *lock(&binding.lifecycle),
589                ProviderBindingLifecycle::ClosingRetry { .. }
590            ) {
591                let _ = request_close(&binding);
592            }
593        }
594    }
595
596    pub(crate) fn publish_outcome(&self, outcome: &ProviderRuntimeCommandOutcome) {
597        let state = lock(&self.state.inner)
598            .bindings
599            .get(&outcome.provider_id)
600            .filter(|state| state.binding_id == outcome.binding_id)
601            .cloned();
602        let Some(state) = state else {
603            return;
604        };
605        publish_observation_outcome(&state, outcome);
606        let mut remove = false;
607        let mut lifecycle = lock(&state.lifecycle);
608        match (&*lifecycle, &outcome.result) {
609            (
610                ProviderBindingLifecycle::AttachSent { sequence },
611                Ok(ProviderRuntimeTransition::Attached),
612            ) if *sequence == outcome.sequence => {
613                *lifecycle = ProviderBindingLifecycle::Active;
614            }
615            (ProviderBindingLifecycle::AttachSent { sequence }, Err(_))
616                if *sequence == outcome.sequence =>
617            {
618                *lifecycle = ProviderBindingLifecycle::Closed;
619                remove = true;
620            }
621            (
622                ProviderBindingLifecycle::ClosingRetry { .. }
623                | ProviderBindingLifecycle::DetachSent { .. },
624                Err(_),
625            ) if outcome.sequence == state.binding_id.0 => {
626                // A handle may be dropped before its attach outcome is
627                // published. If attach failed, no detach can ever succeed.
628                *lifecycle = ProviderBindingLifecycle::Closed;
629                remove = true;
630            }
631            (
632                ProviderBindingLifecycle::DetachSent { sequence },
633                Ok(ProviderRuntimeTransition::Detached { .. }),
634            ) if *sequence == outcome.sequence => {
635                *lifecycle = ProviderBindingLifecycle::Closed;
636                remove = true;
637            }
638            (
639                ProviderBindingLifecycle::DetachSent { sequence },
640                Err(
641                    KernelProviderError::IntegrationBlocked
642                    | KernelProviderError::SequenceRegressed { .. },
643                ),
644            ) if *sequence == outcome.sequence => {
645                *lifecycle = ProviderBindingLifecycle::ClosingRetry {
646                    attach_sequence: None,
647                };
648            }
649            (ProviderBindingLifecycle::DetachSent { sequence }, Err(_))
650                if *sequence == outcome.sequence =>
651            {
652                // Snapshot reconciliation below distinguishes confirmed
653                // absence from a foreign or poisoned kernel binding.
654                *lifecycle = ProviderBindingLifecycle::Closed;
655                remove = true;
656            }
657            _ => {}
658        }
659        drop(lifecycle);
660        if remove {
661            retire_binding(&state);
662            let mut authority = lock(&self.state.inner);
663            if authority
664                .bindings
665                .get(&state.provider_id)
666                .is_some_and(|current| Arc::ptr_eq(current, &state))
667            {
668                authority.bindings.remove(&state.provider_id);
669            }
670        }
671    }
672
673    pub(crate) fn reconcile_snapshot(&self, snapshot: &ProviderRuntimeSnapshot) {
674        let canonical = snapshot
675            .bindings
676            .iter()
677            .map(|binding| (binding.provider_id.clone(), binding.binding_id))
678            .collect::<BTreeMap<_, _>>();
679        let mut authority = lock(&self.state.inner);
680        if authority.closed {
681            return;
682        }
683        authority.sequence_exhausted |= snapshot.sequence_exhausted;
684        if !authority.sequence_exhausted && snapshot.last_sequence >= authority.next_sequence {
685            if let Some(next_sequence) = snapshot.last_sequence.checked_add(1) {
686                authority.next_sequence = next_sequence;
687            } else {
688                authority.sequence_exhausted = true;
689            }
690        }
691        authority.foreign_bindings.clear();
692
693        let mut remove = Vec::new();
694        let mut retired = Vec::new();
695        for (provider_id, state) in &authority.bindings {
696            let exact = canonical.get(provider_id) == Some(&state.binding_id);
697            let lifecycle = *lock(&state.lifecycle);
698            let command_was_reduced = match lifecycle {
699                ProviderBindingLifecycle::AttachSent { sequence }
700                | ProviderBindingLifecycle::DetachSent { sequence } => {
701                    snapshot.last_sequence >= sequence
702                }
703                ProviderBindingLifecycle::ClosingRetry { attach_sequence } => {
704                    attach_sequence.is_none_or(|sequence| snapshot.last_sequence >= sequence)
705                }
706                ProviderBindingLifecycle::Active
707                | ProviderBindingLifecycle::Quiescing
708                | ProviderBindingLifecycle::Closed => true,
709            };
710            if snapshot.sequence_exhausted || (!exact && command_was_reduced) {
711                remove.push(provider_id.clone());
712                retired.push(Arc::clone(state));
713            }
714        }
715        for provider_id in remove {
716            authority.bindings.remove(&provider_id);
717        }
718        for (provider_id, binding_id) in canonical {
719            let locally_owned = authority
720                .bindings
721                .get(&provider_id)
722                .is_some_and(|state| state.binding_id == binding_id);
723            if !locally_owned {
724                authority.foreign_bindings.insert(provider_id, binding_id);
725            }
726        }
727        drop(authority);
728        for state in retired {
729            retire_binding(&state);
730        }
731    }
732
733    pub(crate) fn active_bindings(&self) -> BTreeMap<ToolProviderId, ProviderBindingId> {
734        lock(&self.state.inner)
735            .bindings
736            .iter()
737            .filter_map(|(provider_id, state)| {
738                matches!(*lock(&state.lifecycle), ProviderBindingLifecycle::Active)
739                    .then_some((provider_id.clone(), state.binding_id))
740            })
741            .collect()
742    }
743
744    pub(crate) fn publish_effect(
745        &self,
746        bound: ProviderBoundCapabilityEffectEnvelope,
747    ) -> ProviderEffectPublishReport {
748        let provider_id = bound.effect.provider_id.clone();
749        let state = lock(&self.state.inner)
750            .bindings
751            .get(&provider_id)
752            .filter(|state| state.binding_id == bound.binding_id)
753            .cloned();
754        let Some(state) = state else {
755            return ProviderEffectPublishReport {
756                unbound: 1,
757                ..ProviderEffectPublishReport::default()
758            };
759        };
760        let mut lifecycle = lock(&state.lifecycle);
761        if !matches!(
762            *lifecycle,
763            ProviderBindingLifecycle::Active | ProviderBindingLifecycle::Quiescing
764        ) {
765            return ProviderEffectPublishReport {
766                disconnected: 1,
767                ..ProviderEffectPublishReport::default()
768            };
769        }
770        let effect = bound.effect;
771        let work = if matches!(&effect.effect, CapabilityEffect::Invoke { .. }) {
772            let operation = Arc::new(ProviderOperationState::from_effect(&effect));
773            let mut operations = lock(&state.operations);
774            if operations.contains_key(&effect.operation_id) {
775                drop(operations);
776                fault_binding_locked(&state, &mut lifecycle);
777                return ProviderEffectPublishReport {
778                    disconnected: 1,
779                    ..ProviderEffectPublishReport::default()
780                };
781            }
782            operations.insert(effect.operation_id, Arc::clone(&operation));
783            drop(operations);
784            ProviderWork::Invoke(ProviderInvocation {
785                state: Arc::downgrade(&state),
786                operation,
787                binding_id: bound.binding_id,
788                effect,
789                submitted: false,
790            })
791        } else {
792            let mut operations = lock(&state.operations);
793            let exact = operations
794                .get(&effect.operation_id)
795                .filter(|operation| operation.matches(&effect))
796                .cloned();
797            let Some(operation) = exact else {
798                drop(operations);
799                fault_binding_locked(&state, &mut lifecycle);
800                return ProviderEffectPublishReport {
801                    disconnected: 1,
802                    ..ProviderEffectPublishReport::default()
803                };
804            };
805            operation.cancelled.store(true, Ordering::Release);
806            operations.remove(&effect.operation_id);
807            drop(operations);
808            ProviderWork::Cancel(ProviderCancellation {
809                binding_id: bound.binding_id,
810                effect,
811            })
812        };
813        let mut sender = lock(&state.work_tx);
814        let Some(active_sender) = sender.as_ref() else {
815            drop(sender);
816            fault_binding_locked(&state, &mut lifecycle);
817            return ProviderEffectPublishReport {
818                disconnected: 1,
819                ..ProviderEffectPublishReport::default()
820            };
821        };
822        match active_sender.try_send(work) {
823            Ok(()) => ProviderEffectPublishReport {
824                delivered: 1,
825                ..ProviderEffectPublishReport::default()
826            },
827            Err(TrySendError::Full(_)) => {
828                state.cancelled.store(true, Ordering::Release);
829                *lifecycle = ProviderBindingLifecycle::ClosingRetry {
830                    attach_sequence: None,
831                };
832                *sender = None;
833                drop(sender);
834                cancel_all_operations(&state);
835                ProviderEffectPublishReport {
836                    queue_full: 1,
837                    ..ProviderEffectPublishReport::default()
838                }
839            }
840            Err(TrySendError::Disconnected(_)) => {
841                state.cancelled.store(true, Ordering::Release);
842                *lifecycle = ProviderBindingLifecycle::ClosingRetry {
843                    attach_sequence: None,
844                };
845                *sender = None;
846                drop(sender);
847                cancel_all_operations(&state);
848                ProviderEffectPublishReport {
849                    disconnected: 1,
850                    ..ProviderEffectPublishReport::default()
851                }
852            }
853        }
854    }
855
856    pub(crate) fn finish_step(&self) {
857        let bindings = lock(&self.state.inner)
858            .bindings
859            .values()
860            .cloned()
861            .collect::<Vec<_>>();
862        for binding in bindings {
863            lock(&binding.operations)
864                .retain(|_, operation| !operation.settled.load(Ordering::Acquire));
865        }
866    }
867}
868
869impl Drop for ProviderRuntimePort {
870    fn drop(&mut self) {
871        let bindings = {
872            let mut authority = lock(&self.state.inner);
873            close_authority_locked(&mut authority)
874        };
875        for binding in bindings {
876            retire_binding(&binding);
877        }
878    }
879}
880
881impl AddAssign for ProviderEffectPublishReport {
882    fn add_assign(&mut self, other: Self) {
883        self.delivered += other.delivered;
884        self.queue_full += other.queue_full;
885        self.disconnected += other.disconnected;
886        self.unbound += other.unbound;
887    }
888}
889
890fn publish_observation_outcome(
891    state: &Arc<ProviderBindingState>,
892    outcome: &ProviderRuntimeCommandOutcome,
893) {
894    let pending = lock(&state.pending_observations).remove(&outcome.sequence);
895    let Some(pending) = pending else {
896        return;
897    };
898    let status = match &outcome.result {
899        Ok(ProviderRuntimeTransition::ObservationApplied {
900            operation_id,
901            request_key,
902        }) if *operation_id == pending.operation_id && *request_key == pending.request_key => {
903            ProviderObservationStatus::Applied
904        }
905        Ok(ProviderRuntimeTransition::ObservationIgnored {
906            operation_id,
907            request_key,
908            reason,
909        }) if *operation_id == pending.operation_id && *request_key == pending.request_key => {
910            ProviderObservationStatus::Ignored { reason: *reason }
911        }
912        Ok(_) => ProviderObservationStatus::ContractViolation,
913        Err(error) => ProviderObservationStatus::Rejected(error.clone()),
914    };
915    let should_fault = !matches!(
916        status,
917        ProviderObservationStatus::Applied | ProviderObservationStatus::Ignored { .. }
918    );
919    if !should_fault {
920        if let Some(operation) = lock(&state.operations)
921            .get(&pending.operation_id)
922            .filter(|operation| operation.request_key == pending.request_key)
923            .cloned()
924        {
925            operation.settled.store(true, Ordering::Release);
926        }
927    }
928    let receipt = ProviderObservationOutcome {
929        sequence: outcome.sequence,
930        operation_id: pending.operation_id,
931        request_key: pending.request_key,
932        status,
933    };
934    if should_fault {
935        // The binding fence must be visible before a provider can observe a
936        // rejected receipt and attempt another completion.
937        mark_binding_fault(state);
938    }
939    let delivery_failed = lock(&state.observation_outcome_tx)
940        .as_ref()
941        .is_none_or(|sender| sender.try_send(receipt).is_err());
942    if delivery_failed && !should_fault {
943        mark_binding_fault(state);
944    }
945}
946
947fn retire_binding(state: &Arc<ProviderBindingState>) {
948    state.cancelled.store(true, Ordering::Release);
949    *lock(&state.lifecycle) = ProviderBindingLifecycle::Closed;
950    cancel_all_operations(state);
951    *lock(&state.work_tx) = None;
952    let pending = std::mem::take(&mut *lock(&state.pending_observations));
953    let mut outcome_sender = lock(&state.observation_outcome_tx);
954    if let Some(sender) = outcome_sender.as_ref() {
955        for (sequence, pending) in pending {
956            let _ = sender.try_send(ProviderObservationOutcome {
957                sequence,
958                operation_id: pending.operation_id,
959                request_key: pending.request_key,
960                status: ProviderObservationStatus::RuntimeClosed,
961            });
962        }
963    }
964    *outcome_sender = None;
965}
966
967fn submit_observation(
968    state: &Arc<ProviderBindingState>,
969    effect: CapabilityEffectEnvelope,
970    observation: CapabilityObservation,
971) -> Result<u64, ProviderRuntimeError> {
972    let Some(authority) = state.authority.upgrade() else {
973        return Err(ProviderRuntimeError::Disconnected);
974    };
975    let mut inner = lock(&authority.inner);
976    if inner.closed {
977        drop(inner);
978        retire_binding(state);
979        return Err(ProviderRuntimeError::Disconnected);
980    }
981    if inner.sequence_exhausted {
982        return Err(ProviderRuntimeError::CounterExhausted {
983            counter: "provider-runtime-sequence",
984        });
985    }
986    let mut lifecycle = lock(&state.lifecycle);
987    if !inner
988        .bindings
989        .get(&state.provider_id)
990        .is_some_and(|current| Arc::ptr_eq(current, state))
991        || !matches!(
992            *lifecycle,
993            ProviderBindingLifecycle::Active | ProviderBindingLifecycle::Quiescing
994        )
995    {
996        return Err(ProviderRuntimeError::Inactive);
997    }
998    let sequence = inner.next_sequence;
999    let pending = PendingObservation {
1000        operation_id: effect.operation_id,
1001        request_key: effect.request_key.clone(),
1002    };
1003    let raw = CapabilityObservationEnvelope {
1004        operation_id: effect.operation_id,
1005        request_key: effect.request_key,
1006        instance_id: effect.instance_id,
1007        generation: effect.generation,
1008        provider_id: effect.provider_id,
1009        observation,
1010    };
1011    let envelope = ProviderRuntimeEnvelope {
1012        sequence,
1013        command: ProviderRuntimeCommand::Observe {
1014            binding_id: state.binding_id,
1015            observation: raw,
1016        },
1017    };
1018    if let Err(error) = send_ingress(&authority.ingress_tx, envelope) {
1019        drop(lifecycle);
1020        if error == ProviderRuntimeError::Disconnected {
1021            let bindings = close_authority_locked(&mut inner);
1022            drop(inner);
1023            for binding in bindings {
1024                retire_binding(&binding);
1025            }
1026        }
1027        return Err(error);
1028    }
1029    lock(&state.pending_observations).insert(sequence, pending);
1030    commit_sequence(&mut inner, sequence);
1031    if sequence == u64::MAX {
1032        state.cancelled.store(true, Ordering::Release);
1033        *lifecycle = ProviderBindingLifecycle::ClosingRetry {
1034            attach_sequence: None,
1035        };
1036        cancel_all_operations(state);
1037        *lock(&state.work_tx) = None;
1038    }
1039    Ok(sequence)
1040}
1041
1042fn request_close(state: &Arc<ProviderBindingState>) -> Result<u64, ProviderRuntimeError> {
1043    state.cancelled.store(true, Ordering::Release);
1044    cancel_all_operations(state);
1045    let Some(authority) = state.authority.upgrade() else {
1046        retire_binding(state);
1047        return Err(ProviderRuntimeError::Disconnected);
1048    };
1049    let mut inner = lock(&authority.inner);
1050    if inner.closed {
1051        drop(inner);
1052        retire_binding(state);
1053        return Err(ProviderRuntimeError::Disconnected);
1054    }
1055    if !inner
1056        .bindings
1057        .get(&state.provider_id)
1058        .is_some_and(|current| Arc::ptr_eq(current, state))
1059    {
1060        drop(inner);
1061        retire_binding(state);
1062        return Err(ProviderRuntimeError::Inactive);
1063    }
1064    let mut lifecycle = lock(&state.lifecycle);
1065    if inner.sequence_exhausted {
1066        if !matches!(*lifecycle, ProviderBindingLifecycle::Closed) {
1067            *lifecycle = closing_retry(*lifecycle);
1068            *lock(&state.work_tx) = None;
1069        }
1070        return Err(ProviderRuntimeError::CounterExhausted {
1071            counter: "provider-runtime-sequence",
1072        });
1073    }
1074    match *lifecycle {
1075        ProviderBindingLifecycle::DetachSent { sequence } => return Ok(sequence),
1076        ProviderBindingLifecycle::Closed => return Err(ProviderRuntimeError::Inactive),
1077        ProviderBindingLifecycle::AttachSent { .. }
1078        | ProviderBindingLifecycle::Active
1079        | ProviderBindingLifecycle::Quiescing
1080        | ProviderBindingLifecycle::ClosingRetry { .. } => {}
1081    }
1082    let sequence = inner.next_sequence;
1083    let envelope = ProviderRuntimeEnvelope {
1084        sequence,
1085        command: ProviderRuntimeCommand::Detach {
1086            binding_id: state.binding_id,
1087            provider_id: state.provider_id.clone(),
1088        },
1089    };
1090    match authority
1091        .ingress_tx
1092        .try_send(BackendIngress::ToolProvider(envelope))
1093    {
1094        Ok(()) => {
1095            commit_sequence(&mut inner, sequence);
1096            *lifecycle = ProviderBindingLifecycle::DetachSent { sequence };
1097            *lock(&state.work_tx) = None;
1098            Ok(sequence)
1099        }
1100        Err(TrySendError::Full(_)) => {
1101            *lifecycle = closing_retry(*lifecycle);
1102            *lock(&state.work_tx) = None;
1103            Err(ProviderRuntimeError::Full)
1104        }
1105        Err(TrySendError::Disconnected(_)) => {
1106            drop(lifecycle);
1107            let bindings = close_authority_locked(&mut inner);
1108            drop(inner);
1109            for binding in bindings {
1110                retire_binding(&binding);
1111            }
1112            Err(ProviderRuntimeError::Disconnected)
1113        }
1114    }
1115}
1116
1117fn mark_binding_fault(state: &Arc<ProviderBindingState>) {
1118    let mut lifecycle = lock(&state.lifecycle);
1119    fault_binding_locked(state, &mut lifecycle);
1120}
1121
1122fn fault_binding_locked(
1123    state: &Arc<ProviderBindingState>,
1124    lifecycle: &mut ProviderBindingLifecycle,
1125) {
1126    state.cancelled.store(true, Ordering::Release);
1127    if matches!(
1128        *lifecycle,
1129        ProviderBindingLifecycle::AttachSent { .. }
1130            | ProviderBindingLifecycle::Active
1131            | ProviderBindingLifecycle::Quiescing
1132            | ProviderBindingLifecycle::ClosingRetry { .. }
1133    ) {
1134        *lifecycle = closing_retry(*lifecycle);
1135    }
1136    cancel_all_operations(state);
1137    *lock(&state.work_tx) = None;
1138}
1139
1140fn cancel_all_operations(state: &Arc<ProviderBindingState>) {
1141    let operations = std::mem::take(&mut *lock(&state.operations));
1142    for operation in operations.into_values() {
1143        operation.cancelled.store(true, Ordering::Release);
1144    }
1145}
1146
1147fn begin_quiesce(state: &Arc<ProviderBindingState>) -> Result<(), ProviderRuntimeError> {
1148    let Some(authority) = state.authority.upgrade() else {
1149        retire_binding(state);
1150        return Err(ProviderRuntimeError::Disconnected);
1151    };
1152    let authority = lock(&authority.inner);
1153    if authority.closed
1154        || !authority
1155            .bindings
1156            .get(&state.provider_id)
1157            .is_some_and(|current| Arc::ptr_eq(current, state))
1158    {
1159        drop(authority);
1160        retire_binding(state);
1161        return Err(ProviderRuntimeError::Inactive);
1162    }
1163    let mut lifecycle = lock(&state.lifecycle);
1164    match *lifecycle {
1165        ProviderBindingLifecycle::Active => {
1166            *lifecycle = ProviderBindingLifecycle::Quiescing;
1167        }
1168        ProviderBindingLifecycle::Quiescing => return Ok(()),
1169        ProviderBindingLifecycle::AttachSent { .. }
1170        | ProviderBindingLifecycle::ClosingRetry { .. }
1171        | ProviderBindingLifecycle::DetachSent { .. }
1172        | ProviderBindingLifecycle::Closed => return Err(ProviderRuntimeError::Inactive),
1173    }
1174    state.cancelled.store(true, Ordering::Release);
1175    for operation in lock(&state.operations).values() {
1176        operation.cancelled.store(true, Ordering::Release);
1177    }
1178    Ok(())
1179}
1180
1181fn close_authority_locked(inner: &mut ProviderAuthorityInner) -> Vec<Arc<ProviderBindingState>> {
1182    inner.closed = true;
1183    inner.foreign_bindings.clear();
1184    std::mem::take(&mut inner.bindings).into_values().collect()
1185}
1186
1187fn send_ingress(
1188    sender: &SyncSender<BackendIngress>,
1189    envelope: ProviderRuntimeEnvelope,
1190) -> Result<(), ProviderRuntimeError> {
1191    sender
1192        .try_send(BackendIngress::ToolProvider(envelope))
1193        .map_err(|error| match error {
1194            TrySendError::Full(_) => ProviderRuntimeError::Full,
1195            TrySendError::Disconnected(_) => ProviderRuntimeError::Disconnected,
1196        })
1197}
1198
1199fn closing_retry(lifecycle: ProviderBindingLifecycle) -> ProviderBindingLifecycle {
1200    let attach_sequence = match lifecycle {
1201        ProviderBindingLifecycle::AttachSent { sequence } => Some(sequence),
1202        ProviderBindingLifecycle::ClosingRetry { attach_sequence } => attach_sequence,
1203        ProviderBindingLifecycle::Active
1204        | ProviderBindingLifecycle::Quiescing
1205        | ProviderBindingLifecycle::DetachSent { .. }
1206        | ProviderBindingLifecycle::Closed => None,
1207    };
1208    ProviderBindingLifecycle::ClosingRetry { attach_sequence }
1209}
1210
1211fn public_lifecycle(lifecycle: ProviderBindingLifecycle) -> ProviderRuntimeState {
1212    match lifecycle {
1213        ProviderBindingLifecycle::AttachSent { .. } => ProviderRuntimeState::Attaching,
1214        ProviderBindingLifecycle::Active => ProviderRuntimeState::Active,
1215        ProviderBindingLifecycle::Quiescing => ProviderRuntimeState::Closing,
1216        ProviderBindingLifecycle::ClosingRetry { .. }
1217        | ProviderBindingLifecycle::DetachSent { .. } => ProviderRuntimeState::Closing,
1218        ProviderBindingLifecycle::Closed => ProviderRuntimeState::Closed,
1219    }
1220}
1221
1222fn commit_sequence(inner: &mut ProviderAuthorityInner, sequence: u64) {
1223    if sequence == u64::MAX {
1224        inner.sequence_exhausted = true;
1225    } else {
1226        inner.next_sequence = sequence + 1;
1227    }
1228}
1229
1230fn bounded_capacity(requested: usize, max: usize) -> usize {
1231    requested.clamp(1, max)
1232}
1233
1234fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
1235    mutex
1236        .lock()
1237        .unwrap_or_else(|poisoned| poisoned.into_inner())
1238}
1239
1240#[cfg(test)]
1241mod tests {
1242    use super::*;
1243    use gate4agent_tool_protocol::{
1244        CapabilityRequestId, CapabilityResultDelivery, CapabilityResultMetadata, ConsumerId,
1245        ProviderRuntimeBindingSnapshot, ProviderRuntimeSnapshot, ToolActorId,
1246    };
1247    use gate4agent_types::{AgentInstanceId, SessionGeneration};
1248
1249    fn provider_id() -> ToolProviderId {
1250        ToolProviderId::new("gate.provider.unit").unwrap()
1251    }
1252
1253    fn bound_invoke(binding_id: ProviderBindingId) -> ProviderBoundCapabilityEffectEnvelope {
1254        bound_invoke_with_operation(binding_id, 1)
1255    }
1256
1257    fn bound_invoke_with_operation(
1258        binding_id: ProviderBindingId,
1259        operation: u64,
1260    ) -> ProviderBoundCapabilityEffectEnvelope {
1261        let consumer_id = ConsumerId::new("gate-consumer-unit").unwrap();
1262        let actor_id = ToolActorId::new("gate-actor-unit").unwrap();
1263        ProviderBoundCapabilityEffectEnvelope {
1264            binding_id,
1265            effect: CapabilityEffectEnvelope {
1266                sequence: operation,
1267                operation_id: ToolOperationId(operation),
1268                request_key: CapabilityRequestKey {
1269                    consumer_id: consumer_id.clone(),
1270                    actor_id: actor_id.clone(),
1271                    local_id: CapabilityRequestId(operation),
1272                },
1273                instance_id: AgentInstanceId(1),
1274                generation: SessionGeneration(1),
1275                provider_id: provider_id(),
1276                deadline_tick: 10,
1277                effect: CapabilityEffect::Invoke {
1278                    consumer_id,
1279                    actor_id,
1280                    capability_id: ToolCapabilityId::new("browser.snapshot").unwrap(),
1281                    resource_scope_id: ResourceScopeId::new("page.active").unwrap(),
1282                    payload: b"{}".to_vec(),
1283                },
1284            },
1285        }
1286    }
1287
1288    fn bound_cancel(
1289        invoke: &ProviderBoundCapabilityEffectEnvelope,
1290        sequence: u64,
1291        reason: InvocationCancelReason,
1292    ) -> ProviderBoundCapabilityEffectEnvelope {
1293        let mut cancel = invoke.clone();
1294        cancel.effect.sequence = sequence;
1295        cancel.effect.effect = CapabilityEffect::Cancel { reason };
1296        cancel
1297    }
1298
1299    fn result() -> CapabilityResult {
1300        CapabilityResult {
1301            metadata: CapabilityResultMetadata {
1302                byte_len: 2,
1303                media_type: Some("application/json".to_owned()),
1304                truncated: false,
1305                redacted_summary: None,
1306            },
1307            delivery: CapabilityResultDelivery::Inline {
1308                bytes: b"{}".to_vec(),
1309            },
1310        }
1311    }
1312
1313    fn activate(
1314        authority: &ProviderRuntimeAuthorityHandle,
1315        port: &ProviderRuntimePort,
1316        ingress: &Receiver<BackendIngress>,
1317    ) -> ProviderRuntimeHandle {
1318        let runtime = authority.bind_provider(provider_id(), 2).unwrap();
1319        let BackendIngress::ToolProvider(attach) = ingress.try_recv().unwrap() else {
1320            panic!("expected attach ingress");
1321        };
1322        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1323            sequence: attach.sequence,
1324            binding_id: runtime.binding_id(),
1325            provider_id: provider_id(),
1326            result: Ok(ProviderRuntimeTransition::Attached),
1327        });
1328        assert_eq!(runtime.state(), ProviderRuntimeState::Active);
1329        runtime
1330    }
1331
1332    #[test]
1333    fn rejected_observation_is_correlated_and_faults_binding() {
1334        let (ingress_tx, ingress_rx) = sync_channel(8);
1335        let (authority, port) = provider_runtime(ingress_tx);
1336        let runtime = activate(&authority, &port, &ingress_rx);
1337        assert_eq!(
1338            port.publish_effect(bound_invoke(runtime.binding_id()))
1339                .delivered,
1340            1
1341        );
1342        let ProviderWork::Invoke(mut invocation) = runtime.try_recv().unwrap() else {
1343            panic!("expected invocation");
1344        };
1345        let sequence = runtime.try_succeed(&mut invocation, &result()).unwrap();
1346        let BackendIngress::ToolProvider(observation) = ingress_rx.try_recv().unwrap() else {
1347            panic!("expected observation ingress");
1348        };
1349        assert_eq!(observation.sequence, sequence);
1350
1351        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1352            sequence,
1353            binding_id: runtime.binding_id(),
1354            provider_id: provider_id(),
1355            result: Err(KernelProviderError::IntegrationBlocked),
1356        });
1357        let receipt = runtime.try_recv_observation_outcome().unwrap();
1358        assert_eq!(receipt.sequence, sequence);
1359        assert_eq!(
1360            receipt.status,
1361            ProviderObservationStatus::Rejected(KernelProviderError::IntegrationBlocked)
1362        );
1363        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1364        assert!(runtime.cancellation_token().is_cancelled());
1365    }
1366
1367    #[test]
1368    fn full_observation_ingress_is_retryable_without_consuming_ticket() {
1369        let (ingress_tx, ingress_rx) = sync_channel(1);
1370        let (authority, port) = provider_runtime(ingress_tx);
1371        let runtime = activate(&authority, &port, &ingress_rx);
1372        assert_eq!(
1373            port.publish_effect(bound_invoke(runtime.binding_id()))
1374                .delivered,
1375            1
1376        );
1377        assert_eq!(
1378            port.publish_effect(bound_invoke_with_operation(runtime.binding_id(), 2))
1379                .delivered,
1380            1
1381        );
1382        let ProviderWork::Invoke(mut first) = runtime.try_recv().unwrap() else {
1383            panic!("expected first invocation");
1384        };
1385        let ProviderWork::Invoke(mut second) = runtime.try_recv().unwrap() else {
1386            panic!("expected second invocation");
1387        };
1388
1389        assert_eq!(runtime.try_succeed(&mut first, &result()).unwrap(), 2);
1390        assert_eq!(
1391            runtime.try_succeed(&mut second, &result()),
1392            Err(ProviderRuntimeError::Full)
1393        );
1394        assert!(matches!(
1395            ingress_rx.try_recv().unwrap(),
1396            BackendIngress::ToolProvider(ProviderRuntimeEnvelope { sequence: 2, .. })
1397        ));
1398        assert_eq!(runtime.try_succeed(&mut second, &result()).unwrap(), 3);
1399        assert!(matches!(
1400            ingress_rx.try_recv().unwrap(),
1401            BackendIngress::ToolProvider(ProviderRuntimeEnvelope { sequence: 3, .. })
1402        ));
1403    }
1404
1405    #[test]
1406    fn closing_attach_survives_prefix_snapshot_until_attach_is_reduced() {
1407        let (ingress_tx, ingress_rx) = sync_channel(1);
1408        let (authority, port) = provider_runtime(ingress_tx);
1409        let runtime = authority.bind_provider(provider_id(), 1).unwrap();
1410        let binding_id = runtime.binding_id();
1411        assert_eq!(runtime.close(), Err(ProviderRuntimeError::Full));
1412        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1413
1414        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1415            last_sequence: 0,
1416            sequence_exhausted: false,
1417            bindings: Vec::new(),
1418        });
1419        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1420        assert!(matches!(
1421            authority.bind_provider(provider_id(), 1),
1422            Err(ProviderRuntimeError::AlreadyBound)
1423        ));
1424
1425        let BackendIngress::ToolProvider(attach) = ingress_rx.try_recv().unwrap() else {
1426            panic!("expected retained attach");
1427        };
1428        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1429            sequence: attach.sequence,
1430            binding_id,
1431            provider_id: provider_id(),
1432            result: Ok(ProviderRuntimeTransition::Attached),
1433        });
1434        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1435            last_sequence: attach.sequence,
1436            sequence_exhausted: false,
1437            bindings: vec![ProviderRuntimeBindingSnapshot {
1438                binding_id,
1439                provider_id: provider_id(),
1440            }],
1441        });
1442        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1443
1444        port.flush_closing_bindings();
1445        let BackendIngress::ToolProvider(detach) = ingress_rx.try_recv().unwrap() else {
1446            panic!("expected detach after attach reduction");
1447        };
1448        assert_eq!(detach.sequence, 2);
1449        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1450            sequence: detach.sequence,
1451            binding_id,
1452            provider_id: provider_id(),
1453            result: Ok(ProviderRuntimeTransition::Detached {
1454                closed_request_count: 0,
1455            }),
1456        });
1457        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1458            last_sequence: detach.sequence,
1459            sequence_exhausted: false,
1460            bindings: Vec::new(),
1461        });
1462        assert_eq!(runtime.state(), ProviderRuntimeState::Closed);
1463    }
1464
1465    #[test]
1466    fn prequeued_work_is_fenced_as_soon_as_close_linearizes() {
1467        let (ingress_tx, ingress_rx) = sync_channel(8);
1468        let (authority, port) = provider_runtime(ingress_tx);
1469        let runtime = activate(&authority, &port, &ingress_rx);
1470        assert_eq!(
1471            port.publish_effect(bound_invoke(runtime.binding_id()))
1472                .delivered,
1473            1
1474        );
1475        runtime.close().unwrap();
1476        assert!(runtime.cancellation_token().is_cancelled());
1477        assert!(matches!(
1478            runtime.try_recv(),
1479            Err(TryRecvError::Disconnected)
1480        ));
1481    }
1482
1483    #[test]
1484    fn maximum_sequence_retires_binding_without_local_zombie() {
1485        let (ingress_tx, ingress_rx) = sync_channel(8);
1486        let (authority, port) = provider_runtime(ingress_tx);
1487        let runtime = activate(&authority, &port, &ingress_rx);
1488        lock(&authority.state.inner).next_sequence = u64::MAX;
1489
1490        assert_eq!(runtime.close().unwrap(), u64::MAX);
1491        assert!(authority.sequence_exhausted());
1492        let BackendIngress::ToolProvider(detach) = ingress_rx.try_recv().unwrap() else {
1493            panic!("expected terminal detach ingress");
1494        };
1495        assert_eq!(detach.sequence, u64::MAX);
1496
1497        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1498            last_sequence: u64::MAX,
1499            sequence_exhausted: true,
1500            bindings: Vec::new(),
1501        });
1502        assert_eq!(runtime.state(), ProviderRuntimeState::Closed);
1503        assert_eq!(authority.active_binding_count(), 0);
1504        assert!(matches!(
1505            authority.bind_provider(provider_id(), 1),
1506            Err(ProviderRuntimeError::CounterExhausted {
1507                counter: "provider-runtime-sequence"
1508            })
1509        ));
1510    }
1511
1512    #[test]
1513    fn foreign_binding_is_quarantined_until_canonical_absence() {
1514        let (ingress_tx, ingress_rx) = sync_channel(8);
1515        let (authority, port) = provider_runtime(ingress_tx);
1516        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1517            last_sequence: 9,
1518            sequence_exhausted: false,
1519            bindings: vec![ProviderRuntimeBindingSnapshot {
1520                binding_id: ProviderBindingId(9),
1521                provider_id: provider_id(),
1522            }],
1523        });
1524        assert!(matches!(
1525            authority.bind_provider(provider_id(), 1),
1526            Err(ProviderRuntimeError::AlreadyBound)
1527        ));
1528
1529        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1530            last_sequence: 9,
1531            sequence_exhausted: false,
1532            bindings: Vec::new(),
1533        });
1534        let replacement = authority.bind_provider(provider_id(), 1).unwrap();
1535        assert_eq!(replacement.binding_id(), ProviderBindingId(10));
1536        let BackendIngress::ToolProvider(attach) = ingress_rx.try_recv().unwrap() else {
1537            panic!("expected reconciled attach ingress");
1538        };
1539        assert_eq!(attach.sequence, 10);
1540    }
1541
1542    #[test]
1543    fn cancel_marks_only_exact_operation_before_cancel_work_is_received() {
1544        let (ingress_tx, ingress_rx) = sync_channel(8);
1545        let (authority, port) = provider_runtime(ingress_tx);
1546        let runtime = activate(&authority, &port, &ingress_rx);
1547        let first_effect = bound_invoke_with_operation(runtime.binding_id(), 1);
1548        let second_effect = bound_invoke_with_operation(runtime.binding_id(), 2);
1549        assert_eq!(port.publish_effect(first_effect.clone()).delivered, 1);
1550        assert_eq!(port.publish_effect(second_effect).delivered, 1);
1551        let ProviderWork::Invoke(first) = runtime.try_recv().unwrap() else {
1552            panic!("expected first invocation");
1553        };
1554        let ProviderWork::Invoke(second) = runtime.try_recv().unwrap() else {
1555            panic!("expected second invocation");
1556        };
1557        let first_token = first.cancellation_token();
1558        let second_token = second.cancellation_token();
1559        let binding_token = runtime.cancellation_token();
1560
1561        let cancel = bound_cancel(&first_effect, 3, InvocationCancelReason::GrantRevoked);
1562        assert_eq!(port.publish_effect(cancel).delivered, 1);
1563        assert!(first_token.is_cancelled());
1564        assert!(!second_token.is_cancelled());
1565        assert!(!binding_token.is_cancelled());
1566        let ProviderWork::Cancel(cancel) = runtime.try_recv().unwrap() else {
1567            panic!("expected exact cancellation");
1568        };
1569        assert_eq!(cancel.operation_id(), first.operation_id());
1570        assert_eq!(cancel.request_key(), first.request_key());
1571        assert_eq!(cancel.reason(), InvocationCancelReason::GrantRevoked);
1572    }
1573
1574    #[test]
1575    fn quiesce_hides_binding_but_preserves_admitted_work_and_receipts() {
1576        let (ingress_tx, ingress_rx) = sync_channel(8);
1577        let (authority, port) = provider_runtime(ingress_tx);
1578        let runtime = activate(&authority, &port, &ingress_rx);
1579        let effect = bound_invoke(runtime.binding_id());
1580        assert_eq!(port.publish_effect(effect).delivered, 1);
1581
1582        runtime.begin_quiesce().unwrap();
1583        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1584        assert_eq!(authority.active_binding_count(), 0);
1585        assert!(port.active_bindings().is_empty());
1586
1587        let ProviderWork::Invoke(mut invocation) = runtime.try_recv().unwrap() else {
1588            panic!("expected already admitted invocation");
1589        };
1590        assert!(invocation.cancellation_token().is_cancelled());
1591        let sequence = runtime.try_succeed(&mut invocation, &result()).unwrap();
1592        let BackendIngress::ToolProvider(observation) = ingress_rx.try_recv().unwrap() else {
1593            panic!("expected provider observation ingress");
1594        };
1595        assert_eq!(observation.sequence, sequence);
1596
1597        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1598            sequence,
1599            binding_id: runtime.binding_id(),
1600            provider_id: provider_id(),
1601            result: Ok(ProviderRuntimeTransition::ObservationApplied {
1602                operation_id: invocation.operation_id(),
1603                request_key: invocation.request_key().clone(),
1604            }),
1605        });
1606        let receipt = runtime.try_recv_observation_outcome().unwrap();
1607        assert_eq!(receipt.sequence, sequence);
1608        assert_eq!(receipt.status, ProviderObservationStatus::Applied);
1609        port.finish_step();
1610        assert!(lock(&runtime.state.operations).is_empty());
1611
1612        runtime.close().unwrap();
1613        assert!(matches!(
1614            ingress_rx.try_recv(),
1615            Ok(BackendIngress::ToolProvider(_))
1616        ));
1617    }
1618
1619    #[test]
1620    fn ignored_observation_is_pruned_only_after_same_step_cancel_is_published() {
1621        let (ingress_tx, ingress_rx) = sync_channel(8);
1622        let (authority, port) = provider_runtime(ingress_tx);
1623        let runtime = activate(&authority, &port, &ingress_rx);
1624        let effect = bound_invoke(runtime.binding_id());
1625        assert_eq!(port.publish_effect(effect.clone()).delivered, 1);
1626        let ProviderWork::Invoke(mut invocation) = runtime.try_recv().unwrap() else {
1627            panic!("expected invocation");
1628        };
1629        let token = invocation.cancellation_token();
1630        let sequence = runtime.try_succeed(&mut invocation, &result()).unwrap();
1631        let _observation = ingress_rx.try_recv().unwrap();
1632
1633        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1634            sequence,
1635            binding_id: runtime.binding_id(),
1636            provider_id: provider_id(),
1637            result: Ok(ProviderRuntimeTransition::ObservationIgnored {
1638                operation_id: invocation.operation_id(),
1639                request_key: invocation.request_key().clone(),
1640                reason: ObservationIgnoredReason::RequestNotDispatched,
1641            }),
1642        });
1643        assert!(!token.is_cancelled());
1644        assert_eq!(lock(&runtime.state.operations).len(), 1);
1645        assert_eq!(
1646            port.publish_effect(bound_cancel(
1647                &effect,
1648                effect.effect.sequence + 1,
1649                InvocationCancelReason::GrantRevoked,
1650            ))
1651            .delivered,
1652            1
1653        );
1654        assert!(token.is_cancelled());
1655        port.finish_step();
1656        assert!(lock(&runtime.state.operations).is_empty());
1657        assert_eq!(runtime.state(), ProviderRuntimeState::Active);
1658        assert!(!runtime.cancellation_token().is_cancelled());
1659    }
1660
1661    #[test]
1662    fn dropping_provider_runtime_port_retires_retained_handles_and_pending_receipts() {
1663        let (ingress_tx, ingress_rx) = sync_channel(8);
1664        let (authority, port) = provider_runtime(ingress_tx);
1665        let runtime = activate(&authority, &port, &ingress_rx);
1666        let first_effect = bound_invoke_with_operation(runtime.binding_id(), 1);
1667        let second_effect = bound_invoke_with_operation(runtime.binding_id(), 2);
1668        assert_eq!(port.publish_effect(first_effect).delivered, 1);
1669        assert_eq!(port.publish_effect(second_effect).delivered, 1);
1670        let ProviderWork::Invoke(mut submitted) = runtime.try_recv().unwrap() else {
1671            panic!("expected submitted invocation");
1672        };
1673        let ProviderWork::Invoke(mut retained) = runtime.try_recv().unwrap() else {
1674            panic!("expected retained invocation");
1675        };
1676        let submitted_token = submitted.cancellation_token();
1677        let retained_token = retained.cancellation_token();
1678        let completion = runtime.completion_handle();
1679        let pending_sequence = runtime.try_succeed(&mut submitted, &result()).unwrap();
1680        let _observation = ingress_rx.try_recv().unwrap();
1681
1682        drop(port);
1683
1684        assert_eq!(runtime.state(), ProviderRuntimeState::Closed);
1685        assert!(runtime.cancellation_token().is_cancelled());
1686        assert!(submitted_token.is_cancelled());
1687        assert!(retained_token.is_cancelled());
1688        let receipt = runtime.try_recv_observation_outcome().unwrap();
1689        assert_eq!(receipt.sequence, pending_sequence);
1690        assert_eq!(receipt.status, ProviderObservationStatus::RuntimeClosed);
1691        assert!(matches!(
1692            runtime.try_recv(),
1693            Err(TryRecvError::Disconnected)
1694        ));
1695        assert_eq!(
1696            completion.try_succeed(&mut retained, &result()),
1697            Err(ProviderRuntimeError::Disconnected)
1698        );
1699        assert!(matches!(
1700            authority.bind_provider(provider_id(), 1),
1701            Err(ProviderRuntimeError::Disconnected)
1702        ));
1703    }
1704
1705    #[test]
1706    fn disconnected_ingress_fences_all_provider_bindings() {
1707        let (ingress_tx, ingress_rx) = sync_channel(8);
1708        let (authority, port) = provider_runtime(ingress_tx);
1709        let runtime = activate(&authority, &port, &ingress_rx);
1710        let effect = bound_invoke(runtime.binding_id());
1711        assert_eq!(port.publish_effect(effect).delivered, 1);
1712        let ProviderWork::Invoke(mut invocation) = runtime.try_recv().unwrap() else {
1713            panic!("expected invocation");
1714        };
1715        let token = invocation.cancellation_token();
1716        drop(ingress_rx);
1717
1718        assert_eq!(
1719            runtime.try_succeed(&mut invocation, &result()),
1720            Err(ProviderRuntimeError::Disconnected)
1721        );
1722        assert_eq!(runtime.state(), ProviderRuntimeState::Closed);
1723        assert!(token.is_cancelled());
1724        assert!(matches!(
1725            authority.bind_provider(provider_id(), 1),
1726            Err(ProviderRuntimeError::Disconnected)
1727        ));
1728    }
1729
1730    #[test]
1731    fn detach_retries_after_block_and_sequence_resync_until_canonical_absence() {
1732        let (ingress_tx, ingress_rx) = sync_channel(16);
1733        let (authority, port) = provider_runtime(ingress_tx);
1734        let runtime = activate(&authority, &port, &ingress_rx);
1735        let binding_id = runtime.binding_id();
1736        let first_detach = runtime.close().unwrap();
1737        let _first = ingress_rx.try_recv().unwrap();
1738        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1739            sequence: first_detach,
1740            binding_id,
1741            provider_id: provider_id(),
1742            result: Err(KernelProviderError::IntegrationBlocked),
1743        });
1744        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1745
1746        port.flush_closing_bindings();
1747        let BackendIngress::ToolProvider(second) = ingress_rx.try_recv().unwrap() else {
1748            panic!("expected retried detach");
1749        };
1750        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1751            sequence: second.sequence,
1752            binding_id,
1753            provider_id: provider_id(),
1754            result: Err(KernelProviderError::SequenceRegressed {
1755                current: 9,
1756                requested: second.sequence,
1757            }),
1758        });
1759        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1760            last_sequence: 9,
1761            sequence_exhausted: false,
1762            bindings: vec![ProviderRuntimeBindingSnapshot {
1763                binding_id,
1764                provider_id: provider_id(),
1765            }],
1766        });
1767        port.flush_closing_bindings();
1768        let BackendIngress::ToolProvider(final_detach) = ingress_rx.try_recv().unwrap() else {
1769            panic!("expected resynchronized detach");
1770        };
1771        assert_eq!(final_detach.sequence, 10);
1772        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1773            sequence: final_detach.sequence,
1774            binding_id,
1775            provider_id: provider_id(),
1776            result: Ok(ProviderRuntimeTransition::Detached {
1777                closed_request_count: 0,
1778            }),
1779        });
1780        port.reconcile_snapshot(&ProviderRuntimeSnapshot {
1781            last_sequence: 10,
1782            sequence_exhausted: false,
1783            bindings: Vec::new(),
1784        });
1785        assert_eq!(runtime.state(), ProviderRuntimeState::Closed);
1786        assert_eq!(authority.active_binding_count(), 0);
1787    }
1788
1789    #[test]
1790    fn mismatched_cancel_correlation_faults_binding_without_retargeting() {
1791        let (ingress_tx, ingress_rx) = sync_channel(8);
1792        let (authority, port) = provider_runtime(ingress_tx);
1793        let runtime = activate(&authority, &port, &ingress_rx);
1794        let effect = bound_invoke(runtime.binding_id());
1795        assert_eq!(port.publish_effect(effect.clone()).delivered, 1);
1796        let ProviderWork::Invoke(invocation) = runtime.try_recv().unwrap() else {
1797            panic!("expected invocation");
1798        };
1799        let token = invocation.cancellation_token();
1800        let mut mismatched = bound_cancel(&effect, 2, InvocationCancelReason::GrantRevoked);
1801        mismatched.effect.request_key.local_id = CapabilityRequestId(99);
1802
1803        let report = port.publish_effect(mismatched);
1804        assert_eq!(report.disconnected, 1);
1805        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1806        assert!(runtime.cancellation_token().is_cancelled());
1807        assert!(token.is_cancelled());
1808        assert!(lock(&runtime.state.operations).is_empty());
1809        assert!(matches!(
1810            runtime.try_recv(),
1811            Err(TryRecvError::Disconnected)
1812        ));
1813    }
1814
1815    #[test]
1816    fn work_queue_overflow_cancels_registered_operations_without_zombies() {
1817        let (ingress_tx, ingress_rx) = sync_channel(8);
1818        let (authority, port) = provider_runtime(ingress_tx);
1819        let runtime = authority.bind_provider(provider_id(), 1).unwrap();
1820        let BackendIngress::ToolProvider(attach) = ingress_rx.try_recv().unwrap() else {
1821            panic!("expected attach ingress");
1822        };
1823        port.publish_outcome(&ProviderRuntimeCommandOutcome {
1824            sequence: attach.sequence,
1825            binding_id: runtime.binding_id(),
1826            provider_id: provider_id(),
1827            result: Ok(ProviderRuntimeTransition::Attached),
1828        });
1829        assert_eq!(
1830            port.publish_effect(bound_invoke_with_operation(runtime.binding_id(), 1))
1831                .delivered,
1832            1
1833        );
1834        let ProviderWork::Invoke(first) = runtime.try_recv().unwrap() else {
1835            panic!("expected first invocation");
1836        };
1837        let first_token = first.cancellation_token();
1838        assert_eq!(
1839            port.publish_effect(bound_invoke_with_operation(runtime.binding_id(), 2))
1840                .delivered,
1841            1
1842        );
1843
1844        let report = port.publish_effect(bound_invoke_with_operation(runtime.binding_id(), 3));
1845        assert_eq!(report.queue_full, 1);
1846        assert_eq!(runtime.state(), ProviderRuntimeState::Closing);
1847        assert!(first_token.is_cancelled());
1848        assert!(runtime.cancellation_token().is_cancelled());
1849        assert!(lock(&runtime.state.operations).is_empty());
1850    }
1851
1852    #[test]
1853    fn observation_submission_racing_port_drop_is_linearizable() {
1854        use std::sync::Barrier;
1855
1856        let (ingress_tx, ingress_rx) = sync_channel(8);
1857        let (authority, port) = provider_runtime(ingress_tx);
1858        let runtime = activate(&authority, &port, &ingress_rx);
1859        assert_eq!(
1860            port.publish_effect(bound_invoke(runtime.binding_id()))
1861                .delivered,
1862            1
1863        );
1864        let ProviderWork::Invoke(mut invocation) = runtime.try_recv().unwrap() else {
1865            panic!("expected invocation");
1866        };
1867        let token = invocation.cancellation_token();
1868        let completion = runtime.completion_handle();
1869        let barrier = Arc::new(Barrier::new(2));
1870        let worker_barrier = Arc::clone(&barrier);
1871
1872        let completion_result = std::thread::scope(|scope| {
1873            let worker = scope.spawn(move || {
1874                worker_barrier.wait();
1875                completion.try_succeed(&mut invocation, &result())
1876            });
1877            barrier.wait();
1878            drop(port);
1879            worker.join().unwrap()
1880        });
1881
1882        assert!(matches!(
1883            completion_result,
1884            Ok(_) | Err(ProviderRuntimeError::Disconnected)
1885        ));
1886        assert_eq!(runtime.state(), ProviderRuntimeState::Closed);
1887        assert!(token.is_cancelled());
1888        assert!(matches!(
1889            authority.bind_provider(provider_id(), 1),
1890            Err(ProviderRuntimeError::Disconnected)
1891        ));
1892    }
1893}