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(crate) mod marker;
9pub mod model;
10pub mod store;
11pub mod trust;
12
13use std::path::PathBuf;
14
15pub use model::{OrgPolicyV1, OrgVerifyResult};
16pub use trust::{TrustStore, TrustedKey};
17
18use crate::core::policy::ResolvedPolicy;
19
20#[must_use]
21pub fn org_key_id(org: &str) -> String {
22    let safe: String = org
23        .chars()
24        .map(|c| {
25            if c.is_ascii_alphanumeric() || c == '-' {
26                c.to_ascii_lowercase()
27            } else {
28                '-'
29            }
30        })
31        .collect();
32    format!("org-{safe}")
33}
34
35/// Load + verify + trust-check the installed org policy artifact.
36/// Returns `Some(resolved)` only when all three hold:
37/// 1. An artifact is installed on disk
38/// 2. Its Ed25519 signature is valid
39/// 3. The signer's public key is trust-pinned
40/// 4. The admin declared `enforced = true`
41#[must_use]
42pub fn active_resolved() -> Option<ResolvedPolicy> {
43    let artifact = store::load_active()?;
44
45    let result = artifact.verify();
46    if !result.signature_valid {
47        tracing::warn!(
48            "org policy: signature invalid ({}), ignoring",
49            result.error.as_deref().unwrap_or("unknown")
50        );
51        return None;
52    }
53
54    let signer = artifact.signer_public_key.as_deref()?;
55    if !trust::is_trusted(signer) {
56        tracing::debug!("org policy: signer not trust-pinned, ignoring (fail-open)");
57        return None;
58    }
59
60    if !artifact.enforced {
61        tracing::debug!("org policy: admin did not set enforced=true, skipping");
62        return None;
63    }
64
65    match artifact.resolved() {
66        Ok(resolved) => Some(resolved),
67        Err(e) => {
68            tracing::warn!("org policy: failed to resolve pack: {e}");
69            None
70        }
71    }
72}
73
74/// Error-preserving resolution for enforcement callers. Only an absent policy
75/// or a verified, trusted policy explicitly marked unenforced yields `None`.
76///
77/// A recorded requirement (the required marker) changes both cases: without a
78/// policy it is an error, and a version-one policy does not satisfy it. A
79/// version-two artifact is not a version-one artifact and fails to load, which
80/// is an error as well. This is the same floor the admission snapshot applies
81/// (`runtime::refresh`), for callers that resolve the policy directly.
82pub fn active_resolved_checked() -> Result<Option<ResolvedPolicy>, String> {
83    AuthoritySnapshot::capture()?.resolve()
84}
85
86/// The bytes organization authority is read from, captured once: the selected
87/// policy source, the trust store and the required marker.
88///
89/// The fingerprint and the resolution both come from these captured bytes, so
90/// a caller that keeps a resolved policy under the fingerprint can never pair
91/// one authority's rules with another's identity: there is no second read
92/// between the two. Whoever reuses a kept resolution captures again and
93/// compares fingerprints first.
94pub struct AuthoritySnapshot {
95    fingerprint: blake3::Hash,
96    /// The fingerprint's state before the marker text.
97    before_marker: blake3::Hasher,
98    policy: Option<String>,
99    trust: Option<String>,
100    marker: Option<String>,
101}
102
103impl AuthoritySnapshot {
104    /// Reads the three inputs once. Paths are part of the fingerprint and
105    /// absence is distinct from emptiness; any read error is an error.
106    pub fn capture() -> Result<Self, String> {
107        let (policy_path, policy_required) = store::selected_source()?;
108        let mut hash = blake3::Hasher::new();
109        // The artifact alone is read at its own, larger bound; trust store
110        // and marker keep the usual one.
111        let mut read =
112            |path: PathBuf, required: bool, artifact: bool| -> Result<Option<String>, String> {
113                let path_bytes = path.as_os_str().as_encoded_bytes();
114                hash.update(&(path_bytes.len() as u64).to_le_bytes());
115                hash.update(path_bytes);
116                let text = if artifact {
117                    crate::core::policy::files::read_org_artifact(&path, required, false)?
118                } else {
119                    crate::core::policy::files::read(&path, required)?
120                };
121                hash_text(&mut hash, text.as_deref());
122                Ok(text)
123            };
124        let policy = read(policy_path, policy_required, true)?;
125        let trust = read(trust::trust_path_read_only()?, false, false)?;
126        // The marker goes last, path before text, so that the fingerprint of
127        // the same authority under another marker can be derived.
128        let marker_path = marker::marker_path_read_only()?;
129        let path_bytes = marker_path.as_os_str().as_encoded_bytes();
130        hash.update(&(path_bytes.len() as u64).to_le_bytes());
131        hash.update(path_bytes);
132        let marker = crate::core::policy::files::read(&marker_path, false)?;
133        let before_marker = hash.clone();
134        hash_text(&mut hash, marker.as_deref());
135        Ok(Self {
136            fingerprint: hash.finalize(),
137            before_marker,
138            policy,
139            trust,
140            marker,
141        })
142    }
143
144    #[cfg(any(test, feature = "enterprise"))]
145    pub fn fingerprint(&self) -> blake3::Hash {
146        self.fingerprint
147    }
148
149    /// Whether the captured marker records a requirement in force. A marker
150    /// that cannot be parsed is an error.
151    fn required(&self) -> Result<bool, String> {
152        Ok(self
153            .marker
154            .as_deref()
155            .map(marker::parse)
156            .transpose()?
157            .is_some_and(|marker| marker.is_required()))
158    }
159
160    /// Resolves the organization policy from the captured bytes, without
161    /// reading anything again. Same floor as the admission snapshot: under a
162    /// recorded requirement a missing policy is an error and a version-one
163    /// policy does not qualify; a version-two artifact is not a version-one
164    /// artifact and fails to parse, which is an error too.
165    pub fn resolve(&self) -> Result<Option<ResolvedPolicy>, String> {
166        self.resolve_observed().map(|(floor, _)| floor)
167    }
168
169    /// [`Self::resolve`], together with what the verifier's answer stated
170    /// when this very resolution asked it: handed out only after the
171    /// recapture and both bounds, with the floor it belongs to. `None`
172    /// whenever no verifier was asked. For a caller that records what it
173    /// decided under; `resolve` is this without the observation.
174    pub(crate) fn resolve_observed(
175        &self,
176    ) -> Result<(Option<ResolvedPolicy>, Option<Observation>), String> {
177        let required = self.required()?;
178        let Some(policy) = self.policy.as_deref() else {
179            if required {
180                return Err("required organization policy is missing".into());
181            }
182            return Ok((None, None));
183        };
184        // Version two is decided by the authenticated verifier for these
185        // captured bytes, on this call; nothing here decides it locally.
186        if is_v2(policy) {
187            let verified = verify_v2(
188                policy,
189                self.trust.as_deref(),
190                self.marker.as_deref(),
191                std::time::Instant::now() + RESOLVE_DEADLINE,
192            )?;
193            // The answer took time. Before the floor is used, the authority
194            // is read again and must be exactly what was captured — except
195            // for the marker, which must be exactly what this decision left
196            // stored. Any other change to policy, trust store or marker
197            // while waiting refuses.
198            let mut expected = self.before_marker.clone();
199            hash_text(&mut expected, Some(&verified.marker));
200            if Self::capture()?.fingerprint != expected.finalize() {
201                return Err("organization authority changed during verification".into());
202            }
203            // That read took time too: horizon and deadline once more, last.
204            verified.lifetime.check()?;
205            return Ok((verified.floor, Some(verified.observation)));
206        }
207        // Only a version-two artifact may exceed the usual input bound.
208        if policy.len() as u64 > crate::core::policy::files::MAX_BYTES {
209            return Err("policy input exceeds byte limit".into());
210        }
211        if required {
212            return Err("required organization policy is not verifiable".into());
213        }
214        let artifact = OrgPolicyV1::from_json(policy)?;
215        let trust: TrustStore = match self.trust.as_deref() {
216            Some(text) => {
217                toml::from_str(text).map_err(|_| "org trust input is invalid".to_string())?
218            }
219            None => TrustStore::default(),
220        };
221        resolve_checked(&artifact, &trust).map(|floor| (floor, None))
222    }
223}
224
225fn hash_text(hash: &mut blake3::Hasher, text: Option<&str>) {
226    match text {
227        Some(text) => {
228            hash.update(&[1]);
229            hash.update(&(text.len() as u64).to_le_bytes());
230            hash.update(text.as_bytes());
231        }
232        None => {
233            hash.update(&[0]);
234        }
235    }
236}
237
238/// Whether installed organization-policy text is a version-two artifact: one
239/// this Engine cannot verify by itself. Read from the artifact's own schema
240/// field; anything unreadable is simply not version two here and fails the
241/// version-one checks instead.
242pub(crate) fn is_v2(text: &str) -> bool {
243    let Ok(value) = serde_json::from_str::<serde_json::Value>(text) else {
244        return false;
245    };
246    let schema = value
247        .get("schema")
248        .or_else(|| value.get("statement").and_then(|s| s.get("schema")))
249        .and_then(serde_json::Value::as_str);
250    schema == Some(lean_ctx_protocol::org_policy_verifier::ORG_POLICY_V2_ARTIFACT_SCHEMA_ID)
251}
252
253/// A version-two organization policy as the verifier decides it for exactly
254/// these captured bytes, now.
255///
256/// The marker must already be stored: it names the organization and is the
257/// predecessor the decision is recorded on. Without one nothing is asked and
258/// nothing is created — the first marker of an endpoint is a separately
259/// authorized enrollment, not an outcome of a verification. The accepted
260/// state is recorded on that exact marker before the policy is used; a marker
261/// that changed, was damaged or was lost in between refuses. Nothing of an
262/// earlier answer is kept: every call asks again.
263///
264/// `deadline` is the caller's one monotonic bound for the whole decision:
265/// the verifier gets only the time left, the marker's replacement is not
266/// admitted at or after it, and nothing is returned at or after it. The
267/// bound is that admission: once admitted, the operating system's replace
268/// and directory sync run to their end, and a marker that was written is
269/// never taken back — a later error here does not undo it.
270pub(crate) fn verify_v2(
271    artifact: &str,
272    trust: Option<&str>,
273    marker_text: Option<&str>,
274    deadline: std::time::Instant,
275) -> Result<VerifiedV2, String> {
276    use crate::core::intelligence_runtime::org_policy_verifier::{Accepted, Snapshot, verify};
277    use lean_ctx_protocol::org_policy_verifier::{
278        OrgRequiredMarkerEntryV1, OrgRequiredMarkerStateV1,
279    };
280
281    let marker_text =
282        marker_text.ok_or("organization policy has no recorded requirement to verify against")?;
283    let stored = marker::parse(marker_text)?;
284    let accepted = verify(
285        &Snapshot {
286            organization: &stored.organization_id,
287            artifact: artifact.as_bytes(),
288            trust: trust.map(str::as_bytes),
289            marker: Some(marker_text.as_bytes()),
290        },
291        deadline,
292    )
293    .map_err(|_| "organization policy could not be verified")?;
294    // What this very answer states, taken before the answer is consumed: the
295    // observation below is made of nothing else.
296    let (state, sequence, pack, observed, statement) = match accepted {
297        Accepted::Enforce {
298            pack_toml,
299            policy_digest,
300            sequence,
301            valid_until,
302        } => {
303            let statement = policy_digest.as_str().to_owned();
304            (
305                OrgRequiredMarkerStateV1::Required { policy_digest },
306                sequence,
307                Some((pack_toml, valid_until)),
308                ObservedState::Required,
309                statement,
310            )
311        }
312        Accepted::NotRequired {
313            unpin_digest,
314            sequence,
315        } => {
316            let statement = unpin_digest.as_str().to_owned();
317            (
318                OrgRequiredMarkerStateV1::Unpinned { unpin_digest },
319                sequence,
320                None,
321                ObservedState::Unpinned,
322                statement,
323            )
324        }
325    };
326    let recorded_at = lean_ctx_protocol::TeamTimestampV1::new(
327        chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true),
328    )
329    .map_err(|_| "organization policy decision could not be dated")?;
330    let (_, marker) = marker::advance_within(
331        &stored.organization_id,
332        OrgRequiredMarkerEntryV1 {
333            sequence,
334            recorded_at,
335            state,
336        },
337        marker::Predecessor::Stored(marker_text),
338        deadline,
339    )
340    .map_err(|_| "organization policy decision could not be recorded")?;
341    let (floor, horizon) = match pack {
342        Some((pack_toml, valid_until)) => {
343            let floor = crate::core::policy::parse(&pack_toml)
344                .and_then(|pack| crate::core::policy::resolve(&pack))
345                .map_err(|_| "verified organization policy is invalid")?;
346            // An unreadable horizon is an expired one.
347            let horizon = chrono::DateTime::parse_from_rfc3339(valid_until.as_str())
348                .map_err(|_| "verified organization policy has expired")?
349                .with_timezone(&chrono::Utc);
350            (Some(floor), Some(horizon))
351        }
352        None => (None, None),
353    };
354    let lifetime = Lifetime { horizon, deadline };
355    // Recording may have waited for the marker lock and compiling took time.
356    lifetime.check()?;
357    Ok(VerifiedV2 {
358        floor,
359        marker,
360        lifetime,
361        // Only here, after the decision was recorded on the marker, the pack
362        // compiled and both bounds checked: an answer that got no further
363        // leaves no observation behind.
364        observation: Observation {
365            state: observed,
366            statement,
367            policy_version: sequence,
368            valid_until: horizon,
369        },
370    })
371}
372
373/// What one verification established: the floor to enforce, if any, the
374/// marker text that is stored now — the captured one, or the one this very
375/// decision appended — how long the decision may be acted on, and what the
376/// answer stated, for the record of whatever is decided under it.
377pub(crate) struct VerifiedV2 {
378    pub(crate) floor: Option<ResolvedPolicy>,
379    pub(crate) marker: String,
380    pub(crate) lifetime: Lifetime,
381    pub(crate) observation: Observation,
382}
383
384/// Which of the two authenticated answers was observed.
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub(crate) enum ObservedState {
387    /// A signed policy is enforced.
388    Required,
389    /// The requirement was lifted by a signed unpin. This is an organization
390    /// decision in force, not the absence of an organization: it is recorded
391    /// although it leaves no floor.
392    Unpinned,
393}
394
395/// The organization decision one verification observed, for a receipt's
396/// trace: which statement the verifier accepted, the policy version it
397/// enforces, and — for an enforced policy — the signed end that was checked.
398///
399/// Immutable and content-free: a digest, a number, a closed state and an
400/// instant, all from the same accepted answer. No policy text, trust store,
401/// marker, key, path or credential is in it. `policy_version` is the signed
402/// sequence of the organization's statement; it is NOT the `version` a policy
403/// pack carries in its own text, which stays the pack's.
404#[derive(Debug, Clone, PartialEq, Eq)]
405pub struct Observation {
406    state: ObservedState,
407    /// `sha256:<hex>` of the accepted statement: the policy, or the unpin.
408    statement: String,
409    policy_version: u64,
410    /// The signed horizon of an enforced policy as this Engine parsed and
411    /// checked it; `None` for an unpin, which has none.
412    valid_until: Option<chrono::DateTime<chrono::Utc>>,
413}
414
415impl Observation {
416    /// A signed unpin as a verification would have observed it. Tests only:
417    /// outside of them an observation comes from `verify_v2` and nowhere else.
418    #[cfg(any(test, feature = "test-support"))]
419    pub fn unpinned_for_test(policy_version: u64, fill: char) -> Self {
420        Self {
421            state: ObservedState::Unpinned,
422            statement: format!("sha256:{}", fill.to_string().repeat(64)),
423            policy_version,
424            valid_until: None,
425        }
426    }
427
428    pub(crate) fn state(&self) -> ObservedState {
429        self.state
430    }
431
432    pub(crate) fn statement(&self) -> &str {
433        &self.statement
434    }
435
436    pub fn policy_version(&self) -> u64 {
437        self.policy_version
438    }
439
440    pub(crate) fn valid_until(&self) -> Option<chrono::DateTime<chrono::Utc>> {
441        self.valid_until
442    }
443}
444
445/// The two bounds of one verified decision: the signed horizon of an enforced
446/// policy (none when the requirement was lifted) and the request's own
447/// monotonic deadline. Whoever acts on the decision checks both on fresh
448/// clocks at the last moment before acting — after any further read or wait,
449/// not only when the answer arrived.
450#[derive(Debug, Clone, Copy)]
451pub(crate) struct Lifetime {
452    horizon: Option<chrono::DateTime<chrono::Utc>>,
453    deadline: std::time::Instant,
454}
455
456impl Lifetime {
457    /// A decision signed up to `horizon` and asked under `deadline`. Tests
458    /// only: outside of them a lifetime comes from `verify_v2` and nowhere else.
459    #[cfg(any(test, feature = "test-support"))]
460    pub(crate) fn for_test(
461        horizon: Option<chrono::DateTime<chrono::Utc>>,
462        deadline: std::time::Instant,
463    ) -> Self {
464        Self { horizon, deadline }
465    }
466
467    /// A lifetime restored from its stored signed horizon, for a holder that
468    /// outlives the request which obtained the decision and so only ever
469    /// checks [`Self::check_horizon`]. Its request deadline has passed by
470    /// construction: [`Self::check`] refuses it.
471    pub(crate) fn restored(horizon: Option<chrono::DateTime<chrono::Utc>>) -> Self {
472        Self {
473            horizon,
474            deadline: std::time::Instant::now(),
475        }
476    }
477
478    /// The signed instant an enforced policy runs out at, for the compiled
479    /// policy to carry: whoever keeps that policy keeps its end with it.
480    pub(crate) fn horizon(&self) -> Option<chrono::DateTime<chrono::Utc>> {
481        self.horizon
482    }
483
484    /// Both bounds are exclusive, like the verifier's own check.
485    pub(crate) fn check(&self) -> Result<(), String> {
486        self.check_horizon()?;
487        if std::time::Instant::now() >= self.deadline {
488            return Err("organization policy verification deadline exceeded".into());
489        }
490        Ok(())
491    }
492
493    /// The signed horizon alone: for a holder that outlives the request which
494    /// obtained the decision, and so has no request deadline any more.
495    pub(crate) fn check_horizon(&self) -> Result<(), String> {
496        if self
497            .horizon
498            .is_some_and(|horizon| chrono::Utc::now() >= horizon)
499        {
500            return Err("verified organization policy has expired".into());
501        }
502        Ok(())
503    }
504}
505
506/// Bound for a verification asked for outside the admission worker, which
507/// brings its own.
508const RESOLVE_DEADLINE: std::time::Duration = std::time::Duration::from_secs(5);
509
510pub(crate) fn resolve_checked(
511    artifact: &OrgPolicyV1,
512    trusted: &TrustStore,
513) -> Result<Option<ResolvedPolicy>, String> {
514    if !artifact.verify().signature_valid {
515        return Err("org policy signature invalid".into());
516    }
517    let signer = artifact
518        .signer_public_key
519        .as_deref()
520        .ok_or_else(|| "org policy signer missing".to_string())?;
521    if !trusted
522        .keys
523        .iter()
524        .any(|key| key.public_key == signer && key.org == artifact.org)
525    {
526        return Err("org policy signer is not pinned for this organization".into());
527    }
528    let resolved = artifact.resolved().map_err(|error| error.to_string())?;
529    Ok(artifact.enforced.then_some(resolved))
530}
531
532/// What `policy org uninstall` removed, and what it left.
533#[derive(Debug, Default, PartialEq, Eq)]
534pub struct Uninstalled {
535    /// An installed artifact was removed.
536    pub artifact: bool,
537    /// The organization of the removed artifact, when it could be read.
538    pub org: Option<String>,
539    /// Public keys that were pinned for that organization and are removed.
540    pub unpinned: Vec<String>,
541    /// Keys still pinned (for other organizations). With no policy installed
542    /// they make every governed call refuse (a pinned organization never
543    /// disappears silently), so the caller names them.
544    pub remaining: Vec<TrustedKey>,
545}
546
547/// Remove the installed artifact together with the trust pins of its
548/// organization, so the endpoint is back where it was before
549/// `policy org install --trust`. Removing the artifact alone would leave a
550/// pinned organization without a policy, which enforcement reads as tampering
551/// and answers with deny-all. Pins for other organizations and a recorded
552/// requirement (Hive) are left alone; they are not this artifact's to lift.
553pub fn uninstall() -> Result<Uninstalled, String> {
554    let path = store::installed_path()?;
555    let mut outcome = Uninstalled::default();
556    if path.exists() {
557        // Read before removing: the organization says which pins go with it.
558        // A damaged artifact is still removed; its pins then stay and are
559        // reported as remaining.
560        outcome.org = store::read(&path).ok().map(|artifact| artifact.org);
561        outcome.artifact = store::uninstall()?;
562    }
563    if let Some(org) = &outcome.org {
564        outcome.unpinned = trust::remove_org(org)?;
565    }
566    outcome.remaining = trust::load()?.keys;
567    Ok(outcome)
568}
569
570/// Status and the captured-authority path against real files in an isolated
571/// data directory: what the status reports must be what enforcement decides.
572#[cfg(test)]
573mod authority_tests {
574    use super::*;
575    use lean_ctx_protocol::org_policy_verifier::{
576        OrgRequiredMarkerEntryV1, OrgRequiredMarkerStateV1,
577    };
578    use lean_ctx_protocol::{RecordDigest, TeamId, TeamTimestampV1};
579
580    const PACK: &str = "name = \"floor\"\nversion = \"1.0.0\"\ndescription = \"test\"\n[context]\ndeny_tools = [\"ctx_shell\"]\n";
581
582    /// A signed, enforced version-one policy for `acme`, installed.
583    fn install_policy() -> OrgPolicyV1 {
584        let key = ed25519_dalek::SigningKey::from_bytes(&[23; 32]);
585        let mut artifact = OrgPolicyV1::build("acme", "1", true, PACK).unwrap();
586        artifact.sign_with_key(&key);
587        store::install(&artifact).unwrap();
588        artifact
589    }
590
591    fn pin(org: &str, artifact: &OrgPolicyV1) {
592        trust::pin(org, artifact.signer_public_key.as_deref().unwrap()).unwrap();
593    }
594
595    fn require(sequence: u64) {
596        let entry = OrgRequiredMarkerEntryV1 {
597            sequence,
598            recorded_at: TeamTimestampV1::new("2026-10-10T09:00:00Z").unwrap(),
599            state: OrgRequiredMarkerStateV1::Required {
600                policy_digest: RecordDigest::new(format!("sha256:{}", "a".repeat(64))).unwrap(),
601            },
602        };
603        // The fixture stands in for an accepted decision made on the marker
604        // as it is stored now — or for the first one when none is stored.
605        let path = marker::marker_path_read_only().unwrap();
606        let stored = crate::core::policy::files::read(&path, false).unwrap();
607        let predecessor = stored.as_deref().map_or(
608            marker::Predecessor::NeverStored,
609            marker::Predecessor::Stored,
610        );
611        marker::advance(&TeamId::new("acme").unwrap(), entry, predecessor).unwrap();
612    }
613
614    #[test]
615    fn status_reports_a_key_pinned_for_another_organization_as_not_applied() {
616        let _data = crate::core::data_dir::isolated_data_dir();
617        let artifact = install_policy();
618        pin("other", &artifact);
619
620        let status = status();
621        assert!(status.present && status.signature_valid);
622        assert!(!status.trusted, "the key is pinned, but not for this org");
623        assert!(!status.applied);
624        assert!(
625            active_resolved_checked().is_err(),
626            "enforcement refuses the same state"
627        );
628
629        pin("acme", &artifact);
630        // `pin` keeps one entry per key: the wrong-org pin stays the only one.
631        assert!(!self::status().trusted);
632
633        // The exact assignment the commands admit on is the one enforcement
634        // uses: this key is pinned, but not for this organization — and
635        // pinning again says so instead of moving the pin.
636        let key = artifact.signer_public_key.as_deref().unwrap();
637        assert!(trust::is_trusted(key), "the key alone is pinned");
638        assert!(!trust::is_trusted_for("acme", key));
639        assert!(trust::is_trusted_for("other", key));
640        assert_eq!(trust::pinned_organization(key).as_deref(), Some("other"));
641        assert_eq!(
642            trust::pin_for("acme", key).unwrap(),
643            trust::PinOutcome::PinnedForAnother("other".into())
644        );
645        assert_eq!(
646            trust::pin_for("other", key).unwrap(),
647            trust::PinOutcome::AlreadyPinned
648        );
649        assert!(!trust::is_trusted_for("acme", key), "nothing was moved");
650        assert_eq!(trust::trusted_keys().len(), 1);
651    }
652
653    /// The status observes: it reads what is stored and leaves the endpoint as
654    /// it found it — no directory is created for it, with nothing stored and
655    /// with an explicit source that does not exist (an error, not "absent").
656    #[test]
657    fn status_creates_nothing_and_reports_a_missing_explicit_source() {
658        let data = crate::core::data_dir::isolated_data_dir();
659        let observed = || {
660            [
661                store::selected_source().unwrap().0,
662                trust::trust_path_read_only().unwrap(),
663                marker::marker_path_read_only().unwrap(),
664            ]
665            .map(|path| {
666                (
667                    path.exists(),
668                    path.parent().is_some_and(std::path::Path::exists),
669                )
670            })
671        };
672        let before = observed();
673        let status = status();
674        assert!(!status.present && status.resolve_error.is_none());
675        assert_eq!(status.recorded, RecordedRequirement::Absent);
676        assert_eq!(observed(), before, "the status left a trace on disk");
677
678        let missing = data.path().join("does-not-exist.json");
679        crate::test_env::set_var("LEANCTX_ORG_POLICY", &missing);
680        let status = self::status();
681        crate::test_env::remove_var("LEANCTX_ORG_POLICY");
682        assert!(
683            status.resolve_error.is_some(),
684            "an explicit source that is missing is not 'nothing installed'"
685        );
686        assert!(!missing.exists());
687    }
688
689    #[test]
690    fn status_and_enforcement_agree_for_a_correctly_pinned_policy() {
691        let _data = crate::core::data_dir::isolated_data_dir();
692        let artifact = install_policy();
693        pin("acme", &artifact);
694
695        let status = status();
696        assert!(status.trusted && status.applied && status.resolve_error.is_none());
697        assert!(active_resolved_checked().unwrap().is_some());
698    }
699
700    #[test]
701    fn a_recorded_requirement_or_a_damaged_marker_is_never_reported_as_applied() {
702        let _data = crate::core::data_dir::isolated_data_dir();
703        let artifact = install_policy();
704        pin("acme", &artifact);
705        require(1);
706
707        let status = status();
708        assert!(status.present && status.trusted);
709        assert!(!status.applied, "a version-one policy does not satisfy it");
710        assert!(status.resolve_error.is_some());
711        assert!(active_resolved_checked().is_err());
712
713        // Artifact removed: still required, still an error, not "absent".
714        store::uninstall().unwrap();
715        let status = self::status();
716        assert!(!status.present && !status.applied);
717        assert!(status.resolve_error.is_some());
718        assert!(active_resolved_checked().is_err());
719
720        // A damaged marker is an error for both, with or without a policy.
721        std::fs::write(marker::marker_path_read_only().unwrap(), b"{}").unwrap();
722        assert!(self::status().resolve_error.is_some());
723        assert!(active_resolved_checked().is_err());
724        store::install(&artifact).unwrap();
725        let status = self::status();
726        assert!(!status.applied && status.resolve_error.is_some());
727        assert!(AuthoritySnapshot::capture().unwrap().resolve().is_err());
728    }
729
730    /// The stored record is read as what it is. A signed unpin without any
731    /// artifact is a decision on record — neither an error nor "no central
732    /// policy" — and a record that cannot be read is unavailable, not absent.
733    #[test]
734    fn the_stored_record_is_read_as_recorded_absent_or_unavailable() {
735        let _data = crate::core::data_dir::isolated_data_dir();
736        assert_eq!(recorded_requirement(), RecordedRequirement::Absent);
737        assert_eq!(status().recorded, RecordedRequirement::Absent);
738
739        require(3);
740        match recorded_requirement() {
741            RecordedRequirement::Recorded {
742                organization,
743                required: true,
744                sequence: 3,
745                entries: 1,
746                ..
747            } => assert_eq!(organization, "acme"),
748            other => panic!("not the recorded requirement: {other:?}"),
749        }
750
751        let path = marker::marker_path_read_only().unwrap();
752        let stored = crate::core::policy::files::read(&path, false)
753            .unwrap()
754            .unwrap();
755        marker::advance(
756            &TeamId::new("acme").unwrap(),
757            OrgRequiredMarkerEntryV1 {
758                sequence: 4,
759                recorded_at: TeamTimestampV1::new("2026-10-11T09:00:00Z").unwrap(),
760                state: OrgRequiredMarkerStateV1::Unpinned {
761                    unpin_digest: RecordDigest::new(format!("sha256:{}", "b".repeat(64))).unwrap(),
762                },
763            },
764            marker::Predecessor::Stored(&stored),
765        )
766        .unwrap();
767        let status = status();
768        assert!(!status.present && status.source.is_none());
769        assert!(
770            status.resolve_error.is_none(),
771            "a lifted requirement refuses nothing"
772        );
773        match status.recorded {
774            RecordedRequirement::Recorded {
775                required: false,
776                sequence: 4,
777                entries: 2,
778                statement,
779                ..
780            } => assert_eq!(statement, format!("sha256:{}", "b".repeat(64))),
781            other => panic!("the unpin is not on record: {other:?}"),
782        }
783
784        std::fs::write(&path, b"{}").unwrap();
785        assert!(matches!(
786            recorded_requirement(),
787            RecordedRequirement::Unavailable(_)
788        ));
789    }
790
791    #[test]
792    fn a_capture_resolves_its_own_bytes_whatever_changes_afterwards() {
793        let _data = crate::core::data_dir::isolated_data_dir();
794        let artifact = install_policy();
795        pin("acme", &artifact);
796
797        let enforced = AuthoritySnapshot::capture().unwrap();
798        // Between capture and resolve: the policy is removed, the trust store
799        // emptied and a requirement recorded.
800        store::uninstall().unwrap();
801        trust::remove(artifact.signer_public_key.as_deref().unwrap()).unwrap();
802        require(1);
803        assert!(
804            enforced.resolve().unwrap().is_some(),
805            "the capture answers for the bytes it holds"
806        );
807
808        let now = AuthoritySnapshot::capture().unwrap();
809        assert_ne!(now.fingerprint(), enforced.fingerprint());
810        assert!(now.resolve().is_err(), "required, and nothing installed");
811
812        // Each of the three inputs is part of the identity.
813        let before = AuthoritySnapshot::capture().unwrap().fingerprint();
814        store::install(&artifact).unwrap();
815        let with_policy = AuthoritySnapshot::capture().unwrap().fingerprint();
816        pin("acme", &artifact);
817        let with_trust = AuthoritySnapshot::capture().unwrap().fingerprint();
818        require(2);
819        let with_marker = AuthoritySnapshot::capture().unwrap().fingerprint();
820        assert!(before != with_policy && with_policy != with_trust && with_trust != with_marker);
821    }
822
823    #[test]
824    fn an_unreadable_explicit_source_refuses_instead_of_resolving_to_no_policy() {
825        let data = crate::core::data_dir::isolated_data_dir();
826        let missing = data.path().join("does-not-exist.json");
827        crate::test_env::set_var("LEANCTX_ORG_POLICY", &missing);
828        let captured = AuthoritySnapshot::capture();
829        crate::test_env::remove_var("LEANCTX_ORG_POLICY");
830        assert!(captured.is_err(), "an explicit source that is absent");
831    }
832}
833
834#[cfg(test)]
835mod checked_tests {
836    use super::*;
837
838    fn fixture(enforced: bool) -> (OrgPolicyV1, TrustStore) {
839        let key = ed25519_dalek::SigningKey::from_bytes(&[19; 32]);
840        let mut artifact = OrgPolicyV1::build(
841            "acme", "1", enforced,
842            "name = \"floor\"\nversion = \"1.0.0\"\ndescription = \"test\"\n[context]\ndeny_tools = [\"ctx_shell\"]\n",
843        ).unwrap();
844        artifact.sign_with_key(&key);
845        let trusted = TrustStore {
846            keys: vec![TrustedKey {
847                org: "acme".into(),
848                public_key: artifact.signer_public_key.clone().unwrap(),
849                added_at: "2026-09-09T00:00:00Z".into(),
850            }],
851        };
852        (artifact, trusted)
853    }
854
855    #[test]
856    fn checked_resolution_requires_valid_signature_and_org_pin() {
857        let (mut artifact, mut trusted) = fixture(true);
858        assert!(resolve_checked(&artifact, &trusted).unwrap().is_some());
859        assert!(resolve_checked(&artifact, &TrustStore::default()).is_err());
860        trusted.keys[0].org = "other".into();
861        assert!(resolve_checked(&artifact, &trusted).is_err());
862        trusted.keys[0].org = "acme".into();
863        artifact.enforced = false;
864        assert!(resolve_checked(&artifact, &trusted).is_err());
865    }
866
867    #[test]
868    fn checked_resolution_allows_only_trusted_signed_disable() {
869        let (artifact, trusted) = fixture(false);
870        assert!(resolve_checked(&artifact, &trusted).unwrap().is_none());
871        assert!(resolve_checked(&artifact, &TrustStore::default()).is_err());
872    }
873}
874
875/// The endpoint's stored requirement record, as it reads now.
876///
877/// A record of the last decision that was accepted and stored — read from the
878/// marker file and nothing else. It is not a verification: no verifier is
879/// asked, nothing is appended, and what it shows may be older than what the
880/// enforcement pipeline would decide at the next use.
881#[derive(Debug, Clone, Default, PartialEq, Eq)]
882pub enum RecordedRequirement {
883    /// No record is stored on this endpoint.
884    #[default]
885    Absent,
886    /// A record is stored and cannot be read or parsed. Never "absent":
887    /// governed operations refuse while it is in this state.
888    Unavailable(String),
889    /// The newest stored entry.
890    Recorded {
891        organization: String,
892        /// `true`: a policy is required. `false`: the requirement was lifted
893        /// by a signed unpin — an organization decision that stays on record.
894        required: bool,
895        /// The signed sequence of the recorded statement.
896        sequence: u64,
897        /// `sha256:<hex>` of the recorded statement (the policy, or the unpin).
898        statement: String,
899        recorded_at: String,
900        /// Entries on record; this is the last of them.
901        entries: usize,
902    },
903}
904
905/// Reads the stored requirement record. Read-only: see [`RecordedRequirement`].
906#[must_use]
907pub fn recorded_requirement() -> RecordedRequirement {
908    use lean_ctx_protocol::org_policy_verifier::OrgRequiredMarkerStateV1;
909
910    let text = match marker::marker_path_read_only()
911        .and_then(|path| crate::core::policy::files::read(&path, false))
912    {
913        Ok(Some(text)) => text,
914        Ok(None) => return RecordedRequirement::Absent,
915        Err(error) => return RecordedRequirement::Unavailable(error),
916    };
917    let stored = match marker::parse(&text) {
918        Ok(stored) => stored,
919        Err(error) => return RecordedRequirement::Unavailable(error),
920    };
921    let current = stored.current();
922    let (required, statement) = match &current.state {
923        OrgRequiredMarkerStateV1::Required { policy_digest } => (true, policy_digest.as_str()),
924        OrgRequiredMarkerStateV1::Unpinned { unpin_digest } => (false, unpin_digest.as_str()),
925    };
926    RecordedRequirement::Recorded {
927        organization: stored.organization_id.as_str().to_owned(),
928        required,
929        sequence: current.sequence,
930        statement: statement.to_owned(),
931        recorded_at: current.recorded_at.as_str().to_owned(),
932        entries: stored.entries.len(),
933    }
934}
935
936#[derive(Debug, Clone, Default)]
937pub struct OrgStatus {
938    /// The stored requirement record, read as it is — not a verification.
939    pub recorded: RecordedRequirement,
940    pub present: bool,
941    pub source: Option<PathBuf>,
942    pub org: Option<String>,
943    pub policy_version: Option<String>,
944    pub enforced: bool,
945    pub issued_at: Option<String>,
946    pub signer_public_key: Option<String>,
947    pub signature_valid: bool,
948    pub trusted: bool,
949    pub applied: bool,
950    pub resolve_error: Option<String>,
951    pub pinned_anchors: usize,
952    /// The resolved pack of the version-one artifact this status read — what
953    /// a floor made of it would be. A preview of stored bytes, independent of
954    /// trust and of `applied`; `None` without such an artifact.
955    pub floor_preview: Option<ResolvedPolicy>,
956}
957
958#[must_use]
959pub fn status() -> OrgStatus {
960    // An observation and nothing else. Every input is read through the
961    // read-only resolutions the admission snapshot uses — the selected
962    // source, the trust store, the stored record — each once: this function
963    // creates and repairs no directory, asks no verifier and writes nothing.
964    // What it reports is a diagnosis of what is stored, not an authority.
965    let recorded = recorded_requirement();
966    // The requirement, from that one read of the record. A record that
967    // cannot be read is an error, never "no requirement".
968    let required = match &recorded {
969        RecordedRequirement::Absent => Ok(false),
970        RecordedRequirement::Recorded { required, .. } => Ok(*required),
971        RecordedRequirement::Unavailable(error) => Err(error.clone()),
972    };
973    // An unreadable trust store pins nothing here and is reported: it is
974    // not an empty store, and enforcement refuses on it.
975    let (anchors, trust_error) = match trust::load_read_only() {
976        Ok(store) => (store.keys, None),
977        Err(error) => (Vec::new(), Some(error)),
978    };
979    let pinned_anchors = anchors.len();
980    // The selected source, read once. An explicit source that is missing or
981    // unreadable is an error — it never falls back to another artifact and
982    // is never "nothing installed".
983    let (source, text) = match store::selected_source() {
984        Ok((path, explicit)) => {
985            match crate::core::policy::files::read_org_artifact(&path, explicit, false) {
986                Ok(text) => (text.as_ref().map(|_| path), text),
987                Err(error) => {
988                    return OrgStatus {
989                        recorded,
990                        present: true,
991                        source: Some(path),
992                        resolve_error: Some(error),
993                        pinned_anchors,
994                        ..Default::default()
995                    };
996                }
997            }
998        }
999        Err(error) => {
1000            return OrgStatus {
1001                recorded,
1002                resolve_error: Some(error),
1003                pinned_anchors,
1004                ..Default::default()
1005            };
1006        }
1007    };
1008    let artifact = text
1009        .as_deref()
1010        .filter(|text| text.len() as u64 <= crate::core::policy::files::MAX_BYTES)
1011        .and_then(|text| OrgPolicyV1::from_json(text).ok());
1012    let Some(artifact) = artifact else {
1013        // "No version-one policy" is not always "no organization policy":
1014        // say so when a requirement is recorded, when the record is damaged,
1015        // or when something is installed that this status cannot read.
1016        let resolve_error = match required {
1017            Ok(true) => Some(
1018                "an organization policy is required on this endpoint, but none that can be \
1019                 verified is installed"
1020                    .to_string(),
1021            ),
1022            Ok(false) => source
1023                .as_ref()
1024                .map(|_| {
1025                    "the installed organization policy is not a version-one artifact; it needs \
1026                     a verifier"
1027                        .to_string()
1028                })
1029                .or(trust_error),
1030            Err(error) => Some(error),
1031        };
1032        return OrgStatus {
1033            recorded,
1034            // Something is installed even when this status cannot read it.
1035            present: source.is_some(),
1036            source,
1037            resolve_error,
1038            pinned_anchors,
1039            ..Default::default()
1040        };
1041    };
1042    let verify = artifact.verify();
1043    let signer_key = artifact.signer_public_key.clone();
1044    // Trusted means what enforcement means by it (`resolve_checked`): this key
1045    // pinned for this artifact's organization. The same key pinned under
1046    // another organization does not count.
1047    let trusted = signer_key.as_deref().is_some_and(|signer| {
1048        anchors
1049            .iter()
1050            .any(|key| key.public_key == signer && key.org == artifact.org)
1051    });
1052    let resolve_result = artifact.resolved();
1053    // A version-one policy is not applied under a recorded requirement, nor
1054    // while the record or the trust store cannot be read: enforcement refuses
1055    // in each case, and the status must not say otherwise.
1056    let authority_error = match required {
1057        Ok(true) => Some(
1058            "an organization policy is required on this endpoint; the installed version-one \
1059             policy does not satisfy it"
1060                .to_string(),
1061        ),
1062        Ok(false) => trust_error,
1063        Err(error) => Some(error),
1064    };
1065    let applied = authority_error.is_none()
1066        && trusted
1067        && verify.signature_valid
1068        && artifact.enforced
1069        && resolve_result.is_ok();
1070
1071    OrgStatus {
1072        recorded,
1073        present: true,
1074        source,
1075        org: Some(artifact.org.clone()),
1076        policy_version: Some(artifact.policy_version.clone()),
1077        enforced: artifact.enforced,
1078        issued_at: Some(artifact.issued_at.clone()),
1079        signer_public_key: signer_key,
1080        signature_valid: verify.signature_valid,
1081        trusted,
1082        applied,
1083        // The floor of the artifact read above, for a preview: the same
1084        // bytes this status describes, not a second load.
1085        floor_preview: resolve_result.as_ref().ok().cloned(),
1086        resolve_error: authority_error.or_else(|| resolve_result.err().map(|e| e.to_string())),
1087        pinned_anchors,
1088    }
1089}