Skip to main content

a3s_code_core/capability/
scope.rs

1use std::fmt;
2use std::future::Future;
3use std::marker::PhantomData;
4use std::sync::{Arc, Mutex, Weak};
5
6use async_trait::async_trait;
7use serde::Serialize;
8use tokio_util::sync::CancellationToken;
9
10use super::supervisor::{
11    remove_registered_child, EffectSupervisor, SupervisedChild, SupervisorInner,
12};
13use super::{
14    CapabilityCeiling, CapabilityEffect, CapabilityEffectError, CapabilityLease,
15    CapabilityScopeError, CapabilitySet, RetainedUseGeneration, ScopeClosePolicy, ScopeCloseReport,
16    Sha256Digest, SupervisedTaskId, SupervisedTaskSpawner, UseCapabilityGeneration,
17    MAX_CAPABILITY_IDENTIFIER_BYTES,
18};
19
20/// Closed scope hierarchy used by the capability lifecycle kernel.
21#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
22#[serde(rename_all = "kebab-case")]
23pub enum CapabilityScopeKind {
24    Session,
25    Run,
26    Turn,
27    Subtask,
28}
29
30impl CapabilityScopeKind {
31    pub const fn as_str(self) -> &'static str {
32        match self {
33            Self::Session => "session",
34            Self::Run => "run",
35            Self::Turn => "turn",
36            Self::Subtask => "subtask",
37        }
38    }
39}
40
41impl fmt::Display for CapabilityScopeKind {
42    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43        formatter.write_str(self.as_str())
44    }
45}
46
47mod sealed {
48    pub trait Sealed {}
49}
50
51/// Sealed marker implemented only by the four supported scope levels.
52pub trait ScopeKind: sealed::Sealed + Send + Sync + 'static {
53    const KIND: CapabilityScopeKind;
54}
55
56#[derive(Debug)]
57pub struct Session;
58
59#[derive(Debug)]
60pub struct Run;
61
62#[derive(Debug)]
63pub struct Turn;
64
65#[derive(Debug)]
66pub struct Subtask;
67
68impl sealed::Sealed for Session {}
69impl sealed::Sealed for Run {}
70impl sealed::Sealed for Turn {}
71impl sealed::Sealed for Subtask {}
72
73impl ScopeKind for Session {
74    const KIND: CapabilityScopeKind = CapabilityScopeKind::Session;
75}
76
77impl ScopeKind for Run {
78    const KIND: CapabilityScopeKind = CapabilityScopeKind::Run;
79}
80
81impl ScopeKind for Turn {
82    const KIND: CapabilityScopeKind = CapabilityScopeKind::Turn;
83}
84
85impl ScopeKind for Subtask {
86    const KIND: CapabilityScopeKind = CapabilityScopeKind::Subtask;
87}
88
89/// Canonical hierarchical scope identity assigned by the Code host.
90#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
91#[serde(transparent)]
92pub struct CapabilityScopeId(Box<str>);
93
94impl CapabilityScopeId {
95    fn root(
96        kind: CapabilityScopeKind,
97        local_id: impl Into<String>,
98    ) -> Result<Self, CapabilityScopeError> {
99        let local_id = local_id.into();
100        validate_scope_local_id(&local_id)?;
101        Self::from_complete(format!("{kind}/{local_id}"))
102    }
103
104    fn child(
105        parent: &Self,
106        kind: CapabilityScopeKind,
107        local_id: impl Into<String>,
108    ) -> Result<Self, CapabilityScopeError> {
109        let local_id = local_id.into();
110        validate_scope_local_id(&local_id)?;
111        Self::from_complete(format!("{}/{kind}/{local_id}", parent.as_str()))
112    }
113
114    fn from_complete(value: String) -> Result<Self, CapabilityScopeError> {
115        if value.len() > MAX_CAPABILITY_IDENTIFIER_BYTES {
116            return Err(CapabilityScopeError::BoundExceeded {
117                field: "scope_id",
118                max: MAX_CAPABILITY_IDENTIFIER_BYTES,
119            });
120        }
121        Ok(Self(value.into_boxed_str()))
122    }
123
124    pub fn as_str(&self) -> &str {
125        &self.0
126    }
127}
128
129impl fmt::Display for CapabilityScopeId {
130    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
131        formatter.write_str(self.as_str())
132    }
133}
134
135struct ParentRegistration {
136    supervisor: Weak<SupervisorInner>,
137    id: u64,
138}
139
140pub(super) struct ScopeInner {
141    id: CapabilityScopeId,
142    kind: CapabilityScopeKind,
143    parent_id: Option<CapabilityScopeId>,
144    set: Arc<CapabilitySet>,
145    ceiling: CapabilityCeiling,
146    use_generation: Option<UseCapabilityGeneration>,
147    supervisor: EffectSupervisor,
148    parent_registration: Mutex<Option<ParentRegistration>>,
149}
150
151impl ScopeInner {
152    pub(super) fn id(&self) -> &CapabilityScopeId {
153        &self.id
154    }
155
156    pub(super) fn parent_id(&self) -> Option<&CapabilityScopeId> {
157        self.parent_id.as_ref()
158    }
159
160    pub(super) fn set(&self) -> &CapabilitySet {
161        &self.set
162    }
163
164    pub(super) fn ceiling(&self) -> &CapabilityCeiling {
165        &self.ceiling
166    }
167
168    pub(super) fn use_generation(&self) -> Option<&UseCapabilityGeneration> {
169        self.use_generation.as_ref()
170    }
171
172    pub(super) fn supervisor_cancellation(&self) -> CancellationToken {
173        self.supervisor.cancellation()
174    }
175
176    pub(super) fn ensure_active(&self) -> Result<(), CapabilityScopeError> {
177        if !self.supervisor.is_open() {
178            return Err(CapabilityScopeError::ScopeInactive {
179                scope_id: self.id.to_string(),
180            });
181        }
182        Ok(())
183    }
184
185    async fn close(&self) -> Result<ScopeCloseReport, CapabilityScopeError> {
186        let report = self.supervisor.close().await?;
187        self.detach_from_parent();
188        Ok(report)
189    }
190
191    fn set_parent_registration(&self, supervisor: Weak<SupervisorInner>, id: u64) {
192        *self
193            .parent_registration
194            .lock()
195            .unwrap_or_else(std::sync::PoisonError::into_inner) =
196            Some(ParentRegistration { supervisor, id });
197    }
198
199    fn detach_from_parent(&self) {
200        let registration = self
201            .parent_registration
202            .lock()
203            .unwrap_or_else(std::sync::PoisonError::into_inner)
204            .take();
205        if let Some(registration) = registration {
206            remove_registered_child(&registration.supervisor, registration.id);
207        }
208    }
209}
210
211impl fmt::Debug for ScopeInner {
212    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
213        formatter
214            .debug_struct("ScopeInner")
215            .field("id", &self.id)
216            .field("kind", &self.kind)
217            .field("parent_id", &self.parent_id)
218            .field("catalog_digest", &self.set.digest())
219            .field("ceiling", &self.ceiling)
220            .field("use_generation", &self.use_generation)
221            .field("active", &self.supervisor.is_open())
222            .finish()
223    }
224}
225
226struct ChildScopeOwner {
227    inner: Arc<ScopeInner>,
228}
229
230#[async_trait]
231impl SupervisedChild for ChildScopeOwner {
232    fn name(&self) -> &str {
233        self.inner.id.as_str()
234    }
235
236    fn cancel(&self) {
237        self.inner.supervisor.cancel();
238    }
239
240    async fn close(self: Box<Self>) -> Result<ScopeCloseReport, CapabilityScopeError> {
241        self.inner.close().await
242    }
243}
244
245/// Typed immutable catalog and monotonic governance scope.
246///
247/// The value is deliberately not `Clone`: its owner must close it explicitly.
248/// Borrowed [`CapabilityLease`] values carry the marker type and cannot outlive
249/// this owner. Dropping the owner synchronously cancels and aborts supervised
250/// tasks; it never starts asynchronous cleanup.
251#[must_use = "capability scopes own effects and must be closed explicitly"]
252pub struct CapabilityScope<K: ScopeKind> {
253    inner: Arc<ScopeInner>,
254    _kind: PhantomData<K>,
255}
256
257/// Cloneable weak registration handle for one typed capability scope.
258///
259/// A handle can transfer tasks and reversible effects into an active scope,
260/// and the permitted marker-specific methods can derive child scopes. It does
261/// not retain the scope, its immutable catalog generation, or an upstream A3S
262/// Use lease. Once the owner closes or drops the scope, every operation fails
263/// closed.
264pub struct CapabilityScopeHandle<K: ScopeKind> {
265    inner: Weak<ScopeInner>,
266    id: CapabilityScopeId,
267    _kind: PhantomData<K>,
268}
269
270impl<K: ScopeKind> Clone for CapabilityScopeHandle<K> {
271    fn clone(&self) -> Self {
272        Self {
273            inner: self.inner.clone(),
274            id: self.id.clone(),
275            _kind: PhantomData,
276        }
277    }
278}
279
280impl<K: ScopeKind> fmt::Debug for CapabilityScopeHandle<K> {
281    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
282        formatter
283            .debug_struct("CapabilityScopeHandle")
284            .field("marker", &K::KIND)
285            .field("id", &self.id)
286            .field("active", &self.is_active())
287            .finish()
288    }
289}
290
291impl<K: ScopeKind> fmt::Debug for CapabilityScope<K> {
292    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
293        formatter
294            .debug_struct("CapabilityScope")
295            .field("marker", &K::KIND)
296            .field("inner", &self.inner)
297            .finish()
298    }
299}
300
301impl CapabilityScope<Session> {
302    pub fn new_session(
303        local_id: impl Into<String>,
304        set: Arc<CapabilitySet>,
305        ceiling: CapabilityCeiling,
306    ) -> Result<Self, CapabilityScopeError> {
307        Self::new_session_with_close_policy(local_id, set, ceiling, ScopeClosePolicy::default())
308    }
309
310    pub fn new_session_with_close_policy(
311        local_id: impl Into<String>,
312        set: Arc<CapabilitySet>,
313        ceiling: CapabilityCeiling,
314        close_policy: ScopeClosePolicy,
315    ) -> Result<Self, CapabilityScopeError> {
316        Self::new_session_with_close_policy_and_cancellation(
317            local_id,
318            set,
319            ceiling,
320            close_policy,
321            CancellationToken::new(),
322        )
323    }
324
325    /// Create a Session scope rooted in a host-owned cancellation tree.
326    ///
327    /// Every admitted descendant derives its token from this exact root, so a
328    /// Run cannot remain active after its host invocation or Session lifetime
329    /// has been cancelled.
330    pub fn new_session_with_cancellation(
331        local_id: impl Into<String>,
332        set: Arc<CapabilitySet>,
333        ceiling: CapabilityCeiling,
334        cancellation: CancellationToken,
335    ) -> Result<Self, CapabilityScopeError> {
336        Self::new_session_with_close_policy_and_cancellation(
337            local_id,
338            set,
339            ceiling,
340            ScopeClosePolicy::default(),
341            cancellation,
342        )
343    }
344
345    pub fn new_session_with_close_policy_and_cancellation(
346        local_id: impl Into<String>,
347        set: Arc<CapabilitySet>,
348        ceiling: CapabilityCeiling,
349        close_policy: ScopeClosePolicy,
350        cancellation: CancellationToken,
351    ) -> Result<Self, CapabilityScopeError> {
352        if ceiling.catalog_digest() != set.digest() {
353            return Err(CapabilityScopeError::CeilingCatalogMismatch);
354        }
355        let id = CapabilityScopeId::root(CapabilityScopeKind::Session, local_id)?;
356        if cancellation.is_cancelled() {
357            return Err(CapabilityScopeError::ScopeInactive {
358                scope_id: id.to_string(),
359            });
360        }
361        let supervisor = EffectSupervisor::new(id.to_string(), cancellation, close_policy);
362        let use_generation = set.use_capability_generation().cloned();
363        Ok(Self {
364            inner: Arc::new(ScopeInner {
365                id,
366                kind: CapabilityScopeKind::Session,
367                parent_id: None,
368                set,
369                ceiling,
370                use_generation,
371                supervisor,
372                parent_registration: Mutex::new(None),
373            }),
374            _kind: PhantomData,
375        })
376    }
377
378    /// Admit a Run for a catalog without an upstream A3S Use cursor.
379    pub fn admit_run(
380        &self,
381        local_id: impl Into<String>,
382        ceiling: CapabilityCeiling,
383    ) -> Result<CapabilityScope<Run>, CapabilityScopeError> {
384        if self.inner.use_generation.is_some() {
385            return Err(CapabilityScopeError::MissingUseGenerationLease);
386        }
387        self.create_child(local_id, ceiling, None, None)
388    }
389
390    /// Admit a Run while retaining the exact, non-clone A3S Use generation
391    /// lease for the complete Run lifetime.
392    pub fn admit_use_run<L>(
393        &self,
394        local_id: impl Into<String>,
395        ceiling: CapabilityCeiling,
396        lease: L,
397    ) -> Result<CapabilityScope<Run>, CapabilityScopeError>
398    where
399        L: RetainedUseGeneration,
400    {
401        let Some(expected) = self.inner.use_generation.as_ref() else {
402            return Err(CapabilityScopeError::UnexpectedUseGenerationLease);
403        };
404        let actual = lease.use_generation();
405        if actual != expected {
406            return Err(CapabilityScopeError::UseGenerationLeaseMismatch {
407                expected_generation: expected.generation(),
408                actual_generation: actual.generation(),
409                revision_mismatch: expected.revision() != actual.revision(),
410                registry_revision_mismatch: expected.registry_revision()
411                    != actual.registry_revision(),
412            });
413        }
414        self.create_child(
415            local_id,
416            ceiling,
417            Some(Box::new(lease)),
418            Some(expected.clone()),
419        )
420    }
421}
422
423impl CapabilityScope<Run> {
424    pub fn turn(
425        &self,
426        local_id: impl Into<String>,
427        ceiling: CapabilityCeiling,
428    ) -> Result<CapabilityScope<Turn>, CapabilityScopeError> {
429        self.create_child(local_id, ceiling, None, self.inner.use_generation.clone())
430    }
431
432    pub fn subtask(
433        &self,
434        local_id: impl Into<String>,
435        ceiling: CapabilityCeiling,
436    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
437        self.create_child(local_id, ceiling, None, self.inner.use_generation.clone())
438    }
439}
440
441impl CapabilityScope<Turn> {
442    pub fn subtask(
443        &self,
444        local_id: impl Into<String>,
445        ceiling: CapabilityCeiling,
446    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
447        self.create_child(local_id, ceiling, None, self.inner.use_generation.clone())
448    }
449}
450
451impl CapabilityScope<Subtask> {
452    /// Start one model/tool Turn inside a delegated execution scope.
453    ///
454    /// This transition makes the hierarchy recursively composable: a child
455    /// Agent can own Turns, whose tool calls can in turn create Subtasks.
456    pub fn turn(
457        &self,
458        local_id: impl Into<String>,
459        ceiling: CapabilityCeiling,
460    ) -> Result<CapabilityScope<Turn>, CapabilityScopeError> {
461        self.create_child(local_id, ceiling, None, self.inner.use_generation.clone())
462    }
463
464    pub fn subtask(
465        &self,
466        local_id: impl Into<String>,
467        ceiling: CapabilityCeiling,
468    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
469        self.create_child(local_id, ceiling, None, self.inner.use_generation.clone())
470    }
471}
472
473impl CapabilityScopeHandle<Run> {
474    pub fn turn(
475        &self,
476        local_id: impl Into<String>,
477        ceiling: CapabilityCeiling,
478    ) -> Result<CapabilityScope<Turn>, CapabilityScopeError> {
479        self.create_child(local_id, ceiling)
480    }
481
482    pub fn turn_inheriting(
483        &self,
484        local_id: impl Into<String>,
485    ) -> Result<CapabilityScope<Turn>, CapabilityScopeError> {
486        self.create_child_inheriting(local_id)
487    }
488
489    pub fn subtask(
490        &self,
491        local_id: impl Into<String>,
492        ceiling: CapabilityCeiling,
493    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
494        self.create_child(local_id, ceiling)
495    }
496
497    pub fn subtask_inheriting(
498        &self,
499        local_id: impl Into<String>,
500    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
501        self.create_child_inheriting(local_id)
502    }
503}
504
505impl CapabilityScopeHandle<Turn> {
506    pub fn subtask(
507        &self,
508        local_id: impl Into<String>,
509        ceiling: CapabilityCeiling,
510    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
511        self.create_child(local_id, ceiling)
512    }
513
514    pub fn subtask_inheriting(
515        &self,
516        local_id: impl Into<String>,
517    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
518        self.create_child_inheriting(local_id)
519    }
520}
521
522impl CapabilityScopeHandle<Subtask> {
523    pub fn turn(
524        &self,
525        local_id: impl Into<String>,
526        ceiling: CapabilityCeiling,
527    ) -> Result<CapabilityScope<Turn>, CapabilityScopeError> {
528        self.create_child(local_id, ceiling)
529    }
530
531    pub fn turn_inheriting(
532        &self,
533        local_id: impl Into<String>,
534    ) -> Result<CapabilityScope<Turn>, CapabilityScopeError> {
535        self.create_child_inheriting(local_id)
536    }
537
538    pub fn subtask(
539        &self,
540        local_id: impl Into<String>,
541        ceiling: CapabilityCeiling,
542    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
543        self.create_child(local_id, ceiling)
544    }
545
546    pub fn subtask_inheriting(
547        &self,
548        local_id: impl Into<String>,
549    ) -> Result<CapabilityScope<Subtask>, CapabilityScopeError> {
550        self.create_child_inheriting(local_id)
551    }
552}
553
554impl<K: ScopeKind> CapabilityScopeHandle<K> {
555    pub fn id(&self) -> &CapabilityScopeId {
556        &self.id
557    }
558
559    pub const fn kind(&self) -> CapabilityScopeKind {
560        K::KIND
561    }
562
563    pub fn is_active(&self) -> bool {
564        self.inner
565            .upgrade()
566            .is_some_and(|inner| inner.supervisor.is_open())
567    }
568
569    pub fn cancellation(&self) -> Result<CancellationToken, CapabilityScopeError> {
570        let inner = self.upgrade()?;
571        inner.ensure_active()?;
572        Ok(inner.supervisor.cancellation())
573    }
574
575    pub fn register_effect<E>(&self, effect: E) -> Result<(), CapabilityScopeError>
576    where
577        E: CapabilityEffect,
578    {
579        let inner = self.upgrade()?;
580        inner.ensure_active()?;
581        inner.supervisor.register_effect(Box::new(effect))
582    }
583
584    pub fn spawn_task<F>(
585        &self,
586        name: impl Into<String>,
587        task: F,
588    ) -> Result<SupervisedTaskId, CapabilityScopeError>
589    where
590        F: Future<Output = Result<(), CapabilityEffectError>> + Send + 'static,
591    {
592        let inner = self.upgrade()?;
593        inner.ensure_active()?;
594        inner.supervisor.spawn_task(name, task)
595    }
596
597    pub fn cancel(&self) -> Result<(), CapabilityScopeError> {
598        let inner = self.upgrade()?;
599        inner.ensure_active()?;
600        inner.supervisor.cancel();
601        Ok(())
602    }
603
604    fn create_child<C: ScopeKind>(
605        &self,
606        local_id: impl Into<String>,
607        ceiling: CapabilityCeiling,
608    ) -> Result<CapabilityScope<C>, CapabilityScopeError> {
609        let parent = self.upgrade()?;
610        create_child_scope(
611            &parent,
612            local_id,
613            ceiling,
614            None,
615            parent.use_generation.clone(),
616        )
617    }
618
619    fn create_child_inheriting<C: ScopeKind>(
620        &self,
621        local_id: impl Into<String>,
622    ) -> Result<CapabilityScope<C>, CapabilityScopeError> {
623        let parent = self.upgrade()?;
624        create_child_scope(
625            &parent,
626            local_id,
627            parent.ceiling.clone(),
628            None,
629            parent.use_generation.clone(),
630        )
631    }
632
633    fn upgrade(&self) -> Result<Arc<ScopeInner>, CapabilityScopeError> {
634        self.inner
635            .upgrade()
636            .ok_or_else(|| CapabilityScopeError::ScopeInactive {
637                scope_id: self.id.to_string(),
638            })
639    }
640}
641
642impl<K: ScopeKind> CapabilityScope<K> {
643    pub fn id(&self) -> &CapabilityScopeId {
644        &self.inner.id
645    }
646
647    pub const fn kind(&self) -> CapabilityScopeKind {
648        K::KIND
649    }
650
651    pub fn parent_id(&self) -> Option<&CapabilityScopeId> {
652        self.inner.parent_id.as_ref()
653    }
654
655    pub fn catalog_digest(&self) -> &Sha256Digest {
656        self.inner.set.digest()
657    }
658
659    pub fn ceiling(&self) -> &CapabilityCeiling {
660        &self.inner.ceiling
661    }
662
663    pub fn use_generation(&self) -> Option<&UseCapabilityGeneration> {
664        self.inner.use_generation.as_ref()
665    }
666
667    pub fn is_active(&self) -> bool {
668        self.inner.supervisor.is_open()
669    }
670
671    pub fn cancellation(&self) -> CancellationToken {
672        self.inner.supervisor.cancellation()
673    }
674
675    pub fn handle(&self) -> CapabilityScopeHandle<K> {
676        CapabilityScopeHandle {
677            inner: Arc::downgrade(&self.inner),
678            id: self.inner.id.clone(),
679            _kind: PhantomData,
680        }
681    }
682
683    pub fn lease(&self) -> Result<CapabilityLease<'_, K>, CapabilityScopeError> {
684        self.inner.ensure_active()?;
685        Ok(CapabilityLease::new(&self.inner))
686    }
687
688    pub fn register_effect<E>(&self, effect: E) -> Result<(), CapabilityScopeError>
689    where
690        E: CapabilityEffect,
691    {
692        self.inner.supervisor.register_effect(Box::new(effect))
693    }
694
695    pub fn spawn_task<F>(
696        &self,
697        name: impl Into<String>,
698        task: F,
699    ) -> Result<SupervisedTaskId, CapabilityScopeError>
700    where
701        F: Future<Output = Result<(), CapabilityEffectError>> + Send + 'static,
702    {
703        self.inner.supervisor.spawn_task(name, task)
704    }
705
706    pub(crate) fn task_spawner(&self) -> SupervisedTaskSpawner {
707        self.inner.supervisor.task_spawner()
708    }
709
710    pub fn cancel(&self) {
711        self.inner.supervisor.cancel();
712    }
713
714    pub async fn close(&self) -> Result<ScopeCloseReport, CapabilityScopeError> {
715        self.inner.close().await
716    }
717
718    fn create_child<C: ScopeKind>(
719        &self,
720        local_id: impl Into<String>,
721        ceiling: CapabilityCeiling,
722        generation_lease: Option<Box<dyn RetainedUseGeneration>>,
723        use_generation: Option<UseCapabilityGeneration>,
724    ) -> Result<CapabilityScope<C>, CapabilityScopeError> {
725        create_child_scope(
726            &self.inner,
727            local_id,
728            ceiling,
729            generation_lease,
730            use_generation,
731        )
732    }
733}
734
735impl<K: ScopeKind> Drop for CapabilityScope<K> {
736    fn drop(&mut self) {
737        self.inner.supervisor.cancel();
738    }
739}
740
741fn create_child_scope<C: ScopeKind>(
742    parent: &Arc<ScopeInner>,
743    local_id: impl Into<String>,
744    ceiling: CapabilityCeiling,
745    generation_lease: Option<Box<dyn RetainedUseGeneration>>,
746    use_generation: Option<UseCapabilityGeneration>,
747) -> Result<CapabilityScope<C>, CapabilityScopeError> {
748    parent.ensure_active()?;
749    ceiling.ensure_within(&parent.ceiling)?;
750    let id = CapabilityScopeId::child(&parent.id, C::KIND, local_id)?;
751    let cancellation = parent.supervisor.cancellation().child_token();
752    let supervisor =
753        EffectSupervisor::new(id.to_string(), cancellation, parent.supervisor.policy());
754    if let Some(lease) = generation_lease {
755        supervisor.register_generation_lease(lease)?;
756    }
757    let child = Arc::new(ScopeInner {
758        id,
759        kind: C::KIND,
760        parent_id: Some(parent.id.clone()),
761        set: Arc::clone(&parent.set),
762        ceiling,
763        use_generation,
764        supervisor,
765        parent_registration: Mutex::new(None),
766    });
767    let registration_id = parent.supervisor.register_child(Box::new(ChildScopeOwner {
768        inner: Arc::clone(&child),
769    }))?;
770    child.set_parent_registration(parent.supervisor.downgrade(), registration_id);
771    Ok(CapabilityScope {
772        inner: child,
773        _kind: PhantomData,
774    })
775}
776
777fn validate_scope_local_id(value: &str) -> Result<(), CapabilityScopeError> {
778    if value.is_empty() {
779        return Err(CapabilityScopeError::InvalidScopeId {
780            reason: "it is empty",
781        });
782    }
783    if value.len() > MAX_CAPABILITY_IDENTIFIER_BYTES {
784        return Err(CapabilityScopeError::BoundExceeded {
785            field: "scope_local_id",
786            max: MAX_CAPABILITY_IDENTIFIER_BYTES,
787        });
788    }
789    if !value.bytes().all(|byte| {
790        byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'-' | b'_' | b'.')
791    }) || !value
792        .as_bytes()
793        .first()
794        .is_some_and(u8::is_ascii_alphanumeric)
795        || !value
796            .as_bytes()
797            .last()
798            .is_some_and(u8::is_ascii_alphanumeric)
799    {
800        return Err(CapabilityScopeError::InvalidScopeId {
801            reason: "it contains non-canonical characters or boundaries",
802        });
803    }
804    Ok(())
805}