Skip to main content

qualia_core_db/modalities/
delegation.rs

1//! Delegation & credential-chain logic (§21, legal_logic.md) — the trust fabric.
2//!
3//! Legal power and identity flow through chains of authorization. This module governs how
4//! authority propagates along a delegation DAG and how **revocation of an upstream node
5//! cascades** to defeat every downstream dependent (ZCAP-LD / capability chains; Open Badges
6//! `EndorsementCredential`). Authority delegation edges are `(delegator, q42:delegatesTo,
7//! delegatee)`. Bounded BFS, zero-heap.
8
9use crate::{q_hash, NQuin};
10
11/// Bound on distinct nodes in one delegation query.
12pub const MAX_DELEGATION_NODES: usize = 256;
13
14const NO_REVOCATION: u64 = u64::MAX;
15
16/// The delegation-edge predicate `(delegator, q42:delegatesTo, delegatee)`.
17#[inline]
18pub fn delegates_predicate() -> u64 {
19    q_hash("q42:delegatesTo")
20}
21
22/// Internal: is `agent` reachable from `root_authority` along delegation edges, with `revoked`
23/// excised (revocation cuts the chain there and below)? Bounded, zero-heap.
24fn reaches(edges: &[NQuin], root_authority: u64, agent: u64, revoked: u64) -> bool {
25    if agent == revoked || root_authority == revoked {
26        return false;
27    }
28    if agent == root_authority {
29        return true;
30    }
31    let p = delegates_predicate();
32    let mut frontier = [0u64; MAX_DELEGATION_NODES];
33    let mut visited = [0u64; MAX_DELEGATION_NODES];
34    let mut fl = 1usize;
35    let mut vl = 0usize;
36    frontier[0] = root_authority;
37    while fl > 0 {
38        fl -= 1;
39        let cur = frontier[fl];
40        if visited[..vl].contains(&cur) {
41            continue;
42        }
43        if vl < MAX_DELEGATION_NODES {
44            visited[vl] = cur;
45            vl += 1;
46        } else {
47            break;
48        }
49        for e in edges {
50            if e.predicate == p && e.subject == cur && e.subject != revoked && e.object != revoked {
51                let nxt = e.object;
52                if nxt == agent {
53                    return true;
54                }
55                if fl < MAX_DELEGATION_NODES && !visited[..vl].contains(&nxt) {
56                    frontier[fl] = nxt;
57                    fl += 1;
58                }
59            }
60        }
61    }
62    false
63}
64
65/// Does `agent` hold authority delegated (transitively) from `root_authority`?
66/// `Auth(α,p) ∧ Deleg*(α,…,β) → Auth(β,p)`.
67#[inline]
68pub fn has_delegated_authority(edges: &[NQuin], root_authority: u64, agent: u64) -> bool {
69    reaches(edges, root_authority, agent, NO_REVOCATION)
70}
71
72/// **Revocation cascade**: after `revoked` is revoked, does `agent` still hold authority from
73/// `root_authority`? An agent whose only chain ran through `revoked` is now **defeated**.
74#[inline]
75pub fn authority_after_revocation(
76    edges: &[NQuin],
77    root_authority: u64,
78    revoked: u64,
79    agent: u64,
80) -> bool {
81    reaches(edges, root_authority, agent, revoked)
82}
83
84/// Collect, into `out`, the `candidates` **defeated** by revoking `revoked` — held authority
85/// before, lost it after. Returns the count. Zero-heap.
86pub fn revoked_descendants(
87    edges: &[NQuin],
88    root_authority: u64,
89    revoked: u64,
90    candidates: &[u64],
91    out: &mut [u64],
92) -> usize {
93    let mut n = 0usize;
94    for &c in candidates {
95        if has_delegated_authority(edges, root_authority, c)
96            && !authority_after_revocation(edges, root_authority, revoked, c)
97        {
98            if n >= out.len() {
99                break;
100            }
101            out[n] = c;
102            n += 1;
103        }
104    }
105    n
106}
107
108// ─── Attenuation: a delegatee receives ≤ the delegator's authority ────────────────
109
110/// **Attenuation** (ZCAP-LD / Macaroons): a sub-delegation's capability set `child` is valid only
111/// if a SUBSET of the delegator's `parent` set — a delegatee never gains MORE authority than the
112/// delegator holds. (Empty child trivially attenuates.)
113pub fn attenuates(parent: &[u64], child: &[u64]) -> bool {
114    child.iter().all(|c| parent.contains(c))
115}
116
117// ─── CRL: cascading revocation against a cryptographic revocation list ────────────
118
119/// Is `node` on the cryptographic revocation list `crl`?
120#[inline]
121pub fn is_revoked(crl: &[u64], node: u64) -> bool {
122    crl.contains(&node)
123}
124
125/// Does `agent` still hold authority from `root_authority` after excising EVERY node on the
126/// revocation list `crl` (a real-time CRL check across the whole chain)? Zero-heap (bounded BFS).
127pub fn authority_after_crl(edges: &[NQuin], root_authority: u64, crl: &[u64], agent: u64) -> bool {
128    if is_revoked(crl, agent) || is_revoked(crl, root_authority) {
129        return false;
130    }
131    if agent == root_authority {
132        return true;
133    }
134    let p = delegates_predicate();
135    let mut frontier = [0u64; MAX_DELEGATION_NODES];
136    let mut visited = [0u64; MAX_DELEGATION_NODES];
137    let mut fl = 1usize;
138    let mut vl = 0usize;
139    frontier[0] = root_authority;
140    while fl > 0 {
141        fl -= 1;
142        let cur = frontier[fl];
143        if visited[..vl].contains(&cur) {
144            continue;
145        }
146        if vl < MAX_DELEGATION_NODES {
147            visited[vl] = cur;
148            vl += 1;
149        } else {
150            break;
151        }
152        for e in edges {
153            if e.predicate == p
154                && e.subject == cur
155                && !is_revoked(crl, e.subject)
156                && !is_revoked(crl, e.object)
157            {
158                let nxt = e.object;
159                if nxt == agent {
160                    return true;
161                }
162                if fl < MAX_DELEGATION_NODES && !visited[..vl].contains(&nxt) {
163                    frontier[fl] = nxt;
164                    fl += 1;
165                }
166            }
167        }
168    }
169    false
170}
171
172// ─── Spatial & temporal bounds on delegated authority ─────────────────────────────
173
174/// Is a delegation temporally in-force at `now`? `[from, until]` Unix-epoch bounds, where
175/// `from == 0` means "no start bound" and `until == 0` means "open-ended".
176pub fn delegation_in_force(from: u32, until: u32, now: u32) -> bool {
177    (from == 0 || now >= from) && (until == 0 || now <= until)
178}
179
180/// Is a delegation valid in `location`? Its `scope_region` (`0` = unbounded / global) must equal
181/// `location`. Spatial bounding of delegated authority.
182pub fn delegation_in_region(scope_region: u64, location: u64) -> bool {
183    scope_region == 0 || scope_region == location
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    fn edge(delegator: u64, delegatee: u64) -> NQuin {
191        let mut q = NQuin {
192            subject: delegator,
193            predicate: delegates_predicate(),
194            object: delegatee,
195            context: 0,
196            metadata: 0,
197            parity: 0,
198        };
199        q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
200        q
201    }
202
203    #[test]
204    fn authority_flows_along_the_chain() {
205        // root → agency → officer ; root → ngo
206        let root = q_hash("did:state");
207        let agency = q_hash("did:agency");
208        let officer = q_hash("did:officer");
209        let ngo = q_hash("did:ngo");
210        let edges = [edge(root, agency), edge(agency, officer), edge(root, ngo)];
211        assert!(
212            has_delegated_authority(&edges, root, officer),
213            "transitive delegation"
214        );
215        assert!(has_delegated_authority(&edges, root, ngo));
216        // An agent with no chain from root holds nothing.
217        assert!(!has_delegated_authority(
218            &edges,
219            root,
220            q_hash("did:stranger")
221        ));
222    }
223
224    #[test]
225    fn revocation_cascades_to_descendants() {
226        let root = q_hash("did:state");
227        let agency = q_hash("did:agency");
228        let officer = q_hash("did:officer");
229        let ngo = q_hash("did:ngo");
230        let edges = [edge(root, agency), edge(agency, officer), edge(root, ngo)];
231        // Revoke the agency: the officer (downstream) is defeated; the NGO (independent) is not.
232        assert!(!authority_after_revocation(&edges, root, agency, officer));
233        assert!(authority_after_revocation(&edges, root, agency, ngo));
234        let mut out = [0u64; 8];
235        let n = revoked_descendants(&edges, root, agency, &[agency, officer, ngo], &mut out);
236        // Both the agency itself and the officer lose authority; the NGO keeps it.
237        assert!(out[..n].contains(&officer));
238        assert!(out[..n].contains(&agency));
239        assert!(!out[..n].contains(&ngo));
240    }
241
242    #[test]
243    fn attenuation_never_broadens_authority() {
244        let (read, write, admin) = (q_hash("cap:read"), q_hash("cap:write"), q_hash("cap:admin"));
245        assert!(attenuates(&[read, write, admin], &[read, write]));
246        assert!(
247            !attenuates(&[read], &[read, admin]),
248            "cannot grant a capability the delegator lacks"
249        );
250        assert!(attenuates(&[read], &[]));
251    }
252
253    #[test]
254    fn crl_excises_every_revoked_node() {
255        let root = q_hash("did:state");
256        let agency = q_hash("did:agency");
257        let officer = q_hash("did:officer");
258        let ngo = q_hash("did:ngo");
259        let edges = [edge(root, agency), edge(agency, officer), edge(root, ngo)];
260        // Empty CRL → behaves like full authority.
261        assert!(authority_after_crl(&edges, root, &[], officer));
262        // Revoke the agency via the CRL: officer defeated, ngo (independent) survives.
263        assert!(!authority_after_crl(&edges, root, &[agency], officer));
264        assert!(authority_after_crl(&edges, root, &[agency], ngo));
265        // A multi-entry CRL revoking both branches.
266        assert!(!authority_after_crl(&edges, root, &[agency, ngo], ngo));
267    }
268
269    #[test]
270    fn spatial_and_temporal_bounds() {
271        // Temporal: in force only within [100, 200].
272        assert!(delegation_in_force(100, 200, 150));
273        assert!(!delegation_in_force(100, 200, 50), "before start");
274        assert!(!delegation_in_force(100, 200, 250), "after end");
275        assert!(delegation_in_force(0, 0, 999), "open-ended");
276        // Spatial: scoped to a region (0 = global).
277        let region = q_hash("region:au");
278        assert!(delegation_in_region(region, region));
279        assert!(!delegation_in_region(region, q_hash("region:us")));
280        assert!(
281            delegation_in_region(0, q_hash("region:anywhere")),
282            "global scope"
283        );
284    }
285}