> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/moeru-ai/airi/llms.txt
> Use this file to discover all available pages before exploring further.

# Event System

> Event routing, middleware, and policy-based access control

## Event Routing

The server runtime implements a flexible event routing system that determines how events are distributed to connected peers.

## Routing Flow

```mermaid theme={null}
flowchart TD
    A[Event Received] --> B{Authenticated?}
    B -->|No| C[Send Error]
    B -->|Yes| D{Bypass Route?}
    D -->|Yes & Devtools| E[Broadcast All]
    D -->|No| F[Collect Destinations]
    F --> G[Apply Middleware]
    G --> H{Decision?}
    H -->|Drop| I[Discard Event]
    H -->|Broadcast| E
    H -->|Targets| J[Send to Targets]
    H -->|None| K[Check Destinations]
    K -->|Has Destinations| L[Match Destinations]
    K -->|No Destinations| E
    L --> J
    J --> M[Event Delivered]
```

## Route Middleware

Middleware functions intercept events and make routing decisions.

### Middleware Interface

```typescript theme={null}
type RouteMiddleware = (context: RouteContext) => RouteDecision | void

interface RouteContext {
  event: WebSocketEvent
  fromPeer: AuthenticatedPeer
  peers: Map<string, AuthenticatedPeer>
  destinations?: Array<string | RouteTargetExpression>
}

type RouteDecision
  = | { type: 'drop' }           // Discard the event
    | { type: 'broadcast' }      // Send to all peers
    | { type: 'targets', targetIds: Set<string> }  // Send to specific peers
```

### Custom Middleware Example

```typescript theme={null}
import { setupApp, RouteMiddleware } from '@proj-airi/server-runtime'

// Only allow events from specific plugins
const pluginFilterMiddleware: RouteMiddleware = (context) => {
  const pluginId = context.fromPeer.identity?.plugin?.id
  
  if (!pluginId || !['allowed-plugin-1', 'allowed-plugin-2'].includes(pluginId)) {
    return { type: 'drop' }
  }
  
  // Return void to continue to next middleware
}

// Rate limiting middleware
const rateLimiters = new Map<string, { count: number, resetAt: number }>()

const rateLimitMiddleware: RouteMiddleware = (context) => {
  const peerId = context.fromPeer.peer.id
  const now = Date.now()
  const limit = rateLimiters.get(peerId)
  
  if (!limit || now > limit.resetAt) {
    rateLimiters.set(peerId, { count: 1, resetAt: now + 60000 })
    return
  }
  
  if (limit.count >= 100) {
    return { type: 'drop' }
  }
  
  limit.count++
}

const { app } = setupApp({
  routing: {
    middleware: [pluginFilterMiddleware, rateLimitMiddleware]
  }
})
```

## Routing Policies

Policies provide declarative access control without custom middleware code.

### Policy Configuration

```typescript theme={null}
interface RoutingPolicy {
  allowPlugins?: string[]    // Whitelist of plugin IDs
  denyPlugins?: string[]     // Blacklist of plugin IDs
  allowLabels?: string[]     // Required labels (any match)
  denyLabels?: string[]      // Forbidden labels (any match)
}
```

### Policy Examples

#### Plugin Whitelist

```typescript theme={null}
const { app } = setupApp({
  routing: {
    policy: {
      allowPlugins: ['core-module', 'trusted-plugin']
    }
  }
})
```

#### Label-Based Access Control

```typescript theme={null}
const { app } = setupApp({
  routing: {
    policy: {
      allowLabels: ['tier=premium', 'env=production'],
      denyLabels: ['deprecated=true']
    }
  }
})
```

#### Combined Policy

```typescript theme={null}
const { app } = setupApp({
  routing: {
    policy: {
      allowPlugins: ['core-module', 'ai-module'],
      denyPlugins: ['legacy-module'],
      allowLabels: ['tier=premium'],
      denyLabels: ['beta=true', 'deprecated=true']
    }
  }
})
```

## Route Destinations

Events can specify destinations using module names or label selectors.

### Destination Types

```typescript theme={null}
type RouteTargetExpression = {
  name?: string                    // Module name
  index?: number                   // Module instance index
  labels?: Record<string, string>  // Label selectors
}
```

### Specifying Destinations

#### By Module Name

```typescript theme={null}
client.send({
  type: 'custom:event',
  data: { message: 'Hello' },
  route: {
    destinations: ['ai-module', 'chat-module']
  }
})
```

#### By Module Instance

```typescript theme={null}
client.send({
  type: 'custom:event',
  data: { message: 'Hello' },
  route: {
    destinations: [
      { name: 'ai-module', index: 0 },
      { name: 'ai-module', index: 1 }
    ]
  }
})
```

#### By Labels

```typescript theme={null}
client.send({
  type: 'custom:event',
  data: { message: 'Hello' },
  route: {
    destinations: [
      { labels: { role: 'processor' } },
      { labels: { tier: 'premium', region: 'us-east' } }
    ]
  }
})
```

#### In Event Data

Destinations can also be specified in the data payload:

```typescript theme={null}
client.send({
  type: 'custom:event',
  data: {
    message: 'Hello',
    destinations: ['ai-module']
  }
})
```

## Bypass Mode

Devtools peers can bypass routing policies for debugging and monitoring.

### Enabling Bypass

A peer is considered a devtools peer if:

* It has the label `devtools=true` or `devtools=1`, OR
* Its module name contains "devtools"

```typescript theme={null}
// Module with devtools label
client.send({
  type: 'module:announce',
  data: {
    name: 'monitoring-tool',
    identity: {
      kind: 'plugin',
      plugin: { id: 'monitoring', labels: { devtools: 'true' } },
      id: 'monitor-1'
    }
  }
})

// Send event with bypass
client.send({
  type: 'monitor:observe',
  data: { target: 'all' },
  route: {
    bypass: true  // Only works for devtools peers
  }
})
```

### Disabling Bypass

```typescript theme={null}
const { app } = setupApp({
  routing: {
    allowBypass: false  // Disable bypass even for devtools
  }
})
```

## Label Matching

Label selectors support exact matching with AND logic.

```typescript theme={null}
// Peer labels
const peerLabels = {
  tier: 'premium',
  region: 'us-east',
  env: 'production'
}

// Match: all selector labels must match peer labels
const selector1 = { tier: 'premium' }  // ✓ Matches
const selector2 = { tier: 'premium', region: 'us-east' }  // ✓ Matches
const selector3 = { tier: 'free' }  // ✗ No match
const selector4 = { tier: 'premium', region: 'eu-west' }  // ✗ No match
```

## Built-in Routing Rules

### Self-Exclusion

Events are never sent back to the originating peer:

```typescript theme={null}
// Peer A sends event
client.send({
  type: 'broadcast:message',
  data: { text: 'Hello' }
})

// Server sends to all peers EXCEPT Peer A
```

### Module Registry Access

The routing context provides access to the module registry:

```typescript theme={null}
const customMiddleware: RouteMiddleware = (context) => {
  const targetIds = new Set<string>()
  
  for (const [id, peer] of context.peers) {
    if (peer.name === 'ai-module') {
      targetIds.add(id)
    }
  }
  
  return { type: 'targets', targetIds }
}
```

## Performance Considerations

<AccordionGroup>
  <Accordion title="Minimize middleware chain">
    Keep middleware functions lightweight. Heavy computation or I/O operations will block event processing.
  </Accordion>

  <Accordion title="Use policies over middleware">
    Built-in policies are optimized for common access control patterns. Use custom middleware only when policies are insufficient.
  </Accordion>

  <Accordion title="Cache label lookups">
    If performing complex label matching, cache results within middleware to avoid repeated computation.
  </Accordion>

  <Accordion title="Consider event volume">
    High-frequency events benefit from explicit destinations rather than broadcast-then-filter approaches.
  </Accordion>
</AccordionGroup>

## Debugging Routing

Enable WebSocket logging to trace routing decisions:

```typescript theme={null}
const { app } = setupApp({
  logger: {
    websocket: {
      level: 'debug',
      format: 'pretty'
    }
  }
})
```

Log output includes:

* Received events with peer information
* Routing decisions (broadcast, targets, drop)
* Delivery status to each peer
* Connection health (heartbeat, timeout)

## Next Steps

<CardGroup cols={2}>
  <Card title="WebSocket Protocol" icon="network-wired" href="/api/server-runtime/websocket">
    Review the WebSocket message protocol
  </Card>

  <Card title="Server SDK" icon="code" href="/api/packages/server-sdk">
    Use the client SDK with built-in routing
  </Card>
</CardGroup>
