aether_ast/
lib.rs

1use serde::{de::Deserializer, Deserialize, Serialize};
2use std::fmt;
3
4macro_rules! id_type {
5    ($name:ident) => {
6        #[derive(
7            Clone,
8            Copy,
9            Debug,
10            Default,
11            Eq,
12            Hash,
13            Ord,
14            PartialEq,
15            PartialOrd,
16            Serialize,
17            Deserialize,
18        )]
19        pub struct $name(pub u64);
20
21        impl $name {
22            pub const fn new(value: u64) -> Self {
23                Self(value)
24            }
25        }
26
27        impl fmt::Display for $name {
28            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29                write!(f, "{}", self.0)
30            }
31        }
32    };
33}
34
35id_type!(EntityId);
36id_type!(AttributeId);
37id_type!(ElementId);
38id_type!(PredicateId);
39id_type!(RuleId);
40id_type!(TupleId);
41id_type!(ReplicaId);
42
43#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
44pub struct PartitionId(pub String);
45
46impl PartitionId {
47    pub fn new(value: impl Into<String>) -> Self {
48        Self(value.into())
49    }
50
51    pub fn as_str(&self) -> &str {
52        &self.0
53    }
54}
55
56impl fmt::Display for PartitionId {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
63pub struct PartitionCut {
64    pub partition: PartitionId,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub as_of: Option<ElementId>,
67}
68
69impl PartitionCut {
70    pub fn current(partition: impl Into<PartitionId>) -> Self {
71        Self {
72            partition: partition.into(),
73            as_of: None,
74        }
75    }
76
77    pub fn as_of(partition: impl Into<PartitionId>, element: ElementId) -> Self {
78        Self {
79            partition: partition.into(),
80            as_of: Some(element),
81        }
82    }
83
84    pub fn is_current(&self) -> bool {
85        self.as_of.is_none()
86    }
87}
88
89impl fmt::Display for PartitionCut {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self.as_of {
92            Some(element) => write!(f, "{}@e{}", self.partition, element.0),
93            None => write!(f, "{}@current", self.partition),
94        }
95    }
96}
97
98impl From<&str> for PartitionId {
99    fn from(value: &str) -> Self {
100        Self::new(value)
101    }
102}
103
104impl From<String> for PartitionId {
105    fn from(value: String) -> Self {
106        Self::new(value)
107    }
108}
109
110#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
111pub struct FederatedCut {
112    pub cuts: Vec<PartitionCut>,
113}
114
115impl FederatedCut {
116    pub fn normalized(mut self) -> Self {
117        self.cuts
118            .sort_by(|left, right| left.partition.cmp(&right.partition));
119        self
120    }
121}
122
123pub fn merge_partition_cuts<'a, I>(cuts: I) -> Vec<PartitionCut>
124where
125    I: IntoIterator<Item = &'a PartitionCut>,
126{
127    let mut merged = cuts.into_iter().cloned().collect::<Vec<_>>();
128    merged.sort_by(|left, right| {
129        left.partition
130            .cmp(&right.partition)
131            .then_with(|| left.as_of.cmp(&right.as_of))
132    });
133    merged.dedup();
134    merged
135}
136
137#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
138pub enum Value {
139    #[default]
140    Null,
141    Bool(bool),
142    I64(i64),
143    U64(u64),
144    F64(f64),
145    String(String),
146    Bytes(Vec<u8>),
147    Entity(EntityId),
148    List(Vec<Value>),
149}
150
151#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
152pub enum OperationKind {
153    #[default]
154    Assert,
155    Retract,
156    Add,
157    Remove,
158    InsertAfter,
159    LeaseOpen,
160    LeaseRenew,
161    LeaseExpire,
162    Claim,
163    Release,
164    Annotate,
165}
166
167#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
168pub struct CausalContext {
169    pub frontier: Vec<ElementId>,
170}
171
172#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
173pub struct SourceRef {
174    pub uri: String,
175    pub digest: Option<String>,
176}
177
178#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum SidecarKind {
181    #[default]
182    Artifact,
183    Vector,
184}
185
186#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
187pub struct SidecarOrigin {
188    pub kind: SidecarKind,
189    pub sidecar_id: String,
190    pub record_id: String,
191}
192
193#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
194pub struct DatomProvenance {
195    pub author_principal: String,
196    pub agent_id: String,
197    pub tool_id: String,
198    pub session_id: String,
199    pub source_ref: SourceRef,
200    pub parent_datom_ids: Vec<ElementId>,
201    pub confidence: f32,
202    pub trust_domain: String,
203    pub schema_version: String,
204}
205
206impl Default for DatomProvenance {
207    fn default() -> Self {
208        Self {
209            author_principal: String::new(),
210            agent_id: String::new(),
211            tool_id: String::new(),
212            session_id: String::new(),
213            source_ref: SourceRef::default(),
214            parent_datom_ids: Vec::new(),
215            confidence: 1.0,
216            trust_domain: String::new(),
217            schema_version: String::new(),
218        }
219    }
220}
221
222#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
223pub struct PolicyEnvelope {
224    pub capabilities: Vec<String>,
225    pub visibilities: Vec<String>,
226}
227
228#[derive(Clone, Debug, Default, Deserialize)]
229struct RawPolicyEnvelope {
230    #[serde(default)]
231    capabilities: Vec<String>,
232    #[serde(default)]
233    visibilities: Vec<String>,
234    #[serde(default)]
235    capability: Option<String>,
236    #[serde(default)]
237    visibility: Option<String>,
238}
239
240impl<'de> Deserialize<'de> for PolicyEnvelope {
241    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
242    where
243        D: Deserializer<'de>,
244    {
245        let raw = RawPolicyEnvelope::deserialize(deserializer)?;
246        let mut capabilities = raw.capabilities;
247        if let Some(capability) = raw.capability {
248            capabilities.push(capability);
249        }
250        let mut visibilities = raw.visibilities;
251        if let Some(visibility) = raw.visibility {
252            visibilities.push(visibility);
253        }
254        Ok(Self {
255            capabilities: normalize_policy_values(capabilities),
256            visibilities: normalize_policy_values(visibilities),
257        })
258    }
259}
260
261impl PolicyEnvelope {
262    pub fn is_public(&self) -> bool {
263        self.capabilities.is_empty() && self.visibilities.is_empty()
264    }
265
266    pub fn normalized(mut self) -> Self {
267        self.capabilities = normalize_policy_values(self.capabilities);
268        self.visibilities = normalize_policy_values(self.visibilities);
269        self
270    }
271
272    pub fn union_with(&mut self, other: &Self) {
273        self.capabilities.extend(other.capabilities.iter().cloned());
274        self.visibilities.extend(other.visibilities.iter().cloned());
275        self.capabilities = normalize_policy_values(std::mem::take(&mut self.capabilities));
276        self.visibilities = normalize_policy_values(std::mem::take(&mut self.visibilities));
277    }
278}
279
280pub fn merge_policy_envelopes<'a, I>(envelopes: I) -> Option<PolicyEnvelope>
281where
282    I: IntoIterator<Item = Option<&'a PolicyEnvelope>>,
283{
284    let mut merged = PolicyEnvelope::default();
285    for envelope in envelopes.into_iter().flatten() {
286        merged.union_with(envelope);
287    }
288    (!merged.is_public()).then_some(merged)
289}
290
291#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
292pub struct PolicyContext {
293    pub capabilities: Vec<String>,
294    pub visibilities: Vec<String>,
295}
296
297impl PolicyContext {
298    pub fn is_empty(&self) -> bool {
299        self.capabilities.is_empty() && self.visibilities.is_empty()
300    }
301
302    pub fn allows(&self, envelope: &PolicyEnvelope) -> bool {
303        let capability_allowed = envelope
304            .capabilities
305            .iter()
306            .all(|capability| self.capabilities.iter().any(|value| value == capability));
307        let visibility_allowed = envelope
308            .visibilities
309            .iter()
310            .all(|visibility| self.visibilities.iter().any(|value| value == visibility));
311        capability_allowed && visibility_allowed
312    }
313
314    pub fn subset_of(&self, other: &Self) -> bool {
315        self.capabilities
316            .iter()
317            .all(|value| other.capabilities.iter().any(|allowed| allowed == value))
318            && self
319                .visibilities
320                .iter()
321                .all(|value| other.visibilities.iter().any(|allowed| allowed == value))
322    }
323}
324
325pub fn policy_allows(context: Option<&PolicyContext>, envelope: Option<&PolicyEnvelope>) -> bool {
326    match envelope {
327        None => true,
328        Some(envelope) => match context {
329            Some(context) => context.allows(envelope),
330            None => envelope.is_public(),
331        },
332    }
333}
334
335fn normalize_policy_values(mut values: Vec<String>) -> Vec<String> {
336    values.sort_unstable();
337    values.dedup();
338    values
339}
340
341#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
342pub struct Datom {
343    pub entity: EntityId,
344    pub attribute: AttributeId,
345    pub value: Value,
346    pub op: OperationKind,
347    pub element: ElementId,
348    pub replica: ReplicaId,
349    pub causal_context: CausalContext,
350    pub provenance: DatomProvenance,
351    pub policy: Option<PolicyEnvelope>,
352}
353
354#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
355pub struct FactProvenance {
356    pub source_datom_ids: Vec<ElementId>,
357    #[serde(default, skip_serializing_if = "Vec::is_empty")]
358    pub imported_cuts: Vec<PartitionCut>,
359    pub sidecar_origin: Option<SidecarOrigin>,
360    pub source_ref: Option<SourceRef>,
361}
362
363#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
364pub struct Variable(pub String);
365
366impl Variable {
367    pub fn new(name: impl Into<String>) -> Self {
368        Self(name.into())
369    }
370}
371
372#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
373pub struct PredicateRef {
374    pub id: PredicateId,
375    pub name: String,
376    pub arity: usize,
377}
378
379#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
380pub enum AggregateFunction {
381    Count,
382    Sum,
383    Min,
384    Max,
385}
386
387impl fmt::Display for AggregateFunction {
388    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389        let label = match self {
390            Self::Count => "count",
391            Self::Sum => "sum",
392            Self::Min => "min",
393            Self::Max => "max",
394        };
395        write!(f, "{label}")
396    }
397}
398
399#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
400pub struct AggregateTerm {
401    pub function: AggregateFunction,
402    pub variable: Variable,
403}
404
405#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
406pub enum Term {
407    Variable(Variable),
408    Value(Value),
409    Aggregate(AggregateTerm),
410}
411
412#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
413pub struct Atom {
414    pub predicate: PredicateRef,
415    pub terms: Vec<Term>,
416}
417
418#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
419pub enum Literal {
420    Positive(Atom),
421    Negative(Atom),
422}
423
424#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
425pub struct RuleAst {
426    pub id: RuleId,
427    pub head: Atom,
428    pub body: Vec<Literal>,
429}
430
431#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
432pub struct QueryAst {
433    pub goals: Vec<Atom>,
434    pub keep: Vec<Variable>,
435}
436
437#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
438pub struct ExtensionalFact {
439    pub predicate: PredicateRef,
440    pub values: Vec<Value>,
441    pub policy: Option<PolicyEnvelope>,
442    pub provenance: Option<FactProvenance>,
443}
444
445#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
446pub enum TemporalView {
447    #[default]
448    Current,
449    AsOf(ElementId),
450}
451
452#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
453pub struct QuerySpec {
454    pub view: TemporalView,
455    pub query: QueryAst,
456}
457
458#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
459pub struct NamedQuerySpec {
460    pub name: Option<String>,
461    pub spec: QuerySpec,
462}
463
464#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
465pub enum ExplainTarget {
466    #[default]
467    Plan,
468    Tuple(Atom),
469}
470
471#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
472pub struct ExplainSpec {
473    pub view: TemporalView,
474    pub target: ExplainTarget,
475}
476
477#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
478pub struct NamedExplainSpec {
479    pub name: Option<String>,
480    pub spec: ExplainSpec,
481}
482
483#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
484pub struct RuleProgram {
485    pub predicates: Vec<PredicateRef>,
486    pub rules: Vec<RuleAst>,
487    pub materialized: Vec<PredicateId>,
488    pub facts: Vec<ExtensionalFact>,
489}
490
491#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
492pub struct Tuple {
493    pub id: TupleId,
494    pub predicate: PredicateId,
495    pub values: Vec<Value>,
496}
497
498#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
499pub struct DerivedTupleMetadata {
500    pub rule_id: RuleId,
501    pub predicate_id: PredicateId,
502    pub stratum: usize,
503    pub scc_id: usize,
504    pub iteration: usize,
505    pub parent_tuple_ids: Vec<TupleId>,
506    pub source_datom_ids: Vec<ElementId>,
507    #[serde(default, skip_serializing_if = "Vec::is_empty")]
508    pub imported_cuts: Vec<PartitionCut>,
509}
510
511#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
512pub struct DerivedTuple {
513    pub tuple: Tuple,
514    pub metadata: DerivedTupleMetadata,
515    pub policy: Option<PolicyEnvelope>,
516}
517
518#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
519pub struct QueryRow {
520    pub values: Vec<Value>,
521    pub tuple_id: Option<TupleId>,
522}
523
524#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
525pub struct QueryResult {
526    pub rows: Vec<QueryRow>,
527}
528
529#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
530pub struct DerivationTrace {
531    pub root: TupleId,
532    pub tuples: Vec<DerivedTuple>,
533}
534
535#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
536pub struct PhaseSignature {
537    pub available: Vec<String>,
538    pub provides: Vec<String>,
539    pub keep: Vec<String>,
540}
541
542#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
543pub struct PhaseNode {
544    pub id: String,
545    pub signature: PhaseSignature,
546    pub recursive_scc: Option<usize>,
547}
548
549#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
550pub struct PhaseEdge {
551    pub from: String,
552    pub to: String,
553}
554
555#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
556pub struct PhaseGraph {
557    pub nodes: Vec<PhaseNode>,
558    pub edges: Vec<PhaseEdge>,
559}
560
561#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
562pub struct PlanExplanation {
563    pub summary: String,
564    pub phase_graph: PhaseGraph,
565}
566
567#[cfg(test)]
568mod tests {
569    use super::{
570        merge_policy_envelopes, policy_allows, AttributeId, Datom, DatomProvenance, ElementId,
571        EntityId, FactProvenance, FederatedCut, OperationKind, PartitionCut, PartitionId,
572        PolicyContext, PolicyEnvelope, ReplicaId, SidecarKind, SidecarOrigin, SourceRef, Value,
573    };
574
575    #[test]
576    fn ids_are_deterministic_and_displayable() {
577        let entity = EntityId::new(7);
578        let attribute = AttributeId::new(11);
579        let element = ElementId::new(19);
580        let partition = PartitionId::new("ops-west");
581
582        assert_eq!(entity, EntityId::new(7));
583        assert_eq!(attribute.to_string(), "11");
584        assert_eq!(element.to_string(), "19");
585        assert_eq!(partition.to_string(), "ops-west");
586    }
587
588    #[test]
589    fn partition_cuts_format_and_normalize() {
590        let current = PartitionCut::current("tenant-b");
591        let as_of = PartitionCut::as_of("tenant-a", ElementId::new(7));
592
593        assert!(current.is_current());
594        assert_eq!(current.to_string(), "tenant-b@current");
595        assert_eq!(as_of.to_string(), "tenant-a@e7");
596
597        let normalized = FederatedCut {
598            cuts: vec![current.clone(), as_of.clone()],
599        }
600        .normalized();
601        assert_eq!(
602            normalized.cuts,
603            vec![
604                PartitionCut::as_of("tenant-a", ElementId::new(7)),
605                PartitionCut::current("tenant-b"),
606            ]
607        );
608    }
609
610    #[test]
611    fn value_default_is_null_and_round_trips_through_serde() {
612        assert_eq!(Value::default(), Value::Null);
613
614        let value = Value::List(vec![
615            Value::String("task-1".into()),
616            Value::Bool(true),
617            Value::Entity(EntityId::new(42)),
618        ]);
619
620        let json = serde_json::to_string(&value).expect("serialize value");
621        let decoded: Value = serde_json::from_str(&json).expect("deserialize value");
622
623        assert_eq!(decoded, value);
624    }
625
626    #[test]
627    fn provenance_defaults_and_datom_round_trip_preserve_fields() {
628        let provenance = DatomProvenance {
629            author_principal: "jamie".into(),
630            agent_id: "codex".into(),
631            tool_id: "shell".into(),
632            session_id: "session-1".into(),
633            source_ref: SourceRef {
634                uri: "file:///fixture".into(),
635                digest: Some("sha256:abc".into()),
636            },
637            parent_datom_ids: vec![ElementId::new(2), ElementId::new(3)],
638            confidence: 0.75,
639            trust_domain: "dev".into(),
640            schema_version: "v1".into(),
641        };
642        let datom = Datom {
643            entity: EntityId::new(1),
644            attribute: AttributeId::new(2),
645            value: Value::String("ready".into()),
646            op: OperationKind::Assert,
647            element: ElementId::new(4),
648            replica: ReplicaId::new(5),
649            causal_context: Default::default(),
650            provenance: provenance.clone(),
651            policy: None,
652        };
653
654        assert_eq!(DatomProvenance::default().confidence, 1.0);
655
656        let json = serde_json::to_string(&datom).expect("serialize datom");
657        let decoded: Datom = serde_json::from_str(&json).expect("deserialize datom");
658
659        assert_eq!(decoded.provenance, provenance);
660        assert_eq!(decoded, datom);
661    }
662
663    #[test]
664    fn fact_provenance_round_trips_with_sidecar_origin() {
665        let provenance = FactProvenance {
666            source_datom_ids: vec![ElementId::new(7)],
667            imported_cuts: Vec::new(),
668            sidecar_origin: Some(SidecarOrigin {
669                kind: SidecarKind::Vector,
670                sidecar_id: "vector-sidecar".into(),
671                record_id: "vec-1".into(),
672            }),
673            source_ref: Some(SourceRef {
674                uri: "s3://vectors/vec-1".into(),
675                digest: Some("sha256:def".into()),
676            }),
677        };
678
679        let json = serde_json::to_string(&provenance).expect("serialize fact provenance");
680        let decoded: FactProvenance =
681            serde_json::from_str(&json).expect("deserialize fact provenance");
682
683        assert_eq!(decoded, provenance);
684    }
685
686    #[test]
687    fn policy_context_requires_capability_and_visibility_when_present() {
688        let envelope = PolicyEnvelope {
689            capabilities: vec!["executor".into()],
690            visibilities: vec!["ops".into()],
691        };
692        let allowed = PolicyContext {
693            capabilities: vec!["executor".into()],
694            visibilities: vec!["ops".into()],
695        };
696        let missing_visibility = PolicyContext {
697            capabilities: vec!["executor".into()],
698            visibilities: vec![],
699        };
700
701        assert!(policy_allows(Some(&allowed), Some(&envelope)));
702        assert!(!policy_allows(Some(&missing_visibility), Some(&envelope)));
703        assert!(!policy_allows(None, Some(&envelope)));
704        assert!(policy_allows(None, None));
705    }
706
707    #[test]
708    fn policy_envelope_deserializes_legacy_single_value_fields() {
709        let decoded: PolicyEnvelope =
710            serde_json::from_str(r#"{"capability":"executor","visibility":"ops"}"#)
711                .expect("deserialize legacy policy envelope");
712
713        assert_eq!(
714            decoded,
715            PolicyEnvelope {
716                capabilities: vec!["executor".into()],
717                visibilities: vec!["ops".into()],
718            }
719        );
720    }
721
722    #[test]
723    fn policy_envelope_merge_is_conjunctive_union() {
724        let merged = merge_policy_envelopes([
725            Some(&PolicyEnvelope {
726                capabilities: vec!["executor".into()],
727                visibilities: vec!["ops".into()],
728            }),
729            Some(&PolicyEnvelope {
730                capabilities: vec!["memory_reader".into()],
731                visibilities: vec!["finance".into()],
732            }),
733        ])
734        .expect("merged policy envelope");
735
736        assert_eq!(
737            merged,
738            PolicyEnvelope {
739                capabilities: vec!["executor".into(), "memory_reader".into()],
740                visibilities: vec!["finance".into(), "ops".into()],
741            }
742        );
743    }
744
745    #[test]
746    fn policy_context_subset_checks_requested_capabilities_and_visibilities() {
747        let granted = PolicyContext {
748            capabilities: vec!["executor".into(), "operator".into()],
749            visibilities: vec!["ops".into(), "finance".into()],
750        };
751        let requested = PolicyContext {
752            capabilities: vec!["executor".into()],
753            visibilities: vec!["ops".into()],
754        };
755        let escalated = PolicyContext {
756            capabilities: vec!["admin".into()],
757            visibilities: vec!["ops".into()],
758        };
759
760        assert!(requested.subset_of(&granted));
761        assert!(!escalated.subset_of(&granted));
762        assert!(PolicyContext::default().is_empty());
763        assert!(!granted.is_empty());
764    }
765}