blob: da24a96dbe068917c89d7d5578c0435f196a463a [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.
*/
//! Call ownership shared by native and JavaScript adapters, never a wire grant.
mod attachments;
mod resources;
pub use attachments::{Attachments, ReadResource, ResourceEncoding};
pub use resources::{Resources, Ticket};
use maka_agent_facts::event::Invocation;
use maka_agent_facts::tools::ToolError;
use serde::{Deserialize, Serialize};
use std::{any::Any, sync::Arc};
use tokio_util::sync::CancellationToken;
tokio::task_local! { static CURRENT: Scope; }
/// Available while polling an admitted plugin tool. Preparation and detached
/// tasks have no implicit authority; Service/Executor contexts pass it explicitly.
pub fn current() -> Option<Scope> {
CURRENT.try_with(Clone::clone).ok()
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(
tag = "kind",
rename_all = "snake_case",
rename_all_fields = "camelCase"
)]
pub enum Identity {
Agent {
invocation: Invocation,
operation_id: Option<String>,
},
Remote {
request_id: uuid::Uuid,
},
Background {
grant: crate::authorization::Id,
},
}
impl Identity {
pub fn agent(&self) -> Option<&Invocation> {
match self {
Self::Agent { invocation, .. } => Some(invocation),
_ => None,
}
}
pub fn operation_id(&self) -> Option<&str> {
match self {
Self::Agent { operation_id, .. } => operation_id.as_deref(),
_ => None,
}
}
}
/// Host dispatch creates the initial scope after authorization. Identity is
/// observation, not a grant: resource services must still validate admission.
/// Forwarding keeps revocation but owns a distinct resource settlement group.
#[derive(Clone)]
pub struct Scope {
source: Arc<()>,
// The embedding owns its authorization evidence. It is neither part of the
// public observation nor serializable into a plugin-supplied wire grant.
evidence: Option<Arc<dyn Any + Send + Sync>>,
context: Metadata,
}
/// Read-only through Scope; observation cannot be rewritten into authority.
#[derive(Clone)]
pub struct Metadata {
pub identity: Identity,
pub cancellation: CancellationToken,
pub resources: Arc<Resources>,
}
impl std::ops::Deref for Scope {
type Target = Metadata;
fn deref(&self) -> &Metadata {
&self.context
}
}
/// Embedding dispatch owns an issuer. A different embedding can create its own
/// issuer but cannot mint scopes accepted by this one's resource adapters.
pub type Evidence = Arc<dyn Any + Send + Sync>;
/// Capture the embedding's authority once at call entry. Forwarded calls retain
/// this evidence; resource adapters still recheck revocation before effects.
pub trait Admission: Send + Sync {
fn authorize<'a>(
&'a self,
identity: &'a Identity,
) -> futures_util::future::BoxFuture<'a, Result<Evidence, ToolError>>;
}
#[derive(Clone, Default)]
pub struct Issuer {
source: Arc<()>,
admission: Option<Arc<dyn Admission>>,
}
impl Issuer {
pub fn with_admission(admission: Arc<dyn Admission>) -> Self {
Self {
admission: Some(admission),
..Self::default()
}
}
pub async fn admit(
&self,
identity: Identity,
cancellation: CancellationToken,
) -> Result<Scope, ToolError> {
let evidence = match &self.admission {
Some(admission) => Some(tokio::select! {
biased;
_ = cancellation.cancelled() => return Err(ToolError::Failed("plugin invocation is closed".into())),
evidence = admission.authorize(&identity) => evidence?,
}),
None => None,
};
Scope::new(self.source.clone(), identity, evidence, cancellation)
}
pub async fn run<T>(
&self,
identity: Identity,
cancellation: CancellationToken,
operation: impl Future<Output = Result<T, ToolError>>,
) -> Result<T, ToolError> {
let scope = self.admit(identity, cancellation).await?;
let _closed = scope.cancellation.clone().drop_guard();
let result = CURRENT.scope(scope.clone(), operation).await;
scope.finish().await?;
result
}
pub fn issue(
&self,
identity: Identity,
cancellation: CancellationToken,
) -> Result<Scope, ToolError> {
Scope::new(self.source.clone(), identity, None, cancellation)
}
pub fn owns(&self, scope: &Scope) -> bool {
Arc::ptr_eq(&self.source, &scope.source)
}
pub fn issue_with<T: Any + Send + Sync>(
&self,
identity: Identity,
evidence: T,
cancellation: CancellationToken,
) -> Result<Scope, ToolError> {
Scope::new(
self.source.clone(),
identity,
Some(Arc::new(evidence)),
cancellation,
)
}
/// Only the issuing embedding can inspect its private evidence type.
pub fn evidence<'a, T: Any + Send + Sync>(&self, scope: &'a Scope) -> Option<&'a T> {
self.owns(scope).then_some(())?;
scope.evidence.as_ref()?.downcast_ref()
}
}
impl Scope {
fn new(
source: Arc<()>,
identity: Identity,
evidence: Option<Arc<dyn Any + Send + Sync>>,
cancellation: CancellationToken,
) -> Result<Self, ToolError> {
if cancellation.is_cancelled() {
return Err(ToolError::Failed("plugin invocation is closed".into()));
}
Ok(Self {
source,
evidence,
context: Metadata {
identity,
cancellation: cancellation.child_token(),
resources: Arc::default(),
},
})
}
pub fn child(&self) -> Result<Self, ToolError> {
Self::new(
self.source.clone(),
self.identity.clone(),
self.evidence.clone(),
self.cancellation.clone(),
)
}
pub async fn finish(&self) -> Result<(), ToolError> {
self.cancellation.cancel();
self.resources.finish().await
}
}
/// The caller owns this lifetime; forwarding a Scope does not transfer it.
/// Drop requests cancellation. `finish` additionally confirms resource cleanup.
pub struct Owned(Scope);
impl Owned {
pub fn new(scope: Scope) -> Self {
Self(scope)
}
pub fn scope(&self) -> Scope {
self.0.clone()
}
pub async fn finish(self) -> Result<(), ToolError> {
self.0.finish().await
}
}
impl Drop for Owned {
fn drop(&mut self) {
self.0.cancellation.cancel();
}
}