Skip to main content

lean_ctx/core/policy/org/
mod.rs

1//! Org-level policy distribution (ADR-023, GL #674).
2//!
3//! An organisation distributes a centrally signed [`OrgPolicyV1`] artifact.
4//! When installed, signature-verified, and the signer's key is trust-pinned,
5//! the runtime folds it in as an un-bypassable enforcement floor beneath the
6//! local project pack.
7
8pub mod model;
9pub mod store;
10pub mod trust;
11
12use std::path::PathBuf;
13
14pub use model::{OrgPolicyV1, OrgVerifyResult};
15pub use trust::{TrustStore, TrustedKey};
16
17use crate::core::policy::ResolvedPolicy;
18
19#[must_use]
20pub fn org_key_id(org: &str) -> String {
21    let safe: String = org
22        .chars()
23        .map(|c| {
24            if c.is_ascii_alphanumeric() || c == '-' {
25                c.to_ascii_lowercase()
26            } else {
27                '-'
28            }
29        })
30        .collect();
31    format!("org-{safe}")
32}
33
34/// Load + verify + trust-check the installed org policy artifact.
35/// Returns `Some(resolved)` only when all three hold:
36/// 1. An artifact is installed on disk
37/// 2. Its Ed25519 signature is valid
38/// 3. The signer's public key is trust-pinned
39/// 4. The admin declared `enforced = true`
40#[must_use]
41pub fn active_resolved() -> Option<ResolvedPolicy> {
42    let artifact = store::load_active()?;
43
44    let result = artifact.verify();
45    if !result.signature_valid {
46        tracing::warn!(
47            "org policy: signature invalid ({}), ignoring",
48            result.error.as_deref().unwrap_or("unknown")
49        );
50        return None;
51    }
52
53    let signer = artifact.signer_public_key.as_deref()?;
54    if !trust::is_trusted(signer) {
55        tracing::debug!("org policy: signer not trust-pinned, ignoring (fail-open)");
56        return None;
57    }
58
59    if !artifact.enforced {
60        tracing::debug!("org policy: admin did not set enforced=true, skipping");
61        return None;
62    }
63
64    match artifact.resolved() {
65        Ok(resolved) => Some(resolved),
66        Err(e) => {
67            tracing::warn!("org policy: failed to resolve pack: {e}");
68            None
69        }
70    }
71}
72
73#[derive(Debug, Clone, Default)]
74pub struct OrgStatus {
75    pub present: bool,
76    pub source: Option<PathBuf>,
77    pub org: Option<String>,
78    pub policy_version: Option<String>,
79    pub enforced: bool,
80    pub issued_at: Option<String>,
81    pub signer_public_key: Option<String>,
82    pub signature_valid: bool,
83    pub trusted: bool,
84    pub applied: bool,
85    pub resolve_error: Option<String>,
86    pub pinned_anchors: usize,
87}
88
89#[must_use]
90pub fn status() -> OrgStatus {
91    let Some(artifact) = store::load_active() else {
92        return OrgStatus {
93            pinned_anchors: trust::trusted_keys().len(),
94            ..Default::default()
95        };
96    };
97    let verify = artifact.verify();
98    let signer_key = artifact.signer_public_key.clone();
99    let trusted = signer_key.as_deref().is_some_and(trust::is_trusted);
100    let resolve_result = artifact.resolved();
101
102    OrgStatus {
103        present: true,
104        source: store::source_path(),
105        org: Some(artifact.org.clone()),
106        policy_version: Some(artifact.policy_version.clone()),
107        enforced: artifact.enforced,
108        issued_at: Some(artifact.issued_at.clone()),
109        signer_public_key: signer_key,
110        signature_valid: verify.signature_valid,
111        trusted,
112        applied: trusted && verify.signature_valid && artifact.enforced && resolve_result.is_ok(),
113        resolve_error: resolve_result.err().map(|e| e.to_string()),
114        pinned_anchors: trust::trusted_keys().len(),
115    }
116}