Skip to content

firma-core (root module)

Modules

  • action_class - Canonical action-class identifiers.
  • capability_seed - Canonical on-disk capability seed schema.
  • cedar - Cedar entity UID types and shared schema for Firma policy evaluation.
  • connector - Connector trait and shared types for outbound request dispatch.
  • credential - Credential bundle types shared between the sidecar and connector
  • decision
  • envelope
  • policy
  • run_audit - Wire contract for the firma run → Sidecar audit channel.
  • token - Capability token types and signing/verification traits.
  • transport - Read-only transport view consumed by connector implementations.

Canonical action-class identifiers.

Every intent.action_class in an ExecutionEnvelope, every capability token action_set entry, and every Cedar action UID resolves to one of these identifiers. The wire/config/PASETO surfaces are all string-based, so [ActionClass] exists to give callers that construct those strings a single, typo-proof source of truth via [ActionClass::as_str].

The set mirrors the sidecar’s runtime ActionClassRegistry; a drift test there asserts the two stay in lockstep.

Canonical on-disk capability seed schema.

Cedar entity UID types and shared schema for Firma policy evaluation.

Encodes the entity roles used in Firma policy evaluation and produces the Cedar entity UID via [TryFrom] using Cedar’s typed builder API. Both the Authority (issuance) and the Sidecar (enforcement) must use identical UID formats — keeping this type in firma-core makes that contract explicit.

VariantCedar type nameID source
AgentFirma::Agent[AgentId]
ActionFirma::Actionnormalizer string
ResourceFirma::Resourcenormalizer string

[FirmaEntityUid] uses [EntityUid::from_type_name_and_id] rather than string interpolation. [EntityId] stores the id opaquely and never re-parses it as Cedar syntax, so special characters in the id cannot break out of the entity binding. [AgentId] additionally enforces [a-zA-Z0-9_-]{1,128} at construction time as a second defence layer.

Connector trait and shared types for outbound request dispatch.

The connector is the layer that runs after enforcement and credential injection have produced an authorised TransportView. Its job is precise and bounded by FEP §6.2: translate the envelope into the target wire format, apply technical constraints (rate limit, timeout, connection pool), merge injected credentials into the outbound request, and return the target response.

The connector layer is not an enforcement layer. By the time a request reaches it, Stage 1 (capability validation), Stage 2 (constraint enforcement), and credential injection have all run. Implementations must not:

  • make authorization decisions,
  • modify intent or capability fields on the envelope,
  • implement business logic,
  • source credentials independently of the TransportView.

Implementations may:

  • translate the envelope into the target wire format,
  • apply technical constraints (rate limit, timeout, connection pool),
  • read credentials from the TransportView and merge them into the outbound request,
  • enrich audit metadata with outcome information returned via [ConnectorResponse].

The trait and its associated types live in firma-core so that external crates can implement specialized connectors (LLM providers, databases, tool APIs) without pulling in the sidecar binary. The sidecar owns the registry and the generic HTTP implementation; specialized connectors plug in as host overrides.

A minimal connector that echoes the request body back as the response body:

use std::collections::HashMap;
use std::time::Duration;
use async_trait::async_trait;
use firma_core::{
ActionParams, Connector, ConnectorError, ConnectorResponse, TransportView,
};
use firma_http::HeaderMap;
struct EchoConnector;
#[async_trait]
impl Connector for EchoConnector {
async fn dispatch(
&self,
view: &TransportView,
) -> Result<ConnectorResponse, ConnectorError> {
let body = match &view.envelope().intent().params {
ActionParams::Http(http) => http.body.clone().unwrap_or_default(),
_ => Vec::new(),
};
let response_size = body.len();
Ok(ConnectorResponse {
status: 200,
headers: HeaderMap::new(),
body,
dispatch_latency: Duration::from_millis(0),
response_size,
})
}
}

Credential bundle types shared between the sidecar and connector implementations.

The sidecar runs credential injection after enforcement passes and produces an [InjectedCredentials] value. That value is handed to connectors (in-tree or out-of-tree) through a TransportView so they can merge the injected headers into the outbound request.

Only the read-only bundle lives here. The CredentialInjector trait and its implementations stay inside firma-sidecar because they are consumed only by the enforcement pipeline.

Wire contract for the firma run → Sidecar audit channel.

Some enforcement happens outside the Sidecar’s request pipeline and so never flows through an [crate::envelope::ExecutionEnvelope]. The canonical example is a wrapped agent’s direct loopback connection: it bypasses HTTP_PROXY, so the firma run egress guard blocks it at the sandbox boundary and reports the fact to the Sidecar after the event. The Sidecar turns each report into a signed audit event so it surfaces in firma monitor alongside hot-path decisions.

This module holds only the wire types shared by the producer (firma-run) and the consumer (firma-sidecar). It is intentionally semantics-free: a [RunAuditEvent] states an observation, and the Sidecar — which signs the audit log — owns the mapping from observation to (action_class, decision, deny_reason, resource). That split keeps the signature meaningful: the reporter can never assert an arbitrary audit record.

Capability token types and signing/verification traits.

Read-only transport view consumed by connector implementations.

The sidecar assembles a TransportView after enforcement passes and credential injection completes. The view pairs an approved [ExecutionEnvelope] with its [InjectedCredentials] and is the single value handed to the Connector layer at dispatch time.

Neither the envelope nor the credentials can be mutated through this view. This is intentional: by the time a connector runs, Stage 1, Stage 2, and credential injection have all executed, and the connector’s role is limited to protocol translation and dispatch (FEP §6.2).