lean_ctx/core/engine_interface/context_plan/
presenter.rs1use std::path::Path;
16
17use lean_ctx_protocol::{
18 EngineContextSourceMaterializationRequestV2, EngineContextSourceMaterializationResponseV2,
19 EnginePresenterGrantV1, EnginePresenterRequestV1, ProtocolReference,
20};
21
22use crate::core::archive::presented::{self, PresenterCapability};
23
24pub const CAPABILITY_REFUSED: &str = "presenter_capability_refused";
28
29pub fn materialize_v2(
39 root: &Path,
40 request: &EngineContextSourceMaterializationRequestV2,
41 shown: Option<PresenterCapability>,
42) -> Result<EngineContextSourceMaterializationResponseV2, &'static str> {
43 request
44 .validate_payload()
45 .map_err(|_| "invalid_source_materialization_request")?;
46 let (capability, expires_at, fresh) = match (request.presenter, shown) {
47 (EnginePresenterRequestV1::Issue { ttl_seconds }, None) => {
48 let issued = presented::issue(ttl_seconds).ok_or(CAPABILITY_REFUSED)?;
49 (issued.capability, issued.expires_at, true)
50 }
51 (EnginePresenterRequestV1::Renew, Some(capability)) => {
52 let expires_at =
53 presented::issued_lifetime_end(&capability).ok_or(CAPABILITY_REFUSED)?;
54 (capability, expires_at, false)
55 }
56 _ => return Err(CAPABILITY_REFUSED),
57 };
58 let response = presented::with_presenter(Some(capability.clone()), || {
59 super::materialize(root, &request.materialization)
60 })
61 .and_then(|materialization| {
62 let response = EngineContextSourceMaterializationResponseV2 {
63 schema_version: 2,
64 materialization,
65 presenter: EnginePresenterGrantV1 {
66 presenter_ref: ProtocolReference::new(capability.reference())
67 .map_err(|_| "internal")?,
68 expires_at_unix: u64::try_from(expires_at).map_err(|_| "internal")?,
69 capability: fresh.then(|| String::clone(&capability.expose())),
70 },
71 };
72 response
73 .validate()
74 .map_err(|_| "source_materialization_invalid")?;
75 Ok(response)
76 });
77 if response.is_err() && fresh {
78 presented::revoke(&capability);
80 }
81 response
82}