# MAPL Policy Guide
## A Tutorial for the MACAW Agentic Policy Language

**Version:** 3.0.1
**Last Updated:** January 27, 2026

---

## Table of Contents

1. [Introduction](#introduction)
2. [Part 1: Understanding MAPL](#part-1-understanding-mapl)
3. [Part 2: Your First MAPL Policy](#part-2-your-first-mapl-policy)
4. [Part 3: Policy Inheritance with extends](#part-3-policy-inheritance-with-extends)
5. [Part 4: Parameter Constraints](#part-4-parameter-constraints)
6. [Part 5: Attestations](#part-5-attestations)
7. [Part 6: Complete Tutorial Example](#part-6-complete-tutorial-example)
8. [Part 7: Best Practices](#part-7-best-practices)
9. [Part 8: Troubleshooting](#part-8-troubleshooting)
10. [Appendix A: Quick Reference](#appendix-a-quick-reference)
11. [Appendix B: Theoretical Foundations](#appendix-b-theoretical-foundations)
12. [Appendix C: Future Enhancements](#appendix-c-future-enhancements)

---

## Introduction

Welcome to the MAPL (MACAW Agentic Policy Language) tutorial! This guide will teach you how to write security policies for AI agent systems.

### What is MAPL?

MAPL is a declarative policy language designed specifically for agentic AI systems. Unlike traditional access control systems designed for static files and databases, MAPL handles the unique challenges of AI agents:

- **Agents that morph identities** and delegate capabilities
- **Systems that scale 10-100x** per user through agent multiplication
- **Dynamic workflows** where agents spawn sub-agents and invoke tools

### Why MAPL is Different

Traditional policy systems require **O(M×N) rules** for M principals and N resources. This explodes quickly:
- 1,000 users × 100 resources = 100,000 rules to manage ❌

MAPL uses **hierarchical composition** to achieve **O(log M + N) policies**:
- log(1,000) ≈ 3-4 organizational levels + 100 resource policies ✅

### Three Key Innovations

1. **Deferred Principal Binding**: Principals are resolved at runtime from authenticated context (JWT claims), not hardcoded in policies. Alice's role changes? No policy updates needed.

2. **Dual Perspective Enforcement**: Caller and resource policies are enforced independently, then composed via intersection. Even if the caller is compromised, the resource enforces its own limits.

3. **Provable Security**: MAPL policies compose via mathematical intersection semantics with formal guarantees:
   - **Monotonic Restriction**: Adding policies can only make things more restrictive, never less
   - **Transitive Denial**: If any policy denies something, it stays denied
   - **No Privilege Escalation**: Combining policies cannot grant denied permissions

### What You'll Learn

By the end of this tutorial, you'll be able to:
- Write MAPL policies for your organization
- Use inheritance to create hierarchical policies
- Specify parameter constraints for security and compliance
- Use attestations for multi-step workflows (including conditional attestations)
- Understand how policies compose and intersect
- Debug policy issues when things don't work as expected

Let's get started!

---
## Part 1: Understanding MAPL

### The Agentic Challenge

Traditional access control was designed for:
```
User → File System
  "Alice can READ /etc/passwd"
```

But AI agents are different:
```
User → Agent → Sub-Agent → Tool → LLM → Database
  Alice invokes AnalystAgent
    → AnalystAgent spawns DataAgent (as Alice's delegate)
      → DataAgent calls DatabaseTool
        → DatabaseTool queries SQL
          → Returns to Alice
```

This creates challenges:
- **Contextual identity**: The same agent represents different users at different times
- **Delegation chains**: Sub-agents inherit but restrict parent permissions
- **Scale**: 1 user might spawn 100 agents, creating combinatorial explosion

### MAPL's Solution

Instead of writing rules like:
```
❌ alice CAN invoke data-agent
❌ data-agent CAN query database
❌ database CAN return financial-records
```

MAPL uses hierarchical composition:
```
✅ Company policy: "employees can access company tools"
✅ Finance BU policy: "finance team can access finance:* resources"
✅ Analyst team policy: "analysts limited to read-only, 500 token limits"
✅ Alice's runtime context: {dept: "finance", team: "analysts"}
   → Policies automatically composed at invocation time
```

### The Composition Algebra (Simplified)

When policies compose, they follow simple rules:

**Resources** (what you can access):
- Parent allows A, B, C
- Child allows B, C, D
- **Result**: B, C (intersection - both must allow)

**Denied Resources** (what's explicitly blocked):
- Parent denies X
- Child denies Y
- **Result**: X, Y (union - either can deny)

**Constraints** (limits on parameters):
- Parent: max_tokens ≤ 2000
- Child: max_tokens ≤ 500
- **Result**: max_tokens ≤ 500 (most restrictive wins)

This means:
- ✅ Policies always get MORE restrictive down the hierarchy
- ✅ You can't accidentally grant more permissions by inheriting
- ✅ Denials always propagate (no overrides!)

---

## Part 2: Your First MAPL Policy

### The Basic Structure

A MAPL policy is a JSON document with this structure:

```json
{
  "policy_id": "unique-identifier",
  "extends": "parent-policy-id",
  "resources": ["resource-patterns"],
  "denied_resources": ["denial-patterns"],
  "attestations": ["attestation-requirements"],
  "constraints": {
    "parameters": {},
    "denied_parameters": {},
    "attestations": {}
  }
}
```

### Your First Policy

Let's create a simple policy for a financial analyst:

```json
{
  "policy_id": "user:alice",
  "version": "1.0",
  "description": "Alice - Financial Analyst",

  "resources": [
    "llm:openai/chat.completions",
    "tool:database/query"
  ],

  "denied_resources": [
    "admin:**",
    "*.secret"
  ],

  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 500}
      }
    }
  }
}
```

**What this policy does:**
- ✅ Alice can use OpenAI chat completions
- ✅ Alice can query the database
- ❌ Alice cannot access admin resources
- ❌ Alice cannot access any `.secret` files
- ⚠️ Alice's OpenAI requests are limited to 500 tokens

### Understanding Each Section

#### policy_id
```json
"policy_id": "user:alice"
```
- Unique identifier for this policy
- Convention: `{scope}:{name}` (e.g., `company:acme`, `bu:finance`, `team:analysts`, `user:alice`)
- Used for inheritance and reference

#### version
```json
"version": "1.0"
```
- Semantic versioning for policy tracking
- Helps with auditing and rollback

#### description
```json
"description": "Alice - Financial Analyst"
```
- Human-readable explanation
- Helps other admins understand the policy's purpose

#### resources
```json
"resources": [
  "llm:openai/chat.completions",
  "tool:database/query"
]
```
- **Arrays of operation patterns** (not resources + actions!)
- In MAPL, resources ARE operations:
  - `llm:openai/chat.completions` - the operation of creating a chat completion
  - `tool:database/query` - the operation of querying the database
- Supports wildcards (more on this below)

#### denied_resources
```json
"denied_resources": [
  "admin:**",
  "*.secret"
]
```
- **Explicit blocks** that override allows
- Denials always win in conflicts
- Used for sensitive resources that should never be accessed

#### constraints
```json
"constraints": {
  "parameters": {
    "llm:openai/chat.completions": {
      "max_tokens": {"max": 500}
    }
  }
}
```
- Limits on what parameters can be passed to operations
- We'll cover this in detail in Part 4

### Resource Patterns and Wildcards

MAPL supports hierarchical resource naming with wildcards:

| Pattern | Meaning | Examples |
|---------|---------|----------|
| `llm:openai/chat.completions` | Exact match | Only that specific operation |
| `llm:openai/*` | Single-level wildcard | `llm:openai/chat.completions`, `llm:openai/embeddings` |
| `llm:openai/**` | Multi-level wildcard | All OpenAI LLM operations at any depth |
| `llm:**` | All LLMs | Any LLM provider, any operation |
| `**` | Everything | All resources (use with caution!) |

**Examples:**

```json
{
  "resources": [
    "llm:openai/*",           // All OpenAI operations
    "tool:database/query",     // Specific operation
    "file:data/*/read"         // Read any file in data/
  ]
}
```

### Why Resources ARE Operations

You might wonder: "Where are the actions like read, write, execute?"

In traditional systems:
```
Resource: /etc/passwd
Actions: [read, write, execute]
```

In MAPL (agentic/RPC world):
```
Operations:
  - file:/etc/passwd/read      ← operation
  - file:/etc/passwd/write     ← operation
  - file:/etc/passwd/execute   ← operation
```

The resource path INCLUDES the operation. This is because:

1. **Tools are callable operations by nature**:
   - `llm:openai/chat.completions` - the operation IS creating completions
   - `tool:database/query` - the operation IS querying

2. **Granularity is in the path, not separate actions**:
   - Instead of: `database:users` + action=`read`
   - MAPL uses: `database:users/read` (clearer!)

3. **No combinatorial explosion**:
   - Traditional: M resources × N actions = M×N rules
   - MAPL: Resources encode operations = M patterns

4. **Wildcards group operations naturally**:
   - `database:users/*` - all user database operations
   - `database:*/read` - read from any database table

---

## Part 3: Policy Inheritance with extends

### Why Inheritance?

Imagine you have:
- 1 company
- 5 business units
- 20 teams
- 500 users

Without inheritance, you'd need to repeat common rules in all 500 user policies. Inheritance lets you:

✅ Define common rules once (company level)
✅ Specialize at each level (BU, team, user)
✅ Scale from O(M×N) rules to O(log M + N) policies

### Your First Inheritance Chain

Let's build a 3-level hierarchy:

**Level 1: Company Policy**
```json
{
  "policy_id": "company:FinTech",
  "description": "Company-wide base policy",

  "resources": [
    "llm:openai/*"
  ],

  "denied_resources": [
    "*.secret",
    "*.password"
  ],

  "constraints": {
    "rate_limit": 100,
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 4000}
      }
    }
  }
}
```

**Level 2: Business Unit Policy (extends company)**
```json
{
  "policy_id": "bu:Analytics",
  "extends": "company:FinTech",
  "description": "Analytics BU - enforces deterministic results",

  "constraints": {
    "rate_limit": 50,
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 2000},
        "temperature": {"max": 0.3}
      }
    }
  }
}
```

**Level 3: User Policy (extends BU)**
```json
{
  "policy_id": "user:alice",
  "extends": "bu:Analytics",
  "description": "Alice - Analyst",

  "resources": [
    "llm:openai/chat.completions"
  ],

  "constraints": {
    "rate_limit": 10,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo"],
        "max_tokens": {"max": 500}
      }
    }
  },

  "denied_resources": [
    "data:executive/*"
  ]
}
```

### How Inheritance Works: Step-by-Step Resolution

When Alice makes a request, MAPL resolves the policy chain:

```
company:FinTech → bu:Analytics → user:alice
```

**Step 1: Start with Company Policy**
```
Resources: [llm:openai/*]
Denied: [*.secret, *.password]
max_tokens: 4000
rate_limit: 100
```

**Step 2: Intersect with BU Policy**
```
Resources: [llm:openai/*]  (unchanged - BU didn't specify)
Denied: [*.secret, *.password]  (unchanged - BU didn't add denials)
max_tokens: min(4000, 2000) = 2000  ← More restrictive!
temperature: 0.3  ← New constraint added
rate_limit: min(100, 50) = 50  ← More restrictive!
```

**Step 3: Intersect with Alice's Policy**
```
Resources: [llm:openai/chat.completions]  ← More specific!
  (intersection of llm:openai/* and llm:openai/chat.completions)

Denied: [*.secret, *.password, data:executive/*]  ← Alice adds executive denial

max_tokens: min(2000, 500) = 500  ← Most restrictive!
temperature: 0.3  (Alice didn't override)
model: [gpt-3.5-turbo]  ← Alice adds model restriction
rate_limit: min(50, 10) = 10  ← Most restrictive!
```

**Final Effective Policy for Alice:**
```json
{
  "resources": ["llm:openai/chat.completions"],
  "denied_resources": ["*.secret", "*.password", "data:executive/*"],
  "constraints": {
    "rate_limit": 10,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo"],
        "max_tokens": {"max": 500},
        "temperature": {"max": 0.3}
      }
    }
  }
}
```

### The Intersection Rules

Here's how different fields merge during inheritance:

| Field | Merge Strategy | Example |
|-------|----------------|---------|
| **resources** | Domain-Aware Extend+Restrict | Parent `[finance:*, tool:calc]` + Child `[finance:trading/*]` = `[finance:trading/*, tool:calc]` |
| **denied_resources** | Union | Parent `[X]` + Child `[Y]` = `[X,Y]` |
| **max** | Minimum | Parent `max:2000` + Child `max:500` = `max:500` |
| **min** | Maximum | Parent `min:0` + Child `min:10` = `min:10` |
| **range** | Intersection | Parent `[0,2000]` + Child `[0,500]` = `[0,500]` |
| **allowed_values** | Intersection | Parent `["A","B","C"]` + Child `["B","C"]` = `["B","C"]` |

**The key principle**: Most restrictive always wins!

**Note on resources**: Resource inheritance uses **domain-aware logic** (not simple set intersection). If a child doesn't mention a domain (e.g., `tool:`), it automatically inherits the parent's resources for that domain. If the child specifies patterns in a domain, it restricts the parent's patterns for that domain only. This is what enables **O(log M + N) complexity** - children don't need to re-specify everything they inherit!

### Visual Example: Range Intersection

```
Company Policy:
    max_tokens: [------------ 0 to 4000 ------------]

BU Analytics:
    max_tokens: [------ 0 to 2000 ------]

User Alice:
    max_tokens: [-- 0 to 500 --]

Final Result:
    max_tokens: [-- 0 to 500 --]  ← Intersection = most restrictive
```

### Visual Example: Allowed Values Intersection

```
Company Policy:
    model: [gpt-3.5-turbo, gpt-4, gpt-4-turbo]

BU Analytics:
    model: [gpt-3.5-turbo, gpt-4]  (removes gpt-4-turbo)

User Alice:
    model: [gpt-3.5-turbo]  (removes gpt-4)

Final Result:
    model: [gpt-3.5-turbo]  ← Intersection
```

### Advanced: Domain-Aware Resource Inheritance

**Why domain-aware inheritance matters**: This is one of the core innovations that enables MAPL to achieve **O(log M + N) complexity** instead of O(M×N). Without it, every child policy would need to re-specify every resource it wants, defeating the purpose of hierarchical composition.

#### The Algorithm: Extend + Restrict Per Domain

Resource inheritance works differently than simple set intersection. Here's the actual algorithm:

1. **Extend**: Create union of parent and child resources
2. **Group by domain**: Group patterns by domain prefix (`finance:`, `tool:`, `llm:`, etc.)
3. **Domain-aware restriction**: For each parent pattern, find ALL child restrictions in that domain
4. **Inherit unrestricted domains**: If child has no patterns in a domain, inherit parent's patterns as-is

**Result**: Child inherits what it doesn't mention, restricts what it does mention.

#### Example 1: Automatic Inheritance Without Re-Specification

This is the **key use case** - child doesn't need to re-list everything:

```json
// Parent: BU Finance
{
  "policy_id": "bu:finance",
  "resources": [
    "finance:*",           // All finance operations
    "tool:calculator",     // Calculator tool
    "tool:analyzer",       // Analyzer tool
    "report:*"            // All reports
  ]
}

// Child: Trading Team
{
  "policy_id": "team:trading",
  "extends": "bu:finance",
  "resources": [
    "finance:trading/*",    // Restrict finance domain
    "finance:positions/*"   // Additional finance restriction
  ]
  // NOTE: Child doesn't mention tool: or report: domains!
}

// Final Effective Policy for team:trading
{
  "resources": [
    "finance:trading/*",    // ← Restricted from finance:*
    "finance:positions/*",  // ← Additional restriction
    "tool:calculator",      // ← Inherited (child didn't mention tool: domain)
    "tool:analyzer",        // ← Inherited (child didn't mention tool: domain)
    "report:*"             // ← Inherited (child didn't mention report: domain)
  ]
}
```

**Why this works**:
- Child specifies patterns in `finance:` domain → restricts parent's `finance:*` to specific sub-patterns
- Child doesn't specify anything in `tool:` domain → inherits `tool:calculator` and `tool:analyzer` automatically
- Child doesn't specify anything in `report:` domain → inherits `report:*` automatically

**Without domain-aware inheritance**, the child would need to manually list `tool:calculator`, `tool:analyzer`, and `report:*` - defeating the purpose of hierarchical composition!

### Service Agent Policies (app: prefix)

So far we've focused on **organizational policies** (company, BU, team, user). But MAPL also supports **service agent policies** that define what services themselves allow.

#### The Dual Perspective Model

MAPL enforces policies from **two perspectives**:

1. **Caller Perspective** (user:alice): What can Alice request?
2. **Service Perspective** (app:openai-service): What does the OpenAI service allow?

The final effective policy is the **intersection** of both:
```
Effective Policy = Caller Policies ∩ Service Policies
```

This means **BOTH must allow** an operation for it to succeed.

### Why No Overrides?

You might ask: "What if I need to give Alice emergency access?"

**MAPL explicitly prohibits overrides.** Why?

1. **Provable security**: The theorems (Monotonic Restriction, Transitive Denial) don't hold with overrides
2. **Audit nightmare**: If policies can be overridden, you can't trust the policy hierarchy
3. **Privilege escalation risk**: Overrides create paths to bypass restrictions

**Instead, use time-bounded groups**:

```json
{
  "policy_id": "group:emergency-access",
  "validity": {
    "not_before": "2025-01-17T09:00:00Z",
    "not_after": "2025-01-17T17:00:00Z"
  },
  "resources": ["admin:**"],
  "constraints": {
    "audit_level": "maximum",
    "require_approval": true
  }
}
```

Add Alice to the emergency group temporarily. When the validity period expires, access is automatically revoked.

---

## Part 4: Parameter Constraints

### What Are Parameter Constraints?

Parameter constraints limit the **values** passed to operations, not just which operations are allowed.

**Example scenario:**
- ✅ Alice can call `llm:openai/chat.completions`
- ⚠️ But her `max_tokens` must be ≤ 500
- ⚠️ And she can only use `gpt-3.5-turbo` (not gpt-4)

This prevents:
- **Cost overruns**: Limit token usage
- **Security issues**: Block dangerous parameter values (injection attacks)
- **Compliance violations**: Enforce required parameters

### Basic Constraint Structure

Parameter constraints are nested under `constraints.parameters`:

```json
{
  "constraints": {
    "parameters": {
      "operation-pattern": {
        "parameter_name": constraint_specification
      }
    }
  }
}
```

**Example:**
```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 500},
        "model": ["gpt-3.5-turbo"],
        "temperature": {"type": "number", "range": [0.0, 0.8]}
      }
    }
  }
}
```

### Constraint Types

#### 1. Type Validation

Ensure parameters match expected types:

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"type": "integer"},
        "temperature": {"type": "number"},
        "model": {"type": "string"},
        "stream": {"type": "boolean"},
        "messages": {"type": "array"}
      }
    }
  }
}
```

**Supported types:**
- `integer` - Whole numbers (1, 2, 100)
- `number` - Integers or floats (1, 1.5, 3.14)
- `string` - Text values
- `boolean` - true or false
- `array` - Lists
- `object` - Dictionaries

#### 2. Numeric Constraints

Control numeric ranges:

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {
          "type": "integer",
          "min": 1,
          "max": 4000
        },
        "temperature": {
          "type": "number",
          "range": [0.0, 2.0]
        }
      },
      "finance:transfer": {
        "amount": {
          "type": "number",
          "min": 0,
          "max": 1000000
        }
      }
    }
  }
}
```

**Available numeric constraints:**
- `min` - Minimum value (inclusive)
- `max` - Maximum value (inclusive)
- `range` - Array `[min, max]` (shorthand for min+max)

**Note:** Use either `min`/`max` OR `range`, not both.

#### 3. String Constraints

Control string values:

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "model": {
          "type": "string",
          "allowed_values": ["gpt-3.5-turbo", "gpt-4"]
        }
      },
      "report:generate": {
        "format": {
          "type": "string",
          "allowed_values": ["PDF", "XLSX", "CSV"]
        },
        "time_period": {
          "type": "string",
          "pattern": "^(Q[1-4]|H[1-2]|FY)\\d{4}$"
        }
      },
      "user:create": {
        "username": {
          "type": "string",
          "min_length": 3,
          "max_length": 32,
          "pattern": "^[a-zA-Z0-9_]+$"
        }
      }
    }
  }
}
```

**Available string constraints:**
- `allowed_values` - Array of permitted strings (enumeration)
- `pattern` - Regular expression (must match entire string)
- `min_length` - Minimum string length
- `max_length` - Maximum string length (prevents buffer overflow, prompt injection)

#### 4. Array Constraints

Control array sizes:

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "messages": {
          "type": "array",
          "min_items": 1,
          "max_items": 100
        }
      },
      "database:batch_insert": {
        "records": {
          "type": "array",
          "max_items": 1000
        }
      }
    }
  }
}
```

**Available array constraints:**
- `min_items` - Minimum array length
- `max_items` - Maximum array length (prevents resource exhaustion)

#### 5. Combining Constraints

You can combine multiple constraint types:

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "model": {
          "type": "string",
          "allowed_values": ["gpt-3.5-turbo", "gpt-4"]
        },
        "max_tokens": {
          "type": "integer",
          "min": 1,
          "max": 4000
        },
        "temperature": {
          "type": "number",
          "range": [0.0, 2.0]
        },
        "messages": {
          "type": "array",
          "min_items": 1,
          "max_items": 100
        }
      }
    }
  }
}
```

### Shorthand Formats

For backward compatibility and convenience, MAPL supports shorthand formats:

#### List Enumeration (Shorthand for allowed_values)

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo", "gpt-4"]
      }
    }
  }
}
```

Equivalent to:
```json
{
  "model": {
    "allowed_values": ["gpt-3.5-turbo", "gpt-4"]
  }
}
```

### Denied Parameters (Injection Defense)

The `constraints.denied_parameters` field lets you block dangerous parameter patterns:

```json
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo", "gpt-4"],
        "max_tokens": {"max": 4000}
      }
    },
    "denied_parameters": {
      "llm:**": {
        "prompt": [
          "*DROP TABLE*",
          "*rm -rf*",
          "*eval(*",
          "*exec(*"
        ]
      },
      "tool:shell/*": {
        "command": [
          "*sudo*",
          "*rm -*",
          "*dd if=*"
        ]
      }
    }
  }
}
```

**Important: Uses wildcard patterns, NOT regex!**
- `*` matches any characters (including spaces, `/`, `:`, special chars)
- `**` matches everything (same as `*` for parameter values)
- Patterns are case-sensitive: `*DROP TABLE*` ≠ `*drop table*`
- Use wildcards to match substrings: `*DROP TABLE*` matches "foo DROP TABLE bar"
- For better patterns: `*rm -*` (requires dash) instead of `*rm *` (matches "perform task")

### How Constraints Merge During Inheritance

When policies inherit, constraints merge using the "most restrictive wins" rule:

**Example: Numeric Constraints**

```json
// Company policy
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 4000}
      }
    }
  }
}

// BU policy (extends company)
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 2000}  // More restrictive
      }
    }
  }
}

// User policy (extends BU)
{
  "constraints": {
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 500}  // Even more restrictive
      }
    }
  }
}

// Final result: max_tokens max = 500 (most restrictive)
```


---

## Part 5: Attestations - Provable Workflow Dependencies

### What Are Attestations?

Imagine you're building a financial trading system with an AI agent. The agent needs to:
1. Verify the user's identity
2. Get manager approval for large trades
3. Execute the trade

Traditional approach:
```python
# Agent memory (can be tampered with!)
agent.memory = {
    "identity_verified": True,    # ← Agent says it did this
    "manager_approved": True       # ← But can you trust it?
}

if agent.memory["identity_verified"] and agent.memory["manager_approved"]:
    execute_trade()  # Hope the agent isn't lying!
```

**The problem**: You're trusting the agent to honestly report its own state. If the agent is compromised (or just buggy), it can:
- Skip the identity verification step
- Lie about getting manager approval
- Execute unauthorized trades

MAPL attestations solve this through **cryptographic proofs**:

```python
# Attestation (cryptographically signed proof)
attestation = {
    "key": "identity_verified",
    "value": {"user_id": "alice@acme.com"},
    "set_by": "tool.verify_identity",    # ← Who created it
    "signature": "a4b8c2...",            # ← Cryptographic proof
    "timestamp": 1700000000,
    "one_time": True                     # ← Self-enforcing metadata
}
```

The signature proves:
- ✅ The `verify_identity` tool actually executed
- ✅ The attestation wasn't forged or tampered with
- ✅ The metadata (one_time, time_to_live) is cryptographically bound

### Attestation Types: Internal vs External

MAPL v3 introduces a **unified attestation model** with two types:

#### Internal Attestations (Workflow/Self-Set)

Created by tools during workflow execution. The tool itself sets the attestation after successful completion.

```json
{
  "attestations": [
    "identity_verified",
    "mfa_complete"
  ]
}
```

**Characteristics:**
- Set by the tool that performed the operation
- Immediate (no waiting)
- Used for workflow sequencing ("A must happen before B")

#### External Attestations (Third-Party Approval)

Require approval from an external party (manager, admin, compliance officer).

```json
{
  "attestations": [
    "trade_approved::{params.amount > 5000}"
  ],
  "constraints": {
    "attestations": {
      "trade_approved": {
        "approval_criteria": "role:manager",
        "timeout": 300,
        "time_to_live": 3600
      }
    }
  }
}
```

**Characteristics:**
- Require `approval_criteria` in metadata
- Can block execution waiting for approval (`timeout > 0`)
- Used for human-in-the-loop workflows

**The discriminator**: If `approval_criteria` is present, it's an external attestation. If absent, it's internal.

**Approval Outcomes:**
- **Approved**: Approver approved with `client.approve_attestation()` - requestor proceeds
- **Denied**: Approver rejected with `client.deny_attestation()` - requestor fails with reason

**approval_criteria Formats:**

| Format           | Example                | Matches                                      |
|------------------|------------------------|----------------------------------------------|
| `user:xxx`       | `user:alice`           | caller_id == "alice" OR email == "alice"     |
| `user:xxx@domain`| `user:alice@fintech.com`| Exact match on caller_id or email           |
| `role:xxx`       | `role:manager`         | Approver has role "manager" from IDP/JWT     |
| `xxx` (bare)     | `manager`              | Fallback: treated as `role:manager`          |

### The Blocking Flow

When a request requires an external attestation with `timeout > 0`, the system blocks and waits for approval:

```
Request arrives → Policy requires attestation
    ↓
Check in-memory cache → Not found
    ↓
Check external storage → Not found
    ↓
Create pending attestation (for_agent, approval_criteria, timeout)
    ↓
Poll with backoff [1, 1, 2, 2, 3, 5, 7, 9, 10] seconds
    ↓
┌─────────────────────────────────────────────┐
│ Outcome:                                    │
│   • approved → Request proceeds             │
│   • denied → Request fails with reason      │
│   • timeout → Request fails (expired)       │
└─────────────────────────────────────────────┘
```

**Key points:**
- The `for_agent` field identifies WHO needs the attestation (the requester)
- The `approval_criteria` field specifies WHO can approve
- Approvers call `client.list_attestations(status='pending')` to see requests they can approve
- Approvers call `client.approve_attestation()` or `client.deny_attestation()` to respond

### The for_agent Field

When an attestation is created, `for_agent` identifies who needs it approved:

```json
{
  "key": "trade_approved",
  "for_agent": "alice@trading-app",    // WHO needs this approved
  "approval_criteria": "role:manager", // WHO can approve
  "invocation_id": "inv-123",          // Links to the blocked request
  "status": "pending"
}
```

**Why for_agent matters:**

1. **Filtering in list_attestations()**: When an agent calls `list_attestations(status='pending')`, they see:
   - Attestations where they ARE the `for_agent` (attestations they're waiting on)
   - Attestations where they MATCH the `approval_criteria` (attestations they can approve)

2. **Activity Graph correlation**: The `invocation_id` links the attestation to the original request, enabling end-to-end tracing in the Activity Graph.

3. **Audit trail**: The `for_agent` field is recorded in audit events, showing who requested the attestation and who approved it.

### Conditional Attestations (NEW in v3)

**The most powerful feature**: Attestation requirements that are conditional on runtime parameters.

#### Basic Syntax

```
"attestation_key::{condition}"
```

**Examples:**

```json
{
  "attestations": [
    "identity_verified",
    "trade_approved::{params.amount > 5000}",
    "manager_override::{params.priority == 'urgent' AND params.amount > 10000}"
  ]
}
```

**What this means:**
- `identity_verified` - Always required (no condition)
- `trade_approved::{params.amount > 5000}` - Only required when amount exceeds 5000
- `manager_override::{...}` - Only required when both conditions are true

#### Condition Syntax

**References:**
| Pattern | Description | Example |
|---------|-------------|---------|
| `params.X` | Invocation parameter | `params.amount > 5000` |
| `principal.X` | Authenticated user attribute | `principal.user_id == 'admin'` |
| `principal.has_role('X')` | Role check | `principal.has_role('manager')` |
| `principal.has_group('X')` | Group check | `principal.has_group('trading')` |
| `context.has_attestation('X')` | Existing attestation check | `context.has_attestation('mfa')` |

**Operators:**
| Operator | Description | Example |
|----------|-------------|---------|
| `==` | Equals | `params.currency == 'USD'` |
| `!=` | Not equals | `params.status != 'draft'` |
| `<`, `<=`, `>`, `>=` | Comparison | `params.amount > 5000` |
| `IN` | In list | `params.region IN ('us', 'eu')` |
| `MATCHES` | Glob pattern matching | `params.command MATCHES '*npm install*'` |

**Boolean Logic:**
| Operator | Description | Example |
|----------|-------------|---------|
| `AND` | Both true | `params.a > 10 AND params.b < 5` |
| `OR` | Either true | `params.x == 'a' OR params.x == 'b'` |
| `NOT` | Negation | `NOT params.override` |
| `()` | Grouping | `(params.a > 10 AND params.b < 5) OR params.urgent` |

#### Complex Conditional Examples

**Tiered approval based on amount:**
```json
{
  "attestations": [
    "identity_verified",
    "team_lead_approval::{params.amount > 1000 AND params.amount <= 10000}",
    "manager_approval::{params.amount > 10000 AND params.amount <= 50000}",
    "director_approval::{params.amount > 50000}"
  ]
}
```

**Multi-factor conditions:**
```json
{
  "attestations": [
    "large_trade::{(params.amount > 25000 AND params.currency == 'USD') OR params.priority == 'urgent'}"
  ]
}
```

**Pattern matching for command strings:**
```json
{
  "attestations": [
    "tier2_session::{params.command MATCHES '*npm install*'}",
    "tier3_operation::{params.command MATCHES '*rm -rf*' OR params.command MATCHES '*curl*'}",
    "tier4_critical::{params.command MATCHES '*sudo*'}"
  ]
}
```

Pattern syntax uses glob-style wildcards:
- `*` matches any characters
- `*npm*` matches any string containing "npm"
- `npm*` matches strings starting with "npm"
- `*lodash` matches strings ending with "lodash"

**Role-based conditional:**
```json
{
  "attestations": [
    "extra_approval::{NOT principal.has_role('senior_trader') AND params.amount > 5000}"
  ]
}
```

### Attestation Metadata

Configure attestation behavior in `constraints.attestations`:

```json
{
  "constraints": {
    "attestations": {
      "trade_approved": {
        "approval_criteria": "role:manager",
        "timeout": 300,
        "time_to_live": 3600,
        "one_time": true,
        "max_uses": 1,
        "priority": 100
      }
    }
  }
}
```

#### Metadata Fields

| Field | Type | Description |
|-------|------|-------------|
| `approval_criteria` | string | Who can approve (external attestation). Format: `role:X`, `user:email@domain.com`, `admin` |
| `timeout` | integer | Seconds to wait for approval. `0` = don't block, `> 0` = block and wait |
| `time_to_live` | integer | Seconds the attestation remains valid after approval |
| `one_time` | boolean | If true, attestation is consumed after single verification |
| `max_uses` | integer | Maximum number of times the attestation can be verified (monitor pattern) |
| `priority` | integer | Processing order when multiple attestations match. Higher values processed first (default: 0) |

#### Metadata Semantics

**Internal vs External (determined by approval_criteria):**
```json
// Internal attestation (no approval_criteria)
{
  "identity_verified": {
    "one_time": true,
    "time_to_live": 300
  }
}

// External attestation (has approval_criteria)
{
  "trade_approved": {
    "approval_criteria": "role:manager",
    "timeout": 300,
    "time_to_live": 3600,
    "one_time": true
  }
}
```

**Blocking behavior (determined by timeout):**
- `timeout: 0` or absent → Don't block, deny immediately if attestation missing
- `timeout: 300` with `approval_criteria` → Block up to 300 seconds waiting for approval

#### one_time (Boolean)

```python
logger.attest(key="sudo_verified", one_time=True)
```

**What it does**:
- Attestation is consumed after first verification
- Second verification attempt fails (attestation deleted)

**Use cases**:
- Authentication tokens (one login = one session)
- Approval workflows (one approval = one action)
- One-time operations (password reset, account activation)

#### Grants: Reusable Approvals (one_time: false)

Set `one_time: false` to create a **grant** that can be reused multiple times:

```json
{
  "constraints": {
    "attestations": {
      "trade_approved": {
        "approval_criteria": "role:manager",
        "one_time": false,      // Reusable grant
        "time_to_live": 3600    // Valid for 1 hour
      }
    }
  }
}
```

**What it does**:
- Manager approves once → Grant is created
- Agent can execute multiple trades for 1 hour without re-approval
- Each use emits `attestation_accessed` audit event
- Grant expires when `time_to_live` elapses

**Use cases**:
- Batch operations (approve once, run many)
- Time-boxed elevated access (trading window)
- Delegated authority (manager grants permission for a period)

#### time_to_live (Integer, seconds)

```python
logger.attest(key="session_authenticated", time_to_live=300)
```

**What it does**:
- Attestation expires after 300 seconds (5 minutes)
- Verifier checks: `current_time > attestation.timestamp + time_to_live`

**Use cases**:
- Session management (5 minute authentication window)
- Time-sensitive operations (trading hours enforcement)
- Temporary escalations (sudo access for 10 minutes)

#### max_uses (Integer) - Monitor Pattern

```python
logger.attest(key="api_quota", max_uses=10)
```

**What it does**:
- Attestation valid for 10 verifications
- Use counter incremented on each verification
- Fails when `use_count >= max_uses`

**Use cases**:
- API rate limiting (10 calls per attestation)
- Quota enforcement (10 file downloads)
- Multi-use but not unlimited (batch operations)

**Example**:
```python
# Create attestation with max_uses
logger.attest(key="batch_quota", max_uses=3)

# Use 1
verify("batch_quota")  # ✅ Works (use_count: 1/3)

# Use 2
verify("batch_quota")  # ✅ Works (use_count: 2/3)

# Use 3
verify("batch_quota")  # ✅ Works (use_count: 3/3)

# Use 4
verify("batch_quota")  # ❌ Fails (use_count: 3/3, exhausted)
```

### The Security Model: ToolAgent Signing + Registry TCB

#### Why Independent Verification?

In MACAW, each tool is wrapped in a **ToolAgent** (Policy Enforcement Point). This creates isolation:

```
User Agent (orchestrator)
  ↓ invokes
ToolAgent PEP (independent verifier)
  ↓ enforces policies for
Tool Code (business logic)
```

**Benefit**: If a tool is compromised, the blast radius is limited:
- Compromised tool can't access other tools directly
- ToolAgent PEP enforces resource policies
- ToolAgent verifies signatures on invocations

#### How Verification Works

1. **ToolAgent Creates Attestations (Not the Tool)**

```
verify_identity tool executes successfully
  ↓
ToolAgent PEP creates attestation
  ↓
ToolAgent signs with its own private key
  ↓
Stores in session context
```

**Why this matters**: The tool code never sees the private key. Even if the tool is compromised, it cannot forge attestations.

2. **Registry as Trusted Computing Base (TCB)**

When verifying an attestation:
```
Attestation says: "Created by tool.verify_identity"
  ↓
Need public key to verify signature
  ↓
Look up in GlobalAgentRegistry (TCB)
  ↓
Registry returns: tool.verify_identity's registered public key
```

**Why this matters**:
- Registry is part of the **Trusted Computing Base** (TCB)
- All ToolAgents register their public keys on startup
- Verifier trusts ONLY keys from the registry
- Attackers can't embed fake public keys in attestation data

### Creating Attestations

#### 1. Auto-Attestation (Config-Driven)

For simple "I executed successfully" cases, configure attestations declaratively:

```python
# In your MACAWClient setup:
tool_attestations = {
    "verify_identity": {
        "key": "identity_verified",
        "one_time": True,
        "time_to_live": 300,
        "scope": "session"
    }
}

client = MACAWClient(
    app_name="trading-app",
    tool_handlers={"verify_identity": verify_identity_handler},
    tool_attestations=tool_attestations
)
```

**Use when**:
- Simple "this operation completed" semantics
- No custom data needed in attestation value
- Standard metadata sufficient

#### 2. Explicit Attestation (Programmatic)

For cases where you need custom values:

```python
def manager_approval_handler(params):
    """Manager approval tool - creates attestation with custom data"""

    logger = params.get('_logger')

    trade_id = params.get('trade_id')
    amount = params.get('amount')

    # Business logic
    check_manager_approval(trade_id, amount)

    # Create attestation with custom value
    logger.attest(
        key="trade_approved",
        value={
            "trade_id": trade_id,
            "amount": amount,
            "approved_by": get_current_manager()
        },
        one_time=True
    )

    return {"status": "approved", "trade_id": trade_id}
```

**Use when**:
- Need to embed custom data (trade details, approval info)
- Want to capture who performed the action
- Need application-specific metadata

### Enforcing Attestation Requirements in Policies

In your MAPL policy, require attestations before allowing operations:

```json
{
  "policy_id": "intent:trading",
  "resources": [
    "tool:verify_identity",
    "tool:approve_trade",
    "tool:execute_trade"
  ],
  "attestations": [
    "identity_verified",
    "trade_approved::{params.amount > 5000}"
  ],
  "constraints": {
    "attestations": {
      "trade_approved": {
        "approval_criteria": "role:manager",
        "timeout": 300,
        "time_to_live": 3600,
        "one_time": true
      }
    }
  }
}
```

**How enforcement works**:

1. **Agent tries to execute trade with amount=10000**
2. **Policy enforcer evaluates attestation requirements**:
   - `identity_verified` - Always required → Check
   - `trade_approved::{params.amount > 5000}` - Condition: 10000 > 5000 = true → Required
3. **For each required attestation**:
   - Get attestation from context
   - Look up signer's public key from registry (TCB)
   - Verify cryptographic signature
   - Check metadata (one_time used? time_to_live expired? max_uses exhausted?)
4. **Decision**:
   - ✅ All attestations verify → Allow execution
   - ❌ Any attestation fails → Deny (or block if timeout > 0 for external)

### Real-World Example: Financial Trading Workflow

#### The Requirement

**Scenario**: Trading platform where:
- Users can execute trades
- Trades over $5,000 require manager approval
- Identity must be verified before any trade
- Each approval is one-time use (prevents replay)
- Verifications expire after 5 minutes

#### Step 1: Define the Policy

```json
{
  "policy_id": "intent:secure-trading",
  "description": "Secure trading with conditional approval",

  "resources": [
    "tool:verify_identity",
    "tool:approve_trade",
    "tool:execute_trade"
  ],

  "attestations": [
    "identity_verified",
    "trade_approved::{params.amount > 5000}"
  ],

  "constraints": {
    "attestations": {
      "identity_verified": {
        "one_time": true,
        "time_to_live": 300
      },
      "trade_approved": {
        "approval_criteria": "role:manager",
        "timeout": 300,
        "time_to_live": 3600,
        "one_time": true
      }
    },
    "parameters": {
      "tool:execute_trade": {
        "amount": {"type": "number", "min": 0, "max": 1000000}
      }
    }
  }
}
```

#### Step 2: Configure Auto-Attestation

```python
tool_attestations = {
    "verify_identity": {
        "key": "identity_verified",
        "one_time": True,
        "time_to_live": 300
    }
}
```

#### Step 3: Execute Workflow

```python
# Trade 1: Small trade ($1000) - no manager approval needed
client.invoke_tool("verify_identity", {"user_id": "alice@acme.com"})
result = client.invoke_tool("execute_trade", {"trade_id": "T-001", "amount": 1000})
# ✅ Succeeds - identity_verified present, trade_approved not required (1000 < 5000)

# Trade 2: Large trade ($10000) - manager approval needed
client.invoke_tool("verify_identity", {"user_id": "alice@acme.com"})
result = client.invoke_tool("execute_trade", {"trade_id": "T-002", "amount": 10000})
# ⏳ Blocks waiting for manager approval (up to 300 seconds)
# Manager approves via separate interface
# ✅ Succeeds after approval

# Trade 3: Try to reuse attestations
result = client.invoke_tool("execute_trade", {"trade_id": "T-003", "amount": 500})
# ❌ Fails - identity_verified was consumed (one_time=true)
```

#### Step 4: Bob's Approval Code (Separate Terminal)

While Alice's Trade 2 is blocking, Bob runs this in another terminal:

```python
from macaw_agent.client import MACAWClient

# Bob authenticates via Keycloak (has "manager" role)
jwt_token = get_keycloak_token("bob", "Bob@123!")

client = MACAWClient(
    user_name="bob",
    iam_token=jwt_token,
    agent_type="user",
    app_name="attestation-approver"
)
client.register()

# List pending attestations Bob can approve (matches his roles)
attestations = client.list_attestations(status="pending")
print(f"Found {len(attestations)} pending attestations")

# Approve Alice's trade request
for att in attestations:
    if att['key'] == 'trade_approved':
        # Approve
        client.approve_attestation(att, reason="Manager approved")
        print("Approved!")

        # OR Deny
        # client.deny_attestation(att, reason="Budget exceeded")

client.unregister()
```

When Bob approves, Alice's blocked request (in the other terminal) automatically proceeds.

### MACAWClient Approval APIs

The SDK provides three methods for working with attestations:

#### list_attestations(status)

List attestations visible to the current agent:

```python
# List all pending attestations (you can approve OR are waiting on)
pending = client.list_attestations(status="pending")

# List approved attestations
approved = client.list_attestations(status="approved")

# List all attestations (no filter)
all_atts = client.list_attestations()
```

**Returns** attestations where:
- You ARE the `for_agent` (attestations you're waiting on), OR
- You MATCH the `approval_criteria` (attestations you can approve)

#### approve_attestation(attestation, reason)

Approve a pending attestation:

```python
client.approve_attestation(
    attestation,
    reason="Approved for Q4 trading window"
)
```

**Requirements:**
- Attestation must be in `pending` status
- Your identity must match `approval_criteria` (e.g., have "manager" role)
- Emits `attestation_approved` audit event with your signature

#### deny_attestation(attestation, reason)

Deny a pending attestation:

```python
client.deny_attestation(
    attestation,
    reason="Budget exceeded for this quarter"
)
```

**Requirements:**
- Attestation must be in `pending` status
- Your identity must match `approval_criteria`
- Emits `attestation_denied` audit event
- Blocked requester receives denial with your reason

**Security Guarantees Achieved**:
- ✅ Agent can't skip identity verification
- ✅ Agent can't forge manager approval
- ✅ Agent can't reuse old approvals (one_time=true)
- ✅ Agent can't use expired verifications (time_to_live)
- ✅ Small trades don't need manager approval (conditional)

### Comparison to Alternatives

| Approach | Forgeable? | Verifiable? | Temporal Control? | Conditional? | Infrastructure |
|----------|-----------|-------------|------------------|--------------|----------------|
| **Agent Memory** | ✅ Yes | ❌ No | ❌ No | ❌ No | None |
| **Blockchain** | ❌ No | ✅ Yes | ⚠️ Via contract | ⚠️ Complex | 🔴 High |
| **OAuth2 Scopes** | ❌ No | ✅ Yes | ✅ Yes | ❌ No | 🟡 Medium |
| **MAPL Attestations** | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | 🟢 Low |


---

## Part 6: Complete Tutorial Example

Let's build a complete policy system for a financial services company.

### The Scenario

**FinTech Corp** has:
- Company-wide security baseline
- **Analytics Business Unit** (BU)
  - **Reporting Team**
    - Alice (Junior Analyst)
    - Bob (Manager)
- All users access OpenAI for financial analysis

**Requirements:**
- Company: Max 4000 tokens, all OpenAI models allowed
- Analytics BU: Max 2000 tokens, low temperature for consistency
- Reporting Team: Max 1000 tokens
- Alice: GPT-3.5 only, 500 tokens max
- Bob: GPT-3.5 and GPT-4, 2000 tokens max
- Large trades (>$5000) require manager approval

### Step 1: Company-Wide Base Policy

```json
{
  "policy_id": "company:FinTech",
  "version": "1.0",
  "description": "FinTech Corp company-wide policy",

  "resources": [
    "llm:openai/*",
    "tool:trade/*"
  ],

  "denied_resources": [
    "*.secret",
    "*.password",
    "*.key"
  ],

  "attestations": [
    "identity_verified"
  ],

  "constraints": {
    "rate_limit": 100,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo", "gpt-4"],
        "max_tokens": {"max": 4000},
        "temperature": {"min": 0, "max": 1.0}
      }
    },
    "attestations": {
      "identity_verified": {
        "one_time": true,
        "time_to_live": 3600
      }
    }
  }
}
```

### Step 2: Analytics BU Policy

```json
{
  "policy_id": "bu:Analytics",
  "version": "1.0",
  "extends": "company:FinTech",
  "description": "Analytics BU - deterministic results",

  "attestations": [
    "trade_approved::{params.amount > 5000}"
  ],

  "constraints": {
    "rate_limit": 50,
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 2000},
        "temperature": {"max": 0.3},
        "seed": "required"
      }
    },
    "attestations": {
      "trade_approved": {
        "approval_criteria": "role:manager",
        "timeout": 300,
        "time_to_live": 3600,
        "one_time": true
      }
    }
  }
}
```

### Step 3: Reporting Team Policy

```json
{
  "policy_id": "team:Reporting",
  "version": "1.0",
  "extends": "bu:Analytics",
  "description": "Reporting team under Analytics BU",

  "constraints": {
    "rate_limit": 30,
    "parameters": {
      "llm:openai/chat.completions": {
        "max_tokens": {"max": 1000}
      }
    }
  }
}
```

### Step 4: Alice's Policy (Junior Analyst)

```json
{
  "policy_id": "user:alice",
  "version": "1.0",
  "extends": "team:Reporting",
  "description": "Alice - Junior Financial Analyst",

  "resources": [
    "llm:openai/chat.completions"
  ],

  "constraints": {
    "rate_limit": 10,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo"],
        "max_tokens": {"max": 500},
        "temperature": {"max": 0.5}
      }
    }
  },

  "denied_resources": [
    "data:executive/*",
    "data:confidential/*"
  ]
}
```

### Step 5: Bob's Policy (Manager)

```json
{
  "policy_id": "user:bob",
  "version": "1.0",
  "extends": "team:Reporting",
  "description": "Bob - Finance Manager",

  "constraints": {
    "rate_limit": 30,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo", "gpt-4"],
        "max_tokens": {"max": 2000},
        "temperature": {"max": 0.8}
      }
    }
  }
}
```

### Final Effective Policies

**Alice's Final Policy:**
```json
{
  "resources": ["llm:openai/chat.completions"],
  "denied_resources": ["*.secret", "*.password", "*.key", "data:executive/*", "data:confidential/*"],
  "attestations": ["identity_verified", "trade_approved::{params.amount > 5000}"],
  "constraints": {
    "rate_limit": 10,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo"],
        "max_tokens": {"max": 500},
        "temperature": {"max": 0.3},
        "seed": "required"
      }
    }
  }
}
```

**Bob's Final Policy:**
```json
{
  "resources": ["llm:openai/*", "tool:trade/*"],
  "denied_resources": ["*.secret", "*.password", "*.key"],
  "attestations": ["identity_verified", "trade_approved::{params.amount > 5000}"],
  "constraints": {
    "rate_limit": 30,
    "parameters": {
      "llm:openai/chat.completions": {
        "model": ["gpt-3.5-turbo", "gpt-4"],
        "max_tokens": {"max": 1000},
        "temperature": {"max": 0.3},
        "seed": "required"
      }
    }
  }
}
```

### Testing the Policies

**Alice tries to execute trades:**

```python
# Test 1: Alice, small trade ($1000)
client.invoke_tool("execute_trade", {"trade_id": "T-001", "amount": 1000})
# Result: ✅ SUCCESS (identity_verified required, trade_approved NOT required)

# Test 2: Alice, large trade ($10000)
client.invoke_tool("execute_trade", {"trade_id": "T-002", "amount": 10000})
# Result: ⏳ BLOCKS waiting for manager approval
# After Bob approves: ✅ SUCCESS
```

**Alice tries to call OpenAI:**

```python
# Test 1: Alice, gpt-3.5-turbo, 400 tokens
request = {"model": "gpt-3.5-turbo", "max_tokens": 400}
# Result: ✅ SUCCESS

# Test 2: Alice, gpt-3.5-turbo, 600 tokens
request = {"model": "gpt-3.5-turbo", "max_tokens": 600}
# Result: ❌ BLOCKED - "max_tokens=600 exceeds maximum: 500"

# Test 3: Alice, gpt-4, 400 tokens
request = {"model": "gpt-4", "max_tokens": 400}
# Result: ❌ BLOCKED - "model=gpt-4 not in allowed values"
```


---

## Part 7: Best Practices

### 1. Start with Least Privilege

Begin with restrictive base policies, then expand as needed:

```json
{
  "policy_id": "company:default-deny",
  "resources": [],
  "denied_resources": ["**"]
}
```

Then grant specific access through inheritance.

### 2. Use Clear Policy IDs

Follow a consistent naming convention:
- `company:{name}` - Top-level company policy
- `bu:{name}` - Business unit policies
- `team:{name}` - Team policies
- `user:{username}` - User-specific policies

### 3. Document Your Policies

Always include meaningful descriptions:

```json
{
  "policy_id": "team:analysts",
  "description": "Financial analysts: GPT-3.5 only, 500 token limit, read-only finance data",
  "version": "1.2.0"
}
```

### 4. Version Your Policies

Use semantic versioning:
- `1.0.0` - Initial policy
- `1.1.0` - Added new constraint (backward compatible)
- `2.0.0` - Breaking change (stricter limits)

### 5. Use Wildcards Wisely

```json
{
  "resources": [
    "llm:openai/chat.completions",      // ✅ Specific first
    "llm:openai/text-embedding-*",      // ✅ Targeted wildcard
    "tool:database/query"                // ✅ Specific operation
  ],
  "denied_resources": [
    "llm:openai/gpt-4*",                 // ✅ Block expensive models
    "admin:**"                           // ✅ Block all admin operations
  ]
}
```

Avoid overly broad wildcards like `**` unless necessary.

### 6. Leverage Inheritance for DRY

Don't repeat constraints across policies:

```json
// ✅ GOOD: Define once in company policy, inherit everywhere
// company.json
{
  "constraints": {
    "parameters": {
      "llm:**": {"max_tokens": {"max": 4000}}
    }
  }
}

// team-analysts.json (only override what changes)
{
  "extends": "company:acme",
  "constraints": {
    "parameters": {
      "llm:**": {"max_tokens": {"max": 500}}
    }
  }
}
```

### 7. Use Conditional Attestations for Tiered Approval

```json
{
  "attestations": [
    "team_lead_approval::{params.amount > 1000 AND params.amount <= 10000}",
    "manager_approval::{params.amount > 10000 AND params.amount <= 50000}",
    "director_approval::{params.amount > 50000}"
  ]
}
```

### 8. Set Appropriate Timeouts for External Attestations

```json
{
  "constraints": {
    "attestations": {
      "urgent_approval": {
        "approval_criteria": "role:manager",
        "timeout": 60,  // Short timeout for urgent
        "time_to_live": 300
      },
      "standard_approval": {
        "approval_criteria": "role:manager",
        "timeout": 3600,  // 1 hour for standard
        "time_to_live": 86400
      }
    }
  }
}
```

### 9. Use one_time for Security-Critical Attestations

```json
{
  "constraints": {
    "attestations": {
      "trade_approved": {
        "one_time": true  // Prevents replay attacks
      }
    }
  }
}
```

### 10. Monitor and Audit

Enable audit logging in your policies:

```json
{
  "constraints": {
    "audit_enabled": true,
    "audit_level": "detailed"
  }
}
```

Review audit logs regularly for:
- Unexpected denials
- Policy violations
- Usage patterns
- Attestation failures


---

## Part 8: Troubleshooting

### Common Issue 1: Access Denied Unexpectedly

**Symptom**: Request is denied but should be allowed.

**Debugging steps:**

1. **Check inheritance chain**: Verify which policies are being merged
   ```bash
   python -m macaw_agent.policy_cli list
   ```

2. **Check denied_resources**: Denials override allows
   ```json
   {
     "resources": ["llm:openai/*"],
     "denied_resources": ["llm:openai/gpt-4"]  // ← This blocks gpt-4!
   }
   ```

3. **Check wildcards**: Ensure patterns match intended resources
   - `llm:openai/*` matches `llm:openai/chat.completions`
   - But NOT `llm:openai/v1/chat.completions` (too deep!)
   - Use `llm:openai/**` for multi-level

4. **Check parameter constraints**: Even if resource is allowed, constraints can cause denial

5. **Enable debug logging**:
   ```bash
   export MACAW_LOG_LEVEL=DEBUG
   python your_app.py
   ```

### Common Issue 2: Attestation Verification Failing

**Symptom**: "Missing or invalid attestation" error but attestation was created.

**Debugging steps**:

1. **Check if attestation exists in context**:
   ```python
   print(context.attestations.keys())
   ```

2. **Check conditional attestation evaluation**:
   - Is the condition evaluating to true?
   - Check parameter values: `params.amount > 5000` - is amount actually > 5000?

3. **Enable attestation debug logging**:
   ```python
   import logging
   logging.getLogger("macaw.protocol.mcp_types").setLevel(logging.DEBUG)
   ```

4. **Check metadata expiration**:
   ```python
   # time_to_live expired?
   if "expires_at" in attestation:
       import time
       if time.time() > attestation["expires_at"]:
           print("Attestation expired!")

   # one_time already used?
   if attestation.get("one_time") and "use_count" in attestation:
       print("Attestation already consumed!")

   # max_uses exhausted?
   if attestation.get("max_uses") and attestation.get("use_count", 0) >= attestation["max_uses"]:
       print("Attestation max_uses exhausted!")
   ```

5. **Verify ToolAgent registered**:
   - Check if ToolAgent is in registry
   - Is the ToolAgent's public key in the registry?

### Common Issue 3: External Attestation Not Blocking

**Symptom**: Request denied immediately instead of waiting for approval.

**Debugging steps**:

1. **Check timeout value**:
   ```json
   {
     "attestations": {
       "trade_approved": {
         "timeout": 300  // Must be > 0 to block
       }
     }
   }
   ```

2. **Check approval_criteria is present**:
   - External attestations require `approval_criteria`
   - Without it, it's treated as internal attestation (no blocking)

3. **Check condition evaluation**:
   - Is the conditional attestation actually required?
   - Verify: `trade_approved::{params.amount > 5000}` - is amount > 5000?

### Common Issue 4: Conditional Attestation Not Triggering

**Symptom**: Attestation should be required but isn't.

**Debugging steps**:

1. **Check condition syntax**:
   ```json
   // ❌ Wrong operator
   "trade_approved::{params.amount => 5000}"  // Should be >=

   // ✅ Correct
   "trade_approved::{params.amount >= 5000}"
   ```

2. **Check parameter names**:
   ```json
   // ❌ Wrong parameter name
   "trade_approved::{params.total > 5000}"  // Should be 'amount'

   // ✅ Correct
   "trade_approved::{params.amount > 5000}"
   ```

3. **Check boolean logic**:
   ```json
   // Both must be true
   "approval::{params.a > 10 AND params.b < 5}"

   // Either can be true
   "approval::{params.a > 10 OR params.b < 5}"
   ```

### Common Issue 5: External Attestation Approval Rejected

**Symptom**: `"Not authorized: caller does not match criteria"` when approving.

**Debugging steps**:

1. **Check approver's roles match approval_criteria**:
   ```python
   # approval_criteria: "role:manager"
   # Bob's JWT must have "manager" in realm_access.roles
   claims = decode_jwt(bob_token)
   print(claims['realm_access']['roles'])  # Should include 'manager'
   ```

2. **Check approval_criteria format**:
   ```json
   // For exact user match
   "approval_criteria": "user:bob@acme.com"

   // For role match (most common)
   "approval_criteria": "role:manager"

   // Bare name (treated as role)
   "approval_criteria": "manager"
   ```

3. **Ensure approver is registered**:
   - Bob must call `client.register()` before approving
   - Bob's roles are extracted from JWT claims and stored in registry
   - Check: `metadata.principal.roles` in registry entry

4. **Verify approver can see the attestation**:
   ```python
   # Bob should see attestations matching his approval_criteria
   attestations = client.list_attestations(status="pending")
   if not attestations:
       print("No pending attestations visible - check roles")
   ```

### Getting Help

If you encounter issues:
1. Check audit logs for policy evaluation traces
2. Enable DEBUG logging for detailed policy resolution
3. Use `python -m macaw_agent.policy_cli validate` to test policies
4. Review this guide's examples and best practices


---

## Appendix A: Quick Reference

### Complete Policy Template

```json
{
  "policy_id": "{scope}:{name}",
  "version": "1.0",
  "extends": "parent-policy-id",
  "description": "Human-readable description",

  "resources": [
    "operation-pattern",
    "llm:openai/*",
    "tool:database/query"
  ],

  "denied_resources": [
    "admin:**",
    "*.secret"
  ],

  "attestations": [
    "always_required",
    "conditional::{params.amount > threshold}"
  ],

  "constraints": {
    "rate_limit": 100,
    "max_requests": 1000,
    "timeout": 30000,

    "parameters": {
      "operation-pattern": {
        "param_name": {
          "type": "integer|number|string|boolean|array|object",
          "min": 0,
          "max": 1000,
          "range": [0, 1000],
          "allowed_values": ["value1", "value2"],
          "pattern": "^regex$",
          "min_length": 1,
          "max_length": 100,
          "min_items": 1,
          "max_items": 100
        }
      }
    },

    "denied_parameters": {
      "operation-pattern": {
        "param_name": ["*dangerous*", "*DROP TABLE*"]
      }
    },

    "attestations": {
      "attestation_key": {
        "approval_criteria": "role:manager",
        "timeout": 300,
        "time_to_live": 3600,
        "one_time": true,
        "max_uses": 1,
        "priority": 100
      }
    }
  }
}
```

### Attestation Formats

**Simple (always required):**
```json
"attestations": ["identity_verified", "mfa_complete"]
```

**Conditional (required when condition is true):**
```json
"attestations": [
  "trade_approved::{params.amount > 5000}",
  "manager_override::{params.priority == 'urgent' AND params.amount > 10000}"
]
```

### Condition Syntax Reference

| Pattern | Example |
|---------|---------|
| Parameter comparison | `params.amount > 5000` |
| String equality | `params.currency == 'USD'` |
| In list | `params.region IN ('us', 'eu')` |
| Glob pattern match | `params.command MATCHES '*rm -rf*'` |
| Role check | `principal.has_role('manager')` |
| Group check | `principal.has_group('trading')` |
| Attestation check | `context.has_attestation('mfa')` |
| Boolean AND | `params.a > 10 AND params.b < 5` |
| Boolean OR | `params.x == 'a' OR params.x == 'b'` |
| Negation | `NOT params.override` |
| Grouping | `(params.a AND params.b) OR params.c` |

### Attestation Metadata Fields

| Field | Type | Description |
|-------|------|-------------|
| `approval_criteria` | string | Who can approve (makes it external attestation) |
| `timeout` | integer | Seconds to wait (0 = don't block, >0 = block) |
| `time_to_live` | integer | Validity duration after approval (seconds) |
| `one_time` | boolean | Consume after single use |
| `max_uses` | integer | Maximum verification count |
| `priority` | integer | Processing order (higher = first, default: 0) |

### Attestation Status Lifecycle

| Status | Description |
|--------|-------------|
| `pending` | Awaiting approval (external) or set (internal) |
| `approved` | Approved by authorized party |
| `denied` | Rejected by authorized party |
| `expired` | Time window (TTL) elapsed |
| `approved` + `alive=false` | Consumed: one-time attestation used (status remains `approved`, but inactive) |

### approval_criteria Format

| Format | Example | Matches |
|--------|---------|---------|
| `user:xxx` | `user:alice` | caller_id or email == "alice" |
| `user:xxx@domain` | `user:alice@fintech.com` | Exact match on caller_id or email |
| `role:xxx` | `role:manager` | Approver has role from IDP/JWT |
| `xxx` (bare) | `manager` | Treated as `role:manager` |

### Attestation Audit Events

| Event | When | Key Fields |
|-------|------|------------|
| `attestation_created` | Pending request created | `key`, `for_agent`, `approval_criteria` |
| `attestation_approved` | Approver granted | `approved_by`, `reason`, `signature` |
| `attestation_denied` | Approver rejected | `denied_by`, `reason` |
| `attestation_accessed` | Grant reused (one_time=false) | `invocation_id`, `accessed_by` |
| `attestation_consumed` | One-time used up | `consumed_at` |
| `attestation_expired` | Timeout/TTL exceeded | `expires_at`, `reason` |

### All Constraint Types

| Constraint | Applies To | Format | Example |
|------------|-----------|--------|---------|
| `type` | All | String | `{"type": "integer"}` |
| `min` | Numbers | Number | `{"min": 0}` |
| `max` | Numbers | Number | `{"max": 4000}` |
| `range` | Numbers | `[min, max]` | `{"range": [0, 100]}` |
| `allowed_values` | Strings | Array | `{"allowed_values": ["A", "B"]}` |
| `pattern` | Strings | Regex | `{"pattern": "^[a-z]+$"}` |
| `min_length` | Strings | Integer | `{"min_length": 3}` |
| `max_length` | Strings | Integer | `{"max_length": 255}` |
| `min_items` | Arrays | Integer | `{"min_items": 1}` |
| `max_items` | Arrays | Integer | `{"max_items": 100}` |

### Inheritance Merge Rules

| Constraint | Merge Strategy | Example |
|------------|---------------|---------|
| `resources` | Domain-Aware | Child restricts within domains it mentions |
| `denied_resources` | Union | Both enforced |
| `attestations` | Union | All required |
| `range` | Intersection | Parent `[0,100]` + Child `[10,50]` = `[10,50]` |
| `min` | Maximum | Parent `min:0` + Child `min:10` = `min:10` |
| `max` | Minimum | Parent `max:100` + Child `max:50` = `max:50` |
| `allowed_values` | Intersection | Parent `["A","B","C"]` + Child `["B","C","D"]` = `["B","C"]` |
| `denied_parameters` | Union | Both patterns enforced |
| `priority` | Maximum | Parent `0` + Child `100` = `100` (higher wins) |


---

## Appendix B: Theoretical Foundations

### Composition Algebra

A policy P = (R, D, A, C) consists of:
- **R**: Allowed resource patterns
- **D**: Denied resource patterns
- **A**: Required attestations (simple and conditional)
- **C**: Operational constraints (parameters, attestation metadata)

**Policy Intersection** P1 ∩ P2 = (R', D', A', C') where:
- R' = R1 ∩ R2 (allow only if both policies permit)
- D' = D1 ∪ D2 (deny if either policy forbids)
- A' = A1 ∪ A2 (all attestations required)
- C' = MostRestrictive(C1, C2) (apply tightest constraint)

### Formal Security Properties

**Theorem 1 (Monotonic Restriction)**: For composition P0 ∩ ... ∩ Pn:

∀i, j : (i < j) ⇒ π(P0 ∩ ... ∩ Pj) ⊆ π(P0 ∩ ... ∩ Pi)

**Proof Sketch**: By construction, Pi+1 = Pi ∩ Pnext. Resource intersection: Ri+1 ⊆ Ri. Denial union: Di+1 ⊇ Di. Therefore π(Pi+1) ⊆ π(Pi). By induction, π(Pj) ⊆ π(Pi) for i < j. □

**Theorem 2 (Transitive Denial)**: If resource r is denied by any policy, it remains denied:

∃i: r ∈ Match(Di) ⇒ r ∈ Match(Deff)

**Proof Sketch**: Deff = D0 ∪ D1 ∪ ... ∪ Dn. If r ∈ Di for any i, then r ∈ Deff by set union. □

**Theorem 3 (No Privilege Escalation)**: If base policy P0 denies r, no composition grants access:

(r ∈ Match(D0)) ⇒ r ∉ Allowed(Peff)

**Proof**: By Theorem 2, if r ∈ Match(D0), then r ∈ Match(Deff). By definition of π, denied resources cannot be allowed. □

### Attestation Security Properties

**Theorem 4 (Unforgeable Attestations)**: An adversary without access to a ToolAgent's private key cannot create a valid attestation that verifies under that ToolAgent's public key.

**Proof Sketch**: Follows from the security of the signature scheme (RSA/Ed25519). □

**Theorem 5 (Metadata Binding)**: An adversary cannot modify attestation metadata (time_to_live, one_time, max_uses) without invalidating the signature.

**Proof Sketch**: Metadata is included in the canonical representation signed by the ToolAgent. Any modification changes the signed data, causing signature verification to fail. □

**Theorem 6 (Conditional Attestation Soundness)**: A conditional attestation `key::{condition}` is required if and only if the condition evaluates to true at runtime.

**Proof Sketch**: The condition evaluator has access to the complete invocation context (params, principal, existing attestations). Evaluation is deterministic and side-effect-free. □

### Security Implications

These theorems provide formal guarantees:
- **Theorem 1** prevents privilege expansion
- **Theorem 2** ensures any layer's denial is absolute
- **Theorem 3** prevents escalation
- **Theorems 4-6** ensure attestations are cryptographically verifiable and unforgeable
- **Theorem 6** ensures conditional attestations are correctly enforced


---

## Appendix C: Future Enhancements

The following features are planned for future releases of MAPL:

### Phase 2 Enhancements

**Enhanced Error Messages with Policy Context:**
```json
// Current error:
"Parameter 'max_tokens'=600 exceeds maximum: 500"

// Future error:
"Parameter 'max_tokens'=600 exceeds maximum: 500
  Policy Chain: company:acme → bu:finance → team:analysts → user:alice
  Constraint Source: team:analysts.json (line 15)
  Resolved Path: 4000 → 2000 → 500 (most restrictive)"
```

**Performance Optimizations:**
- Condition evaluation caching
- Policy resolution caching
- Optimized inheritance chain evaluation

### Phase 3 Enhancements (Advanced)

**Array Item Validation:**
```json
{
  "constraints": {
    "parameters": {
      "database:batch_insert": {
        "records": {
          "type": "array",
          "max_items": 1000,
          "items": {
            "type": "object",
            "required": ["id", "name"]
          }
        }
      }
    }
  }
}
```

**Nested Object Validation:**
```json
{
  "constraints": {
    "parameters": {
      "user:create": {
        "profile": {
          "type": "object",
          "properties": {
            "email": {"pattern": "^[^@]+@[^@]+$"},
            "age": {"min": 18, "max": 120}
          }
        }
      }
    }
  }
}
```


---

## Conclusion

Congratulations! You now know how to write MAPL policies for AI agent systems.

**Key Takeaways:**

1. **MAPL scales** from O(M×N) to O(log M + N) through hierarchical composition
2. **Resources ARE operations** (no separate actions needed)
3. **Inheritance composes via intersection** (most restrictive always wins)
4. **Denials propagate** (no overrides, provably secure)
5. **Parameter constraints** provide fine-grained control
6. **Attestations** enable provable workflow dependencies
7. **Conditional attestations** allow dynamic, context-aware requirements
8. **External attestations** support human-in-the-loop workflows
9. **Deferred principal binding** enables dynamic identity resolution

**Next Steps:**

1. Write policies for your organization
2. Test with the policy CLI
3. Deploy and monitor
4. Iterate based on usage patterns

**Need Help?**

- Review the examples in this guide
- Check the troubleshooting section
- Enable debug logging
- Consult the quick reference

Happy policy writing!

---

**Document Version:** 3.0.1
**MACAW SDK Version:** 3.x
**Last Updated:** January 27, 2026

---

Copyright MACAW Security. All rights reserved.
