lean-ctx-engine 3.12.1

LeanCTX | OSS library: the open-source runtime for the Context Gateway for AI agents behind the lean-ctx CLI, MCP server and proxy integrations.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// SPDX-License-Identifier: Apache-2.0
//! The Hive effective policy as a floor of the gateway policy engine
//! (Release B 3.1.4).
//!
//! A verified bundle (the Hive connector's effective-policy pull) is installed as two files
//! in `<data_dir>/hive/policy/effective/`, both 0600 and written atomically:
//!
//! - `floor.json` ([`Floor`]): the verified `policy` (default + per vault),
//!   its version, digest and `not_after`;
//! - `required.json`: the highest version ever installed. It only grows, and
//!   it is what makes a missing policy never mean "no rules": while it
//!   exists, a missing, damaged or older floor fails every policy refresh
//!   closed instead of dropping the Hive rules.
//!
//! The policy snapshot ([`crate::core::policy::runtime`]) reads both files
//! and folds the floor *underneath* whatever the engine would enforce
//! otherwise (local pack, org policy), with [`crate::core::policy::floor::merge_floor`]:
//! every field merges toward the stricter side, so a local setting can only
//! tighten the Hive policy, never loosen it. [`loosened_by_floor`] names the
//! local settings the floor overrides, for `hive status`.
//!
//! Only the members the schema marks `x-enforcement: engine` are mapped
//! (their `x-engine` field); everything else is reported as unenforced on
//! the ack. Expiry (`not_after`) does not lift the floor: the last valid
//! rules keep restricting local work offline; only sensitive operations
//! fail closed (the connector's sensitive-operation gate).

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::core::input_filters::FilterAction;
use crate::core::policy::{
    BudgetRules, EgressRules, FilterRules, ResolvedPolicy, RoutingPolicyRules,
};
use lean_ctx_protocol::hive::effective_policy::{BundlePolicyV1, EffectivePolicyV1};

pub const FLOOR_SCHEMA: &str = "leanctx.hive.policy-floor/v1";
pub const DIR: &str = "effective";
pub const FLOOR_FILE: &str = "floor.json";
pub const REQUIRED_FILE: &str = "required.json";
/// Name of the resolved Hive floor (shows in `policy` diagnostics).
pub const FLOOR_NAME: &str = "hive-policy";

/// What `floor.json` holds: the verified effective policy, nothing else.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Floor {
    pub schema: String,
    pub version: u64,
    pub digest: String,
    pub not_after: String,
    pub policy: BundlePolicyV1,
}

/// `required.json`: the highest installed version.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Required {
    pub version: u64,
}

/// `<data_dir>/hive/policy/effective` of this installation.
pub fn floor_dir() -> Option<PathBuf> {
    let data = crate::core::paths::data_dir().ok()?;
    Some(data.join("hive").join("policy").join(DIR))
}

/// Whether `target` (canonical) lies in `<data_dir>/hive/policy`, the files
/// only the engine's own policy pull writes: agent file tools are refused
/// there ([`crate::core::pathjail::enforce_writable`]).
pub fn is_policy_path(target: &Path) -> bool {
    let Ok(data) = crate::core::paths::data_dir() else {
        return false;
    };
    let dir = data.join("hive").join("policy");
    let canonical =
        crate::core::pathutil::canonicalize_secure(&dir).unwrap_or_else(|_| dir.clone());
    target.starts_with(&dir) || target.starts_with(&canonical)
}

/// `floor_dir` below a Hive store's data directory (`<data_dir>/hive`).
pub fn floor_dir_in(hive_data: &Path) -> PathBuf {
    hive_data.join("policy").join(DIR)
}

/// The installed floor and requirement, checked against each other: `Ok(None)`
/// only when neither exists (no Hive policy was ever installed here).
pub fn check(floor: Option<&str>, required: Option<&str>) -> Result<Option<Floor>, String> {
    let required = required
        .map(serde_json::from_str::<Required>)
        .transpose()
        .map_err(|_| "the Hive policy requirement is damaged")?;
    let floor = match floor {
        Some(text) => Some(
            serde_json::from_str::<Floor>(text)
                .ok()
                .filter(|f| f.schema == FLOOR_SCHEMA)
                .filter(|f| {
                    f.policy.effective.validate().is_ok()
                        && f.policy
                            .vaults
                            .iter()
                            .all(|v| v.effective.validate().is_ok())
                })
                .ok_or("the installed Hive policy is damaged")?,
        ),
        None => None,
    };
    match (floor, required) {
        (None, Some(_)) => Err("the Hive policy in force is missing".into()),
        (Some(floor), Some(required)) if floor.version < required.version => Err(format!(
            "the installed Hive policy v{} is older than v{} in force",
            floor.version, required.version
        )),
        (floor, _) => Ok(floor),
    }
}

/// Read both files of `dir` (absent = `None`).
pub fn read(dir: &Path) -> Result<(Option<String>, Option<String>), String> {
    let read = |name: &str| match std::fs::read_to_string(dir.join(name)) {
        Ok(text) => Ok(Some(text)),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
        Err(_) => Err(format!("the Hive policy file {name} is unreadable")),
    };
    Ok((read(FLOOR_FILE)?, read(REQUIRED_FILE)?))
}

/// The vault whose effective policy applies to `project_root`, when the
/// floor has per-vault entries: the vault this checkout is linked to.
pub fn vault_for(floor_text: &str, hive_data: &Path, project_root: &Path) -> Option<String> {
    let floor: Floor = serde_json::from_str(floor_text).ok()?;
    if floor.policy.vaults.is_empty() {
        return None;
    }
    let map = crate::core::vault_map::VaultMap::load(hive_data);
    let record = map.for_root(&project_root.to_string_lossy())?;
    floor
        .policy
        .vaults
        .iter()
        .any(|v| v.vault_id == record.vault_id)
        .then(|| record.vault_id.clone())
}

/// The floor for `vault` (or the default) as a resolved policy, or `None`
/// when no Hive policy is installed or the verified policy sets no members.
/// Enrollment metadata alone must not activate protected source/egress mode.
/// An inconsistent pair is still an error, and the caller fails closed.
pub fn resolve(
    floor: Option<&str>,
    required: Option<&str>,
    vault: Option<&str>,
) -> Result<Option<ResolvedPolicy>, String> {
    Ok(check(floor, required)?.and_then(|floor| {
        let policy = floor.policy.for_vault(vault);
        // Inspect the selected wire policy before mapping it: fields enforced
        // elsewhere and unknown future members must not be mistaken for an
        // empty policy. Explicit values, even `off` or an empty allowlist,
        // conservatively keep the floor active.
        (!policy.members().is_empty()).then(|| to_resolved(policy, floor.version))
    }))
}

fn cap_u32(value: Option<u64>) -> Option<u32> {
    value.map(|v| u32::try_from(v).unwrap_or(u32::MAX))
}

/// Cents per the schema; the engine counts USD.
#[allow(clippy::cast_precision_loss)] // ≤ 1e8 cents is exact in f64
fn usd(cents: Option<u64>) -> Option<f64> {
    cents.map(|c| c as f64 / 100.0)
}

/// The `x-engine` mapping of every `engine` member onto the gateway pack.
pub fn to_resolved(policy: &EffectivePolicyV1, version: u64) -> ResolvedPolicy {
    let s = &policy.security;
    let c = &policy.context;
    // A pack's label list replaces the built-in set; a Hive list must
    // only add to it, so the built-in labels stay in force with it.
    let blocked_labels = s.blocked_labels.as_ref().map_or_else(Vec::new, |labels| {
        let mut all: Vec<String> = crate::core::input_filters::classification::DEFAULT_LABELS
            .iter()
            .map(|l| (*l).to_string())
            .collect();
        all.extend(labels.iter().cloned());
        all
    });
    ResolvedPolicy {
        name: FLOOR_NAME.into(),
        version: version.to_string(),
        description: format!("Hive effective policy v{version}"),
        chain: Vec::new(),
        default_read_mode: c.read_mode.clone(),
        allow_tools: c.allowed_tools.clone(),
        deny_tools: c.denied_tools.clone().unwrap_or_default(),
        max_context_tokens: cap_u32(c.max_context_tokens),
        audit_retention_days: None,
        redaction: BTreeMap::new(),
        filters: FilterRules {
            pii: s.pii.map(|a| a.as_str().to_string()),
            classification: s.classification.map(|a| a.as_str().to_string()),
            injection: c.injection_filter.map(|a| a.as_str().to_string()),
            blocked_labels,
            ..FilterRules::default()
        },
        egress: EgressRules {
            forbidden_patterns: Vec::new(),
            block_secrets: s.block_secrets,
            max_writes_per_min: cap_u32(c.max_writes_per_min),
        },
        routing: RoutingPolicyRules {
            allowed_models: Vec::new(),
            model_ceiling_groups: s.allowed_models.clone().unwrap_or_default(),
            forbid_downgrade_for: Vec::new(),
        },
        budgets: BudgetRules {
            max_cost_usd_per_person_per_day: usd(c.max_cost_cents_per_person_per_day),
            max_cost_usd_per_project_per_month: usd(c.max_cost_cents_per_project_per_month),
            max_requests_per_minute_per_person: cap_u32(c.max_requests_per_minute_per_person),
        },
    }
}

/// Fold the Hive floor under `effective` (what the engine enforces without
/// it). Tighten-only: see [`crate::core::policy::floor::merge_floor`].
pub fn apply(floor: &ResolvedPolicy, effective: Option<&ResolvedPolicy>) -> ResolvedPolicy {
    crate::core::policy::floor::merge_floor(floor, effective)
}

/// The local settings of the project at `project_root` (its
/// `.lean-ctx/policy.toml`) that the installed Hive floor overrides.
pub fn local_overrides(hive_data: &Path, project_root: &Path) -> Vec<String> {
    let Ok((Some(text), required)) = read(&floor_dir_in(hive_data)) else {
        return Vec::new();
    };
    let Ok(Some(floor)) = check(Some(&text), required.as_deref()) else {
        return Vec::new();
    };
    let pack = project_root.join(".lean-ctx").join("policy.toml");
    let Some(local) = crate::core::policy::parse_file(&pack)
        .ok()
        .and_then(|p| crate::core::policy::resolve(&p).ok())
    else {
        return Vec::new();
    };
    let vault = vault_for(&text, hive_data, project_root);
    let hive = to_resolved(floor.policy.for_vault(vault.as_deref()), floor.version);
    loosened_by_floor(&hive, &local)
}

fn rank(action: Option<&String>) -> Option<u8> {
    action
        .and_then(|a| FilterAction::parse(a))
        .map(FilterAction::rank)
}

/// The settings of `local` that are looser than the Hive `floor` and are
/// therefore ignored, one line each, in a fixed order.
pub fn loosened_by_floor(floor: &ResolvedPolicy, local: &ResolvedPolicy) -> Vec<String> {
    let mut out = Vec::new();
    let mut cap = |field: &str, local: Option<f64>, hive: Option<f64>| {
        if let (Some(l), Some(a)) = (local, hive)
            && l > a
        {
            out.push(format!("{field}: local {l}, Hive {a} applies"));
        }
    };
    let f = |v: Option<u32>| v.map(f64::from);
    cap(
        "context.max_context_tokens",
        f(local.max_context_tokens),
        f(floor.max_context_tokens),
    );
    cap(
        "egress.max_writes_per_min",
        f(local.egress.max_writes_per_min),
        f(floor.egress.max_writes_per_min),
    );
    cap(
        "budgets.max_cost_usd_per_person_per_day",
        local.budgets.max_cost_usd_per_person_per_day,
        floor.budgets.max_cost_usd_per_person_per_day,
    );
    cap(
        "budgets.max_cost_usd_per_project_per_month",
        local.budgets.max_cost_usd_per_project_per_month,
        floor.budgets.max_cost_usd_per_project_per_month,
    );
    cap(
        "budgets.max_requests_per_minute_per_person",
        f(local.budgets.max_requests_per_minute_per_person),
        f(floor.budgets.max_requests_per_minute_per_person),
    );
    for (field, l, a) in [
        ("filters.pii", &local.filters.pii, &floor.filters.pii),
        (
            "filters.classification",
            &local.filters.classification,
            &floor.filters.classification,
        ),
        (
            "filters.injection",
            &local.filters.injection,
            &floor.filters.injection,
        ),
    ] {
        if let (Some(lr), Some(ar)) = (rank(l.as_ref()), rank(a.as_ref()))
            && lr < ar
        {
            out.push(format!(
                "{field}: local {}, Hive {} applies",
                l.as_deref().unwrap_or_default(),
                a.as_deref().unwrap_or_default()
            ));
        }
    }
    if local.egress.block_secrets == Some(false) && floor.egress.block_secrets == Some(true) {
        out.push("egress.block_secrets: local false, Hive true applies".into());
    }
    if let (Some(l), Some(a)) = (&local.default_read_mode, &floor.default_read_mode)
        && l != a
    {
        out.push(format!(
            "context.default_read_mode: local {l}, Hive {a} applies"
        ));
    }
    if let Some(local_allow) = &local.allow_tools {
        let extra: Vec<&str> = local_allow
            .iter()
            .filter(|t| {
                floor.deny_tools.contains(t)
                    || floor
                        .allow_tools
                        .as_ref()
                        .is_some_and(|allow| !allow.contains(t))
            })
            .map(String::as_str)
            .collect();
        if !extra.is_empty() {
            out.push(format!(
                "context.allow_tools: local allows {}, Hive does not",
                extra.join(", ")
            ));
        }
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    fn empty_floor() -> serde_json::Value {
        json!({
            "schema": FLOOR_SCHEMA, "version": 1,
            "digest": format!("sha256:{}", "a".repeat(64)),
            "not_after": "2099-01-01T00:00:00Z",
            "policy": {"effective": {
                "schema": "leanctx.hive.policy/v1",
                "security": {}, "context": {}, "sharing": {}
            }, "vaults": []}
        })
    }

    #[test]
    fn empty_floor_is_neutral_only_after_required_policy_validation() {
        let floor = empty_floor().to_string();
        let required = r#"{"version":1}"#;
        assert!(
            resolve(Some(&floor), Some(required), None)
                .unwrap()
                .is_none()
        );
        assert!(resolve(None, Some(required), None).is_err());
        assert!(resolve(Some("damaged"), Some(required), None).is_err());
        assert!(resolve(Some(&floor), Some(r#"{"version":2}"#), None).is_err());
    }

    #[test]
    fn selected_vault_and_unmapped_members_never_count_as_empty() {
        for (section, member, value) in [
            ("security", "pii", json!("redact")),
            ("context", "allowed_tools", json!([])),
            ("context", "allowed_sources", json!([])),
            ("security", "future_control", json!(true)),
        ] {
            let mut floor = empty_floor();
            let mut vault_policy = floor["policy"]["effective"].clone();
            vault_policy[section][member] = value;
            floor["policy"]["vaults"] = json!([{
                "vault_id": "restricted", "effective": vault_policy
            }]);
            let floor = floor.to_string();
            assert!(resolve(Some(&floor), None, None).unwrap().is_none());
            assert!(
                resolve(Some(&floor), None, Some("restricted"))
                    .unwrap()
                    .is_some(),
                "{section}.{member} must retain the selected vault's policy"
            );
        }
    }
}