Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Development Patterns and Workflows

This document covers practical patterns and workflows for developing Aura systems.

Effects vs Coordination

A critical distinction guides where code belongs in the architecture.

Single-Party Operations

aura-effects implements single-party operations that are stateless and context-free. Each operation takes input and produces output without maintaining state or coordinating with other handlers.

Examples:

  • sign(key, msg) → Signature - One device, one cryptographic operation
  • store_chunk(id, data) → Ok(()) - One device, one write
  • RealCryptoHandler - Self-contained cryptographic operations
  • MockNetworkHandler - Simulated peer communication for testing

Single-party operations are reusable in any context. They work in unit tests, integration tests, and production equally well.

Multi-Party Coordination

aura-protocol implements multi-party coordination where multiple handlers orchestrate together. Operations are stateful and context-specific.

Examples:

  • execute_anti_entropy(...) - Orchestrates sync across multiple parties
  • CrdtCoordinator - Manages state of multiple CRDT handlers
  • GuardChain - Coordinates authorization checks across sequential operations

Multi-party coordination requires a context. It assumes multiple handlers are involved and state is maintained across operations.

The distinction is critical for understanding where code belongs. Single-party operations go in aura-effects. Multi-party coordination goes in aura-protocol.

Code Location Decision Matrix

Use these questions to classify code and determine the correct crate.

PatternAnswerLocation
Implements single effect trait methodStateless and single operationaura-effects
Coordinates multiple effects or handlersStateful and multi-handleraura-protocol
Multi-party coordination logicDistributed state and orchestrationaura-protocol
Domain-specific types and semanticsPure logic without handlersDomain crate or aura-mpst
Complete reusable protocolEnd-to-end without UIFeature/protocol crate
Assembles handlers and protocolsRuntime compositionaura-agent or aura-simulator
User-facing applicationHas main() entry pointaura-terminal or app-*

Boundary Questions for Edge Cases

Is it stateless or stateful?

Stateless and single operation go in aura-effects. Stateful and coordinating go in aura-protocol.

Does it work for one party or multiple?

Single-party code goes in aura-effects. Multi-party code goes in aura-protocol.

Is it context-free or context-specific?

Context-free code (works anywhere) goes in aura-effects. Context-specific code (requires orchestration) goes in aura-protocol.

Does it coordinate multiple handlers?

No coordination goes in aura-effects. Multiple handlers being orchestrated goes in aura-protocol.

Typical Workflows

Adding a New Cryptographic Primitive

  1. Define the type in aura-core crypto module
  2. Implement aura-core traits for the type's semantics
  3. Add a single-operation handler in aura-effects that implements the primitive
  4. Use the handler in feature crates or protocols through the effect system

Adding a New Distributed Protocol

  1. Write the choreography in aura-mpst using session types or DSL syntax with aura-macros
  2. Use annotation syntax for security: Role[guard_capability = "...", flow_cost = N] -> Target: Message
  3. Create the protocol implementation in aura-protocol or a feature crate
  4. Implement the coordination logic using handlers from aura-effects
  5. Wire the protocol into aura-agent runtime with appropriate leakage budget policies
  6. Expose the protocol through CLI or application interfaces

Writing a New Test

  1. Create test fixtures in aura-testkit
  2. Use mock handlers from aura-effects for reproducibility
  3. Configure appropriate leakage budget policies for the test scenarios
  4. Drive the agent from the test harness
  5. Compose protocols using aura-simulator for deterministic execution

Type Consolidation and Single Source of Truth

ProtocolType

The canonical definition of ProtocolType lives in aura-core. All other crates re-export and use this canonical definition.

Variants:

  • Dkd - Deterministic Key Derivation
  • Counter - Counter reservation protocol
  • Resharing - Key resharing for threshold updates
  • Locking - Resource locking protocol
  • Recovery - Account recovery protocol
  • Compaction - Ledger compaction protocol

Usage is consistent across aura-protocol and aura-simulator.

SessionStatus

The canonical definition of SessionStatus lives in aura-core. Variants represent the session lifecycle.

Lifecycle order:

  1. Initializing - Session initializing before execution
  2. Active - Session currently executing
  3. Waiting - Session waiting for participant responses
  4. Completed - Session completed successfully
  5. Failed - Session failed with error
  6. Expired - Session expired due to timeout
  7. TimedOut - Session timed out during execution
  8. Cancelled - Session was cancelled

All crates that track session state use the canonical definition from aura-core.

Capability System Layering

The capability system intentionally uses multiple architectural layers. Each layer serves legitimate purposes.

  • Canonical types in aura-core provide lightweight references
  • Authorization layer (aura-wot) adds policy enforcement features
  • Storage layer (aura-store) implements capability-based access control

Clear conversion paths enable inter-layer communication without confusion.

Effect Handler Patterns

Stateless Handler Pattern

Effect handlers follow a consistent pattern. Each handler implements one or more effect traits from aura-core.

A handler is stateless. It receives input, performs a single operation, and returns output. No state is maintained between calls.

Example:

#![allow(unused)]
fn main() {
pub struct RealCryptoHandler;

impl CryptoEffects for RealCryptoHandler {
    async fn sign(&self, key: &SecretKey, msg: &[u8]) -> Signature {
        // Single operation: sign the message
    }
    
    async fn verify(&self, key: &PublicKey, msg: &[u8], sig: &Signature) -> bool {
        // Single operation: verify the signature
    }
}
}

Mock handlers follow the same pattern but use deterministic or simulated implementations:

#![allow(unused)]
fn main() {
pub struct MockCryptoHandler;

impl CryptoEffects for MockCryptoHandler {
    async fn sign(&self, key: &SecretKey, msg: &[u8]) -> Signature {
        // Deterministic mock signature for testing
    }
    
    async fn verify(&self, key: &PublicKey, msg: &[u8], sig: &Signature) -> bool {
        // Always returns true in testing mode
    }
}
}

Multi-Party Coordination Pattern

Coordination logic in aura-protocol manages multiple handlers working together.

Coordination functions are async and stateful. They orchestrate handlers to accomplish multi-party goals.

Example:

#![allow(unused)]
fn main() {
pub async fn execute_anti_entropy(
    coordinator: CrdtCoordinator,      // Coordinates multiple CRDT handlers
    adapter: AuraHandlerAdapter,       // Coordinates choreography and effects
    guards: GuardChain,                // Coordinates authorization
) -> Result<SyncResult> {
    // Orchestrates distributed sync across parties
}
}

Coordination functions typically accept multiple handlers or a composed system. They maintain state across multiple operations. They coordinate between different concerns like authorization, storage, and transport. They return results that depend on the combined state.

Guard Chain Execution Pattern

The guard chain coordinates authorization, flow budgets, and journal effects in strict sequence. Guards themselves are pure: evaluation runs synchronously over a prepared GuardSnapshot and yields EffectCommand items that an async interpreter executes. This keeps guard logic deterministic and prevents observable side effects from failed authorization attempts.

#![allow(unused)]
fn main() {
async fn send_storage_put(
    bridge: &BiscuitAuthorizationBridge,
    guards: &GuardChain,
    interpreter: &dyn EffectInterpreter,
    ctx: ContextId,
    peer: AuthorityId,
    token: Biscuit,
    payload: PutRequest,
) -> Result<()> {
    // Phase 1: Authorization via Biscuit + policy (async, cached)
    let auth_result = bridge.authorize(&token, "storage_write", &payload.scope())?;
    if !auth_result.authorized {
        return Err(AuraError::permission_denied("Token authorization failed"));
    }

    // Phase 2: Prepare snapshot (async) and evaluate guards (sync)
    let snapshot = prepare_guard_snapshot(ctx, peer, &auth_result.cap_frontier).await?;
    let outcome = guards.evaluate(&snapshot, &payload.guard_request());
    if outcome.decision.is_denied() {
        return Err(AuraError::permission_denied("Guard evaluation denied"));
    }

    // Phase 3: Execute commands (async) - charge, record leakage, commit journal, send transport
    for cmd in outcome.effects {
        interpreter.exec(cmd).await?;
    }

    Ok(())
}
}

This pattern implements the guard chain guarantee: snapshot preparation happens before synchronous guard evaluation, and no transport observable occurs until the interpreter executes the resulting commands in order.

Security-First Design Philosophy

Privacy Budget Enforcement

The leakage tracking system implements security by default with backward compatibility.

#![allow(unused)]
fn main() {
// Secure by default - denies undefined budgets
let tracker = LeakageTracker::new(); // UndefinedBudgetPolicy::Deny

// Legacy compatibility mode
let tracker = LeakageTracker::legacy_permissive(); // UndefinedBudgetPolicy::Allow

// Configurable policy
let tracker = LeakageTracker::with_undefined_policy(
    UndefinedBudgetPolicy::DefaultBudget(1000)
);
}

The default policy is to deny access to undefined budgets. This prevents accidental privacy violations. Legacy mode is available for existing code that needs to operate without strict budget enforcement.

Annotation Parsing

Robust syn-based validation prevents malformed choreographies from compiling. Proper error messages guide developers toward secure patterns. All placeholders have been replaced with complete implementations for deployment readiness.

The choreography compiler validates annotations at compile time. Invalid or missing annotations are rejected with helpful error messages that explain the requirement.

Creating a New Domain Service

Domain crates define stateless handlers that take effect references per-call. The agent layer wraps these with services that manage RwLock access.

Step 1: Create the Domain Handler

In the domain crate (e.g., aura-chat/src/service.rs):

#![allow(unused)]
fn main() {
/// Stateless handler - takes effect reference per-call
pub struct MyHandler;

impl MyHandler {
    pub fn new() -> Self { Self }

    pub async fn my_operation<E>(
        &self,
        effects: &E,  // <-- Per-call reference
        param: SomeType,
    ) -> Result<Output>
    where
        E: StorageEffects + RandomEffects + PhysicalTimeEffects
    {
        // Use effects for side effects
        let uuid = effects.random_uuid().await;
        // ... domain logic
    }
}
}

Step 2: Create the Agent Service Wrapper

In aura-agent/src/handlers/my_service.rs:

#![allow(unused)]
fn main() {
pub struct MyService {
    handler: MyHandler,
    effects: Arc<RwLock<AuraEffectSystem>>,
}

impl MyService {
    pub fn new(effects: Arc<RwLock<AuraEffectSystem>>) -> Self {
        Self {
            handler: MyHandler::new(),
            effects,
        }
    }

    pub async fn my_operation(&self, param: SomeType) -> AgentResult<Output> {
        let effects = self.effects.read().await;  // <-- Acquire lock
        self.handler
            .my_operation(&*effects, param)
            .await
            .map_err(Into::into)
    }
}
}

Step 3: Expose via Agent API

In aura-agent/src/core/api.rs:

#![allow(unused)]
fn main() {
impl AuraAgent {
    pub fn my_service(&self) -> MyService {
        MyService::new(self.runtime.effects())
    }
}
}

Benefits

  • Domain crate stays pure: No tokio/RwLock dependency
  • Testable: Pass mock effects directly in unit tests
  • Consistent: Same pattern across all domain crates
  • Safe: RwLock managed automatically at agent layer

See docs/106_effect_system_and_runtime.md section 13 for more details.

Implementing Aura in a New Environment

When building an Aura application for a new platform (mobile, web, embedded, or custom infrastructure), use the AgentBuilder API to assemble the runtime with appropriate effect handlers.

Choosing a Builder Strategy

Aura provides three paths for creating agents:

StrategyUse CaseCompile-Time Safety
Platform presetStandard platforms (CLI, iOS, Android, Web)Configuration validation
Custom presetFull control over all effectsTypestate enforcement
Effect overridesPreset with specific customizationsMixed

Using Platform Presets

Platform presets provide sensible defaults for common environments.

CLI Preset

The CLI preset is the simplest path for terminal applications:

#![allow(unused)]
fn main() {
use aura_agent::AgentBuilder;

let agent = AgentBuilder::cli()
    .data_dir("~/.aura")
    .testing_mode()
    .build()
    .await?;
}

The CLI preset wires:

  • RealCryptoHandler for cryptographic operations
  • FilesystemStorageHandler for persistent storage
  • PhysicalTimeHandler for wall-clock time
  • RealRandomHandler for secure randomness
  • RealConsoleHandler for terminal output
  • TcpTransportHandler for network transport

Mobile Presets

Mobile presets require platform-specific feature flags:

#![allow(unused)]
fn main() {
// iOS (requires --features ios)
let agent = AgentBuilder::ios()
    .app_group("group.com.example.aura")
    .keychain_access_group("com.example.aura")
    .data_protection(DataProtectionClass::CompleteProtection)
    .build()
    .await?;

// Android (requires --features android)
let agent = AgentBuilder::android()
    .application_id("com.example.aura")
    .use_strongbox(true)
    .require_user_authentication(Some(300)) // 5 minutes
    .build()
    .await?;
}

Web Preset

The web preset targets browser environments:

#![allow(unused)]
fn main() {
// Web/WASM (requires --features web)
let agent = AgentBuilder::web()
    .storage_prefix("aura_")
    .use_session_storage(false)
    .build()
    .await?;
}

Custom Preset with Typestate

When you need explicit control over all effects, use the custom preset. The Rust type system enforces that all required effects are provided before build() is available.

#![allow(unused)]
fn main() {
use std::sync::Arc;
use aura_agent::AgentBuilder;
use aura_effects::{
    RealCryptoHandler, FilesystemStorageHandler,
    PhysicalTimeHandler, RealRandomHandler, RealConsoleHandler,
};

// All five required effects must be provided
let agent = AgentBuilder::custom()
    .with_crypto(Arc::new(RealCryptoHandler::new()))
    .with_storage(Arc::new(FilesystemStorageHandler::new("~/.aura".into())))
    .with_time(Arc::new(PhysicalTimeHandler::new()))
    .with_random(Arc::new(RealRandomHandler::new()))
    .with_console(Arc::new(RealConsoleHandler::new()))
    .testing_mode()
    .build()
    .await?;
}

Attempting to call build() without providing all required effects results in a compile error:

#![allow(unused)]
fn main() {
// This will not compile - missing effects
let agent = AgentBuilder::custom()
    .with_crypto(Arc::new(RealCryptoHandler::new()))
    .build()  // Error: method not found for this type
    .await?;
}

Required vs Optional Effects

Core Required Effects

Every agent requires these five effects:

EffectPurposeTrait
CryptoSigning, verification, encryptionCryptoEffects
StoragePersistent data storageStorageEffects
TimeWall-clock timestampsPhysicalTimeEffects
RandomCryptographically secure randomnessRandomEffects
ConsoleLogging and outputConsoleEffects

Optional Effects

These effects have defaults or are derived from required effects:

EffectDefault Behavior
TransportEffectsTCP transport (can be customized)
LogicalClockEffectsDerived from storage
OrderClockEffectsDerived from random
ReactiveEffectsDefault reactive handler
JournalEffectsDerived from storage + crypto
BiometricEffectsFallback no-op handler

Implementing Custom Effect Handlers

To support a new platform, implement the core effect traits:

#![allow(unused)]
fn main() {
use aura_core::effects::{CryptoEffects, Signature, SecretKey, PublicKey};

pub struct MyPlatformCrypto {
    // Platform-specific state
}

#[async_trait]
impl CryptoEffects for MyPlatformCrypto {
    async fn sign(&self, key: &SecretKey, msg: &[u8]) -> Result<Signature> {
        // Platform-specific signing implementation
    }

    async fn verify(&self, key: &PublicKey, msg: &[u8], sig: &Signature) -> Result<bool> {
        // Platform-specific verification
    }

    // ... other required methods
}
}

Then use it with the custom builder:

#![allow(unused)]
fn main() {
let agent = AgentBuilder::custom()
    .with_crypto(Arc::new(MyPlatformCrypto::new()))
    .with_storage(Arc::new(MyPlatformStorage::new()))
    .with_time(Arc::new(MyPlatformTime::new()))
    .with_random(Arc::new(MyPlatformRandom::new()))
    .with_console(Arc::new(MyPlatformConsole::new()))
    .build()
    .await?;
}

Testing Custom Implementations

Use mock handlers from aura-testkit for testing:

#![allow(unused)]
fn main() {
use aura_testkit::{MockCryptoHandler, MockStorageHandler};

#[tokio::test]
async fn test_custom_agent() {
    let agent = AgentBuilder::custom()
        .with_crypto(Arc::new(MockCryptoHandler::new()))
        .with_storage(Arc::new(MockStorageHandler::new()))
        .with_time(Arc::new(MockTimeHandler::new()))
        .with_random(Arc::new(MockRandomHandler::seeded(42)))
        .with_console(Arc::new(MockConsoleHandler::new()))
        .testing_mode()
        .build()
        .await
        .expect("Agent should build");

    // Test agent operations
}
}

Feature Flags

Platform-specific presets require feature flags:

[dependencies]
aura-agent = { version = "0.1", features = ["ios"] }
# or
aura-agent = { version = "0.1", features = ["android"] }
# or
aura-agent = { version = "0.1", features = ["web"] }

The default feature set includes CLI support. Multiple platform features can be enabled simultaneously for cross-platform codebases.

Platform Implementation Checklist

When implementing Aura for a new platform:

  • Identify platform-specific APIs for crypto, storage, time, random, and console
  • Implement the five core effect traits using platform APIs
  • Create a preset builder (optional but recommended)
  • Add feature flags for platform-specific dependencies
  • Write integration tests using mock handlers
  • Document platform-specific security considerations
  • Consider transport layer requirements (WebSocket, BLE, etc.)