Forge documentation
Library referenceRust

forge-auth

Arsenal capability token integration for Forge agents — ANVIL Spec §8.7, §11.3

Arsenal capability token integration for Forge agents — ANVIL Spec §8.7, §11.3

Package contract

FieldValue
Languagerust
Source version0.2.0
Manifestforge-rs/crates/forge-auth/Cargo.toml
Source files7
EvidenceSource reference; registry publication and runtime conformance are separate checks

Import boundary

use forge_auth;

Use a source checkout or your verified private registry. Manifest coordinates identify the package; they do not establish that a public registry release exists.

Crate boundary

The following entries are taken from src/lib.rs. Feature conditions in the exact source still apply.

pub mod auth_context;

pub mod capability;

pub mod delegation;

pub mod error;

pub mod local_capability;

pub mod tool_auth;

pub mod prelude;

pub use crate::auth_context::AuthContext;

pub use crate::capability::{
        act_allows_proxy_variable, act_allows_scope, extract_scopes, verify_act,
    };

pub use crate::delegation::{delegate_capabilities, DelegationRequest};

pub use crate::error::{ForgeAuthError, ForgeAuthResult};

pub use crate::tool_auth::{
        authorize_proxy_tool_invocation, authorize_tool_invocation, ToolAuthorizationDecision,
        ToolAuthorizationRequest,
    };

Source reference

Download package reference JSON. Each original source file and generated declaration artifact has its own SHA-256 digest. Function bodies and constant values are omitted from downloads. These are source declaration inventories, not compiler-resolved rustdoc, TypeDoc, DocC, or Dokka output. Private modules can contain public declarations that are not reachable through the package boundary; consult the entry point before importing.

auth_context.rs

Read declaration text · 10 declaration entries

pub struct AuthContext {

}

pub fn new(identity: ForgeAgentIdentity, act: AgentCapabilityToken) -> Self;

pub fn agent_did(&self) -> &str;

pub fn identity(&self) -> &ForgeAgentIdentity;

pub fn act(&self) -> &AgentCapabilityToken;

pub fn lineage_depth(&self) -> u32;

pub fn sign(&self, message: &[u8]) -> Vec<u8>;

#[instrument(skip(self), fields(agent_did = %self.identity.did(), tool = %tool_name, tier = %tool_tier))]
pub fn authorize_tool(
        &self,
        tool_name: &str,
        tool_tier: ToolTier,
    ) -> ForgeAuthResult<ToolAuthorizationDecision>;

#[instrument(skip(self, variables), fields(agent_did = %self.identity.did(), tool = %tool_name))]
pub fn authorize_proxy_tool(
        &self,
        tool_name: &str,
        tool_tier: ToolTier,
        variables: &[String],
    ) -> ForgeAuthResult<ToolAuthorizationDecision>;

#[instrument(skip(self, requested_scopes), fields(parent_did = %self.identity.did(), child_name = %name))]
pub fn create_authorized_subagent(
        &self,
        name: &str,
        namespace: &str,
        requested_scopes: ScopeSet,
    ) -> ForgeAuthResult<AuthContext>;

capability.rs

Read declaration text · 5 declaration entries

#[instrument(skip(act), fields(token_id = %act.id(), agent_id = %act.subject()))]
pub fn verify_act(act: &AgentCapabilityToken) -> ForgeAuthResult<()>;

pub fn decode_and_check_ttl(act: &AgentCapabilityToken) -> ForgeAuthResult<()>;

pub fn extract_scopes(act: &AgentCapabilityToken) -> Vec<String>;

#[instrument(skip(act), fields(token_id = %act.id(), scope = %scope))]
pub fn act_allows_scope(act: &AgentCapabilityToken, scope: &str) -> ForgeAuthResult<bool>;

#[instrument(skip(act), fields(token_id = %act.id(), variable = %variable))]
pub fn act_allows_proxy_variable(
    act: &AgentCapabilityToken,
    variable: &str,
) -> ForgeAuthResult<bool>;

delegation.rs

Read declaration text · 2 declaration entries

#[derive(Debug)]
pub struct DelegationRequest<'a> {
/// The parent agent's Arsenal ACT.

pub parent_act: &'a AgentCapabilityToken,
/// The parent agent's OAS DID (for error messages).

pub parent_did: String,
/// The child agent's Arsenal identity.

pub child_agent_id: AgentId,
/// The child agent's OAS DID (for error messages).

pub child_did: String,
/// The scopes requested for the child agent.

///

/// Must be a subset of the parent's scopes.

pub requested_scopes: ScopeSet
}

#[instrument(
    skip(request),
    fields(
        parent_did = %request.parent_did,
        child_did = %request.child_did,
        requested_scope_count = request.requested_scopes.len()
    )
)]
pub fn delegate_capabilities(
    request: &DelegationRequest<'_>,
) -> ForgeAuthResult<AgentCapabilityToken>;

error.rs

Read declaration text · 12 declaration entries

pub type ForgeAuthResult<T> = Result<T, ForgeAuthError>;

#[derive(Debug, thiserror::Error)]
pub enum ForgeAuthError {
    /// The Arsenal ACT has expired and is no longer valid.
    ///
    /// Agents must obtain a fresh token before retrying the operation.
    ///
    /// # ANVIL Spec §8.7.1
    #[error(
        "ACT '{token_id}' for agent '{agent_did}' expired at {expired_at} — obtain a fresh token"
    )]
    TokenExpired {
        /// The unique identifier of the expired token.
        token_id: String,
        /// The DID of the agent that presented the token.
        agent_did: String,
        /// The timestamp at which the token expired (ISO 8601).
        expired_at: String,
    },

    /// The agent's ACT does not grant the required scope for the operation.
    ///
    /// The agent must obtain a token with the missing capability, or the
    /// operation must be re-scoped to match the agent's granted permissions.
    ///
    /// # ANVIL Spec §8.7.2
    #[error(
        "agent '{agent_did}' lacks required scope '{required_scope}' — available scopes: {available_scopes:?}"
    )]
    InsufficientScope {
        /// The DID of the agent whose token was checked.
        agent_did: String,
        /// The scope string that was required but not granted.
        required_scope: String,
        /// The scopes that the agent's ACT actually grants.
        available_scopes: Vec<String>,
    },

    /// A sub-agent delegation attempted to grant capabilities exceeding the parent's scope.
    ///
    /// Child agent capabilities must be a strict subset of the parent's. This is
    /// a security invariant that prevents privilege escalation via delegation.
    ///
    /// # ANVIL Spec §11.3.1
    #[error(
        "capability escalation denied: child '{child_did}' requested scope '{requested}' but parent '{parent_did}' only grants {available:?}"
    )]
    CapabilityEscalation {
        /// The DID of the parent agent whose ACT was being delegated.
        parent_did: String,
        /// The DID of the child agent that would receive the delegation.
        child_did: String,
        /// The scope string that was requested but exceeds the parent's grant.
        requested: String,
        /// The scopes available in the parent's ACT.
        available: Vec<String>,
    },

    /// Delegation was denied due to constraint violations in the parent's ACT.
    ///
    /// This covers cases where the parent's token does not permit delegation at all,
    /// or the target agent is not in the allowed delegates list, or the delegation
    /// depth has been exceeded.
    ///
    /// # ANVIL Spec §11.3.2
    #[error("delegation from parent '{parent_did}' to child '{child_did}' denied: {reason}")]
    DelegationDenied {
        /// The DID of the parent agent attempting to delegate.
        parent_did: String,
        /// The DID of the intended child agent.
        child_did: String,
        /// Human-readable reason for the denial.
        reason: String,
    },

    /// The ACT is structurally invalid or malformed.
    ///
    /// This indicates the token could not be validated even before checking
    /// scopes or time validity. The token may be corrupted, incorrectly
    /// constructed, or tampered with.
    ///
    /// # ANVIL Spec §8.7.3
    #[error("invalid Arsenal ACT: {reason}")]
    InvalidToken {
        /// Human-readable explanation of why the token is invalid.
        reason: String,
    },

    /// An error propagated from the underlying Arsenal SDK.
    ///
    /// This wraps errors from `arsenal-core` operations that do not map
    /// cleanly to a Forge-specific authorization error. Boxed to keep
    /// the overall enum size small.
    #[error("arsenal error: {0}")]
    Arsenal(Box<ArsenalError>),

    /// A proxy request was denied due to insufficient proxy scopes.
    #[error(
        "proxy access denied for variable '{variable}' — agent '{agent_did}' lacks proxy scope"
    )]
    ProxyDenied {
        /// The DID of the agent.
        agent_did: String,
        /// The variable name that was denied.
        variable: String,
    },

    /// A proxy request requires consent that has not been granted.
    #[error("consent required for agent '{agent_did}' to access variable '{variable}'")]
    ConsentRequired {
        /// The DID of the agent.
        agent_did: String,
        /// The variable name requiring consent.
        variable: String,
    },

    /// A fingerprint hash chain mismatch was detected.
    #[error("fingerprint mismatch for agent '{agent_did}' — possible key compromise")]
    FingerprintMismatch {
        /// The DID of the agent with the mismatched fingerprint.
        agent_did: String,
    },

    /// An identity operation failed during an auth workflow.
    ///
    /// This wraps errors from `forge-identity` operations (derivation, lineage)
    /// that occur during identity-aware auth flows like sub-agent creation.
    ///
    /// # ANVIL Spec §11.1
    #[error("identity error: {reason}")]
    IdentityError {
        /// Human-readable description of the identity failure.
        reason: String,
    },
}

#[must_use]
pub fn is_expired(&self) -> bool;

#[must_use]
pub fn is_insufficient_scope(&self) -> bool;

#[must_use]
pub fn is_capability_escalation(&self) -> bool;

#[must_use]
pub fn is_delegation_denied(&self) -> bool;

#[must_use]
pub fn is_invalid_token(&self) -> bool;

#[must_use]
pub fn is_proxy_denied(&self) -> bool;

#[must_use]
pub fn is_consent_required(&self) -> bool;

#[must_use]
pub fn is_fingerprint_mismatch(&self) -> bool;

#[must_use]
pub fn is_identity_error(&self) -> bool;

#[must_use]
pub fn error_code(&self) -> &'static str;

lib.rs

Read declaration text · 12 declaration entries

pub mod auth_context;

pub mod capability;

pub mod delegation;

pub mod error;

pub mod local_capability;

pub mod tool_auth;

pub mod prelude;

pub use crate::auth_context::AuthContext;

pub use crate::capability::{
        act_allows_proxy_variable, act_allows_scope, extract_scopes, verify_act,
    };

pub use crate::delegation::{delegate_capabilities, DelegationRequest};

pub use crate::error::{ForgeAuthError, ForgeAuthResult};

pub use crate::tool_auth::{
        authorize_proxy_tool_invocation, authorize_tool_invocation, ToolAuthorizationDecision,
        ToolAuthorizationRequest,
    };

local_capability.rs

Read declaration text · 11 declaration entries

pub const DEV_ISSUER: &str;

pub const DEV_AUDIENCE: &str;

pub const DEV_TOKEN_TTL_SECONDS: i64;

#[derive(Debug, Clone)]
pub struct LocalCapabilityFactory {

}

pub fn new(org_id: &str) -> Self;

pub fn with_ttl(org_id: &str, ttl_seconds: i64) -> Self;

pub fn create_dev_token(&self, agent_did: &str) -> AgentCapabilityToken;

pub fn create_scoped_dev_token(
        &self,
        agent_did: &str,
        scope_strs: &[&str],
    ) -> ForgeAuthResult<AgentCapabilityToken>;

pub fn issuer(&self) -> &str;

pub fn audience(&self) -> &str;

pub fn default_ttl_seconds(&self) -> i64;

tool_auth.rs

Read declaration text · 7 declaration entries

#[derive(Debug)]
pub struct ToolAuthorizationRequest<'a> {
/// The OAS DID of the agent requesting tool invocation.

pub agent_did: String,
/// The name of the tool being invoked.

pub tool_name: String,
/// The tier classification of the tool.

pub tool_tier: ToolTier,
/// The agent's Arsenal ACT, if available.

///

/// When `None`, the request is processed in legacy mode (no ACT checks).

pub act: Option<&'a AgentCapabilityToken>
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ToolAuthorizationDecision {
    /// The tool invocation is authorized.
    ///
    /// Returned for:
    /// - Tier 1 (Platform) tools — always allowed.
    /// - Tier 3 (Embedded) tools — always allowed.
    /// - Tier 2 (Host) tools — when the ACT grants the required scope.
    Allowed,

    /// The tool invocation is denied.
    ///
    /// Returned for Tier 2 (Host) tools when the ACT does not grant the
    /// required scope. The `reason` field provides an actionable explanation.
    Denied {
        /// Human-readable explanation of why the tool was denied.
        reason: String,
    },

    /// No ACT was provided — operating in legacy mode.
    ///
    /// Legacy mode allows tool execution without authorization. This mode
    /// exists for backward compatibility during the OAS integration migration.
    /// A `WARN`-level log is emitted when this mode is triggered.
    ///
    /// Legacy mode will be removed in a future major version.
    LegacyMode,
}

#[must_use]
pub fn is_allowed(&self) -> bool;

#[must_use]
pub fn is_denied(&self) -> bool;

#[must_use]
pub fn is_legacy_mode(&self) -> bool;

#[instrument(
    skip(request),
    fields(
        agent_did = %request.agent_did,
        tool = %request.tool_name,
        tier = %request.tool_tier,
        has_act = request.act.is_some()
    )
)]
pub fn authorize_tool_invocation(
    request: &ToolAuthorizationRequest<'_>,
) -> ForgeAuthResult<ToolAuthorizationDecision>;

#[instrument(
    skip(request, variables),
    fields(
        agent_did = %request.agent_did,
        tool = %request.tool_name,
        variable_count = variables.len()
    )
)]
pub fn authorize_proxy_tool_invocation(
    request: &ToolAuthorizationRequest<'_>,
    variables: &[String],
) -> ForgeAuthResult<ToolAuthorizationDecision>;

Continue

On this page