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)]
24pub struct ProviderRuntimeAuthorityHandle {
27 state: Arc<ProviderAuthorityState>,
28}
29
30pub struct ProviderRuntimeHandle {
32 state: Arc<ProviderBindingState>,
33 work_rx: Receiver<ProviderWork>,
34 observation_outcome_rx: Receiver<ProviderObservationOutcome>,
35}
36
37#[derive(Clone)]
38pub struct ProviderCompletionHandle {
40 state: Arc<ProviderBindingState>,
41}
42
43#[derive(Clone)]
44pub struct ProviderCancellationToken {
49 state: Weak<ProviderBindingState>,
50 operation: Option<Arc<ProviderOperationState>>,
51}
52
53pub enum ProviderWork {
55 Invoke(ProviderInvocation),
56 Cancel(ProviderCancellation),
57}
58
59pub struct ProviderInvocation {
62 state: Weak<ProviderBindingState>,
63 operation: Arc<ProviderOperationState>,
64 binding_id: ProviderBindingId,
65 effect: CapabilityEffectEnvelope,
66 submitted: bool,
67}
68
69pub struct ProviderCancellation {
72 binding_id: ProviderBindingId,
73 effect: CapabilityEffectEnvelope,
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub 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)]
87pub enum ProviderObservationStatus {
89 Applied,
90 Ignored { reason: ObservationIgnoredReason },
91 Rejected(KernelProviderError),
92 ContractViolation,
93 RuntimeClosed,
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub 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 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 pub fn begin_quiesce(&self) -> Result<(), ProviderRuntimeError> {
365 begin_quiesce(&self.state)
366 }
367
368 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 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 *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 *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 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}