kernel/delegation.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! Delegation: two delegate kinds, one level deep.
7//! The guard is two-layered on purpose: statically, a `Delegate` value
8//! has no delegate method — minting a grandchild cannot be spelled;
9//! dynamically, `admit` re-checks depth at every spawn so a hand-rolled
10//! path cannot sneak past the type. One level is an invariant, not a
11//! constant — a constant would imply it is tunable.
12//! The part `crates/kernel/spec/Delegation.lean` specifies this module.
13
14use serde::{Deserialize, Serialize};
15
16/// Resident delegates join society and answer for output; Ephemerals are
17/// tools — one clarification channel up, nothing else.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum DelegateKind {
21 Resident,
22 Ephemeral,
23}
24
25impl DelegateKind {
26 /// The one spelling. Both the tool that parses these words and the
27 /// status line that prints them read it here, so a rename cannot
28 /// half-happen.
29 #[must_use]
30 pub fn as_str(self) -> &'static str {
31 match self {
32 DelegateKind::Resident => "resident",
33 DelegateKind::Ephemeral => "ephemeral",
34 }
35 }
36}
37
38/// The depth-zero position: the only type with a `delegate` method
39/// (15.3-10). Who holds a `Delegator` is assembly's discipline; what a
40/// `Delegate` value can mint is this module's — nothing.
41#[derive(Debug)]
42pub struct Delegator(());
43
44impl Delegator {
45 /// Minting point: assembly and citysim only.
46 pub fn root() -> Delegator {
47 Delegator(())
48 }
49
50 pub fn delegate(&self, kind: DelegateKind) -> Delegate {
51 Delegate { kind }
52 }
53}
54
55/// A delegated position. No delegate method — the compile error is the
56/// design (trybuild counterexample).
57#[derive(Debug)]
58pub struct Delegate {
59 kind: DelegateKind,
60}
61
62impl Delegate {
63 pub fn kind(&self) -> &DelegateKind {
64 &self.kind
65 }
66}
67
68/// Where a spawner stands.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum Depth {
71 Root,
72 Delegated,
73}
74
75/// Deliberately exhaustive verdict; the refusal (E_DELEGATION_DEPTH,
76/// three parts) is shaped by gate::spawn — the sole gate-code producer.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum DelegationVerdict {
79 Allow,
80 Deny,
81}
82
83/// The dynamic half of the two-layer guard: a delegated position spawns
84/// nothing, whatever kind it asks for.
85pub fn admit(parent: Depth, kind: &DelegateKind) -> DelegationVerdict {
86 match parent {
87 Depth::Root => {
88 // Exhaustive on purpose: a new kind must decide its depth rule.
89 match kind {
90 DelegateKind::Resident | DelegateKind::Ephemeral => DelegationVerdict::Allow,
91 }
92 }
93 Depth::Delegated => DelegationVerdict::Deny,
94 }
95}
96
97#[cfg(test)]
98#[allow(
99 clippy::unwrap_used,
100 clippy::expect_used,
101 clippy::panic,
102 clippy::indexing_slicing,
103 reason = "test code"
104)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn root_spawns_both_kinds_delegated_spawns_none() {
110 assert_eq!(
111 admit(Depth::Root, &DelegateKind::Resident),
112 DelegationVerdict::Allow
113 );
114 assert_eq!(
115 admit(Depth::Root, &DelegateKind::Ephemeral),
116 DelegationVerdict::Allow
117 );
118 assert_eq!(
119 admit(Depth::Delegated, &DelegateKind::Resident),
120 DelegationVerdict::Deny
121 );
122 assert_eq!(
123 admit(Depth::Delegated, &DelegateKind::Ephemeral),
124 DelegationVerdict::Deny
125 );
126 }
127
128 #[test]
129 fn the_static_path_mints_one_level_only() {
130 let root = Delegator::root();
131 let child = root.delegate(DelegateKind::Ephemeral);
132 assert_eq!(child.kind(), &DelegateKind::Ephemeral);
133 // `child.delegate(...)` does not compile — pinned by the
134 // trybuild case; this test documents the runtime-visible half.
135 }
136}