Skip to main content

fsqlite_types/
cx.rs

1//! Capability context (`Cx`) for FrankenSQLite.
2//!
3//! This is a **capability-passing style** context object that:
4//! - threads cancellation checks (`checkpoint`) through long-running operations
5//! - carries a [`Budget`] for deadline/priority propagation
6//! - encodes available effects (spawn/time/random/io/remote) in the type system
7//!   via [`cap::CapSet`], so widening is a **compile-time error**.
8//!
9//! # Compile-time capability narrowing
10//!
11//! Narrowing always succeeds:
12//! ```
13//! use fsqlite_types::cx::{cap, Cx};
14//!
15//! let cx = Cx::<cap::All>::new();
16//! let _compute = cx.restrict::<cap::None>();
17//! ```
18//!
19//! Widening is rejected at compile time:
20//! ```compile_fail
21//! use fsqlite_types::cx::{cap, Cx};
22//!
23//! let cx = Cx::<cap::All>::new();
24//! let compute = cx.restrict::<cap::None>();
25//! let _nope = compute.restrict::<cap::All>();
26//! ```
27//
28
29use std::future::Future;
30use std::marker::PhantomData;
31use std::pin::Pin;
32#[cfg(feature = "native")]
33use std::sync::atomic::AtomicU8;
34use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
35use std::sync::{Arc, Mutex, Weak};
36use std::task::{Context as TaskContext, Poll, Waker};
37use std::time::Duration;
38
39use crate::sync_primitives::SystemTime;
40
41#[cfg(feature = "native")]
42use asupersync::types::Time as NativeTime;
43#[cfg(feature = "native")]
44use asupersync::types::{CancelKind as NativeCancelKind, CancelReason as NativeCancelReason};
45#[cfg(feature = "native")]
46use asupersync::{Budget as NativeBudget, Cx as NativeCx};
47
48#[cfg(not(feature = "native"))]
49mod native_cx_shim {
50    use std::sync::atomic::{AtomicBool, Ordering};
51    use std::sync::{Arc, Mutex};
52
53    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
54    pub enum NativeCancelKind {
55        User,
56        Timeout,
57        Deadline,
58        PollQuota,
59        CostBudget,
60        FailFast,
61        RaceLost,
62        ParentCancelled,
63        Shutdown,
64        LinkedExit,
65        ResourceUnavailable,
66    }
67
68    #[derive(Debug, Clone, PartialEq, Eq)]
69    pub struct NativeCancelReason {
70        pub kind: NativeCancelKind,
71    }
72
73    impl NativeCancelReason {
74        #[must_use]
75        pub const fn timeout() -> Self {
76            Self {
77                kind: NativeCancelKind::Timeout,
78            }
79        }
80
81        #[must_use]
82        pub fn user(_message: impl Into<String>) -> Self {
83            Self {
84                kind: NativeCancelKind::User,
85            }
86        }
87
88        #[must_use]
89        pub const fn parent_cancelled() -> Self {
90            Self {
91                kind: NativeCancelKind::ParentCancelled,
92            }
93        }
94
95        #[must_use]
96        pub const fn resource_unavailable() -> Self {
97            Self {
98                kind: NativeCancelKind::ResourceUnavailable,
99            }
100        }
101    }
102
103    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
104    pub struct NativeCheckpointError;
105
106    #[derive(Debug, Default)]
107    struct NativeCxInner {
108        cancel_requested: AtomicBool,
109        cancel_reason: Mutex<Option<NativeCancelReason>>,
110    }
111
112    #[derive(Debug, Clone, Default)]
113    pub struct NativeCx {
114        inner: Arc<NativeCxInner>,
115    }
116
117    impl NativeCx {
118        #[must_use]
119        pub fn for_testing() -> Self {
120            Self::default()
121        }
122
123        pub fn set_cancel_requested(&self, requested: bool) {
124            self.inner
125                .cancel_requested
126                .store(requested, Ordering::Release);
127            if !requested {
128                *self
129                    .inner
130                    .cancel_reason
131                    .lock()
132                    .unwrap_or_else(std::sync::PoisonError::into_inner) = None;
133            }
134        }
135
136        pub fn set_cancel_reason(&self, reason: NativeCancelReason) {
137            *self
138                .inner
139                .cancel_reason
140                .lock()
141                .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(reason);
142            self.inner.cancel_requested.store(true, Ordering::Release);
143        }
144
145        #[must_use]
146        pub fn is_cancel_requested(&self) -> bool {
147            self.inner.cancel_requested.load(Ordering::Acquire)
148        }
149
150        #[must_use]
151        pub fn cancel_reason(&self) -> Option<NativeCancelReason> {
152            self.inner
153                .cancel_reason
154                .lock()
155                .unwrap_or_else(std::sync::PoisonError::into_inner)
156                .clone()
157        }
158
159        pub fn checkpoint(&self) -> std::result::Result<(), NativeCheckpointError> {
160            if self.is_cancel_requested() {
161                Err(NativeCheckpointError)
162            } else {
163                Ok(())
164            }
165        }
166    }
167}
168
169#[cfg(not(feature = "native"))]
170use native_cx_shim::NativeCx;
171
172use crate::eprocess::{EProcessDecision, EProcessOracle, EProcessSnapshot};
173
174/// SQLite error code for `SQLITE_INTERRUPT`.
175pub const SQLITE_INTERRUPT: i32 = 9;
176
177/// Maximum nesting depth for masked cancellation sections (INV-MASK-BOUNDED).
178///
179/// Exceeding this limit panics in lab mode and emits a fatal diagnostic in production.
180pub const MAX_MASK_DEPTH: u32 = 64;
181
182// ---------------------------------------------------------------------------
183// §4.12 Cancellation State Machine
184// ---------------------------------------------------------------------------
185
186/// Observable state of a task's cancellation lifecycle (asupersync oracle model).
187///
188/// ```text
189/// Created → Running → CancelRequested → Cancelling → Finalizing → Completed
190/// ```
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
192pub enum CancelState {
193    Created,
194    Running,
195    CancelRequested,
196    Cancelling,
197    Finalizing,
198    Completed,
199}
200
201/// Reason for cancellation, ordered from weakest to strongest.
202///
203/// INV-CANCEL-IDEMPOTENT: multiple cancel requests are monotone — the strongest
204/// reason wins and the reason can never get weaker.
205#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
206pub enum CancelReason {
207    Timeout = 0,
208    UserInterrupt = 1,
209    RegionClose = 2,
210    Abort = 3,
211}
212
213/// Capability set definitions and subset reasoning.
214pub mod cap {
215    mod sealed {
216        pub trait Sealed {}
217
218        pub struct Bit<const V: bool>;
219
220        pub trait Le {}
221        impl Le for (Bit<false>, Bit<false>) {}
222        impl Le for (Bit<false>, Bit<true>) {}
223        impl Le for (Bit<true>, Bit<true>) {}
224    }
225
226    /// Type-level capability set: `[SPAWN, TIME, RANDOM, IO, REMOTE]`.
227    #[derive(Debug, Clone, Copy, Default)]
228    pub struct CapSet<
229        const SPAWN: bool,
230        const TIME: bool,
231        const RANDOM: bool,
232        const IO: bool,
233        const REMOTE: bool,
234    >;
235
236    impl<
237        const SPAWN: bool,
238        const TIME: bool,
239        const RANDOM: bool,
240        const IO: bool,
241        const REMOTE: bool,
242    > sealed::Sealed for CapSet<SPAWN, TIME, RANDOM, IO, REMOTE>
243    {
244    }
245
246    /// Full capability set.
247    pub type All = CapSet<true, true, true, true, true>;
248    /// No capabilities.
249    pub type None = CapSet<false, false, false, false, false>;
250
251    /// Type-level subset relation.
252    ///
253    /// Encodes pointwise ordering on capability bits: `false <= false`, `false <= true`,
254    /// `true <= true`. The missing impl `(true <= false)` forbids widening.
255    pub trait SubsetOf<Super>: sealed::Sealed {}
256
257    impl<
258        const S_SPAWN: bool,
259        const S_TIME: bool,
260        const S_RANDOM: bool,
261        const S_IO: bool,
262        const S_REMOTE: bool,
263        const P_SPAWN: bool,
264        const P_TIME: bool,
265        const P_RANDOM: bool,
266        const P_IO: bool,
267        const P_REMOTE: bool,
268    > SubsetOf<CapSet<P_SPAWN, P_TIME, P_RANDOM, P_IO, P_REMOTE>>
269        for CapSet<S_SPAWN, S_TIME, S_RANDOM, S_IO, S_REMOTE>
270    where
271        (sealed::Bit<S_SPAWN>, sealed::Bit<P_SPAWN>): sealed::Le,
272        (sealed::Bit<S_TIME>, sealed::Bit<P_TIME>): sealed::Le,
273        (sealed::Bit<S_RANDOM>, sealed::Bit<P_RANDOM>): sealed::Le,
274        (sealed::Bit<S_IO>, sealed::Bit<P_IO>): sealed::Le,
275        (sealed::Bit<S_REMOTE>, sealed::Bit<P_REMOTE>): sealed::Le,
276    {
277    }
278
279    pub trait HasSpawn: sealed::Sealed {}
280    impl<const TIME: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool> HasSpawn
281        for CapSet<true, TIME, RANDOM, IO, REMOTE>
282    {
283    }
284
285    pub trait HasTime: sealed::Sealed {}
286    impl<const SPAWN: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool> HasTime
287        for CapSet<SPAWN, true, RANDOM, IO, REMOTE>
288    {
289    }
290
291    pub trait HasRandom: sealed::Sealed {}
292    impl<const SPAWN: bool, const TIME: bool, const IO: bool, const REMOTE: bool> HasRandom
293        for CapSet<SPAWN, TIME, true, IO, REMOTE>
294    {
295    }
296
297    pub trait HasIo: sealed::Sealed {}
298    impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const REMOTE: bool> HasIo
299        for CapSet<SPAWN, TIME, RANDOM, true, REMOTE>
300    {
301    }
302
303    pub trait HasRemote: sealed::Sealed {}
304    impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const IO: bool> HasRemote
305        for CapSet<SPAWN, TIME, RANDOM, IO, true>
306    {
307    }
308}
309
310/// Connection-level capabilities: everything enabled.
311pub type FullCaps = cap::All;
312/// Storage-layer capabilities: time + I/O only.
313pub type StorageCaps = cap::CapSet<false, true, false, true, false>;
314/// Pure computation capabilities: no I/O, no time, no randomness.
315pub type ComputeCaps = cap::None;
316
317/// A budget for cancellation/deadline/priority propagation.
318///
319/// This is a product lattice with mixed meet/join semantics:
320/// - resource constraints tighten by `min` (deadline/poll/cost)
321/// - priority propagates by `max`
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub struct Budget {
324    /// Relative timeout translated into the receiving runtime's clock domain
325    /// when work crosses a native async boundary.
326    pub deadline: Option<Duration>,
327    pub poll_quota: u32,
328    pub cost_quota: Option<u64>,
329    pub priority: u8,
330}
331
332impl Budget {
333    /// No constraints (identity for [`Self::meet`]).
334    pub const INFINITE: Self = Self {
335        deadline: None,
336        poll_quota: u32::MAX,
337        cost_quota: None,
338        priority: 0,
339    };
340
341    /// Minimal budget for cleanup/finalizers.
342    pub const MINIMAL: Self = Self {
343        deadline: None,
344        poll_quota: 100,
345        cost_quota: None,
346        priority: 0,
347    };
348
349    #[must_use]
350    pub const fn with_deadline(self, deadline: Duration) -> Self {
351        Self {
352            deadline: Some(deadline),
353            ..self
354        }
355    }
356
357    #[must_use]
358    pub const fn with_priority(self, priority: u8) -> Self {
359        Self { priority, ..self }
360    }
361
362    #[must_use]
363    pub const fn with_poll_quota(self, poll_quota: u32) -> Self {
364        Self { poll_quota, ..self }
365    }
366
367    #[must_use]
368    pub const fn with_cost_quota(self, cost_quota: u64) -> Self {
369        Self {
370            cost_quota: Some(cost_quota),
371            ..self
372        }
373    }
374
375    /// Meet (tighten) two budgets.
376    #[must_use]
377    pub fn meet(self, other: Self) -> Self {
378        Self {
379            deadline: match (self.deadline, other.deadline) {
380                (Some(a), Some(b)) => Some(a.min(b)),
381                (Some(a), None) => Some(a),
382                (None, Some(b)) => Some(b),
383                (None, None) => None,
384            },
385            poll_quota: self.poll_quota.min(other.poll_quota),
386            cost_quota: match (self.cost_quota, other.cost_quota) {
387                (Some(a), Some(b)) => Some(a.min(b)),
388                (Some(a), None) => Some(a),
389                (None, Some(b)) => Some(b),
390                (None, None) => None,
391            },
392            priority: self.priority.max(other.priority),
393        }
394    }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
398pub enum ErrorKind {
399    Cancelled,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct Error {
404    kind: ErrorKind,
405}
406
407impl std::fmt::Display for Error {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        match self.kind {
410            ErrorKind::Cancelled => write!(f, "operation cancelled"),
411        }
412    }
413}
414
415impl std::error::Error for Error {}
416
417impl Error {
418    #[must_use]
419    pub const fn cancelled() -> Self {
420        Self {
421            kind: ErrorKind::Cancelled,
422        }
423    }
424
425    #[must_use]
426    pub const fn kind(&self) -> ErrorKind {
427        self.kind
428    }
429
430    #[must_use]
431    pub const fn sqlite_error_code(&self) -> i32 {
432        match self.kind {
433            ErrorKind::Cancelled => SQLITE_INTERRUPT,
434        }
435    }
436}
437
438pub type Result<T, E = Error> = std::result::Result<T, E>;
439
440#[derive(Debug)]
441struct LocalCancelWaiter {
442    id: u64,
443    waker: Waker,
444}
445
446#[derive(Debug, Default)]
447struct LocalCancelWaiters {
448    next_id: u64,
449    entries: Vec<LocalCancelWaiter>,
450}
451
452impl LocalCancelWaiters {
453    fn allocate_id(&mut self) -> u64 {
454        loop {
455            let id = self.next_id;
456            self.next_id = self.next_id.wrapping_add(1);
457            if self.entries.iter().all(|waiter| waiter.id != id) {
458                return id;
459            }
460        }
461    }
462
463    fn remove(&mut self, id: u64) -> Option<LocalCancelWaiter> {
464        self.entries
465            .iter()
466            .position(|waiter| waiter.id == id)
467            .map(|position| self.entries.swap_remove(position))
468    }
469}
470
471#[derive(Debug)]
472struct CxInner {
473    cancel_requested: AtomicBool,
474    // Strongest ordinary cancellation for this local node. Kept separate
475    // from `cancel_reason` so operation-local cancellation is never mirrored
476    // into native I/O. NativeCx clones remain upstream-owned overwrite handles;
477    // this rank intentionally does not claim cross-sibling arbitration.
478    #[cfg(feature = "native")]
479    native_cancel_reason: AtomicU8,
480    cancel_state: Mutex<CancelState>,
481    cancel_reason: Mutex<Option<CancelReason>>,
482    mask_depth: AtomicU32,
483    cancel_dispatch_gate: Arc<Mutex<()>>,
484    local_cancel_waiters: Mutex<LocalCancelWaiters>,
485    children: Mutex<Vec<Weak<Self>>>,
486    last_checkpoint_msg: Mutex<Option<String>>,
487    last_eprocess_decision: Mutex<Option<EProcessDecision>>,
488    eprocess_oracle: std::sync::OnceLock<Arc<EProcessOracle>>,
489    #[cfg(feature = "native")]
490    attached_native_cx: Mutex<Option<NativeCx>>,
491    #[cfg(feature = "native")]
492    fallback_native_cx: std::sync::OnceLock<NativeCx>,
493    // bd-bjm5d: set only on the root Cx of a dedicated engine OS thread
494    // that owns its Connection exclusively and is not a shared scheduler
495    // worker. Grants VFS backends permission to issue bounded EINTR-safe
496    // positional I/O inline instead of hopping to the blocking pool.
497    // Deliberately NOT feature-gated: this is an OS-thread-ownership
498    // property, not an asupersync property.
499    blocking_io_inline_safe: AtomicBool,
500    // Deterministic clock override: milliseconds since epoch for tests.
501    //
502    // The mode bit is separate from the value so every `u64`, including zero
503    // and `u64::MAX`, remains a valid fixed timestamp. Writers publish the
504    // value before the mode bit with release ordering; readers acquire the
505    // mode before loading the value.
506    unix_millis: AtomicU64,
507    unix_millis_is_fixed: AtomicBool,
508}
509
510impl CxInner {
511    fn new(cancel_dispatch_gate: Arc<Mutex<()>>) -> Self {
512        Self {
513            cancel_requested: AtomicBool::new(false),
514            #[cfg(feature = "native")]
515            native_cancel_reason: AtomicU8::new(0),
516            cancel_state: Mutex::new(CancelState::Created),
517            cancel_reason: Mutex::new(None),
518            mask_depth: AtomicU32::new(0),
519            cancel_dispatch_gate,
520            local_cancel_waiters: Mutex::new(LocalCancelWaiters::default()),
521            children: Mutex::new(Vec::new()),
522            last_checkpoint_msg: Mutex::new(None),
523            last_eprocess_decision: Mutex::new(None),
524            eprocess_oracle: std::sync::OnceLock::new(),
525            #[cfg(feature = "native")]
526            attached_native_cx: Mutex::new(None),
527            #[cfg(feature = "native")]
528            fallback_native_cx: std::sync::OnceLock::new(),
529            blocking_io_inline_safe: AtomicBool::new(false),
530            unix_millis: AtomicU64::new(0),
531            unix_millis_is_fixed: AtomicBool::new(false),
532        }
533    }
534}
535
536#[cfg(feature = "native")]
537#[must_use]
538fn local_reason_to_native(reason: CancelReason) -> NativeCancelReason {
539    match reason {
540        CancelReason::Timeout => NativeCancelReason::timeout(),
541        CancelReason::UserInterrupt => NativeCancelReason::user("sqlite interrupt"),
542        CancelReason::RegionClose => NativeCancelReason::parent_cancelled(),
543        CancelReason::Abort => NativeCancelReason::resource_unavailable(),
544    }
545}
546
547#[cfg(feature = "native")]
548#[must_use]
549fn native_reason_to_local(reason: &NativeCancelReason) -> CancelReason {
550    match reason.kind {
551        NativeCancelKind::User => CancelReason::UserInterrupt,
552        NativeCancelKind::Timeout
553        | NativeCancelKind::Deadline
554        | NativeCancelKind::PollQuota
555        | NativeCancelKind::CostBudget => CancelReason::Timeout,
556        NativeCancelKind::FailFast
557        | NativeCancelKind::RaceLost
558        | NativeCancelKind::ParentCancelled
559        | NativeCancelKind::Shutdown
560        | NativeCancelKind::LinkedExit => CancelReason::RegionClose,
561        NativeCancelKind::ResourceUnavailable => CancelReason::Abort,
562    }
563}
564
565#[cfg(feature = "native")]
566const fn encode_native_cancel_reason(reason: CancelReason) -> u8 {
567    match reason {
568        CancelReason::Timeout => 1,
569        CancelReason::UserInterrupt => 2,
570        CancelReason::RegionClose => 3,
571        CancelReason::Abort => 4,
572    }
573}
574
575#[cfg(feature = "native")]
576fn decode_native_cancel_reason(encoded: u8) -> Option<CancelReason> {
577    match encoded {
578        0 => None,
579        1 => Some(CancelReason::Timeout),
580        2 => Some(CancelReason::UserInterrupt),
581        3 => Some(CancelReason::RegionClose),
582        4 => Some(CancelReason::Abort),
583        _ => unreachable!("invalid native cancellation reason rank"),
584    }
585}
586
587#[cfg(feature = "native")]
588fn record_native_cancel_reason(inner: &CxInner, reason: CancelReason) {
589    inner
590        .native_cancel_reason
591        .fetch_max(encode_native_cancel_reason(reason), Ordering::AcqRel);
592}
593
594#[cfg(feature = "native")]
595fn mirrored_native_cancel_reason(inner: &CxInner) -> Option<CancelReason> {
596    decode_native_cancel_reason(inner.native_cancel_reason.load(Ordering::Acquire))
597}
598
599#[cfg(feature = "native")]
600fn sync_one_native_cx_cancel(inner: &CxInner, native: &NativeCx) {
601    let mut encoded = inner.native_cancel_reason.load(Ordering::Acquire);
602    while let Some(reason) = decode_native_cancel_reason(encoded) {
603        // NativeCx drops its own lock before invoking arbitrary wakers. Hold
604        // no FrankenSQLite lock across this callback boundary.
605        native.set_cancel_reason(local_reason_to_native(reason));
606        let latest = inner.native_cancel_reason.load(Ordering::Acquire);
607        if latest == encoded {
608            break;
609        }
610        encoded = latest;
611    }
612}
613
614#[cfg(feature = "native")]
615#[must_use]
616#[allow(dead_code)]
617fn native_budget_from_local_at(budget: Budget, now: NativeTime) -> NativeBudget {
618    let mut native_budget = NativeBudget::new()
619        .with_poll_quota(budget.poll_quota)
620        .with_priority(budget.priority);
621    if let Some(cost_quota) = budget.cost_quota {
622        native_budget = native_budget.with_cost_quota(cost_quota);
623    }
624    if let Some(timeout) = budget.deadline {
625        native_budget = native_budget.with_timeout(now, timeout);
626    }
627    native_budget
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq)]
631enum NativeCancelPropagation {
632    LocalAndNative,
633    LocalOnly,
634}
635
636#[must_use]
637fn local_cancellation_matches(inner: &CxInner, respect_mask: bool) -> bool {
638    inner.cancel_requested.load(Ordering::Acquire)
639        && (!respect_mask || inner.mask_depth.load(Ordering::Acquire) == 0)
640}
641
642fn take_local_cancel_waiters(inner: &CxInner) -> Vec<LocalCancelWaiter> {
643    let mut waiters = inner
644        .local_cancel_waiters
645        .lock()
646        .unwrap_or_else(std::sync::PoisonError::into_inner);
647    std::mem::take(&mut waiters.entries)
648}
649
650fn capture_cancel_callback_panic(
651    first_panic: &mut Option<Box<dyn std::any::Any + Send>>,
652    callback: impl FnOnce(),
653) {
654    if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(callback)) {
655        if first_panic.is_none() {
656            *first_panic = Some(payload);
657        } else {
658            // A panic payload may itself panic when dropped. The first panic
659            // is the one we report; leaking a secondary payload is preferable
660            // to aborting before the remaining cancellation observers run.
661            std::mem::forget(payload);
662        }
663    }
664}
665
666fn dispatch_local_cancel_waiters(
667    waiters: Vec<LocalCancelWaiter>,
668    first_panic: &mut Option<Box<dyn std::any::Any + Send>>,
669) {
670    // A Waker callback or final destructor may run arbitrary executor code.
671    // Both therefore happen only after the FrankenSQLite mutex is released.
672    // One panicking callback must not prevent the remaining observers from
673    // being notified.
674    for waiter in waiters {
675        capture_cancel_callback_panic(first_panic, || waiter.waker.wake_by_ref());
676        capture_cancel_callback_panic(first_panic, || drop(waiter));
677    }
678}
679
680fn publish_cancel_state(
681    inner: &CxInner,
682    local_reason: CancelReason,
683    native_reason: Option<CancelReason>,
684) -> CancelReason {
685    #[cfg(not(feature = "native"))]
686    let _ = native_reason;
687
688    // Monotone reason update.
689    let effective_local_reason = {
690        let mut r = inner
691            .cancel_reason
692            .lock()
693            .unwrap_or_else(std::sync::PoisonError::into_inner);
694        match *r {
695            Some(existing) if existing >= local_reason => existing,
696            _ => {
697                *r = Some(local_reason);
698                local_reason
699            }
700        }
701    };
702
703    // State transition: Created/Running → CancelRequested.
704    {
705        let mut state = inner
706            .cancel_state
707            .lock()
708            .unwrap_or_else(std::sync::PoisonError::into_inner);
709        if matches!(*state, CancelState::Created | CancelState::Running) {
710            *state = CancelState::CancelRequested;
711        }
712    }
713
714    // Record only cancellation requests that are allowed to cross the native
715    // boundary. This reason is deliberately separate from the aggregate local
716    // reason: a stronger operation-local cancellation must never be laundered
717    // into a shared native context by a later, weaker ordinary cancellation.
718    #[cfg(feature = "native")]
719    if let Some(reason) = native_reason {
720        record_native_cancel_reason(inner, reason);
721    }
722
723    // Publish the fast-path flag only after the reason and state are visible.
724    // This gives attachment and child-registration rechecks a stable reason to
725    // consume once they observe cancellation.
726    inner.cancel_requested.store(true, Ordering::Release);
727
728    effective_local_reason
729}
730
731fn try_append_live_children(
732    inner: &CxInner,
733    descendants: &mut Vec<Arc<CxInner>>,
734) -> std::result::Result<(), std::collections::TryReserveError> {
735    let mut children = inner
736        .children
737        .lock()
738        .unwrap_or_else(std::sync::PoisonError::into_inner);
739    children.retain(|child| child.strong_count() > 0);
740    descendants.try_reserve(children.len())?;
741    for child in children.iter().filter_map(Weak::upgrade) {
742        descendants.push(child);
743    }
744    Ok(())
745}
746
747#[must_use]
748fn local_cancel_waiter_count(inner: &CxInner) -> usize {
749    inner
750        .local_cancel_waiters
751        .lock()
752        .unwrap_or_else(std::sync::PoisonError::into_inner)
753        .entries
754        .len()
755}
756
757fn drain_local_cancel_waiters_into(inner: &CxInner, waiters: &mut Vec<LocalCancelWaiter>) {
758    let mut registered_waiters = inner
759        .local_cancel_waiters
760        .lock()
761        .unwrap_or_else(std::sync::PoisonError::into_inner);
762    debug_assert!(
763        waiters.capacity() - waiters.len() >= registered_waiters.entries.len(),
764        "cancellation waiter capacity must be reserved before publication"
765    );
766    waiters.append(&mut registered_waiters.entries);
767}
768
769#[cfg(feature = "native")]
770struct NativeCancelTarget {
771    descendant_index: Option<usize>,
772    native_cx: NativeCx,
773}
774
775#[cfg(feature = "native")]
776fn append_native_cancel_targets(
777    inner: &CxInner,
778    descendant_index: Option<usize>,
779    targets: &mut Vec<NativeCancelTarget>,
780) {
781    let attached_native = inner
782        .attached_native_cx
783        .lock()
784        .unwrap_or_else(std::sync::PoisonError::into_inner)
785        .as_ref()
786        .cloned();
787    if let Some(native_cx) = attached_native {
788        targets.push(NativeCancelTarget {
789            descendant_index,
790            native_cx,
791        });
792    }
793
794    if let Some(native_cx) = inner.fallback_native_cx.get().cloned() {
795        targets.push(NativeCancelTarget {
796            descendant_index,
797            native_cx,
798        });
799    }
800}
801
802/// Propagate cancellation to a `CxInner` node and all its descendants.
803///
804/// The explicit worklist avoids consuming one physical stack frame per
805/// descendant. Each node lock is released before its children are visited.
806fn propagate_cancel_tree(
807    inner: &CxInner,
808    reason: CancelReason,
809    native_propagation: NativeCancelPropagation,
810) {
811    let mut descendants = Vec::new();
812    let mut waiters = Vec::new();
813    #[cfg(feature = "native")]
814    let mut native_targets = Vec::new();
815    let mut reserve_error = None;
816    {
817        // Every context in one parent/child family shares this phase gate.
818        // Only state publication and waiter extraction happen while it is
819        // held; arbitrary native and executor callbacks run after unlock.
820        let _dispatch = inner
821            .cancel_dispatch_gate
822            .lock()
823            .unwrap_or_else(std::sync::PoisonError::into_inner);
824        let native_reason = match native_propagation {
825            NativeCancelPropagation::LocalAndNative => Some(reason),
826            NativeCancelPropagation::LocalOnly => None,
827        };
828
829        if let Err(error) = try_append_live_children(inner, &mut descendants) {
830            reserve_error = Some(error);
831        }
832        let mut cursor = 0;
833        while reserve_error.is_none() && cursor < descendants.len() {
834            let node = Arc::clone(&descendants[cursor]);
835            if let Err(error) = try_append_live_children(&node, &mut descendants) {
836                reserve_error = Some(error);
837                break;
838            }
839            cursor += 1;
840        }
841
842        if reserve_error.is_none() {
843            // Pending registration takes the same family gate. Concurrent
844            // unregistration can only make this count smaller, so one
845            // aggregate reservation covers every later append.
846            let waiter_count = descendants
847                .iter()
848                .fold(local_cancel_waiter_count(inner), |count, node| {
849                    count.saturating_add(local_cancel_waiter_count(node))
850                });
851            if let Err(error) = waiters.try_reserve(waiter_count) {
852                reserve_error = Some(error);
853            }
854        }
855
856        #[cfg(feature = "native")]
857        if reserve_error.is_none() && native_propagation == NativeCancelPropagation::LocalAndNative
858        {
859            let target_capacity = descendants.len().saturating_mul(2).saturating_add(2);
860            if let Err(error) = native_targets.try_reserve(target_capacity) {
861                reserve_error = Some(error);
862            } else {
863                append_native_cancel_targets(inner, None, &mut native_targets);
864                for (index, node) in descendants.iter().enumerate() {
865                    append_native_cancel_targets(node, Some(index), &mut native_targets);
866                }
867            }
868        }
869
870        if reserve_error.is_none() {
871            let propagated_local_reason = publish_cancel_state(inner, reason, native_reason);
872            drain_local_cancel_waiters_into(inner, &mut waiters);
873
874            #[cfg(feature = "native")]
875            let propagated_native_reason = match native_propagation {
876                NativeCancelPropagation::LocalAndNative => mirrored_native_cancel_reason(inner),
877                NativeCancelPropagation::LocalOnly => None,
878            };
879            #[cfg(not(feature = "native"))]
880            let propagated_native_reason = None;
881
882            for node in &descendants {
883                publish_cancel_state(node, propagated_local_reason, propagated_native_reason);
884                drain_local_cancel_waiters_into(node, &mut waiters);
885            }
886        }
887    }
888
889    // Every node visible when this phase began has its cancellation flag
890    // published before this batch invokes native or executor callbacks.
891    // Reasons are monotone per node; concurrent stronger batches may advance
892    // them further after this phase releases the family gate.
893    if let Some(error) = reserve_error {
894        panic!("failed to reserve cancellation propagation storage: {error}");
895    }
896
897    let mut first_panic = None;
898    #[cfg(feature = "native")]
899    for target in &native_targets {
900        let target_inner = target
901            .descendant_index
902            .map_or(inner, |index| &descendants[index]);
903        capture_cancel_callback_panic(&mut first_panic, || {
904            sync_one_native_cx_cancel(target_inner, &target.native_cx);
905        });
906    }
907    dispatch_local_cancel_waiters(waiters, &mut first_panic);
908    if let Some(payload) = first_panic {
909        std::panic::resume_unwind(payload);
910    }
911}
912
913fn propagate_cancel(inner: &CxInner, reason: CancelReason) {
914    propagate_cancel_tree(inner, reason, NativeCancelPropagation::LocalAndNative);
915}
916
917fn propagate_local_cancel(inner: &CxInner, reason: CancelReason) {
918    propagate_cancel_tree(inner, reason, NativeCancelPropagation::LocalOnly);
919}
920
921/// Cancellation-only authority for one derived [`Cx`] subtree.
922///
923/// This relay deliberately carries only a weak reference to the local
924/// FrankenSQLite cancellation state. It cannot expose the target context,
925/// runtime effects, I/O authority, budgets, or native asupersync context.
926/// Local cancellation reaches the target and its descendants without
927/// cancelling an attached native context that may be shared with the worker
928/// root. Calling the relay performs synchronous local bookkeeping and subtree
929/// traversal; actor integrations must not invoke it from a nonblocking `Drop`.
930#[derive(Debug)]
931#[must_use = "dropping the relay discards the authority to cancel the derived operation"]
932pub struct LocalCancelRelay {
933    inner: Weak<CxInner>,
934}
935
936impl LocalCancelRelay {
937    /// Request cancellation of the derived local context subtree.
938    ///
939    /// The strongest [`CancelReason`] remains monotone. Returns `false` when
940    /// the target context has already been dropped; otherwise returns `true`,
941    /// including for an idempotent repeated request.
942    #[must_use]
943    pub fn cancel_local(&self, reason: CancelReason) -> bool {
944        let Some(inner) = self.inner.upgrade() else {
945            return false;
946        };
947        propagate_local_cancel(&inner, reason);
948        true
949    }
950}
951
952/// Scoped notification of local cancellation for one [`Cx`].
953///
954/// This future watches only FrankenSQLite's local cancellation state. It does
955/// not inspect or modify a native runtime context, carry effect capabilities,
956/// or create a child context. The constructor determines whether masking
957/// defers readiness or whether the raw request itself is sufficient.
958///
959/// A pending registration is removed on `Drop`, so repeatedly creating and
960/// abandoning these futures does not accumulate stale child or waker entries.
961#[derive(Debug)]
962#[must_use = "futures do nothing unless polled or awaited"]
963pub struct LocalCancellation<'a> {
964    inner: &'a CxInner,
965    waiter_id: Option<u64>,
966    respect_mask: bool,
967}
968
969impl LocalCancellation<'_> {
970    fn unregister(&mut self) {
971        let Some(id) = self.waiter_id.take() else {
972            return;
973        };
974        let retired = {
975            let mut waiters = self
976                .inner
977                .local_cancel_waiters
978                .lock()
979                .unwrap_or_else(std::sync::PoisonError::into_inner);
980            waiters.remove(id)
981        };
982        // Releasing the final Waker reference may invoke executor code.
983        drop(retired);
984    }
985}
986
987impl Future for LocalCancellation<'_> {
988    type Output = ();
989
990    fn poll(mut self: Pin<&mut Self>, task_cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
991        let mut prepared_waker = None;
992        let this = self.as_mut().get_mut();
993        let inner = this.inner;
994
995        loop {
996            if local_cancellation_matches(inner, this.respect_mask) {
997                this.unregister();
998                drop(prepared_waker);
999                return Poll::Ready(());
1000            }
1001
1002            // Registration participates in the same family phase gate as
1003            // tree publication. This lets cancellation pre-reserve one flat
1004            // waiter buffer for the complete tree before it changes any
1005            // node. Waker cloning and destruction still happen after both
1006            // internal locks are released.
1007            let dispatch = inner
1008                .cancel_dispatch_gate
1009                .lock()
1010                .unwrap_or_else(std::sync::PoisonError::into_inner);
1011            let mut waiters = inner
1012                .local_cancel_waiters
1013                .lock()
1014                .unwrap_or_else(std::sync::PoisonError::into_inner);
1015            if local_cancellation_matches(inner, this.respect_mask) {
1016                let retired = this.waiter_id.take().and_then(|id| waiters.remove(id));
1017                drop(waiters);
1018                drop(dispatch);
1019                drop(retired);
1020                drop(prepared_waker);
1021                return Poll::Ready(());
1022            }
1023
1024            let existing_position = this.waiter_id.and_then(|id| {
1025                waiters
1026                    .entries
1027                    .iter()
1028                    .position(|registered| registered.id == id)
1029            });
1030            if existing_position
1031                .is_some_and(|position| waiters.entries[position].waker.will_wake(task_cx.waker()))
1032            {
1033                drop(waiters);
1034                drop(dispatch);
1035                drop(prepared_waker);
1036                return Poll::Pending;
1037            }
1038
1039            if existing_position.is_none()
1040                && let Err(error) = waiters.entries.try_reserve(1)
1041            {
1042                drop(waiters);
1043                drop(dispatch);
1044                drop(prepared_waker);
1045                panic!("failed to reserve local-cancellation waiter storage: {error}");
1046            }
1047
1048            let Some(new_waker) = prepared_waker.take() else {
1049                // RawWaker::clone may execute arbitrary user code, so prepare
1050                // the replacement only after releasing the internal mutex.
1051                drop(waiters);
1052                drop(dispatch);
1053                prepared_waker = Some(task_cx.waker().clone());
1054                continue;
1055            };
1056
1057            let retired = if let Some(position) = existing_position {
1058                Some(std::mem::replace(
1059                    &mut waiters.entries[position].waker,
1060                    new_waker,
1061                ))
1062            } else {
1063                let id = waiters.allocate_id();
1064                waiters.entries.push(LocalCancelWaiter {
1065                    id,
1066                    waker: new_waker,
1067                });
1068                this.waiter_id = Some(id);
1069                None
1070            };
1071            drop(waiters);
1072            drop(dispatch);
1073            // Waker destruction is deliberately deferred until after every
1074            // internal lock is released.
1075            drop(retired);
1076            return Poll::Pending;
1077        }
1078    }
1079}
1080
1081impl Drop for LocalCancellation<'_> {
1082    fn drop(&mut self) {
1083        self.unregister();
1084    }
1085}
1086
1087/// Capability context passed through all effectful operations.
1088///
1089/// Carries tracing identifiers (`trace_id`, `decision_id`, `policy_id`) that
1090/// propagate through all context derivations (clone, restrict, scope, child).
1091/// A value of `0` means "unset / not assigned".
1092#[derive(Debug)]
1093pub struct Cx<Caps: cap::SubsetOf<cap::All> = FullCaps> {
1094    inner: Arc<CxInner>,
1095    budget: Budget,
1096    trace_id: u64,
1097    decision_id: u64,
1098    policy_id: u64,
1099    // fn() -> Caps ensures Send+Sync regardless of Caps marker type.
1100    _caps: PhantomData<fn() -> Caps>,
1101}
1102
1103impl<Caps: cap::SubsetOf<cap::All>> Clone for Cx<Caps> {
1104    fn clone(&self) -> Self {
1105        Self {
1106            inner: Arc::clone(&self.inner),
1107            budget: self.budget,
1108            trace_id: self.trace_id,
1109            decision_id: self.decision_id,
1110            policy_id: self.policy_id,
1111            _caps: PhantomData,
1112        }
1113    }
1114}
1115
1116impl Default for Cx<FullCaps> {
1117    fn default() -> Self {
1118        Self::new()
1119    }
1120}
1121
1122impl Cx<FullCaps> {
1123    #[must_use]
1124    pub fn new() -> Self {
1125        Self::with_budget(Budget::INFINITE)
1126    }
1127
1128    /// Mint a fresh, fully **detached** root context for MANDATORY post-cancel
1129    /// cleanup (VACUUM rebind, drop-time external unlock / snapshot-restore).
1130    ///
1131    /// This is the SANCTIONED direct constructor for runtime crates. The audit
1132    /// gate `test_no_direct_cx_constructors_in_runtime_production_code` forbids
1133    /// a bare `Cx::new()` / `Cx::default()` in runtime crates precisely so this
1134    /// documented path is the only way to obtain a detached root context — one
1135    /// with its own `CxInner`, no cancel lineage, and no recorded native-cancel
1136    /// reason. Unlike a masked `create_child()`, such a context keeps running
1137    /// even when the operation `Cx` (and its attached native cx) were cancelled
1138    /// after publication: the db-file write lock's native fast path polls the
1139    /// attached native cx directly, bypassing `masked()`, so a masked child is
1140    /// insufficient. Callers re-attach the live task's native cx via
1141    /// [`Cx::set_native_cx`] and, where trace continuity matters, chain
1142    /// [`Cx::with_trace_context`]. See `Connection::detached_rebind_cx` and the
1143    /// pager drop-cleanup path (bd-gzyk1 / GH#348, VACUUM rebind).
1144    #[must_use]
1145    pub fn detached_rebind() -> Self {
1146        Self::new()
1147    }
1148}
1149
1150impl<Caps: cap::SubsetOf<cap::All>> Cx<Caps> {
1151    #[cfg(all(feature = "native", test))]
1152    #[must_use]
1153    #[allow(dead_code)]
1154    fn effective_native_cx(&self) -> NativeCx {
1155        let native = {
1156            let _dispatch = self
1157                .inner
1158                .cancel_dispatch_gate
1159                .lock()
1160                .unwrap_or_else(std::sync::PoisonError::into_inner);
1161            let attached_native = self
1162                .inner
1163                .attached_native_cx
1164                .lock()
1165                .unwrap_or_else(std::sync::PoisonError::into_inner)
1166                .as_ref()
1167                .cloned();
1168            attached_native.unwrap_or_else(|| {
1169                self.inner
1170                    .fallback_native_cx
1171                    .get_or_init(|| {
1172                        NativeCx::for_request_with_budget(native_budget_from_local_at(
1173                            self.budget,
1174                            asupersync::time::wall_now(),
1175                        ))
1176                    })
1177                    .clone()
1178            })
1179        };
1180        // Recheck after releasing the family publication gate: NativeCx
1181        // dispatches arbitrary wakers synchronously, so no FrankenSQLite lock
1182        // may be held while mirroring the reason.
1183        sync_one_native_cx_cancel(&self.inner, &native);
1184        native
1185    }
1186
1187    #[cfg(feature = "native")]
1188    #[must_use]
1189    fn native_cx_for_checkpoint(&self) -> Option<NativeCx> {
1190        let attached_native = self
1191            .inner
1192            .attached_native_cx
1193            .lock()
1194            .unwrap_or_else(std::sync::PoisonError::into_inner)
1195            .as_ref()
1196            .cloned();
1197        attached_native.or_else(|| self.inner.fallback_native_cx.get().cloned())
1198    }
1199
1200    #[must_use]
1201    pub fn with_budget(budget: Budget) -> Self {
1202        Self::with_budget_and_cancel_dispatch(budget, Arc::new(Mutex::new(())))
1203    }
1204
1205    fn with_budget_and_cancel_dispatch(
1206        budget: Budget,
1207        cancel_dispatch_gate: Arc<Mutex<()>>,
1208    ) -> Self {
1209        Self {
1210            inner: Arc::new(CxInner::new(cancel_dispatch_gate)),
1211            budget,
1212            trace_id: 0,
1213            decision_id: 0,
1214            policy_id: 0,
1215            _caps: PhantomData,
1216        }
1217    }
1218
1219    #[must_use]
1220    pub fn budget(&self) -> Budget {
1221        self.budget
1222    }
1223
1224    // -----------------------------------------------------------------------
1225    // Tracing IDs (§4 Cx capability context threading)
1226    // -----------------------------------------------------------------------
1227
1228    /// The trace ID for this context (0 = unset).
1229    #[must_use]
1230    pub fn trace_id(&self) -> u64 {
1231        self.trace_id
1232    }
1233
1234    /// The decision ID for this context (0 = unset).
1235    #[must_use]
1236    pub fn decision_id(&self) -> u64 {
1237        self.decision_id
1238    }
1239
1240    /// The policy ID for this context (0 = unset).
1241    #[must_use]
1242    pub fn policy_id(&self) -> u64 {
1243        self.policy_id
1244    }
1245
1246    /// Set all three tracing identifiers at once.
1247    ///
1248    /// Typically called once when a connection or request is initialized.
1249    #[must_use]
1250    pub fn with_trace_context(mut self, trace_id: u64, decision_id: u64, policy_id: u64) -> Self {
1251        self.trace_id = trace_id;
1252        self.decision_id = decision_id;
1253        self.policy_id = policy_id;
1254        self
1255    }
1256
1257    /// Return a new context with only the `decision_id` changed.
1258    ///
1259    /// Used when starting a new operation within the same trace.
1260    #[must_use]
1261    pub fn with_decision_id(mut self, decision_id: u64) -> Self {
1262        self.decision_id = decision_id;
1263        self
1264    }
1265
1266    /// Return a new context with only the `policy_id` changed.
1267    #[must_use]
1268    pub fn with_policy_id(mut self, policy_id: u64) -> Self {
1269        self.policy_id = policy_id;
1270        self
1271    }
1272
1273    /// Returns a view of this context with a tighter effective budget.
1274    ///
1275    /// The effective budget is computed as `self.budget.meet(child)`, so the
1276    /// child cannot loosen its parent's constraints.
1277    /// Tracing IDs propagate unchanged.
1278    #[must_use]
1279    pub fn scope_with_budget(&self, child: Budget) -> Self {
1280        Self {
1281            inner: Arc::clone(&self.inner),
1282            budget: self.budget.meet(child),
1283            trace_id: self.trace_id,
1284            decision_id: self.decision_id,
1285            policy_id: self.policy_id,
1286            _caps: PhantomData,
1287        }
1288    }
1289
1290    /// Returns a cleanup scope that uses [`Budget::MINIMAL`].
1291    #[must_use]
1292    pub fn cleanup_scope(&self) -> Self {
1293        self.scope_with_budget(Budget::MINIMAL)
1294    }
1295
1296    /// Re-type this context to a narrower capability set.
1297    ///
1298    /// This is zero-cost at runtime and shares cancellation state.
1299    #[must_use]
1300    pub fn restrict<NewCaps>(&self) -> Cx<NewCaps>
1301    where
1302        NewCaps: cap::SubsetOf<cap::All> + cap::SubsetOf<Caps>,
1303    {
1304        self.retype()
1305    }
1306
1307    /// Internal re-typing helper without subset enforcement.
1308    #[must_use]
1309    fn retype<NewCaps>(&self) -> Cx<NewCaps>
1310    where
1311        NewCaps: cap::SubsetOf<cap::All>,
1312    {
1313        Cx {
1314            inner: Arc::clone(&self.inner),
1315            budget: self.budget,
1316            trace_id: self.trace_id,
1317            decision_id: self.decision_id,
1318            policy_id: self.policy_id,
1319            _caps: PhantomData,
1320        }
1321    }
1322
1323    // -----------------------------------------------------------------------
1324    // Cancellation state machine (§4.12)
1325    // -----------------------------------------------------------------------
1326
1327    #[must_use]
1328    pub fn is_cancel_requested(&self) -> bool {
1329        self.inner.cancel_requested.load(Ordering::Acquire)
1330    }
1331
1332    /// Wait until local cancellation is observable at an unmasked boundary.
1333    ///
1334    /// The returned future borrows this context's existing cancellation node;
1335    /// it does not allocate a child context or attach a native runtime context.
1336    /// Dropping it removes any pending waker registration.
1337    pub fn wait_for_local_cancellation(&self) -> LocalCancellation<'_> {
1338        LocalCancellation {
1339            inner: &self.inner,
1340            waiter_id: None,
1341            respect_mask: true,
1342        }
1343    }
1344
1345    /// Wait until local cancellation has been requested, even while masked.
1346    ///
1347    /// This is a raw wakeup primitive for cancellation-sensitive admission
1348    /// machinery that separately defines whether masked operation is legal.
1349    /// It does not transition cancellation state or inspect a native context.
1350    pub fn wait_for_local_cancel_request(&self) -> LocalCancellation<'_> {
1351        LocalCancellation {
1352            inner: &self.inner,
1353            waiter_id: None,
1354            respect_mask: false,
1355        }
1356    }
1357
1358    /// Request cancellation with the default reason (`UserInterrupt`).
1359    ///
1360    /// Propagates to all child contexts per INV-CANCEL-PROPAGATES.
1361    pub fn cancel(&self) {
1362        self.cancel_with_reason(CancelReason::UserInterrupt);
1363    }
1364
1365    /// Request cancellation with an explicit reason.
1366    ///
1367    /// INV-CANCEL-IDEMPOTENT: the strongest reason wins; weaker reasons are
1368    /// ignored once a stronger one has been set.
1369    ///
1370    /// INV-CANCEL-PROPAGATES: cancellation propagates to all descendants.
1371    pub fn cancel_with_reason(&self, reason: CancelReason) {
1372        propagate_cancel(&self.inner, reason);
1373    }
1374
1375    /// Current state in the cancellation lifecycle.
1376    #[must_use]
1377    pub fn cancel_state(&self) -> CancelState {
1378        *self
1379            .inner
1380            .cancel_state
1381            .lock()
1382            .unwrap_or_else(std::sync::PoisonError::into_inner)
1383    }
1384
1385    /// The strongest cancellation reason set so far, if any.
1386    #[must_use]
1387    pub fn cancel_reason(&self) -> Option<CancelReason> {
1388        *self
1389            .inner
1390            .cancel_reason
1391            .lock()
1392            .unwrap_or_else(std::sync::PoisonError::into_inner)
1393    }
1394
1395    /// Transition from `Created` to `Running`.
1396    pub fn transition_to_running(&self) {
1397        let mut state = self
1398            .inner
1399            .cancel_state
1400            .lock()
1401            .unwrap_or_else(std::sync::PoisonError::into_inner);
1402        if *state == CancelState::Created {
1403            *state = CancelState::Running;
1404        }
1405    }
1406
1407    /// Transition from `Cancelling` to `Finalizing`.
1408    pub fn transition_to_finalizing(&self) {
1409        let mut state = self
1410            .inner
1411            .cancel_state
1412            .lock()
1413            .unwrap_or_else(std::sync::PoisonError::into_inner);
1414        if *state == CancelState::Cancelling {
1415            *state = CancelState::Finalizing;
1416        }
1417    }
1418
1419    /// Transition to `Completed` (from `Finalizing` or `Running`).
1420    pub fn transition_to_completed(&self) {
1421        let mut state = self
1422            .inner
1423            .cancel_state
1424            .lock()
1425            .unwrap_or_else(std::sync::PoisonError::into_inner);
1426        if matches!(*state, CancelState::Finalizing | CancelState::Running) {
1427            *state = CancelState::Completed;
1428        }
1429    }
1430
1431    /// Attach an e-process oracle used by [`Self::checkpoint`].
1432    pub fn set_eprocess_oracle(&self, oracle: Arc<EProcessOracle>) {
1433        let _ = self.inner.eprocess_oracle.set(oracle);
1434    }
1435
1436    /// Remove the currently attached e-process oracle.
1437    pub fn clear_eprocess_oracle(&self) {
1438        // OnceLock cannot be easily cleared. We just leave it as is.
1439        // It's only called in unused methods anyway.
1440    }
1441
1442    /// Attach a native asupersync context used by [`Self::checkpoint`].
1443    #[cfg(feature = "native")]
1444    pub fn set_native_cx(&self, native_cx: NativeCx) {
1445        let retired = {
1446            let _dispatch = self
1447                .inner
1448                .cancel_dispatch_gate
1449                .lock()
1450                .unwrap_or_else(std::sync::PoisonError::into_inner);
1451            let mut attached = self
1452                .inner
1453                .attached_native_cx
1454                .lock()
1455                .unwrap_or_else(std::sync::PoisonError::into_inner);
1456            attached.replace(native_cx.clone())
1457        };
1458        // Synchronize the exact handle supplied by this call. Re-reading the
1459        // attachment here would let a racing replacement return with this
1460        // handle still uncancelled.
1461        sync_one_native_cx_cancel(&self.inner, &native_cx);
1462        // Releasing the final old NativeCx reference may retire cancellation
1463        // Wakers. Do that only after both FrankenSQLite locks are gone and the
1464        // replacement's synchronization postcondition has been established.
1465        drop(retired);
1466    }
1467
1468    /// Attach a native context shim in non-native builds.
1469    #[cfg(not(feature = "native"))]
1470    pub fn set_native_cx<T>(&self, _native_cx: T) {}
1471
1472    /// Return the attached native asupersync context, if one exists.
1473    #[cfg(feature = "native")]
1474    #[must_use]
1475    pub fn attached_native_cx(&self) -> Option<NativeCx> {
1476        self.inner
1477            .attached_native_cx
1478            .lock()
1479            .unwrap_or_else(std::sync::PoisonError::into_inner)
1480            .clone()
1481    }
1482
1483    /// Compute the native runtime budget for a task spawned on `native_cx`.
1484    ///
1485    /// FrankenSQLite deadlines are relative timeouts. They must be translated
1486    /// using the selected native context's clock so production monotonic time
1487    /// and deterministic lab time stay in the same domain. Meeting the result
1488    /// with the native parent's budget preserves every tighter parent bound,
1489    /// including the maximum scheduling priority.
1490    #[cfg(feature = "native")]
1491    #[must_use]
1492    pub fn native_spawn_budget(&self, native_cx: &NativeCx) -> NativeBudget {
1493        let local = native_budget_from_local_at(self.budget, native_cx.now_for_observability());
1494        native_cx.budget().meet(local)
1495    }
1496
1497    /// Return the attached native context shim, if one exists.
1498    #[cfg(not(feature = "native"))]
1499    #[must_use]
1500    pub fn attached_native_cx(&self) -> Option<NativeCx> {
1501        None
1502    }
1503
1504    /// Remove the currently attached native asupersync context.
1505    #[cfg(feature = "native")]
1506    pub fn clear_native_cx(&self) {
1507        let retired = {
1508            let _dispatch = self
1509                .inner
1510                .cancel_dispatch_gate
1511                .lock()
1512                .unwrap_or_else(std::sync::PoisonError::into_inner);
1513            self.inner
1514                .attached_native_cx
1515                .lock()
1516                .unwrap_or_else(std::sync::PoisonError::into_inner)
1517                .take()
1518        };
1519        // NativeCx destruction may invoke executor-owned cleanup.
1520        drop(retired);
1521    }
1522
1523    /// Mark this context as running on a dedicated engine OS thread where
1524    /// bounded blocking I/O may be issued inline (bd-bjm5d). Irreversible
1525    /// for the lifetime of this context; shared through [`Cx::clone`] and
1526    /// propagated to children by [`Cx::create_child`].
1527    pub fn mark_blocking_io_inline_safe(&self) {
1528        self.inner
1529            .blocking_io_inline_safe
1530            .store(true, Ordering::Release);
1531    }
1532
1533    /// Whether bounded, page-sized positional I/O may be issued inline on
1534    /// the calling thread instead of via the blocking pool (bd-bjm5d).
1535    #[must_use]
1536    pub fn blocking_io_inline_safe(&self) -> bool {
1537        self.inner.blocking_io_inline_safe.load(Ordering::Acquire)
1538    }
1539
1540    /// Remove the currently attached native context shim.
1541    #[cfg(not(feature = "native"))]
1542    pub fn clear_native_cx(&self) {}
1543
1544    #[must_use]
1545    fn maybe_cancel_via_eprocess(&self) -> bool {
1546        let Some(oracle) = self.inner.eprocess_oracle.get() else {
1547            return false;
1548        };
1549        let decision = oracle.decision(self.budget.priority);
1550        self.record_eprocess_decision(decision.clone());
1551        tracing::debug!(
1552            target: "fsqlite::cx",
1553            event = "eprocess_checkpoint",
1554            trace_id = self.trace_id,
1555            decision_id = self.decision_id,
1556            policy_id = self.policy_id,
1557            priority = decision.priority,
1558            evalue = decision.snapshot.evalue,
1559            threshold = decision.snapshot.rejection_threshold,
1560            observations = decision.snapshot.observations,
1561            priority_threshold = decision.snapshot.priority_threshold,
1562            should_shed = decision.should_shed,
1563            signal = ?decision.snapshot.last_signal
1564        );
1565        if decision.should_shed {
1566            tracing::info!(
1567                target: "fsqlite::cx",
1568                event = "eprocess_shedding_triggered",
1569                trace_id = self.trace_id,
1570                decision_id = self.decision_id,
1571                policy_id = self.policy_id,
1572                priority = decision.priority,
1573                evalue = decision.snapshot.evalue,
1574                threshold = decision.snapshot.rejection_threshold,
1575                signal = ?decision.snapshot.last_signal
1576            );
1577            self.cancel_with_reason(CancelReason::Abort);
1578            return true;
1579        }
1580        false
1581    }
1582
1583    #[cfg(feature = "native")]
1584    #[must_use]
1585    fn maybe_cancel_via_native_cx(&self, masked: bool) -> bool {
1586        let Some(native) = self.native_cx_for_checkpoint() else {
1587            return false;
1588        };
1589
1590        if masked {
1591            if native.is_cancel_requested() {
1592                let reason = native
1593                    .cancel_reason()
1594                    .as_ref()
1595                    .map_or(CancelReason::Timeout, native_reason_to_local);
1596                self.cancel_with_reason(reason);
1597                return true;
1598            }
1599            return false;
1600        }
1601
1602        if native.checkpoint().is_err() {
1603            let reason = native
1604                .cancel_reason()
1605                .as_ref()
1606                .map_or(CancelReason::Timeout, native_reason_to_local);
1607            self.cancel_with_reason(reason);
1608            return true;
1609        }
1610        false
1611    }
1612
1613    // -----------------------------------------------------------------------
1614    // Checkpoints (§4.12.1)
1615    // -----------------------------------------------------------------------
1616
1617    /// Check for cancellation at a yield point.
1618    ///
1619    /// Returns `Ok(())` when not cancelled **or when inside a masked section**.
1620    /// When cancellation is observed, transitions state from `CancelRequested`
1621    /// to `Cancelling`.
1622    ///
1623    /// Hot-path note: the cheap `cancel_requested` atomic load is consulted
1624    /// first, then `mask_depth`. Only if neither cheap signal proves we're
1625    /// clear do we consult the e-process oracle and the native asupersync
1626    /// `Cx::checkpoint()`. Previously `maybe_cancel_via_native_cx` was
1627    /// evaluated **unconditionally** before the fast-path test — every
1628    /// checkpoint paid for the nested asupersync cancel machinery even when
1629    /// the cheap atomic said "not cancelled". That showed up as 5.87%
1630    /// self-time on the 2026-04-23 post-bench-fix MT 8t capture
1631    /// (`fsqlite-bench-fix-validation-194151`).
1632    pub fn checkpoint(&self) -> Result<()> {
1633        let cancel_requested = self.inner.cancel_requested.load(Ordering::Acquire);
1634        if !cancel_requested {
1635            // Cheap path already proved we're not locally cancelled. Only
1636            // the oracle + native cx can still observe a cancel signal.
1637            if !self.maybe_cancel_via_eprocess() {
1638                #[cfg(feature = "native")]
1639                {
1640                    let masked = self.inner.mask_depth.load(Ordering::Acquire) > 0;
1641                    if !self.maybe_cancel_via_native_cx(masked) {
1642                        return Ok(());
1643                    }
1644                }
1645                #[cfg(not(feature = "native"))]
1646                {
1647                    return Ok(());
1648                }
1649            }
1650        }
1651
1652        // Either cancel_requested is set locally, or one of the async plane
1653        // checks fired. Masked sections defer observation unconditionally.
1654        let masked = self.inner.mask_depth.load(Ordering::Acquire) > 0;
1655        if masked {
1656            return Ok(());
1657        }
1658
1659        // Slow path: transition CancelRequested → Cancelling.
1660        {
1661            let mut state = self
1662                .inner
1663                .cancel_state
1664                .lock()
1665                .unwrap_or_else(std::sync::PoisonError::into_inner);
1666            if *state == CancelState::CancelRequested {
1667                *state = CancelState::Cancelling;
1668            }
1669        }
1670        Err(Error::cancelled())
1671    }
1672
1673    /// Check for cancellation and record a progress message.
1674    pub fn checkpoint_with(&self, msg: impl Into<String>) -> Result<()> {
1675        {
1676            let mut guard = self
1677                .inner
1678                .last_checkpoint_msg
1679                .lock()
1680                .unwrap_or_else(std::sync::PoisonError::into_inner);
1681            *guard = Some(msg.into());
1682        }
1683        self.checkpoint()
1684    }
1685
1686    #[must_use]
1687    pub fn last_checkpoint_message(&self) -> Option<String> {
1688        self.inner
1689            .last_checkpoint_msg
1690            .lock()
1691            .unwrap_or_else(std::sync::PoisonError::into_inner)
1692            .clone()
1693    }
1694
1695    /// Most recent e-process decision recorded during [`Self::checkpoint`].
1696    #[must_use]
1697    pub fn last_eprocess_decision(&self) -> Option<EProcessDecision> {
1698        self.inner
1699            .last_eprocess_decision
1700            .lock()
1701            .unwrap_or_else(std::sync::PoisonError::into_inner)
1702            .clone()
1703    }
1704
1705    /// Snapshot portion of the most recent e-process decision.
1706    #[must_use]
1707    pub fn last_eprocess_snapshot(&self) -> Option<EProcessSnapshot> {
1708        self.last_eprocess_decision()
1709            .map(|decision| decision.snapshot)
1710    }
1711
1712    fn record_eprocess_decision(&self, decision: EProcessDecision) {
1713        *self
1714            .inner
1715            .last_eprocess_decision
1716            .lock()
1717            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(decision);
1718    }
1719
1720    // -----------------------------------------------------------------------
1721    // Masked critical sections (§4.12.2)
1722    // -----------------------------------------------------------------------
1723
1724    /// Enter a masked section where `checkpoint()` returns `Ok(())` even if
1725    /// cancellation is requested.
1726    ///
1727    /// Returns a [`MaskGuard`] whose `Drop` restores the mask depth.
1728    ///
1729    /// # Panics
1730    ///
1731    /// Panics if nesting exceeds [`MAX_MASK_DEPTH`] (INV-MASK-BOUNDED).
1732    #[must_use]
1733    pub fn masked(&self) -> MaskGuard<'_> {
1734        let prev = self.inner.mask_depth.fetch_add(1, Ordering::AcqRel);
1735        if prev >= MAX_MASK_DEPTH {
1736            self.inner.mask_depth.fetch_sub(1, Ordering::Release);
1737            assert!(
1738                prev < MAX_MASK_DEPTH,
1739                "MAX_MASK_DEPTH ({MAX_MASK_DEPTH}) exceeded: mask nesting depth would be {}",
1740                prev + 1
1741            );
1742        }
1743        MaskGuard { inner: &self.inner }
1744    }
1745
1746    /// Current mask nesting depth.
1747    #[must_use]
1748    pub fn mask_depth(&self) -> u32 {
1749        self.inner.mask_depth.load(Ordering::Acquire)
1750    }
1751
1752    // -----------------------------------------------------------------------
1753    // Commit sections (§4.12.3)
1754    // -----------------------------------------------------------------------
1755
1756    /// Execute a logically atomic commit section.
1757    ///
1758    /// The section masks cancellation, enforces a poll quota bound, and
1759    /// guarantees the `finalizer` runs even on cancellation or panic.
1760    pub fn commit_section<R>(
1761        &self,
1762        poll_quota: u32,
1763        body: impl FnOnce(&CommitCtx) -> R,
1764        finalizer: impl FnOnce(),
1765    ) -> R {
1766        struct FinGuard<G: FnOnce()>(Option<G>);
1767        impl<G: FnOnce()> Drop for FinGuard<G> {
1768            fn drop(&mut self) {
1769                if let Some(f) = self.0.take() {
1770                    f();
1771                }
1772            }
1773        }
1774
1775        let _mask = self.masked();
1776        let _fin = FinGuard(Some(finalizer));
1777        let ctx = CommitCtx::new(poll_quota);
1778        body(&ctx)
1779    }
1780
1781    // -----------------------------------------------------------------------
1782    // Child context management (INV-CANCEL-PROPAGATES)
1783    // -----------------------------------------------------------------------
1784
1785    /// Create a child `Cx` that shares the parent's budget but has
1786    /// independent cancellation state. Cancelling the parent propagates
1787    /// to this child. Tracing IDs propagate to the child.
1788    #[must_use]
1789    pub fn create_child(&self) -> Self {
1790        self.create_child_with_runtime_affinity(true)
1791    }
1792
1793    /// Create a child context that may be moved into a newly spawned task.
1794    ///
1795    /// Cancellation lineage, budget, tracing metadata, e-process policy, and
1796    /// typed capabilities are inherited exactly as for [`Self::create_child`].
1797    /// Task- and OS-thread-affine state is deliberately excluded: the child
1798    /// starts without an attached/fallback native context and without inline
1799    /// blocking-I/O permission. The spawned task must attach its own native
1800    /// context after it starts.
1801    #[must_use]
1802    pub fn create_child_for_spawn(&self) -> Self {
1803        self.create_child_with_runtime_affinity(false)
1804    }
1805
1806    fn create_child_with_runtime_affinity(&self, inherit_runtime_affinity: bool) -> Self {
1807        let mut child = Self::with_budget_and_cancel_dispatch(
1808            self.budget,
1809            Arc::clone(&self.inner.cancel_dispatch_gate),
1810        );
1811        child.trace_id = self.trace_id;
1812        child.decision_id = self.decision_id;
1813        child.policy_id = self.policy_id;
1814        if self.inner.unix_millis_is_fixed.load(Ordering::Acquire) {
1815            let unix_millis = self.inner.unix_millis.load(Ordering::Acquire);
1816            child
1817                .inner
1818                .unix_millis
1819                .store(unix_millis, Ordering::Release);
1820            child
1821                .inner
1822                .unix_millis_is_fixed
1823                .store(true, Ordering::Release);
1824        }
1825        if let Some(oracle) = self.inner.eprocess_oracle.get().cloned() {
1826            child.set_eprocess_oracle(oracle);
1827        }
1828        // bd-bjm5d: inline-safety is a property of the owning OS thread,
1829        // not of the native runtime handle. Ordinary same-thread children
1830        // inherit it; spawn-safe children deliberately do not.
1831        if inherit_runtime_affinity && self.blocking_io_inline_safe() {
1832            child.mark_blocking_io_inline_safe();
1833        }
1834
1835        #[cfg(feature = "native")]
1836        let native_to_sync = {
1837            let _dispatch = self
1838                .inner
1839                .cancel_dispatch_gate
1840                .lock()
1841                .unwrap_or_else(std::sync::PoisonError::into_inner);
1842            let attached_native = inherit_runtime_affinity
1843                .then(|| {
1844                    self.inner
1845                        .attached_native_cx
1846                        .lock()
1847                        .unwrap_or_else(std::sync::PoisonError::into_inner)
1848                        .clone()
1849                })
1850                .flatten();
1851            if let Some(native) = attached_native.as_ref() {
1852                *child
1853                    .inner
1854                    .attached_native_cx
1855                    .lock()
1856                    .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(native.clone());
1857            }
1858
1859            let local_reason = self.cancel_reason();
1860            let native_reason = mirrored_native_cancel_reason(&self.inner);
1861            if let Some(local_reason) = local_reason.or(native_reason) {
1862                publish_cancel_state(&child.inner, local_reason, native_reason);
1863            }
1864
1865            // The child becomes visible only after its inherited cancellation
1866            // state is fully initialized.
1867            let mut children = self
1868                .inner
1869                .children
1870                .lock()
1871                .unwrap_or_else(std::sync::PoisonError::into_inner);
1872            if children.len() == children.capacity() {
1873                children.retain(|registered| registered.strong_count() > 0);
1874            }
1875            children.push(Arc::downgrade(&child.inner));
1876
1877            attached_native.filter(|_| native_reason.is_some())
1878        };
1879
1880        #[cfg(not(feature = "native"))]
1881        {
1882            let _dispatch = self
1883                .inner
1884                .cancel_dispatch_gate
1885                .lock()
1886                .unwrap_or_else(std::sync::PoisonError::into_inner);
1887            if let Some(reason) = self.cancel_reason() {
1888                publish_cancel_state(&child.inner, reason, None);
1889            }
1890            let mut children = self
1891                .inner
1892                .children
1893                .lock()
1894                .unwrap_or_else(std::sync::PoisonError::into_inner);
1895            if children.len() == children.capacity() {
1896                children.retain(|registered| registered.strong_count() > 0);
1897            }
1898            children.push(Arc::downgrade(&child.inner));
1899        }
1900
1901        #[cfg(feature = "native")]
1902        if let Some(native) = native_to_sync {
1903            sync_one_native_cx_cancel(&child.inner, &native);
1904        }
1905
1906        child
1907    }
1908
1909    /// Create a child context plus narrowly scoped local cancellation authority.
1910    ///
1911    /// The child inherits the same budget, tracing metadata, effect
1912    /// capabilities, and native I/O context as [`Self::create_child`]. The
1913    /// returned relay can only request local cancellation for that child
1914    /// subtree; it cannot access the context or cancel the inherited native
1915    /// context.
1916    pub fn create_child_with_local_cancel_relay(&self) -> (Self, LocalCancelRelay) {
1917        let child = self.create_child();
1918        let relay = LocalCancelRelay {
1919            inner: Arc::downgrade(&child.inner),
1920        };
1921        (child, relay)
1922    }
1923
1924    /// Set a deterministic unix time for tests.
1925    pub fn set_unix_millis_for_testing(&self, millis: u64)
1926    where
1927        Caps: cap::HasTime,
1928    {
1929        self.inner.unix_millis.store(millis, Ordering::Release);
1930        self.inner
1931            .unix_millis_is_fixed
1932            .store(true, Ordering::Release);
1933    }
1934
1935    /// Return current Unix time in milliseconds.
1936    ///
1937    /// Production contexts use the live system wall clock. Tests can install
1938    /// an exact fixed value with [`Self::set_unix_millis_for_testing`].
1939    #[must_use]
1940    pub fn current_time_unix_millis(&self) -> u64
1941    where
1942        Caps: cap::HasTime,
1943    {
1944        if self.inner.unix_millis_is_fixed.load(Ordering::Acquire) {
1945            return self.inner.unix_millis.load(Ordering::Acquire);
1946        }
1947
1948        u64::try_from(
1949            SystemTime::now()
1950                .duration_since(SystemTime::UNIX_EPOCH)
1951                .unwrap_or_default()
1952                .as_millis(),
1953        )
1954        .unwrap_or(u64::MAX)
1955    }
1956
1957    /// Return current time as a Julian day.
1958    #[must_use]
1959    pub fn current_time_julian_day(&self) -> f64
1960    where
1961        Caps: cap::HasTime,
1962    {
1963        let millis = self.current_time_unix_millis();
1964        #[allow(clippy::cast_precision_loss)]
1965        let secs = (millis as f64) / 1000.0;
1966        // Unix epoch in Julian days: 2440587.5
1967        2_440_587.5 + (secs / 86_400.0)
1968    }
1969}
1970
1971// ---------------------------------------------------------------------------
1972// MaskGuard — RAII guard for masked cancellation sections (§4.12.2)
1973// ---------------------------------------------------------------------------
1974
1975/// RAII guard that keeps the `Cx` masked while alive.
1976///
1977/// Created by [`Cx::masked()`]. On drop, the mask depth is decremented.
1978#[derive(Debug)]
1979pub struct MaskGuard<'a> {
1980    inner: &'a CxInner,
1981}
1982
1983impl Drop for MaskGuard<'_> {
1984    fn drop(&mut self) {
1985        let previous = self.inner.mask_depth.fetch_sub(1, Ordering::AcqRel);
1986        debug_assert!(previous > 0, "mask depth underflow");
1987        if previous == 1 {
1988            let waiters = {
1989                let _dispatch = self
1990                    .inner
1991                    .cancel_dispatch_gate
1992                    .lock()
1993                    .unwrap_or_else(std::sync::PoisonError::into_inner);
1994                if self.inner.mask_depth.load(Ordering::Acquire) == 0
1995                    && self.inner.cancel_requested.load(Ordering::Acquire)
1996                {
1997                    take_local_cancel_waiters(self.inner)
1998                } else {
1999                    Vec::new()
2000                }
2001            };
2002            let mut first_panic = None;
2003            dispatch_local_cancel_waiters(waiters, &mut first_panic);
2004            if let Some(payload) = first_panic {
2005                // Never resume arbitrary callback panic from a destructor:
2006                // this guard may itself be running during another unwind.
2007                std::mem::forget(payload);
2008            }
2009        }
2010    }
2011}
2012
2013// ---------------------------------------------------------------------------
2014// CommitCtx — bounded context for commit sections (§4.12.3)
2015// ---------------------------------------------------------------------------
2016
2017/// Context passed to commit-section bodies.
2018///
2019/// Tracks a poll-quota budget that operations can decrement via [`Self::tick`].
2020#[derive(Debug)]
2021pub struct CommitCtx {
2022    poll_remaining: AtomicU32,
2023}
2024
2025impl CommitCtx {
2026    fn new(poll_quota: u32) -> Self {
2027        Self {
2028            poll_remaining: AtomicU32::new(poll_quota),
2029        }
2030    }
2031
2032    /// Remaining poll budget.
2033    #[must_use]
2034    pub fn poll_remaining(&self) -> u32 {
2035        self.poll_remaining.load(Ordering::Acquire)
2036    }
2037
2038    /// Consume one unit of poll budget. Returns `true` if budget remains.
2039    pub fn tick(&self) -> bool {
2040        let prev = self.poll_remaining.load(Ordering::Acquire);
2041        if prev == 0 {
2042            return false;
2043        }
2044        self.poll_remaining.fetch_sub(1, Ordering::AcqRel);
2045        true
2046    }
2047}
2048
2049#[cfg(test)]
2050mod tests {
2051    use super::*;
2052    use crate::eprocess::{EProcessConfig, EProcessSignal};
2053    use std::path::{Path, PathBuf};
2054    use std::sync::atomic::{AtomicBool, AtomicUsize};
2055    use std::sync::{Arc, Barrier, Weak};
2056
2057    #[derive(Debug, Default)]
2058    struct CountingWake(AtomicUsize);
2059
2060    impl std::task::Wake for CountingWake {
2061        fn wake(self: Arc<Self>) {
2062            self.0.fetch_add(1, Ordering::AcqRel);
2063        }
2064
2065        fn wake_by_ref(self: &Arc<Self>) {
2066            self.0.fetch_add(1, Ordering::AcqRel);
2067        }
2068    }
2069
2070    #[derive(Debug)]
2071    struct DescendantStateProbeWake {
2072        descendant: Weak<CxInner>,
2073        wake_count: AtomicUsize,
2074        saw_descendant_cancelled: AtomicBool,
2075        dispatch_gate_was_unlocked: AtomicBool,
2076    }
2077
2078    impl DescendantStateProbeWake {
2079        fn observe(&self) {
2080            let descendant = self
2081                .descendant
2082                .upgrade()
2083                .expect("observed descendant should remain alive");
2084            self.saw_descendant_cancelled.store(
2085                descendant.cancel_requested.load(Ordering::Acquire),
2086                Ordering::Release,
2087            );
2088            let dispatch_guard = descendant
2089                .cancel_dispatch_gate
2090                .try_lock()
2091                .expect("cancellation callbacks must run outside the family phase gate");
2092            self.dispatch_gate_was_unlocked
2093                .store(true, Ordering::Release);
2094            drop(dispatch_guard);
2095            self.wake_count.fetch_add(1, Ordering::AcqRel);
2096        }
2097    }
2098
2099    impl std::task::Wake for DescendantStateProbeWake {
2100        fn wake(self: Arc<Self>) {
2101            self.observe();
2102        }
2103
2104        fn wake_by_ref(self: &Arc<Self>) {
2105            self.observe();
2106        }
2107    }
2108
2109    #[derive(Debug)]
2110    struct ReentrantFamilyWake {
2111        cx: Cx<FullCaps>,
2112        wake_count: AtomicUsize,
2113        child_inherited_cancellation: AtomicBool,
2114    }
2115
2116    impl ReentrantFamilyWake {
2117        fn exercise(&self) {
2118            self.cx.cancel_with_reason(CancelReason::Abort);
2119            let child = self.cx.create_child();
2120            self.child_inherited_cancellation.store(
2121                child.cancel_reason() == Some(CancelReason::Abort),
2122                Ordering::Release,
2123            );
2124            let mask = self.cx.masked();
2125            drop(mask);
2126            self.wake_count.fetch_add(1, Ordering::AcqRel);
2127        }
2128    }
2129
2130    impl std::task::Wake for ReentrantFamilyWake {
2131        fn wake(self: Arc<Self>) {
2132            self.exercise();
2133        }
2134
2135        fn wake_by_ref(self: &Arc<Self>) {
2136            self.exercise();
2137        }
2138    }
2139
2140    #[derive(Debug, Default)]
2141    struct PanicWake;
2142
2143    impl std::task::Wake for PanicWake {
2144        fn wake(self: Arc<Self>) {
2145            panic!("intentional cancellation-waker panic");
2146        }
2147
2148        fn wake_by_ref(self: &Arc<Self>) {
2149            panic!("intentional cancellation-waker panic");
2150        }
2151    }
2152
2153    #[derive(Debug)]
2154    struct RegistryProbeWake {
2155        inner: Weak<CxInner>,
2156        wake_count: AtomicUsize,
2157        registry_was_unlocked: AtomicBool,
2158    }
2159
2160    impl RegistryProbeWake {
2161        fn probe_registry(&self) {
2162            let inner = self
2163                .inner
2164                .upgrade()
2165                .expect("observed context should remain alive");
2166            let registry_guard = inner
2167                .local_cancel_waiters
2168                .try_lock()
2169                .expect("waker callbacks must run after releasing the waiter registry");
2170            self.registry_was_unlocked.store(true, Ordering::Release);
2171            drop(registry_guard);
2172            self.wake_count.fetch_add(1, Ordering::AcqRel);
2173        }
2174    }
2175
2176    impl std::task::Wake for RegistryProbeWake {
2177        fn wake(self: Arc<Self>) {
2178            self.probe_registry();
2179        }
2180
2181        fn wake_by_ref(self: &Arc<Self>) {
2182            self.probe_registry();
2183        }
2184    }
2185
2186    fn local_cancel_waiter_count<Caps: cap::SubsetOf<cap::All>>(cx: &Cx<Caps>) -> usize {
2187        cx.inner
2188            .local_cancel_waiters
2189            .lock()
2190            .unwrap_or_else(std::sync::PoisonError::into_inner)
2191            .entries
2192            .len()
2193    }
2194
2195    #[test]
2196    fn test_cx_checkpoint_observes_cancellation() {
2197        let cx = Cx::new();
2198        assert_eq!(local_cancel_waiter_count(&cx), 0);
2199        assert!(cx.checkpoint().is_ok());
2200        cx.cancel();
2201        assert_eq!(local_cancel_waiter_count(&cx), 0);
2202        let err = cx.checkpoint().unwrap_err();
2203        assert_eq!(err.kind(), ErrorKind::Cancelled);
2204        assert_eq!(err.sqlite_error_code(), SQLITE_INTERRUPT);
2205    }
2206
2207    #[test]
2208    fn test_cx_capability_narrowing_compiles() {
2209        let cx = Cx::<FullCaps>::new();
2210        let _compute = cx.restrict::<ComputeCaps>();
2211        let _storage = cx.restrict::<StorageCaps>();
2212    }
2213
2214    #[test]
2215    fn test_cx_budget_meet_tightens() {
2216        let parent = Budget::INFINITE.with_deadline(Duration::from_millis(100));
2217        let child = Budget::INFINITE.with_deadline(Duration::from_millis(200));
2218        let effective = parent.meet(child);
2219        assert_eq!(effective.deadline, Some(Duration::from_millis(100)));
2220    }
2221
2222    #[test]
2223    fn test_cx_budget_priority_join() {
2224        let parent = Budget::INFINITE.with_priority(2);
2225        let child = Budget::INFINITE.with_priority(5);
2226        let effective = parent.meet(child);
2227        assert_eq!(effective.priority, 5);
2228    }
2229
2230    #[cfg(feature = "native")]
2231    #[test]
2232    fn bd_2jpu6_2_native_budget_translation_uses_supplied_clock_domain() {
2233        let now = NativeTime::from_nanos(1_000);
2234        let local = Budget::INFINITE.with_deadline(Duration::from_nanos(250));
2235
2236        let native = native_budget_from_local_at(local, now);
2237
2238        assert_eq!(native.deadline, Some(NativeTime::from_nanos(1_250)));
2239    }
2240
2241    #[cfg(feature = "native")]
2242    #[test]
2243    fn bd_2jpu6_2_native_spawn_budget_meets_parent_bounds_and_priority() {
2244        let parent = NativeBudget::INFINITE
2245            .with_poll_quota(80)
2246            .with_cost_quota(900)
2247            .with_priority(9);
2248        let native_cx = NativeCx::for_testing_with_budget(parent);
2249        let local = Cx::<FullCaps>::with_budget(
2250            Budget::INFINITE
2251                .with_poll_quota(60)
2252                .with_cost_quota(700)
2253                .with_priority(3),
2254        );
2255
2256        let effective = local.native_spawn_budget(&native_cx);
2257
2258        assert_eq!(effective.poll_quota, 60);
2259        assert_eq!(effective.cost_quota, Some(700));
2260        assert_eq!(effective.priority, 9);
2261    }
2262
2263    #[test]
2264    fn test_cx_scope_with_budget_cannot_loosen() {
2265        let cx =
2266            Cx::<FullCaps>::with_budget(Budget::INFINITE.with_deadline(Duration::from_millis(50)));
2267        let child = Budget::INFINITE.with_deadline(Duration::from_millis(100));
2268        let scoped = cx.scope_with_budget(child);
2269        assert_eq!(scoped.budget().deadline, Some(Duration::from_millis(50)));
2270    }
2271
2272    #[test]
2273    fn test_cx_checkpoint_with_message_records_message() {
2274        let cx = Cx::new();
2275        assert!(cx.checkpoint_with("vdbe pc=5").is_ok());
2276        assert_eq!(cx.last_checkpoint_message().as_deref(), Some("vdbe pc=5"));
2277    }
2278
2279    #[test]
2280    fn test_cx_cleanup_uses_minimal_budget() {
2281        let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_poll_quota(10_000));
2282        let cleanup = cx.cleanup_scope();
2283        assert_eq!(cleanup.budget(), Budget::MINIMAL);
2284    }
2285
2286    #[test]
2287    fn test_cx_restrict_storage_to_compute() {
2288        let cx = Cx::<FullCaps>::new();
2289        let storage = cx.restrict::<StorageCaps>();
2290        let _compute = storage.restrict::<ComputeCaps>();
2291    }
2292
2293    #[test]
2294    fn test_cx_restrict_is_zero_cost() {
2295        // CapSet is a ZST; Cx carries only Arc + Budget + PhantomData.
2296        // Restrict changes only the phantom marker — same size, same pointer.
2297        assert_eq!(
2298            std::mem::size_of::<Cx<FullCaps>>(),
2299            std::mem::size_of::<Cx<ComputeCaps>>()
2300        );
2301    }
2302
2303    #[test]
2304    fn test_budget_mixed_lattice() {
2305        let a = Budget {
2306            deadline: Some(Duration::from_millis(100)),
2307            poll_quota: 500,
2308            cost_quota: Some(1000),
2309            priority: 2,
2310        };
2311        let b = Budget {
2312            deadline: Some(Duration::from_millis(200)),
2313            poll_quota: 300,
2314            cost_quota: Some(2000),
2315            priority: 5,
2316        };
2317        let m = a.meet(b);
2318        // Resources tighten by min.
2319        assert_eq!(m.deadline, Some(Duration::from_millis(100)));
2320        assert_eq!(m.poll_quota, 300);
2321        assert_eq!(m.cost_quota, Some(1000));
2322        // Priority propagates by max (join).
2323        assert_eq!(m.priority, 5);
2324    }
2325
2326    #[test]
2327    fn test_budget_meet_commutative() {
2328        let a = Budget {
2329            deadline: Some(Duration::from_millis(50)),
2330            poll_quota: 400,
2331            cost_quota: Some(800),
2332            priority: 3,
2333        };
2334        let b = Budget {
2335            deadline: Some(Duration::from_millis(150)),
2336            poll_quota: 200,
2337            cost_quota: None,
2338            priority: 7,
2339        };
2340        assert_eq!(a.meet(b), b.meet(a));
2341    }
2342
2343    #[test]
2344    fn test_budget_meet_associative() {
2345        let a = Budget::INFINITE
2346            .with_deadline(Duration::from_millis(50))
2347            .with_poll_quota(100)
2348            .with_priority(1);
2349        let b = Budget::INFINITE
2350            .with_deadline(Duration::from_millis(150))
2351            .with_poll_quota(200)
2352            .with_priority(5);
2353        let c = Budget::INFINITE
2354            .with_deadline(Duration::from_millis(75))
2355            .with_poll_quota(50)
2356            .with_priority(3);
2357        assert_eq!(a.meet(b).meet(c), a.meet(b.meet(c)));
2358    }
2359
2360    #[test]
2361    fn test_budget_minimal_is_stricter_than_normal() {
2362        let normal = Budget::INFINITE.with_poll_quota(10_000);
2363        let effective = normal.meet(Budget::MINIMAL);
2364        assert_eq!(effective.poll_quota, Budget::MINIMAL.poll_quota);
2365    }
2366
2367    #[test]
2368    fn test_cx_cancel_shared_across_clones() {
2369        let cx1 = Cx::<FullCaps>::new();
2370        let cx2 = cx1.clone();
2371        assert!(!cx2.is_cancel_requested());
2372        cx1.cancel();
2373        assert!(cx2.is_cancel_requested());
2374        assert!(cx2.checkpoint().is_err());
2375    }
2376
2377    #[test]
2378    fn test_cx_cancel_shared_across_restrict() {
2379        let cx = Cx::<FullCaps>::new();
2380        let compute = cx.restrict::<ComputeCaps>();
2381        cx.cancel();
2382        assert!(compute.checkpoint().is_err());
2383    }
2384
2385    fn system_time_unix_millis() -> u64 {
2386        u64::try_from(
2387            SystemTime::now()
2388                .duration_since(SystemTime::UNIX_EPOCH)
2389                .unwrap_or_default()
2390                .as_millis(),
2391        )
2392        .unwrap_or(u64::MAX)
2393    }
2394
2395    #[test]
2396    fn test_cx_current_time_uses_live_clock_by_default() {
2397        let cx = Cx::<FullCaps>::new();
2398        let observed = cx.current_time_unix_millis();
2399        let expected = system_time_unix_millis();
2400
2401        assert!(
2402            observed.abs_diff(expected) <= 60_000,
2403            "default Cx clock must be live: observed={observed}, expected approximately {expected}"
2404        );
2405    }
2406
2407    #[test]
2408    fn test_cx_fixed_unix_millis_supports_full_u64_domain() {
2409        let cx = Cx::<FullCaps>::new();
2410
2411        cx.set_unix_millis_for_testing(0);
2412        assert_eq!(cx.current_time_unix_millis(), 0);
2413
2414        cx.set_unix_millis_for_testing(u64::MAX);
2415        assert_eq!(cx.current_time_unix_millis(), u64::MAX);
2416    }
2417
2418    #[test]
2419    fn test_cx_current_time_julian_day_uses_fixed_unix_millis() {
2420        let cx = Cx::<FullCaps>::new();
2421
2422        // Unix epoch = Julian day 2440587.5.
2423        cx.set_unix_millis_for_testing(0);
2424        let jd = cx.current_time_julian_day();
2425        assert!((jd - 2_440_587.5).abs() < 1e-10);
2426
2427        // One day = 86_400_000 ms.
2428        cx.set_unix_millis_for_testing(86_400_000);
2429        let jd = cx.current_time_julian_day();
2430        assert!((jd - 2_440_588.5).abs() < 1e-10);
2431    }
2432
2433    #[test]
2434    fn test_cx_children_inherit_fixed_or_live_clock_state() {
2435        let fixed_parent = Cx::<FullCaps>::new();
2436        fixed_parent.set_unix_millis_for_testing(0);
2437        let fixed_child = fixed_parent.create_child();
2438        let fixed_spawn_child = fixed_parent.create_child_for_spawn();
2439
2440        assert_eq!(fixed_child.current_time_unix_millis(), 0);
2441        assert_eq!(fixed_spawn_child.current_time_unix_millis(), 0);
2442
2443        // Child clocks are snapshots rather than aliases of the parent's
2444        // override, so a later parent update cannot rewrite an existing child.
2445        fixed_parent.set_unix_millis_for_testing(86_400_000);
2446        assert_eq!(fixed_child.current_time_unix_millis(), 0);
2447
2448        let live_parent = Cx::<FullCaps>::new();
2449        let live_child = live_parent.create_child();
2450        let observed = live_child.current_time_unix_millis();
2451        let expected = system_time_unix_millis();
2452        assert!(
2453            observed.abs_diff(expected) <= 60_000,
2454            "child of live Cx must remain live: observed={observed}, expected approximately {expected}"
2455        );
2456    }
2457
2458    #[test]
2459    fn test_cx_fixed_clock_updates_publish_complete_values() {
2460        const FIRST: u64 = 0xAAAA_AAAA_AAAA_AAAA;
2461        const SECOND: u64 = 0x5555_5555_5555_5555;
2462
2463        let cx = Cx::<FullCaps>::new();
2464        cx.set_unix_millis_for_testing(FIRST);
2465        let writer_cx = cx.clone();
2466        let writer = std::thread::spawn(move || {
2467            for _ in 0..1_000 {
2468                writer_cx.set_unix_millis_for_testing(FIRST);
2469                writer_cx.set_unix_millis_for_testing(SECOND);
2470            }
2471        });
2472
2473        for _ in 0..1_000 {
2474            let observed = cx.current_time_unix_millis();
2475            assert!(matches!(observed, FIRST | SECOND));
2476        }
2477        writer.join().expect("clock writer must not panic");
2478        assert_eq!(cx.current_time_unix_millis(), SECOND);
2479    }
2480
2481    #[test]
2482    fn test_capset_is_zero_sized() {
2483        assert_eq!(std::mem::size_of::<cap::All>(), 0);
2484        assert_eq!(std::mem::size_of::<cap::None>(), 0);
2485        assert_eq!(
2486            std::mem::size_of::<cap::CapSet<true, false, true, false, true>>(),
2487            0
2488        );
2489    }
2490
2491    #[test]
2492    fn test_cx_checkpoint_not_cancelled() {
2493        let cx = Cx::new();
2494        assert!(cx.checkpoint().is_ok());
2495        assert!(cx.checkpoint_with("still going").is_ok());
2496    }
2497
2498    #[test]
2499    fn test_cx_checkpoint_maps_to_sqlite_interrupt() {
2500        let cx = Cx::new();
2501        cx.cancel();
2502        let err = cx.checkpoint().unwrap_err();
2503        assert_eq!(err.sqlite_error_code(), SQLITE_INTERRUPT);
2504    }
2505
2506    #[test]
2507    fn test_cx_checkpoint_eprocess_sheds_low_priority_context() {
2508        let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(3));
2509        let oracle = Arc::new(EProcessOracle::new(
2510            EProcessConfig {
2511                p0: 0.1,
2512                lambda: 5.0,
2513                alpha: 0.05,
2514                max_evalue: 1e12,
2515            },
2516            1,
2517        ));
2518        let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2519        oracle.observe_signal(signal);
2520        oracle.observe_signal(signal);
2521        cx.set_eprocess_oracle(oracle);
2522        let err = cx.checkpoint().unwrap_err();
2523        assert_eq!(err.kind(), ErrorKind::Cancelled);
2524        assert_eq!(cx.cancel_reason(), Some(CancelReason::Abort));
2525        let decision = cx
2526            .last_eprocess_decision()
2527            .expect("checkpoint should record an e-process decision");
2528        assert!(decision.should_shed);
2529        assert_eq!(decision.snapshot.last_signal, Some(signal));
2530    }
2531
2532    #[test]
2533    fn test_cx_checkpoint_eprocess_respects_priority_threshold() {
2534        let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(1));
2535        let oracle = Arc::new(EProcessOracle::new(
2536            EProcessConfig {
2537                p0: 0.1,
2538                lambda: 5.0,
2539                alpha: 0.05,
2540                max_evalue: 1e12,
2541            },
2542            1,
2543        ));
2544        let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2545        oracle.observe_signal(signal);
2546        oracle.observe_signal(signal);
2547        cx.set_eprocess_oracle(oracle);
2548        assert!(cx.checkpoint().is_ok());
2549        assert!(!cx.is_cancel_requested());
2550        let decision = cx
2551            .last_eprocess_decision()
2552            .expect("checkpoint should still record non-shedding decisions");
2553        assert!(!decision.should_shed);
2554        assert_eq!(decision.priority, 1);
2555        assert_eq!(decision.snapshot.last_signal, Some(signal));
2556    }
2557
2558    #[test]
2559    fn test_cx_checkpoint_eprocess_preserves_masking_semantics() {
2560        let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(3));
2561        let oracle = Arc::new(EProcessOracle::new(
2562            EProcessConfig {
2563                p0: 0.1,
2564                lambda: 5.0,
2565                alpha: 0.05,
2566                max_evalue: 1e12,
2567            },
2568            1,
2569        ));
2570        let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2571        oracle.observe_signal(signal);
2572        oracle.observe_signal(signal);
2573        cx.set_eprocess_oracle(oracle);
2574        {
2575            let _mask = cx.masked();
2576            assert!(cx.checkpoint().is_ok());
2577            assert!(cx.is_cancel_requested());
2578            assert_eq!(cx.cancel_state(), CancelState::CancelRequested);
2579            assert_eq!(
2580                cx.last_eprocess_snapshot()
2581                    .expect("checkpoint should record the masked decision")
2582                    .last_signal,
2583                Some(signal)
2584            );
2585        }
2586        let err = cx.checkpoint().unwrap_err();
2587        assert_eq!(err.kind(), ErrorKind::Cancelled);
2588    }
2589
2590    #[test]
2591    fn test_create_child_inherits_eprocess_oracle() {
2592        let parent = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(3));
2593        let oracle = Arc::new(EProcessOracle::new(
2594            EProcessConfig {
2595                p0: 0.1,
2596                lambda: 5.0,
2597                alpha: 0.05,
2598                max_evalue: 1e12,
2599            },
2600            1,
2601        ));
2602        let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2603        oracle.observe_signal(signal);
2604        oracle.observe_signal(signal);
2605        parent.set_eprocess_oracle(oracle);
2606
2607        let child = parent.create_child();
2608        let err = child.checkpoint().unwrap_err();
2609        assert_eq!(err.kind(), ErrorKind::Cancelled);
2610        assert_eq!(child.cancel_reason(), Some(CancelReason::Abort));
2611        assert_eq!(
2612            child
2613                .last_eprocess_snapshot()
2614                .expect("child checkpoint should record inherited oracle decision")
2615                .last_signal,
2616            Some(signal)
2617        );
2618    }
2619
2620    #[test]
2621    fn spawn_child_preserves_logical_context_without_thread_affinity() {
2622        let budget = Budget {
2623            deadline: Some(Duration::from_secs(7)),
2624            poll_quota: 123,
2625            cost_quota: Some(456),
2626            priority: 3,
2627        };
2628        let parent = Cx::<FullCaps>::with_budget(budget).with_trace_context(50, 60, 70);
2629        let oracle = Arc::new(EProcessOracle::new(
2630            EProcessConfig {
2631                p0: 0.1,
2632                lambda: 5.0,
2633                alpha: 0.05,
2634                max_evalue: 1e12,
2635            },
2636            1,
2637        ));
2638        parent.set_eprocess_oracle(Arc::clone(&oracle));
2639        parent.mark_blocking_io_inline_safe();
2640
2641        let child = parent.create_child_for_spawn();
2642        assert_eq!(child.budget(), budget);
2643        assert_eq!(child.trace_id(), 50);
2644        assert_eq!(child.decision_id(), 60);
2645        assert_eq!(child.policy_id(), 70);
2646        assert!(
2647            Arc::ptr_eq(
2648                child
2649                    .inner
2650                    .eprocess_oracle
2651                    .get()
2652                    .expect("spawn child should inherit the e-process oracle"),
2653                &oracle
2654            ),
2655            "spawn child must retain the exact logical policy oracle"
2656        );
2657        assert!(
2658            !child.blocking_io_inline_safe(),
2659            "spawn child must not inherit an OS-thread-only I/O permission"
2660        );
2661
2662        parent.cancel_with_reason(CancelReason::RegionClose);
2663        assert_eq!(child.cancel_reason(), Some(CancelReason::RegionClose));
2664    }
2665
2666    #[test]
2667    fn test_create_child_inherits_preexisting_parent_cancellation() {
2668        let parent = Cx::<FullCaps>::new();
2669        parent.cancel_with_reason(CancelReason::RegionClose);
2670
2671        let child = parent.create_child();
2672        assert_eq!(child.cancel_reason(), Some(CancelReason::RegionClose));
2673        assert_eq!(child.cancel_state(), CancelState::CancelRequested);
2674
2675        let err = child.checkpoint().unwrap_err();
2676        assert_eq!(err.kind(), ErrorKind::Cancelled);
2677    }
2678
2679    #[test]
2680    fn local_cancel_relay_is_subtree_scoped_and_reason_monotone() {
2681        let root = Cx::<FullCaps>::new();
2682        let sibling = root.create_child();
2683        let (operation, relay) = root.create_child_with_local_cancel_relay();
2684        let existing_descendant = operation.create_child();
2685
2686        assert!(relay.cancel_local(CancelReason::Timeout));
2687        assert!(relay.cancel_local(CancelReason::Abort));
2688        assert!(relay.cancel_local(CancelReason::UserInterrupt));
2689
2690        assert_eq!(operation.cancel_reason(), Some(CancelReason::Abort));
2691        assert_eq!(
2692            existing_descendant.cancel_reason(),
2693            Some(CancelReason::Abort)
2694        );
2695        assert!(operation.checkpoint().is_err());
2696        assert!(existing_descendant.checkpoint().is_err());
2697
2698        assert!(root.checkpoint().is_ok());
2699        assert!(sibling.checkpoint().is_ok());
2700        assert!(!root.is_cancel_requested());
2701        assert!(!sibling.is_cancel_requested());
2702
2703        let late_descendant = operation.create_child();
2704        assert_eq!(late_descendant.cancel_reason(), Some(CancelReason::Abort));
2705        assert!(late_descendant.checkpoint().is_err());
2706        assert!(root.checkpoint().is_ok());
2707    }
2708
2709    #[test]
2710    fn local_cancel_relay_is_weak_and_cross_thread_safe() {
2711        fn assert_send_sync<T: Send + Sync>() {}
2712        assert_send_sync::<LocalCancelRelay>();
2713
2714        let root = Cx::<FullCaps>::new();
2715        let (operation, relay) = root.create_child_with_local_cancel_relay();
2716        let cancel_thread =
2717            std::thread::spawn(move || relay.cancel_local(CancelReason::RegionClose));
2718        assert!(
2719            cancel_thread
2720                .join()
2721                .expect("cancel thread should not panic"),
2722            "live operation should accept a relayed cancellation"
2723        );
2724        assert_eq!(operation.cancel_reason(), Some(CancelReason::RegionClose));
2725
2726        let (dropped_operation, dropped_relay) = root.create_child_with_local_cancel_relay();
2727        drop(dropped_operation);
2728        assert!(
2729            !dropped_relay.cancel_local(CancelReason::Abort),
2730            "a weak relay must become inert after its target is dropped"
2731        );
2732        assert!(root.checkpoint().is_ok());
2733    }
2734
2735    #[test]
2736    fn local_cancellation_future_wakes_for_local_relay() {
2737        let root = Cx::<FullCaps>::new();
2738        let (operation, relay) = root.create_child_with_local_cancel_relay();
2739        let wake_count = Arc::new(CountingWake::default());
2740        let waker = Waker::from(Arc::clone(&wake_count));
2741        let mut task_cx = TaskContext::from_waker(&waker);
2742        let mut cancellation = std::pin::pin!(operation.wait_for_local_cancellation());
2743
2744        assert_eq!(
2745            cancellation.as_mut().poll(&mut task_cx),
2746            Poll::Pending,
2747            "uncancelled operation should register one waiter"
2748        );
2749        assert_eq!(local_cancel_waiter_count(&operation), 1);
2750
2751        assert!(relay.cancel_local(CancelReason::RegionClose));
2752        assert_eq!(
2753            wake_count.0.load(Ordering::Acquire),
2754            1,
2755            "local relay cancellation should wake the registered future"
2756        );
2757        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2758        assert_eq!(
2759            local_cancel_waiter_count(&operation),
2760            0,
2761            "ready future must leave no stale registration"
2762        );
2763        assert!(root.checkpoint().is_ok());
2764    }
2765
2766    #[test]
2767    fn dropping_local_cancellation_future_unregisters_waiter() {
2768        let cx = Cx::<FullCaps>::new();
2769        let wake_count = Arc::new(CountingWake::default());
2770        let waker = Waker::from(wake_count);
2771        let mut task_cx = TaskContext::from_waker(&waker);
2772
2773        {
2774            let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2775            assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2776            assert_eq!(local_cancel_waiter_count(&cx), 1);
2777        }
2778
2779        assert_eq!(
2780            local_cancel_waiter_count(&cx),
2781            0,
2782            "dropping a pending future must remove its waker"
2783        );
2784    }
2785
2786    #[test]
2787    fn already_cancelled_local_future_never_registers_a_waiter() {
2788        let cx = Cx::<FullCaps>::new();
2789        cx.cancel();
2790        assert_eq!(local_cancel_waiter_count(&cx), 0);
2791
2792        let wake_count = Arc::new(CountingWake::default());
2793        let waker = Waker::from(wake_count);
2794        let mut task_cx = TaskContext::from_waker(&waker);
2795        let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2796        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2797        assert_eq!(
2798            local_cancel_waiter_count(&cx),
2799            0,
2800            "an already-ready first poll must not register a waiter"
2801        );
2802    }
2803
2804    #[test]
2805    fn repoll_replaces_the_registered_waker() {
2806        let cx = Cx::<FullCaps>::new();
2807        let first_wake_count = Arc::new(CountingWake::default());
2808        let first_waker = Waker::from(Arc::clone(&first_wake_count));
2809        let mut first_task_cx = TaskContext::from_waker(&first_waker);
2810        let second_wake_count = Arc::new(CountingWake::default());
2811        let second_waker = Waker::from(Arc::clone(&second_wake_count));
2812        let mut second_task_cx = TaskContext::from_waker(&second_waker);
2813        let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2814
2815        assert_eq!(
2816            cancellation.as_mut().poll(&mut first_task_cx),
2817            Poll::Pending
2818        );
2819        assert_eq!(
2820            cancellation.as_mut().poll(&mut second_task_cx),
2821            Poll::Pending
2822        );
2823        assert_eq!(local_cancel_waiter_count(&cx), 1);
2824
2825        cx.cancel();
2826        assert_eq!(
2827            first_wake_count.0.load(Ordering::Acquire),
2828            0,
2829            "a replaced waker must not be invoked"
2830        );
2831        assert_eq!(
2832            second_wake_count.0.load(Ordering::Acquire),
2833            1,
2834            "only the most recently registered waker should be invoked"
2835        );
2836        assert_eq!(
2837            cancellation.as_mut().poll(&mut second_task_cx),
2838            Poll::Ready(())
2839        );
2840    }
2841
2842    #[test]
2843    fn local_cancellation_waker_runs_outside_the_registry_lock() {
2844        let cx = Cx::<FullCaps>::new();
2845        let probe = Arc::new(RegistryProbeWake {
2846            inner: Arc::downgrade(&cx.inner),
2847            wake_count: AtomicUsize::new(0),
2848            registry_was_unlocked: AtomicBool::new(false),
2849        });
2850        let waker = Waker::from(Arc::clone(&probe));
2851        let mut task_cx = TaskContext::from_waker(&waker);
2852        let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2853
2854        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2855        cx.cancel();
2856        assert!(
2857            probe.registry_was_unlocked.load(Ordering::Acquire),
2858            "wake callback should acquire the registry without reentrant deadlock"
2859        );
2860        assert_eq!(probe.wake_count.load(Ordering::Acquire), 1);
2861        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2862    }
2863
2864    #[test]
2865    fn cancellation_publishes_the_complete_subtree_before_waking_observers() {
2866        let root = Cx::<FullCaps>::new();
2867        let descendant = root.create_child();
2868        let probe = Arc::new(DescendantStateProbeWake {
2869            descendant: Arc::downgrade(&descendant.inner),
2870            wake_count: AtomicUsize::new(0),
2871            saw_descendant_cancelled: AtomicBool::new(false),
2872            dispatch_gate_was_unlocked: AtomicBool::new(false),
2873        });
2874        let waker = Waker::from(Arc::clone(&probe));
2875        let mut task_cx = TaskContext::from_waker(&waker);
2876        let mut cancellation = std::pin::pin!(root.wait_for_local_cancellation());
2877
2878        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2879        root.cancel();
2880
2881        assert_eq!(probe.wake_count.load(Ordering::Acquire), 1);
2882        assert!(
2883            probe.saw_descendant_cancelled.load(Ordering::Acquire),
2884            "reentrant observers must never see a half-published cancellation tree"
2885        );
2886        assert!(
2887            probe.dispatch_gate_was_unlocked.load(Ordering::Acquire),
2888            "callbacks must be able to re-enter family cancellation machinery"
2889        );
2890        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2891        assert!(descendant.checkpoint().is_err());
2892    }
2893
2894    #[test]
2895    fn panicking_cancellation_waker_does_not_suppress_other_observers() {
2896        let root = Cx::<FullCaps>::new();
2897        let descendant = root.create_child();
2898        let panic_waker = Waker::from(Arc::new(PanicWake));
2899        let mut panic_task_cx = TaskContext::from_waker(&panic_waker);
2900        let wake_count = Arc::new(CountingWake::default());
2901        let counting_waker = Waker::from(Arc::clone(&wake_count));
2902        let mut counting_task_cx = TaskContext::from_waker(&counting_waker);
2903        let mut panicking = std::pin::pin!(root.wait_for_local_cancellation());
2904        let mut counting = std::pin::pin!(root.wait_for_local_cancellation());
2905
2906        assert_eq!(panicking.as_mut().poll(&mut panic_task_cx), Poll::Pending);
2907        assert_eq!(counting.as_mut().poll(&mut counting_task_cx), Poll::Pending);
2908        let cancel_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2909            root.cancel();
2910        }));
2911
2912        assert!(
2913            cancel_result.is_err(),
2914            "the first callback panic must be resumed after notification completes"
2915        );
2916        assert_eq!(
2917            wake_count.0.load(Ordering::Acquire),
2918            1,
2919            "one panicking observer must not suppress later observers"
2920        );
2921        assert!(descendant.checkpoint().is_err());
2922        assert_eq!(panicking.as_mut().poll(&mut panic_task_cx), Poll::Ready(()));
2923        assert_eq!(
2924            counting.as_mut().poll(&mut counting_task_cx),
2925            Poll::Ready(())
2926        );
2927        assert_eq!(local_cancel_waiter_count(&root), 0);
2928    }
2929
2930    #[test]
2931    fn cancellation_waker_can_reenter_family_state_without_deadlock() {
2932        let root = Cx::<FullCaps>::new();
2933        let probe = Arc::new(ReentrantFamilyWake {
2934            cx: root.clone(),
2935            wake_count: AtomicUsize::new(0),
2936            child_inherited_cancellation: AtomicBool::new(false),
2937        });
2938        let waker = Waker::from(Arc::clone(&probe));
2939        let mut task_cx = TaskContext::from_waker(&waker);
2940        let mut cancellation = std::pin::pin!(root.wait_for_local_cancellation());
2941
2942        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2943        root.cancel();
2944
2945        assert_eq!(probe.wake_count.load(Ordering::Acquire), 1);
2946        assert!(
2947            probe.child_inherited_cancellation.load(Ordering::Acquire),
2948            "a child created reentrantly must be initialized before it is linked"
2949        );
2950        assert_eq!(root.cancel_reason(), Some(CancelReason::Abort));
2951        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2952    }
2953
2954    #[test]
2955    fn repeated_local_waiter_poll_and_drop_accumulates_nothing() {
2956        let cx = Cx::<FullCaps>::new();
2957        let initial_children = cx
2958            .inner
2959            .children
2960            .lock()
2961            .unwrap_or_else(std::sync::PoisonError::into_inner)
2962            .len();
2963        let wake_count = Arc::new(CountingWake::default());
2964        let waker = Waker::from(wake_count);
2965        let mut task_cx = TaskContext::from_waker(&waker);
2966
2967        for _ in 0..256 {
2968            let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2969            assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2970        }
2971
2972        assert_eq!(
2973            local_cancel_waiter_count(&cx),
2974            0,
2975            "dropped local wait futures must not accumulate registry entries"
2976        );
2977        assert_eq!(
2978            cx.inner
2979                .children
2980                .lock()
2981                .unwrap_or_else(std::sync::PoisonError::into_inner)
2982                .len(),
2983            initial_children,
2984            "local wait futures must not allocate child contexts"
2985        );
2986    }
2987
2988    #[test]
2989    fn local_cancellation_future_defers_while_masked_and_wakes_on_unmask() {
2990        let root = Cx::<FullCaps>::new();
2991        let (operation, relay) = root.create_child_with_local_cancel_relay();
2992        let mask = operation.masked();
2993        let wake_count = Arc::new(CountingWake::default());
2994        let waker = Waker::from(Arc::clone(&wake_count));
2995        let mut task_cx = TaskContext::from_waker(&waker);
2996        let mut cancellation = std::pin::pin!(operation.wait_for_local_cancellation());
2997
2998        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2999        assert!(relay.cancel_local(CancelReason::Abort));
3000        assert_eq!(wake_count.0.load(Ordering::Acquire), 1);
3001
3002        assert_eq!(
3003            cancellation.as_mut().poll(&mut task_cx),
3004            Poll::Pending,
3005            "masking must defer cancellation observation"
3006        );
3007        assert_eq!(
3008            local_cancel_waiter_count(&operation),
3009            1,
3010            "a masked future must remain registered for the unmask boundary"
3011        );
3012
3013        drop(mask);
3014        assert_eq!(
3015            wake_count.0.load(Ordering::Acquire),
3016            2,
3017            "outermost unmask must wake a deferred cancellation observer"
3018        );
3019        assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
3020    }
3021
3022    #[test]
3023    fn local_cancel_request_future_is_ready_while_masked() {
3024        let root = Cx::<FullCaps>::new();
3025        let (operation, relay) = root.create_child_with_local_cancel_relay();
3026        let mask = operation.masked();
3027        let wake_count = Arc::new(CountingWake::default());
3028        let waker = Waker::from(Arc::clone(&wake_count));
3029        let mut task_cx = TaskContext::from_waker(&waker);
3030        let mut request = std::pin::pin!(operation.wait_for_local_cancel_request());
3031
3032        assert_eq!(request.as_mut().poll(&mut task_cx), Poll::Pending);
3033        assert!(relay.cancel_local(CancelReason::UserInterrupt));
3034        assert_eq!(wake_count.0.load(Ordering::Acquire), 1);
3035        assert_eq!(
3036            request.as_mut().poll(&mut task_cx),
3037            Poll::Ready(()),
3038            "raw request notification must not reinterpret masking policy"
3039        );
3040        assert!(
3041            operation.checkpoint().is_ok(),
3042            "the context checkpoint itself must continue to defer while masked"
3043        );
3044        drop(mask);
3045        assert!(operation.checkpoint().is_err());
3046    }
3047
3048    #[test]
3049    fn local_cancellation_registration_race_leaves_no_stale_waiter() {
3050        for _ in 0..64 {
3051            let root = Cx::<FullCaps>::new();
3052            let (operation, relay) = root.create_child_with_local_cancel_relay();
3053            let barrier = Arc::new(Barrier::new(2));
3054            let cancel_barrier = Arc::clone(&barrier);
3055            let cancel_thread = std::thread::spawn(move || {
3056                cancel_barrier.wait();
3057                relay.cancel_local(CancelReason::UserInterrupt)
3058            });
3059
3060            let wake_count = Arc::new(CountingWake::default());
3061            let waker = Waker::from(Arc::clone(&wake_count));
3062            let mut task_cx = TaskContext::from_waker(&waker);
3063            let mut cancellation = std::pin::pin!(operation.wait_for_local_cancellation());
3064            barrier.wait();
3065            let first_poll = cancellation.as_mut().poll(&mut task_cx);
3066
3067            assert!(
3068                cancel_thread
3069                    .join()
3070                    .expect("cancel thread should not panic")
3071            );
3072            if first_poll == Poll::Pending {
3073                assert!(
3074                    wake_count.0.load(Ordering::Acquire) > 0,
3075                    "a waiter registered during cancellation must be notified"
3076                );
3077            }
3078            assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
3079            assert_eq!(
3080                local_cancel_waiter_count(&operation),
3081                0,
3082                "registration/cancellation race must not strand a waker"
3083            );
3084        }
3085    }
3086
3087    #[test]
3088    fn local_cancel_relay_handles_deep_subtrees_on_a_small_stack() {
3089        let root = Cx::<FullCaps>::new();
3090        let (operation, relay) = root.create_child_with_local_cancel_relay();
3091        let mut chain = Vec::with_capacity(8_193);
3092        chain.push(operation);
3093        for _ in 0..8_192 {
3094            let child = chain
3095                .last()
3096                .expect("chain must contain its root")
3097                .create_child();
3098            chain.push(child);
3099        }
3100
3101        let cancel_thread = std::thread::Builder::new()
3102            .name("local-cancel-deep-tree".to_owned())
3103            .stack_size(256 * 1024)
3104            .spawn(move || relay.cancel_local(CancelReason::RegionClose))
3105            .expect("small-stack cancellation thread should spawn");
3106        assert!(
3107            cancel_thread
3108                .join()
3109                .expect("iterative cancellation traversal must not overflow")
3110        );
3111        assert_eq!(
3112            chain.last().and_then(Cx::cancel_reason),
3113            Some(CancelReason::RegionClose)
3114        );
3115        assert!(root.checkpoint().is_ok());
3116    }
3117
3118    #[cfg(feature = "native")]
3119    #[test]
3120    fn test_cx_checkpoint_native_cx_cancellation_maps_reason() {
3121        let cx = Cx::<FullCaps>::new();
3122        let native = NativeCx::for_testing();
3123        cx.set_native_cx(native.clone());
3124        native.set_cancel_reason(NativeCancelReason::timeout());
3125
3126        let err = cx.checkpoint().unwrap_err();
3127        assert_eq!(err.kind(), ErrorKind::Cancelled);
3128        assert_eq!(cx.cancel_reason(), Some(CancelReason::Timeout));
3129    }
3130
3131    #[cfg(feature = "native")]
3132    #[test]
3133    fn test_cx_cancel_reason_propagates_to_native_cx() {
3134        let cx = Cx::<FullCaps>::new();
3135        let native = NativeCx::for_testing();
3136        cx.set_native_cx(native.clone());
3137
3138        cx.cancel_with_reason(CancelReason::RegionClose);
3139        let reason = native
3140            .cancel_reason()
3141            .expect("native cancel reason must be set");
3142        assert_eq!(reason.kind, NativeCancelKind::ParentCancelled);
3143    }
3144
3145    #[cfg(feature = "native")]
3146    #[test]
3147    fn local_cancel_relay_preserves_shared_native_context_for_late_children() {
3148        let root = Cx::<FullCaps>::new();
3149        let native = NativeCx::for_testing();
3150        root.set_native_cx(native.clone());
3151        let sibling = root.create_child();
3152        let (operation, relay) = root.create_child_with_local_cancel_relay();
3153        let existing_descendant = operation.create_child();
3154
3155        assert!(relay.cancel_local(CancelReason::Abort));
3156        assert!(operation.checkpoint().is_err());
3157        assert!(existing_descendant.checkpoint().is_err());
3158        assert!(root.checkpoint().is_ok());
3159        assert!(sibling.checkpoint().is_ok());
3160        assert!(
3161            native.checkpoint().is_ok(),
3162            "local operation cancellation must not poison shared native I/O state"
3163        );
3164
3165        let late_descendant = operation.create_child();
3166        assert!(late_descendant.checkpoint().is_err());
3167        assert!(
3168            native.checkpoint().is_ok(),
3169            "a descendant created after local cancellation must inherit locally"
3170        );
3171
3172        root.cancel_with_reason(CancelReason::RegionClose);
3173        assert!(
3174            native.is_cancel_requested(),
3175            "later ordinary root cancellation must still cross the native boundary"
3176        );
3177    }
3178
3179    #[cfg(feature = "native")]
3180    #[test]
3181    fn local_cancel_relay_preserves_native_contexts_attached_after_cancellation() {
3182        let root = Cx::<FullCaps>::new();
3183        let (operation, relay) = root.create_child_with_local_cancel_relay();
3184        assert!(relay.cancel_local(CancelReason::Abort));
3185
3186        let fallback = operation.effective_native_cx();
3187        assert!(
3188            fallback.checkpoint().is_ok(),
3189            "local cancellation must not taint a later fallback native context"
3190        );
3191
3192        let replacement = NativeCx::for_testing();
3193        operation.set_native_cx(replacement.clone());
3194        assert!(
3195            replacement.checkpoint().is_ok(),
3196            "local cancellation must not taint a later explicit native context"
3197        );
3198        assert!(operation.checkpoint().is_err());
3199    }
3200
3201    #[cfg(feature = "native")]
3202    #[test]
3203    fn local_reason_never_leaks_through_later_ordinary_cancellation() {
3204        let root = Cx::<FullCaps>::new();
3205        let shared_native = NativeCx::for_testing();
3206        root.set_native_cx(shared_native.clone());
3207        let (operation, relay) = root.create_child_with_local_cancel_relay();
3208
3209        assert!(relay.cancel_local(CancelReason::Abort));
3210        root.cancel_with_reason(CancelReason::Timeout);
3211
3212        assert_eq!(operation.cancel_reason(), Some(CancelReason::Abort));
3213        assert_eq!(
3214            shared_native
3215                .cancel_reason()
3216                .expect("ordinary cancellation must reach shared native")
3217                .kind,
3218            NativeCancelKind::Timeout,
3219            "the stronger local-only Abort must not cross the native boundary"
3220        );
3221
3222        let late_descendant = operation.create_child();
3223        assert_eq!(
3224            late_descendant.cancel_reason(),
3225            Some(CancelReason::Abort),
3226            "late descendants inherit the aggregate local reason"
3227        );
3228        assert_eq!(
3229            shared_native
3230                .cancel_reason()
3231                .expect("late-child registration must retain ordinary reason")
3232                .kind,
3233            NativeCancelKind::Timeout
3234        );
3235
3236        operation.clear_native_cx();
3237        let fallback = operation.effective_native_cx();
3238        assert_eq!(
3239            fallback
3240                .cancel_reason()
3241                .expect("late fallback must receive ordinary reason")
3242                .kind,
3243            NativeCancelKind::Timeout
3244        );
3245
3246        let replacement = NativeCx::for_testing();
3247        operation.set_native_cx(replacement.clone());
3248        assert_eq!(
3249            replacement
3250                .cancel_reason()
3251                .expect("late explicit attachment must receive ordinary reason")
3252                .kind,
3253            NativeCancelKind::Timeout
3254        );
3255    }
3256
3257    #[cfg(feature = "native")]
3258    #[test]
3259    fn weaker_ordinary_reason_cannot_downgrade_native_cancellation() {
3260        let cx = Cx::<FullCaps>::new();
3261        let native = NativeCx::for_testing();
3262        cx.set_native_cx(native.clone());
3263
3264        cx.cancel_with_reason(CancelReason::Abort);
3265        cx.cancel_with_reason(CancelReason::Timeout);
3266
3267        assert_eq!(cx.cancel_reason(), Some(CancelReason::Abort));
3268        assert_eq!(
3269            native
3270                .cancel_reason()
3271                .expect("native reason must remain present")
3272                .kind,
3273            NativeCancelKind::ResourceUnavailable
3274        );
3275    }
3276
3277    #[cfg(feature = "native")]
3278    #[test]
3279    fn native_attachment_racing_ordinary_cancellation_never_misses_reason() {
3280        for _ in 0..128 {
3281            let cx = Cx::<FullCaps>::new();
3282            let cancel_cx = cx.clone();
3283            let attach_cx = cx.clone();
3284            let native = NativeCx::for_testing();
3285            let attached_native = native.clone();
3286            let gate = Arc::new(std::sync::Barrier::new(3));
3287            let cancel_gate = Arc::clone(&gate);
3288            let attach_gate = Arc::clone(&gate);
3289
3290            let cancel_thread = std::thread::spawn(move || {
3291                cancel_gate.wait();
3292                cancel_cx.cancel_with_reason(CancelReason::RegionClose);
3293            });
3294            let attach_thread = std::thread::spawn(move || {
3295                attach_gate.wait();
3296                attach_cx.set_native_cx(attached_native);
3297            });
3298            gate.wait();
3299            cancel_thread.join().expect("cancellation must not panic");
3300            attach_thread.join().expect("attachment must not panic");
3301
3302            assert_eq!(
3303                native
3304                    .cancel_reason()
3305                    .expect("racing attachment must observe cancellation")
3306                    .kind,
3307                NativeCancelKind::ParentCancelled
3308            );
3309        }
3310    }
3311
3312    #[cfg(feature = "native")]
3313    #[test]
3314    fn racing_native_replacements_each_synchronize_the_exact_supplied_handle() {
3315        for _ in 0..128 {
3316            let cx = Cx::<FullCaps>::new();
3317            cx.cancel_with_reason(CancelReason::RegionClose);
3318
3319            let native_a = NativeCx::for_testing();
3320            let native_b = NativeCx::for_testing();
3321            let setter_a = cx.clone();
3322            let setter_b = cx.clone();
3323            let supplied_a = native_a.clone();
3324            let supplied_b = native_b.clone();
3325            let gate = Arc::new(std::sync::Barrier::new(3));
3326            let gate_a = Arc::clone(&gate);
3327            let gate_b = Arc::clone(&gate);
3328
3329            let thread_a = std::thread::spawn(move || {
3330                gate_a.wait();
3331                setter_a.set_native_cx(supplied_a);
3332            });
3333            let thread_b = std::thread::spawn(move || {
3334                gate_b.wait();
3335                setter_b.set_native_cx(supplied_b);
3336            });
3337            gate.wait();
3338            thread_a.join().expect("first replacement must not panic");
3339            thread_b.join().expect("second replacement must not panic");
3340
3341            for native in [&native_a, &native_b] {
3342                assert_eq!(
3343                    native
3344                        .cancel_reason()
3345                        .expect("each exact supplied handle must be synchronized")
3346                        .kind,
3347                    NativeCancelKind::ParentCancelled
3348                );
3349            }
3350        }
3351    }
3352
3353    #[cfg(feature = "native")]
3354    #[test]
3355    fn fallback_creation_racing_ordinary_cancellation_never_misses_reason() {
3356        for _ in 0..128 {
3357            let cx = Cx::<FullCaps>::new();
3358            let cancel_cx = cx.clone();
3359            let fallback_cx = cx.clone();
3360            let gate = Arc::new(std::sync::Barrier::new(3));
3361            let cancel_gate = Arc::clone(&gate);
3362            let fallback_gate = Arc::clone(&gate);
3363
3364            let cancel_thread = std::thread::spawn(move || {
3365                cancel_gate.wait();
3366                cancel_cx.cancel_with_reason(CancelReason::RegionClose);
3367            });
3368            let fallback_thread = std::thread::spawn(move || {
3369                fallback_gate.wait();
3370                fallback_cx.effective_native_cx()
3371            });
3372            gate.wait();
3373            cancel_thread.join().expect("cancellation must not panic");
3374            let native = fallback_thread
3375                .join()
3376                .expect("fallback creation must not panic");
3377
3378            assert_eq!(
3379                native
3380                    .cancel_reason()
3381                    .expect("racing fallback must observe cancellation")
3382                    .kind,
3383                NativeCancelKind::ParentCancelled
3384            );
3385        }
3386    }
3387
3388    #[cfg(feature = "native")]
3389    #[test]
3390    fn ordinary_cancel_before_native_attachment_is_mirrored_after_registration() {
3391        let cx = Cx::<FullCaps>::new();
3392        cx.cancel_with_reason(CancelReason::RegionClose);
3393
3394        let native = NativeCx::for_testing();
3395        cx.set_native_cx(native.clone());
3396        let reason = native
3397            .cancel_reason()
3398            .expect("ordinary local cancellation must mirror to a later attachment");
3399        assert_eq!(reason.kind, NativeCancelKind::ParentCancelled);
3400    }
3401
3402    #[cfg(feature = "native")]
3403    #[test]
3404    fn test_cx_checkpoint_native_cx_respects_local_masking() {
3405        let cx = Cx::<FullCaps>::new();
3406        let native = NativeCx::for_testing();
3407        cx.set_native_cx(native.clone());
3408        native.set_cancel_reason(NativeCancelReason::user("cancel"));
3409
3410        {
3411            let _mask = cx.masked();
3412            assert!(cx.checkpoint().is_ok());
3413            assert!(cx.is_cancel_requested());
3414            assert_eq!(cx.cancel_state(), CancelState::CancelRequested);
3415        }
3416
3417        let err = cx.checkpoint().unwrap_err();
3418        assert_eq!(err.kind(), ErrorKind::Cancelled);
3419    }
3420
3421    #[cfg(feature = "native")]
3422    #[test]
3423    fn test_cx_effective_native_cx_uses_fallback_without_marking_explicit_attachment() {
3424        let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(7));
3425
3426        assert!(cx.attached_native_cx().is_none());
3427        let native = cx.effective_native_cx();
3428        assert!(cx.attached_native_cx().is_none());
3429        assert!(native.checkpoint().is_ok());
3430    }
3431
3432    #[cfg(feature = "native")]
3433    #[test]
3434    fn test_cx_checkpoint_without_native_context_does_not_create_fallback() {
3435        let cx = Cx::<FullCaps>::new();
3436
3437        assert!(cx.inner.fallback_native_cx.get().is_none());
3438        assert!(cx.checkpoint().is_ok());
3439        assert!(cx.inner.fallback_native_cx.get().is_none());
3440    }
3441
3442    #[cfg(feature = "native")]
3443    #[test]
3444    fn test_cx_set_native_cx_replaces_fallback_context() {
3445        let cx = Cx::<FullCaps>::new();
3446        let _ = cx.effective_native_cx();
3447
3448        let replacement = NativeCx::for_testing();
3449        cx.set_native_cx(replacement.clone());
3450        replacement.set_cancel_reason(NativeCancelReason::timeout());
3451
3452        let err = cx.checkpoint().unwrap_err();
3453        assert_eq!(err.kind(), ErrorKind::Cancelled);
3454        assert_eq!(cx.cancel_reason(), Some(CancelReason::Timeout));
3455    }
3456
3457    #[cfg(feature = "native")]
3458    #[test]
3459    fn test_create_child_copies_preexisting_cancellation_into_fallback_native_cx() {
3460        let parent = Cx::<FullCaps>::new();
3461        parent.cancel_with_reason(CancelReason::RegionClose);
3462
3463        let child = parent.create_child();
3464        let reason = child
3465            .effective_native_cx()
3466            .cancel_reason()
3467            .expect("fallback native cx should mirror inherited cancellation");
3468        assert_eq!(reason.kind, NativeCancelKind::ParentCancelled);
3469    }
3470
3471    #[cfg(feature = "native")]
3472    #[test]
3473    fn test_create_child_inherits_explicit_native_cx_attachment() {
3474        let parent = Cx::<FullCaps>::new();
3475        let native = NativeCx::for_testing();
3476        parent.set_native_cx(native.clone());
3477
3478        let child = parent.create_child();
3479        assert!(child.attached_native_cx().is_some());
3480
3481        native.set_cancel_reason(NativeCancelReason::timeout());
3482        let err = child
3483            .checkpoint()
3484            .expect_err("child should observe inherited native cancel");
3485        assert_eq!(err.kind(), ErrorKind::Cancelled);
3486        assert_eq!(child.cancel_reason(), Some(CancelReason::Timeout));
3487    }
3488
3489    #[cfg(feature = "native")]
3490    #[test]
3491    fn spawn_child_does_not_carry_a_task_affine_native_context() {
3492        let parent = Cx::<FullCaps>::new();
3493        parent.set_native_cx(NativeCx::for_testing());
3494        let child = parent.create_child_for_spawn();
3495
3496        assert!(
3497            child.attached_native_cx().is_none(),
3498            "spawn child must start without the caller task's native context"
3499        );
3500        assert!(
3501            child.inner.fallback_native_cx.get().is_none(),
3502            "spawn child must not invent a fallback context before task entry"
3503        );
3504
3505        let task_native = NativeCx::for_testing();
3506        child.set_native_cx(task_native);
3507        assert!(
3508            child.attached_native_cx().is_some(),
3509            "the spawned task must be able to attach its own native context"
3510        );
3511    }
3512
3513    #[test]
3514    fn test_budget_infinite_is_identity_for_meet() {
3515        let budget = Budget {
3516            deadline: Some(Duration::from_millis(42)),
3517            poll_quota: 500,
3518            cost_quota: Some(1000),
3519            priority: 7,
3520        };
3521        assert_eq!(budget.meet(Budget::INFINITE), budget);
3522        assert_eq!(Budget::INFINITE.meet(budget), budget);
3523    }
3524
3525    #[test]
3526    fn test_budget_none_constraints_propagate() {
3527        let a = Budget {
3528            deadline: None,
3529            poll_quota: u32::MAX,
3530            cost_quota: None,
3531            priority: 0,
3532        };
3533        let b = Budget {
3534            deadline: Some(Duration::from_millis(50)),
3535            poll_quota: 100,
3536            cost_quota: Some(500),
3537            priority: 3,
3538        };
3539        let m = a.meet(b);
3540        assert_eq!(m.deadline, Some(Duration::from_millis(50)));
3541        assert_eq!(m.poll_quota, 100);
3542        assert_eq!(m.cost_quota, Some(500));
3543        assert_eq!(m.priority, 3);
3544    }
3545
3546    #[test]
3547    fn test_cx_scope_budget_chains() {
3548        let cx = Cx::<FullCaps>::with_budget(
3549            Budget::INFINITE
3550                .with_deadline(Duration::from_millis(100))
3551                .with_poll_quota(1000),
3552        );
3553        // First scope tightens deadline.
3554        let s1 = cx.scope_with_budget(Budget::INFINITE.with_deadline(Duration::from_millis(50)));
3555        assert_eq!(s1.budget().deadline, Some(Duration::from_millis(50)));
3556        assert_eq!(s1.budget().poll_quota, 1000);
3557
3558        // Second scope tightens poll_quota further.
3559        let s2 = s1.scope_with_budget(Budget::INFINITE.with_poll_quota(200));
3560        assert_eq!(s2.budget().deadline, Some(Duration::from_millis(50)));
3561        assert_eq!(s2.budget().poll_quota, 200);
3562    }
3563
3564    fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
3565        for entry in std::fs::read_dir(dir)? {
3566            let entry = entry?;
3567            let path = entry.path();
3568            if path.is_dir() {
3569                collect_rs_files(&path, out)?;
3570            } else if path.extension().is_some_and(|ext| ext == "rs") {
3571                out.push(path);
3572            }
3573        }
3574        Ok(())
3575    }
3576
3577    fn scan_file_outside_cfg_test_items(src: &str, patterns: &[&str]) -> Vec<(usize, String)> {
3578        let mut hits = Vec::new();
3579
3580        let mut brace_depth: i32 = 0;
3581        let mut pending_cfg_test = false;
3582        let mut pending_attr_paren_depth: i32 = 0;
3583        let mut skip_until_depth: Option<i32> = None;
3584
3585        for (idx, line) in src.lines().enumerate() {
3586            let trimmed = line.trim_start();
3587            let paren_delta = i32::try_from(line.matches('(').count()).unwrap_or(i32::MAX)
3588                - i32::try_from(line.matches(')').count()).unwrap_or(i32::MAX);
3589
3590            if skip_until_depth.is_none() {
3591                // Handle single-line `#[cfg(test)]` items that open a block immediately.
3592                if trimmed.starts_with("#[cfg(test)]") && trimmed.contains('{') {
3593                    pending_cfg_test = false;
3594                    pending_attr_paren_depth = 0;
3595                    skip_until_depth = Some(brace_depth);
3596                } else if trimmed.contains("fn test_") && trimmed.contains('{') {
3597                    skip_until_depth = Some(brace_depth);
3598                } else if trimmed.starts_with("#[cfg(test)]") {
3599                    pending_cfg_test = true;
3600                    pending_attr_paren_depth = 0;
3601                } else if pending_cfg_test {
3602                    // Allow additional attributes/blank lines before the gated item.
3603                    if trimmed.starts_with("#[") || pending_attr_paren_depth > 0 {
3604                        pending_attr_paren_depth =
3605                            pending_attr_paren_depth.saturating_add(paren_delta);
3606                    } else if trimmed.is_empty() || trimmed.starts_with("//") {
3607                        // keep pending
3608                    } else if trimmed.contains('{') {
3609                        pending_cfg_test = false;
3610                        pending_attr_paren_depth = 0;
3611                        skip_until_depth = Some(brace_depth);
3612                    } else {
3613                        pending_cfg_test = false;
3614                        pending_attr_paren_depth = 0;
3615                    }
3616                } else {
3617                    for &pat in patterns {
3618                        if line.contains(pat) {
3619                            hits.push((idx + 1, pat.to_string()));
3620                        }
3621                    }
3622                }
3623            }
3624
3625            // Update brace depth (coarse; sufficient for `#[cfg(test)] mod ... {}` blocks).
3626            let opens = i32::try_from(line.matches('{').count()).unwrap_or(i32::MAX);
3627            let closes = i32::try_from(line.matches('}').count()).unwrap_or(i32::MAX);
3628            brace_depth = brace_depth.saturating_add(opens).saturating_sub(closes);
3629
3630            if let Some(until) = skip_until_depth
3631                && brace_depth <= until
3632            {
3633                skip_until_depth = None;
3634            }
3635        }
3636
3637        hits
3638    }
3639
3640    #[test]
3641    fn test_scan_file_outside_cfg_test_items_skips_cfg_test_functions_and_modules() {
3642        let src = r"
3643fn production_path() {
3644    let _ = Cx::new();
3645}
3646
3647#[cfg(test)]
3648fn test_only_helper() {
3649    let _ = Cx::new();
3650}
3651
3652#[cfg(test)]
3653mod tests {
3654    fn nested_test_helper() {
3655        let _ = Cx::default();
3656    }
3657}
3658";
3659
3660        let hits = scan_file_outside_cfg_test_items(src, &["Cx::new(", "Cx::default("]);
3661        assert_eq!(hits, vec![(3, "Cx::new(".to_string())]);
3662    }
3663
3664    #[test]
3665    fn test_no_direct_cx_constructors_in_runtime_production_code() {
3666        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
3667        let repo_root = manifest_dir
3668            .parent()
3669            .and_then(Path::parent)
3670            .expect("fsqlite-types manifest dir must be crates/<name>");
3671        let crates_dir = repo_root.join("crates");
3672        let runtime_crates = [
3673            "fsqlite-core",
3674            "fsqlite-vdbe",
3675            "fsqlite-btree",
3676            "fsqlite-pager",
3677            "fsqlite-wal",
3678            "fsqlite-mvcc",
3679        ];
3680        let forbidden = ["Cx::new(", "Cx::default("];
3681
3682        let mut violations: Vec<String> = Vec::new();
3683        let mut crate_dirs: Vec<PathBuf> = Vec::new();
3684        for entry in std::fs::read_dir(&crates_dir).expect("read crates/ dir") {
3685            let entry = entry.expect("read crates/ entry");
3686            let path = entry.path();
3687            if path.is_dir() {
3688                crate_dirs.push(path);
3689            }
3690        }
3691
3692        for crate_dir in crate_dirs {
3693            let crate_name = crate_dir
3694                .file_name()
3695                .and_then(|s| s.to_str())
3696                .unwrap_or("<unknown>");
3697            if !runtime_crates.contains(&crate_name) {
3698                continue;
3699            }
3700
3701            let src_dir = crate_dir.join("src");
3702            if !src_dir.is_dir() {
3703                continue;
3704            }
3705
3706            let mut files = Vec::new();
3707            collect_rs_files(&src_dir, &mut files).expect("collect rs files");
3708
3709            for file in files {
3710                if file
3711                    .file_name()
3712                    .and_then(|name| name.to_str())
3713                    .is_some_and(|name| name.contains("test"))
3714                {
3715                    continue;
3716                }
3717
3718                let src = std::fs::read_to_string(&file).expect("read file");
3719                let rel_path = file.strip_prefix(repo_root).unwrap_or(&file);
3720
3721                for (line, pat) in scan_file_outside_cfg_test_items(&src, &forbidden) {
3722                    let line_text = src.lines().nth(line - 1).unwrap_or("").trim();
3723                    // Every detached-root context now routes through the sanctioned
3724                    // `Cx::detached_rebind()` constructor (fsqlite-types), which the
3725                    // forbidden scan never matches. Bare `Cx::new()` / `Cx::default()`
3726                    // is therefore forbidden in runtime crates with ZERO exceptions
3727                    // (bd-fq2lf: the last connection.rs allowlist entry was removed
3728                    // once `detached_root_cx` and `detached_rebind_cx` were routed
3729                    // through the factory).
3730                    violations.push(format!(
3731                        "{crate_name}:{path}:{line} uses forbidden `{pat}` outside cfg(test) code: {line_text}",
3732                        path = rel_path.display()
3733                    ));
3734                }
3735            }
3736        }
3737
3738        assert!(
3739            violations.is_empty(),
3740            "direct `Cx::new()` / `Cx::default()` production-path violations:\n{}",
3741            violations.join("\n")
3742        );
3743    }
3744
3745    #[test]
3746    fn test_ambient_authority_audit_gate() {
3747        // Scan `crates/*/src/**/*.rs` for ambient-authority usage, excluding
3748        // `#[cfg(test)]`-gated items.
3749        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
3750        let repo_root = manifest_dir
3751            .parent()
3752            .and_then(Path::parent)
3753            .expect("fsqlite-types manifest dir must be crates/<name>");
3754        let crates_dir = repo_root.join("crates");
3755
3756        // Always forbidden everywhere (outside cfg(test) modules).
3757        let always_forbidden = [
3758            "SystemTime::now(",
3759            "Instant::now(",
3760            "thread_rng(",
3761            "getrandom",
3762            "std::net::",
3763            "std::thread::spawn",
3764            "tokio::spawn",
3765        ];
3766
3767        // Forbidden outside VFS boundary (outside cfg(test) modules).
3768        let non_vfs_forbidden = ["std::fs::"];
3769
3770        // Crates exempt from ambient-authority scanning:
3771        // - test infrastructure (harness, cli, e2e)
3772        // - observability (pure diagnostics, needs Instant::now for timing)
3773        // - core (needs std::fs for WAL bootstrap/MVCC key, Instant::now for tracing)
3774        // - vdbe (needs std::fs for sorter temp files, Instant::now for tracing)
3775        // - mvcc (Instant::now in flat_combining/rcu for latency metrics)
3776        // - parser (Instant::now for lexer span timing)
3777        // - planner (Instant::now for access-path selection, SystemTime for contracts)
3778        // - wal (Instant::now for checkpoint timing)
3779        // - vfs (Instant::now for VFS operation metrics, std::fs allowed by design)
3780        // - types (capability-context implementation and its test audit)
3781        // - func (SQL date/time functions are wall-clock by definition:
3782        //   datetime('now'), unixepoch(), strftime('now', ...))
3783        // - fsqlite (migration busy-retry timeout: Instant::now bounds the
3784        //   SQLITE_BUSY retry loop in apply_one)
3785        // - btree (B-tree cursor/instrumentation latency metrics)
3786        // - c-api (FFI boundary, like vfs: the C ABI shim does C-style time/file
3787        //   ops and carries its own local unsafe_code override)
3788        // - pager (pager/page-cache latency metrics + shared_file_state_key
3789        //   canonicalize; the one control-flow time use — eviction shard-probe
3790        //   start — was replaced with a deterministic round-robin, bd-w4yc9)
3791        // - beads-doctor (standalone CLI monitoring tool: std::fs for config /
3792        //   rolling-log / OS-unit-file I/O, and a single SystemTime::now —
3793        //   quarantined behind a `Clock` trait — for calendar-age log rotation,
3794        //   which asupersync's process-monotonic `wall_now` cannot express; the
3795        //   rotation *decision* is a pure, clock-free function — bd-316l0)
3796        //
3797        // The gate still guards the extension crates (fts3/fts5/rtree/json/
3798        // session/icu/misc), ast, error, and wasm, where ambient authority must
3799        // not appear. bd-w4yc9.
3800        let exempt_crates = [
3801            "fsqlite-harness",
3802            "fsqlite-cli",
3803            "fsqlite-e2e",
3804            "fsqlite-observability",
3805            "fsqlite-core",
3806            "fsqlite-vdbe",
3807            "fsqlite-mvcc",
3808            "fsqlite-parser",
3809            "fsqlite-planner",
3810            "fsqlite-wal",
3811            "fsqlite-vfs",
3812            "fsqlite-types",
3813            "fsqlite-func",
3814            "fsqlite",
3815            "fsqlite-btree",
3816            "fsqlite-c-api",
3817            "fsqlite-pager",
3818            "beads-doctor",
3819        ];
3820
3821        let mut violations: Vec<String> = Vec::new();
3822        let mut crate_dirs: Vec<PathBuf> = Vec::new();
3823        for entry in std::fs::read_dir(&crates_dir).expect("read crates/ dir") {
3824            let entry = entry.expect("read crates/ entry");
3825            let path = entry.path();
3826            if path.is_dir() {
3827                crate_dirs.push(path);
3828            }
3829        }
3830
3831        for crate_dir in crate_dirs {
3832            let crate_name = crate_dir
3833                .file_name()
3834                .and_then(|s| s.to_str())
3835                .unwrap_or("<unknown>");
3836            if exempt_crates.contains(&crate_name) {
3837                continue;
3838            }
3839            let src_dir = crate_dir.join("src");
3840            if !src_dir.is_dir() {
3841                continue;
3842            }
3843
3844            let mut files = Vec::new();
3845            collect_rs_files(&src_dir, &mut files).expect("collect rs files");
3846
3847            for file in files {
3848                let src = std::fs::read_to_string(&file).expect("read file");
3849                for (line, pat) in scan_file_outside_cfg_test_items(&src, &always_forbidden) {
3850                    violations.push(format!(
3851                        "{crate_name}:{path}:{line} uses forbidden `{pat}`",
3852                        path = file.display()
3853                    ));
3854                }
3855
3856                if crate_name != "fsqlite-vfs" {
3857                    for (line, pat) in scan_file_outside_cfg_test_items(&src, &non_vfs_forbidden) {
3858                        violations.push(format!(
3859                            "{crate_name}:{path}:{line} uses forbidden `{pat}` (non-vfs crate)",
3860                            path = file.display()
3861                        ));
3862                    }
3863                }
3864            }
3865        }
3866
3867        assert!(
3868            violations.is_empty(),
3869            "ambient authority violations (outside cfg(test) modules):\n{}",
3870            violations.join("\n")
3871        );
3872    }
3873
3874    // ===================================================================
3875    // §4.12 Cancellation Protocol Tests (bd-samf)
3876    // ===================================================================
3877
3878    const BEAD_ID: &str = "bd-samf";
3879
3880    #[test]
3881    fn test_cancel_state_machine_all_transitions() {
3882        // Test 1: State machine transitions through all 6 states.
3883        let cx = Cx::<FullCaps>::new();
3884        assert_eq!(
3885            cx.cancel_state(),
3886            CancelState::Created,
3887            "bead_id={BEAD_ID} initial_state"
3888        );
3889
3890        cx.transition_to_running();
3891        assert_eq!(
3892            cx.cancel_state(),
3893            CancelState::Running,
3894            "bead_id={BEAD_ID} after_start"
3895        );
3896
3897        cx.cancel_with_reason(CancelReason::UserInterrupt);
3898        assert_eq!(
3899            cx.cancel_state(),
3900            CancelState::CancelRequested,
3901            "bead_id={BEAD_ID} after_cancel"
3902        );
3903
3904        // Observing cancellation via checkpoint transitions to Cancelling.
3905        let err = cx.checkpoint();
3906        assert!(err.is_err(), "bead_id={BEAD_ID} checkpoint_returns_err");
3907        assert_eq!(
3908            cx.cancel_state(),
3909            CancelState::Cancelling,
3910            "bead_id={BEAD_ID} after_checkpoint_observation"
3911        );
3912
3913        cx.transition_to_finalizing();
3914        assert_eq!(
3915            cx.cancel_state(),
3916            CancelState::Finalizing,
3917            "bead_id={BEAD_ID} after_finalize_start"
3918        );
3919
3920        cx.transition_to_completed();
3921        assert_eq!(
3922            cx.cancel_state(),
3923            CancelState::Completed,
3924            "bead_id={BEAD_ID} after_complete"
3925        );
3926    }
3927
3928    #[test]
3929    fn test_cancel_propagates_to_children() {
3930        // Test 2: Cancel propagates to 3 children within one call.
3931        let parent = Cx::<FullCaps>::new();
3932        parent.transition_to_running();
3933
3934        let child1 = parent.create_child();
3935        child1.transition_to_running();
3936        let child2 = parent.create_child();
3937        child2.transition_to_running();
3938        let child3 = parent.create_child();
3939        child3.transition_to_running();
3940
3941        assert!(!child1.is_cancel_requested());
3942        assert!(!child2.is_cancel_requested());
3943        assert!(!child3.is_cancel_requested());
3944
3945        parent.cancel_with_reason(CancelReason::RegionClose);
3946
3947        // All children must see cancellation (INV-CANCEL-PROPAGATES).
3948        assert!(
3949            child1.is_cancel_requested(),
3950            "bead_id={BEAD_ID} child1_cancelled"
3951        );
3952        assert!(
3953            child2.is_cancel_requested(),
3954            "bead_id={BEAD_ID} child2_cancelled"
3955        );
3956        assert!(
3957            child3.is_cancel_requested(),
3958            "bead_id={BEAD_ID} child3_cancelled"
3959        );
3960
3961        // Children must be in CancelRequested state.
3962        assert_eq!(child1.cancel_state(), CancelState::CancelRequested);
3963        assert_eq!(child2.cancel_state(), CancelState::CancelRequested);
3964        assert_eq!(child3.cancel_state(), CancelState::CancelRequested);
3965
3966        // Reason must propagate.
3967        assert_eq!(child1.cancel_reason(), Some(CancelReason::RegionClose));
3968    }
3969
3970    #[test]
3971    fn test_dropped_children_are_pruned_from_parent_links() {
3972        let parent = Cx::<FullCaps>::new();
3973
3974        let live_child = parent.create_child();
3975        let dropped_child = parent.create_child();
3976        drop(dropped_child);
3977
3978        // Trigger propagation pass, which prunes dead weak child links.
3979        parent.cancel_with_reason(CancelReason::RegionClose);
3980
3981        let live_count = {
3982            let children = parent
3983                .inner
3984                .children
3985                .lock()
3986                .unwrap_or_else(std::sync::PoisonError::into_inner);
3987            children.iter().filter_map(Weak::upgrade).count()
3988        };
3989        assert_eq!(live_count, 1, "only the live child should remain linked");
3990        assert!(live_child.is_cancel_requested());
3991    }
3992
3993    #[test]
3994    fn dropped_children_are_pruned_before_registry_growth_without_cancellation() {
3995        let parent = Cx::<FullCaps>::new();
3996        drop(parent.create_child());
3997        let initial_capacity = parent
3998            .inner
3999            .children
4000            .lock()
4001            .unwrap_or_else(std::sync::PoisonError::into_inner)
4002            .capacity();
4003        assert!(initial_capacity > 0);
4004
4005        for _ in 0..4_096 {
4006            drop(parent.create_child());
4007        }
4008
4009        let children = parent
4010            .inner
4011            .children
4012            .lock()
4013            .unwrap_or_else(std::sync::PoisonError::into_inner);
4014        assert_eq!(
4015            children.capacity(),
4016            initial_capacity,
4017            "historical dead children must not grow an uncancelled family registry"
4018        );
4019        assert!(
4020            children.len() <= initial_capacity,
4021            "only the current bounded batch of dead weak links may remain"
4022        );
4023    }
4024
4025    #[test]
4026    fn test_cancel_idempotent_strongest_wins() {
4027        // Test 3: Strongest cancel reason wins, cannot get weaker.
4028        let cx = Cx::<FullCaps>::new();
4029        cx.transition_to_running();
4030
4031        cx.cancel_with_reason(CancelReason::Timeout);
4032        assert_eq!(
4033            cx.cancel_reason(),
4034            Some(CancelReason::Timeout),
4035            "bead_id={BEAD_ID} first_reason"
4036        );
4037
4038        // Stronger reason upgrades.
4039        cx.cancel_with_reason(CancelReason::Abort);
4040        assert_eq!(
4041            cx.cancel_reason(),
4042            Some(CancelReason::Abort),
4043            "bead_id={BEAD_ID} upgraded_reason"
4044        );
4045
4046        // Weaker reason does NOT downgrade.
4047        cx.cancel_with_reason(CancelReason::UserInterrupt);
4048        assert_eq!(
4049            cx.cancel_reason(),
4050            Some(CancelReason::Abort),
4051            "bead_id={BEAD_ID} reason_stays_strongest"
4052        );
4053    }
4054
4055    #[test]
4056    fn test_losers_drain_on_race() {
4057        // Test 4: Simulate race combinator — loser with obligation resolves
4058        // before race returns.
4059        use std::sync::atomic::AtomicBool;
4060
4061        let loser_cx = Cx::<FullCaps>::new();
4062        loser_cx.transition_to_running();
4063
4064        // Simulate an obligation on the loser.
4065        let obligation_resolved = Arc::new(AtomicBool::new(false));
4066        let ob_clone = Arc::clone(&obligation_resolved);
4067
4068        // Winner finishes → cancel loser.
4069        loser_cx.cancel_with_reason(CancelReason::RegionClose);
4070
4071        // Loser observes cancellation at next checkpoint.
4072        assert!(loser_cx.checkpoint().is_err());
4073        assert_eq!(loser_cx.cancel_state(), CancelState::Cancelling);
4074
4075        // Loser drains: resolves obligation.
4076        ob_clone.store(true, Ordering::Release);
4077        loser_cx.transition_to_finalizing();
4078        loser_cx.transition_to_completed();
4079
4080        assert!(
4081            obligation_resolved.load(Ordering::Acquire),
4082            "bead_id={BEAD_ID} loser_obligation_resolved"
4083        );
4084        assert_eq!(
4085            loser_cx.cancel_state(),
4086            CancelState::Completed,
4087            "bead_id={BEAD_ID} loser_drained"
4088        );
4089    }
4090
4091    #[test]
4092    fn test_vdbe_checkpoint_cancel_observed_at_next_opcode() {
4093        // Test 5: Simulate VDBE opcode loop — cancel after opcode 50,
4094        // observed at opcode 51.
4095        let cx = Cx::<FullCaps>::new();
4096        cx.transition_to_running();
4097
4098        let mut last_executed = 0u32;
4099        for opcode in 0..100u32 {
4100            // Checkpoint at start of each opcode.
4101            if cx.checkpoint_with(format!("vdbe pc={opcode}")).is_err() {
4102                last_executed = opcode;
4103                break;
4104            }
4105            // Execute opcode.
4106            last_executed = opcode;
4107            // Cancel arrives at end of opcode 50.
4108            if opcode == 50 {
4109                cx.cancel_with_reason(CancelReason::UserInterrupt);
4110            }
4111        }
4112
4113        assert_eq!(
4114            last_executed, 51,
4115            "bead_id={BEAD_ID} cancel_observed_at_opcode_51"
4116        );
4117    }
4118
4119    #[test]
4120    fn test_btree_checkpoint_cancel_within_one_node() {
4121        // Test 6: Simulate B-tree descent — cancel mid-descent, observed
4122        // within 1 node visit.
4123        let cx = Cx::<FullCaps>::new();
4124        cx.transition_to_running();
4125
4126        let nodes = ["root", "internal_l", "internal_r", "leaf_a", "leaf_b"];
4127        let cancel_at = 2; // Cancel after visiting internal_r.
4128        let mut observed_at = None;
4129
4130        for (i, node) in nodes.iter().enumerate() {
4131            // Checkpoint at start of each node visit.
4132            if cx.checkpoint_with(format!("btree node={node}")).is_err() {
4133                observed_at = Some(i);
4134                break;
4135            }
4136            // Visit node.
4137            // Cancel arrives after visiting node at index cancel_at.
4138            if i == cancel_at {
4139                cx.cancel_with_reason(CancelReason::UserInterrupt);
4140            }
4141        }
4142
4143        assert_eq!(
4144            observed_at,
4145            Some(cancel_at + 1),
4146            "bead_id={BEAD_ID} btree_cancel_within_one_node"
4147        );
4148    }
4149
4150    #[test]
4151    fn test_masked_section_defers_cancel() {
4152        // Test 7: Masked section defers cancel — checkpoint returns Ok inside
4153        // mask, Err after exit.
4154        let cx = Cx::<FullCaps>::new();
4155        cx.transition_to_running();
4156
4157        cx.cancel_with_reason(CancelReason::UserInterrupt);
4158        assert!(cx.is_cancel_requested());
4159
4160        // Enter masked section.
4161        {
4162            let _guard = cx.masked();
4163            assert_eq!(cx.mask_depth(), 1);
4164
4165            // Inside mask, checkpoint succeeds despite cancellation.
4166            assert!(
4167                cx.checkpoint().is_ok(),
4168                "bead_id={BEAD_ID} checkpoint_ok_while_masked"
4169            );
4170
4171            // Nested mask.
4172            {
4173                let _inner = cx.masked();
4174                assert_eq!(cx.mask_depth(), 2);
4175                assert!(cx.checkpoint().is_ok());
4176            }
4177            assert_eq!(cx.mask_depth(), 1);
4178        }
4179        assert_eq!(cx.mask_depth(), 0);
4180
4181        // After mask exit, checkpoint observes cancellation.
4182        assert!(
4183            cx.checkpoint().is_err(),
4184            "bead_id={BEAD_ID} checkpoint_err_after_mask_exit"
4185        );
4186    }
4187
4188    #[test]
4189    #[should_panic(expected = "MAX_MASK_DEPTH")]
4190    #[allow(clippy::collection_is_never_read)]
4191    fn test_max_mask_depth_exceeded_panics() {
4192        // Test 8: MAX_MASK_DEPTH=64 exceeded panics in lab mode.
4193        let cx = Cx::<FullCaps>::new();
4194        let mut guards = Vec::new();
4195        for _ in 0..MAX_MASK_DEPTH {
4196            guards.push(cx.masked());
4197        }
4198        // This 65th mask should panic.
4199        let _overflow = cx.masked();
4200    }
4201
4202    #[test]
4203    fn test_commit_section_completes_under_cancel() {
4204        // Test 9: Cancel after op 1 of 3, all 3 complete + finalizers run.
4205        let cx = Cx::<FullCaps>::new();
4206        cx.transition_to_running();
4207
4208        let ops_completed = Arc::new(AtomicU32::new(0));
4209        let finalizer_ran = Arc::new(AtomicBool::new(false));
4210
4211        let ops = Arc::clone(&ops_completed);
4212        let fin = Arc::clone(&finalizer_ran);
4213
4214        cx.commit_section(
4215            10,
4216            |ctx| {
4217                // Op 1.
4218                assert!(ctx.tick());
4219                ops.fetch_add(1, Ordering::Release);
4220
4221                // Cancel mid-section.
4222                cx.cancel_with_reason(CancelReason::UserInterrupt);
4223
4224                // Op 2: still succeeds because commit section is masked.
4225                assert!(ctx.tick());
4226                ops.fetch_add(1, Ordering::Release);
4227                assert!(
4228                    cx.checkpoint().is_ok(),
4229                    "bead_id={BEAD_ID} masked_during_commit"
4230                );
4231
4232                // Op 3.
4233                assert!(ctx.tick());
4234                ops.fetch_add(1, Ordering::Release);
4235            },
4236            move || {
4237                fin.store(true, Ordering::Release);
4238            },
4239        );
4240
4241        assert_eq!(
4242            ops_completed.load(Ordering::Acquire),
4243            3,
4244            "bead_id={BEAD_ID} all_ops_completed"
4245        );
4246        assert!(
4247            finalizer_ran.load(Ordering::Acquire),
4248            "bead_id={BEAD_ID} finalizer_ran"
4249        );
4250
4251        // After commit section, masking is removed — checkpoint should fail.
4252        assert!(cx.checkpoint().is_err());
4253    }
4254
4255    #[test]
4256    fn test_commit_section_enforces_poll_quota() {
4257        // Test 10: Commit section poll quota is bounded.
4258        let cx = Cx::<FullCaps>::new();
4259        cx.transition_to_running();
4260
4261        let ticks_succeeded = Arc::new(AtomicU32::new(0));
4262        let ts = Arc::clone(&ticks_succeeded);
4263
4264        cx.commit_section(
4265            3,
4266            |ctx| {
4267                assert_eq!(ctx.poll_remaining(), 3);
4268                for _ in 0..5 {
4269                    if ctx.tick() {
4270                        ts.fetch_add(1, Ordering::Release);
4271                    }
4272                }
4273            },
4274            || {},
4275        );
4276
4277        assert_eq!(
4278            ticks_succeeded.load(Ordering::Acquire),
4279            3,
4280            "bead_id={BEAD_ID} poll_quota_enforced"
4281        );
4282    }
4283
4284    #[test]
4285    fn test_cancel_unaware_hot_loop_detected() {
4286        // Test 11: Simulate harness detecting a hot loop that never
4287        // calls checkpoint.
4288        let cx = Cx::<FullCaps>::new();
4289        cx.transition_to_running();
4290
4291        // Harness deadline: if 100 iterations pass without checkpoint,
4292        // the loop is cancel-unaware.
4293        let deadline = 100u32;
4294        let mut iterations_without_checkpoint = 0u32;
4295        let mut detected_unaware = false;
4296
4297        cx.cancel_with_reason(CancelReason::UserInterrupt);
4298
4299        for _i in 0..200u32 {
4300            iterations_without_checkpoint += 1;
4301            if iterations_without_checkpoint >= deadline {
4302                detected_unaware = true;
4303                break;
4304            }
4305            // Bug: no cx.checkpoint() call in the loop body.
4306        }
4307
4308        assert!(
4309            detected_unaware,
4310            "bead_id={BEAD_ID} cancel_unaware_loop_detected"
4311        );
4312
4313        // Contrast: a compliant loop would checkpoint and exit.
4314        let cx2 = Cx::<FullCaps>::new();
4315        cx2.transition_to_running();
4316        cx2.cancel_with_reason(CancelReason::UserInterrupt);
4317        let mut compliant_iters = 0u32;
4318        for _ in 0..200u32 {
4319            if cx2.checkpoint().is_err() {
4320                break;
4321            }
4322            compliant_iters += 1;
4323        }
4324        assert_eq!(
4325            compliant_iters, 0,
4326            "bead_id={BEAD_ID} compliant_loop_exits_immediately"
4327        );
4328    }
4329
4330    #[test]
4331    fn test_write_coordinator_commit_section() {
4332        // Test 12: Simulate WriteCoordinator — cancel mid-publish,
4333        // proof+marker completes atomically via commit section.
4334        let cx = Cx::<FullCaps>::new();
4335        cx.transition_to_running();
4336
4337        let proof_published = Arc::new(AtomicBool::new(false));
4338        let marker_published = Arc::new(AtomicBool::new(false));
4339        let reservation_released = Arc::new(AtomicBool::new(false));
4340
4341        let proof = Arc::clone(&proof_published);
4342        let marker = Arc::clone(&marker_published);
4343        let release = Arc::clone(&reservation_released);
4344
4345        cx.commit_section(
4346            10,
4347            |ctx| {
4348                // Step 1: FCW validation passed, commit_seq allocated.
4349                assert!(ctx.tick());
4350
4351                // Cancel arrives mid-publish.
4352                cx.cancel_with_reason(CancelReason::RegionClose);
4353
4354                // Step 2: Publish proof (must complete).
4355                assert!(ctx.tick());
4356                proof.store(true, Ordering::Release);
4357                // Checkpoint inside commit section succeeds (masked).
4358                assert!(cx.checkpoint().is_ok());
4359
4360                // Step 3: Publish marker (must complete).
4361                assert!(ctx.tick());
4362                marker.store(true, Ordering::Release);
4363            },
4364            move || {
4365                // Finalizer: release reservation.
4366                release.store(true, Ordering::Release);
4367            },
4368        );
4369
4370        assert!(
4371            proof_published.load(Ordering::Acquire),
4372            "bead_id={BEAD_ID} proof_published"
4373        );
4374        assert!(
4375            marker_published.load(Ordering::Acquire),
4376            "bead_id={BEAD_ID} marker_published"
4377        );
4378        assert!(
4379            reservation_released.load(Ordering::Acquire),
4380            "bead_id={BEAD_ID} reservation_released"
4381        );
4382
4383        // After commit section, cancellation is visible.
4384        assert!(cx.checkpoint().is_err());
4385    }
4386
4387    // ===================================================================
4388    // Tracing ID propagation tests (bd-2g5.6)
4389    // ===================================================================
4390
4391    #[test]
4392    fn test_trace_ids_default_to_zero() {
4393        let cx = Cx::<FullCaps>::new();
4394        assert_eq!(cx.trace_id(), 0);
4395        assert_eq!(cx.decision_id(), 0);
4396        assert_eq!(cx.policy_id(), 0);
4397    }
4398
4399    #[test]
4400    fn test_with_trace_context_sets_all_ids() {
4401        let cx = Cx::<FullCaps>::new().with_trace_context(42, 99, 7);
4402        assert_eq!(cx.trace_id(), 42);
4403        assert_eq!(cx.decision_id(), 99);
4404        assert_eq!(cx.policy_id(), 7);
4405    }
4406
4407    #[test]
4408    fn test_with_decision_id_preserves_other_ids() {
4409        let cx = Cx::<FullCaps>::new()
4410            .with_trace_context(10, 20, 30)
4411            .with_decision_id(55);
4412        assert_eq!(cx.trace_id(), 10);
4413        assert_eq!(cx.decision_id(), 55);
4414        assert_eq!(cx.policy_id(), 30);
4415    }
4416
4417    #[test]
4418    fn test_with_policy_id_preserves_other_ids() {
4419        let cx = Cx::<FullCaps>::new()
4420            .with_trace_context(100, 200, 300)
4421            .with_policy_id(88);
4422        assert_eq!(cx.trace_id(), 100);
4423        assert_eq!(cx.decision_id(), 200);
4424        assert_eq!(cx.policy_id(), 88);
4425    }
4426
4427    #[test]
4428    #[allow(clippy::redundant_clone)]
4429    fn test_clone_propagates_trace_ids() {
4430        let cx = Cx::<FullCaps>::new().with_trace_context(1, 2, 3);
4431        let cloned = cx.clone();
4432        assert_eq!(cloned.trace_id(), 1);
4433        assert_eq!(cloned.decision_id(), 2);
4434        assert_eq!(cloned.policy_id(), 3);
4435    }
4436
4437    #[test]
4438    fn test_restrict_propagates_trace_ids() {
4439        let cx = Cx::<FullCaps>::new();
4440        let compute = cx.restrict::<ComputeCaps>();
4441        assert_eq!(compute.trace_id(), 0);
4442        assert_eq!(compute.decision_id(), 0);
4443        assert_eq!(compute.policy_id(), 0);
4444    }
4445
4446    #[test]
4447    fn test_scope_with_budget_propagates_trace_ids() {
4448        let cx = Cx::<FullCaps>::new().with_trace_context(5, 6, 7);
4449        let scoped = cx.scope_with_budget(Budget::MINIMAL);
4450        assert_eq!(scoped.trace_id(), 5);
4451        assert_eq!(scoped.decision_id(), 6);
4452        assert_eq!(scoped.policy_id(), 7);
4453        // Budget should be tightened.
4454        assert_eq!(scoped.budget().poll_quota, Budget::MINIMAL.poll_quota);
4455    }
4456
4457    #[test]
4458    fn test_cleanup_scope_propagates_trace_ids() {
4459        let cx = Cx::<FullCaps>::new().with_trace_context(11, 22, 33);
4460        let cleanup = cx.cleanup_scope();
4461        assert_eq!(cleanup.trace_id(), 11);
4462        assert_eq!(cleanup.decision_id(), 22);
4463        assert_eq!(cleanup.policy_id(), 33);
4464    }
4465
4466    #[test]
4467    fn test_create_child_propagates_trace_ids() {
4468        let parent = Cx::<FullCaps>::new().with_trace_context(50, 60, 70);
4469        let child = parent.create_child();
4470        assert_eq!(child.trace_id(), 50);
4471        assert_eq!(child.decision_id(), 60);
4472        assert_eq!(child.policy_id(), 70);
4473        // Child should have independent cancellation.
4474        parent.cancel();
4475        assert!(parent.is_cancel_requested());
4476        assert!(child.is_cancel_requested()); // Propagated.
4477    }
4478
4479    #[test]
4480    fn test_trace_ids_independent_across_children() {
4481        let parent = Cx::<FullCaps>::new().with_trace_context(1, 2, 3);
4482        let child1 = parent.create_child().with_decision_id(100);
4483        let child2 = parent.create_child().with_decision_id(200);
4484        // Children share trace_id but have different decision_ids.
4485        assert_eq!(child1.trace_id(), 1);
4486        assert_eq!(child2.trace_id(), 1);
4487        assert_eq!(child1.decision_id(), 100);
4488        assert_eq!(child2.decision_id(), 200);
4489        // Parent's decision_id unchanged.
4490        assert_eq!(parent.decision_id(), 2);
4491    }
4492
4493    #[test]
4494    fn test_with_budget_starts_at_zero_trace_ids() {
4495        let cx = Cx::<FullCaps>::with_budget(Budget::MINIMAL);
4496        assert_eq!(cx.trace_id(), 0);
4497        assert_eq!(cx.decision_id(), 0);
4498        assert_eq!(cx.policy_id(), 0);
4499    }
4500    // === bd-bjm5d: blocking_io_inline_safe marker ===
4501
4502    #[test]
4503    fn blocking_io_inline_safe_defaults_false() {
4504        let cx = Cx::new();
4505        assert!(!cx.blocking_io_inline_safe());
4506    }
4507
4508    #[test]
4509    fn blocking_io_inline_safe_shared_through_clone() {
4510        let cx = Cx::new();
4511        let clone = cx.clone();
4512        cx.mark_blocking_io_inline_safe();
4513        assert!(clone.blocking_io_inline_safe());
4514    }
4515
4516    #[test]
4517    fn blocking_io_inline_safe_inherited_by_create_child() {
4518        let cx = Cx::new();
4519        cx.mark_blocking_io_inline_safe();
4520        let child = cx.create_child();
4521        assert!(child.blocking_io_inline_safe());
4522    }
4523
4524    #[test]
4525    fn blocking_io_inline_safe_not_invented_by_child_of_unset_parent() {
4526        let cx = Cx::new();
4527        let child = cx.create_child();
4528        assert!(!child.blocking_io_inline_safe());
4529        // Marking the child later must not leak back to the parent: the
4530        // child has its own CxInner.
4531        child.mark_blocking_io_inline_safe();
4532        assert!(!cx.blocking_io_inline_safe());
4533    }
4534}