blob: 79d1afccfba555b8c52b2e67b53bafbf29d8b330 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Canonical permission decisions shared by native workspace executors.
use crate::interaction_wait::{wait, withdraw};
use maka_agent_api::session::SessionConfiguration;
use maka_agent_facts::interaction::InteractionOutcome;
use maka_agent_facts::interaction::InteractionRecord;
use maka_agent_facts::interaction::InteractionRequest;
use maka_agent_facts::interaction::PermissionRequest;
use maka_agent_facts::tools::ToolCallContext;
use maka_agent_facts::tools::ToolError;
use maka_event_log::{EventLog, interactions::PermissionGrant};
use maka_sandbox::grant::Decision;
use sha2::{Digest, Sha256};
use tokio_util::sync::CancellationToken;
pub async fn request(
store: &EventLog,
configuration: &SessionConfiguration,
request: PermissionRequest,
context: &ToolCallContext,
cancellation: &CancellationToken,
) -> Result<PermissionGrant, ToolError> {
if !configuration.permissions.request_permissions {
return Err(failed(
"Workspace policy does not allow requesting additional permissions",
));
}
let record = InteractionRecord {
session_id: context.invocation.session_id.clone(),
turn_id: context.invocation.turn_id.clone(),
run_id: context.invocation.run_id.clone(),
request_id: format!(
"permissions_{}",
hex::encode(Sha256::digest(
serde_json::to_vec(&(
&context.invocation,
&context.operation_id,
configuration.boundary_revision,
&request,
))
.expect("tool identity")
))
),
created_at: crate::now(),
request: InteractionRequest::Permissions {
tool_use_id: Some(context.tool_use_id()),
base_revision: configuration.boundary_revision,
request,
},
outcome: None,
};
let observed = async {
let committed = store
.establish_run_interaction(&context.invocation, &record)
.await
.map_err(|_| "permission publication unavailable")?;
if !committed.matches {
return Err("permission identity conflict");
}
wait(store, &record.request_id, cancellation).await
}
.await;
let outcome = match observed {
Ok(outcome) => outcome,
Err(error) => match withdraw(store, &record).await {
Ok(Some(outcome)) => outcome,
Ok(None) => return Err(failed(error)),
Err(cleanup) => return Err(ToolError::CleanupUnconfirmed(cleanup.into())),
},
};
if cancellation.is_cancelled() {
return Err(failed("Permission request cancelled"));
}
if !matches!(
outcome,
InteractionOutcome::PermissionsDecision {
decision: Decision::Allow { .. },
..
}
) {
return Err(failed("Additional permissions were not granted"));
}
store
.permission_grants(
&context.invocation,
Some(&context.tool_use_id()),
configuration.boundary_revision,
)
.await
.map_err(|_| failed("Permission observation unavailable"))?
.into_iter()
.find(|grant| grant.request_id == record.request_id)
.ok_or_else(|| failed("Approved permissions are no longer current"))
}
fn failed(message: impl Into<String>) -> ToolError {
ToolError::Failed(message.into())
}
pub async fn live(
store: &EventLog,
invocation: &maka_agent_facts::event::Invocation,
) -> Result<SessionConfiguration, ToolError> {
let projection = store
.session_projection::<SessionConfiguration>(&invocation.session_id)
.await
.map_err(|_| failed("Workspace session is unavailable"))?
.filter(|p| {
!p.session.archived
&& p.root_turn.as_ref().is_some_and(|turn| {
turn.invocation == *invocation
&& !matches!(
turn.state,
maka_event_log::turns::InvocationState::Ended { .. }
)
})
})
.ok_or_else(|| failed("Workspace invocation is no longer active"))?;
Ok(projection.session.configuration)
}