Skip to main content

asupersync/cx/
cap.rs

1//! Typed capability sets for `Cx`.
2//!
3//! The capability set is represented at the type level so that operations
4//! requiring certain effects (spawn/time/random/io/remote) can be gated
5//! at compile time.
6//!
7//! # Capability Rows
8//!
9//! A capability row is a fixed-width vector of booleans — one per effect:
10//! `[SPAWN, TIME, RANDOM, IO, REMOTE]`. The [`CapSet`] struct encodes this
11//! row as const generics, making it a zero-sized type with no runtime cost.
12//!
13//! The subset relation ([`SubsetOf`]) is the pointwise ≤ ordering on rows.
14//! Narrowing (dropping capabilities) always succeeds; widening (gaining
15//! capabilities) is a compile-time error.
16//!
17//! # Forging Prevention
18//!
19//! Capability marker traits are sealed to prevent external crates from
20//! implementing them for arbitrary types. This ensures only the runtime's
21//! `CapSet` types can grant access to gated APIs.
22//!
23//! # Narrowing is Monotone
24//!
25//! If `A: SubsetOf<B>`, then every method available on `Cx<A>` is a
26//! subset of those available on `Cx<B>`. Narrowing cannot introduce new
27//! effects because each `Has*` marker is gated on a single boolean
28//! position, and the `SubsetOf` impl requires each bit in the sub to
29//! be ≤ the corresponding bit in the super.
30//!
31//! # Trusted Roots
32//!
33//! - The runtime constructs full contexts internally (e.g., via `RuntimeState`).
34//! - Test-only constructors (e.g., `Cx::for_testing*`) are permitted for harnesses.
35//!
36//! # Compile-time rejection of widening
37//!
38//! ```compile_fail
39//! use asupersync::cx::cap::{CapSet, SubsetOf};
40//!
41//! // WebCaps (no spawn) cannot widen to GrpcCaps (has spawn):
42//! fn widen<Sub: SubsetOf<Super>, Super>() {}
43//! type WebCaps = CapSet<false, true, false, true, false>;
44//! type GrpcCaps = CapSet<true, true, false, true, false>;
45//! widen::<GrpcCaps, WebCaps>(); // ERROR: GrpcCaps is NOT a subset of WebCaps
46//! ```
47//!
48//! ```compile_fail
49//! use asupersync::cx::cap::{CapSet, None, SubsetOf};
50//!
51//! // Cannot widen from None to any capability:
52//! fn widen<Sub: SubsetOf<Super>, Super>() {}
53//! type SpawnOnly = CapSet<true, false, false, false, false>;
54//! widen::<SpawnOnly, None>(); // ERROR: SpawnOnly is NOT a subset of None
55//! ```
56
57mod sealed {
58    pub trait Sealed {}
59
60    /// Type-level capability bit for subset reasoning.
61    ///
62    /// Kept inside `sealed` so external crates cannot construct or
63    /// implement traits on these types, preserving anti-forgery.
64    pub struct Bit<const V: bool>;
65
66    /// Ordering on capability bits: `false ≤ false`, `false ≤ true`, `true ≤ true`.
67    ///
68    /// The missing impl `(Bit<true>, Bit<false>)` encodes that widening
69    /// (gaining a capability you don't have) is statically rejected.
70    pub trait Le {}
71    impl Le for (Bit<false>, Bit<false>) {}
72    impl Le for (Bit<false>, Bit<true>) {}
73    impl Le for (Bit<true>, Bit<true>) {}
74}
75
76/// Type-level capability set.
77///
78/// Each boolean controls whether the capability is present:
79/// - `SPAWN`: spawn tasks/regions
80/// - `TIME`: timers, timeouts
81/// - `RANDOM`: entropy and random values
82/// - `IO`: async I/O capability
83/// - `REMOTE`: remote task spawning
84#[derive(Debug, Clone, Copy, Default)]
85pub struct CapSet<
86    const SPAWN: bool,
87    const TIME: bool,
88    const RANDOM: bool,
89    const IO: bool,
90    const REMOTE: bool,
91>;
92
93impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool>
94    sealed::Sealed for CapSet<SPAWN, TIME, RANDOM, IO, REMOTE>
95{
96}
97
98/// Full capability set (default).
99pub type All = CapSet<true, true, true, true, true>;
100
101/// No capabilities.
102pub type None = CapSet<false, false, false, false, false>;
103
104/// Marker: spawn capability.
105///
106/// ```compile_fail
107/// use asupersync::cx::HasSpawn;
108///
109/// struct FakeCaps;
110/// impl HasSpawn for FakeCaps {}
111/// ```
112pub trait HasSpawn: sealed::Sealed {}
113impl<const TIME: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool> HasSpawn
114    for CapSet<true, TIME, RANDOM, IO, REMOTE>
115{
116}
117
118/// Marker: time capability.
119pub trait HasTime: sealed::Sealed {}
120impl<const SPAWN: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool> HasTime
121    for CapSet<SPAWN, true, RANDOM, IO, REMOTE>
122{
123}
124
125/// Marker: random/entropy capability.
126pub trait HasRandom: sealed::Sealed {}
127impl<const SPAWN: bool, const TIME: bool, const IO: bool, const REMOTE: bool> HasRandom
128    for CapSet<SPAWN, TIME, true, IO, REMOTE>
129{
130}
131
132/// Marker: I/O capability.
133pub trait HasIo: sealed::Sealed {}
134impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const REMOTE: bool> HasIo
135    for CapSet<SPAWN, TIME, RANDOM, true, REMOTE>
136{
137}
138
139/// Marker: remote capability.
140pub trait HasRemote: sealed::Sealed {}
141impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const IO: bool> HasRemote
142    for CapSet<SPAWN, TIME, RANDOM, IO, true>
143{
144}
145
146/// Marker: subset relation between capability sets.
147///
148/// `Sub: SubsetOf<Super>` holds when every capability enabled in `Sub` is
149/// also enabled in `Super`. This is the pointwise ≤ ordering on boolean
150/// capability rows and guarantees that narrowing is **monotone**: you can
151/// only drop capabilities, never gain them.
152///
153/// # Monotonicity argument
154///
155/// Because `sealed::Le` is only implemented for `(false,false)`,
156/// `(false,true)`, and `(true,true)` — but *not* `(true,false)` — the
157/// compiler rejects any attempt to widen a capability set. Combined with
158/// the `Sealed` supertrait, external crates cannot forge `SubsetOf`
159/// implementations.
160///
161/// # Properties
162///
163/// - **Reflexive**: `CapSet<S,T,R,I,Re>: SubsetOf<CapSet<S,T,R,I,Re>>`
164/// - **Transitive**: if `A: SubsetOf<B>` and `B: SubsetOf<C>`, then
165///   `A: SubsetOf<C>` (follows from bit-level ≤ transitivity)
166/// - **Antisymmetric**: `A: SubsetOf<B>` and `B: SubsetOf<A>` implies A = B
167///
168/// ```compile_fail
169/// use asupersync::cx::SubsetOf;
170///
171/// struct FakeCaps;
172/// impl SubsetOf<FakeCaps> for FakeCaps {}
173/// ```
174pub trait SubsetOf<Super>: sealed::Sealed {}
175
176// General pointwise subset: Sub ⊆ Super iff each capability bit in Sub ≤ Super.
177impl<
178    const S1: bool,
179    const T1: bool,
180    const R1: bool,
181    const I1: bool,
182    const RE1: bool,
183    const S2: bool,
184    const T2: bool,
185    const R2: bool,
186    const I2: bool,
187    const RE2: bool,
188> SubsetOf<CapSet<S2, T2, R2, I2, RE2>> for CapSet<S1, T1, R1, I1, RE1>
189where
190    (sealed::Bit<S1>, sealed::Bit<S2>): sealed::Le,
191    (sealed::Bit<T1>, sealed::Bit<T2>): sealed::Le,
192    (sealed::Bit<R1>, sealed::Bit<R2>): sealed::Le,
193    (sealed::Bit<I1>, sealed::Bit<I2>): sealed::Le,
194    (sealed::Bit<RE1>, sealed::Bit<RE2>): sealed::Le,
195{
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    // Helper: assert a type satisfies SubsetOf at compile time.
203    fn assert_subset<Sub: SubsetOf<Super>, Super>() {}
204
205    // Helper: assert a marker trait at compile time.
206    fn assert_has_spawn<C: HasSpawn>() {}
207    fn assert_has_time<C: HasTime>() {}
208    fn assert_has_random<C: HasRandom>() {}
209    fn assert_has_io<C: HasIo>() {}
210    fn assert_has_remote<C: HasRemote>() {}
211
212    // --- Reflexivity ---
213
214    #[test]
215    fn subset_reflexive_all() {
216        assert_subset::<All, All>();
217    }
218
219    #[test]
220    fn subset_reflexive_none() {
221        assert_subset::<None, None>();
222    }
223
224    #[test]
225    fn subset_reflexive_mixed() {
226        // CapSet<true, false, true, false, true> ⊆ itself
227        assert_subset::<
228            CapSet<true, false, true, false, true>,
229            CapSet<true, false, true, false, true>,
230        >();
231    }
232
233    // --- Bottom and top ---
234
235    #[test]
236    fn none_subset_of_all() {
237        assert_subset::<None, All>();
238    }
239
240    #[test]
241    fn none_subset_of_any() {
242        assert_subset::<None, CapSet<false, true, false, false, true>>();
243        assert_subset::<None, CapSet<true, false, false, false, false>>();
244    }
245
246    #[test]
247    fn any_subset_of_all() {
248        assert_subset::<CapSet<true, false, true, false, true>, All>();
249        assert_subset::<CapSet<false, false, false, true, false>, All>();
250    }
251
252    // --- Intermediate narrowing (framework wrapper types) ---
253
254    #[test]
255    fn background_subset_of_grpc() {
256        // Background = <true, true, false, false, false>
257        // Grpc       = <true, true, false, true, false>
258        // Background ⊆ Grpc (Background drops IO)
259        type BackgroundCaps = CapSet<true, true, false, false, false>;
260        type GrpcCaps = CapSet<true, true, false, true, false>;
261        assert_subset::<BackgroundCaps, GrpcCaps>();
262    }
263
264    #[test]
265    fn web_subset_of_all() {
266        // Web = <false, true, false, true, false>
267        type WebCaps = CapSet<false, true, false, true, false>;
268        assert_subset::<WebCaps, All>();
269    }
270
271    #[test]
272    fn pure_subset_of_web() {
273        // Pure = None = <false, false, false, false, false>
274        // Web  = <false, true, false, true, false>
275        type WebCaps = CapSet<false, true, false, true, false>;
276        assert_subset::<None, WebCaps>();
277    }
278
279    #[test]
280    fn single_cap_subset_of_multi() {
281        // <false, true, false, false, false> ⊆ <true, true, false, true, false>
282        assert_subset::<
283            CapSet<false, true, false, false, false>,
284            CapSet<true, true, false, true, false>,
285        >();
286    }
287
288    // --- Transitivity (demonstrated, not mechanized) ---
289
290    #[test]
291    fn transitive_none_background_grpc() {
292        type BackgroundCaps = CapSet<true, true, false, false, false>;
293        type GrpcCaps = CapSet<true, true, false, true, false>;
294        // None ⊆ Background ⊆ Grpc, therefore None ⊆ Grpc
295        assert_subset::<None, BackgroundCaps>();
296        assert_subset::<BackgroundCaps, GrpcCaps>();
297        assert_subset::<None, GrpcCaps>();
298    }
299
300    // --- Marker traits ---
301
302    #[test]
303    fn all_has_every_capability() {
304        assert_has_spawn::<All>();
305        assert_has_time::<All>();
306        assert_has_random::<All>();
307        assert_has_io::<All>();
308        assert_has_remote::<All>();
309    }
310
311    #[test]
312    fn partial_caps_have_correct_markers() {
313        // <true, true, false, true, false> has Spawn+Time+Io but not Random/Remote
314        assert_has_spawn::<CapSet<true, true, false, true, false>>();
315        assert_has_time::<CapSet<true, true, false, true, false>>();
316        assert_has_io::<CapSet<true, true, false, true, false>>();
317    }
318
319    // --- ZST property ---
320
321    #[test]
322    fn capset_is_zero_sized() {
323        assert_eq!(std::mem::size_of::<All>(), 0);
324        assert_eq!(std::mem::size_of::<None>(), 0);
325        assert_eq!(
326            std::mem::size_of::<CapSet<true, false, true, false, true>>(),
327            0
328        );
329    }
330
331    // --- Compile-fail doctests for anti-forgery are on HasSpawn and SubsetOf above ---
332
333    // =========================================================================
334    // Wave 54 – pure data-type trait coverage
335    // =========================================================================
336
337    #[test]
338    fn capset_debug_clone_copy_default() {
339        let all = All::default();
340        let dbg = format!("{all:?}");
341        assert!(dbg.contains("CapSet"), "{dbg}");
342        let copied = all;
343        let cloned = all;
344        // ZST so all instances are identical
345        let _ = (copied, cloned);
346
347        let none = None::default();
348        let dbg_none = format!("{none:?}");
349        assert!(dbg_none.contains("CapSet"), "{dbg_none}");
350    }
351}