Skip to main content

lace_sans_io/logic/
policy.rs

1//! Local 040 policy types for public `Lace` handles.
2//!
3//! An interlace module is the single public policy artifact exchanged by one
4//! operand. It may export convergence, advertised-candidate, exposure, and
5//! directional-veto facets in one canonical Lace-030 module.
6
7use crate::records::RecordHash;
8use std::sync::Arc;
9
10use super::lacegram::{CanonicalLacegram, canonicalize_lacegram_source, prepare_lacegram_rules};
11use super::limits::PreparationError;
12use super::policy_source::validate_request_only_advertisement_dependencies;
13
14/// Public 040 interlace module.
15#[derive(Clone, Debug)]
16pub struct InterlaceModule {
17    canonical: Arc<CanonicalLacegram>,
18}
19
20impl PartialEq for InterlaceModule {
21    fn eq(&self, other: &Self) -> bool {
22        self.canonical.text == other.canonical.text
23    }
24}
25
26impl Eq for InterlaceModule {}
27
28impl InterlaceModule {
29    /// Canonicalize and validate an interlace module from lacegram text.
30    pub fn from_text(text: &str) -> Result<Self, PreparationError> {
31        let canonical =
32            canonicalize_lacegram_source(text, super::limits::DatalogLimits::default())?;
33        validate_request_only_advertisement_dependencies(&canonical.program)?;
34        Ok(Self {
35            canonical: Arc::new(canonical),
36        })
37    }
38
39    /// Canonical 030 lacegram text for this interlace module.
40    pub fn canonical_text(&self) -> &str {
41        &self.canonical.text
42    }
43
44    /// Blob record hash for this canonical lacegram text.
45    pub fn lacegram_resource_hash(&self) -> RecordHash {
46        super::exchange_plan::lacegram_resource_hash(&self.canonical.text)
47    }
48
49    /// Validate ordinary rules and derive their canonical text and identity.
50    pub fn from_rules(rules: Vec<crate::logic::Rule>) -> Result<Self, PreparationError> {
51        let canonical = prepare_lacegram_rules(rules, super::limits::DatalogLimits::default())?;
52        validate_request_only_advertisement_dependencies(&canonical.program)?;
53        Ok(Self {
54            canonical: Arc::new(canonical),
55        })
56    }
57
58    /// Whether some rule defines a standard exchange facet. Operands require
59    /// one; a helper-only program is not an exchange policy.
60    pub fn has_facet(&self) -> bool {
61        self.program().strata.iter().flatten().any(|rule| {
62            super::profile::lookup(&rule.head_predicate)
63                .is_some_and(|predicate| predicate.role == super::profile::Role::Facet)
64        })
65    }
66
67    /// Validated ordinary program retained with this module.
68    pub fn program(&self) -> &crate::logic::Lacegram {
69        &self.canonical.program
70    }
71
72    pub fn id(&self) -> RecordHash {
73        self.lacegram_resource_hash()
74    }
75}
76
77/// Select, expose, and request every record.
78pub fn all_records() -> &'static InterlaceModule {
79    static MODULE: std::sync::OnceLock<InterlaceModule> = std::sync::OnceLock::new();
80    constant(&MODULE, ALL_RECORDS)
81}
82
83/// Veto transmission from the side assigned this operand.
84pub fn tx_disabled() -> &'static InterlaceModule {
85    static MODULE: std::sync::OnceLock<InterlaceModule> = std::sync::OnceLock::new();
86    constant(&MODULE, TX_DISABLED)
87}
88
89/// Veto reception by the side assigned this operand.
90pub fn rx_disabled() -> &'static InterlaceModule {
91    static MODULE: std::sync::OnceLock<InterlaceModule> = std::sync::OnceLock::new();
92    constant(&MODULE, RX_DISABLED)
93}
94
95const ALL_RECORDS: &str = "ExposeRecord(Record) :- Have(Record).\nSelectAdvertisedCandidate(Record) :- Advertised(Record).\nSelectStoredRecord(Record) :- Have(Record).";
96const TX_DISABLED: &str = "TxDisabled() :- true.";
97const RX_DISABLED: &str = "RxDisabled() :- true.";
98
99fn constant(
100    cell: &'static std::sync::OnceLock<InterlaceModule>,
101    text: &str,
102) -> &'static InterlaceModule {
103    cell.get_or_init(|| InterlaceModule::from_text(text).expect("constant policy is valid"))
104}
105
106#[cfg(test)]
107#[test]
108fn constant_policies_are_canonical() {
109    for (module, text) in [
110        (all_records(), ALL_RECORDS),
111        (tx_disabled(), TX_DISABLED),
112        (rx_disabled(), RX_DISABLED),
113    ] {
114        assert_eq!(module.canonical_text(), text);
115    }
116}