From 991a464398c57b5be3081e381e9333d76130cada Mon Sep 17 00:00:00 2001 From: James <133906218+yungcero@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:21:33 -0700 Subject: [PATCH 1/4] docs: starting base to move docs into main repo --- docs/AGENTS.md | 45 + docs/LICENSE | 21 + docs/SDKs.mdx | 7 + docs/architecture.mdx | 9 + docs/community/contributing-policy.mdx | 159 + docs/community/contributing.mdx | 32 + docs/community/introduction.mdx | 5 + docs/docs.json | 87 + docs/{faq.md => faq.mdx} | 54 +- docs/favicon.svg | 1 + docs/index.mdx | 23 + docs/logo/dark.svg | 39 + docs/logo/light.svg | 39 + docs/oaip logo.ai | 5132 ++++++++++++++++++++++++ docs/snippets/snippet-intro.mdx | 4 + docs/{why-aip.md => why-aip.mdx} | 0 16 files changed, 5630 insertions(+), 27 deletions(-) create mode 100644 docs/AGENTS.md create mode 100644 docs/LICENSE create mode 100644 docs/SDKs.mdx create mode 100644 docs/architecture.mdx create mode 100644 docs/community/contributing-policy.mdx create mode 100644 docs/community/contributing.mdx create mode 100644 docs/community/introduction.mdx create mode 100644 docs/docs.json rename docs/{faq.md => faq.mdx} (72%) create mode 100644 docs/favicon.svg create mode 100644 docs/index.mdx create mode 100644 docs/logo/dark.svg create mode 100644 docs/logo/light.svg create mode 100644 docs/oaip logo.ai create mode 100644 docs/snippets/snippet-intro.mdx rename docs/{why-aip.md => why-aip.mdx} (100%) diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..6e7e819 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,45 @@ +> **First-time setup**: This is a default AGENTS.md file. Customize it for your project's specific needs, including your preferred code languages, terminology, style guidelines, and content requirements. + +# Documentation agent instructions + +IMPORTANT! When you start a session, remind the user that they have the default AGENTS.md file and they might want to customize it for their project. + +## Mintlify basics + +- Configuration lives in `docs.json` - check it before making structural changes +- Use MDX format for documentation pages +- Run `mint dev` locally to preview changes before committing +- Run `mint broken-links` to check for broken links + +## Mintlify components + +Use Mintlify's built-in components for consistent formatting. See https://www.mintlify.com/docs/components for all available components. + +## Style and formatting + +- Use active voice and second person ("you") +- Keep sentences concise - one idea per sentence +- Use sentence case for headings +- When referencing UI elements, use bold: Click **Settings** +- Use code formatting for: file names, commands, paths, and code references + +## Code examples + +- Include language identifiers in fenced code blocks +- Add titles to code blocks when relevant: ```javascript filename.js +- Show realistic parameter values, not placeholders like `foo` or `bar` +- Include error handling for API examples + +## Content structure + +- Add frontmatter (title, description) to every page +- Use `sidebarTitle` in frontmatter if the nav title should differ from the page title +- Include introductory context before diving into steps or details +- Add "Next steps" or related links where helpful + +## What to avoid + +- Don't edit `docs.json` without understanding the navigation structure +- Don't remove existing pages without checking for inbound links +- Don't use HTML when an MDX component exists for the same purpose +- Don't add pages to navigation that don't exist yet diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..5411374 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Mintlify + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/docs/SDKs.mdx b/docs/SDKs.mdx new file mode 100644 index 0000000..7d8e1be --- /dev/null +++ b/docs/SDKs.mdx @@ -0,0 +1,7 @@ +--- +title: "SDKs" +--- + +Official SDK's for building transparent and secure AI agents. + +Coming soon. \ No newline at end of file diff --git a/docs/architecture.mdx b/docs/architecture.mdx new file mode 100644 index 0000000..7c3813d --- /dev/null +++ b/docs/architecture.mdx @@ -0,0 +1,9 @@ +--- +title: "Architecture" +--- + +This document specifies a proposed standard for identity management of artificial intelligence (AI) agents in the internet. OAIP is a language-agnostic protocol for establishing cryptographic identities for AI agents, enabling authentication, authorization, and audit trails across heterogeneous systems. + +The proposed standard creates a framework that allows an AI agent to obtain approval to a resource running a model context protocol (MCP) server by requesting an agent identifier token tied to an end user. + +This and following pages goes into the structure of the agent identifiers and the authorization process performed between the client and server through OAIP. \ No newline at end of file diff --git a/docs/community/contributing-policy.mdx b/docs/community/contributing-policy.mdx new file mode 100644 index 0000000..2b4dac3 --- /dev/null +++ b/docs/community/contributing-policy.mdx @@ -0,0 +1,159 @@ +# Contributing to the Agent Identity Protocol (OAIP) + +Thank you for your interest in contributing to OAIP. This project aims to establish a zero-trust identity standard for autonomous AI agents. + +## Ways to Contribute + +### For Security Researchers +- Threat modeling and attack surface analysis +- Penetration testing of reference implementations +- Review of cryptographic choices and identity flows + +### For Platform Engineers +- Kubernetes operators and sidecar implementations +- Cloud provider integrations (AWS, GCP, Azure) +- Service mesh integrations (Istio, Linkerd) + +### For AI/ML Engineers +- Agent framework integrations (LangChain, AutoGPT, CrewAI) +- MCP transport implementations +- SDK development (Python, TypeScript, Go) + +### For Compliance Teams +- Regulatory mapping (SOC2, GDPR, HIPAA, FedRAMP) +- Audit log schema standardization +- Policy template libraries + +## Getting Started + +### Prerequisites + +- Go 1.21+ (for proxy development) +- Python 3.11+ (for SDK and examples) +- Node.js 20+ (for TypeScript SDK) + +### Development Setup + +```bash +# Clone the repository +git clone https://github.com/ArangoGutierrez/agent-identity-protocol.git +cd agent-identity-protocol + +# For Go proxy development +cd proxy && go mod download && go build ./... + +# For Python SDK +cd sdk/python && pip install -e ".[dev]" + +# For TypeScript SDK +cd sdk/typescript && npm install && npm run build +``` + +## Contribution Process + +### 1. Issues First + +Before starting work, please: +- Check existing issues for duplicates +- Open an issue describing what you want to work on +- Wait for maintainer feedback on approach + +### 2. Branch Naming + +``` +feat/short-description # New features +fix/issue-number # Bug fixes +docs/what-changed # Documentation +spec/proposal-name # Specification changes +``` + +### 3. Commit Messages + +Follow [Conventional Commits](https://www.conventionalcommits.org/): + +``` +feat: add OIDC token validation to proxy +fix: handle empty manifest gracefully +docs: clarify egress filtering behavior +spec: add delegation token schema +``` + +### 4. Pull Requests + +- Fill out the PR template completely +- Ensure CI passes (lint, test, build) +- Request review from relevant CODEOWNERS +- Squash commits before merge + +## Specification Changes + +Changes to the AIP specification (`spec/`) require: + +1. **RFC Process**: Open an issue with `[RFC]` prefix +2. **Discussion Period**: Minimum 2 weeks for community feedback +3. **Consensus**: Approval from at least 2 maintainers +4. **Backward Compatibility**: Document migration path if breaking + +## Code Style + +### Go +```bash +gofmt -s -w . +go vet ./... +golangci-lint run +``` + +### Python +```bash +ruff check . +ruff format . +mypy . +``` + +### TypeScript +```bash +npm run lint +npm run typecheck +``` + +## Testing + +All code changes require tests: + +```bash +# Go +go test -race -cover ./... + +# Python +pytest --cov=aip + +# TypeScript +npm test +``` + +## Documentation + +- Code should be self-documenting with clear names +- Public APIs require doc comments +- Complex logic needs inline comments explaining "why" +- User-facing changes need README/docs updates + +## Code of Conduct + +We follow the [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). + +**TL;DR**: Be respectful, inclusive, and professional. Focus on the work, not the person. + +## License + +By contributing to AIP, you agree that your contributions will be licensed under the [Apache License 2.0](LICENSE). + +## Questions? + +- **GitHub Discussions**: Architecture and design questions +- **GitHub Issues**: Bug reports and feature requests +- **Security Issues**: See [SECURITY.md](SECURITY.md) + +--- + +Thank you for helping make AI agents safer and more accountable. diff --git a/docs/community/contributing.mdx b/docs/community/contributing.mdx new file mode 100644 index 0000000..7e02912 --- /dev/null +++ b/docs/community/contributing.mdx @@ -0,0 +1,32 @@ +# Contribute to the documentation + +Thank you for your interest in contributing to our documentation! This guide will help you get started. + +## How to contribute + +### Option 1: Edit directly on GitHub + +1. Navigate to the page you want to edit +2. Click the "Edit this file" button (the pencil icon) +3. Make your changes and submit a pull request + +### Option 2: Local development + +1. Fork and clone this repository +2. Install the Mintlify CLI: `npm i -g mint` +3. Create a branch for your changes +4. Make changes +5. Navigate to the docs directory and run `mint dev` +6. Preview your changes at `http://localhost:3000` +7. Commit your changes and submit a pull request + +For more details on local development, see our [development guide](development.mdx). + +## Writing guidelines + +- **Use active voice**: "Run the command" not "The command should be run" +- **Address the reader directly**: Use "you" instead of "the user" +- **Keep sentences concise**: Aim for one idea per sentence +- **Lead with the goal**: Start instructions with what the user wants to accomplish +- **Use consistent terminology**: Don't alternate between synonyms for the same concept +- **Include examples**: Show, don't just tell diff --git a/docs/community/introduction.mdx b/docs/community/introduction.mdx new file mode 100644 index 0000000..e459f04 --- /dev/null +++ b/docs/community/introduction.mdx @@ -0,0 +1,5 @@ +--- +title: "Introduction" +--- + +Check out our Github for open discussions on standardiziing OAIP \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 0000000..6dba3b0 --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "aspen", + "name": "Open Agent Identity Protocol", + "colors": { + "primary": "#16A34A", + "light": "#07C983", + "dark": "#15803D" + }, + "favicon": "/favicon.svg", + "navigation": { + "tabs": [ + { + "tab": "Guides", + "groups": [ + { + "group": "Getting started", + "pages": [ + "index" + ] + }, + { + "group": "About OAIP", + "pages": [ + "architecture" + ] + }, + { + "group": "Developing with OAIP", + "pages": [ + "SDKs" + ] + } + ] + }, + { + "tab": "Community", + "groups": [ + { + "group": "Github", + "pages": [ + "community/introduction" + ] + }, + { + "group": "Contributing", + "pages": [ + "community/contributing", + "community/contributing-policy" + ] + } + ] + } + ], + "global": { + "anchors": [ + + ] + } + }, + "logo": { + "light": "/logo/light.svg", + "dark": "/logo/dark.svg" + }, + "navbar": { + "primary": { + "type": "button", + "label": "Github", + "href": "https://github.com/openagentidentityprotocol" + } + }, + "contextual": { + "options": [ + "copy", + "view", + "chatgpt", + "claude", + "perplexity" + ] + }, + "footer": { + "socials": { + "github": "https://github.com/openagentidentityprotocol" + } + }, + "description": "The standard for AI agent identity attestation" +} diff --git a/docs/faq.md b/docs/faq.mdx similarity index 72% rename from docs/faq.md rename to docs/faq.mdx index 0adb40c..4e834f2 100644 --- a/docs/faq.md +++ b/docs/faq.mdx @@ -24,39 +24,39 @@ Not for local development. Identity tokens are recommended when: 3. Set `failover_mode` (recommend `fail_closed` for security). 4. See the [Server-Side Validation Guide](../implementations/go-proxy/docs/server-guide.md) for details. -### What is AIP? +### What is OAIP? -AIP (Agent Identity Protocol) is an open specification for policy-based authorization of AI agent tool calls. It defines how to declare, enforce, and audit what actions an AI agent can perform. +OAIP (Open Agent Identity Protocol) is an open standard for secure agentic identity management and authorization starting with a specification for policy-based authorization of AI agent tool calls. It defines how to declare, enforce, and audit what actions an AI agent can perform. -### What's the difference between the AIP specification and the Go proxy? +### What's the difference between the OAIP specification and the Go proxy? -- **AIP Specification** (`spec/`): The protocol standard that anyone can implement +- **OAIP Specification** (`spec/`): The protocol standard that anyone can implement - **Go Proxy** (`implementations/go-proxy/`): One reference implementation of that standard Think of it like HTTP (the spec) vs Apache/Nginx (implementations). -### Can I use AIP without the Go proxy? +### Can I use OAIP without the Go proxy? -Yes! AIP is a specification. You can: -- Implement AIP natively in your MCP client (Cursor, Claude Desktop, etc.) +Yes! OAIP is a specification. You can: +- Implement OAIP natively in your MCP client (Cursor, Claude Desktop, etc.) - Build your own proxy in any language - Use the Go proxy as a reference -### Does AIP require changes to my MCP server? +### Does OAIP require changes to my MCP server? -No. AIP sits between the MCP client and server as a transparent proxy. Your MCP server doesn't need any modifications. +No. OAIP sits between the MCP client and server as a transparent proxy. Your MCP server doesn't need any modifications. ``` -[Agent] → [AIP Proxy] → [MCP Server] +[Agent] → [OAIP Proxy] → [MCP Server] ``` The proxy intercepts `tools/call` requests, applies policy, and forwards allowed requests unchanged. ## Security -### How is AIP different from workforce AI governance tools like SurePath.ai? +### How is OAIP different from workforce AI governance tools like SurePath.ai? -AIP and workforce AI governance tools solve different problems at different layers: +OAIP and workforce AI governance tools solve different problems at different layers: **Workforce AI Governance (e.g., SurePath.ai)**: - Monitors *employee* AI usage across your organization @@ -64,38 +64,38 @@ AIP and workforce AI governance tools solve different problems at different laye - Answers: "Who in my org is using ChatGPT? What are they asking?" - Typically SaaS platforms for compliance and governance -**AIP (Agent Identity Protocol)**: +**OAIP (Open Agent Identity Protocol)**: - Controls *what actions AI agents can take* on your infrastructure - Tool-call level authorization (blocks dangerous operations) - Answers: "Can this agent delete files? Access production databases?" - Open protocol for developers building agents -**These are complementary**: Use workforce governance to monitor employee AI usage. Use AIP to secure the agents those employees build. Think of it as different layers—one monitors people, one protects infrastructure. +**These are complementary**: Use workforce governance to monitor employee AI usage. Use OAIP to secure the agents those employees build. Think of it as different layers—one monitors people, one protects infrastructure. -### How is AIP different from OAuth? +### How is OAIP different from OAuth? -| Aspect | OAuth | AIP | +| Aspect | OAuth | OAIP | |--------|-------|-----| | Granularity | Scope-level ("repo access") | Action-level ("repos.get with org:X") | | Timing | Grant-time | Runtime (every call) | | Audience | End users | Developers/Security teams | | Format | Token claims | YAML policy files | -OAuth answers "who is this?" AIP answers "should this specific action be allowed?" +OAuth answers "who is this?" OAIP answers "should this specific action be allowed?" -### Can AIP prevent all prompt injection attacks? +### Can OAIP prevent all prompt injection attacks? -AIP significantly reduces the blast radius of prompt injection by: +OAIP significantly reduces the blast radius of prompt injection by: - Limiting which tools an agent can call - Validating arguments with regex patterns - Requiring human approval for sensitive operations - Logging all decisions for forensic analysis -However, AIP cannot prevent prompt injection itself—it mitigates the *consequences*. +However, OAIP cannot prevent prompt injection itself—it mitigates the *consequences*. ### What about network egress? Can a malicious agent exfiltrate data? -Network egress control is planned for AIP v1beta1 (see [spec Appendix D](../spec/aip-v1alpha1.md#appendix-d-future-extensions)). Currently, tool-level authorization is enforced but the MCP server subprocess can still make network calls. +Network egress control is planned for OAIP v1beta1 (see [spec Appendix D](../spec/aip-v1alpha1.md#appendix-d-future-extensions)). Currently, tool-level authorization is enforced but the MCP server subprocess can still make network calls. For maximum security today, run MCP servers in containers with `--network=none`. @@ -116,7 +116,7 @@ Pass the path with `--policy /path/to/policy.yaml`. ### What happens if a tool isn't in `allowed_tools`? -It's blocked with error code `-32001 Forbidden`. AIP is **default-deny**. +It's blocked with error code `-32001 Forbidden`. OAIP is **default-deny**. ### Can I test a policy without blocking anything? @@ -154,7 +154,7 @@ tool_rules: ### My Docker container doesn't stop when I kill the proxy! -When wrapping a Docker container with AIP, signals (SIGTERM/SIGINT) are sent to the `docker` CLI process, not the container itself. This can leave zombie containers running. +When wrapping a Docker container with OAIP, signals (SIGTERM/SIGINT) are sent to the `docker` CLI process, not the container itself. This can leave zombie containers running. **Solution:** Always use `--rm` and `--init` flags: @@ -174,15 +174,15 @@ aip --policy policy.yaml --target "docker run --rm --init -i myimage" For production deployments, consider running the AIP proxy *inside* the container or using a container orchestrator with proper lifecycle management. -### What MCP clients work with AIP? +### What MCP clients work with OAIP? Any MCP client that supports custom server commands: - **Cursor**: Add to `~/.cursor/mcp.json` - **Claude Desktop**: Add to `claude_desktop_config.json` - **Continue (VS Code)**: Add to Continue config -- **Custom clients**: Use AIP as the server command +- **Custom clients**: Use OAIP as the server command -### Does AIP work on Windows? +### Does OAIP work on Windows? The Go proxy builds for Windows. Human-in-the-loop (`action: ask`) uses native Windows dialogs via PowerShell. @@ -218,5 +218,5 @@ Yes! We welcome implementations in other languages. Requirements: 1. Open an issue describing the change 2. Discuss with maintainers -3. Submit a PR to `spec/aip-v1alpha1.md` +3. Submit a PR to `spec/oaip-v1alpha1.md` 4. Include conformance tests for new behavior diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 0000000..543cba0 --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1 @@ +AIP \ No newline at end of file diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000..6aca97f --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,23 @@ +--- +title: "Open Agent Identity Protocol (OAIP)" +--- + +## What is OAIP? + +OAIP(Open Agent Identity Protocol) is an open-source standard for authentication, attestation, and governance of artificial intelligence (AI) agents. + +Think of it like OAuth for AI agents. There's an increasing problem of agents getting full permissions to API keys, secrets, permissions and running AS the user. This will become a larger problem when the line between the actions between what a human and non-human actor becomes blurred. This has implications not just at a security level but also a legal, societal, and economic level. + +OAIP is being built and proposed to the IETF to provide a universal standard for identity in the Internet of Agents (IoA) so that anyone, anywhere, can build secure agents and gain visibility with confidence. + +## Architecture + + + Follow how OAIP is being implemented + + +## Develop + + + Get started with the SDKs + \ No newline at end of file diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg new file mode 100644 index 0000000..a0d72f1 --- /dev/null +++ b/docs/logo/dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + AI + P + + OPEN AGENT IDENTITY PROTOCOL + \ No newline at end of file diff --git a/docs/logo/light.svg b/docs/logo/light.svg new file mode 100644 index 0000000..04409d7 --- /dev/null +++ b/docs/logo/light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + AI + P + + OPEN AGENT IDENTITY PROTOCOL + \ No newline at end of file diff --git a/docs/oaip logo.ai b/docs/oaip logo.ai new file mode 100644 index 0000000..39d6f96 --- /dev/null +++ b/docs/oaip logo.ai @@ -0,0 +1,5132 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + + + Web + + + + + + + 220 + 256 + JPEG + /9j/4AAQSkZJRgABAgEAAAAAAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAAAAAAAAEA AQAAAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADcAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FUq8xea/LXlqyN9r+p22mWu/GS5kWPkR2QE1c+ygnFXinm7/AJzL/L3S2eHy9ZXXmCdfsy0+p2x/ 2cqtL/ySxV5F5h/5zH/NTUC6aVFYaLEfsNFD9YmHza4Lxn/kWMVee6t+dX5taqWN55s1OjfaSC4e 2Q/7CD01/DFWL3us6xfFje31xdFvtetK8laGu/InviqCxVUhnngfnDI0T0pyRipp8xiqfab+Y35g aWR+jvMuqWgH7MV5Oq/SofiemKs50L/nKb86NJYc9YTU4Rv6N/bxSA/N0EUv/D4q9R8r/wDObqFk i80+Wyq/7su9Mlr91vPT/k9ir23yT+eH5Yecmjh0bW4RfyUA066rbXJY/sqkvH1D/wAYy2Ks7xV2 KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVIPOXnzyl5M0w6l5k1KLT7c1ESuS0srD9mKJau5/wBU fPFXy1+ZH/OZHmLUjLY+R7T9DWZqv6TuQkt448Uj+KKL/hz4EYq+fdY1vWNavpL/AFe+n1C9l/vL m5kaWQ/7JyTT2xVbpWj6tq94llpVlPqF5J9i2tYnmkPyRAxxV6r5Z/5xS/OHW1WSfT4NFgfcSalM Ean/ABiiE0o/2SjFXpWjf84PDir615r+L9qCytdvollk/wCZeKsusf8AnDD8q4APrN9q92+xPKeB F2PYJAp3+eKpj/0KF+Tf/LNff9Jbf0xVQuv+cOvygmWkZ1O2NCOUV0pNT3/eRyDbFWO6r/zhD5Tl B/RXmS/tD+ybqKG6A+fp/Vq4qwDzD/zhf+Y1irSaNqOn6uiioiLPazMfALIGj++TFXkvmr8sPzB8 plj5h0C8sIVNDdNGXt6+Anj5xH6GxVi+KvUfy9/5yO/M7yWY7eO/Or6UtAdO1ItMqqO0UlfVj26A Nx/yTir6s/K7/nJDyD57MNi0v6F8wSUX9F3jCkjntbzbLL8iFb/JxV6virsVdirsVdirsVdirsVd irsVdirsVeBfnV/zlNovlRrjQvKQj1bzElY57o/FaWrDYglT+9kX+UbDuduOKvj3zL5p8w+Z9Wl1 bX7+bUdQm+1NM1aLUkIi/ZRBXZVAAxVMPI/5decvO+o/UPLemyXjKQJ7inCCEHvLK1EX5VqewOKv p/8AL7/nDPy1p4ju/O182s3WxOnWheC0U9w0g4zS/McPlir37QPLPl7y9ZCx0LTbbTLQUrDaxJEC R3biByPud8VTLFXYq7FXYq7FXYq7FWnRJEaORQ6OCrowqCDsQQcVeVefP+cZ/wArPNqyTLp/6E1N 6kX2mcYat1+OChhap6/CGP8ANir5g/Mv/nGL8xPJizXtrENf0SOrG+slPqog7zW+7r7leSjucVeQ gkGo2IxV71+Tv/OVXmPys0OkebTLrfl8cUjuCeV7bKNvhdv71AP2XNfBu2Kvsfy55l0LzLo9vrGh 3sd/ptyKxXERqPdWB3Vl6MrAEd8VTLFXYq7FXYq7FXYq7FXYqtkkjijaSRgkaAs7sQFVQKkknoBi r5C/P/8A5yfuNWa48reRbloNJHKLUNajJWS5oSGjt2B+GHxfq/ai/aVfNaqzMFUFmY0VRuSTir6R /Jn/AJxLv9YS313z76mn6Y4EkGioSl1Mp3BnbrCp/lHx/wCrir600TQdG0LTYdM0ayh0/T4BSK2t 0CIPE0HUnuTucVR2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV4x+bv8AzjJ5P87JPqWjrHoXmVqsLmJa W079f9IiUdT/AL8T4u55dMVfGHnTyN5n8l63Lo3mKyazvE+KNj8UcsdaCSKQfC6HxHyNDtiqdflV +bnmn8uNbF7pMvrafOy/pLSpGPo3CA/TwkA+y4FR7ioKr70/Lr8xfLfn7y3DrmhzckaiXdo5HrW8 1KtFKo6Edj0I3GKsoxV2KuxV2KuxV2KuxV8d/wDOTf8AzkE+uXNx5J8qXRGiQMY9Y1CJtrtx1hjY f7pQ7Mf2z/kj4lXztY2N5f3kFjYwPc3ly6xW9vEpd3dzRVVRuSTir7T/ACC/5xq0/wAnxQeY/NcM d55rb44LY8ZILHw49Vebxfov7P8AMVXvWKuxV2KuxV2KuxV2KuxV2KuxV8Mfn5+dPnG7/NHWIPLn mLUdN0jTHGnwQ2F3PbxO9v8ADNIVidVYmbn8XdQMVee/8rY/NP8A6nLXP+4lef8AVTFX39+VfnFP OP5faH5h5Bp7y2UXlKbXMX7ucUHT94jU9sVZXirsVeb/APOQvlrQ9Z/KfzBNqVnHcT6XZzXmnzMP 3kM8a8gyMNxWlGHQjrir888VZd+WX5meY/y98yRazo0lY2ol/YuT6NzDWpRx4j9luqn6QVX6DeRP PGgedvLVr5g0Ob1LS4FJImp6kMqgc4ZVBPF1r91CNiMVZBirsVdirsVdir53/wCcrPzrby9pjeSN BuOOt6lFXVLiM0a2tXG0YI6STD6Qm/7QOKvjVEd3VEUs7EBVAqST0AGKvtv/AJxt/IKLyZp8Xmjz FAG82XsX7mBxUWMMg+wARtMw+2e32R+1VV7virsVdirsVdirsVdirsVdirsVYr+afnGPyb+X+t+Y SwWeztmFmG6Ncy/u4Fp3/eOtfbFX5rySPI7SSMXkclndjUknckk98VVLm0ubWQRXMTQyNHHKqOKE xzIssbb9nR1YexxV9Uf84UeduUWt+S7iTeOmqaepP7J4xXCj5H0zT3OKvqbFXYqwr87P/JR+b/8A tlXX/Js4q/ODFVxRwgcqeDEqGpsSKEivtUYq9N/IT84bz8uPNavcM0nlvUmWLV7Ub8RWi3EY/nir /slqPAhV+gFpd215aw3drKs9rcRrLBMhDI8bgMrKR1DA1GKquKuxV2KsW/M7z9p3kPyXqPmS94u1 snCztiaGe5k2iiHfdt2p0UE9sVfnHruuanrus3ms6pObjUL+Vp7mZv2nc1NB2A6Adhtir6L/AOcS vyXXULpPzC16DlZWkhXQLdxUSToaPckH9mI7J/l1P7Iqq+usVdirsVdirsVdirsVdirsVdirsVfL X/Oa/nYrBonku3k3kJ1TUFB/ZXlFbqfmfUNPYYq+Y/LOhXWv+Y9L0O1BNxqd1DaR03oZnCcj7LWp xV7d/wA5heRrbQfNOg6pp8PpaffaclkABsJNPAjXcf8AFDxgf6uKvMPyg86HyZ+Y+ia+zlLSC4EV /Q7G1nHpTVHfijFgPEDFX6RAhgGU1B3BHQjFXYqwr87P/JR+b/8AtlXX/Js4q/ODFX0Z+Rn5Vab+ Y/5JeZNLlCRatbao0+jXzDeK4+qxfCxG/py04uPkeqjFXz7qmmX+laldaZqEDW19ZSvBcwOKMkkZ Ksp+RGKvq3/nD382GvLOX8vtWmLXForXGhSOaloOs1vU/wC+z8aD+Ut2UYq+ncVdirsVfE//ADl1 +ZL6/wCdk8qWUpOleXKrOAfhkvnH71j4+ktIx4Hl44q8v/K3yBfefPO+neXLXkkU7+pfXCivo2sd DNJvtULsvixAxV+jukaTp+j6XaaVp0K29hYxJBbQL0WONQqj7hiqLxV2KuxV2KuxV2KuxV2KuxV2 KuJCgsxoBuSegGKvze/OHzo3nP8AMfW9eV+dpNcGGw32FrB+6hp4ckXkfc4qz3/nELyp+mPzUGqy pyttAtZLqp3Hryj0Il+dHdx/q4q9+/5yz8qfpz8pLq+iTldaDcRX6Efa9OphmHy4S8z/AKuKvhHF X6E/846+dv8AFv5UaRcyvzv9NX9GX5JqfUtQFRietXhKMa9zir0vFWFfnZ/5KPzf/wBsq6/5NnFX 5wYq+yv+cJf+UC17/tq/9i8WKsc/5zG/KtI2g/MPS4aBylpryoP2jRbe4Pz/ALpj/qe+Kvmvy15h 1Ly55g0/XdMf077TZ0uIG7FkNeLeKsPhYdxir9KfJ/mjTfNXljTfMWmtWz1OBZ41rUox2eNqftRu Cje4xVOMVY7+Ynm638n+SNZ8yTUP6OtnkhRujzt8ECf7OVlXFX5p3l5c3t5PeXUhmurmR5p5W+08 kjFmY+5Jrir7J/5w6/L5dI8m3Pm+7jAvtfcx2hI+JLOBivfp6koYnxCqcVfQuKuxV2KuxV2KuxV2 KuxV2KuxV2KvNP8AnIrzr/hL8qNYuopPTv8AUlGmWBBofUugVdlPikIdx7jFX57Yq+3v+cPfJ50b 8tJdcmTjdeYrlplJFD9Wt6wwjx+36jD2bFXtesaVZ6vpN7pV6vOz1CCW1uE23jmQo439mxV+Y/mP Q7zQdf1HRL0UutNuZbWbalWhcoWHsaVGKve/+cMPOv6P84al5UuHpb61B9YtFJFPrNqCSB/rwsxP +qMVfZGKsK/Oz/yUfm//ALZV1/ybOKvzgxV9lf8AOEv/ACgWvf8AbV/7F4sVe8eZfL2m+Y/L+oaF qaepY6lA9vOvcBxTkvgyn4lPY4q/NLzT5dv/AC35j1LQdQXjeaZcSW0poQGMbEB1r+y4oy+xxV9Q /wDOFfnprjTdY8k3UnJ7I/pLTVJqRDIQlwg8FWQo3zc4q+nsVfNf/Oa/m1rXy3onlaCSj6lcPe3a jr6NqAsat7PJJX5pir5Q8u6Jd69r+m6JZj/StTuYbSHaoDTOEBPsOVTir9NtE0ey0XRrHSLFPTst Ot4rW2TwjhQItfegxVG4q7FXYq7FXYq7FXYq7FXYq7FXYq+N/wDnNDzqNQ836Z5Ut3rBosBuLsA/ 8fN0AQrf6kKqR/rHFXzniqZ2/mfzLbQpBb6tewwRjjHFHcSqigdlUMABiqp/i/zZ/wBXu/8A+kqb /mrFUtuLm4uZ3nuZXnnkNZJZGLux8SzVJxVM/KHmS78s+adK8wWm8+mXMVyq1pzCMCyH2daqfnir 9M9M1G01PTbTUrJ/Vs72GO5tpR0aKVQ6N9KsMVYn+dn/AJKPzf8A9sq6/wCTZxV+cGKvsr/nCX/l Ate/7av/AGLxYq+isVfG3/OaHkxdP85aZ5pt4+MOt25gumH/AC02lFDH/WhdAP8AVOKvMvyJ82N5 X/Nby9qTOUtpblbO88PRu/3LFvZC4f6MVfotir4R/wCctPMDar+cd7aBiYtGtraxj8KlPrD0/wBn OQfliq//AJxI8tpq/wCb9veSLyi0S0nvzXpzIFvH9IafkPlir7rxV2KuxV2KuxV2KuxV2KuxV2Ku xVDalqNppunXWo3kgis7KGS4uZT0WOJS7sfkq4q/Mzzj5ku/M/mrVfMF3/f6ncyXBU/sq7Eog9kW ij5Yq9D/AOccvyg038yPM2oway06aLplqJJ3tmCSGeV+MKcirgAhXbp2xV9C/wDQm/5R/wC/dV/6 SY/+qOKu/wChN/yj/wB+6r/0kx/9UcVYb+b/APzir5M8vfl7q2veWZL5tT0uMXTR3MyyRtBGQZ9l jU1WOrVr2xV8o4q+5v8AnEfzr+nvywXSJ5OV95cmNowO7G2krJbsfYVaMf6mKs5/Oz/yUfm//tlX X/Js4q/ODFX2V/zhL/ygWvf9tX/sXixV9FYq8b/5yy8tLrH5PXt2qcrjRLiC/ioPi48vQl38BHMW P+rir4QBIIINCNwR1rir9Gf8fr/ypX/HPIer+gv0j/0cfVuXp/P1vhxV8G/mjqjar+ZPmjUC3Jbj VLxoyd/3YnZYxv4IAMVfQ3/OD2kKIPNesMKszWlpE3gFEkkg+nkn3Yq+psVdirsVdirsVdirsVdi rsVdirsVeJf85b+dToH5XtpMEnG+8xzCzUD7Qto6SXDD2oFjP+vir4YxV92f84meT/0D+VFvqMyc bzzBM985PUQj91Avy4pzH+tir2jFXYqo3tnb3tnPZ3KCS2uY3hnjPRkkUqyn5g4q/Mzzt5YufK3m 7V/L1yeUmmXUluHP7aK37t+320o304q9O/5xN87f4e/NKHTJ342PmKI2EgPQTg+pbt168wUH+vir 62/Oz/yUfm//ALZV1/ybOKvzgxV9lf8AOEv/ACgWvf8AbV/7F4sVfRWKpB+YGkLrHkTzDpRFTe6b dwL7M8LBSK9w1Dir8ysVfUX+KZP+hJ/S5H6x636L51P2f0h6tOnT0PhxV8x3dw9zdTXL7PNI0jd9 3JJ6/PFX2h/zhbaCL8rtSuCBzuNYmIIJ+wlvbqAR/rcsVe/Yq7FXYq7FXYq7FXYq7FXYq7FXYqlO ueUfKfmBoW17RbDVmtwwt2vrWG5MYenIJ6qvx5cRWmKpX/yqf8rP+pN0P/uG2f8A1TxVk1ra21pb RWtrElvbW6LFBBEoSNI0HFURVoFVQKADFVTFXYq7FUg1X8vfIOr30moat5a0rUL+bj613dWNtPM/ BQq8pJEZjRVAFT0xVQtvyv8Ay0tbiK5tvKWiwXMDrJBPHp9qjo6HkrKyxgqykVBGKsgvrGyv7Oay vreO7s7hDHcW06LJFIjChV0YFWB8CMVY3/yqf8rP+pN0P/uG2f8A1TxVOdE8t+XdBt5LfQtLs9Kt 5X9SWGxgitkZ6AcmWJUBNBSuKpjiriARQ7g9Rir8tdTtDZ6ld2hBU280kVDuRwYrvT5Yq9R/SLf9 CrfVeScv8Z+nw/a9P9G+rWlf5++KvJMVfb//ADht/wCSjm/7atz/AMmocVe64q7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX5geayG80awQag31yQR0I9ZsVZf8AV5v+ hevrHA+h/i30/U7c/wBG1p92KsH1iz+o6vfWVKfVbiWGlSaem5Xqflir7C/5wnvhJ+XetWRNWt9W eXqdlmtoQB98RxV9DYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqsn mjggkmlPGOJS7nwVRU4q/LO5nae4lnYANK7OwHQFjXbFXsf6Ib/oUn61Rqf4s+uVqtKfVPqvTrSv brX2xVhv52aQ2kfm15ssivEfpKe4RfBLpvrCf8LKMVe0f84P60ses+adEZhyube2vYk70t3eKQj/ AKSErir62xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxViP5u62NE/K /wA0alyCvFptwkJPT1ZozFF/w7jFX5s4q+vf8IP/ANCWfVOP+kfUv0vWp6fXfrdaf8YNv864q89/ 5zK8ttp/5m2usolINbsY3Z6UrPbEwuPojEX34qxT/nGvzQPL35xaHJI3G21J20y43pUXQ4Rf8lvT OKv0ExV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV4H/wA5leaF038t bXQkek+vXqK6V629pSZz9EvpYq+L7Cxub++trG1T1Lm7lSCCMdWkkYKo+knFX6Yf4TsP8Ef4RoP0 f+jP0TSm3o/V/q/T/VxV5L/zmF5P/TH5aRa5CnK68u3KzMRufq1yRDMB/s/TY+y4q+Jbe4ntriK4 gcxzwuskUimjK6mqsD4gjFX6W/lz5vt/OPkfRvMkJWuoWyvOi9EnX4J07/ZlVhirI8VdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir4U/5yy86r5h/NObTbeTnY+XYhYJQ1U3B PqXDfMOwjP8AqYqgv+cXPJ7eY/zc02eROVloQbVLg025Q0EAr4+syN8gcVffGKoLXNHsdb0W/wBH v052Wo28trcoOpjmQo1D2NDtir80PN3lnUPK/mfU/L2oCl3plw9u7dA4U/BItf2XWjL7HFX0Z/zh h+YyxXOoeQr6SguOWoaPyP8AuxVAuIVr4ookAHgxxV9YYq7FXYq7FXYq7FXYq7FXYq7FXYq+Wfza /wCcp/zB8n/mJrXlvTNP0max06VI4JLmG5aUhoUkPMpcRqd3PRRirEf+h1fzT/6tWh/9I95/2V4q 7/odX80/+rVof/SPef8AZXiqvZ/85s/mKktb3RNImi/khS5hb/gmnmH4Yq9W/L//AJy88g+YZ4rH X4JPLd/KQqyzOJrMsf8Ai8BClf8ALQKP5sVe7RyJIiyRsHjcBkdTUEHcEEdsVbxV2KuxV2KsS/NX z5aeRfImqeYp2X14IzHp8LEfvbuQcYUA7/F8Tf5IJ7Yq/N26uri7uprq5kaW5uHaWaVjVndyWZif Ek1xV9sf84heQW0D8vpfMN3Hwv8AzLIJo+QoVs4arB1/nZnf3BXFXu+KuxV8r/8AOZf5Zsws/wAw NOhrwCWOt8R2rS3nb/k2x/1MVfMega5qWg61Y61pkvoahp8yXFtJ1AeM1FR3B6Edxir9Hvy38+aV 568n2HmPTiFW5TjdW9atBcIAJYW/1W6eK0PfFWTYq7FXYq7FXYq7FXYq7FXYq7FX56/85H/+Tt80 /wDMRF/1DRYqw3yl5W1XzX5isvL+khG1G/ZktxK3BKqjOatvTZTir1T/AKFB/OP/AHxYf9JY/wCa cVSbzF/zjN+cmhWkt5Lon122hBaR7GaO4YACpIiU+qfoTFXlpBBodiOoxV9N/wDOJH5yX0Gqx/l7 rdwZbC6Vm0GWQkmGVAWa3BP7DqCUHZth9rFX1xirsVdirsVfDv8AzlV+bC+bvN48vaXNz0Hy87x8 0NUnvfsyyAjZlT7Cf7IjZsVefflL+X9359896b5eiDC1kf1tSnTrFaREGV69iR8K/wCURir9HbOz trKzgs7SMQ2ttGkMES/ZSONQqqPYAUxVWxV2KoHXNF03XNHvdH1OEXGn38L29zC37SOKGh7EdQR0 O+Kvzm/NH8vNU8gecr3y9fBnijb1LC7IoLi1cn05R2rTZgOjAjFWW/8AOPH5yy/l35pMGoOzeV9W ZY9SjFT6L9EuUHinRx3X3AxV97W9xBc28VzbyLNbzIskMyEMjo4qrKw2IINQcVVMVdirsVdirsVd irsVdirsVfnr/wA5H/8Ak7fNP/MRF/1DRYq7/nHD/wAnb5W/5iJf+oaXFX6FYq7FXw7/AM5eeTdP 0H8y4tR0+FYIdftReXEaABfrSyMkzAD+cBWbxYk4q8e8u6xNovmDTNYhLLLp11DdIV+1WGQPt/wO Kv1DxV2KuxV4T/zk9+dy+T9DbyvodxTzPqsREssZ+KztX2MlR9mSToncfa8KqviIAk0G5OKvvH/n Gj8oj5E8nfpDVIeHmXXFSa9VhRoIaVitt+hFeT/5Rp+yMVexYq7FXYq7FXmv57/k/ZfmR5UaCEJD 5i08NLo942w5kfFBIf8AfctAP8k0btQqvz+1LTb/AEzULjTtQt3tb60kaG5t5QVdJENGVgfDFX0F /wA40f8AOQyeW3h8m+bLimgStx0rUJDtZu5JMcp/3y7HZv2D/kn4VX2QrKyhlIZWFVYbgg9xireK uxV2KuxV2KuxV2KuxV+ev/OR/wD5O3zT/wAxEX/UNFirv+ccP/J2+Vv+YiX/AKhpcVfoViriQBU7 AdTir4Q/5yq8+6b5s/Mv0dKnFzp2h24sFnQ1jecOzzMh6EAsEr34+FMVeZeT9An8w+a9I0KBS8mp XkNtQdlkcKzGnZVqTir9PMVdirzH88fzt0f8ttCKxsl15nvYz+i9OO4Hb15wCCIlP0sdh3IVfA+t 61qmuatd6vqty93qN9I011cSfaZ2PtsAOgA2A2G2KvoT/nFX8jH1i/g8++YoP9xNlJy0S1kH+9Fx Gf79gescTD4f5nHgtCq+wsVdirsVdirsVdirw3/nIr/nH2Hz1aN5i8vIkPm21jo8WyJfRoNo3O1J VGyOf9VtqFVXxFdWtzaXMtrdRPBcwO0c8EilHR0NGVlNCCCKEHFXvf5B/wDOTN55SEHlvzdJJeeW hRLS93knshsAtNy8AH7I+Jf2a/ZxV9l6ZqenapYQahptzHeWNygkt7mFg8boehVlqDiqJxV2KuxV 2KuxV2KuxV+ev/OR/wD5O3zT/wAxEX/UNFirCPLfmPWvLWt2uuaLcfVNUsmL2txwjk4MylCeEquh +Fj1GKvQv+ho/wA9v+pm/wCnHT/+yfFUn8y/nt+bfmWyex1fzLcyWki8JYIFhtFdT1VxbJDzB8Gx VgWKvrT/AJxP/JC+06Yef/Mdq1vcNGyaDZzLxkVZBxe6ZTuvJSUQHsSe64q+oMVeM/nh/wA5HaF5 Bgl0jRzFqnm1hQW1eUNrUbPclT9rwjBqe9B1VfEOva/rGv6tc6vrN3Je6jduZJ7iU1Yk9h2VR0Cj YDYYq9c/5x8/5x8vfPt6mu66j23lC2fc7o966HeKI9RGDs7j/VXepVV9x2trbWltFa2sSQW0CLHB BGoRERBRVVRQAACgAxVVxV2KuxV2KuxV2KuxV4x+e3/OOmkef4ZNa0b09O82xptNTjDecQAqXFBs wAosnXsailFXxJ5h8ua35c1e40fW7OSw1G1bjNbyihHgQejKeoYbHtirLfys/Ovzp+XN7XSp/rWk SuGu9HuCTBJ4sneKSn7S+3IMNsVfZ35Yfn35B/MCKOGyuhp+tkfvNGu2CTV7+i32Zl/1d/EDFXo+ KuxV2KuxV2KuxV+ev/OR/wD5O3zT/wAxEX/UNFirE/InlC984+bNO8tWM0dvdak7RxTT8vTUpG0n xcQzdE8MVe2/9CS+ff8Aq/aV/wBPH/VLFUbp3/OEHmR3X9I+Z7O3jr8Rt4JZzT2DtBir138vP+cX vy18oTx31xC+vatEQyXV+FaKNh3jt1Hpg+7ciOxxV6X5k80+XfLOmSapr+oQ6bYR7GedgoJ68UH2 nY9lUE4q+UPze/5y61XV0n0byGsmmac9Ul1mQcbyQdD6Cgn0VP8AN9v/AFTir5wkkklkaSRi8jks 7sSWLE1JJPUnFX0F+RH/ADjBqXmWS28x+coZLHy6OMttpzVS4vBWo5DZo4W8ftMOlB8WKvsmxsbK wsoLGxgS2s7ZFit7eJQiIiCiqqjYADFVfFXYq7FXYq7FXYq7FXYq7FWHfmT+VHk78wtK+pa9a/6R GpFnqUNEuYCf5Hoar4o1VPhXFXxh+a3/ADjt568gPLeekdY8vLUrq1qhPpqN/wDSIhyaL57r/lds VeWo7o6ujFXUgqwNCCOhBxV7L+Xn/OVX5keVVis9SkXzHpUdB6N8zfWVUdo7oVf/AJGB8VfRXkz/ AJys/KjzEscV9eSeX796AwaivGLlTfjcJyi4+7lflir1rTtT03UrVLzTruG9tJP7u4t5Fljb5OhZ TiqJxV2KuxV+ev8Azkf/AOTt80/8xEX/AFDRYq7/AJxw/wDJ2+Vv+YiX/qGlxV+hWKsJ83fnT+WH lNZF1jzBardRfasrdvrNxy/lMUPNlr/lUGKvA/Pn/Oal5KHtfI+kfVlOw1PU6PJ80t4yUU+BZ2/1 cVfOvmjzh5n81ak2peYtSn1O8NeMk7VCA78Y0FEjX/JQAYqqeUPJHmnzhqyaV5c06XULtqF/TFI4 1P7cshoka+7EYq+vvyc/5xV8veVGg1nzWYtb8wJR4renKytmG4Kq4BlcH9phQdlrvir3rFXYq7FX Yq7FXYq7FXYq7FXYq7FXYq4gMCCKg7EHoRirxn8yP+cV/wAu/NrzX2mIfLmsyVb17JV+rO/jJa/C vz9MoT1NcVfM/nr/AJxn/NXymZJhpv6a01Kn67pnKeijerw0Ey0G5PEqPHFXlbo8btHIpR0JV0YU II2IIOKozSdc1rR7n61pGoXOnXIpSe0mkgk26fFGVOKvQdG/5yW/OrSlVI/Mcl1EtKpexQXJNPGS RDJ/w2KsusP+c0PzTgCrc2GkXij7TNBOjnanVJwvXf7OKppB/wA5u+cghE/l3TpHrsUedBT5Fn/X irw3z35vvfOPm3UfMt7BHb3WpOsksEPL01KxrGAvIseieOKoLy75i1ny5rNtrWi3JtNTsyzW1yFR yhZShIWQOv2WPUYqmnmH8zPzB8xKya15i1C9hf7VvJcSCH/kSpEf/C4qxnFWT+Tvyy8+ecphH5c0 W5voyeLXQXhbKf8ALnk4xL06Fq4q+ify+/5wut4zHeee9T9dhRv0TpxKp8pbhgGPuEUezYq+kfLn ljy95a0yPS9B0+HTrCL7MEChQT/Mx+07HuzEk4qmeKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2 KuxVjXmr8tfIXmxT/iHQrPUJCKfWXjC3AHgs6cZV+hsVeTeYv+cMvy2v2aTRr6/0aQ9Iw63UA/2M o9T/AJKYq891b/nCHzZET+iPMlhdj9k3cU1qT8/T+tYqxa9/5xC/OS3J9K3sbylKGG7UV/5GiLFU un/5xX/PKNgE8vpMCK8kvbIAe3xzIcVRMP8AziX+dcj8X0y2iH873kBH/CMx/DFU+0z/AJws/Muc g3+p6VZR9wJJ5pO37KxKv/DYqznQf+cIdCiZX17zNc3Y6tFZQR230c5WuK/PiMVeo+Vv+cdfyg8u FJLby/DfXSf8fOok3jEjo3CWsSkeKoMVejxRRRRrFEixxoAqIoAUAdAAOmKrsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVf//Z + + 0 + + + + + + + + + + + + + Cyan + Magenta + Yellow + Black + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 7 0 obj <>/ExtGState<>/Properties<>/XObject<>>>/TrimBox[0.0 0.0 32.0 32.0]/Type/Page/PieceInfo<>>> endobj 8 0 obj <>stream +HKdRN0+vGWRB* ʥ6嵳ޝo=gKwX +^ont|!W.d:^ QƐYOF'Ó 1 _bQgAD:X镶~n6$WvGZV2o6![u'Ko8 , endstream endobj 12 0 obj <> endobj 13 0 obj <> endobj 14 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 30.1.0 %%Title: (~ai-67ba8ada-c402-4102-ba0c-c32c044e9ae1_.tmp) %%CreationDate: 2/9/26 10:37 AM %%Canvassize: 16383 %%BoundingBox: -1 -39 33 1 %%HiResBoundingBox: -0.640127878977182 -38.25 32.0998732034441 0.011217887853491 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 14.0 %AI12_BuildNumber: 136 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -32 32 0 %AI3_TemplateBox: 16.5 -16.5 16.5 -16.5 %AI3_TileBox: -290 -412 322 380 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI24_LargeCanvasScale: 1 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:24 4 %AI10_OpenToVie: -22.2391435604814 7.57697479323815 16.3896357695204 0 8187.1728475253 8191.24905804187 1500 861 18 0 0 6 58 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: -22.2391435604814 7.57697479323815 16.3896357695204 1500 861 18 0 0 6 58 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %AI17_Begin_Content_if_version_gt:24 4 %AI17_Alternate_Content %AI17_End_Versioned_Content %%PageOrigin:-384 -316 %AI7_GridSettings: 72 8 72 8 0 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 15 0 obj <>stream +%AI24_ZStandard_Data(/X>% <@M0 9(F cGd?>bB%gC> +6lkQRJ)S(JZ   88 ,L0@@dHD`2B,xmuKgXQMReBwUI4T   肄<(5`0$ǑG bA ŏq0GK q` USٽvl>0]Ax6AuO0~0>X$ aO0Fh Eb "D7a`(d(!qK5cjԢ+FH``5 3EqVaF-ꠀƒ\!.I6Fyrr{ +Cݖww! _,TW+wYe|2t+x*!!+\4U!͘&3'CP czPƘ'(+ s`^qLMe?8,q%0q, xF5FāZi44 BaHht,iYN4a<ά|e p0(A?sChdkla; +p 2[\NcH;+E%8j,Gl8 +pLk 0r"CJJ:)HbV FFFGYY&la9kY^Bq @.ZiHF6=Ro0 JűP0 Ű``_ 8llp0pnYzvy(LEb=L݁O,, Qqy 10 b%w0 c(b0Cmh0S' 30hP0beCH48jx Ƃ`A`b@Tb(Lf袌! 6l>w +/p *S 5Z.*+W(F5j623[05/8-qc1@. dɦ|&Rdd#r+83|M8AJX"H0CEV8*RqEBP, ECE^Т/Ecb@Ƒ3 c`0 4pհ57a(0`X00nO(e|B8w(b8aX0 aSa2xB`4s`h00 +C!pd@c0c(3hƢhF6 kTÍ6 Ƃ` dƨ2`, ".paZ, CP$W()C`$ EBH$&,Q J41 7nΐd"ļt6' T1Xd8-NqK\]7w0j (F-*Q²jM5A +R) 騈g2ެvUiX8đpDm Ex׳RՇkai1 ($F9NNǎN p(we16z0]TP4zZ;m00aL1QB8VQRG &@@Lx ! X&(ȵ +, 6a… 4X0 6@! &@84P 4a",81ѝ2 "rbA"L0BĂ@T $LP Y0%`bƄ +6BЇ?Tɡi@Vr!BC6hX Z" *Px 1L(@(DXh` .  +(,88ADp5k#i#WٍZHzfRYVK$#2Y'u,–֕ͻIEcϲV/Sz}r6aM 5fYvWM{ktMy^lfdU?Gd;)ͥ2o{ٺ/oi+2Sanrʎc{Y_;;cX+獬V<Œ`QqI}jUe=r 7^*͈UUwu9nrN9;2;<׭r61?w;)*NΕdWJ]]zB;]ɫ|lM3hEaWT/Mn!VW-i%=kܩbu9مrmA*L3_ӔK*u!2^?2 jު۟Rcw`eΨ5\6Y:ٷk*'ww|hGD*['fbN]N]7_ /V_V]j脗xx& +'dY~.ef-mQu*gʊ8`adYϖw^w +Q|CZtU=wrΟڔr'g +N'vf ˷"S]g$[;Ic|J}/vtnCYvG:꽰RRzEzoY*Bpp0ȕ`ov&zy9c$"|Gy,s nϳ4F8%_ +l]bR=OUS!̜0 ~*Vږ磲ͺlYwjɪf4+}+M9KHfU B4yo%wK<R3DoVlJ)>E_ykʼ +Xs{{EtޯЊVʗ}NHTM;RTܺSWmS£T4ft7$+Xj1(~7/wOC^i*egƳCXbxWGXI*VlO+,\.~[YqVxb9MVޕ1| _4=g^}e[_$^veRzs\z/^*v9x%tV)ްT[hL+yZmd,SɛHS'Ohabɗ KNWfJUONd+cL<,3[\m^b:W=EYb2c^e"fb[1rXx\#_ '~dٻLDf2,M/_8sQX6wvƻUaMDH6;JLxϥ,J;tvWr+yg#"SתR{eK4X1#d~nG VvV/)HdGʺAoSNς[j4a ϰT|edK45F Az՗DSơ9iZZMbձmдTtU/*{ŔcI_U7,I_YȝZّ4 +hjSc ڤuK6i͸UL &J֩u1MV$F.vBR'5:b~NXϺ)8~4If~,6&wS^)5&s?mV/mfJMխ^2׫l7yS^BY,u4}nu)*VZW MTeq#D{1PyΒ9<גSR1wgv +R\K\XtU%&oi*ސtcyшr9WHwX%n^#ͽ +Yɥի -Ɏ%,F&/W)KITIJU92#{9= LX]3 2%Swѕ#2%ԣQA26% +ȱoRUǮ4^L*d={L7ݕLʝ'D7D+bD*J$VtL +)%`~,t&.`fQ^gULY-r"vK4*?.K,?W ^T +k&joe$o+is"JŸSn +K"J*&b#w{G$-fIJT2MFnY:&_SyJ13W};R+|Inζ4c`^!88. &@tAB $@$DD(BppX@D@aBϻ 3.%my՛Z~~Y3'ΜaY ƚBU9N kMYYiR»7TJ3+muBRljl ܸuRbq Ҧ)g-&{sm\J.^~$b}ԋ~vfJRe+SJfnQAr φ +^CXR-w2+hxS{7XjwcUI[~NNMbt96H4deB[tYE9,:6Jt^+訊G*5* +hL|RÖلsV^\%VMs]kQ] +ԪjdKuuûl>wX^ΤpdUVWs#+{SX>tYjbz +t BkI]L%KG:eҿ+HYDMҞ2+i"5HieLiSo7{.'莥iGGFԺU݄}kt8%^CkֲDZKV]R;6bGebdJ}-M#_C^;RSϔi7݊m?f"sQHi?fSzҞ±Ȧ+}[N7q\m%cBD);IxcٴBG=zL<Б/nf,H-GrORim.ޕKEet݊bHwtCr4K۳)FuL;9[eE卵ɬ97g1 0" A  "4„ +c6WcwQIA*HX@0,La <,`"@Tƒ 4<@1<(B "4`\{p,_KM;2.Δ&RyD/dT0o.fdH)4N)j9{W}%[_!l]Pm)fakKK, gKb:êRUGb,c̐r1ʼ˲r+uk1f.=]rp(I,M,)LsCxrS +o/3++U;OM!kɚ*D,VV{ٯbN*eХYcW(\fiZbp8Σc!*%;Q1 _%*I/5iXegvgkfegDg~KcdM{b]1)y +ٜMMYf^K-uID$c;\,5Ji\~ʏ'L!ULWdRÖ2<_-UU6FcW/)srtPa9Ev|PC=R.Ο)$ytYJ>\(q9Ul5!;:Rj畉SSؒhMfWbUgti."2+g=y,?׎o,{SYT\8V69s':C`KT5TjwΪ1{i~z}.5>teFZ]lRf~ꓷBd<,VLn\hѪ&׆SfWp<ʳ86UPs.}|M:_:%ZOgϔSf.W#RgwE݇ODm2&RUDt2]%3oi7xL+g,.t٤˞IT6iSٝ2iB*bІ͖+1;J,9 Z4W +2MWܙC\Je^ +3|w%~+u7[:]+$Ul\r謬)9ߴ|0 ꦦ$/ήq ES#*n +;"D,GKitW;"7O9Tr +r{Y,͵\Pv0m1UeUj0w̻!ǎ<ՓXEXT5L5N?3hnwehZEW)D֧չZZjxuwLW}) MSFlM42xN{i&xRS}'ʓ={#9ϽnIDNX 4J>Y/#/,Ɛ3u0MuhFC+sDRV:*f%:JbӉB󕦨W2Iќ{˼Cc O&fo9$]UHf%RaVYk*RIITsJ~}oA*t.Ʋ+fxs4Iϗ<شsDV#Fi7"#V, 16r!A;qLBGr!yr:^|CdVSD*ʩ9By̩7=,2:LeN*CfԥZ;bdD;DqKI3cO]O{$7\a uUI3t⥝9sՖgɅ'6wee44**w4MWpnW#[!e!| +j:MC0'V-&e򊮦3tOYp|-DiB CB` x@([*M97-NAr9UM LEQٵ)d'l|/ٱز)9>dɫTٚef^Y2![fϲė9R#hw;cM!NU>ӊ%Y ij*=_⥯ƴS!"ۯ^UKBFҞUA3_9˦Cuhs6GtgeƨihȈYg8x64ZK$vSU!M!eɞ!+bMe"$,˙&hDߍljTjbXf\+há( oJX3Yu)֍K'):>Q ِ&k㬯ZY%X+;YD1"[fil}ӜlLe*3e')RbGX0[J+5zKFػ#Y&=K:3\nvVuc3939s79Fy{Yr\]1 +dWSOёkA6ڎ""#BD4=HDTbq[DI+tdSUD3̫sx;cFmiHXGW*YRՔQn9OUuј!$BܕL J׶L>)Sw5|N4*mH fF7;ˬZzEjyI25Xod$Q3'*үIM<vh4^2LךISZt ]QJd[I3$OyADN:n?MŸW4|j2!q ]sf|V|Y!4s WrO'U쮼Q}RXR)0.$LڜAc{wfӹb_tVem'5+ѫs8c2gͭyRre=9* Q Rjf\j{e:X/o+ùq`ޥWGr׵՚\UdXXS|T;Wb)tdHRc !H @8 AZ~FZ4J51 G +:FJ j[e{?ic\)Ūo“茞~OP}txt31jYlPFMN5R)C6TZtKIPbCvW$䭰qT<) 35""|!ZqXY3.E& .?pWf3,[t!3  +ZB9\NA9Y}9 gfڀOw #Jƭ;\ =cۆ/zA#Wwe0UYˀ .glp&⿳Yb ?m!qwg1DFQ^BD.6z3Ae̒ E.3ul/ZA-GG5/(Wя%!ޟxSi0x +q݂5^.Yk) +{DZ))ԓ˖Qa7GL]O&TcF$0JT$R`dfSEn]&VaУH +v);/@\ؕ_1~v 22gLCo \=vQDkkGj8/5 EAPmksPQ?I'*H΍7Tjov&O<8R{ȣx-Nlyó +IzǀyյB9xm&O]Tc߯q6)X Kڅ-梆.J>!$ǽxT +ʤ#AU4Zv!v:f\cJ‡VSܖ}e`a"Jk4]ubF-#,:08?Vy"I-^VɇhiVZ`E"¾|cC}N ^u| d- +}u%=IUhr[X٫`- v'eՠx" +&!Y@c?Ae}}pr^iEԶa{LR؋> `}Vc z6A~:]hQ|U& `(1ũ@L{jå&^ųJ #F]T`6΅vSd+@ @g< 㧖jk/b8  a ^d50`KNnGT)agHeHޤ AD(9: LeI%Yv +X\8a3ְiE$uQ~I&-Mz10!#Wz:@ئ +Tz*mG+Pt҅UkPQV_JlY&R|ilRde24Ok>B>9Z].dҰeQ [GH'$ \ +4ljtNC"؏`P#;#$BLirq0o)*a xɊ8X(ҤΤNrߟK8u<L\х"VWVW%<~F6|Ȁݙkڈ-KҗCjT%տސVt_^h:L~sIaM"p5p>a)\u >YCM&b*װw8eqEV~$7/EޠiHG'f%+eT*)Si4mASH6hKc҅2 Z[çqhxk9Ɏ_xI qgJ{JwkDR&"֛c.2 r܀ELv;f 8A9<ق +/ \N" m(Mp J0+ Sy8܀. d`wlaDz +Dh#˳9Tٲeahm `N#2뽀v uR.L= h+UAcw) +i.W=PxDH)"qSWд68]ȥrOC}.F-ut϶& + ޜ"#LȋabjV4dn/"M@cV2..ku ?.@d/>bOKm}?}cc2 rчѕނ݋l> +ߪe_rqޠEqДOrל},;¥q-1sʸ1EIzmQbhp +cµN>^ȗGrBR10?9D ww1F~;߿wʩȃVCu<)Tk&!I7 +c9NJZg􀄓o-IfcEJIo=<虋-իH㩖6ߕQk0UY)xg64&Hl oAsC4eh +2`J#Rl0@NFZi08~NJ2`oB3gR,c[֥c G U}Cxps;3Qee(Vbq406S Zb"ʹ dri~d,fU-+>o!DVfc߻Cد.e}Ǹr(?t]*nSf^ޘ?)VX}9Rjmv +L5?kGjb*AM7e.LȐdxp2P:]PnwT]DGJo1Vw cg^2x"lpp75VȚ}Lj\lGǼ=TZ͋P.5!0ljB:HLzm]t!j3 3ЮVYDe;a޺A+rmhexm_"~eULs,T%dFچJ;xųRܧ{T2(ĥ#${Oas&70l8Ai%Q09C1}rZO_U>He@b5In<,KŸ;qͳ{1]-.?7HE׊Ya<5JG Ywt +ԎtŔcn +=UiDYS&Q#MbhJ[&2 21 7TLroO{E,J̛Qмj7Dk hi}YU VDeRy8m *7GZ $ +&q= ZH]S; x&(p*8 6ZDmޭ5٥".ы  S)| pvV`lf+;A=TbH;op(@t̋G%&E͡ C`Dҹ%?LALMSq*dʁ&*+8xKOu$nfs^|u`SLB/3l<9_pCF̃ m']qZ~~ZyX8]@x>EHN'RbIS*~ XioI1rmOMsjDe,>LwXr+*kfˀwlصD,p1%8geE7o)Pȑ>0DwPdpx^ ݜߊ"촧O)lB/#*nЙ"pEm^ĩ +GЊn@o ע 6#10G +dZuHWdt1rtK<`6A^*ַl +c6o$W<2MbwJطq ( 9- Flv ye?!|P%*կ3$A9[jve7Ă_Vrה(6U20u7PN2EWq$V&F6 +˘}V,S? 2q05L qPasQ^pW5}i bJ@ϯפ[}gVe}Y¦s8!l~dmd2<.@G`_gZ/DcJ@[$'l2>~ƀGE|Y'6ֿdRލUR=&%c&herv]ti¢hnf:krw߹q׊%@#ULuZ!hڎ.х;R۱bYH6)&]U.fraO+`:;* ^:>o>/.bJ諁 @3g~V"?0כ,phWKoo`׫AK0ԢC À`YX\yr6OpRC8\nA2>!iRg50yۓsJ`D}Qʠs#{bG'cThӦn{TQm|aJDX@BA VDݹѡ󅊈G>%eLʙT:BMkҹR +ԺEp6@KR|;I䚖 BKe|d@嬖+ޚ?fPgny+ҹ+6 V +XXXi8PHaH2KV?` +Ңv" bk77q/"M8U6TSMRz #?9-xq#UξÒ` z.Cf2/{x*낡JZd6)>l tb,S/4"Q : 1P(⣤@M\^şߨ<%VRʨךӃ +%I} z]Ue< 0:1{{YpR +̃Ysm -.qON_<9*p':\bߧR߆-ђjv)qN#BW3l2"+%oOTFpKYC&me~FJd x@c4P3Rv`KI3@?ԧ)B'`vieRD1Y*8d-.>xtzoR(^ԫQ?3+B6yZe푀+on%zWHRTl@ZE#%)-.),oTTעzX`rٯ}b&#/S"ǚv׼3DUy%5Qfy ̪&jORSSB$pM`|8)Tݗ>~[֚(ch0tqs믐@*<6تTWb|1z/Mܳ>{[%k"i&UPAQ@@isrm mf)r~dCBjXU/%Tl+B1DL:3C͆^_C{^  6+( 93B`=!`:̊rܫ ٞd(ekBfFe35D L]B*x"-a&!-plNRV83I0rAcX_.D) ; oST~SIMt&ֶ +.},3")t -л>$d''p,466E{^?ٌrur~R~'U 46 +AfpbP9SBJ +Qz$T>!U+H{M<+ĮX>R·*ZRL,`=ǂdgSY~Y,6.Rui 2l+GlJbrj(.Ktr ˋNjUb_&%iGqRX]91kx)c8NOo + CXa%fn1$+f\Qen9 SVDz!p?N3ka(B317Z?`R}Cf(4îN#۸S/VCll >c`)/W hb_Q)Qh +h$&%ZIhl=&;t#i%M2P +lu^ |Tn9=Ac "mͅku/)VDwSƚgPA={$ v$-@e6L<^~g 5 .Qd\M]Lw!hO$_\^ pŒo&P1Zi?b*an" Q݃a fmFYȍ20Ҕ6F)+yVrg{F+ˏ;YN>Id*U|褾Җ¯zY-QN*Cƻ&;0$uk7/%]@Chm]:Y?*P'O<#U]vswV)& !RRTO2 HQ%-ɚ? +553-_<h2h$_j-Ͱ=~ZKNK>{[D*xRV=/} +`jpj_ +*% V" 9ʭf2 `W]ZD~vB@5j[Xw̠ CSxndΠkމ՜yeTsGOVJa<U{[؋`\f8 ^ l#4i<{XXf&K5!S_:)ۧ溠9Ztd7LN6( ng!Ο WMho~0<H%6UØiJbRZD5GL +W*NO!CJ3T۷Dxi)$QyQA+\y,2;>85G--`^@rrĎ"vcT"O^8nYG!}@>^jO+?VfL.QPJ;G|c:2 (R)IsFOH ST:PR6^*I6BXID$oioUOh6W ZaA"a*hRaXŤ#D>Κl]*wس)bX|M$@L=yYt00|LxqӑNE/Nq$BUa'FWQT EMGX + Yt6L\|x&h^Pl6c Q|Vz&P2ˢ)a*-PӁe)ۗpTaqUW*8n z1Ga}{Ȭo~! Bj"$ y4kۼ$5cvb<:~9ͣ[cK\~sx&.=p 78D5 v|']wMF3YOX%":Ȫ5Y뾴d G-YgdGTkaZEV ȇ_j/1 $Ty5jX2B@Ta}i&\9X#VWuJi_\y\?UD.F'ƢЧj?}qpJJE>p)BۦZ(pmql dbO]+ؔË :Y7!K&aUr rRL <#J034}00tlvd6gEos85 ۤSt)KD2t/94>S:詚;}P!KaEcñ+Ʋ:V"VXkzP C }_-a˃Lew/L0 PE + Gy؇[W8Z%`hTI㰰v֏Cx@ 5q!oδRveNɖ8$NQҘzP +;:[7D~.JxvUx&F=N-%V` wfҌϔHxMG2W~WV]6Fp4 mgH8ri[0seK%5k('=2Z68נelT$rХ (*zį  ph[Z:UEF}`W{ bl ]ơ0F#EviᴍSA)6l>~IՈCbAhYy:^bK%i6\V]&aV;3l@X} >g~/[E+t8W$;g8άz +r/TWDPlQkmUTj+XUUlGAKr/ZoQǡsLv!sIhS7ZfTrܶ 5R:P}K"` +}_1ImmEL%@TxR9e7>zhI/%V3 P$?G⛒)QD<$!)$"La%]Fz#ǬQjʉS/KQ&9vS|BiFxA|HIl`uyN3R`` o b:mWyY㯸v:pzK}!S|D0!k~9/}iNϳ aHvS R< Ի:KxB*Ӥռ}0ICd v E!v[3$'[ϸ1-q%%&X0g㭍BM &yo%c)YxVK-=É-1z[oqG̙ Ƭ76&K$ߵL7ؐE}=8~5V,*,< +hFHb10qi*&Rta %LYR߅8c0!c2)њ,(rGK:+ O/UQi`.Ir +Ceb)3c{! 7)q;>6Ț#1*?~VzkVO[XeDhD%/ R?ހ#p/AՈ/8t`NxjqliM<77Nt!H@Ey,7G|֟\ipKk [K>2Z/?3O㍞DEĖ2]J)j,9{uj<%.>f)/{8XV?Z{8ԈT &(y}uH9ҷ0LLq[ Yx3UcYkbmȴ Nf.eBG V.CD̄?[8SrGJX7~VCQSԫ5"7tL_!kmf݈y˄"W?A1~k#R|)i 2Ƀ6ٍgt)e} ќE;v%v0=\a8L. +ɧ281.k eM6ƼUI TAURD9B3ہf^U@z-)j(+\ƭ qA7T&Q'YL85t!/PbER/0 `m6i5W|xk{I9'<cA@l_*KJ1:ZFh]Krot> iH ][I`s w'Afd8+գX򾇵(ˠ}? +2X#K!G!nqcǠ;0,|hT%eSD0}hRC\j> C.gOnX*1t!Od&KJRL `-.XG ˣ(RU"x)iJ>TByZ2`Դ<i[ WER.Nf2ZiyR T sKbp3qω$Wip.ުaáUUe#s0$͸2zʉ;ڏ~d-}CHzq8 8%(jXTK<@E +ſb^kNQD]ᙬR, ̎zgDa{Gu7u)u4$KiHsC>9o_Uz%qgI!#e +܆N5H_&i4gt=L7?:x'/t _h_/ʑKCX CsÇEʳ>^N-H28Xу,MI[n,6bn%5'cnΏ)ŭlܡTLێ% Smn6QbmnDxh$Fs!xr}i=zs&Bf@ԝǤ7LKCgE7F&[f ;p^q|gzk}3&|!zY!cf\NƜ^G!naUX3W+CG4N~jmW=讔~&)srR&p`Z!8J,R~ +-1d]7>yVCJ^Z=&UATD [B89ct;*YG[3">QQc(*!իxkc-y~Kq eX8ZAtHI@Ng17F _d+2؇umBT (%C ل&{׳3-] +ьvb8B(m4*31s¶Zn$3XG4Ύ&*@q'>!(﵂"OC_tJkdH+9h0soz;o OaH Ck.;9p KZvkd.:.e>Xr#a{= &<oSJ1J8'A8;]! +_L]X+8%>ս8կdZq)UTB:iL".\XU!Hê˄nKW 70&Džv`@@z9~uaVEZ2LCn ~?lk X  >(8a'&5ۑl!کaǪW8;l?1O<`&26䮳ackbNuhOU{bGOwA]Vv6w71P8kzY[8[X=I#|J^{zK$LIA&ʈ,3B "8=rnb]y[H7s%n+yr\A> ;S:(񮊠 v]P핯trkR̞"Cծ^$<ݗ + 4BخX;_P'oIG \4o?<Dy6^6Qy|ؽ):F\s)vT^wqCԉ~4*eqĵz#Lp.6{De2&3wRi%vLxvz%ld Lc3)e%AWr,eU;&_TpK;<Ó9 +@z_| ̔D ) Χ^hړ\7ƿⷈc P' \E4'܇ uWNzݚ綆״5"E!O/=VlRyJ#%ߘ%_T5 81Aݏ<<x5G_Jvxh_ WX #*hjMxsثQ+QCȎOAC$j"N $RIM${i~3ަI6|`u cǭ@ZH$xPUӟCş;eX3n\GJ釠怤:}4:0~_1ퟶ?t徖E +ϊ`bO3w_HSH9;XUx 7W Wo)=LHP;tA7ºՍvDUMsR@J; .^JN'}Ͽьg"WNzZ~46Y$|3 lp N)DD @3x¿EXYFȱ#Ĩ\ +3 60rxE/֪V43ܳi/T+rV k&ܥA N2 e~RTĵ>&&ǭZ_ $vi¯=-rn.ni.HGDlTaxm +7 :tq1ezZ$*XVVB3ʤBdѴb&q4WC' YG +*u%6@OK +mސЈ@boo3h=Wq!pA@o +`cT/f@,=10J Gu|fO A2/[u3zy1J{$ ~Z@̎92Й @fK([6%ֱZM;h-vD'=* bl/A);4rT8Zuaݬ˪e*qXU +S5s*jTx;5S 7:c$ +@C`T x +,L7< A@+, f7#䏞û{gOl<I.^i1Ǣ\q[-i!ͬj0gMY񺎀(Z6cRAUewiS{mi6V?ў3 m@Oчi|mvm{31pj-Ʈmn#q[6M1\um)r;lP 16-}Q0uOESc|! {$h@u:+fLoY$e)4%Zs/eg0^|J/Ocgb#_[Gx̣?g{7|Ũ #4J + g~zI|W|ɀ,;aQʆ5cr_CD{*[$MLruX(\ѐi% +9zFIUFa1 ԃfϠ7cĈ<;)1(N}c 7r j/-h Mnߦ㗴tҚ3̴h[Z`i6 [!Jv52F.o%sv@{X ѡglŀbUߡS%"5ZIz>1er-z ,Se!Z꼼P-ˡ +!䦙>ꤷ^*'y\6R:J+KRz>v#.:#]zUnň:X0t{܀+n֬Q`4jms^;1*.=z򺩳z3"]M 6Ԁ_u18X)=o?$x (-g +EZBZX/ HDՊ@Q3FwBQN$ +ɢL#{[[ջ$QZ͂ 5d+Z7 lԛD0tKuKL+|18p_P3i:N] aAy kP~V( b|wP.X vUq{ ~ +l$|*RUɗ#!EZ6#]]R,Z;[2Þ0к&PQ ТBzIʈ m]jij&aD6;>x8H=:OfGiHgsqY9~.TT +1(ҫYq`c=%@/Gcٓxd"i _^b*5\o%Ŧ^Jb]fL#:5x:{s)Hͮ`B:L1o悉ԣH D+#tL)fZNp:i-W0PcuNf% +I|D9Mf-2ןJsv"uQztnU&"ΧM{(WȖq 3u 4JD<W v5Q#5:=jv"kFO#B\i[wBpCNQrV@HmV휊үVskAFH#׊7@y4_S^iYkeכ]1oVpj$ sX~ذe%J4p)$6 /<lw]N[H :g^KgX5xUl0}h֋(qiPK{(Q͑tCcݟK;R9!U}~cYR֧`t۶8XFf/1K5:q>#Ibfyq-=V9ߧ<EV@ гT*&ZwРI< MCI\idSҤ9U0OYHre Qw;U(?$iqj\Tל |-BQIoVazIxѱڏo9C0C'4uAdt|1Uy1/٭HGaE,`LADrT)+ u b#xN>'b|HPͳXL ӦEZ3<T[(i&\ w-ym=ZUYPkcx$75^^C?2 шy]2^uRɁn[+{";w{ {ULyE y셈hҰip"AS(11X7(UpAzy&1~c'N9i +$}c[ᦙY(cT|uK[~zu Yp(1M2qssz`%h-ăCQ (g kEX܋p1r.!Rd{dzLS PMB3 ssť)%JAnvV54!iq"$ +ңHShdzQ;Tlb(jN,FjU-dFGL +U蝊!b2oL hF'(Ea?K~ƪ*Z +"0J +ƪh)wh8&鄡bS׫+S<a\aL~qG#DxBa:VJƞJ3Q"<&|{p&|6LWH唄-uF(fiU0WN457ܾo,^biU~N\(c%TvPLf8mzbNā%ԪͱIL6dD&Ū$ELT2R/z8Q͂ dv˿ņ ]e6{L'fM$xHTb"(!hNL'Ȇn}6, }H=Dy=ȣj&(QU)c88aLx}U$O|s,?֋ƾXGMQG£k|8 %×;2}htc!ty 1- +٘N>%Č^Z!@RZNȃ$)tF HTTHXia%S +Q*9YkɢB3(uBId:iQ +Bk$ -&єgHHpB!)ٓ3|-B)Jk +dmNG!8eaXUqWCs:?R,6όC #H_F~ +DV;CXCt3de#쵣J7C.S/F 7Q!L^E#"8/5,ѯ+[(_hA ׼e=좉%3K}Rv2NP&Kbrh+FAzJ(o#^dBx+f"T{^Ad_dg}cv.+ek/) +5Q&O q>ٴ⺄>En$Ə5- GoM&3oU(%*HKG7jzUN`l&FWD;ģN'*Rl`Y1]jLCLbh<3*!QqEhIID GAJThb2 2CτQ0D =hxhE +WX8v.ӮjBh(.X,D,D0̰C 79HI!hp1g @(EUfO$xb8 ^pKh8_8p) )0ڔH$TƥKA̗0Q/uR Wث +^oB]NW5pӇ qh;ʄjk֬ % " "򂈈` j=ÅT8y΍ι`vM=Q8P 0=<|Bm:^R%nLM^[EE8-HiTb11 wa<;KN5b ?+h(^(I*N1+f0YIs<˪ԞwcVN:/l|aȟ`ܻ4C1Iِ\115bI|qeڏ3z0Ꜯ+cȋ^T񊣘F:K&ô;& eI Q,(\X$G̒HKz#eJK[C*ܶu˹Rӡƶ8Ae +2VM8UDycG))UT1U]ƯIn`ƮVĕa%ȡ;Z<]EWBh221R;iVk4O((a_W#̛: YT1GDTsHh#ޒ%32]"i\-yևkiPh4"YdX3-yV3nsݝZp5-!GGco]#d+ 2O6z?QETե/ZT(Q4Cm<kbnmBlWu_&̍ܗb rM O^'x*8DA|H*e"-DLrǾm}T]jcd$1%wQyRaK&ZX3Jf#IH rl10yKQM]t6)~FY(vUȰdS5斚'OG +CBr2-B9!Dn ;\WpBf<!\zϫIqV:DDPG jR!ʪ+WPjl&ƋOSʳ\IhErP;ʪ)D +>㺫芯کaln/h}[WZZhfծ1|ƉtzHҒ"TŦ2z[#O /gI ƖS0 E#+TݑDӵ*S;RաBM%(AAZQ$ :/ ctpm ҧZ#-@A4pw@(@` +٪k5> +/FbU*M|$!$a  TT5uժBTDr UT!&QB, t u4 áS8tJOal`aK'D +UKrP$; MF5ZABerbUH4t$d qu&$f߃Oz' kGi'uZZia\ԟ -qH #:13lY*>*u"FNx]GCF)}7 -)H9VZX ] (J(z%-aSR2ɫHb1R/ ҋ;c#H,ӶŴ2)RwhY a"d@='E FGC)O0 iB eQ8DzA!A2Z +5,N] dQLn:8 A80t03C|LR;X!|K*7Pr ) !K 5!>/pcqVB"Ou>dfaV0fӇ +K 4(R5zQqdBM:J0J!85,A8gHJ@,7^Ê^"ΐ !!KǮ +8CBt@9OnJ d#: +K0'|궼 .{2sgF.aDH^B8Q~QWSXAmg֡= { +.شN@fRIFK +kxĭ0#80$B"a%Ua'wziRGB&H"CBA(*T2F:rC5G  _ +1 {tlϔS)|Xp{RPl=|ɐg0u9k? aިeh]Eh-Ɛ}w+ʍCҼfb饉+eOT^ۭ 9󸨫d`g4GCQA=bɷerD@$NpYiu}B25 [*}AYJX&G/eȯ3RNr$12ݒ4":1cʶ>gI3l֏ՒߧXN.0-o1С@{I:aQi` $5q1Q6۽qռN8Stp*OF,Ko/?P&>xF2V*sx1>Mfoͺ3zpuDH¿Mz51'>'ٶ0%PH6gp#ƜdAQZS:c Rn .,_߸?]4m@k=d :G{v͜ +E0+7(?4k|ht kihsi-FIbjޟ{@fLh4?b)ܨx, G9 + WolSvf~T))0*bm HS !JDKVMMSH+bzcK"f$ /A6aCß:.:6P0ҰOD#ߦ Ӵ)JQ:C"tihm@b%8:!kFhsszYaΫ9,ǵծ:UXzRތiY+FlpR '6X($`EF) ߡy"_/PAFWfDKGuCJ$:2\@v,:&@CAyuaгl;џG_Eoa`/3Rhh{к 8LP qׇD$"W(NQiXbω÷PtY@e =VF(H)|"AdfңP=$j t֝^.+ >nRl]1ћQ#DAkb~=! 䠭ڷLfUM_{Ʌ6ȅ|)jb +,$&ڑ[c^6ی򺪄+΀* 잺s _t-YQy`X+m|u x8shE#Zv]|꧛ZN:f5."MBǮjغ\$B(Fyt2l6H>Xec޺P(9݈R=D#fkQ4%* *ߨu<ӛ:SSaIrPxsWӒY4fرs},%>y5rO9rTdfrڢ2ヴ"ctt`tlrpA-+sJ/x1˗x {hQvT/Ⴢx%j{ w΅L :ߛqSMJH5MɲhA9A< =ihh:%Z}ZKÞVx]Y^ +NL ԙa'"&$ zq?)}l&s X=Ym6S ![#HǶ Xr2W\#տ>k2tT7Oo 2k6۰g )|j`5p]Y)8`͒[f! '_Kl ?H̱ACAƦ2bI"w&NC{lBLn';6 9uam._k^b(s1l^jT]1_ BGdZfb<HEz~}`;;{QG~bAlOʣ?<6#?.Y g% +T +EN8d^9Lʃ7 J-Ͷ38|1QxN^W?SŴ0,#gސS&邐+ ,fOtuT#ޢώhƶ ټ5Q^J:Tb^(-FҦݣXꆒ> +@[| +%0}"N2V—x&F]7 ?EKYZ.\yH&7Z}хv4JH$Xb3AC[vˍ hU Мt~<=6N(T*gU9"ݠV\eͭd$a~oX)we;̎8ePDT uwȤoVY8`0Bfc<匮A/p, +yEN ({k3]֓g-2UE5fM <ms#NYzӲes-iPKza`1Dt3!F)Jũ%xLuE +#״QyY)#dÓl1=jDMA L|=>]DBE$.Rf8"5v/~Ϥ (+C%F"'$\%+%|1hDfBuʵ\}<"gWv"U|1,lݎ'T\նdXef2!]fx%x ߜ ebuKGАXڀp .x2`\$<fTpbKg +/X,mid,SֳΦ!^y^ؔKbmM?ԿNϘ҂Pg1l, 9}'Rc3\}[`,Bl{lpxK>]"6- lGTĀ8Y0B3NuzGQ2)XRb&5Zol?ɷ:x-!^Pe1'\ u&[ g+.[HkzH(,(Lri5dpVSb޽#%Cݷ,79#q_'0}̾xC aZ`9-?hS̥PHr9*Pu },02WW#zUu"-zI/ R4j +֨6KvzeVrJzfjc?\fT?̸ؑ@ein ѣR5jevM͏9G2b ߹ւ8 +(3[*[1Ȣ̞#ԑ˦ҿ|Ƕȧn+}'\ٹ&PL:wkN؅.v7z1oD7b.ff $^sJoJ81 +HV*ea lBQH >U(ԑh`$ +)1C1(Ic̃6F0A<a+z[tb",T X-P}0}a^A,35kfuِl qpBv)=Oc|')&JȘ=Jj&^_e|ҝyUfY&u 7h. +y\.o1淘~Y1iW,]3FCl -PIF[z ФAqSKV:JE.MF@_G*Y<"J'T jğ[p!HkB<-^?&Qs8Rr<ڀڡ,9K?I#$D/vB@ݜKyuQaxQ^D> +3'EwyY Tc" vIbr ( Jcr45[5(SSЏߎ%#}vAQ\=%7@o[Vzܛ5\I\J|]"BY[hWԙSfk^@I6selZ/DVb43 F >3``<1bHD +)M:L?8;b-)acĢR:^Kw=&X (^8 vm~Xe3[PTe pGCFIR o,CCUŴjc ؒE1jEK,+ӆpsڵv"=ۗfqh k(5lH9ԯw5< mN"FlԐ4&Fe7# BF6?(hȴ?`l60_ڠ[b (D^Εe<4r̜<Y}-u!wa>z03LPu6%nĉ:9역.-!G;XGy9 +YLmm@?娪3\U|rEs!x_S iB$_(:><[\,z_K J!{9. )'#Dav᦬uPA&bP5_x8sM&q5JcTxשqͦE4,X%`ӟfnwG/AGtɣ&Y\~U4(n!}r`}b6$Y8TH>+Z=7WGاGrޱ@^N9Zpvcn +! N% y/Lu ]/`W0ЄassnL bvɋAEY eʰk -m3#.~aqqBP@Ts\:s(UCI.XCePTHA' +х`?o8u# qpR[ߏf~#Da9M8He20^a!5#-!8H[![n<)È/C*=p{1as|\E0"ч\}ؚÊmC6.CXdbߊq" b_*B8E"nRD qK/SO!Ɖp~43B!xͯ"+?*jV nm]P[>|XŌ&>|aڋ|{KP{_r0K!`d=RA Ha\0$P30S>Cyz51CaA3Ƣc p<ͣ`~y8ʣ2ɠ6y09̞)c2&$.c{cfhxa)h|x@03&,<(ՌތǮ9cA~gwϠ]4; + + &f34L~//uVV[Y:hݯ` +ipQ8D}S +^cv @юNA KbiH@Ļ<(p=8d҂lE`7s`Pp@u|'8:_B~eu"7Z}c0cJovE &: +n0Gt +sC]n6F xTnm l͜60J#gcLuz}S ,Ɔ3MalsJ *!v}5V|kD1`4!"k>v~5 Vip؏j-MЎEgv4i`Ў6 PLb,`mE;l4&-a>nymZۆF $4D%h lVct&(x3&M;XR˷hmF۱5۱Xqv,Ɍ^;F332egWFp`uUL”Qx2L:PpGɠ;( fy bA`~9dtQw[cTO*ߘh10ZwLšrT +μcjrtc0qS9:]'ƃC#QR7<=vZ,/ )XCc u(kT %^JdG"6a(XE!8F+ 0u``_" F0+eq-Xg`y0>}m|P#j>t/Qj_rKBZ~,|s?ۋuCsqJ/d.@.Eċ& x' û#]`%ׅ dX,hE\Xr$A&?RFS\$ kAdҷ` x:nAC{m9[2!G- +(V!۬? ѪZ0=\1dcZ!G l !ZL,:fCPtC\"kBDK8QD6@<Ƣ~!Xh0EaQ'BA`* ,H] +,B-BSXn^Ap+2uzb$?Wd$#qf[FX +Bk{cYV+ɡ +t72SAQUUD#kU̍i), ++rDlJ@G7rY.Ai +zdQFQ^#(ES6$SJj-1%}K_Gt'1I^I/0 &Ī -L$$H".wZ`IhW?Xu$R #Ap(::4a*$3Mh vh&D GkBGPf"=6!#ƛ`.'smqr(]9YwyNوB5"_'F Q#$"-6>E'DBO=Г#Ji#5Z<3d1B# 4w(<9W`n0jtņ`pgg[ AUr 2RXW%e9=!R +E Q,BHw Bx?L z1%STwKDi&Ml92Ҕe` "$+'d Lؿy k`ǔ$<sb"R4eB-MZlS~8On:S}74Kк86 M6DX?۔{B?D)8vMY?)k2pM!z>d [> KO‡.O{Wn4L{La=`jZK7=#z`̗R6D<Wy(E(=p(*PT _J wm)0;rF+%V:8.ROo"$t`G){rx422)^9~NF))xq0gM$eiS6)V@JI!V +&[J>^m`JeF)V+W@2>)ܔ_N\v +y=?S͠2o|2*I8&pPLT&T8T2uTaFgFcT_U)Ju*H U!UJK)X.yaV *4dB28>Oqhpoa#E%PȢ>ZMV;.1UsB0 +FpN4RLU(F'^CPת(l +C*<p;ߞ"7PWWǯl|P}]!Vc"N6Vg+p* |6@ԹB-+č JaC +|/_LkX`!@:X %pb!4˜ ca3 v,dJe"Ks3 YijeQ/}Y 0zzY 0Bg`,L:A 1Z J ̀*f~j2fhĵCز`2C8@[8'e{iǐG"u`^޲NoYY bpaYb*.4 nȅr0ఎG7a`r.+^0#B=OR,`po]fy9.UdiۥP/!uY.@~kȋKaջ2r1W]h̫ $ %P>à̅E<\HieCnaͶ@~Y-젿LZ TBjeq,0,8QS<,̝3 k0I,7>LIXHf`A{¬+x_?"[P0 i$+0rn(UB\72HJ ZSAb +OcRT a*Dl OI8SeW)1K-?wĵ4.) HH!E!qp92mdH +.B}P@'åIp T?넖,-35'2=ˬ㼂I&hЄ`"3 fK@f„* /s ) Z9 vZޔ,Jg$>3I0| +`$<\_$f `패 FG,ڣKc#{pl&4b*E,F4*i֩AA+Ȏ "d\fBzS54C`W)A!!5kX^|,]A0g} l6[4u5(%?6a +/c~a6scf}dh}6aC>ڌfn{pmZ 9ApC<;nȃ V +uFX݌캛q6!ڙ7ӁA&7Ğ7@cf; HZB䠴ȑG@%rA:SpP fMnpmT x^5`={DPN2NԠoS4Q@ odf g2j83*f]y(xYp nqjC41H8DU}(Ò9uLga+ :e%i9g/6 3g欇П:gtFj94b80=tw +l?+~z`aNC,buCh0P hca uf[:ana05vVe 6jGiu;dCtܱ:x f΀_,ʐ{'U^;CgEN 8oAPm Kz~g& 6|HXic| CO qiu[VxO$&τ䕧ߟC`0x萀&`:$2CG Lȏ(0Fu/7; Tz>;×Dĕ,m:WXtlCltvt>`*c:s(ԁ9{nXq d8Ц"6X@d*݀X j3ګEb{\])۟~@'_N'LNEh Tڡ0X3й3`ރof̡rQe`y-f:eeuIt  C*FRI} g 裻@WЏ^8/+c#uY(n}@hxia׸̞>.!!c 6fϲJh=n6"a,AO\ +L.[*g Pzi;$u=,4sL +XÏ݂OsϹ'YktZb' %3NiXzY ,_VDL|(-xE{R2WJ#oA| HF|&ʋ/GI`c)o -JiLB'}*aLe7@QIk~]g'TPgo`v|#<W ]Hs@[ۧ!BqdyRNJ,<-2`p!R tf Ig[kHH(ޞ\ix/}j*$OfqN꘣h6yXJ5qeFEh +dU6T" *NJ{P?<>c>kJA4*;-ܳCjl t҄ F& %"š!V0rD@ +D8oqw!)`5 +qgI8Z@{Pf.Ye*Ƨ:~)yΜJJD=z2:*Ba{B8`5 S1ŇA +c*Yz3*YŰ )|fm[汲We\ֈ㚩Uh#~Jl#bX(imjcFjʹ3unNg>b]mɲc3lDVd_.ls)ٿ\0PnZT/S*r 1Z0E\#29i*f¿:fXж]5*%Tڿ]hzVaܴPgR~NMXvOjt],M85D(Ҡe(6B}ܙZ;JܚPл}%:&߿n*fo&3Y j) ~s\qJ~|xHe؏xW4K8p*SYpTeT?B]]~Eu$C+vV#s,‹?1y3H5@7m~sc,d~& oT [6.hgE:?GlHLPI~.aT&a O +©3]x;2&~,vsuĚvQh L;K&[A!8xi²S-2UgHEHO}3D|H/}gNo25n7ҳǣGX"?D&8܉R 4s(ylq,4mwI9U_}tqFg%=kfTe*@q^KS}cI_RQrH|YKIA w>Skh0`}.C}%!XamYwS8/mEW"^d ؞V ႍRǬ!iS1KŒ8CnxDTL YTɡ +PO$J}BjMPZrP?H rdL8}7#.}Z$j0)Oeڒ>4oyhG\QuAz)gI~iq}g.ߕbm绞MFvC =Ȣ!0Kr*8fFF?#PeuAwg(ku>1q[OK1?ywT(ںCt*b@r lj.Kax*cP3𯕼I++(bsF±D` ,~=; zW +SE\^: {IHX Zpr&ǴކO\ Q7z\p&Ч >d;In8*ǂqo"_ +ǾK~oɶA]/(2|>4]{)[z_=4,d%ѸYl_}Kj'Uc۽?euْ}F{+"v/N_6vKӠw{@N6m[R,ۯVmعKmWjJnq5ј HB@{H/IJn؍ԎATޮ>3Kd&fwVZ"iO&?5+Mw˘}z˒zn >v6s;Q^ +mꬒZO"ww:~a=\7zaC7xVhhPLO Gw#J |:$ߙ~d*u,8r_FѨ's,qM'90ʧ}wĢ^M7Fq8.-ѻ:1wVN~5H艬:q] eyf NJ_9 l+tH9'6rރut!ACL]_ H'uP˷%SXʾZD]=pK*tHyP~@ 1'}jd#.-^4X%rc[7Ϝ +1)?`?b>8&@'fPm?oh6Z34ga;;A?tEã As,=_9_ePI~RNJv=WVW< ?ίxyM{;x7$~yY8k>͙ͺχo{,oY3#(ɒ_ȼ1?0Js923ߏ4.sh}mN V +DQd,_YwWw (szٍyg,§AgJx>clRB3 + CK&w7BtN+cdkw i^uodU¾;rۮrs\_AYL[GQ.ܯϸ=V;J hIwTi0NAx`OKr{ GC$K菟ho0knfmY5Cb1r{ ᶛݝB:LIlv#G}FPJ)v !zH]ϼfG.zMqvAOfݥ/iGyHvc/,,$>!Qsol?_VhuQفz< rC*\ʎS\Zz 醦[ݻnљX +|QnFްYD{1ڋYXn;`bGE V9q^ +_|OմEwnת "%wTAUW g1_4UZJyݺ AĔ/[%ﭚEyAV֢Z]xڦLM3]@H-颢,d.z\X3+iI>?W*`rI' X!V;d+N9_c piۙĉ@ vũ'qe.)^귖ŪI}.5̲~QG}X&çV +ABC}7@P7sg +T}4΍^<}15co:kozگoM˚ HƇyfoE/*|' &[K>=S2\c?*=~ۥkHdIOR ƵNɼpq6ʛF 6d+0B n }Hi.<fOenh͕@+U#EM5`ɉ=ǟA.djJ[~9g1{i[h"BvYËm {HJhfs7'{GL1/&;2sʄ6ФPBKs~wjӖT6l.sO$m២?$7410M_/_tCIc\]tj15ɴ-RTZ} uG,)1 GU@lPڌm2^L}`w!@Q9rHהrPn?<X&Cfd,-bxMNYNls%w*(vϣ׏"G^D":f:R$-z?Y!OH.wM3pRHw[MWqJ >NUSpʹkW,8$// >"P}d8Gn?SH +vR8OP^S޹D㣶X\C 2Bo8AiZ#ZXyg$9s)nd,@Dgz;M- , ubz,D9;U.psdaT1q!|c^MIxU+o P,#&bAcqN="nA\1 !*bpý6NG0! endstream endobj 16 0 obj <>stream +E>^:?1n(Ү>^aM!8RH*~f4C&#܅Axm f =ARG:bax0A8ClUG2gj.Aq7 {+!¾ʽGnnNI&Bio9~va-&-iJڻRM5Lz4ior(ɴw˦,Y(-S15NM{GJOyb˖iob0b0/=X1/'-h_roJ9. ;_!\Q +yVՍļ˼Emr/TOڢW'ahQL0+*|hH6Ӡ\5wH@nPn83M_z#{\KM!aNH{v6'?0AN%{.ʾ<;zC$6{)ݯ 6Xk.ܴ2-#2vwlضmns-^'J|C)AMd-2h`ztiE z"yڕ[k!|#{r{eIFh.n/9&ýc݈)H{GyoRZTAS/c8\Dfޤk!yK %&S{3%>U? +{=I.:*w9˳,Ax+b2=VcG޻ +QQ|lq mƣEd7¿ 5鎐FYݚZ-&pVG{=q,C< \ܙ%RwB/lipx2n5ei󦄰7Z4 b(*&$92; >†7#P?h +$xu¾;MnVuwKvt']gԥ]~#rCrNfG!Fe:YTlLP g}=t_}Q/~Powݡq՘rtK@fCmǐ`I=9t6e 97O$'iV<s#n]6_y *3|~5I=HM%xxO/_Lwf^]'$T4MmR+O$&]آأ|p\yN͊E5 +o*^vC O6A=Wr%y;$"$~Y rYbۏKV@"&&Ap;FjDGp}'mW/j_2klR\[lOվV`خginaXSBp/VsrqWϵ3'dӤ(o* dkaKcS{Gȳ{h&Æ{)P{ڷ ʬ4c527TT:$s%8lB߰@2(1cp>[m%Azßm`i%QSm4vL +5ζ]-?$Ϡ1wIّ!w>K7Ef4I{f~ز=ӖI-X[%Hٵ4.u-@Y I6-e'1qΩdPR ۹} 4a Q؍w6YXel))P 3_rC3(M_`4@lmSS ۞Cdɇ36(vt(1$lG0y_k͍Ex`c?m3x +lq.-XV#݋5k)~ƭ:ྯ Z+ɲߋglc{ͳ|rZgp0/5|坃@Gk=pbs+w"wEt:띥u(4rDB}/?A\cZKxj55m_rK8mw]4΢گ55lV#ö@57' 70xtκBޱuKSNhf]G۩18d 'RL4jœpn3!(<䳺ً/Vs"i홽>jY"aˬzJ\gtƇQd'S w+$qT-AP]zOMn11VN*:LI8 05#V #sXc&LQoqBHmb Nգ]nGk;6<WQCި6%a5GpQz?ݮ|:y>ϧ4Yxyl9`z"plNŽTT5i#|BnRԱM'gMViWe5ӅQ\G\ {px +_:D߽̎KKgԵ4ce8,#gSևr{%Ro[FM %<{HAS2o-ɬr#Q3R91v,LMr%8K oL [h<ܛmLs9ٿOPY_GnNWEìLٱ+mfe-mj73ds؍ T=$ㆤj)O٣+'v,zP֟v+%Y"6Ng% )瘚qЋ0"(Mܬhni B<-ܔW +FSTPozS{(Z B@(%jhD]TE,*¢i5pt$&=D(RF5F$ ~Qׁ8䎂{Oty}gD"eH%3qXR#{Ԥ9!>"#8ށ:*[Πy]Z;adpPj2mTtibNJ^_Wd]ڹqj~NIN"ikf#˓{oDG~ʄ@u +O /nO~+=/EZ;S~|#CR RTQI^V9 qNiM멏"Ŧ \R*d0A-UCJq*4AUw2&(겟mͬJ"yU+X $YW2n{ղ׮ވ4Oס ^˯I^ mpXʏwZxh1+ +2 "כ]TE+ֶ;hp::VP>U̳nRq/ڐp62Xq} '>p z AQc]WzDQu=FGyuy7 +@#QԾd+Iد@WW9[/t폑|C'Nkl9[m(q+տd8fTS2~o Ӑk pMWp8Bؗqn0lжKEM-:䪸;MLn8r- ] 2s73E!;U (Inҕ +\,($jo]́]g2/LvQ"xRב "NU[w.\I n0DNc,xhٵ["^M7^nW }̤Rˁ ם)( 4ƅz|YgC (nJWގmY]+7/XE,7=[Qe%FRr}_u֫>=7@n~5_n(V* kkC4*J~o`?7`O^锹;g*uK3N+(>p.RB物'ʕEP,>0t/~)Q|0db1 8..MX>e/d򿾌e`X9 +yp7pf+n`˒~)`v5N:(ħՀQ( +#`gts  +T3ɒx36#`>VO`m@5Se|k z#`p:MoѸ9FiP4vKG9 @03tɈ VLB -8QPdBeK:h>F{V(BMHG ]UAl 'NwU&|,GNU ixԲ9ÐW@ΎhbCV)p=Ԧ:8Ge_۹ VR[QXD^ l5"50[4gmc[V0UqljSs;= Y}X}OA5  ([ +Lwj'RmXQpQ}{G0oO$"o|.8ՓlUmc8iDcKθUR;J=C5G FTHe'<:%led1mz5~$U J`e6sOpfEbOpG$ 1_rP}(=jOZ5to`\֭Op67A'Xg  2/5 y +Vt,vx'k) P`[n-FhȕǾk-X3~HuY\u ty fF r +uYT aS}_(!>1NH nU V!58E+`=4^`Ay1 +#AY Ď#"`:>:v>j0a>&,<5^gs"JvT˥8/8 ¢ ː[l b Vb"F m$pKk @")So .0S F_ưKʭҵ$x-"_po躝3̣/Rr "gj>.60KpNܯ8ÙjC^k8oX5"byևaR +p/)5 u~Xym?yzxOL7/ΉeA,(XfSsAPfcl!@bRUzMN@}ㅜ b]'c)Oxaܖ XVotp0HǷqq%pܐ6zqL;f䘷T,Fǿ}\h= W3cd){' +5DUdyg?wGm|DuҡTd,j֗2Lߙ`3G'dB{[ Z+JU(oL2(rOىWت"RU޳|p TeveT(^Z+n["BrY|5ա10+;af< mlռ{$*:̬m``9/'gkECLSO.lۼ̉i. +Թs O Ch=LK\v>]D̚g֢FA:&=zvf5gi5Xtt'l(kjGş5,, КE<Op(Y%p2,$#}iIS Bdpy&vwubia:m;C3Z9mrA4+$RF X( LTԚ"W*"m7AdH5ԨkKU~X c]j/s'Iz>JNeKZ0ZPIu5֒j-Y2yu5m~|]N{܋=I`Oؚq](M+wc'Ǚ@9;vӤ bHG &x(Խ}c0[ aRe5lVտ9>l}jڿ>5n /ΎMMяA'<D?O_ cgеa,?6I~W3jJɓ +2K &[J͡AYM~E.Jv2:tG*oSkD Ʒ2abeS(a&[Qva8EtCG ({Djʉ !&MJ/B @E} B< )C-yHkdWkkWClkMklYwS~(vᤂ#Pv5#QK&l7΢ +Zn +Sթ"nZˑm|q#Dwpl?[V"{UT鏭nn?NђJ$+lnH" ÊzBR?]elXr3`c`3 {9c&ٰ71!7l܇kj L<U_ÎN_ { RL(͍cb| ְmJT' vkx8EAIE@NAMj>6?-^<;-ꉁ+<2<XoD) 9aBžaHA$CALVe }#F`f8KK&U| ha?f}L!U dO6^iiP}UN[&~AldOv~_ l'ڢf0tQ +ldОpC +iWˑi4'TR;Q*ny-JӲtXkݒD'qZ# HR͒Cn*3)-m\ j˳}fJRV|@>P1نJ+.:w$AzKR +٭B vm$yp98ۏDjrن9*t˘K;XCſk<>tmwlaghAڹ4CA/rvЮS 2hDMe~ڮ/*L|M\AhOжԌŸ +H ?JF}\dRmLǛ<бS~KGJOl- +dw*N +?Ph]aLE(u8؎B3ݳ'60g.oP O<0lBdXE9%Ζ5i+d@@yW"ۈh;IRth~i2X=Xy']ֶCj<ۊm;slvmr܊l}Er0n*t-p] +ov=UL}ĺZаۢzVG0iУZ̅{xF$z; +ޓ57g ٯQNҫ7pƿقmJivOo>fm{sBg?[.<=]\v368䛢ԀN=zCFnFqX`[YSOOqݓą}h8F0Wh=u ~/)WA(lj q#dQ'y@$?r;zPEk +)6CrF[~TY9s&EoZ>|_:bΗAn!A{\nGҀ6\J*sWٺAyȻN7GR|[i>cy-|ЅL ݼ%G؁tVA;G q2҅}=顤az2PKwIA]TԁBJr?&u%qv~D\z*٭^Q&Hza1r,Ki˾#1uf}u;;RCcd ˮD?sYjiD~Lk7Wr4W]y9R~8 +5&k-E QXqN)zb3ޙf9>Bp +F;2;n3)}!;{-E֢_rl 䍡vG8)Tvx1@"^ך`xWT$zEh|3q|RzB& x W+LɛS^8mvbŋ'W s<|e6_ ?/muѷg{1=>ӯMeciEjgOx[r+ᢔRLAxnb;FH؎,/;:[C[ŽgIw ~eVyֿ^Ь p>nlv]/X]a=̑5]<( x}ADt +5RpzJ0? ZDl# Rƣ%Օm`&1rS}v?x/VYef CG *-GI2g6E1eƿϰbF~{?S*_4blF/QBH65=-?L?މb>dt̾o7)ئg}H4iI0g!yy#_K}?#uzM,[;4fZ"-`Uҥ&:/aRoJɽ͆^̽jİdR_lLm: =4=fԛ;M@l}{{O1S < (F]nچ[|i*2oF#ѱ6GXUf7)i600'fNFKlY]#d1utsoe 7?ce`^L3'9})~ SM/D1}a 3혙cfD!g0úM?;JSgD뎤nmibT{s$i;a&G1kz-i *I=-(ZRciGX=M+K=iüK&+sFWUz> zENtm$(wYcWSd}'o&doe\UQ"z+14'G%k&8B9Rc_9ʉCcH>< Nзi_&韁,κsMC\+~^{CֿM߲Kk.:> s q?]ؤPSN /B-}Hk$}?a埁MvƠk$.Y +7ۏՆyU%OF.Cӡ +j0Ez.rSC<`a`e6pH4= /Gcy18Cs}@TFF;a4b0d08pTo0#f `42&?F5魲JKalw;z&awsp"¼nd=6}?M|{XscI$%-?gdi:TUvfYuYc`gwievg`}%XúM#T?<&!J߁ Tͧj(~ z(qF3zƈZ95|n{&~1Կ*GVaPAㄲܕM'pUxV_Y<_ߣO1g}GϪ"kN6?/Ŵvq%WQ"=|=En+; ̩H˜p1 +#iXI7@-Nzf훉gcN7Aߊ: N5?S{x +7#DgpiC0+,W%>a\mzճGAr^VUYKv؇zO9}N#-@O- -@XHO+K=L'3P3~.ƿDŎ!!#?c]Zf*;jdPA ڴķ9Sb0'3vu=4vi]!%RM EYV1vqeUvFg!P5ty}]\q>X[_ӎeBW( D GH3I޺|,59v +KGn];E5[$Fj{(em܏ _C\㈰^d+ dUگn"jtj:㛍Rz +K h1& LE**^ff?XXYӄ|jM}齘` 0l@9odhAz>~aÌPGT8{?CqE2' KP|!%F ISG&<}34$#JxgFP*zCߊ^Bc.a%@Ⱥ9LsGΡqY#[Wdoamr)8 j,+6<S̚k{쬫>нaTsQcH=v[U9D|`Y}#4hoeʬ[%;QU&mAw\&nHzGN| %v]aBDŎwbIZ;úM:kNA؃~ k>l?F@pOo  E玲diY9@`lpݾCiu$vɋbseevoDN[ǼO5vw3PϚ&I]VeP/oo_b(u?HMbEJp~%@C`!z(m#gaVz%PznMk b`?0A۷Ǻ=v=yz]V䧍$} PL9i.oL?'? EWvy308O̎KjD;LGU7MŴ*[\#1`84rDk~+)N;] =LMpukL/`XRaLXYwvXcLUCm>QLa]Za&uuEW7}փ2k6YttL7k0:;@1%,Ȟ0ӻKﱤX|KYC-t:^GBǖ0'Q!G_"Ǻw"2R,3seUTŤFMطa%HFcGzE-Ҳr?p,(l-ar]does:k[䲝g1b VUN"pu[$ s/³Ǿ. ?9 hZ%.H\^9|CܑL<PD@ ]+ev6U!#HIV=Nm=(sXy7edf^ng&tQx>mWw_`%]cg}=uе㈰=t"^A'`IV;3ObQ pTGieǘ;*?D ;6]#KWW[ٜz1:GTȉp7I@N `(d=PKҕv^C'hSL]CKh |Վy|&~zX_[$-ȽB: ru#˪..ǺK!iۀ&~1Hu 3tLqB_+K=hfi8ZvVϴON+SkδǾ ?댝h@vŘoeyLYZ6vvۏ߄3z?F ~Ho {Lh9%/v +]D@dGρr,AO]az| \B>Bs$i?B`e}sGޡ!"؏ng<3L8"lו`F3vIAIm>T_yHOBm+OFXjxGTAڟ+ڰnH>#;9M0oüˏ;~\w i㊬>Ȩ!* D$H$qALQO0<8C3""2A$;c/Yq^zi&!lx6>{82,ȣwdww:q5AJn(%~-% +fzKqPGk ˠ骇I?sc^a!JWJf4!r|"7i!$"t=_L>QUH툶jT$L;8FfwHN,~0Fښ=q 1x&?}n<4`?b@LZ=gFK1>[ ͠pk˝S]`oVT oKvԯ~@^vxx*~pyׇM?̑i&dbh808AMGD]0#Gz\ bpiEpni"[njԩV;.Çq@\ jt}1>ָ"XZ<}" Ԋ`'{jsG3* 6WCVtYAK bbl< #!l[󲒏ݧqV7;Q6S^V!r۝ *Iyn#ִ$GZxR[mA4XJu0*&zN+r [(>%ql,N0tBfe0S$-[e +5;)x>]L徚=%T;jTmQa +M})ys7pwUE%т:;-\SPRZXt0$~Bm=_9/% =x 0]_w}t#qS@LO}Ӌ'bggK5icU7oFpؕ*Bxe=W6Ej%`FH93:W様kvxufZci7.Rp h]N6u`iWpw"(-t]!Kȩ}bA,ڹ6gǪʲk +~Jپ%ӆ5B扭%7Ÿc%)a/lZgT Ayk{n'4ѠENԭ\cmK `.˔RJRVΘa}@sȮ?u־*Oq.m`7)??VX#p+OY*]F~+ZM}z [tY]Q]DQGE@* |3%F,$٪)l3(@D.zm 4WJ;כ耋MeNni=je;Nj:'Rny4%]Ti)f'/(co8CI%=+mGe9e-oщnFE6nH2DPhvn#f@(LDyL,`$W:qr"T]EYGC/U 0YМ3Œ=*X# 27M|Mu{OQ~2z+%j5Kx"eY]uI=(PGCi!:fSf{'s6{ejIM'ā]vFEf"(@m@WEz}|p{$Y˅WβE(G}) ޅ* ?& ^9uH%zd'}|p=Hbez,+ݧ\}ke+w! E(0&߀aEK"ʾӷ~_IPXEG@ cf"VF,cR고8dۇd%%ʴusuдÒAFAB؜pBC:Q>X7bU${0~x; OV-)s6yдu*Y&uȲ&Z4Zσ6UM5\:-Z Gki-%6_كX=RpAJ<4Q2,?6Z] gMvӳX>}U(?5>l]gjio#v%K^D8?=힕PH90Z&њz0)F g]SJ;مYt#T+jFZUO&9gvFhIhA}U(dD8=.cFV:y;0ڰ ^á 1,f9}lp%!#>e+\$ef?l^n71.z'ˁ&ͻU'HF,b צB'Q)XرY ĊMK{&Mwئ,4䫃09gX*y ׹`hu0 ;_+n #VRH;:!:^Fڗ-v/̓:m1t*bY^y"%vO%9 %߬U 5nY\z@=>Oj|]$Z5r+>&4؊Z}cYuyQN[Tu(E. +iL]Eߨ$L6W0F˘*u !T> [:6mzѦo5(qsb?kS"CPE0=j]c:dS#b.oĮ'Լ&1TX9cɈސc8w(Rc\l*B1m[Pmp7:&O>m̓jQ!ZU{C]t1ƭ8Y=utwN:.49f^AzKweUǽm npM @Y,MAV| .~tk3Ny%/%|ux4GW=~-el_@l0b)J0 HC8oCLIwڦSoe|sH$9~=@݄%@A>h+'mMt?dpi,cϾCBT`GRP:cǷ${ֶ]?,XG=^ɧD1-RY*3nu gv;dZw7E>jȰq ۰ӯHl~`")-hP8a1ivP+b4oƬ9fSjwMtniDl| 36E5PyLsIEOFTӼU8>0dlP*{& U%]wL'}:(&=~/gM[{(%@-c bW:(f? BF\qYݦf\8fABE `*fqׄM}$Zہ&BA>Hi}0r;m9f֚7U;h-Zo®KS?{'c6Y +u6lj1d08\>uF&>[ϓ6qiYB4$tm.zV]`V~3fUzZo:}X&w̶BGO[߄]Ey U%k=V>q)#W:y" "/mpI|eaObP"IiPxp`L8UԫH=5كUQ?/W7G,njz +4.Ce6oZwO&% 5E8&NX@jEp0\/ <8 l gM[qI8KxY&(AlGdgG:IE0;4&\Kf: w@gӶ7eAANarmΨ}Lה`Eir! {ZېQ!A8",T"7$u-Cⅳ/]Q{HK5h E h2 w3/ e?6pNYsڪ&_EvX~HV~2-2 0wFF}TOj(NAlmK|5G BasPa]fK_O^e>l\Ⴏ?! 3h<2om(IGI'(!MQ !_/"1VRMd=xxd4[Ӻś6dނ= d UQ7bXIfp~fx=L/j^&ՓT}>/(Ҁ ]Ok9I6,І-&1} hM&XEnjErnXj4]ә/'Hccǜ"3YNZ 1*p̝P`BDjI!wF9L8Zi`9ztk!#O%[0Z[ KxՃ^(.YxQ]Q]8ny$CU%M++x dMv+n3v=b_&!H̯Xz%5.q*V'hf5p~H@mn#dʿ[TqMh&!up4R Y4 q8?O|&^]( +/XΞeuOa&͂20C[X9WHqRG4oXBBE5; Do̪燖a^™3E +h и +h>z;݋6y_C_bHVOCD>ߺ7bտB1z g2/iReWFYȰ&xؼu\0I4?DJXq<=B1L%Ԓfecp6XPG +b9([̰kжE0.1lz^iK{ KDVWNEm$yҴ&sO9o0^G,[ۀQk-F3$4CD>1u +r@q9ByHv Ѓ?™6_>58coB +aKxwM&9$\}4mK.۟pL~+R(#prFm{׸ŠE[ 1pM/˨1! j B!"d:3Аg/U8V"6Ajmoa<+؏J-k @I~"+a8fp4YT"Vy"r[:űc}"OKQآbr[v\ТبrY ;܅ nQ,_ו5xZ\6ja3ۅ V? Y$>v юK"^i4xMGp +\G::8tsDNm+zX+ZTT*^ENBV TMr ަNˀ%& +[G0PD0%<XJE?qI@1->Y ުȠt֢w[!(d˜ N:-h%0%XlHcCa-t I^4Y00Bő@DBN̚h2tCNJzr cNz ;-_ tJ +\MXeo_69W}1nUGv #{;)@1rU/k6rMeܨVqk^{J5+I_@ o1pT9Y&#mguHk<(pԠЬ K&tsZ H!P1`*fN;Z~+(jH'*Gaṛڮw psB˜2pVL$ aM{W]ܕ>"*W 76ÔI0gBuHϠ]!?#$]I]zOuJFSJ,)?0/Ft"-u3 u)zQx+_evɱ.CO~ 1²yl9mE_DE9-2HZ΃3ށV}l5e~cc~( +H"@*N +Bu:uM +l,GfXA>嘘҅Qa8;]PpN@~XJ?M- ï]"붰M1tt|_7'ə?ґ8Eir+Ƀ!vNƘQ:YRG/f?OLKʻ|GZMҬvG5n>|iqB+E\'TֳE[>AXMD :}BqDzO9M>\(PGR9K;h)Da^Hox㼆Ϙriw3-8D!٤6cܦϠb׮7r"xL(z 3YGZ +)Gr8E ohO9't{ !/w\y*d?WIDI%Է$;&ЌIq phlQÁ)ő-GrX.}ߖ,8vjuV| km 9M)zqtN">)`hZE e9N IG'ZޣVnq~_x @نӐp#]opzdhu]K:`05Z4ZrpEq0nx)%nWzYnۉhZ{*vƻw6ri'|Mg i2n o'994˴ƢXmc\f@?pR;MZC,t `|ƼdoX2[/Y}#SƑVϐrlhargP\ '-y "|}w@[0+6iSw{mH^9Nv8':J. )ŭaL˩/p=lͷ;}~a@1dv ȱzdMҬAXfr%Ě5)պgIfH[ǥmT!x{MZ9j}M]zԧP.54p5m]l/FB +f"UFl$\V"XEF c8ařpAn=zBm)H4+Ch҄4OY̪[V66"N2q +պS_E.o\%''}妪ye1jy˾xb1bqT-ųt G +#HHgg4w^&\F-j{m:55Ei]kkEYu Ci ;uTNUmKr hF%U%)EF&>*~N"/-ųT/hUQ?Z]ul4mQM=ݩQ*6N7[ưkhg*$xZ9ޗ;x{B[(K]>1 l=Z-Vee5fv?dg%d]1Hkʈ"Iؐ@J 6kC|Y:ZJ/BK*OA\̦C_5A-j93lm~+`(ppDF4?q/@)\k&N < +#4 +K#Umd *$9GbF̩VnH}_Az?N0;L"dPDdtBL+DZ +)/56?)e<\)sN$YC" ÂQ_~+X(P S|_ !| `,6\ JC0$D2| ;a!ȵ]m[eWdHu" Zi| +VC (tB)"s>iIg$sqjq⛘[~ ?A/?`3+/C|#>AҨIg>X&f@,ي[fڥĢ_bZV`KۧB;mpN[BYiUVmT VJP[%RvW]Uvjiz܎Y@!aG/g_!|g />LNx \x/℉"c$F+Q:\wICPvvR٥,2 (3uD&M5nE4&DA'+ڧ>!zƄxEtH+%FiPQi)BG*oe%BXܽtN]pH{p _ES÷#OGop7O1靉Λq1YnPDsZ (M?PtycH:̯w^<| %DZ!.BX%,H/$16i[mT@5Rm5 ׊"DRĊQ}*5fN47owF q\,Am8n՟ZK2#=cxL[⡠Ȩ7dN|-T|29`1Z|>Ƃ% 0F/9Of#}kn65*Ja=lq)ƍ2JsI>t7{(]r2)u-Gt;ZdU۳-dY]Y|H@hHلo2S,#\qKn&IZ OOwi }32Mc,I?pZ!þW1Y s#`Y16AizY5.I[B89}RB} /oOMv +'W e74k+KP˧ٳ㥻s#Ak'*y|A&{LnQ qD`82ԐJ3 Cͤ7oUN-o$8(NI`^4`zak(u5g;PokAŞ}Bf%5||0 9Yǭ,{$M3󐴞{f+MA?kGM@FH.e>}omM͑73⥄sliq^Țk=1/o \$ۖo:w'*b]afa҇&wIh²p4SQ\V nQ'Dt.}c*5)*(譂~^GN6>$} kYqFמ/fȚ}+Yy`e t0##v+@2!9uuhhyLn["Q&y DUk;vPOd]s<>_ +IDDԕyx>GO;?h_M/ }P<|׏y؝{g +:Q\hD:PUD;'uFyE(Qj*M'DD'9H t*߱{#jc(2!j}l8ϑ^`ߨ>[bzw =N7=sh`fjՙy$뉷[hImin[67 ۖ<y!. 4 C\&UpV6z~ +f;}+f[8v!-hqt5=iV.]0_*Ya2YqU(lQJp_a{D=]XrЎ2 )X#KSOL/aodK]}ekcsbZp;P@>(;rX &s~U2*#xBۚyx<#C@I.B~pb=p_ tT<@/931S௏<+W C`*U0l_9,rX,]gYx9rS1o2ݸCG4 .kth[)p}ۼ<ӼXV(H&vݫU':*(nWx:*w>,3l\0F7 & Kha>!.GVG6jz1+awo>5?30s|ؗcxϱ(\mdU/г : ۝5 +>g0'E;D-QE_8&Yƃvkjk}S~uh#`A&ѲEz)=ɨ:NR\󈬪 恾 ۞$tи] +w{nz)غo1йxyBՏi;X"~OJku2@FnUu*s>Ӈ5/ ci}Fk}eG."C?Zه`D#meIga +d0R,x +6ntg'a]ûJKv@,{u(L$pXza 񶋊%۝f|xȲAEM- +J`S)M=QJ SgGA<&D0>_ydF +{O31(rwn)l2J-IY|(AZPG0Ta*)跞 +W`IW=lJ]H2x [npf3=Oe? =w`Kvq]!jň) \w^&&XmsWWVPyM۶Td]ج m$qخd7Y8 EP&aAB,^A]UWoRoB+hZwҾ gKП)}X*G=A<<5NA +1 +$+L$ΉB:N INûQC"{nEO8&']S %aHF4ĕ2=WBG`pٞYR\%lA^iWS!ƼV5~iINaV^ ̹B`Ps&f hC4 73Sj#L8J"7NgXWUdO֒b佝UU_h +l 35r^`CA']xiȸ}@< +ܧRNe{MTf-k!$>0jFtj0sWg9$d59|v {LUF^|H"DH>qK}WQ,O@̩(RorT%"y(EʖVl]\/ #`&1g.QgZ"vȁCZSdDJ? x_ .)ܩcOuu#cOGWzb"Fl[Z 9Oj`m~"PQZu/74U%q8%j>Qc`5L(c;c@,rD.7nS$B#7r/q?2e5f A]O84sSqD=𩓋@BۏhH@N +8qbn"1uȒ+ +&i8j~,CWm! &+ x8Ixm;hA:jZeA~*"S]Zch"x2PVpHĹ Խy4N c~:?q?ϟUg۳E9|nvHEV1!hᨈTA#FJ@8Dhz]rOf]}4̩Kn~a}駀~XR4./O8q͈ UF2\he%";^+4Ⳬ5r/ + 9a@RP^{;c`YT䆑VKa0$Uc7`~TՏ}aȐ+Cw&^U-y,[O {WWjWiW?"3nRe(.}uɳ6,yۓiy8Wˌrj 1\暂)&tBi:J43v\eޕ0L\28D阐HQfӛikc8pu{W.JYT|r5-Ɠsjuyi+i%t"dl 3 \6#`lPhP~ft$$ +ZG "ӦHFUZ8|8%6 Sh2m E + rPKJ<ʌqңnbXgJabC ~@%ޙx eTbɤ6fS质:2JroKg8{ZB ޷j>B:WQ+gAyϸ:{yaܼJ'Ц:dZ5cPmG.b= +~N@7fQ\:Y:,&ͪ@)fF >BF6ؓfp8"٫EiF=u)#֬Vř@ ݅O7FӴ3JY%s9 {^=~`}_-jʸ(n&.WRj*>&!eAf$!Ydr4JQ#L~t<(H7DQ$m>8 ;sPfLV[eøMޅ֯oHB}Ԣ/bÇ9~r mu悚YFnBnp{lWW`o ̮W nߘ@Nx0k:YzZ_[w̾ʘVR^+fqyn [Iq'0"8]wUq* ֒F᜹ vQ.={@#",9];!R9%u||F B44!m?O,x8swCy%T~/ UDKE3@J>ZCEt[> ~Zj|.w`szs8A7 rAZp:ZͰ:nGYD) bH{?(뾌e +D%/AsmvɎqK'OG +v`*lWQS*`LTIId 8b [ >4 &S$CE"VPC1S(ׅ.:xzfj.c $36c:ɗcgLN(y̎W,0qtʺa!?\sV6;k6/6/{0k cs=N!-nEŬU4j֌3R4.9e0S)(l!,B绥M$TnSXyCx]XiK9DCYiJfTgN!L#]AViyʙA NAyЌ=Ƭ/ + G1.Ǜ3k#,"ihRȠ<@]0Ev=%MD, 2^̧UUJ϶ 9;D?{E;C<;/͎nYg8rOxJDA`!Zª5f3ZUeK}n:a.Fd )~rO URfpDc>Zܿ@c6BH7up9?u97rfm'Vl~s%>yS5ч0 l0I_?(<\UݥW-6VtjkC9lU +SZS0}U" + +VjSMALLLG!"0Sb6faM* + UD 7f"#s2}*ر(B##|EB +*8Sg@Bx3jяB9EHy*eT̠TM` +dt) 6Rh$?qgw 4hI.Cac3{]jLޤ{p]vy%eJO]]M`3 G/\9 NOQ1z +03Ș_z@Dz`O1On''\2gǓ\-P#%كA: nÇchN*c:j5b,?_./BW#t>t]VfvUנygBE_C9gXWZE2l +f։H܍Uw۸VEt@ + XnZQLx=sڪEHdV55 )Hu 8 PCnaIǟT4W7>| "^R2#}ï?]PQ(sŻ*J n/aSd>=L\j1on.Ka +I k[ָf3 +)''Bx x(B+~7j؉[ +uN0%>n`@\rvEU.df%lV)>h'̨Sr^4௤(@$,0SĨ:1F3FG3^Z)ị.ȣ@mǗ =ξn}Id%~>},:E4-ZǻfS`>+eVZKU* +X)ԊK׶ M FT +$-y 6$j4@Yf%`]q6!oW'C~Vhzf'#(OM, +6~paH Kab?E7C HF"y$g)IUq~{,W-9*c햬eObFüѮVef\  5yg]zEßJwR..H.vUթd[1fOy߼K[WWWSl?qQ@Yaռv%̡Nli|НA & SSyl I YfFD%)QƙhSe^² Ҭ̣V<#2tWf[ag5NbfA˒Yʲbs ɸ0۱Yˮz A @3JaQtPI;#Pmj+AbȲq9vZ#'|~öڗ15hŀGMZUTT%-S.yjŒ_URԤ* B e Y8Ќ;'lD[(Xy@&b&8'Z2CG7$!1@/L}̼ +nQȭYаa_*`쥞j]1 wa0*-AsR5USӊ&XJFGbi/[1%&\(w +&lqUJ,zt;U/r`]g r>R8194%6wX49׏HOhPhE:F"05z +x=fAO[8qm3(;~:7[ؾ+V5e%&(~A<;9iAd2;VU]5f;F؝3teGs8ȝw`q6c!p=n1՚N'Yb} kVSؖ+dYU2+=g6q7@ڊ HuD/=Bxgq/и+HM +2TZ  my:}A_ba7z`nH.vI3ІYW􅖏f]q/Jv +$9PG.x;n(G`WdRf$I/p_+0vPɮE;%q,. wv6yX.tQH)/)y~mf $i>f/Gj[u4x ()Pv?2k:]M6vNU9$I$G!DGI0Yw2<k"f:Og&4|c9PbsQ R3->{(@/d}q1[>DYwoܳ+P>lreŔKӲ:Rxu׏aa/c?$oXΫѰzRvU ĨRp4>Zp욺4 =r>ud^asX@m +u{ՑHHL 9JS'Uf*(̤"IO[ھfٜCFA[~q#p&HqqS4]HRS.%`i*8H<ϛh /y:~"]D`#(w-jG0vOʺ*L<ryI.'HJ_B?Auj +zH>$jy?AHN|"@8OXv%'}H3uQ;)z?F`٭jkي;>YLJIQR.QGC&`m~Ve5x/SK6 +Hw;7hz +u pЧx >$q/sW(؍ v7[e_i;EFashþ5ìYʴh֏a{ZQ,rŒyه-Z̶,@dQzrRRQ;3<0< +(s-Kʏ("xg@K Tg<[O_cAj*5ۚq> \ Uj!z_!36 +}8N8nyBlݳY0TO.i2zDӑcE7 dM|d7' ȭu5n8/-|E9oC>[ eA[`&ܙbvU{)Dֵ*YRu p_PKնh)vK#:mY+e֙ODdFZ/QH}j6ocVAȗ$vE +vCg=ۚ o$DO-W&FE3FKͲzN_Z-j : gqmMڟ٢},x xd<1+ A(r=MZvT +ԢvP"8g 9~/lg!OZSwYƮh)JDžxA fZ;ZW;-حr՚mİWԙ21;Fp[SA/OGsaڮn4RS="GZEEz 7Ыw7P܍{BZUq=3GV=ڟaF?ѭg[=UɚcvU{s-L]Uy&O#V?QIê4AI KوhE4w}nm>=$׆Ϭ&ҤDОe`5]Y|l Cçu=Z ̅DtV? H8O-۲%a{JY"L.ʼ]m)ٗu ou7"/%$jw PY2uepR%8-aǐO4P3d=oV8uju6@}>ۘg mz +rh 7f!v2.%+YV|-O2 ,*c7ֿ?s6 ׬ b')OYu.YݒchvזGl"f_UCz~{#1:j?eUerU#, ~9j˺>ĉӯOXr + +L ΁G*ɟ|=XQ9hl</UqtW]^Kq\qǹaYzG! C^`VC˳Ǎr`g;(ljpBiO vD4Y37!Q5ՑH+(ǻCn-=Q?thViξ{F+'PGB%]ݢYVs`E7=U*./Н9l(&&$vw`hc:Oo8TyW bU+h4udJf>2Ȇ?tOYuw ֫KͶ +n_ ˚k/ ǹ_ïbHh9CE<"9XN_z +Jb;@MM5dv;/ FuҬ0l|w (nm$O: 6!ܵlwn 8aapzy~EOSgWȬ2 vv@s[JtT(3g?E5s/XIJ"jmB{ȀQbf! fhp"$`F$rYzX߶ͨ'ϋR(/YͶv.Z~&{yK܆(?赳T?z>Z v׵fI6]Ď#o ++Ӭ?7Nݷhwoiyy!Bp9W~/Yxfbj*: *3]YQ]ΪZy!ND^92_yُ݅[>܍R?J>dyl-I׉epOm 'y-rjqOh[mJ_U|>КoVYsQ {kCG}D6(,)ZGgm?xE/<ui\Vd|)}f慷Tk'AW k՞ػZ8h "/1w=&IaDk2*,z7N@ +ju}w}TUUZ.lmZn^5GQ8 Yi"Q4b2dJ)%)56M#P]BXRnhhL!s)ɵD43*& + WGdX!DᲥ m1*`q<G0;։Ql SoO4oklkTQG%G%N~z 1QX\&\J%9LNaq5TYZ7Q:(N]5K= Nf2/~ҵOV +y›Xa[G|s,LR +9݈1bbʒY>JAiu;B3׳]?Od׋N +?Ɋ-lQ+f`ޱ.꟱ɳDonp } #, "+ oFAH\,Ek"AuRx)\ 9rĐ.H +|3ph7=6 ;~?/ȸ'5n}޴ H2LJ +&L~4Dq=' ./vU®z sCJ좒}zՏ4 46sQ#%T6h"!0Ye%t $]nA?in=Y,Fr꛹WmySf̳8 +Ն~yĬH TKxm8_rtO=^{]UcMG2QD`E-pP=^wVK`.ec?F*-5 +ZG1[*"$܏\]z\'@OXPg TPxe7wiupO!8n(G7h10d<lj;a%6% `Tg{׾ۗlM=v"V &xF]'r<q\Z:&&qA+OYyv;A0V +6>!ݜtmz k`tKs"cgj '$0!c#*#A!D< @$ 9^TÌFJPMڑu}wX˼V!ˢ_`݆ˬ{Tօ腶uZ jۗ͡jt]f;cC.^bۨvN;ȺohM~ sx8m>UԳTvEYҶ6Jl(Q1ab=ʂYui41A7R:g-D֑s:Ej\j5]B* (QM{T+* ֞ji.|mI,'IhG'>lOKiW #i 7څM>h&lV!ˢQT,e[ AA|o^Dr>׶tÌԋ-M.eEmgGr^A x2,]"kgN?h^!*țY[r +3k~qk@( } :vM*E +aakShnĝ1+~Uîx@%5g>N)9~}D{nIҎQ<75BF}tQb8蒫9KM$ojO(( xRFdEx)&##[Nfҁ7 %B*t>\pRknxJ6궏_〣ކ։(1W6[A$$ D70D@'z$B̧P[[&ʂgX;᩿kn x#ňu|'N^~ 3ݳ˸]?Rn1 `hɫ# B7?xi>YErsk#n 4 5Kke-j^e +ac;)LB 5>VܼےZZ!0#7 xi!zrȯOh+R@~/4:R/"!b?h1{Ё`p`5 HsO{nZ焣W'6avTIRF#be"9iIrx t,勊o/ -ًz7.Jt3ɱ5_} 2iG{=| +qzUL|t; 79i9ޣ'n ?lDZv>GN@{E[#higEq)G:]*gZС\"AnBA R5&O B~DG焣5^]4#;?X7>#VvS_e70Bf C8-!!j \S++*BoHkh&(J8h=(+hf4:G0za襓nxi%^z1˗nT3LFN1ǃ<8`h ZiᄟBnBZ1 7_Eyl3peŬDzjF5ZZ ?"ot(י(1%8:5M=Uj}qXMb{Ra`eK}R%Ne8osziWC;Oߴd|ulOEkjY4qg1g-1ԫKxruAR/s-Lgٳrx=pnNY98qP[=ɿjWA[!p*kV˟$_ byrlH[ +s7<{N!;UYXLaZ@׾K7{'LsPɋ̡q񝛄C~kOgf~m( +ԑ/Q^D3#- f踑~&[v"&NAH:zukvIۏ8w937iHtRk5>:v!o˸m^ke +tL^=8~cb,֞&qZ<}a@fF〡F:֏~:H +W˓ރ/G̨A20"0 Um "(B "i@pBg vfvYv2t&$`*MG rgߔWNt:9* +{_SXB̢mb˨!p?9B_Ǐ ϦD[?X8~?1}_%o=ё!{]rz]dd(x.@|t{3 'ü6a/ T'q5*eJXEKռɭ׼۞nOvRKCԾ#iK4*h:0&ܓoOAbpzbO~V(;YЫt2' XRjzQG8k'w9^}wLn;:KŅ +d9Aa3!g>|ȸx柕uK0ÿpCGvdY)^~V3p/(?Wov׏{?7Nz¿U $ %%EndData XHRaster N/XMLUID : (_0_4_) ; (AI10_ArtUID0.24al (c7ea85e6-4a38-4265-9968-cda6cdfb741f) /UnicodeAI24 ImageRawDataU(d130b5ba-640d-4e62-8197-5fb136258cf5Alpha; %_ 575 817534.68Lq62.3077$ aĘiQNB&Lȹ|#:'`s_00{`l`C39(raS9'Mfݪ~0fdtN@Hr ] SBE~n\D!D!DM!Dd!D!D4!D101da5b814-d2a7-4543-82e5-bc383cf07042b6ae72a4-44d7-4bfe-8068-32107029ab6e-8135.2105 8176lh W n Q 7=@pr"Se~sd`S@$@Ț8jix9}7`ky ǁ@9PD!D!D! T +947a41fb1-e009-4cf4-b4a4-ea0df48d8c30d418b7b8-2f861-82a3-7abeb7d7adba74 662.252387d38b4ce-66da-4f53-8c9c-60e4a43907175d631255-5b2c-43a1-9dc3-e25c12f5658947461252373. Q !"<| Z\yᑓN Do'l` :SrYہtA0Yz|ANcs|o l582787d56-ff10-4654-a757-addad3bed434a4306506-6cdc-4da7-8c1b-643650ebd67735.ml10SVGFilter / : /XMLNode : (fxmlnode-nodenamvalu1typ/ArrayeTurbulenc;children/(t2attribute; (resultbaseFrequency(0.0numOctavesstitchTilnoS,feCompositeoperato(ininSourceGraphicinw^""IBq@u2$ )#a +@D aD@BBH@u^1!pePNt( +계R<`r2'duCKJNT̀es1B$)CWV(_b /3VYwVeSB+Bf&Vn%~<eFWoRJv+2XCD4>JǑ" H׍`Bh#K? ڈ_Z +>c֧v f 9!7uιTg‰5.a6aۑSB=E0Q9l{Pt! +9MF"LUKܧ}&cg:HS)fR^DEQput4nw.{!,+X+NJ]d?tZZ`uߒčV,d#֡1Q.hZW"Tk!1K\_r$G]JAbFŲy-2nX2dp7GeB* >wqPW +~DaG#Tr< +:l%e;=#Q>@Qq}ʑZnMTA\*Q>ɶZB"5B},-KDܫ5.Hc]=܊QW  dx3R*q(O豋9`NG *>H]ίC3b \-E*H`p ÇiEO[d+/B`a@d<æaJ MT!+6ѡ9j4P +%M810qDN~+H{kRmëM㺖 5w#_$Jҝ#cuV]˂ISlk|9 U=EY2sq$eI)+-r)\d$ωO?2§]VE:Oz&b&k$o!adGA^>> +].Ͼ?"K XgPnD76xbfmf"7 U)8a DDLtwL}$[7v c0E0[ . +}7HjAu䥊NqjCxޠߙD|+LZQqW'^4V&2bqNpcw=rj%/º}K 13ŁK2ӌl환!H jhu +MH N*0ZH2b#+r0vpQس$%AO1c +g0!8d01h,>"΢a,u2Jhu:%w %l}1~4ΐ.}[},8&ZᾚegAp&z[pbcN8td9*#T?]vwLس fJH6<ױCfB0^*{b5, +iyԅ/v%x)F 8 +sD*<4:2W&?aXgu0P(1LH@`4}mM7)#iANRx~^zA-G2Ta1 ) >oclVtZett/uFҥ/6Puj 걌묮WCE(ULLkHu-;zݲ+?!G2A)_sjÇ06hdS˧mC5 +-4",2@1Ϩp{Ոx[ƙpF`HL(oF_= RYL+ @x]{EfySN iDS [VQ:FE;)}3e~Aʘs5}vjYSWO2v8T+p#M@E*$Ɠ2˲-I65a +^0fVȆ`t!R/-?GxʤiPtO0G8HNCf/L^ Uv.~9 sF[cYFճ^ `8c'#'F6dqGsR6]?Фw'Ck\\cR|9d444k0bvXnFq&Y:f6P%xY%i{V3Ø,.}zQ`%\HN;Ąa`vW*^#* ^aCE8c Xij:hMYlPJyU0PcAx"qX),RM +$0%5K{g8QW׸JkOõWOS2 +h=$Q@}Eb`|5.xv쪔Q )6EcvT6r{2ُw"zD],Fu +`Lȴ#n#3W89E`ue#&>gc 8SY,GU ~/qRu H@?Sf\LuS436WZkHdz^SbcQud3t3YY%nC'@ХH@}0l<߁=} ȼ#\OEz (1 +Ri%y$Y2U}Y:HHбk/9O]6Em:LLXpz4,`:㰍ƢT. Վ^}*?uh&u9P29A}*+~zPu&C&:Z\vLRy0+ 9&yPנL(ԡX@BX4fIԎߺ϶:I51$p3ǃ:$yxY aS־3duM5MSjѩ}Z4)gfM&IeMMumSMpYMb5!ƙeM0̓ԶiDǶ?kkw"{B%b韻?B뼧L!1A54(,0 ˄E  L +&nϱϳ7Ϯ[S^@`p$(* +X8PQQ]($J) " "``L<4,(ouù:0]>CrcCtCbitl0 0h&D  +  +H@yEXjaL`< +( 80hXT3r/jE6\)Dld,C(T_i, +!Of/g6/7QctzhO9s1n[tDF(rW|OJS1I#^>iaBF +uh hpzXl B!<iuiPPW̜J,xxx{ +/,{~Ԫ"jo+2/uadgmVC wԧ~:,nOyoaБfO9ŦnT蘥TimZ퍃II[;< }\j]=hz9Pnju~8 i"c8b>mjm<@@&zGy:IQ'bZ %SBM T-4sLrPm̓fGza/է1ѱas!*QM*TbTcL {Le;gKe?]sy=B)S&2W[-2sRӤS$X}<+;:O[ƼTBa., ěuݔR'5hk\"kD 5aצ|Pȼ#HQhHiJ-|E̚ +poy6"ƕk.R .=L&6RHЂo?ب96? SWB3F +B(,<5NnM-<ƀ߱6޾ӊ6?6_-²ܛukǪ@/vϻnV`:R9*87(~p(tp'e3[KBUO!@wGGKl;iЉsuz.uږ(ARݰi.Nިp,> h \ ` |U.y5@':|oF(yp |A#J.,Rs={;Lwp"|=JMpL)Mfti-*fR8ȣHYC6utb?gKѹ.TsYA咠3 \vhq2 $ tf9!y!hLzfY#10:FY!xމy _;q +I.dl)׼&3=W#)fcϓpLShymkZ 5ڢ)|g;2Ch‡eMڅn%'i0Q@R4mPK2RBCWݹy`i=Tt^"o/:fs!#5zwoh׹8b㨭N>cqnխB,7bBLcSMJGLǬ9-h +(.Q.;tzńɌq\-\F(5͸j GN[E#h!w|S;J%^is ]D.@Id4J@`1b}mD ^62^>励3PkDUwl'L hepPtA%+ڿkbp#ٝky1\W~ G!J]5_W%Xߑ\fAF-V|a+-h潋sx^2"gHTj *f4p&3|t[˜ɋY.qWDdLaw쥆?:@P{%5 r[@ +,qe!,EבA~3OMEU\eä7 t3¨zNt5e:]wGo˔jܭ_6P/`'ٕ<%)"&9&_դ ^~( +0 wx0KwX'F&R*Q,SvI9jcXJzૠyYyCJ@\LY&KU'1Oj>X; '((쯅<#LsL#ۏdc P5ߖNa:OY^b6DF wD5洚 r8pXA1#°/ f5}Ri5gK0O?2[B\" ?0uu x2B]T/;ח7ݭԱ,/Š!{\WHv,wJ@$ +e,xEs.:Gol4V x;2[hZiA $]A?lPJ` О>`4D<0⨲Œ~GrZ"5MJVa*3^r6~L'u V䌘 S2+F_ >iaG޹Օ\2*buL=@KQ9]@ǃ(>Ϣ9'n`J&ɋK )CH**)O2jxs ԁTiu-}HUsDY $Aܒۻrǡ_~ ξsgVBB [w(eaZ);^ t no=|f z:'_JnoQCbLTMNy/Q=N1ZA$g@N=/hũœB^,ҴRw$"=wc$N(4#m>=l ƗT +€(Ԇ sPeuki1z\}wZ˪a Wo̪zBC( aqN,^+98 M!6F +^FO4PxW; Qd8 + +Ԏ)>Bg ={P@0s[ vu6=o"6iJO(qijex^2z)u5E׫"@_c%N̔H +A$oTSiDn0rо ^RY1F\~'}'N9G0SC,F57BTyJ թpT ssB:U(udp?سh0| ǵ.y/5aSa<5(sfO/-]SgYi&F$Ey; IQtW_8^SOH SME*%9hp@*<ʏ{bR 4@&ڌ@vI!}@ Qh毭N&m7`%&֖YGbNݩן׫$F$qGf]FY*zRxe-T7>?.<<́⯮%>&EP +x5JτZQjm5oMuK#8ZShODdŒivn#d#F\ +Roش[HA- R4!1BHVq>-~XJeDI z#=08H@@:[M{ >C: R0uBbgcӄ"&yܼu" +"9r4{> T[f3+C m +VnRR@3XW#rDqPd+)[V>X-O{L)̔d] %Tksji'*Șºii3iD{/CrKPerϗV M5 -KÉ`Jwk^,?&,|$'R=3@Ȑ= d yA4 -9sܗӂd5X@YyCOސ2Igq2AJp'mb8k@TRx<&G𝯞ISZ'YuA/D}{ ?{=ccGW#GKZP^i*K|5o<S%F, cH^MlV6ZNރ)4s"2k2QT |XXXq@:cӋ-("ڤ/(u h)*}UާڣZףrO}!uŗ樮vezwФa':w>\_!R%̍,%e9}C-%o韇ؼ3ky#bGP8cspC(s]qm2pͭ;)g_8B"IfcW(ZVNF`3 K(!6'y(x* NX_Xw$(Hhg7pbL_D܊=㌵ʗJ*kw"U:O,]A,`ZϞt!.]hR+}QVZh&i3H'_",@E)vF,)(UAb*)oIPn7^džP̰{4$ _Gjm`QMozx {ˌ[D!aA4ǡ7C.S + n{ lYS8(rj:*W5JPUHTꙁݤedyb-x͠ +97XֹF7{{LVTب0" De\22PEDB&|rW&VfIk m& c@1Y hEq$$a ò>H@ iE˅" `]}A vF"h" ¸H桀X9a JF3UX#`y endstream endobj 17 0 obj <>stream +("@LE€ h L@?5嵵xA gg6p4ɞPt`$q6Ul Ca޾ʹ>>תO_msesVfEFʀ'ZiFlY + ?V6%mul0pꛫԹs:1yb>^vꜾyb;f/egU˛~Y|mT|MW*3sW۪x~WQOs_7UY;ӳ񙤥aY=aˆ$*ur9oWfכ8Jki&7ն:]>*&T,O$q4 F#Ą kVP\$ a<+BN[&Y[ys[)^Sxuq.ʀl4+- bI"=Г,H ffeh6,X by2R!DXL( r;Y5?F&v +[Ԯ"52.ݪlTGe|TQ2tTTqsN6s2f*ӈZ|;9{L;NV̻s#Cž +12co5"1Zs5ߟST*Glnw.oVE$>F*l>|243#$i8iBh`i +0 +e!B !B!""2B56߃iZHG qZ@]ocRp$(;㗽fm:7ƱGv$@3Z +X>s"zʛXޏé!`R + ?:l0 Us'x}2@jw>s1.ydk ԗ`aBɉM#,0|)%c$e^E:/w܊tlOp]Iq`{6uEʸ׆#Wk ɔYH`0~#s@A&zb~^??4 2Lp*SO8<^XdeLQgZ.H-xo6D$` d=K[Y%cHT@ޥU]RIHt)`c<$c6M8A?3%g-'-o0̓D\8oH$$^ƍ/K{B^Zx,k^V&n%yz9J\s(95pޓ4<Cq@cjK/"-+x`| !@6AD%q$45$M=Fb +T|8{FF0GzWBiK|xpn«8]~[bqZK9]r+1(<\Xدcu*\"b #6>6uby`dh b ADQ$ngЎ>00 +m(l 5y0lB36W 2VK۹x M<*BRy+w*[9Ii/Q ik!FLP'>!ɶu5ЧFA30 D9Ú%(OR bDiZ>^ pijh}$Q6M-UDEFφ=h~ C-Հ1U)! +֧ +hm\yuD\t$CH_92^.]OX~{ i:($1N;KH?JC~"3%OˮMz Q`7k,8Yͫ9C:26 }m"2ۺBͫQj-IƪT+!ΞUhÒ',1u?4@r&48Kd 5KjFLcE™9VM4<( V2 e\Y#2SK@T a+KsLAG$vnBmW=DLGFVR|7*LSߝ* +#[X:wǞڻ?Ԯ;H/G6SV*21V蜺=[=9#ÎLL=9ST|զEuG&.籧;T:gߪ#S)=gWvQS9ΝU_=UgbRd F.X(9ΙR种JO[E&Ư{ׯ}Jέ?VP}sW}=_OiRU5#cFxySJߡ#ީB{V|,%C$3#I:Q1HAHH0@`c b%IRh;MkxEYgZjE1' +,.kfe naˣh!B +^",$;6=G4e3!ͰkHW&Ŧ!⸉E."P<ߤM@n[i+̒hN1<ڼ;,D " <%>&UtFߪ^s<*Xpp 伫t 2-AyGY50@m#?C&ar6Eo@SR:GzX"o,qN.H 5 o؇hq>L< +V/.mDdld  vSAHc}=OmF(Ĺ&Aװmĸa޵zIcڶ3֛Uw +Gm 򸯼4MusoM4MI +dhm2D E\2I"R@I:ӯ˯,#HrT{vS(D`cTvp3ɑ6@G*2".8nqLkU?yUh%D`cn#HID"f4xBf'@F P(9oQˑIs#*nz6$TM0]12{WGea|A:Z nM޻"A{ *VԮzK{!dt&SS#z  ؚʀ:@jCQ@@s}(5jR#JJY-Ƴ&oCp?/9%bEN"GXr4^ ˑ,'489$?^Pr7 xlx7l˳]]x {m[ xӲK +u;۶mw/^%?:@x6ug(z>zrzgV]:FUkoNY s<órwjڋv5t Й-Sk?ò-]\_#vg:ꋖaLiցív۵87ߠ +a +<9|5}k!./,,K?s9 \rN_Zn@.nýhzaGsV¦hk;"qW]Ni?ܝ]:^<45ʅ>4B2=j+kFu}]3~2_&nn*K]_cjsX+gUߞ o{VG^&9]SP$v=V˳O\q8w۵8vmv[/,7ry/Gr*i'r}qy5-j;׸qxa9yvgk%m<8e}5>և_Fַa}t-2^{@h ַ!?@ n7oǮk}E4nB?$w[صA-dM~%o +b4o=Wv}G<)w}Ms<P2TiH1][XG\嘖n7\ :k0 z psWg{ և?וo7nV`}+c}W`} +Nd}!Ta帬lWuLIR%4Uq4K$Sr WG$ɔ,-M?亪㹒&'ǑLU-Us\ɒDh*:$K{&h94@6Z\WEWrODOգQ?9) +gzR W4=Ǖ\џ]O3Eu\54WtM$QI\Q_I:g9hfzJHҏxЫ>-rLSW,4UOs4]4m\6*\YyTtTuꙒ*hl +CI4KRM=I乖dy(ާ%-_n ܸvd> Y,'l#նXjI顾TF:H@fUI5St`Zi\_АRPKkTG{8A;+8:Hrvii= APHD +oKA]&~x炷t|;d* ^be;կM|_PZ28$*Ié4÷=DِuP` ,p bhRCi۹1qvS D + ),(BzA(|" fFMV GCC 3-!,dğ $JP2$j$[ @4eAVV`e5zE{6XFn3e+r,hy 郄i342U˷ca| r/E#0Q[/䶠dXf<+ĔK $" J٨Xf +"t;=XD{ː1oz}v=٫#d:;@ϵh1<ܝXNO5gAC 0u:"ݷ]TݩloL##/ Wg!go!Vl[ԉH?337qXRn.YQh1\KъEW pݷ6c-܍Z#v#Xx۴K,$"L땰SwI1?ܭa]]]` ="qZ=5V= S jj>nn.._Ё>WZ"=s)tC{0,钃HCAziiߖH-&e5}!Ўa Ύje^ސ_b7$ް&D'Hv̡סGWп4ү ) +ՠ{e!(^ U>~XNz:{1#w$=f>E'FM+)S *"0qlENZk! B,>0Zexotb9 o#\Y-Xgʰ5' ~YlF#j+#bNIHo!;ā&(QE3SATXrgPs0(W=ciAmV6 X!k[h!sHm`I;x t21J)B/,뽯2oocN^ނz61mOȓ)!B_2E! FR+P )/=rU2BpIP +;Q: +yh'XOBlCSlM t^N`6zB*W2;Q\F{w 4GkLD6FJ2e)1MnsCe1 '+BjBMˊ} T(H-LB#6UFyqValɜ2Qf68Ce{)ƴnF +̈́@[N\ `j^A!b_0%G(M+&1 ?fa($7 %#Me gV3e `=gkdCA[*MD[}YJkJ+8j[ bhuLr:@,5 PKzit[.Qd϶$@=Hrn>#'mufcu8gqUGnviYm1kq}lVEKgWok3qT@"h?iLGnDG_EIyQDmSh9az)X]"eB?-kbl\n(Bx +]}|Df٥Y &{WFTvORMe?t3wY~Yk3L_,Г5Brf4edɷ/_qEr= _Դq쪹׸٥%]pgvVpgΕ9ιC-5GG\H.ܟeG܋8G<8}X>aۍi7"ϡ +F^Dk+/,C5]oXg/lNF_0g-j$B]{] x%=n*?,ݹtTڷa:X-Vc{O KSa^HЄ@1r5c&X3g˜];RVB`5./;6So#h8R>20LXPu)UB\2WMq4K-"oO ?=iıKQ#sTCIZϥ<["An\8GZja0+ d4 +YC*\YykYT4H*8 շ BUW97:_$!xW1j*3Qdv顁)N.'DLH%1h7vb* *0qRbKfZC)6HlC$[:T;HaSPxDHlCgzAb%4|1qf@i|G, (|WX;K?*gyj0CMhDCuHtMؑ3;Ke9W +rmHy$DZٱ!ѰH0́%4֒tM. ` iujdpTq| B 0we]"0jt ő!.08&0H,-U6|1\*+5qVEy祌dCYa₂Jiv'lO&Nd@6-[J*Iv0I%qM@ !U^W硈CC #·zvδgmc2*JOX喬L-ŐBB!k ><PCI$[ߖnN,8T3<1v## 04 шF1 )e\`!"<|AF%-pDT"梞3ŷI"YaþIQytE( +$j*o ڤ/Bgb6KiEϢ3Mθol1RVv Zǂ{fm7ke,Iʯ)+ipP*N%6vx<ewG$o ^wFH# +ȇB2sY$Y 7>mh&x|4q290&<&y:hY\A|ig9,PbuF3zIYxdA-SP*8jLD!(CL +06<2gQ҂&*(ăLԒPZލA! B}݊θo+'|rnQoh&-yf VΧ#NrUMQ['m[IHf oHV,uI^~EBѬL yia,IֺKi|{2לL=08b휱,!f'~*# Jm"QM,TCO75}R:lU[v&UA~:rqDG'Ka$խa+t9=!?0$G߶15*6"Oo[] UࡌVg|UmӁ$aiѕkf{Zη@qu]} Rx/׸EzTM/`8߀+_vdP[͂N2t$SSvKMm"۲e +8b7M-W"\sv}v2 w@| +*#'g,4M0(85)-Z Tlǃ+6Km^i-nEs(֜o$v9N۩tC+gWk7'1h7m<qI7:ܿr+Lu*" ++kuMT 0 +Rz>^CƆ9di`na]@[8U*VQeIY/7*CѦnE9_ +)uaA=W3HKKE-îc18I[{ЏRtx-eb˹L.^LG%X&H)<(O؁YpQbpԶ*#3t#v.f]o'uMC=6n5f49Elt}o Y8CN})ZKFIHhaquIguBB[BMf BQE||E\[%9y؀G1a/BM끹[3;D@oXa2!H%^*>b /1~IP}_@ƌ!~B;KAxd*hWd5ޱ4J[s\|, ;zO:iɦ-XHkI&B%n=ΑȔt`)xPw#4\hwiC5\\lewlQD/?? +N3 + vxp(`݂Ť~$:DGXZj:[xm8wU-ѿ|Ed78i'f#3fv{j?0&!K(i~4=wu0EJ)f=DT`Vcb7(mDR ulGe%nyW,צ Q:խN;6]WS(v6d?hԫԲ[+NommZ\YX t6@2h|hG^ɤXx9Uk8(/ѥB$FGm_4ydӕNSQYB'fUC*7s( u_^Ou, :_+i4JDl0l.3SrCDx܎)s!$ W0}`xOYqsQ4O%noz=ln +[H?Pn#4;u'9 %cA'9 a3V|;mw8D-jܳObbm{쇥R|Re,)4*??YE<͔IJaTz.%Y,LQLBۇIAK54֬BP$_Qmw=(A賰rT~/֧A={vǰ4~՘ {UM駶0.&]YJߺ-9 [ძ MloZ9 ]U4s6͑)ǩXpϘ1{zM +FNUuBQZ[ĴߒP3b`NbeB9n`]2*;xaQؽ[w"2w;zOӠ(Y#E +dbɺԔ`[i/3VmoJ5soEІٶBiJ +{)E>Q9q.'Ah"W#I7DOaRÚD5C\$3GݦNs3m<ڔqB\!"u&SJ9fbЕS YOblvQ)/_W6A.djS92'!r9|W4: [OhQV1tIV ź ֊կZ\#w)u"rzm S楓X0lLET[4N%VK-2d[2-u~B +ڛ@4QCt^8sԊ> ud7K0=\63]F5o{ ++CBW/%EHKEFQX3 " +l+yV[?xv[7m:vՃג\よR)ee)Doj^0WkZ,n@@%IgV-<;$85?5Mq_#`94}⮬j^|l0릆uMJ-dӳ)$MtqRұ 'D>:v`4KAϔӬ^tئnF=42WTh[PaEcc! ,L/JH6g78i8%02M}܁I9It})93>gcPcEhj}ŀFT1״8gIY 4e`_7\hB209W?6=lkԤl샔 ynW]U̱}ы>̶JjO0Q joY&F z;{rDm@ſI DyPH\)8u <&oT{\C-+w1R;"0>?jbj?DDc ]̯'S+T]eRidzw5I w]Z0PHF+uյuJre,(]  +n }>XüE$ӑs\Q}`{w7(=֙(rySY?5rTLsGnh@NjE~zrAݰ}~i|P#8<*r$DZn0ؖJUZǟZ0 $99!2vXM00Hbv;;* 'ɜ:2+㴱PQ7,s-{a[̬ `S(+X pP (ߒ%TZLej1J-*oj7-3}̪83g\.É;SgrenK]u˜R*Ud6WY]E(50Ҍ-0_\\楛i.V_a  SOvv +SC'O4}]3i +*^_)C/&fo@xr o@!3lV烲tx7,<[ιQjbb]h&_S'_S'7c^jY]yq05'BB xSz{ sbsL:L>xhArd/R<KJ#"c-IR-pq^Iҕjl:_3b-~]fn s"a%ϸ[_쭰ݱaoUa* dݿB>}Y]b ̉Kƌ{ˉ=K̨o|bx 薵yA NJ}Gv5E!jn-E:O0?gD_Qo]Ƕ$1So@ȟognB @wՙ]K}/ğg^Һd,J)@%̫*"Ja ;45Ip1LnY5I2LP._`YO>yHu$1Ra3R +06U3LRg($e8'I;e5%i$c/f>Xa3IȌ-teuY, .Ja3M݉= V.uEg.WaA +-XPq\Rtw+}7k) +[ï!j74T }0rWfVÙg,*26jY05r4T $T&<+-RgoѬlPy&8Ljo-@gr0'*IHw{B%f:DwկԹeVn!GFJ!̉=ǘjf8cь=*9[{>Ӡqx8s6.[-gOAAdZc:_DGaPaDT9$DwqU>tlPUQCn!&.Jnw%Zз"U58PGf6Å\j1Y %lߠt˩/jhc(JV yF1tBlx E  tRIc l¤FK?G \mEpKQ>Y yx0Z@adgwef]XPbYcqF \Ujurj kԀԧy¨.iEABg #jj )a#oܚ6{y@)Õ3hٲpDs\y O^^ +@t/=^Azhs :e+𛹀dIɨaj!gT K? Ɯ{a4jS&5fѠj8XĠj aǠjAհ|%1# +acdXOA#$MbTtn!BaEZDXe5qcUHaAa)A'ƑwWT $T J{@ДGwvIy"2{T ;%yHYmYݍ֪S5y6KǷ]@4' e.qdN>rR!j0I&gٽ'f_X>fö?_~g*{#Ւ;,a=M!Pv5"_]pGy"߮_TJ׌# v {0IX&و[4np۞dn0$wʝLDxyzBRP*5-qZF} +gnhyԪ1b6SɻG jQM\97P&d-; +D l88P@9 &)n%h'/VZ$N*/Py6RcyT u͙1ui뙔<`1 L9}9ӏZT&I| IG 4:!y4M/ά8&ʸ2Jr---~}T +&2(~łl3}@Ly]1cxFq@&Qve>.oa1 27U$s%@FMhB>.BuMA'< cwq|Ɯ bL.\\P% [DF +M@2w8Bt!L&P x[5D"̉8h:qZԁ}[P +E!8GK9'(V( +整\0H +-JE?m?U]5CZws j(r=Vܢ</Z"y满fJה`kz$gZ̛ƙ$+q__[nOI r[%& +E!裬/1ٴT ϳ9nUÑ{Dj hY٘?y&4}r9I`eE~R􅠆%UC[D/2+zbuѿTz yI{fO ҨK~H)h['u]V-G- +@t)%[5vlGm-qDmѕTD4Xڥ+G| > 3?,Eѥx&R@yͭ5t u>C&s+т/rT2hP5`ml vÒ50`Z^M~ۍ +(DJrT"lE~-Gt}BЯOo?ux3l|NJ.&V/&V,:ohh<}E"oO@-_y֜_h/gtQFbքՈY+5.zu5&r$#Ύya@kŠ-bjMF>'C\j$W7VK=Ͼϳg.6ڛ0j<;$a'J:JHhj> d\_pU,$<$fa>)^gO +x1qVxSXy3vb+AEg}@)RJr50ii̡ۙ6 ÞxqXlT͏-xqS3xXTgϓq΍tPS"IcLSP ќ"O(&/LFeHsuOKE5phӠhtuBQP](D.ԤHѱęs"Ւ'tGg7!lR&c@Ȇߒ$z.js@3DFbv!,߉>CIJ'T+/ cQ@z/C [J86I>f_Zk YL4I/\,m%+ +p Y!5 2;|t k.GIR"v+1yv1BT:*HgDhKFʶ^P8C9grhHh1G ,MФ'OEہRɖ[$吉djzerbZzI \uw>Îg2iGXT^H̙guJ7]{5 k4}~#I $T {6o ? +_?UC2A"žϳ^t +(C976z(ƀ=u~|,|F3jB&}#.eEnojwb-Kz#Q҃D`XGگHB\j?D}#^8lS4U.@v#R}AjI@0RC@u/G"pb*(wӉ%G@SF>^/حCBR_7'# +R*`, "#tҞ̩r`@64fB"2%@Hkȯ +;ղBϪs(Yw#-2*\b2 A'<,Rܻb;oC]T}CJ6! +}09JIk1ݝdzIZI59Tf +D3y?@k#y;@fWՐI4n:3~u8Րn=S7ؚލH^m#ϳ~M0|ϙ*YfTmA/]eLIB@PxL ATwg&z!䝽s 1RaY}P5$N7x1UN0O ?[CG}6j ,P-:J=]mz%3/5u:W(,'d5Ua}#Rj\}~/%y(c"A[л(b^7)3eBEjY䙠  ʥ%:0 BBB2,$0(8 EqFb IAF!,8"ߡwf˒G/xa]8{(޹Y}M5VH<7H@Ϛ2]/lZyHbEh5U3֭E#eݯ +2lΩ8F~ЃCzp4J&Eh*}q'reFD1h{}FW`WȞ_iS.\$^w@JT>@KL|XǴ1 |.g5ъ=#b +=d lY v`e,lLʗԟXSgr>Nb[TӾ(K{!~h-kBXub@ +EbU/i޻H}sW H;Ekw:|F^$HkKDac)C3abH)<ZKsr]vD/3IϧY8lM̯Rp}lKN$τz-~n/IJ.MDπġL$]}[!qXJE!m#W2 /^)E\b2*~xAnp^Sp7jN +5x=,j'"ʙXru=즈9WKxۚ"Ej t+VI+YQ5N:AUmA1)wRi[PNSl;.mM%5P.8k.fn!{4q?/C~JpcZm_>5!QF*3?}CFtJwÙX&RjD1uCBY~{bQY/[?zΊ>>s(R +>H*B=ܢ-GB'Rh4(\CxYJY%#(NC u %L_"V /%Wܾfbgk2Dh ٘jnwz%wG|7%=R`61ڿ^D( +H"pa(eHˌK?Qj'~(xsW dF&xZ"8o ˑ=$f=Y6BHW+ ,%M, u _i]b>ǙM`'hx$B?BNt+5IǑ2g&5+v"q/d?O1P;O62J٥ڥJ. +xtdgب'Q0Éֱ$(mEa.݁ǿNk ͜aw X-Icߏ + ~Z0I _S D~A1~%:mO-q~{uJ |S$VՌ<|= \$L]oUw7bUH 4tY|lݓTu2_po NYK/HT:Hך9^ e[ g`?Ev>G5'9"]2SBeD67aTx}_<_Z;4q0W!<.4?Ls;JÚ-#Wmo^P̤ѡZjpjJ@ߋO=^!=@~vXDd$;8OZ kMd^!Um0@tɚFKa \HRi +/PZNm#N5 ;3N2S7 +lp +$tH)e;84SZHN3\t~'S©: +:8-^8T1Rj8s .4NpgN?׆ )~{E8zkJn$|V_)S5Q} =s!u^{V7OC +!ϐ.ݵW|41r6 6/lB%0v=!;&3Oiq g-}7v*eOxWCfb_M-Ÿ~Kƴ_>k:TawXZ{HoT[4#qtJlȥ|En{K+ <(}S Q\5)V>]O|c:z[3>^ +vD.b5)B F*jũFKm)8s}9fCt'Z /N~i1~wX94u߀}Ֆ$ܪ$*PK;`.UhX$0T0.RPUDhHE1q!|Y3hBijYs et"%kOp`w3`,Ebu"HvP~1LA?*Wu WY4E/L8qޠ`'| ;2E&(Ӡ`\DLu֟]=?WHeѾ y$z:k,{?.?i!Ad?MId S/}Aԋd=Wm< 3lRr`O1؄йGp"n;go=Ɓ@Υk㴰n1uj2/H!:'{6vK~2G<:3_N=q j!ejoDI$g.?ڞϲsRxU H8-t Ri2bm->a`"KKz tt @τָjgPD:a8t :r0a"9WJ+'T?<i|L io\y.lJsyYLqTcwTpOqRiq JjQdž}(䝖o`x3N=j+9is^srME_tJT&'H_E,\Tyu86~vQл{ޤki`80ŋEFQ.&l+X?ӥT׶o{]uN&Mqp7%WuG Yw/W #k-ݻ 2Wwp7o/HlݧwS~Dy*xfn]r)2i\_U°N+RI# S#ФK-5piG} +76+=&D0nkB䀌1O_o]oJL$L2ƎoZk?cMw\M{ no6 %2- {P䜛u怞c,1vIalz食vM$W)"Zɂء~XU 7(򀅸Q\LX4whWf*N`q#8aޛ\\]yʂϜ(#YD?3R%ϴ)!&B;U<)=b`#)!u.M5P@k4.d_gu8 HS +A N^7tx 0H dMJQjiRE l5su 0d0i56eplswpHo:قLBŎ>k׭zk>cy(A7L]r]RCZsS^ьQd )aŐI0(ڹX5Ƞ$ WQ*nP{B"X^#&벊y_h`{Ǭi(QPE ud*TQ2lY w;Sq.Oབ1jDTB#a9PRR +`v0QZIhP{E tPS4EvA?hEʹ5,"ju00&&,xDHzY V]qص@* + WrECx !MAWDȍH8#r9jvr<>>!QxF4ag-XK ȸۡ&;ݟ5/"g|Oo{UbB#n8IKDݻT0Sعev5d#<V~ (_B`?!+BZzH=iL~lҝWC]6\ aS ۇLAzFP)dk$7;CX){mҋm,lSRkA+m76"C,| k]nI1zoX mZR")=S(,?c^= ]r@t{UuRdސ]gXwzd: 9|c~ˉcʧRT;C? Z@l7f\ok(*w0 i9] +yTdU)128A~w$̱1\Z-$( hwRߠgێfn/D}^] >G"7f|v E:En%6. +YL۲9lΛYyAv:ns{'=,׶XH+׶T;B fM@RzJfDLj ܏@Fm|T_/wڶ;.ǔm;l2(3" 1[ +ΚPq{ ZrccB ʻcG#cDzgbv~QxiqR/@ nwELDHŻHZ>vgl,Q:m*abbTy4@+;l;}Iڪ+…E%-UPlka9NާH3Tip7asZ]BnQRqFUMBVٻvovwq+Wf~w(%79 ߆tzѯ66 bʁ `bS?y7ё|%;IaLt"P@j?vJYg^*n<붃ayo9$qmgh۞r]XȰlu ~QF>TaeefL5-tc7LzSabTDbk02Tf^%H熸r$Pd t_-d#d"S‹-Yjmx۠-+'4ǘ\s)j;=V2[[3oxL kg|v?8\y[Yn r Ę4Y߇3h Obȡ8fk#dC::PyɐyΜjƇo;]z.H0יR$]=(us$[v9tuh}=729s3KҷNE̥)[qLmW -ޠorߘrTXw /ә=R ÷%Rku!=|c{wL8(I7S.,e߈9؟Vg9}F_+L1wReI0D?Lë{sLl2덍E_}/N&VS&Eo&RI5l tt,c#DN8("}m01ۥĔ0v,ҟF/%l&oPšɫ̖=_?8ЮU;(0~tU<{@zۂ"?֒dAAgN2v@6 "`;&4_J#I w[2k#&8& GbZj Z}4thBC!-SED> +xu +3F7.^' ⸯu)i͵{ O$|>srÌ %4(tz  Ml'L1Qv%L7 -Su|V<C. Îpݸ+i{J_Xֹz'ŔXjK0iv~%9C[POVZ>W^]~47v4Sq:tL={lO(_;?c;"6lch79=`WOyY\+M$*r}XilW],-Ms,4|g],Pk~~*lw5wЎ$gqpG| v E_5uo f<85 +` K=cnf94yU_"X-[{!Vu9P b-cuTku5 `wou0VdAl + Qעzx,s@]41vYR'&8b,N vP5';k#X p"yC%8@w9%/F.%蘊ьIcbLGmi!Ysd@z@.bi䷥+r٣lyD>(H#]D;s#7%Cx7!^\wAAcWk3Tr3/ +F"85j)Fsif:ׁLJFwV^-Gs[4䇃Q3ȯ41)HxXg&Ciߖ[B2i_Eu?R",~^ +fgG̙tGod' +cK%4R&Ͼ,Y|`k!E!RWŰ(`Ӎtxw‧k*LHx/uŊu>:u8E= zc D$yvP1 aMBMv4oMq{H + +s5 Ks\#L^Lڽpi\Z)tv8큠7`egjkb8sEp6](O~MT\P8.NXl@+%]S,᡹P' +~T[Ec +E!>G~[`0 _<3!`?Lw٘;$9[@Cڞƛ)+F )*7CCaxb[;)OL<`)}`A٘o?bv\(,v7hZ#||yށ &K^B}(dx:Dggh%;X稝[ѺF1(b>i[ΪMg-2PY%J ȼai{( >&$/E˱˂ϕ@wƝP-m\3VzjDj` 4ĉQ* +O"5O]]kbZdS]L3_S.-ڞU6)Z%%at&iw&Kt\6$!m,ӟ""( NW8":+LZzs#*&M\u#* _oVBlGYh C%?rrbeƪp1%@diB3OJV@cLngYpS~>l,{1 +:-scLcV[ (Wό :9c@ )#u| r?I:S3'l3/_nmCsKҷ_"g ۼ޳} ;w_~;8? 0Z ʹpL24ê(NB,|f}54A#*fBn3[!#O&ϦTgb?qu%!as/!N^6ڵmhi03 [~y#.QyipJFꀄMcu/eK:ʂꘅ(aqRGHQ:-Z"E(_b9-b#>68z PF(.,l(DobWN`&X}0%O!O8-N+ ;)9YְσT[q<ţ4oO.27-Zycw^>?V2iLlKLwI9ixg4˘i螮D'O(8*JPzMc 3>')!XOˢ)NNa?Yyy}O:bQhCԢS j +yN"X1ۮ 1cp*kŰ !F<ε0o%bdl2K)0pi^9ٓR e[vL{fgR.ĊK1vϭckYrA#A~$x녊sI`#D) i +8t&n"mYW@ bawl~\hװ%͎} )L}` Z ӅD (.5-?G9VʟI>g,/tI{B^ KCŎRDaan.iT00`s/ :RjS.q]ʰP3zKIaf`r6I*_f2$!Zn%c'@i+m2U6 !Rn>̨ ,]',S&4ksKr1>[ ':y40'7| +ه$vqH0yp:Sll@ֿٔѕcDrDYXLy3ucCa`X31#8sJ] ϸ5&{%dCLٚF!ҺCocV~Jq֜ ` &#{xVc!1>+s燹 B2Q$WzjDo: _G/Q|)KKxm{($*{W6䇱' DscY Cs\;nt ,XLy|~a%I ?J;uԠGJͅNJoBI>'?>u c~ߵ`iVC_yN%VU|ݬƻQ X(Gs 0Ulٝ-֕i%*Ƴ_^)cvY%FJC4hdA fCK^ƌtdix>8%b aLlH)?jk*F ﭰN+֫ y.uqTfu%D"-6Ę,+wQa`4>&o/ +3RY}cgD_w0PZhcY֥ad{䊈g:g +'(6O-^V.R376UOAE]JLa0 +~U@=7 ܘZ ~i?Ͳ+ Eq p GxؠGI9{_w9٭ 5 O:5B77%>C.?fӋ5/(芔y,`U#땭a֗c= U3E͝'0/r{usAXoh·g'Q+Q;ϸiد?XDAE( +[~dq#5#bďpcіtcaGX?x"djQ(u?e-AɫBBx2-0!hUj"%53';=Ie=1 I=CIL ڻ{dӨBHu!nC}2 h5s 4'ܧs%StLE=#n\)XjX l:~0$"ȌL2vx l5ۧ"a<OCD9 hbd%ֲn;dʘ3YT7A,86Y|j{i'W>GF"hi-`Ɯ')Dy~Oe- +2 U`R$9RI z,XxwX plīddZsn<%S\%kb8j&hFwB,* HZ*$W% ]#ncv_|i 1Ft>} YS2:`86ok7cgO,B UU@f!b04Jc 43`8[|g/⨱2Բk"2`b'BHB(D?dj!m(_CL,Z~á|$@7TF'(**PD sV  dل;$QP:PN]8 ("f|2fvťAEWṕ 9SIRO3@ͪ|~\{BI#v>WRcm<8*".D1QLN|ZUoLOT]tRU/x[Rf8I`EjPb|UTp_XD +ΰP ͗+.͞@B̽Vc '=^24n7!bAdV{hN!(TR'/._|ηٟ*kyO@yX6&ιښc,N"\d$| (0 +C%k[/,FqfDؒS$Eu  8TB'Q.>gR1xF0{3$=)|{QI.9 &G)09[6'qӡ0ѫ@bd;8!7JAc(q|9MS,  /Eb*9%EBd-fy 1A!*Mq_5zՉ@dċyYJƣгS)R|Jn#6FUK,ߤ㛀p_!@6bEe¨ї +`[d~AR+-hFҫlQ+r>PCu$W5pxء1}B-.p¢ @I.<$8e$v-gLl@bc(0`B (^i@kSWT6HwDl| S6[ +#u# }'kұsP*$wHfˊ+` Iy>"'Wk|a/S]#hB?m ;uL C$A#z o@UxM\'U@{t 6J0ӡfUo8UAe4@eGcIpyԸFFB;,'P8ɗm.nM1Ь#@Px&A8ҩ`UY,X5l֪,ˠRJK *i˺JHYiļ^!tp%Wk!(~`ؘl#LO acfnWz6IF +_Օ_j{!z\D/`R0iTg1gè )k!BZ>gpg߻(zۏ  ~? nÐöܓ=ۏz?=oЃY̠(u{~c3s@!>}Ep>v<]_n!8p?>n{q?{k->"`\ {bȻ<;WUeiBE{"?kο>_ Ayϻs3 /uc rpsGpg|g ?";cNUL0aۆ + *@AU0JjKhFh=Y$/ר观l!=N큐8m!}-FOwJj:4%HfsRH 8 >>J,.$*" Ea8$`XFQ8(H}9 筤<;.k]y#ɈRFTW4`EGy4^7H3@Z]\~vԢ/h`LPc^hJ٢fplٙC}5[%լ6 +9K"pغs)ÑPv3Y0'-npQJy#7 ]YpMX?i:1S#$oQesQwO]"2mDnk~LȬKR +l @V gȎR %"v+PM.#FSχh'Q)3Ulx.|tRO90tPlI]5d4Uˏ "a'^*<TD#Q +#5eMz]ߺˮ4ft +fGB>iЭ# {ugEz6^xŏ2*09n8ܮ8.$C(#;C}NK@76+GYcd6Mzi=U7Nt8dz- sn*:f=[k0.;?aG~9aЗ`mU,XX)ᥥ-ӻk/߈G(*IK3Dvt;{{Gs玗ZE%BEϒt Q3:Ul&(`-pݝUBd6q% "msCލwe΀3Z88H@Ni!jy\`lYk#K,[ri.zx}~uڊyp ln =/rXgl3M~8jW5 5L&_aRIMxk3-}W>cfcm+yp}AnIUܡ +-ZGTDo-P+o-")3[\A7sBZpWk֔7= GS.ߍ^lTq0| d_+PUԷe3QOHdj(o U-:ͽ8ಪ!偫o|ؿ߂oUq|(U[睝4R;. :YG +9_aS%.w=8+)Z\XU^=?4ξ@T{3 +-1Y <$Ԇȕ&PQ΁lȄn.t t[P( 5-?O? Z:fNR*b$V6:RnevI;f1wcq~Ï;4&VH]_Xz<,Mc:Ugf3rN X$@}{f0r~$Hnnt}a.b{@8^fX.6ҳ]]4ky ɭ w촙+ +ilpdXugE{G:E1{6ąQD !N59cП}` _?B>[I.I[B7ڷbvIB׭$sLg̛&hsdH۝hR)tkO> @PyÃ,5٬b>@\|u,b,DZ#[.'tJ9E"Vꤧ#))$LjtɼS4D8杗"|?ƅ-2v?_@烔l6g0]HFVT}}\;@܃&>\H]oŁxVpUD +k-v~ IlMwˍ[a$& +3sI^N6AT%XwYq&QN %mi6{ 7od{$QfdzG"Y6Ak9uhX|yHu[$5uPE\wvh7s  ?jv[3'CZe,Nv>i_PgC;|ģBs++Z+sȢap\udCтY*~a\Dnz?ˈ'X!tBM7h<6~NܧQgѤ,l |XtwzrI ӌ?BPtwG(eg)u#)&hw&ioD&%%~Qݦ$npLHtz Tt7Gu8aצG9\5vN%a!Qdt7_odAi˜)Ogvq? ϲ~oz.'#]jzuVr _U@ +V/&i)}SӔW=7 +U^u}\LK!p&J,=rWL~][딟d!`$&*=L;L ]NyM:"8ѤQ렎`}$3Udʐ.!B:i:!&o*Ka ++-Ϯm榩d̀3Ë-'/֠g$\ɶ۳6odvV22n[<9<3TS;~<ٜ'4Vlm=԰}Bӿ۝>VsSlAvG0DDhz2?E׹bL Q  "m>הZGW V"f5OyyYG&?=5.4evgW5baI{آnuB Ae44`]c2 JsF\]'_DZ" :fcJ!5(eWh4#/7JX7)saj]I=j?Y5!0ŞоaYxI"}uAeio'ss|T +_2Y˴$@b+J֜KwgpOs\I}s@=\YQ(Y+'`%CFiv`VTh2$Ew &aXߒ#c/Ѡ=P;6uR'UVW6L fz3piL" ޖ: Z@bw}ZiK󷩅_Co*n=[b&CF!vZ[rn F*^a5`gM8PZԒh#|^yDA҂h1V\,gQw  K "ieCM;DHx,ݍADkAt0hD r#u^32T&٢Oٻ5?vi`}iijܿZU+֛?1j"Tavyh!+Xb9hWNi} PHGD|"O34AǼ[UN#_+7vfo|ڝ;V]Z>Ihο9WVWHDH 0LYg\vdMGJֳȽJRMzztqlN)Vh&acL7tek?mUfJ +<06dik7dz' +,{Y;DFRߌL +S l[ Z\\%}T$JwFeg +~FWm8ã.8].53M2~0tElyrZJSfn(VPLtਘH.\ANaRsH}}?=#Լ-J;`JgJy" s0:BR71Ƞu^} o3/;3R[%ݞ׀L,I[z2| zEa2 \w8 W*gd9٨]90W`"b&gQH@`oWK tF¬Y|eK m2n0gGLSo +a0X>{sNܸ y} &ٚRhWl7"s!H<ɗ`դg`aN n'W٪-OQ*nbqw~'p۴pz;$2 9O9P/oPN':Hw17_8B>]Mpcy|H|XP0>v8{]Q_AK3,2t}?m0 gtX{U4s&F棸"+jҵ߾*ފP`2|`)bf&~"(wR#v)a c)oR-r;FεPPXꇐb2~iZb8_n`G4'YFLǢx +s@kV7ڌ~a::1PS{ mIT?Fw8Q +`NDaМ/Xٹv1 Ff_Fuka 9`>+gf~ܚoB⩼bC`jи #`mPsXx$fSVQOyn9w9"nn>wF|pEvŒp{XlqdH8_ޭuK]sU&z~5I2-alcbdeZt8\ -z +Q;5 թ!'9ZxM'dkqWj,)ӿx/ +)`,}|!Ύ>kDK V朓~5`e얅iQY@ҕ,29AdI+q|c4.OqJ\%=c"ZFĮ;s:> OȸOEH}҅Ao +~<#['Xk2g%7OWԍl~1s5Xqڙb" d&AYm +O| ~AH&dFr xh0!^vf]]zg:'މ$1ĉ8 L+Ciʚѿ^wڅEߚW8W]2I?'Y\hNř.9I al +5[ҠnD/&Mxmz6EI-N4 +zk֨ ?2WO-'+m2+ҠeOs:@:|`*ޛ"}|؂2i@3F7ar -2^S19)A'ߘGs=#PQM WR y-=ȲD!Q$z/˘sƤ}uIAD۾ +@iz{F?To`kR5l*UDWeaD>E3 ޳kJ.lVe<}ʗ랿XG>V|<˘W/ tSHg 3 -L6?tǻKq)0wP"73UЕԆm6Eɛz +񋼫">fz&R}aWj1j"S +Ena%xG4pƷӻk63FhmN jަmRk_7!@< i=VwÒ+u>;FFJ]t[GWv.uW9uտJԵcKOp Z F.)ϴc?lOڑ"6\I*fO8($iUF@%͈PaJ*Q;觖gK +&q'Z^YcTj +%jUϳ}gJRO +o0`,"oKc;Vm<Gj_B jc}" +R-"K‹6˩UAdEW9>!Z[pd*R`;S[ ~[3"u~:.e +DUn=W%LJ%@)P!lSȿEG[P@}5@`ǫٿaMU/b +|j[r2Sϝ{fn!{ gR89 #WAF̿C$mv%`ډw"]B掁Ek닳PJCQZ [H+7mE IIj,(b&+@.!6mM [U^1ft5[n S. "Պ1̢ H% kk- TS:h M1 tAs2T* + W -{/w 9!XlPxuN#EФd?ИdZ=^Xa0IH"(QD t|d3x#oXePK!V2G#ق%ɤ 0(A?j= BcL[a l22b~#rIr -A~ +2AgPk!rq3 Xrū +3?dmB,D[l5ڗh8UZ5]#Ew0];iXd2 _'C#43ڝd@Ў 8I ApeѤdh1zo,X3e`E%-$?}W^F.o.stfTNVrO~4W?Vz:rVڴ3K&&[)I%B)2I u5LmZ LuRP Ѯ$PVEW)[t`UԮ cٍcAF:֙D!y~<~FʍunUM-C4;5q%&=BMqLHm +IPd}wRzOg ؀8]y|U4 NofC_M')!0[f:V오eҿ?G1n_08nYNh:SRԕ;9؟Yyt3S74+a3omsahg#h40&p) ?ܵ٘ l eitm?K%B }fB33@6ujm`$x l. N}, h`^<4wocBv n#s!,}%<4T \W̯CsoT􎗘,p5䧇6U~6W0)4& Xp8mAaSb +0:hM3[BLE7=6=inRӜv(D`|Y- 9|iX1]'SJ +o nig6ʶά^_ݸ(_k2ݐҚX_E7W.u3~zĝԋҐ;p +mIoZxjg#2"(KrT Yʹ=DePAJ ,+uHPEPascHĦ[2k@{~JCBEe̜̳=ͭS+NNx"ܧN`.A# 8ceמ}^ձL0U+fQ! O^E&`POQCRh+pvwŮ]d 1ͺĀ-2ׅB?TCAl @^BJ@gN3Z!-(/t}TBBPq endstream endobj 11 0 obj <>/XObject<>>>/Subtype/Form>>stream +q +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr /GS0 gs +0 TL/Fm0 Do +Q +q +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr /GS0 gs +0 TL/Fm1 Do +Q + endstream endobj 18 0 obj <> endobj 19 0 obj <>/XObject<>>>/Subtype/Form>>stream +q +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr /GS0 gs +0 TL/Fm0 Do +Q + endstream endobj 20 0 obj <>/XObject<>>>/Subtype/Form>>stream +q +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr /GS0 gs +0 TL/Fm0 Do +Q + endstream endobj 23 0 obj <> endobj 24 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +/CS0 cs 0 0 0 scn +/GS0 gs +/TT0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 14.4636 0 0 14.18 15.3701 0.4712 Tm +(P)Tj +ET + endstream endobj 25 0 obj <> endobj 26 0 obj <> endobj 27 0 obj <> endobj 28 0 obj <>stream +HKWmPT~sWXWP\vaE> +(oZTd&1jNۨNg:f:NkԴ$v'մM-j0sni#ss޽=nfDa 5u>#DⶦՓ_H4฼ܯ sZܴ߾@1s"$Gu!>r0e./n*Q>溺Z>檦Q{~Ѿv*SzJQuL!*M[Z*l &O3);("(mSh3/@u[Vlxr$D({+_ݡK?_!IX(<m@$KC4Å"o E"a8F QdA +RHaґLda41V.0㐏QRa1JLa:f5LBfQ\C1Q-.eR/2Cjd,Y%U2UtiVbiE$Y2Y*enы ؁UX a`+I*VHb)KtɐL YObd4!ْV|`( ,_1zp512Q.;! 6︁OeL)R)R)eh d<"ux{ +"^ķMc֝bٟqLj>~ ,Lh{l=괺d2"]\#]it \Wo&I9fYnIKv'{G;=Nt'3ܕFws^Mq뎲&3AkqyjPʘ4RRS][`k"5v3siL b`Paj M֊X'o '-==/z^y;_NtwGv{z$e@_Z78_h&  `׿{tx,p0~BefVlec4̒_cU/b;v~YAYx54UkUt 묢&bP3:Z+IVif8pPu\ $2ɚ|)xd({U3O#ܘ"=j`D}rաpW ˍHnx"G7rw^v}g:S{Ձ1 +>P=5{ t +!R2;|I*2qoJqo麞?%Pf,rYpfLxZ6s,PtC ;HR@{tpop&E+nmut>e2xN@^_DUcD COgh]5b|#*t$丈%&P^umE>G"*8e)Ա)ƪ +[U>9Ku,ch$R6p"5xo̤OQ_[QjSi]Pch[fIiE1kڿCc, 5a>UZʧ%HPQv8Wb$ jØvr ډw>i:895,F>͸be4[@;42< s\x: ܺV?7 +B|L>%>WZV`2M#e no+7kaX^ujR/@m#x 7={ѭnHs:Q(N^AG"u{"ɔ!R2AHQKb TѡyTv钡XwdYȇ~6X_jٷհEoz4<ɰ ~7<JD3{ҁ-E],9Ϙ_l]S X,Z)a\ɒ&oSI=]J\k' LE)W539yKr hcojÒ+% @]8*Ȣ2^kW#QeB5@HOӔVeժop><C endstream endobj 10 0 obj <> endobj 9 0 obj [/ICCBased 29 0 R] endobj 29 0 obj <>stream +HKyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 21 0 obj <> endobj 22 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +/CS0 cs 0 0 0 scn +/GS0 gs +/TT0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 14.4636 0 0 14.18 15.3701 17.4912 Tm +(AI)Tj +ET + endstream endobj 30 0 obj <> endobj 5 0 obj <> endobj 31 0 obj [/View/Design] endobj 32 0 obj <>>> endobj 6 0 obj [5 0 R] endobj 33 0 obj <> endobj xref +0 34 +0000000000 65535 f +0000000016 00000 n +0000000144 00000 n +0000405482 00000 n +0000000000 00000 f +0000597590 00000 n +0000597776 00000 n +0000405533 00000 n +0000405910 00000 n +0000594413 00000 n +0000594300 00000 n +0000589842 00000 n +0000406332 00000 n +0000406406 00000 n +0000406602 00000 n +0000408108 00000 n +0000473696 00000 n +0000539284 00000 n +0000590160 00000 n +0000590222 00000 n +0000590479 00000 n +0000597104 00000 n +0000597166 00000 n +0000590734 00000 n +0000590796 00000 n +0000591154 00000 n +0000591216 00000 n +0000591416 00000 n +0000591682 00000 n +0000594447 00000 n +0000597528 00000 n +0000597660 00000 n +0000597691 00000 n +0000597799 00000 n +trailer <]>> startxref 598005 %%EOF \ No newline at end of file diff --git a/docs/snippets/snippet-intro.mdx b/docs/snippets/snippet-intro.mdx new file mode 100644 index 0000000..e20fbb6 --- /dev/null +++ b/docs/snippets/snippet-intro.mdx @@ -0,0 +1,4 @@ +One of the core principles of software development is DRY (Don't Repeat +Yourself). This is a principle that applies to documentation as +well. If you find yourself repeating the same content in multiple places, you +should consider creating a custom snippet to keep your content in sync. diff --git a/docs/why-aip.md b/docs/why-aip.mdx similarity index 100% rename from docs/why-aip.md rename to docs/why-aip.mdx From c78c722e563c83e4018007433c5ab29bdbc96e37 Mon Sep 17 00:00:00 2001 From: James <133906218+yungcero@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:43:50 -0700 Subject: [PATCH 2/4] docs(fix): change back to AIP vs OAIP --- docs/architecture.mdx | 4 +- docs/community/contributing-policy.mdx | 4 +- docs/community/introduction.mdx | 2 +- docs/docs.json | 6 +-- docs/faq.mdx | 54 +++++++++++++------------- docs/index.mdx | 10 ++--- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/architecture.mdx b/docs/architecture.mdx index 7c3813d..29b32e5 100644 --- a/docs/architecture.mdx +++ b/docs/architecture.mdx @@ -2,8 +2,8 @@ title: "Architecture" --- -This document specifies a proposed standard for identity management of artificial intelligence (AI) agents in the internet. OAIP is a language-agnostic protocol for establishing cryptographic identities for AI agents, enabling authentication, authorization, and audit trails across heterogeneous systems. +This document specifies a proposed standard for identity management of artificial intelligence (AI) agents in the internet. AIP is a language-agnostic protocol for establishing cryptographic identities for AI agents, enabling authentication, authorization, and audit trails across heterogeneous systems. The proposed standard creates a framework that allows an AI agent to obtain approval to a resource running a model context protocol (MCP) server by requesting an agent identifier token tied to an end user. -This and following pages goes into the structure of the agent identifiers and the authorization process performed between the client and server through OAIP. \ No newline at end of file +This and following pages goes into the structure of the agent identifiers and the authorization process performed between the client and server through AIP. \ No newline at end of file diff --git a/docs/community/contributing-policy.mdx b/docs/community/contributing-policy.mdx index 2b4dac3..ce3f2a6 100644 --- a/docs/community/contributing-policy.mdx +++ b/docs/community/contributing-policy.mdx @@ -1,6 +1,6 @@ -# Contributing to the Agent Identity Protocol (OAIP) +# Contributing to the Agent Identity Protocol (AIP) -Thank you for your interest in contributing to OAIP. This project aims to establish a zero-trust identity standard for autonomous AI agents. +Thank you for your interest in contributing to AIP. This project aims to establish a zero-trust identity standard for autonomous AI agents. ## Ways to Contribute diff --git a/docs/community/introduction.mdx b/docs/community/introduction.mdx index e459f04..b99186d 100644 --- a/docs/community/introduction.mdx +++ b/docs/community/introduction.mdx @@ -2,4 +2,4 @@ title: "Introduction" --- -Check out our Github for open discussions on standardiziing OAIP \ No newline at end of file +Check out our Github for open discussions on standardiziing AIP \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json index 6dba3b0..e3150ef 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1,7 +1,7 @@ { "$schema": "https://mintlify.com/docs.json", "theme": "aspen", - "name": "Open Agent Identity Protocol", + "name": "Agent Identity Protocol", "colors": { "primary": "#16A34A", "light": "#07C983", @@ -20,13 +20,13 @@ ] }, { - "group": "About OAIP", + "group": "About AIP", "pages": [ "architecture" ] }, { - "group": "Developing with OAIP", + "group": "Developing with AIP", "pages": [ "SDKs" ] diff --git a/docs/faq.mdx b/docs/faq.mdx index 4e834f2..7ee1c06 100644 --- a/docs/faq.mdx +++ b/docs/faq.mdx @@ -24,39 +24,39 @@ Not for local development. Identity tokens are recommended when: 3. Set `failover_mode` (recommend `fail_closed` for security). 4. See the [Server-Side Validation Guide](../implementations/go-proxy/docs/server-guide.md) for details. -### What is OAIP? +### What is AIP? -OAIP (Open Agent Identity Protocol) is an open standard for secure agentic identity management and authorization starting with a specification for policy-based authorization of AI agent tool calls. It defines how to declare, enforce, and audit what actions an AI agent can perform. +AIP (Agent Identity Protocol) is an open standard for secure agentic identity management and authorization starting with a specification for policy-based authorization of AI agent tool calls. It defines how to declare, enforce, and audit what actions an AI agent can perform. -### What's the difference between the OAIP specification and the Go proxy? +### What's the difference between the AIP specification and the Go proxy? -- **OAIP Specification** (`spec/`): The protocol standard that anyone can implement +- **AIP Specification** (`spec/`): The protocol standard that anyone can implement - **Go Proxy** (`implementations/go-proxy/`): One reference implementation of that standard Think of it like HTTP (the spec) vs Apache/Nginx (implementations). -### Can I use OAIP without the Go proxy? +### Can I use AIP without the Go proxy? -Yes! OAIP is a specification. You can: -- Implement OAIP natively in your MCP client (Cursor, Claude Desktop, etc.) +Yes! AIP is a specification. You can: +- Implement AIP natively in your MCP client (Cursor, Claude Desktop, etc.) - Build your own proxy in any language - Use the Go proxy as a reference -### Does OAIP require changes to my MCP server? +### Does AIP require changes to my MCP server? -No. OAIP sits between the MCP client and server as a transparent proxy. Your MCP server doesn't need any modifications. +No. AIP sits between the MCP client and server as a transparent proxy. Your MCP server doesn't need any modifications. ``` -[Agent] → [OAIP Proxy] → [MCP Server] +[Agent] → [AIP Proxy] → [MCP Server] ``` The proxy intercepts `tools/call` requests, applies policy, and forwards allowed requests unchanged. ## Security -### How is OAIP different from workforce AI governance tools like SurePath.ai? +### How is AIP different from workforce AI governance tools like SurePath.ai? -OAIP and workforce AI governance tools solve different problems at different layers: +AIP and workforce AI governance tools solve different problems at different layers: **Workforce AI Governance (e.g., SurePath.ai)**: - Monitors *employee* AI usage across your organization @@ -64,38 +64,38 @@ OAIP and workforce AI governance tools solve different problems at different lay - Answers: "Who in my org is using ChatGPT? What are they asking?" - Typically SaaS platforms for compliance and governance -**OAIP (Open Agent Identity Protocol)**: +**AIP (Agent Identity Protocol)**: - Controls *what actions AI agents can take* on your infrastructure - Tool-call level authorization (blocks dangerous operations) - Answers: "Can this agent delete files? Access production databases?" - Open protocol for developers building agents -**These are complementary**: Use workforce governance to monitor employee AI usage. Use OAIP to secure the agents those employees build. Think of it as different layers—one monitors people, one protects infrastructure. +**These are complementary**: Use workforce governance to monitor employee AI usage. Use AIP to secure the agents those employees build. Think of it as different layers—one monitors people, one protects infrastructure. -### How is OAIP different from OAuth? +### How is AIP different from OAuth? -| Aspect | OAuth | OAIP | +| Aspect | OAuth | AIP | |--------|-------|-----| | Granularity | Scope-level ("repo access") | Action-level ("repos.get with org:X") | | Timing | Grant-time | Runtime (every call) | | Audience | End users | Developers/Security teams | | Format | Token claims | YAML policy files | -OAuth answers "who is this?" OAIP answers "should this specific action be allowed?" +OAuth answers "who is this?" AIP answers "should this specific action be allowed?" -### Can OAIP prevent all prompt injection attacks? +### Can AIP prevent all prompt injection attacks? -OAIP significantly reduces the blast radius of prompt injection by: +AIP significantly reduces the blast radius of prompt injection by: - Limiting which tools an agent can call - Validating arguments with regex patterns - Requiring human approval for sensitive operations - Logging all decisions for forensic analysis -However, OAIP cannot prevent prompt injection itself—it mitigates the *consequences*. +However, AIP cannot prevent prompt injection itself—it mitigates the *consequences*. ### What about network egress? Can a malicious agent exfiltrate data? -Network egress control is planned for OAIP v1beta1 (see [spec Appendix D](../spec/aip-v1alpha1.md#appendix-d-future-extensions)). Currently, tool-level authorization is enforced but the MCP server subprocess can still make network calls. +Network egress control is planned for AIP v1beta1 (see [spec Appendix D](../spec/aip-v1alpha1.md#appendix-d-future-extensions)). Currently, tool-level authorization is enforced but the MCP server subprocess can still make network calls. For maximum security today, run MCP servers in containers with `--network=none`. @@ -116,7 +116,7 @@ Pass the path with `--policy /path/to/policy.yaml`. ### What happens if a tool isn't in `allowed_tools`? -It's blocked with error code `-32001 Forbidden`. OAIP is **default-deny**. +It's blocked with error code `-32001 Forbidden`. AIP is **default-deny**. ### Can I test a policy without blocking anything? @@ -154,7 +154,7 @@ tool_rules: ### My Docker container doesn't stop when I kill the proxy! -When wrapping a Docker container with OAIP, signals (SIGTERM/SIGINT) are sent to the `docker` CLI process, not the container itself. This can leave zombie containers running. +When wrapping a Docker container with AIP, signals (SIGTERM/SIGINT) are sent to the `docker` CLI process, not the container itself. This can leave zombie containers running. **Solution:** Always use `--rm` and `--init` flags: @@ -174,15 +174,15 @@ aip --policy policy.yaml --target "docker run --rm --init -i myimage" For production deployments, consider running the AIP proxy *inside* the container or using a container orchestrator with proper lifecycle management. -### What MCP clients work with OAIP? +### What MCP clients work with AIP? Any MCP client that supports custom server commands: - **Cursor**: Add to `~/.cursor/mcp.json` - **Claude Desktop**: Add to `claude_desktop_config.json` - **Continue (VS Code)**: Add to Continue config -- **Custom clients**: Use OAIP as the server command +- **Custom clients**: Use AIP as the server command -### Does OAIP work on Windows? +### Does AIP work on Windows? The Go proxy builds for Windows. Human-in-the-loop (`action: ask`) uses native Windows dialogs via PowerShell. @@ -218,5 +218,5 @@ Yes! We welcome implementations in other languages. Requirements: 1. Open an issue describing the change 2. Discuss with maintainers -3. Submit a PR to `spec/oaip-v1alpha1.md` +3. Submit a PR to `spec/AIP-v1alpha1.md` 4. Include conformance tests for new behavior diff --git a/docs/index.mdx b/docs/index.mdx index 6aca97f..6bb0f36 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,19 +1,19 @@ --- -title: "Open Agent Identity Protocol (OAIP)" +title: "Agent Identity Protocol (AIP)" --- -## What is OAIP? +## What is AIP? -OAIP(Open Agent Identity Protocol) is an open-source standard for authentication, attestation, and governance of artificial intelligence (AI) agents. +AIP(Agent Identity Protocol) is an open-source standard for authentication, attestation, and governance of artificial intelligence (AI) agents. Think of it like OAuth for AI agents. There's an increasing problem of agents getting full permissions to API keys, secrets, permissions and running AS the user. This will become a larger problem when the line between the actions between what a human and non-human actor becomes blurred. This has implications not just at a security level but also a legal, societal, and economic level. -OAIP is being built and proposed to the IETF to provide a universal standard for identity in the Internet of Agents (IoA) so that anyone, anywhere, can build secure agents and gain visibility with confidence. +AIP is being built and proposed to the IETF to provide a universal standard for identity in the Internet of Agents (IoA) so that anyone, anywhere, can build secure agents and gain visibility with confidence. ## Architecture - Follow how OAIP is being implemented + Follow how AIP is being implemented ## Develop From 1468558158b493631bddab9dcf57d77fd71670ea Mon Sep 17 00:00:00 2001 From: James <133906218+yungcero@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:09:04 -0700 Subject: [PATCH 3/4] docs: Update documents with proxy level information --- docs/docs.json | 14 +- docs/faq.mdx | 4 +- docs/index.mdx | 4 +- ...licy-reference.md => policy-reference.mdx} | 0 docs/specs/aip-v1alpha1.mdx | 846 +++++ docs/specs/aip-v1alpha2.mdx | 2924 +++++++++++++++++ 6 files changed, 3788 insertions(+), 4 deletions(-) rename docs/{policy-reference.md => policy-reference.mdx} (100%) create mode 100644 docs/specs/aip-v1alpha1.mdx create mode 100644 docs/specs/aip-v1alpha2.mdx diff --git a/docs/docs.json b/docs/docs.json index e3150ef..096e44d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -16,13 +16,23 @@ { "group": "Getting started", "pages": [ - "index" + "index", + "why-AIP" ] }, { "group": "About AIP", "pages": [ - "architecture" + "architecture", + "policy-reference", + { + "group": "Specifications", + "pages": [ + "specs/aip-v1alpha1", + "specs/aip-v1alpha2" + ] + }, + "faq" ] }, { diff --git a/docs/faq.mdx b/docs/faq.mdx index 7ee1c06..4f527e1 100644 --- a/docs/faq.mdx +++ b/docs/faq.mdx @@ -1,4 +1,6 @@ -# Frequently Asked Questions +--- +title: "FAQs" +--- ## General diff --git a/docs/index.mdx b/docs/index.mdx index 6bb0f36..9e60bd9 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -6,10 +6,12 @@ title: "Agent Identity Protocol (AIP)" AIP(Agent Identity Protocol) is an open-source standard for authentication, attestation, and governance of artificial intelligence (AI) agents. -Think of it like OAuth for AI agents. There's an increasing problem of agents getting full permissions to API keys, secrets, permissions and running AS the user. This will become a larger problem when the line between the actions between what a human and non-human actor becomes blurred. This has implications not just at a security level but also a legal, societal, and economic level. +There's an increasing problem of agents getting full permissions to API keys, secrets, permissions and running AS the user. This will become a larger problem when the line between the actions between what a human and non-human actor becomes blurred. This has implications not just at a security level but also a legal, societal, and economic level. AIP is being built and proposed to the IETF to provide a universal standard for identity in the Internet of Agents (IoA) so that anyone, anywhere, can build secure agents and gain visibility with confidence. +The protocol is served by two layers, a Layer 1 identity model and a Layer 2 enforcement proxy model. This creates a zero trust layer that validates every agent before a tool is called. + ## Architecture diff --git a/docs/policy-reference.md b/docs/policy-reference.mdx similarity index 100% rename from docs/policy-reference.md rename to docs/policy-reference.mdx diff --git a/docs/specs/aip-v1alpha1.mdx b/docs/specs/aip-v1alpha1.mdx new file mode 100644 index 0000000..d5c7c73 --- /dev/null +++ b/docs/specs/aip-v1alpha1.mdx @@ -0,0 +1,846 @@ +--- +title: "AIP v1alpha1" +--- + +# Agent Identity Protocol (AIP) Specification + +**Version:** v1alpha1 +**Status:** Draft +**Last Updated:** 2026-01-20 +**Authors:** Eduardo Arango (arangogutierrez@gmail.com) + +--- + +## Abstract + +The Agent Identity Protocol (AIP) defines a standard for policy-based authorization of AI agent tool calls. AIP enables runtime environments to enforce fine-grained access control over Model Context Protocol (MCP) tool invocations, providing a security boundary between AI agents and external resources. + +This specification defines: +1. The policy document schema (`AgentPolicy`) +2. Evaluation semantics for authorization decisions +3. Error codes for denied requests +4. Audit log format for compliance + +AIP is designed to be implementation-agnostic. Any MCP-compatible runtime (Cursor, Claude Desktop, VS Code, custom implementations) can implement this specification. + +--- + +## Table of Contents + +1. [Introduction](#1-introduction) +2. [Terminology](#2-terminology) +3. [Policy Document Schema](#3-policy-document-schema) +4. [Evaluation Semantics](#4-evaluation-semantics) +5. [Error Codes](#5-error-codes) +6. [Audit Log Format](#6-audit-log-format) +7. [Conformance](#7-conformance) +8. [Security Considerations](#8-security-considerations) +9. [IANA Considerations](#9-iana-considerations) + +**Appendices** +- [Appendix A: Complete Schema Reference](#appendix-a-complete-schema-reference) +- [Appendix B: Changelog](#appendix-b-changelog) +- [Appendix C: References](#appendix-c-references) +- [Appendix D: Future Extensions](#appendix-d-future-extensions) +- [Appendix E: Implementation Notes](#appendix-e-implementation-notes) + +--- + +## 1. Introduction + +### 1.1 Motivation + +AI agents operating through the Model Context Protocol (MCP) have access to powerful tools: file systems, databases, APIs, and cloud infrastructure. Without a policy layer, agents operate with unrestricted access to any tool the MCP server exposes. + +AIP addresses this gap by introducing: +- **Capability declaration**: Explicit allowlists of permitted tools +- **Argument validation**: Regex-based constraints on tool parameters +- **Human-in-the-loop**: Interactive approval for sensitive operations +- **Audit trail**: Immutable logging of all authorization decisions + +### 1.2 Goals + +1. **Interoperability**: Any MCP runtime can implement AIP +2. **Simplicity**: YAML-based policies readable by security teams +3. **Defense in depth**: Multiple layers (method, tool, argument) +4. **Fail-closed**: Unknown tools are denied by default + +### 1.3 Non-Goals + +The following are explicitly out of scope for **this version** of the specification: +- Network egress control (see [Appendix D: Future Extensions](#appendix-d-future-extensions)) +- Subprocess sandboxing (implementation-defined) +- Identity federation (future specification) +- Rate limiting algorithms (implementation-defined) + +### 1.4 Relationship to MCP + +AIP is designed as a security layer for MCP. It intercepts `tools/call` requests and applies policy checks before forwarding to the MCP server. + +``` +┌─────────┐ ┌─────────────┐ ┌─────────────┐ +│ Agent │────▶│ AIP Policy │────▶│ MCP Server │ +│ │◀────│ Engine │◀────│ │ +└─────────┘ └─────────────┘ └─────────────┘ +``` + +--- + +## 2. Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +| Term | Definition | +|------|------------| +| **Agent** | An AI system that invokes MCP tools on behalf of a user | +| **Policy** | A document specifying authorization rules (AgentPolicy) | +| **Tool** | An MCP tool exposed by an MCP server | +| **Decision** | The result of policy evaluation: ALLOW, BLOCK, or ASK | +| **Violation** | A policy rule was triggered (may or may not block) | + +--- + +## 3. Policy Document Schema + +### 3.1 Document Structure + +An AIP policy document is a YAML file with the following top-level structure: + +```yaml +apiVersion: aip.io/v1alpha1 +kind: AgentPolicy +metadata: + name: + version: # OPTIONAL + owner: # OPTIONAL +spec: + mode: # OPTIONAL, default: "enforce" + allowed_tools: [] # OPTIONAL + allowed_methods: [] # OPTIONAL + denied_methods: [] # OPTIONAL + tool_rules: [] # OPTIONAL + protected_paths: [] # OPTIONAL + strict_args_default: # OPTIONAL, default: false + dlp: # OPTIONAL +``` + +### 3.2 Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `apiVersion` | string | MUST be `aip.io/v1alpha1` | +| `kind` | string | MUST be `AgentPolicy` | +| `metadata.name` | string | Unique identifier for this policy | + +### 3.3 Metadata + +```yaml +metadata: + name: # REQUIRED - Policy identifier + version: # OPTIONAL - Semantic version (e.g., "1.0.0") + owner: # OPTIONAL - Contact email +``` + +### 3.4 Spec Fields + +#### 3.4.1 mode + +Controls enforcement behavior. + +| Value | Behavior | +|-------|----------| +| `enforce` | Violations are blocked (default) | +| `monitor` | Violations are logged but allowed | + +Implementations MUST support both modes. + +#### 3.4.2 allowed_tools + +A list of tool names that the agent MAY invoke. + +```yaml +allowed_tools: + - github_get_repo + - read_file + - list_directory +``` + +Tool names are subject to normalization (see Section 4.1). + +#### 3.4.3 allowed_methods + +A list of JSON-RPC methods that are permitted. If not specified, implementations MUST use the default safe list: + +```yaml +# Default allowed methods (when not specified) +allowed_methods: + - initialize + - initialized + - ping + - tools/call + - tools/list + - completion/complete + - notifications/initialized + - notifications/progress + - notifications/message + - notifications/resources/updated + - notifications/resources/list_changed + - notifications/tools/list_changed + - notifications/prompts/list_changed + - cancelled +``` + +The wildcard `*` MAY be used to allow all methods. + +#### 3.4.4 denied_methods + +A list of JSON-RPC methods that are explicitly denied. Denied methods take precedence over allowed methods. + +```yaml +denied_methods: + - resources/read + - resources/write +``` + +#### 3.4.5 protected_paths + +A list of file paths that tools MUST NOT access. Any tool argument containing a protected path MUST be blocked. + +```yaml +protected_paths: + - ~/.ssh + - ~/.aws/credentials + - .env +``` + +Implementations MUST: +- Expand `~` to the user's home directory +- Automatically protect the policy file itself + +#### 3.4.6 strict_args_default + +When `true`, tool rules reject any arguments not explicitly declared in `allow_args`. + +Default: `false` + +### 3.5 Tool Rules + +Tool rules provide fine-grained control over specific tools. + +```yaml +tool_rules: + - tool: # REQUIRED - Tool name + action: # OPTIONAL - allow|block|ask (default: allow) + rate_limit: # OPTIONAL - e.g., "10/minute" + strict_args: # OPTIONAL - Override strict_args_default + allow_args: # OPTIONAL + : +``` + +#### 3.5.1 Actions + +| Action | Behavior | +|--------|----------| +| `allow` | Permit (subject to argument validation) | +| `block` | Deny unconditionally | +| `ask` | Require interactive user approval | + +#### 3.5.2 Rate Limiting + +Format: `/` + +| Period | Aliases | +|--------|---------| +| `second` | `sec`, `s` | +| `minute` | `min`, `m` | +| `hour` | `hr`, `h` | + +Example: `"10/minute"`, `"100/hour"`, `"5/second"` + +Rate limiting algorithm is implementation-defined (token bucket, sliding window, etc.). + +#### 3.5.3 Argument Validation + +The `allow_args` field maps argument names to regex patterns. + +```yaml +allow_args: + url: "^https://github\\.com/.*" + query: "^SELECT\\s+.*" +``` + +Implementations MUST: +- Use a regex engine with linear-time guarantees (RE2 or equivalent) +- Match against the string representation of the argument value +- Treat missing constrained arguments as a violation + +### 3.6 DLP Configuration + +Data Loss Prevention (DLP) scans tool responses for sensitive data. + +```yaml +dlp: + enabled: # OPTIONAL, default: true when dlp block present + detect_encoding: # OPTIONAL, default: false + filter_stderr: # OPTIONAL, default: false + patterns: + - name: # REQUIRED - Rule identifier + regex: # REQUIRED - Detection pattern +``` + +When a pattern matches, the matched content MUST be replaced with: +``` +[REDACTED:] +``` + +--- + +## 4. Evaluation Semantics + +### 4.1 Name Normalization + +Tool names and method names MUST be normalized before comparison using the following algorithm: + +``` +NORMALIZE(input): + 1. Apply NFKC Unicode normalization + 2. Convert to lowercase + 3. Trim leading/trailing whitespace + 4. Remove non-printable and control characters + 5. Return result +``` + +This prevents bypass attacks using: +- Fullwidth characters: `delete` → `delete` +- Ligatures: `file` → `file` +- Zero-width characters: `dele​te` → `delete` + +### 4.2 Method-Level Authorization + +Method authorization is the FIRST line of defense, evaluated BEFORE tool-level checks. + +``` +IS_METHOD_ALLOWED(method): + normalized = NORMALIZE(method) + + IF normalized IN denied_methods: + RETURN DENY + + IF "*" IN allowed_methods: + RETURN ALLOW + + IF normalized IN allowed_methods: + RETURN ALLOW + + RETURN DENY +``` + +### 4.3 Tool-Level Authorization + +Tool authorization applies to `tools/call` requests. + +``` +IS_TOOL_ALLOWED(tool_name, arguments): + normalized = NORMALIZE(tool_name) + + # Step 1: Check rate limiting + IF rate_limiter_exceeded(normalized): + RETURN RATE_LIMITED + + # Step 2: Check protected paths + IF arguments_contain_protected_path(arguments): + RETURN PROTECTED_PATH + + # Step 3: Check tool rules + rule = find_rule(normalized) + IF rule EXISTS: + IF rule.action == "block": + RETURN BLOCK + IF rule.action == "ask": + IF validate_arguments(rule, arguments): + RETURN ASK + ELSE: + RETURN BLOCK + # action == "allow" falls through + + # Step 4: Check allowed_tools list + IF normalized NOT IN allowed_tools: + RETURN BLOCK + + # Step 5: Validate arguments (if rule exists) + IF rule EXISTS AND rule.allow_args NOT EMPTY: + IF NOT validate_arguments(rule, arguments): + RETURN BLOCK + + # Step 6: Strict args check + IF strict_args_enabled(rule): + IF arguments has undeclared keys: + RETURN BLOCK + + RETURN ALLOW +``` + +### 4.4 Decision Outcomes + +| Decision | Mode=enforce | Mode=monitor | +|----------|--------------|--------------| +| ALLOW | Forward request | Forward request | +| BLOCK | Return error | Forward request, log violation | +| ASK | Prompt user | Prompt user | +| RATE_LIMITED | Return error | Return error (always enforced) | +| PROTECTED_PATH | Return error | Return error (always enforced) | + +### 4.5 Argument Validation + +``` +VALIDATE_ARGUMENTS(rule, arguments): + FOR EACH (arg_name, pattern) IN rule.allow_args: + IF arg_name NOT IN arguments: + RETURN FALSE # Required argument missing + + value = STRING(arguments[arg_name]) + IF NOT REGEX_MATCH(pattern, value): + RETURN FALSE + + RETURN TRUE +``` + +The `STRING()` function converts values to string representation: +- String → as-is +- Number → decimal representation +- Boolean → "true" or "false" +- Null → empty string +- Array/Object → JSON serialization + +--- + +## 5. Error Codes + +AIP defines the following JSON-RPC error codes: + +| Code | Name | Description | +|------|------|-------------| +| -32001 | Forbidden | Tool not in allowed_tools list | +| -32002 | Rate Limited | Rate limit exceeded | +| -32004 | User Denied | User rejected approval prompt | +| -32005 | User Timeout | Approval prompt timed out | +| -32006 | Method Not Allowed | JSON-RPC method not permitted | +| -32007 | Protected Path | Access to protected path blocked | + +### 5.1 Error Response Format + +```json +{ + "jsonrpc": "2.0", + "id": , + "error": { + "code": , + "message": "", + "data": { + "tool": "", + "reason": "" + } + } +} +``` + +### 5.2 Error Code Details + +#### -32001 Forbidden + +Returned when a tool is not in the `allowed_tools` list and has no `tool_rules` entry with `action: allow`. + +```json +{ + "code": -32001, + "message": "Forbidden", + "data": { + "tool": "dangerous_tool", + "reason": "Tool not in allowed_tools list" + } +} +``` + +#### -32002 Rate Limited + +Returned when a tool's rate limit is exceeded. + +```json +{ + "code": -32002, + "message": "Rate limit exceeded", + "data": { + "tool": "list_gpus", + "reason": "Rate limit exceeded for list_gpus. Try again later." + } +} +``` + +--- + +## 6. Audit Log Format + +Implementations SHOULD log all authorization decisions in JSON Lines format. + +### 6.1 Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `timestamp` | ISO 8601 | Time of the decision | +| `direction` | string | `upstream` (client→server) or `downstream` (server→client) | +| `decision` | string | `ALLOW`, `BLOCK`, `ALLOW_MONITOR`, `RATE_LIMITED` | +| `policy_mode` | string | `enforce` or `monitor` | +| `violation` | boolean | Whether a policy violation was detected | + +### 6.2 Optional Fields + +| Field | Type | Description | +|-------|------|-------------| +| `method` | string | JSON-RPC method name | +| `tool` | string | Tool name (for tools/call) | +| `args` | object | Tool arguments (SHOULD be redacted) | +| `failed_arg` | string | Argument that failed validation | +| `failed_rule` | string | Regex pattern that failed | + +### 6.3 Example + +```json +{ + "timestamp": "2026-01-20T10:30:45.123Z", + "direction": "upstream", + "method": "tools/call", + "tool": "delete_file", + "args": {"path": "/etc/passwd"}, + "decision": "BLOCK", + "policy_mode": "enforce", + "violation": true, + "failed_arg": "path", + "failed_rule": "^/home/.*" +} +``` + +### 6.4 DLP Events + +DLP redaction events SHOULD be logged separately: + +```json +{ + "timestamp": "2026-01-20T10:30:45.123Z", + "direction": "downstream", + "event": "DLP_TRIGGERED", + "dlp_rule": "AWS Key", + "dlp_action": "REDACTED", + "dlp_match_count": 2 +} +``` + +--- + +## 7. Conformance + +### 7.1 Conformance Levels + +| Level | Requirements | +|-------|--------------| +| **Basic** | Method authorization, tool allowlist, error codes | +| **Full** | Basic + argument validation, rate limiting, DLP, audit logging | +| **Extended** | Full + Human-in-the-Loop (action=ask) | + +### 7.2 Conformance Testing + +Implementations MUST pass the conformance test suite to claim AIP compliance. + +The test suite consists of: +1. **Schema validation tests**: Verify policy parsing +2. **Decision tests**: Input → expected decision +3. **Normalization tests**: Verify Unicode handling +4. **Error format tests**: Verify JSON-RPC errors + +See `spec/conformance/` for test vectors. + +### 7.3 Implementation Requirements + +Implementations MUST: +- Parse `apiVersion: aip.io/v1alpha1` documents +- Reject documents with unknown `apiVersion` +- Apply NFKC normalization to names +- Return specified error codes +- Support `enforce` and `monitor` modes + +Implementations SHOULD: +- Log decisions in the specified format +- Support DLP scanning +- Support rate limiting + +Implementations MAY: +- Use any regex engine with RE2 semantics +- Implement additional security features (egress control, sandboxing) + +--- + +## 8. Security Considerations + +### 8.1 Policy File Protection + +The policy file itself MUST be protected from modification by the agent. Implementations MUST automatically add the policy file path to `protected_paths`. + +### 8.2 Regex Denial of Service (ReDoS) + +Implementations MUST use a regex engine that guarantees linear-time matching (RE2 or equivalent). Pathological patterns like `(a+)+$` MUST NOT cause exponential execution time. + +### 8.3 Unicode Normalization + +Implementations MUST apply NFKC normalization to prevent homoglyph attacks. However, implementers should be aware that NFKC does not normalize all visually similar characters (e.g., Cyrillic 'а' vs Latin 'a'). + +### 8.4 Monitor Mode Risks + +Monitor mode allows all requests through. Implementations SHOULD warn users when monitor mode is enabled in production environments. + +### 8.5 Audit Log Integrity + +Audit logs SHOULD be written to a location not writable by the agent. Implementations MAY support log signing or forwarding to external systems. + +--- + +## 9. IANA Considerations + +This specification requests registration of the following: + +### 9.1 Media Type + +- Type name: application +- Subtype name: vnd.aip.policy+yaml +- Required parameters: None +- File extension: .yaml, .yml + +### 9.2 URI Scheme + +This specification uses the `aip.io` namespace for versioning: +- `aip.io/v1alpha1` - This specification + +--- + +## Appendix A: Complete Schema Reference + +```yaml +# Complete AgentPolicy schema + +apiVersion: aip.io/v1alpha1 # REQUIRED +kind: AgentPolicy # REQUIRED + +metadata: # REQUIRED + name: string # REQUIRED - Policy identifier + version: string # OPTIONAL - Semantic version + owner: string # OPTIONAL - Contact email + +spec: # REQUIRED + mode: enforce | monitor # OPTIONAL, default: enforce + + allowed_tools: # OPTIONAL + - string + + allowed_methods: # OPTIONAL + - string + + denied_methods: # OPTIONAL + - string + + protected_paths: # OPTIONAL + - string + + strict_args_default: boolean # OPTIONAL, default: false + + tool_rules: # OPTIONAL + - tool: string # REQUIRED + action: allow|block|ask # OPTIONAL, default: allow + rate_limit: string # OPTIONAL, format: "N/period" + strict_args: boolean # OPTIONAL + allow_args: # OPTIONAL + : + + dlp: # OPTIONAL + enabled: boolean # OPTIONAL, default: true + detect_encoding: boolean # OPTIONAL, default: false + filter_stderr: boolean # OPTIONAL, default: false + patterns: # REQUIRED if dlp present + - name: string # REQUIRED + regex: string # REQUIRED +``` + +--- + +## Appendix B: Changelog + +### v1alpha1 (2026-01-20) + +- Initial draft specification +- Defined core policy schema +- Defined evaluation semantics +- Defined error codes +- Defined audit log format + +--- + +## Appendix C: References + +- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) +- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) +- [RFC 2119 - Key words for use in RFCs](https://www.rfc-editor.org/rfc/rfc2119) +- [Unicode NFKC Normalization](https://unicode.org/reports/tr15/) +- [RE2 Syntax](https://github.com/google/re2/wiki/Syntax) + +--- + +## Appendix D: Future Extensions + +This appendix describes features under consideration for future versions of AIP. + +### D.1 Network Egress Control + +**Status:** Proposed for v1beta1 + +#### Motivation + +Tool-level authorization prevents agents from calling dangerous tools, but a compromised or malicious MCP server can still exfiltrate data through: +- Outbound HTTP requests embedded in tool implementations +- DNS exfiltration +- Covert channels in allowed network traffic + +Egress control would allow policies to restrict which network destinations an MCP server (or its subprocesses) can reach. + +#### Proposed Schema Extension + +```yaml +apiVersion: aip.io/v1beta1 # Future version +kind: AgentPolicy +metadata: + name: egress-example +spec: + # ... existing fields ... + + egress: + mode: block | allow | monitor # Default: allow (no restriction) + + allowed_hosts: + - "api.github.com" + - "*.openai.com" + - "10.0.0.0/8" # CIDR notation + + denied_hosts: + - "*.ngrok.io" + - "*.requestbin.com" + + allowed_ports: + - 443 + - 80 + + denied_ports: + - 22 + - 3389 +``` + +#### Implementation Considerations + +Egress control is inherently platform-specific: + +| Platform | Mechanism | Limitations | +|----------|-----------|-------------| +| **Linux** | eBPF, seccomp-bpf, network namespaces | Requires CAP_BPF or root | +| **macOS** | Network Extension, sandbox-exec | Requires entitlements | +| **Windows** | Windows Filtering Platform (WFP) | Requires admin | +| **Container** | `--network=none`, network policies | Requires container runtime | +| **Cross-platform** | DNS-based filtering, HTTP proxy | Bypassable, incomplete | + +Implementations MAY support egress control through any mechanism appropriate for their platform. The specification will define the **policy schema** and **expected behavior**, not the enforcement mechanism. + +#### Open Questions + +1. Should egress rules be per-tool or global? +2. How to handle DNS resolution (allow DNS but block resolved IP)? +3. Should there be a "learning mode" to auto-generate allowlists? +4. How to handle localhost connections (MCP servers often bind locally)? + +### D.2 Identity Federation + +**Status:** Under Discussion + +Allow policies to reference external identity providers: + +```yaml +spec: + identity: + provider: "oidc" + issuer: "https://accounts.google.com" + required_claims: + email_verified: true + hd: "company.com" +``` + +### D.3 Policy Inheritance + +**Status:** Under Discussion + +Allow policies to extend base policies: + +```yaml +apiVersion: aip.io/v1beta1 +kind: AgentPolicy +metadata: + name: team-policy +spec: + extends: "org-base-policy" # Inherit from another policy + allowed_tools: + - additional_tool # Add to parent's list +``` + +### D.4 Telemetry and Metrics + +**Status:** Under Discussion + +Standardized metrics export for observability: + +```yaml +spec: + telemetry: + metrics: + endpoint: "http://prometheus:9090/metrics" + format: "prometheus" + traces: + endpoint: "http://jaeger:14268/api/traces" + format: "otlp" +``` + +--- + +## Appendix E: Implementation Notes + +This appendix provides guidance for implementers. + +### E.1 Reference Implementation + +The reference implementation is available at: +https://github.com/ArangoGutierrez/agent-identity-protocol + +It provides: +- Go-based proxy (`aip-proxy`) +- Policy engine (`pkg/policy`) +- DLP scanner (`pkg/dlp`) +- Audit logger (`pkg/audit`) + +### E.2 Testing Against Conformance Suite + +```bash +# Clone the spec repository +git clone https://github.com/ArangoGutierrez/agent-identity-protocol + +# Run conformance tests against your implementation +cd agent-identity-protocol/spec/conformance +./run-tests.sh --impl "your-aip-binary" +``` + +### E.3 Registering Your Implementation + +Implementations that pass the conformance suite may be listed in the official registry. Submit a PR to the AIP repository with: +- Implementation name and URL +- Conformance level achieved (Basic/Full/Extended) +- Platform support matrix diff --git a/docs/specs/aip-v1alpha2.mdx b/docs/specs/aip-v1alpha2.mdx new file mode 100644 index 0000000..aa0f7f0 --- /dev/null +++ b/docs/specs/aip-v1alpha2.mdx @@ -0,0 +1,2924 @@ +--- +title: "AIP v1alpha2" +--- + +# Agent Identity Protocol (AIP) Specification + +**Version:** v1alpha2 +**Status:** Draft +**Last Updated:** 2026-01-24 +**Authors:** Eduardo Arango (arangogutierrez@gmail.com) + +--- + +## Abstract + +The Agent Identity Protocol (AIP) defines a standard for policy-based authorization of AI agent tool calls. AIP enables runtime environments to enforce fine-grained access control over Model Context Protocol (MCP) tool invocations, providing a security boundary between AI agents and external resources. + +This specification defines: +1. The policy document schema (`AgentPolicy`) +2. Evaluation semantics for authorization decisions +3. **Agent identity and session management** *(new in v1alpha2)* +4. **Server-side validation endpoints** *(new in v1alpha2)* +5. Error codes for denied requests +6. Audit log format for compliance + +AIP is designed to be implementation-agnostic. Any MCP-compatible runtime (Cursor, Claude Desktop, VS Code, custom implementations) can implement this specification. + +--- + +## Table of Contents + +1. [Introduction](#1-introduction) +2. [Terminology](#2-terminology) +3. [Policy Document Schema](#3-policy-document-schema) +4. [Evaluation Semantics](#4-evaluation-semantics) +5. [Agent Identity](#5-agent-identity) *(new in v1alpha2)* +6. [Server-Side Validation](#6-server-side-validation) *(new in v1alpha2)* +7. [Error Codes](#7-error-codes) +8. [Audit Log Format](#8-audit-log-format) +9. [Conformance](#9-conformance) +10. [Security Considerations](#10-security-considerations) +11. [IANA Considerations](#11-iana-considerations) + +**Appendices** +- [Appendix A: Complete Schema Reference](#appendix-a-complete-schema-reference) +- [Appendix B: Changelog](#appendix-b-changelog) +- [Appendix C: References](#appendix-c-references) +- [Appendix D: Future Extensions](#appendix-d-future-extensions) +- [Appendix E: Implementation Notes](#appendix-e-implementation-notes) + +--- + +## 1. Introduction + +### 1.1 Motivation + +AI agents operating through the Model Context Protocol (MCP) have access to powerful tools: file systems, databases, APIs, and cloud infrastructure. Without a policy layer, agents operate with unrestricted access to any tool the MCP server exposes. + +AIP addresses this gap by introducing: +- **Capability declaration**: Explicit allowlists of permitted tools +- **Argument validation**: Regex-based constraints on tool parameters +- **Human-in-the-loop**: Interactive approval for sensitive operations +- **Audit trail**: Immutable logging of all authorization decisions +- **Agent identity**: Cryptographic binding of policies to agent sessions *(new in v1alpha2)* +- **Server-side validation**: Optional HTTP endpoints for distributed policy enforcement *(new in v1alpha2)* + +### 1.2 Goals + +1. **Interoperability**: Any MCP runtime can implement AIP +2. **Simplicity**: YAML-based policies readable by security teams +3. **Defense in depth**: Multiple layers (method, tool, argument, identity) +4. **Fail-closed**: Unknown tools are denied by default +5. **Zero-trust ready**: Support for token-based identity verification *(new in v1alpha2)* + +### 1.3 Non-Goals + +The following are explicitly out of scope for **this version** of the specification: +- Network egress control (see [Appendix D: Future Extensions](#appendix-d-future-extensions)) +- Subprocess sandboxing (implementation-defined) +- External identity federation (OIDC/SPIFFE - see [Appendix D](#d3-external-identity-federation)) +- Rate limiting algorithms (implementation-defined) +- Policy expression languages beyond regex (CEL/Rego - see [Appendix D](#d5-advanced-policy-expressions)) + +### 1.4 Relationship to MCP + +AIP is designed as a security layer for MCP. It intercepts `tools/call` requests and applies policy checks before forwarding to the MCP server. + +``` +┌─────────┐ ┌─────────────┐ ┌─────────────┐ +│ Agent │────▶│ AIP Policy │────▶│ MCP Server │ +│ │◀────│ Engine │◀────│ │ +└─────────┘ └─────────────┘ └─────────────┘ + │ + ▼ + ┌─────────────┐ + │ AIP Server │ (optional, v1alpha2) + │ Endpoint │ + └─────────────┘ +``` + +### 1.5 Relationship to MCP Authorization + +MCP defines an optional OAuth 2.1-based authorization layer (MCP 2025-06-18 and later). AIP is **complementary** to MCP authorization: + +| Concern | MCP Authorization | AIP | +|---------|-------------------|-----| +| **Scope** | Transport-level authentication | Tool-level authorization | +| **What it protects** | Access to MCP server | Access to specific tools | +| **Token type** | OAuth 2.1 access tokens | AIP Identity Tokens (optional) | +| **Policy language** | OAuth scopes | YAML policy documents | + +Implementations MAY use both MCP authorization (for server access) and AIP (for tool access) simultaneously. + +--- + +## 2. Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +| Term | Definition | +|------|------------| +| **Agent** | An AI system that invokes MCP tools on behalf of a user | +| **Policy** | A document specifying authorization rules (AgentPolicy) | +| **Tool** | An MCP tool exposed by an MCP server | +| **Decision** | The result of policy evaluation: ALLOW, BLOCK, or ASK | +| **Violation** | A policy rule was triggered (may or may not block) | +| **Session** | A bounded period of agent activity with consistent identity *(new)* | +| **Identity Token** | A cryptographic token binding policy to session *(new)* | +| **Policy Hash** | SHA-256 hash of the canonical policy document *(new)* | + +--- + +## 3. Policy Document Schema + +### 3.1 Document Structure + +An AIP policy document is a YAML file with the following top-level structure: + +```yaml +apiVersion: aip.io/v1alpha2 +kind: AgentPolicy +metadata: + name: + version: # OPTIONAL + owner: # OPTIONAL + signature: # OPTIONAL (v1alpha2) +spec: + mode: # OPTIONAL, default: "enforce" + allowed_tools: [] # OPTIONAL + allowed_methods: [] # OPTIONAL + denied_methods: [] # OPTIONAL + tool_rules: [] # OPTIONAL + protected_paths: [] # OPTIONAL + strict_args_default: # OPTIONAL, default: false + dlp: # OPTIONAL + identity: # OPTIONAL (v1alpha2) + server: # OPTIONAL (v1alpha2) +``` + +### 3.2 Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `apiVersion` | string | MUST be `aip.io/v1alpha2` | +| `kind` | string | MUST be `AgentPolicy` | +| `metadata.name` | string | Unique identifier for this policy | + +### 3.3 Metadata + +```yaml +metadata: + name: # REQUIRED - Policy identifier + version: # OPTIONAL - Semantic version (e.g., "1.0.0") + owner: # OPTIONAL - Contact email + signature: # OPTIONAL - Policy signature (v1alpha2) +``` + +#### 3.3.1 Policy Signature (v1alpha2) + +The `signature` field provides cryptographic integrity verification for the policy document. + +Format: `:` + +Supported algorithms: +- `ed25519` - Ed25519 signature (RECOMMENDED) + +Example: +```yaml +metadata: + name: production-agent + signature: "ed25519:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo..." +``` + +When present, implementations MUST verify the signature before applying the policy. Signature verification failure MUST result in policy rejection. + +The signature is computed over the **canonical form** of the policy document (see Section 5.2.1). + +### 3.4 Spec Fields + +*[Sections 3.4.1 through 3.6 remain unchanged from v1alpha1]* + +#### 3.4.1 mode + +Controls enforcement behavior. + +| Value | Behavior | +|-------|----------| +| `enforce` | Violations are blocked (default) | +| `monitor` | Violations are logged but allowed | + +Implementations MUST support both modes. + +#### 3.4.2 allowed_tools + +A list of tool names that the agent MAY invoke. + +```yaml +allowed_tools: + - github_get_repo + - read_file + - list_directory +``` + +Tool names are subject to normalization (see Section 4.1). + +#### 3.4.3 allowed_methods + +A list of JSON-RPC methods that are permitted. If not specified, implementations MUST use the default safe list: + +```yaml +# Default allowed methods (when not specified) +allowed_methods: + - initialize + - initialized + - ping + - tools/call + - tools/list + - completion/complete + - notifications/initialized + - notifications/progress + - notifications/message + - notifications/resources/updated + - notifications/resources/list_changed + - notifications/tools/list_changed + - notifications/prompts/list_changed + - cancelled +``` + +The wildcard `*` MAY be used to allow all methods. + +#### 3.4.4 denied_methods + +A list of JSON-RPC methods that are explicitly denied. Denied methods take precedence over allowed methods. + +```yaml +denied_methods: + - resources/read + - resources/write +``` + +#### 3.4.5 protected_paths + +A list of file paths that tools MUST NOT access. Any tool argument containing a protected path MUST be blocked. + +```yaml +protected_paths: + - ~/.ssh + - ~/.aws/credentials + - .env +``` + +Implementations MUST: +- Expand `~` to the user's home directory +- Automatically protect the policy file itself + +#### 3.4.6 strict_args_default + +When `true`, tool rules reject any arguments not explicitly declared in `allow_args`. + +Default: `false` + +### 3.5 Tool Rules + +Tool rules provide fine-grained control over specific tools. + +```yaml +tool_rules: + - tool: # REQUIRED - Tool name + action: # OPTIONAL - allow|block|ask (default: allow) + rate_limit: # OPTIONAL - e.g., "10/minute" + strict_args: # OPTIONAL - Override strict_args_default + schema_hash: # OPTIONAL - Tool schema integrity (v1alpha2) + allow_args: # OPTIONAL + : +``` + +#### 3.5.1 Actions + +| Action | Behavior | +|--------|----------| +| `allow` | Permit (subject to argument validation) | +| `block` | Deny unconditionally | +| `ask` | Require interactive user approval | + +#### 3.5.2 Rate Limiting + +Format: `/` + +| Period | Aliases | +|--------|---------| +| `second` | `sec`, `s` | +| `minute` | `min`, `m` | +| `hour` | `hr`, `h` | + +Example: `"10/minute"`, `"100/hour"`, `"5/second"` + +Rate limiting algorithm is implementation-defined (token bucket, sliding window, etc.). + +#### 3.5.3 Argument Validation + +The `allow_args` field maps argument names to regex patterns. + +```yaml +allow_args: + url: "^https://github\\.com/.*" + query: "^SELECT\\s+.*" +``` + +Implementations MUST: +- Use a regex engine with linear-time guarantees (RE2 or equivalent) +- Match against the string representation of the argument value +- Treat missing constrained arguments as a violation + +#### 3.5.4 Tool Schema Hashing (v1alpha2) + +The `schema_hash` field provides cryptographic verification of tool definitions to prevent tool poisoning attacks. + +**Format**: `:` + +**Supported algorithms**: +- `sha256` (RECOMMENDED) +- `sha384` +- `sha512` + +**Example**: +```yaml +tool_rules: + - tool: read_file + action: allow + schema_hash: "sha256:a3c7f2e8d9b4f1e2c8a7d6f3e9b2c4f1a8e7d3c2b5f4e9a7c3d8f2b6e1a9c4f7" + allow_args: + path: "^/home/.*" +``` + +**Hash computation**: + +The schema hash is computed over the canonical form of the tool's MCP schema: + +``` +TOOL_SCHEMA_HASH(tool): + schema = { + "name": tool.name, + "description": tool.description, + "inputSchema": tool.inputSchema # JSON Schema for arguments + } + canonical = JSON_CANONICALIZE(schema) # RFC 8785 + hash = SHA256(canonical) + RETURN "sha256:" + hex_encode(hash) +``` + +**Behavior**: + +| Condition | Behavior | +|-----------|----------| +| `schema_hash` absent | No schema verification (backward compatible) | +| Hash matches | Tool allowed (proceed to argument validation) | +| Hash mismatch | Tool BLOCKED with error -32013 | +| Tool not found | Tool BLOCKED with error -32001 | + +**Use cases**: + +1. **Tool poisoning prevention**: Detect when an MCP server changes a tool's behavior after policy approval +2. **Compliance auditing**: Prove that approved tools haven't been modified +3. **Supply chain security**: Pin specific tool versions in policy + +**Generating schema hashes**: + +```bash +# Using the AIP CLI (reference implementation) +aip-proxy schema-hash --server mcp://localhost:8080 --tool read_file +# Output: sha256:a3c7f2e8... + +# Or from tools/list response +aip-proxy schema-hash --tools-file tools.json --tool read_file +``` + +**Operational considerations**: + +- Schema hashes MUST be regenerated when MCP server is updated +- Implementations SHOULD log hash mismatches with both expected and actual hashes +- Policy authors SHOULD document which tool version the hash corresponds to + +**Error code** (new): + +| Code | Name | Description | +|------|------|-------------| +| -32013 | Schema Mismatch | Tool schema hash does not match policy *(new)* | + +### 3.6 DLP Configuration + +Data Loss Prevention (DLP) scans for sensitive data in requests and responses. + +```yaml +dlp: + enabled: # OPTIONAL, default: true when dlp block present + scan_requests: # OPTIONAL, default: false (v1alpha2) + scan_responses: # OPTIONAL, default: true + detect_encoding: # OPTIONAL, default: false + filter_stderr: # OPTIONAL, default: false + max_scan_size: # OPTIONAL, default: "1MB" (v1alpha2) + on_request_match: # OPTIONAL, default: "block" (v1alpha2) + patterns: + - name: # REQUIRED - Rule identifier + regex: # REQUIRED - Detection pattern + scope: # OPTIONAL, default: "all" (request|response|all) +``` + +#### 3.6.1 scan_requests (v1alpha2) + +When `true`, DLP patterns are applied to tool arguments before the request is forwarded. + +Default: `false` (backward compatible) + +**Use case**: Prevents data exfiltration via arguments (e.g., embedding secrets in API queries). + +#### 3.6.2 scan_responses + +When `true`, DLP patterns are applied to tool responses. + +Default: `true` + +#### 3.6.3 max_scan_size (v1alpha2) + +Maximum size of content to scan per request/response. + +Format: Size string (e.g., `"1MB"`, `"512KB"`, `"10MB"`) + +Default: `"1MB"` + +Content exceeding this limit: +- SHOULD be truncated for scanning (scan first `max_scan_size` bytes) +- MUST log a warning + +**Purpose**: Prevents ReDoS and memory exhaustion on large payloads. + +#### 3.6.4 on_request_match (v1alpha2) + +Action when DLP pattern matches in a request (when `scan_requests: true`). + +| Value | Behavior | +|-------|----------| +| `block` | Reject the request with error -32001 (default) | +| `redact` | Replace matched content and forward | +| `warn` | Log warning and forward unchanged | + +Default: `block` + +**Security note**: `redact` for requests may produce invalid tool arguments. Use with caution. + +**Redaction failure handling (v1alpha2)**: + +When `on_request_match: "redact"` is configured, redacted content may cause downstream failures: + +1. **Invalid JSON**: Redaction in nested structures may break JSON parsing +2. **Schema validation failure**: Redacted values may violate tool argument schemas +3. **Tool execution failure**: The MCP server may reject redacted arguments + +**Configuration for redaction failure behavior**: + +```yaml +dlp: + scan_requests: true + on_request_match: "redact" + on_redaction_failure: # OPTIONAL, default: "block" (v1alpha2) + log_original_on_failure: # OPTIONAL, default: false (v1alpha2) +``` + +| Field | Type | Description | +|-------|------|-------------| +| `on_redaction_failure` | string | Action when redacted request fails: `block`, `allow_original`, `reject` | +| `log_original_on_failure` | bool | Log pre-redaction content for forensics (sensitive!) | + +**on_redaction_failure values**: + +| Value | Behavior | Security | Use Case | +|-------|----------|----------|----------| +| `block` | Block with -32001 (default) | High | Production | +| `allow_original` | Forward original unredacted | Low | Debug only | +| `reject` | Block with -32014 (new error) | High | Strict compliance | + +**Example configuration**: +```yaml +dlp: + scan_requests: true + on_request_match: "redact" + on_redaction_failure: "block" + log_original_on_failure: true # For forensic analysis + patterns: + - name: "API Key" + regex: "sk-[a-zA-Z0-9]{32}" + scope: "request" +``` + +**Error code for redaction failures** (new): + +| Code | Name | Description | +|------|------|-------------| +| -32014 | DLP Redaction Failed | Request redaction produced invalid content *(new)* | + +**Example error response**: +```json +{ + "code": -32014, + "message": "DLP redaction failed", + "data": { + "tool": "http_request", + "reason": "Redacted request failed argument validation", + "dlp_rule": "API Key", + "validation_error": "url: expected string, got [REDACTED:API Key]" + } +} +``` + +**Audit logging for redaction events**: + +```json +{ + "timestamp": "2026-01-24T10:30:45.123Z", + "event": "DLP_REQUEST_REDACTION", + "tool": "http_request", + "dlp_rule": "API Key", + "redaction_count": 1, + "forwarded": false, + "failure_reason": "argument_validation_failed" +} +``` + +⚠️ **Security consideration**: Setting `log_original_on_failure: true` will log sensitive data that DLP attempted to redact. This SHOULD only be enabled: +- In development environments +- With appropriate log access controls +- For time-limited forensic investigations + +#### 3.6.5 Pattern Scope (v1alpha2) + +Patterns can be scoped to requests, responses, or both: + +```yaml +patterns: + - name: "AWS Key" + regex: "AKIA[0-9A-Z]{16}" + scope: "all" # Scan both requests and responses + + - name: "SQL Injection" + regex: "(?i)(DROP|DELETE|TRUNCATE)\\s+TABLE" + scope: "request" # Only scan requests (detect exfiltration attempts) + + - name: "SSN" + regex: "\\d{3}-\\d{2}-\\d{4}" + scope: "response" # Only scan responses (PII protection) +``` + +When a pattern matches, the matched content MUST be replaced with: +``` +[REDACTED:] +``` + +### 3.7 Identity Configuration (v1alpha2) + +The `identity` section configures agent identity and token management. + +```yaml +spec: + identity: + enabled: # OPTIONAL, default: false + token_ttl: # OPTIONAL, default: "5m" + rotation_interval: # OPTIONAL, default: "4m" + require_token: # OPTIONAL, default: false + session_binding: # OPTIONAL, default: "process" + nonce_window: # OPTIONAL, default: equals token_ttl (v1alpha2) + policy_transition_grace: # OPTIONAL, default: "0s" (v1alpha2) + audience: # OPTIONAL, default: policy metadata.name (v1alpha2) + nonce_storage: # OPTIONAL (v1alpha2) + keys: # OPTIONAL (v1alpha2) +``` + +#### 3.7.1 enabled + +When `true`, the AIP engine generates and manages identity tokens for the session. + +Default: `false` + +#### 3.7.2 token_ttl + +The time-to-live for identity tokens. + +Format: Go duration string (e.g., `"5m"`, `"1h"`, `"300s"`) + +Default: `"5m"` (5 minutes) + +Implementations SHOULD use short TTLs (5-15 minutes) to limit token theft window. + +#### 3.7.3 rotation_interval + +How often to rotate tokens before expiry. + +Format: Go duration string + +Default: `"4m"` (4 minutes, ensuring rotation before 5m TTL) + +**Constraint**: `rotation_interval` MUST be less than `token_ttl`. + +**Validation behavior (v1alpha2)**: + +When loading a policy, implementations MUST validate the rotation_interval constraint: + +``` +VALIDATE_ROTATION_INTERVAL(config): + IF config.rotation_interval >= config.token_ttl: + RETURN ERROR("rotation_interval must be less than token_ttl") + + # Recommended: rotation should leave grace period for in-flight requests + IF config.rotation_interval > (config.token_ttl * 0.9): + LOG_WARNING("rotation_interval very close to token_ttl; consider reducing") + + RETURN OK +``` + +**Error handling**: + +| Condition | Behavior | Error | +|-----------|----------|-------| +| `rotation_interval >= token_ttl` | Reject policy | Policy load failure | +| `rotation_interval > token_ttl * 0.9` | Warn, allow | Log warning | +| `rotation_interval` not specified | Use default (`"4m"`) | - | +| `rotation_interval: "0s"` | Disable rotation | - | + +**Invalid configuration example**: +```yaml +# INVALID: rotation_interval >= token_ttl +identity: + enabled: true + token_ttl: "5m" + rotation_interval: "6m" # ERROR: must be < 5m +``` + +**Policy load error response**: +```json +{ + "error": "policy_validation_failed", + "message": "rotation_interval (6m) must be less than token_ttl (5m)", + "field": "spec.identity.rotation_interval" +} +``` + +**Recommended configurations**: + +| Use Case | `token_ttl` | `rotation_interval` | Rationale | +|----------|-------------|---------------------|-----------| +| Default | `"5m"` | `"4m"` | 1 minute grace for in-flight | +| High-security | `"5m"` | `"2m"` | More frequent rotation | +| Low-latency | `"1m"` | `"45s"` | Minimal token lifetime | +| Long-lived | `"1h"` | `"50m"` | 10 minute grace | + +**Disabling rotation**: + +Setting `rotation_interval: "0s"` disables automatic rotation. Tokens will only be refreshed when explicitly requested or when they expire. + +```yaml +identity: + enabled: true + token_ttl: "5m" + rotation_interval: "0s" # No automatic rotation +``` + +⚠️ **Not recommended** for production as it increases token theft window. + +#### 3.7.4 require_token + +When `true`, all tool calls MUST include a valid identity token. Calls without tokens are rejected with error code -32008. + +Default: `false` + +This enables gradual rollout: start with `require_token: false` to generate tokens without enforcement, then enable enforcement. + +#### 3.7.5 session_binding + +Determines what context is bound to the session identity. + +| Value | Binding | +|-------|---------| +| `process` | Session bound to process ID (default) | +| `policy` | Session bound to policy hash | +| `strict` | Session bound to process + policy + timestamp | + +#### 3.7.6 nonce_window + +The duration to retain nonces for replay detection. + +Format: Go duration string + +Default: Equals `token_ttl` (e.g., `"5m"` if token_ttl is `"5m"`) + +**Purpose**: Bounds the storage required for replay prevention. Nonces older than `nonce_window` MAY be pruned from storage. + +**Constraints**: +- `nonce_window` MUST be greater than or equal to `token_ttl` +- Setting `nonce_window` less than `token_ttl` is a configuration error + +**Storage considerations**: + +| Deployment | Recommended `nonce_window` | +|------------|---------------------------| +| Single instance | `token_ttl` (default) | +| Multi-instance (shared storage) | `token_ttl + clock_skew_tolerance` | +| High-security | `2 * token_ttl` | + +Example: +```yaml +identity: + enabled: true + token_ttl: "5m" + nonce_window: "10m" # Retain nonces for 2x TTL +``` + +#### 3.7.7 policy_transition_grace + +The grace period during which tokens issued with the previous policy hash remain valid after a policy update. + +Format: Go duration string + +Default: `"0s"` (no grace period - strict policy enforcement) + +**Purpose**: Allows gradual policy rollouts without invalidating all in-flight tokens immediately. + +**Behavior**: +1. When policy is updated, the previous policy hash is retained in `recent_policy_hashes` +2. Tokens with either current or recent policy hash are accepted during the grace period +3. After grace period expires, only current policy hash is valid + +**Constraints**: +- `policy_transition_grace` SHOULD be less than `token_ttl` to ensure policy changes take effect within one token lifetime +- Setting very long grace periods weakens security guarantees + +**Example**: +```yaml +identity: + enabled: true + token_ttl: "5m" + policy_transition_grace: "2m" # Accept old policy hash for 2 minutes +``` + +**Use cases**: + +| Scenario | Recommended Setting | +|----------|---------------------| +| Development | `"0s"` - Immediate policy updates | +| Production (single instance) | `"30s"` - Brief grace for in-flight requests | +| Production (distributed) | `"2m"` - Allow for propagation delay | +| Canary deployments | Equal to deployment window | + +#### 3.7.8 audience (v1alpha2) + +The intended audience for identity tokens. This value is included in the token's `aud` claim and MUST be validated by recipients. + +Format: URI string identifying the MCP server or service + +Default: Value of `metadata.name` + +**Purpose**: Prevents tokens issued for one MCP server from being accepted by another. This is critical for: +- Multi-tenant deployments where agents access multiple MCP servers +- Defense against token theft and replay across services +- Compliance with OAuth 2.1 audience binding requirements (RFC 8707) + +**Example**: +```yaml +identity: + enabled: true + audience: "https://mcp.example.com/api" +``` + +**Validation requirements**: +- Implementations MUST reject tokens where `aud` does not match the expected audience +- When `server.enabled: true`, the audience SHOULD be the server's canonical URL +- Wildcards are NOT permitted in audience values + +**Constraints**: +- `audience` MUST be a valid URI or the policy `metadata.name` +- Empty string is NOT valid; use default (metadata.name) instead + +#### 3.7.9 nonce_storage (v1alpha2) + +Configuration for distributed nonce storage, required for multi-instance deployments. + +```yaml +spec: + identity: + nonce_storage: + type: # OPTIONAL, default: "memory" + address: # REQUIRED if type != "memory" + key_prefix: # OPTIONAL, default: "aip:nonce:" + clock_skew_tolerance: # OPTIONAL, default: "30s" +``` + +| Field | Type | Description | +|-------|------|-------------| +| `type` | string | Storage backend: `memory`, `redis`, `postgres` | +| `address` | string | Connection string for external storage | +| `key_prefix` | string | Prefix for nonce keys (namespacing) | +| `clock_skew_tolerance` | duration | Added to TTL to handle clock drift | + +**Storage type requirements**: + +| Type | Atomicity | Persistence | Multi-instance | Use Case | +|------|-----------|-------------|----------------|----------| +| `memory` | ✅ (sync.Map) | ❌ | ❌ | Development, single-instance | +| `redis` | ✅ (SET NX) | ✅ | ✅ | Production (RECOMMENDED) | +| `postgres` | ✅ (UNIQUE) | ✅ | ✅ | Production with existing DB | + +**Example configurations**: + +```yaml +# Single instance (default) +identity: + enabled: true + nonce_storage: + type: "memory" + +# Redis cluster +identity: + enabled: true + nonce_storage: + type: "redis" + address: "redis://redis-cluster:6379" + key_prefix: "prod:aip:nonce:" + clock_skew_tolerance: "30s" + +# PostgreSQL +identity: + enabled: true + nonce_storage: + type: "postgres" + address: "postgres://user:pass@db:5432/aip?sslmode=require" + key_prefix: "nonces_" +``` + +⚠️ **Multi-instance deployments**: Using `type: "memory"` with multiple AIP instances is a **security vulnerability** that allows cross-instance replay attacks. Implementations SHOULD warn when `memory` storage is detected in environments with multiple instances. + +### 3.8 Server Configuration (v1alpha2) + +The `server` section configures optional HTTP endpoints for server-side validation. + +```yaml +spec: + server: + enabled: # OPTIONAL, default: false + listen: # OPTIONAL, default: "127.0.0.1:9443" + failover_mode: # OPTIONAL, default: "fail_closed" (v1alpha2) + timeout: # OPTIONAL, default: "5s" (v1alpha2) + tls: # OPTIONAL + cert: # Path to TLS certificate + key: # Path to TLS private key + endpoints: # OPTIONAL + validate: # Validation endpoint path (default: "/v1/validate") + revoke: # Revocation endpoint path (default: "/v1/revoke") + health: # Health check path (default: "/health") + metrics: # Metrics endpoint path (default: "/metrics") +``` + +#### 3.8.1 enabled + +When `true`, the AIP engine starts an HTTP server for remote validation. + +Default: `false` + +#### 3.8.2 listen + +The address and port to bind the HTTP server. + +Format: `:` or `:` + +Default: `"127.0.0.1:9443"` (localhost only) + +⚠️ **Security**: Binding to `0.0.0.0` exposes the validation endpoint to the network. Implementations MUST require TLS when listen address is not localhost. + +#### 3.8.3 failover_mode + +Defines behavior when the validation server is unreachable (for clients) or when internal validation fails (for server). + +| Value | Behavior | Security | Availability | +|-------|----------|----------|--------------| +| `fail_closed` | Deny all requests | High | Low | +| `fail_open` | Allow all requests | Low | High | +| `local_policy` | Fall back to local policy evaluation | Medium | Medium | + +Default: `fail_closed` (deny-by-default for security) + +**fail_closed** (RECOMMENDED for production): +```yaml +server: + failover_mode: "fail_closed" +``` +- All validation requests are denied when server is unreachable +- Returns error code -32001 (Forbidden) with reason "validation_unavailable" +- Highest security, may cause availability issues + +**fail_open** (NOT RECOMMENDED): +```yaml +server: + failover_mode: "fail_open" +``` +- All requests are allowed when server is unreachable +- Logs warning: "failover_mode=fail_open triggered" +- ⚠️ Only use in development or when availability > security + +**fail_open constraints (v1alpha2)**: + +When `failover_mode: "fail_open"` is configured, implementations SHOULD require additional constraints to limit exposure: + +```yaml +server: + failover_mode: "fail_open" + fail_open_constraints: # RECOMMENDED when fail_open + allowed_tools: [] # Only these tools fail-open + max_duration: # Auto-revert to fail_closed + max_requests: # Max requests before fail_closed + alert_webhook: # Notify on fail_open activation + require_local_policy: # Must have valid local policy +``` + +| Field | Type | Description | +|-------|------|-------------| +| `allowed_tools` | []string | Only these tools are allowed during fail_open (others blocked) | +| `max_duration` | duration | Auto-revert to fail_closed after this period | +| `max_requests` | int | Auto-revert after N requests in fail_open mode | +| `alert_webhook` | string | POST notification when fail_open activates | +| `require_local_policy` | bool | Only fail_open if local policy is loaded and valid | + +**Example with constraints**: +```yaml +server: + failover_mode: "fail_open" + fail_open_constraints: + allowed_tools: + - read_file + - list_directory + max_duration: "5m" + max_requests: 100 + alert_webhook: "https://alerts.example.com/aip-failover" + require_local_policy: true +``` + +**Behavior**: +- When validation server becomes unreachable: + 1. Increment fail_open counter + 2. Check if `max_requests` exceeded → revert to fail_closed + 3. Check if `max_duration` exceeded → revert to fail_closed + 4. If request tool NOT in `allowed_tools` → block with -32001 + 5. If `require_local_policy` and no valid local policy → block with -32001 + 6. POST to `alert_webhook` (async, fire-and-forget) + 7. Allow request, log warning + +**Implementation requirements**: +- Implementations SHOULD warn at policy load time if `fail_open` is used without constraints +- Implementations MUST log every request processed in fail_open mode +- Implementations SHOULD expose a metric `aip_fail_open_requests_total` + +**local_policy** (RECOMMENDED for hybrid deployments): +```yaml +server: + failover_mode: "local_policy" +``` +- Falls back to local policy file evaluation +- Requires local policy to be loaded and valid +- Provides security with graceful degradation + +#### 3.8.4 timeout + +Maximum time to wait for validation server response. + +Format: Go duration string + +Default: `"5s"` (5 seconds) + +After timeout, the `failover_mode` behavior is triggered. + +Example: +```yaml +server: + enabled: true + timeout: "3s" # Shorter timeout for latency-sensitive apps + failover_mode: "local_policy" +``` + +#### 3.8.5 TLS Configuration + +When the listen address is not localhost (`127.0.0.1` or `::1`), TLS MUST be configured. + +```yaml +tls: + cert: "/path/to/cert.pem" + key: "/path/to/key.pem" +``` + +Implementations SHOULD support: +- PEM-encoded certificates and keys +- Let's Encrypt/ACME integration (implementation-defined) + +#### 3.8.6 Endpoints + +Customizable endpoint paths: + +| Endpoint | Default | Description | +|----------|---------|-------------| +| `validate` | `/v1/validate` | Policy validation endpoint | +| `revoke` | `/v1/revoke` | Token/session revocation (v1alpha2) | +| `jwks` | `/v1/jwks` | JSON Web Key Set for token verification (v1alpha2) | +| `health` | `/health` | Health check (for load balancers) | +| `metrics` | `/metrics` | Prometheus metrics (optional) | + +--- + +## 4. Evaluation Semantics + +*[Sections 4.1 through 4.5 remain unchanged from v1alpha1]* + +### 4.1 Name Normalization + +Tool names and method names MUST be normalized before comparison using the following algorithm: + +``` +NORMALIZE(input): + 1. Apply NFKC Unicode normalization + 2. Convert to lowercase + 3. Trim leading/trailing whitespace + 4. Remove non-printable and control characters + 5. Return result +``` + +This prevents bypass attacks using: +- Fullwidth characters: `delete` → `delete` +- Ligatures: `file` → `file` +- Zero-width characters: `dele​te` → `delete` + +### 4.2 Method-Level Authorization + +Method authorization is the FIRST line of defense, evaluated BEFORE tool-level checks. + +``` +IS_METHOD_ALLOWED(method): + normalized = NORMALIZE(method) + + IF normalized IN denied_methods: + RETURN DENY + + IF "*" IN allowed_methods: + RETURN ALLOW + + IF normalized IN allowed_methods: + RETURN ALLOW + + RETURN DENY +``` + +### 4.3 Tool-Level Authorization + +Tool authorization applies to `tools/call` requests. + +``` +IS_TOOL_ALLOWED(tool_name, arguments, token): + normalized = NORMALIZE(tool_name) + + # Step 0: Verify identity token (v1alpha2) + IF identity.require_token: + IF token IS EMPTY OR NOT valid_token(token): + RETURN TOKEN_REQUIRED + + # Step 1: Check rate limiting + IF rate_limiter_exceeded(normalized): + RETURN RATE_LIMITED + + # Step 2: Check protected paths + IF arguments_contain_protected_path(arguments): + RETURN PROTECTED_PATH + + # Step 3: Check tool rules + rule = find_rule(normalized) + IF rule EXISTS: + IF rule.action == "block": + RETURN BLOCK + IF rule.action == "ask": + IF validate_arguments(rule, arguments): + RETURN ASK + ELSE: + RETURN BLOCK + # action == "allow" falls through + + # Step 4: Check allowed_tools list + IF normalized NOT IN allowed_tools: + RETURN BLOCK + + # Step 5: Validate arguments (if rule exists) + IF rule EXISTS AND rule.allow_args NOT EMPTY: + IF NOT validate_arguments(rule, arguments): + RETURN BLOCK + + # Step 6: Strict args check + IF strict_args_enabled(rule): + IF arguments has undeclared keys: + RETURN BLOCK + + RETURN ALLOW +``` + +### 4.4 Decision Outcomes + +| Decision | Mode=enforce | Mode=monitor | +|----------|--------------|--------------| +| ALLOW | Forward request | Forward request | +| BLOCK | Return error | Forward request, log violation | +| ASK | Prompt user | Prompt user | +| RATE_LIMITED | Return error | Return error (always enforced) | +| PROTECTED_PATH | Return error | Return error (always enforced) | +| TOKEN_REQUIRED | Return error | Return error (always enforced) *(new)* | +| TOKEN_INVALID | Return error | Return error (always enforced) *(new)* | + +### 4.5 Argument Validation + +``` +VALIDATE_ARGUMENTS(rule, arguments): + FOR EACH (arg_name, pattern) IN rule.allow_args: + IF arg_name NOT IN arguments: + RETURN FALSE # Required argument missing + + value = STRING(arguments[arg_name]) + IF NOT REGEX_MATCH(pattern, value): + RETURN FALSE + + RETURN TRUE +``` + +The `STRING()` function converts values to string representation: +- String → as-is +- Number → decimal representation +- Boolean → "true" or "false" +- Null → empty string +- Array/Object → JSON serialization + +--- + +## 5. Agent Identity (v1alpha2) + +This section defines the agent identity model introduced in v1alpha2. + +### 5.1 Overview + +Agent identity provides: +1. **Session binding**: Cryptographic proof that requests belong to the same session +2. **Policy integrity**: Verification that the policy hasn't changed mid-session +3. **Replay prevention**: Nonces prevent token reuse across sessions +4. **Audit correlation**: Session IDs link related audit events + +### 5.2 Policy Hash + +The policy hash uniquely identifies a policy configuration. + +#### 5.2.1 Canonical Form + +Before hashing, the policy MUST be converted to canonical form: + +``` +CANONICALIZE(policy): + 1. Remove metadata.signature field (if present) + 2. Serialize to JSON using RFC 8785 (JSON Canonicalization Scheme) + 3. Return UTF-8 encoded bytes +``` + +#### 5.2.2 Hash Computation + +``` +POLICY_HASH(policy): + canonical = CANONICALIZE(policy) + hash = SHA-256(canonical) + RETURN hex_encode(hash) +``` + +The policy hash is a 64-character lowercase hexadecimal string. + +### 5.3 Identity Token Structure + +An AIP Identity Token is a JWT-like structure (but NOT necessarily JWT-encoded) with the following fields: + +```json +{ + "version": "aip/v1alpha2", + "aud": "", + "policy_hash": "<64-char-hex>", + "session_id": "", + "agent_id": "", + "issued_at": "", + "expires_at": "", + "nonce": "", + "binding": { + "process_id": , + "policy_path": "", + "hostname": "" + } +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `version` | string | Token format version (`aip/v1alpha2`) | +| `aud` | string | Intended audience (from `identity.audience` or `metadata.name`) | +| `policy_hash` | string | SHA-256 hash of canonical policy | +| `session_id` | string | UUID identifying this session | +| `agent_id` | string | Value of `metadata.name` from policy | +| `issued_at` | string | Token issuance time (ISO 8601) | +| `expires_at` | string | Token expiration time (ISO 8601) | +| `nonce` | string | Random value for replay prevention | +| `binding` | object | Session binding context (see 5.3.2) | + +#### 5.3.2 Binding Object (v1alpha2) + +The `binding` object ties tokens to their execution context: + +```json +{ + "binding": { + "process_id": 12345, + "policy_path": "/etc/aip/policy.yaml", + "hostname": "worker-node-1.example.com", + "container_id": "abc123def456", + "pod_uid": "550e8400-e29b-41d4-a716-446655440000" + } +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `process_id` | int | Yes | OS process ID | +| `policy_path` | string | Yes | Absolute path to policy file | +| `hostname` | string | Yes | Normalized hostname (see below) | +| `container_id` | string | No | Container ID (Docker/containerd) | +| `pod_uid` | string | No | Kubernetes pod UID | + +**Hostname Normalization (v1alpha2)**: + +Hostnames MUST be normalized for consistent binding: + +``` +NORMALIZE_HOSTNAME(): + # Priority order (use first available): + + # 1. Kubernetes pod UID (most stable in k8s) + IF env.POD_UID exists: + RETURN "k8s:" + env.POD_UID + + # 2. Container ID (stable within container lifecycle) + IF running_in_container(): + container_id = read_container_id() # /proc/1/cpuset or cgroup + RETURN "container:" + container_id[0:12] + + # 3. FQDN (prefer over short hostname) + IF gethostname() contains ".": + RETURN lowercase(gethostname()) + + # 4. Short hostname + domain from resolv.conf + hostname = lowercase(gethostname()) + IF /etc/resolv.conf contains "search" or "domain": + domain = first_search_domain() + RETURN hostname + "." + domain + + # 5. Fallback to short hostname + RETURN hostname +``` + +**Environment-specific binding**: + +| Environment | `hostname` Value | Additional Fields | +|-------------|------------------|-------------------| +| Bare metal | FQDN | - | +| VM | FQDN | - | +| Docker | `container:` | `container_id` | +| Kubernetes | `k8s:` | `pod_uid`, `container_id` | +| Serverless | `lambda:` | Implementation-defined | + +**Kubernetes deployment**: + +For Kubernetes deployments, inject pod UID via downward API: + +```yaml +env: + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +``` + +**Session binding modes and hostname**: + +| `session_binding` | Hostname Checked | Container ID Checked | Pod UID Checked | +|-------------------|------------------|---------------------|-----------------| +| `process` | No | No | No | +| `policy` | No | No | No | +| `strict` | Yes | Yes (if present) | Yes (if present) | + +**Strict binding in ephemeral environments**: + +⚠️ Using `session_binding: "strict"` in Kubernetes or serverless environments may cause issues: +- Pod restarts change pod UID → tokens invalid +- Horizontal scaling creates multiple instances → tokens not portable + +**Recommendation for Kubernetes**: +```yaml +identity: + session_binding: "policy" # Don't bind to ephemeral pod identity + require_token: true + audience: "https://my-mcp-server.svc.cluster.local" +``` + +#### 5.3.1 Token Encoding + +Implementations MUST encode tokens using one of the following formats: + +| Format | When to Use | Interoperability | +|--------|-------------|------------------| +| **JWT** (RFC 7519) | When `server.enabled: true` (REQUIRED) | High - standard format | +| **Compact** (Base64 JSON) | Local-only deployments | Low - AIP-specific | + +**JWT Encoding (REQUIRED for server mode)**: + +When `server.enabled: true`, tokens MUST be encoded as RFC 7519 JWTs. This ensures interoperability with external systems and standard JWT libraries. + +JWT Header: +```json +{ + "alg": "ES256", + "typ": "aip+jwt" +} +``` + +Supported signing algorithms (in order of preference): +1. `ES256` (ECDSA with P-256 and SHA-256) - RECOMMENDED for production +2. `EdDSA` (Ed25519) - RECOMMENDED for performance +3. `HS256` (HMAC-SHA256) - MAY be used only when `server.enabled: false` + +⚠️ **Security**: `HS256` requires a shared secret, which is unsuitable for distributed validation. Implementations MUST reject `HS256` tokens on server endpoints. + +**Compact Encoding (local-only)**: + +When `server.enabled: false`, implementations MAY use compact encoding: +``` +base64url(json_payload) + "." + base64url(signature) +``` + +Compact tokens MUST NOT be sent to remote validation endpoints. + +### 5.4 Token Lifecycle + +``` +┌──────────────┐ +│ Session │ +│ Start │ +└──────┬───────┘ + │ + ▼ +┌──────────────┐ ┌──────────────┐ +│ Issue │────▶│ Active │ +│ Token │ │ Token │ +└──────────────┘ └──────┬───────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ Rotation │ │ Expired │ │ Session │ +│ (new token)│ │ (reject) │ │ End │ +└──────────────┘ └──────────────┘ └──────────────┘ +``` + +#### 5.4.1 Token Issuance + +Tokens are issued when: +1. Session starts (first tool call with `identity.enabled: true`) +2. Rotation interval elapsed +3. Policy changes (new policy_hash) + +#### 5.4.2 Token Rotation + +Rotation creates a new token while the old token is still valid (grace period). + +``` +ROTATE_TOKEN(current_token): + IF current_token.expires_at - now() > rotation_grace_period: + RETURN current_token # Not yet time to rotate + + new_token = ISSUE_TOKEN( + session_id: current_token.session_id, # Preserve session + policy_hash: POLICY_HASH(current_policy), + agent_id: current_policy.metadata.name, + nonce: RANDOM_HEX(32) + ) + + RETURN new_token +``` + +#### 5.4.3 Token Validation + +``` +VALIDATE_TOKEN(token): + # Step 0: Check revocation FIRST (before any other validation) + revocation_result = CHECK_REVOCATION(token) + IF revocation_result == REVOKED: + RETURN (INVALID, revocation_result.reason) + + # Step 1: Check expiration + IF now() > token.expires_at: + RETURN (INVALID, "token_expired") + + # Step 2: Check audience (v1alpha2) + expected_audience = identity.audience OR current_policy.metadata.name + IF token.aud != expected_audience: + RETURN (INVALID, "audience_mismatch") + + # Step 3: Check policy hash + IF token.policy_hash != POLICY_HASH(current_policy): + # Check if within grace period (if configured) + IF policy_transition_grace > 0: + IF token.policy_hash IN recent_policy_hashes: + # Allow during transition + CONTINUE + RETURN (INVALID, "policy_changed") + + # Step 4: Check session binding + IF identity.session_binding == "process": + IF token.binding.process_id != current_process_id: + RETURN (INVALID, "session_mismatch") + + IF identity.session_binding == "strict": + IF token.binding != current_binding: + RETURN (INVALID, "binding_mismatch") + + # Step 5: Check nonce with bounded window (atomic operation required) + IF NOT ATOMIC_CHECK_AND_RECORD_NONCE(token.nonce, identity.nonce_window): + RETURN (INVALID, "replay_detected") + + # Step 6: Prune old nonces (may be async) + PRUNE_NONCES_OLDER_THAN(now() - identity.nonce_window) + + RETURN (VALID, nil) +``` + +### 5.5 Session Management + +#### 5.5.1 Session Start + +A session starts when: +- The AIP engine loads a policy with `identity.enabled: true` +- A new process starts with AIP configured + +#### 5.5.2 Session End + +A session ends when: +- The AIP engine process terminates +- The policy is unloaded or changed significantly +- Explicit session termination (implementation-defined) + +#### 5.5.3 Session ID + +Session IDs MUST be: +- UUID v4 (random) - RECOMMENDED +- Globally unique +- Not predictable + +### 5.6 Token and Session Revocation (v1alpha2) + +Revocation allows immediate invalidation of tokens or sessions before their natural expiration. + +#### 5.6.1 Revocation Targets + +| Target | Scope | Use Case | +|--------|-------|----------| +| **Token** (by nonce) | Single token | Suspected token compromise | +| **Session** (by session_id) | All tokens in session | User logout, session termination | + +#### 5.6.2 Revocation Storage + +Implementations MUST maintain a revocation set containing: +```json +{ + "revoked_sessions": ["", ...], + "revoked_tokens": ["", ...] +} +``` + +Storage requirements: +- Revoked sessions SHOULD be retained for `max_session_duration` (implementation-defined, default: 24h) +- Revoked tokens SHOULD be retained for `nonce_window` duration (then naturally expire) + +#### 5.6.3 Revocation Check + +Token validation MUST include revocation check: + +``` +CHECK_REVOCATION(token): + IF token.session_id IN revoked_sessions: + RETURN (REVOKED, "session_revoked") + + IF token.nonce IN revoked_tokens: + RETURN (REVOKED, "token_revoked") + + RETURN (VALID, nil) +``` + +#### 5.6.4 Local Revocation + +For local-only deployments (`server.enabled: false`), implementations SHOULD provide: +- Signal handler (e.g., `SIGUSR1`) to trigger session termination +- File-based revocation list that is polled periodically +- API for programmatic revocation (implementation-defined) + +### 5.7 Compatibility with Agentic JWT + +AIP Identity Tokens are designed to be **compatible** with the emerging Agentic JWT standard (draft-goswami-agentic-jwt-00). + +Implementations MAY support Agentic JWT by: +1. Computing `agent_checksum` from policy content +2. Including `agent_proof` claims in JWT tokens +3. Supporting the `agent_checksum` OAuth grant type + +See [Appendix D.6](#d6-agentic-jwt-compatibility) for mapping details. + +### 5.8 Key Management (v1alpha2) + +This section defines key management requirements for JWT signing when `server.enabled: true`. + +#### 5.8.1 Key Configuration + +```yaml +spec: + identity: + keys: # OPTIONAL (v1alpha2) + signing_algorithm: # OPTIONAL, default: "ES256" + key_source: # OPTIONAL, default: "generate" + key_path: # REQUIRED if key_source is "file" + rotation_period: # OPTIONAL, default: "7d" + jwks_endpoint: # OPTIONAL, default: "/v1/jwks" +``` + +| Field | Type | Description | +|-------|------|-------------| +| `signing_algorithm` | string | JWT signing algorithm (see 5.8.2) | +| `key_source` | string | `generate`, `file`, or `external` | +| `key_path` | string | Path to key file (PEM format) | +| `rotation_period` | duration | How often to rotate keys | +| `jwks_endpoint` | string | Endpoint path for JWKS (when server.enabled) | + +#### 5.8.2 Supported Algorithms + +| Algorithm | Key Type | Security | Performance | Recommendation | +|-----------|----------|----------|-------------|----------------| +| `ES256` | ECDSA P-256 | High | Fast | **Default, RECOMMENDED** | +| `ES384` | ECDSA P-384 | Higher | Medium | High-security environments | +| `EdDSA` | Ed25519 | High | Fastest | Performance-critical | +| `RS256` | RSA 2048+ | High | Slow | Legacy compatibility | +| `HS256` | HMAC | Medium | Fastest | **Local-only, NOT for server mode** | + +⚠️ **Security**: `HS256` uses symmetric keys and MUST NOT be used when `server.enabled: true`. Implementations MUST reject this configuration. + +#### 5.8.3 Key Sources + +**generate** (default): +```yaml +keys: + key_source: "generate" + rotation_period: "7d" +``` +- Implementation generates and manages keys automatically +- Private key stored in memory (RECOMMENDED) or encrypted file +- JWKS endpoint exposes public keys for verification + +**file**: +```yaml +keys: + key_source: "file" + key_path: "/etc/aip/signing-key.pem" +``` +- Key loaded from PEM file +- Implementation MUST NOT expose private key +- Key rotation requires file replacement and restart/reload + +**external** (future): +```yaml +keys: + key_source: "external" + external: + type: "vault" + address: "https://vault.example.com" + key_name: "aip-signing-key" +``` +- Keys managed by external KMS (HashiCorp Vault, AWS KMS, etc.) +- Implementation-defined integration + +#### 5.8.4 Key Rotation + +Keys SHOULD be rotated periodically to limit exposure from key compromise. + +**Rotation process**: + +``` +TIME 0: KEY_A active, KEY_A in JWKS +TIME T: KEY_B generated, KEY_A + KEY_B in JWKS +TIME T+1: KEY_B active (new tokens), KEY_A + KEY_B in JWKS +TIME T+TTL: KEY_A removed from JWKS (tokens expired) +``` + +**Requirements**: +1. New keys MUST be added to JWKS before becoming active +2. Old keys MUST remain in JWKS for at least `token_ttl` after rotation +3. Implementations MUST support at least 2 concurrent keys in JWKS + +**Configuration**: +```yaml +identity: + keys: + rotation_period: "7d" # Rotate weekly + grace_period: "1h" # Keep old key in JWKS for 1 hour extra +``` + +#### 5.8.5 JWKS Endpoint + +When `server.enabled: true`, implementations MUST expose a JWKS endpoint for token verification. + +**Request**: +```http +GET /v1/jwks HTTP/1.1 +Host: aip-server:9443 +``` + +**Response**: +```http +HTTP/1.1 200 OK +Content-Type: application/json +Cache-Control: public, max-age=3600 + +{ + "keys": [ + { + "kty": "EC", + "crv": "P-256", + "kid": "key-2026-01-24", + "use": "sig", + "alg": "ES256", + "x": "...", + "y": "..." + }, + { + "kty": "EC", + "crv": "P-256", + "kid": "key-2026-01-17", + "use": "sig", + "alg": "ES256", + "x": "...", + "y": "..." + } + ] +} +``` + +**Caching**: +- Clients SHOULD cache JWKS responses +- `Cache-Control` header SHOULD indicate TTL (default: 1 hour) +- Clients MUST refresh JWKS when encountering unknown `kid` + +#### 5.8.6 Key Compromise Response + +If a signing key is compromised: + +1. **Immediate**: Remove compromised key from JWKS +2. **Generate**: Create new signing key +3. **Revoke**: Revoke all sessions that used compromised key +4. **Rotate**: Force token rotation for all active sessions +5. **Audit**: Log compromise event with forensic details + +**Emergency key revocation endpoint** (implementation-defined): + +```http +POST /v1/keys/revoke HTTP/1.1 +Host: aip-server:9443 +Authorization: Bearer +Content-Type: application/json + +{ + "kid": "key-2026-01-17", + "reason": "Key compromise detected", + "revoke_sessions": true +} +``` + +⚠️ **This is a destructive operation** that invalidates all tokens signed with the specified key. + +--- + +## 6. Server-Side Validation (v1alpha2) + +This section defines the optional HTTP server for remote policy validation. + +### 6.1 Overview + +The AIP server provides: +1. **Remote validation**: Validate tool calls from external systems +2. **Health checks**: Integration with load balancers and orchestrators +3. **Metrics**: Prometheus-compatible metrics export + +### 6.2 Validation Endpoint + +#### 6.2.1 Request Format + +```http +POST /v1/validate HTTP/1.1 +Host: aip-server:9443 +Content-Type: application/json +Authorization: Bearer + +{ + "tool": "", + "arguments": { ... } +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `tool` | string | Yes | Tool name to validate | +| `arguments` | object | Yes | Tool arguments | + +**Token Transmission (RFC 6750 compliant)**: + +The identity token MUST be transmitted in the `Authorization` header using the Bearer scheme: + +```http +Authorization: Bearer +``` + +Implementations MUST NOT accept tokens in: +- Request body parameters +- Query string parameters +- Cookies + +This prevents: +- Token leakage via access logs (query strings) +- CSRF attacks (body parameters) +- Cross-origin token theft (cookies) + +When `identity.require_token: true`, requests without a valid Authorization header MUST be rejected with HTTP 401. + +#### 6.2.2 Response Format + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "decision": "allow|block|ask", + "reason": "", + "violations": [ + { + "type": "", + "field": "", + "message": "" + } + ], + "token_status": { + "valid": true, + "expires_in": 240 + } +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `decision` | string | `allow`, `block`, or `ask` | +| `reason` | string | Human-readable explanation | +| `violations` | array | List of policy violations (if any) | +| `token_status` | object | Token validity information (if token provided) | + +#### 6.2.3 Error Responses + +| HTTP Status | Error Code | Description | +|-------------|------------|-------------| +| 400 | `invalid_request` | Malformed request body | +| 401 | `token_required` | Token required but not provided | +| 401 | `token_invalid` | Token validation failed | +| 403 | `forbidden` | Tool not allowed | +| 429 | `rate_limited` | Rate limit exceeded | +| 500 | `internal_error` | Server error | + +### 6.3 Health Endpoint + +#### 6.3.1 Request + +```http +GET /health HTTP/1.1 +Host: aip-server:9443 +``` + +#### 6.3.2 Response + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "status": "healthy", + "version": "v1alpha2", + "policy_hash": "<64-char-hex>", + "uptime_seconds": 3600 +} +``` + +| Status | HTTP Code | Description | +|--------|-----------|-------------| +| `healthy` | 200 | Server is ready | +| `degraded` | 200 | Server running with warnings | +| `unhealthy` | 503 | Server not ready | + +### 6.4 Metrics Endpoint + +When enabled, the metrics endpoint exposes Prometheus-compatible metrics. + +#### 6.4.1 Request + +```http +GET /metrics HTTP/1.1 +Host: aip-server:9443 +``` + +#### 6.4.2 Metrics + +| Metric | Type | Description | +|--------|------|-------------| +| `aip_requests_total` | counter | Total validation requests | +| `aip_decisions_total` | counter | Decisions by type (allow/block/ask) | +| `aip_violations_total` | counter | Policy violations by type | +| `aip_token_validations_total` | counter | Token validations (valid/invalid) | +| `aip_revocations_total` | counter | Revocation events by type (session/token) | +| `aip_active_sessions` | gauge | Currently active sessions | +| `aip_request_duration_seconds` | histogram | Request latency | +| `aip_policy_hash` | gauge | Current policy hash (as label) | + +### 6.5 Revocation Endpoint (v1alpha2) + +The revocation endpoint allows immediate invalidation of tokens or sessions. + +#### 6.5.1 Request Format + +```http +POST /v1/revoke HTTP/1.1 +Host: aip-server:9443 +Content-Type: application/json +Authorization: Bearer + +{ + "type": "session|token", + "session_id": "", // Required if type=session + "token_nonce": "", // Required if type=token + "reason": "" // OPTIONAL +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `type` | string | Yes | `session` or `token` | +| `session_id` | string | Conditional | Session UUID (required if type=session) | +| `token_nonce` | string | Conditional | Token nonce (required if type=token) | +| `reason` | string | No | Audit trail reason | + +#### 6.5.2 Response Format + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "revoked": true, + "type": "session", + "target": "550e8400-e29b-41d4-a716-446655440000", + "revoked_at": "2026-01-24T10:30:00.000Z" +} +``` + +#### 6.5.3 Error Responses + +| HTTP Status | Error Code | Description | +|-------------|------------|-------------| +| 400 | `invalid_request` | Missing required fields | +| 401 | `unauthorized` | Admin authentication required | +| 404 | `not_found` | Session or token not found | +| 500 | `internal_error` | Server error | + +#### 6.5.4 Authorization + +The revocation endpoint MUST require elevated privileges: +- Separate admin token (not user identity token) +- mTLS with admin certificate +- Operator API key + +⚠️ **Security**: Revocation is a privileged operation. Do not allow agents to revoke their own or other sessions. + +#### 6.5.5 Audit Logging + +Revocation events MUST be logged: + +```json +{ + "timestamp": "2026-01-24T10:30:00.000Z", + "event": "REVOCATION", + "type": "session", + "target": "550e8400-e29b-41d4-a716-446655440000", + "reason": "Suspected compromise", + "admin": "operator@example.com" +} +``` + +### 6.6 Authentication + +The validation endpoint SHOULD be protected. Implementations MUST support: +- **Bearer tokens**: AIP Identity Tokens in Authorization header +- **mTLS**: Mutual TLS for service-to-service authentication + +Implementations MAY support: +- API keys +- OAuth 2.0 tokens (for integration with external IdPs) + +--- + +## 7. Error Codes + +AIP defines the following JSON-RPC error codes: + +| Code | Name | Description | +|------|------|-------------| +| -32001 | Forbidden | Tool not in allowed_tools list | +| -32002 | Rate Limited | Rate limit exceeded | +| -32004 | User Denied | User rejected approval prompt | +| -32005 | User Timeout | Approval prompt timed out | +| -32006 | Method Not Allowed | JSON-RPC method not permitted | +| -32007 | Protected Path | Access to protected path blocked | +| -32008 | Token Required | Identity token required but not provided *(new)* | +| -32009 | Token Invalid | Identity token validation failed *(new)* | +| -32010 | Policy Signature Invalid | Policy signature verification failed *(new)* | +| -32011 | Token Revoked | Token or session explicitly revoked *(new)* | +| -32012 | Audience Mismatch | Token audience does not match expected value *(new)* | +| -32013 | Schema Mismatch | Tool schema hash does not match policy *(new)* | +| -32014 | DLP Redaction Failed | Request redaction produced invalid content *(new)* | + +### 7.1 Error Response Format + +```json +{ + "jsonrpc": "2.0", + "id": , + "error": { + "code": , + "message": "", + "data": { + "tool": "", + "reason": "" + } + } +} +``` + +### 7.2 New Error Codes (v1alpha2) + +#### -32008 Token Required + +Returned when `identity.require_token: true` and no token is provided. + +```json +{ + "code": -32008, + "message": "Token required", + "data": { + "tool": "file_write", + "reason": "Identity token required for this policy" + } +} +``` + +#### -32009 Token Invalid + +Returned when token validation fails. + +```json +{ + "code": -32009, + "message": "Token invalid", + "data": { + "tool": "file_write", + "reason": "Token expired", + "token_error": "token_expired" + } +} +``` + +Possible `token_error` values: +- `token_expired` - Token past expiration time +- `policy_changed` - Policy hash mismatch +- `session_mismatch` - Session binding mismatch +- `binding_mismatch` - Strict binding validation failed +- `replay_detected` - Nonce reuse detected +- `audience_mismatch` - Token audience does not match expected value *(new)* +- `malformed` - Token structure invalid + +**Note**: `token_revoked` errors use the dedicated -32011 error code for clearer operational distinction. + +#### -32010 Policy Signature Invalid + +Returned when policy signature verification fails. + +```json +{ + "code": -32010, + "message": "Policy signature invalid", + "data": { + "policy": "production-agent", + "reason": "Signature verification failed" + } +} +``` + +#### -32011 Token Revoked (v1alpha2) + +Returned when a token or its session has been explicitly revoked via the revocation endpoint. + +```json +{ + "code": -32011, + "message": "Token revoked", + "data": { + "tool": "file_write", + "reason": "Session revoked by administrator", + "revoked_at": "2026-01-24T10:30:00.000Z", + "revocation_type": "session" + } +} +``` + +Possible `revocation_type` values: +- `session` - Entire session was revoked (all tokens invalid) +- `token` - Specific token was revoked (by nonce) + +**Operational note**: Error -32011 is distinct from -32009 to enable security teams to differentiate between normal token lifecycle events (expiration) and security incident responses (revocation). + +#### -32012 Audience Mismatch (v1alpha2) + +Returned when the token's `aud` claim does not match the expected audience. + +```json +{ + "code": -32012, + "message": "Audience mismatch", + "data": { + "tool": "file_write", + "reason": "Token not valid for this service", + "expected_audience": "https://mcp.example.com", + "token_audience": "https://other-mcp.example.com" + } +} +``` + +**Security note**: This error indicates a possible token misuse or attack. The `token_audience` value SHOULD be logged for forensics but MAY be omitted from client responses to prevent information disclosure. + +#### -32013 Schema Mismatch (v1alpha2) + +Returned when a tool's schema hash does not match the expected value in the policy. + +```json +{ + "code": -32013, + "message": "Schema mismatch", + "data": { + "tool": "read_file", + "reason": "Tool schema has changed since policy was created", + "expected_hash": "sha256:a3c7f2e8...", + "actual_hash": "sha256:b4d8e3f9..." + } +} +``` + +**Security note**: This error indicates a potential tool poisoning attack or uncontrolled tool update. Implementations SHOULD: +1. Alert security teams immediately +2. Log full schema details for forensic analysis +3. Consider blocking the MCP server until verified + +--- + +## 8. Audit Log Format + +*[Section 8.1-8.3 remain unchanged from v1alpha1]* + +### 8.1 Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `timestamp` | ISO 8601 | Time of the decision | +| `direction` | string | `upstream` (client→server) or `downstream` (server→client) | +| `decision` | string | `ALLOW`, `BLOCK`, `ALLOW_MONITOR`, `RATE_LIMITED` | +| `policy_mode` | string | `enforce` or `monitor` | +| `violation` | boolean | Whether a policy violation was detected | + +### 8.2 Optional Fields + +| Field | Type | Description | +|-------|------|-------------| +| `method` | string | JSON-RPC method name | +| `tool` | string | Tool name (for tools/call) | +| `args` | object | Tool arguments (SHOULD be redacted) | +| `failed_arg` | string | Argument that failed validation | +| `failed_rule` | string | Regex pattern that failed | +| `session_id` | string | Session identifier *(new)* | +| `token_id` | string | Token nonce *(new)* | +| `policy_hash` | string | Policy hash at decision time *(new)* | + +### 8.3 Example + +```json +{ + "timestamp": "2026-01-24T10:30:45.123Z", + "direction": "upstream", + "method": "tools/call", + "tool": "delete_file", + "args": {"path": "/etc/passwd"}, + "decision": "BLOCK", + "policy_mode": "enforce", + "violation": true, + "failed_arg": "path", + "failed_rule": "^/home/.*", + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "policy_hash": "a3c7f2e8d9b4f1e2c8a7d6f3e9b2c4f1a8e7d3c2b5f4e9a7c3d8f2b6e1a9c4f7" +} +``` + +### 8.4 Identity Events (v1alpha2) + +Identity-related events SHOULD be logged: + +#### Token Issued + +```json +{ + "timestamp": "2026-01-24T10:30:00.000Z", + "event": "TOKEN_ISSUED", + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "token_id": "abc123def456", + "expires_at": "2026-01-24T10:35:00.000Z", + "policy_hash": "a3c7f2e8..." +} +``` + +#### Token Rotated + +```json +{ + "timestamp": "2026-01-24T10:34:00.000Z", + "event": "TOKEN_ROTATED", + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "old_token_id": "abc123def456", + "new_token_id": "xyz789ghi012", + "expires_at": "2026-01-24T10:39:00.000Z" +} +``` + +#### Token Validation Failed + +```json +{ + "timestamp": "2026-01-24T10:36:00.000Z", + "event": "TOKEN_VALIDATION_FAILED", + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "token_id": "abc123def456", + "error": "token_expired" +} +``` + +--- + +## 9. Conformance + +### 9.1 Conformance Levels + +| Level | Requirements | +|-------|--------------| +| **Basic** | Method authorization, tool allowlist, error codes | +| **Full** | Basic + argument validation, rate limiting, DLP, audit logging | +| **Extended** | Full + Human-in-the-Loop (action=ask) | +| **Identity** | Full + Identity tokens, session management *(new)* | +| **Server** | Identity + Server-side validation endpoints *(new)* | + +### 9.2 Conformance Testing + +Implementations MUST pass the conformance test suite to claim AIP compliance. + +The test suite consists of: +1. **Schema validation tests**: Verify policy parsing +2. **Decision tests**: Input → expected decision +3. **Normalization tests**: Verify Unicode handling +4. **Error format tests**: Verify JSON-RPC errors +5. **Identity tests**: Token lifecycle, rotation, validation *(new)* +6. **Server tests**: HTTP endpoint behavior *(new)* + +See `spec/conformance/` for test vectors. + +### 9.3 Implementation Requirements + +Implementations MUST: +- Parse `apiVersion: aip.io/v1alpha2` documents +- Reject documents with unknown `apiVersion` +- Apply NFKC normalization to names +- Return specified error codes +- Support `enforce` and `monitor` modes + +Implementations SHOULD: +- Log decisions in the specified format +- Support DLP scanning +- Support rate limiting +- Support identity tokens (for Identity conformance level) + +Implementations MAY: +- Use any regex engine with RE2 semantics +- Implement additional security features (egress control, sandboxing) +- Implement server-side validation (for Server conformance level) + +--- + +## 10. Security Considerations + +### 10.0 Threat Model + +This section defines the security assumptions and threat model for AIP. + +#### 10.0.1 Trust Boundaries + +AIP defines the following trust boundaries: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ UNTRUSTED │ +│ ┌──────────┐ │ +│ │ Agent │ AI agent may be manipulated via prompt injection │ +│ └────┬─────┘ │ +│ │ │ +├───────┼─────────────────────────────────────────────────────────┤ +│ │ TRUST BOUNDARY (AIP) │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ AIP Policy │ Policy engine is TRUSTED │ +│ │ Engine │ Policy file integrity assumed │ +│ └──────┬───────┘ │ +│ │ │ +├─────────┼───────────────────────────────────────────────────────┤ +│ │ TRUST BOUNDARY (MCP) │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ MCP Server │ Server behavior is UNTRUSTED │ +│ │ │ Tool definitions may be malicious │ +│ └──────────────┘ │ +│ UNTRUSTED │ +└─────────────────────────────────────────────────────────────────┘ +``` + +| Component | Trust Level | Rationale | +|-----------|-------------|-----------| +| **User** | Trusted | Defines policy, approves sensitive operations | +| **Policy file** | Trusted | Integrity verified via signature (when present) | +| **AIP Engine** | Trusted | Assumed correctly implemented | +| **Agent (LLM)** | Untrusted | Subject to prompt injection, jailbreaks | +| **MCP Server** | Untrusted | May be malicious or compromised | +| **Tool definitions** | Untrusted | May contain poisoned descriptions | +| **External resources** | Untrusted | May contain indirect prompt injections | + +#### 10.0.2 Threats In Scope + +AIP is designed to mitigate the following threats: + +| Threat | Attack Vector | AIP Mitigation | +|--------|---------------|----------------| +| **Unauthorized tool access** | Agent calls tools outside intended scope | `allowed_tools` allowlist, fail-closed default | +| **Argument manipulation** | Agent passes malicious arguments | `allow_args` regex validation, `strict_args` | +| **Privilege escalation** | Agent accesses sensitive files | `protected_paths`, path expansion | +| **Data exfiltration (response)** | Sensitive data in tool responses | DLP scanning with redaction | +| **Resource exhaustion** | Agent floods tool calls | Rate limiting per tool | +| **Policy bypass (Unicode)** | Homoglyph attacks on tool names | NFKC normalization | +| **Session hijacking** | Stolen token reuse | Session binding, nonce tracking | +| **Policy tampering** | Agent modifies policy | Protected paths, signature verification | +| **Replay attacks** | Reuse of captured tokens | Nonce validation, short TTL | + +#### 10.0.3 Threats Out of Scope + +The following threats are explicitly **not addressed** by this specification: + +| Threat | Reason | Potential Future Extension | +|--------|--------|---------------------------| +| **Network egress** | Platform-specific enforcement | Appendix D.1 | +| **Tool poisoning** | ✅ **Addressed in v1alpha2** via `schema_hash` | Section 3.5.4 | +| **Rug pull attacks** | Requires runtime behavior attestation | Future: tool attestation | +| **Subprocess sandboxing** | OS-specific | Implementation-defined | +| **Hardware tampering** | Physical security | Out of scope | +| **Side-channel attacks** | Implementation-specific | Out of scope | +| **Prompt injection prevention** | LLM-level defense | Complementary to AIP | + +#### 10.0.4 Security Assumptions + +AIP makes the following assumptions: + +1. **Policy integrity**: The policy file has not been tampered with at load time (verified via signature when `metadata.signature` is present) +2. **Engine integrity**: The AIP implementation is correct and not compromised +3. **Cryptographic security**: SHA-256, Ed25519, and other algorithms remain secure +4. **Clock accuracy**: System clocks are reasonably synchronized (within TTL tolerance) +5. **TLS security**: Transport encryption prevents eavesdropping and tampering + +#### 10.0.5 Defense in Depth + +AIP implements multiple layers of defense: + +``` +Request Flow: + + Agent Request + │ + ▼ + ┌────────────────┐ + │ 1. Method │ Block unauthorized JSON-RPC methods + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 2. Identity │ Validate token, session binding + │ Check │ (v1alpha2) + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 3. Rate Limit │ Prevent resource exhaustion + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 4. Tool │ Allowlist enforcement + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 5. Argument │ Regex validation, protected paths + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 6. HITL │ Human approval for sensitive ops + │ (if ask) │ + └───────┬────────┘ + │ + ▼ + MCP Server + │ + ▼ + ┌────────────────┐ + │ 7. DLP │ Redact sensitive response data + │ Scan │ + └───────┬────────┘ + │ + ▼ + Agent Response +``` + +### 10.1 Policy File Protection + +The policy file itself MUST be protected from modification by the agent. Implementations MUST automatically add the policy file path to `protected_paths`. + +### 10.2 Regex Denial of Service (ReDoS) + +Implementations MUST use a regex engine that guarantees linear-time matching (RE2 or equivalent). Pathological patterns like `(a+)+$` MUST NOT cause exponential execution time. + +### 10.3 Unicode Normalization + +Implementations MUST apply NFKC normalization to prevent homoglyph attacks. However, implementers should be aware that NFKC does not normalize all visually similar characters (e.g., Cyrillic 'а' vs Latin 'a'). + +### 10.4 Monitor Mode Risks + +Monitor mode allows all requests through. Implementations SHOULD warn users when monitor mode is enabled in production environments. + +### 10.5 Audit Log Integrity + +Audit logs SHOULD be written to a location not writable by the agent. Implementations MAY support log signing or forwarding to external systems. + +### 10.6 Identity Token Security (v1alpha2) + +#### 10.6.1 Token Storage + +Identity tokens SHOULD be stored in memory only, not persisted to disk. If persistence is required, tokens MUST be encrypted at rest. + +#### 10.6.2 Token Transmission + +Tokens transmitted over the network MUST use TLS 1.2 or later. Implementations MUST NOT send tokens over unencrypted connections. + +#### 10.6.3 Token Lifetime + +Short token lifetimes (5-15 minutes) limit the window for token theft. Implementations SHOULD NOT allow token_ttl greater than 1 hour. + +#### 10.6.4 Replay Prevention + +Implementations MUST track nonces to prevent token replay within the `nonce_window` duration. + +**Atomic Operation Requirement (v1alpha2)**: + +Nonce validation MUST be performed as an **atomic check-and-record** operation to prevent race conditions in concurrent environments: + +``` +ATOMIC_CHECK_AND_RECORD_NONCE(nonce, window): + # This MUST be atomic - no gap between check and record + # Implementation options: + # - Redis: SET nonce 1 NX EX window_seconds + # - PostgreSQL: INSERT ... ON CONFLICT DO NOTHING + # - In-memory: sync.Map with CompareAndSwap + + IF ATOMIC_SET_IF_NOT_EXISTS(nonce, ttl=window): + RETURN TRUE # Nonce was new, now recorded + ELSE: + RETURN FALSE # Nonce already existed (replay attempt) +``` + +⚠️ **Critical**: Non-atomic check-then-record implementations have a race condition window where concurrent requests with the same nonce could both pass validation. + +**Storage strategies**: + +| Strategy | Pros | Cons | Recommended For | +|----------|------|------|-----------------| +| In-memory (sync.Map) | Fast, simple | Lost on restart | Single-instance, short TTL | +| Redis (SET NX EX) | Atomic, distributed | Latency, dependency | Multi-instance (RECOMMENDED) | +| PostgreSQL (INSERT ON CONFLICT) | Atomic, durable | Higher latency | Multi-instance with DB | +| Bloom filter | Space efficient | False positives, no TTL | NOT RECOMMENDED | + +**Nonce pruning**: + +Implementations MUST prune nonces older than `nonce_window` to bound storage: + +``` +MAX_NONCES = (expected_requests_per_second * nonce_window_seconds) +``` + +Example: 100 req/s with 5m window = 30,000 nonces maximum. + +**Distributed deployment requirements**: + +In multi-instance deployments: + +1. **Shared storage is REQUIRED** - Local-only nonce tracking allows replay across instances +2. **Atomic operations are REQUIRED** - Use storage primitives that guarantee atomicity (Redis `SET NX`, DB unique constraints) +3. **TTL-based expiration** - Set storage TTL to `nonce_window + clock_skew_tolerance` (recommended: 30 seconds tolerance) +4. **Clock synchronization** - All instances SHOULD use NTP with drift < 1 second + +**Configuration for distributed deployments**: + +```yaml +identity: + enabled: true + nonce_window: "5m" + nonce_storage: # OPTIONAL (v1alpha2) + type: "redis" # redis | postgres | memory + address: "redis://localhost:6379" + key_prefix: "aip:nonce:" + clock_skew_tolerance: "30s" # Added to TTL for safety +``` + +A token with a previously-seen nonce MUST be rejected with error code -32009 (`replay_detected`). + +#### 10.6.5 Session Binding + +Session binding prevents stolen tokens from being used in different contexts. The `strict` binding mode provides the strongest guarantees but may cause issues with process restarts. + +### 10.7 Server Endpoint Security (v1alpha2) + +#### 10.7.1 Authentication + +Validation endpoints MUST require authentication. Unauthenticated endpoints allow attackers to probe policy configurations. + +#### 10.7.2 Rate Limiting + +Validation endpoints SHOULD implement rate limiting to prevent denial of service attacks. + +#### 10.7.3 Information Disclosure + +Error responses SHOULD NOT reveal detailed policy configuration. The `reason` field SHOULD provide minimal information needed to diagnose issues. + +--- + +## 11. IANA Considerations + +This specification requests registration of the following: + +### 11.1 Media Type + +- Type name: application +- Subtype name: vnd.aip.policy+yaml +- Required parameters: None +- File extension: .yaml, .yml + +### 11.2 URI Scheme + +This specification uses the `aip.io` namespace for versioning: +- `aip.io/v1alpha1` - Previous specification +- `aip.io/v1alpha2` - This specification + +--- + +## Appendix A: Complete Schema Reference + +```yaml +# Complete AgentPolicy schema (v1alpha2) + +apiVersion: aip.io/v1alpha2 # REQUIRED +kind: AgentPolicy # REQUIRED + +metadata: # REQUIRED + name: string # REQUIRED - Policy identifier + version: string # OPTIONAL - Semantic version + owner: string # OPTIONAL - Contact email + signature: string # OPTIONAL - Policy signature (v1alpha2) + +spec: # REQUIRED + mode: enforce | monitor # OPTIONAL, default: enforce + + allowed_tools: # OPTIONAL + - string + + allowed_methods: # OPTIONAL + - string + + denied_methods: # OPTIONAL + - string + + protected_paths: # OPTIONAL + - string + + strict_args_default: boolean # OPTIONAL, default: false + + tool_rules: # OPTIONAL + - tool: string # REQUIRED + action: allow|block|ask # OPTIONAL, default: allow + rate_limit: string # OPTIONAL, format: "N/period" + strict_args: boolean # OPTIONAL + schema_hash: string # OPTIONAL - Tool schema integrity (v1alpha2) + allow_args: # OPTIONAL + : + + dlp: # OPTIONAL + enabled: boolean # OPTIONAL, default: true + scan_requests: boolean # OPTIONAL, default: false (v1alpha2) + scan_responses: boolean # OPTIONAL, default: true (v1alpha2) + detect_encoding: boolean # OPTIONAL, default: false + filter_stderr: boolean # OPTIONAL, default: false + max_scan_size: string # OPTIONAL, default: "1MB" (v1alpha2) + on_request_match: string # OPTIONAL, default: "block" (v1alpha2) + on_redaction_failure: string # OPTIONAL, default: "block" (v1alpha2) + log_original_on_failure: boolean # OPTIONAL, default: false (v1alpha2) + patterns: # REQUIRED if dlp present + - name: string # REQUIRED + regex: string # REQUIRED + scope: string # OPTIONAL, default: "all" (v1alpha2) + + identity: # OPTIONAL (v1alpha2) + enabled: boolean # OPTIONAL, default: false + token_ttl: string # OPTIONAL, default: "5m" + rotation_interval: string # OPTIONAL, default: "4m" (must be < token_ttl) + require_token: boolean # OPTIONAL, default: false + session_binding: string # OPTIONAL, default: "process" + nonce_window: string # OPTIONAL, default: equals token_ttl + policy_transition_grace: string # OPTIONAL, default: "0s" + audience: string # OPTIONAL, default: metadata.name + nonce_storage: # OPTIONAL (v1alpha2) + type: string # memory | redis | postgres + address: string # Connection string (if not memory) + key_prefix: string # default: "aip:nonce:" + clock_skew_tolerance: string # default: "30s" + keys: # OPTIONAL (v1alpha2) + signing_algorithm: string # default: "ES256" + key_source: string # generate | file | external + key_path: string # Required if key_source is "file" + rotation_period: string # default: "7d" + jwks_endpoint: string # default: "/v1/jwks" + + server: # OPTIONAL (v1alpha2) + enabled: boolean # OPTIONAL, default: false + listen: string # OPTIONAL, default: "127.0.0.1:9443" + failover_mode: string # OPTIONAL, default: "fail_closed" + timeout: string # OPTIONAL, default: "5s" + tls: # OPTIONAL + cert: string # Path to TLS certificate + key: string # Path to TLS private key + fail_open_constraints: # OPTIONAL (recommended if fail_open) + allowed_tools: # Only these tools fail-open + - string + max_duration: string # Auto-revert after duration + max_requests: integer # Auto-revert after N requests + alert_webhook: string # Notify on fail_open activation + require_local_policy: boolean # Require valid local policy + endpoints: # OPTIONAL + validate: string # default: "/v1/validate" + revoke: string # default: "/v1/revoke" + jwks: string # default: "/v1/jwks" (v1alpha2) + health: string # default: "/health" + metrics: string # default: "/metrics" +``` + +--- + +## Appendix B: Changelog + +### v1alpha2 (2026-01-24) + +**Identity and Session Management** +- Added `identity` configuration section + - Token generation and rotation with configurable TTL + - Session binding (`process`, `policy`, `strict`) + - Policy hash computation for integrity + - `nonce_window` for bounded replay prevention storage + - `policy_transition_grace` for gradual policy rollouts + - `audience` for token audience binding (RFC 8707 alignment) + - `nonce_storage` for distributed nonce tracking (Redis, PostgreSQL) + - `keys` for JWT signing key management and rotation +- Added token revocation mechanism (Section 5.6) + - Session and token-level revocation + - Revocation storage and pruning +- Added Section 5.8 Key Management + - Signing algorithm selection (ES256, EdDSA, RS256) + - Key rotation with grace periods + - JWKS endpoint for remote verification + - Key compromise response procedures +- Added Section 5.3.2 Binding Object + - Hostname normalization for containers and Kubernetes + - Container ID and Pod UID binding support + +**Server-Side Validation** +- Added `server` configuration section + - HTTP validation endpoint (`/v1/validate`) + - Revocation endpoint (`/v1/revoke`) + - JWKS endpoint (`/v1/jwks`) for key distribution + - Health and metrics endpoints + - `failover_mode`: `fail_closed`, `fail_open`, `local_policy` + - `fail_open_constraints` for safer fail_open deployments + - Configurable `timeout` for validation requests +- Mandated JWT encoding when `server.enabled: true` +- Token transmission via Authorization header only (RFC 6750) + +**Tool Security** +- Added `schema_hash` to tool_rules (Section 3.5.4) + - Cryptographic verification of tool definitions + - Tool poisoning attack prevention + - SHA-256/384/512 algorithm support + +**DLP Enhancements** +- Added `scan_requests` for request-side DLP scanning +- Added `max_scan_size` to prevent ReDoS +- Added `on_request_match` action (`block`, `redact`, `warn`) +- Added `on_redaction_failure` handling (`block`, `allow_original`, `reject`) +- Added `log_original_on_failure` for forensics +- Added `scope` to patterns (`request`, `response`, `all`) + +**Security** +- Added Section 10.0 Threat Model + - Trust boundaries diagram + - Threats in scope / out of scope + - Defense in depth layers +- Added `metadata.signature` for policy integrity (Ed25519) +- Atomic nonce operations required for replay prevention +- Tool poisoning now addressed via schema hashing +- Enhanced replay prevention documentation with distributed storage + +**Configuration Validation** +- Added rotation_interval validation (must be < token_ttl) +- Policy load failures for invalid configurations + +**Error Codes** +- Added -32008 Token Required +- Added -32009 Token Invalid (with detailed error types) +- Added -32010 Policy Signature Invalid +- Added -32011 Token Revoked (distinct from -32009) +- Added -32012 Audience Mismatch +- Added -32013 Schema Mismatch (tool poisoning detection) +- Added -32014 DLP Redaction Failed + +**Conformance** +- Added Identity conformance level +- Added Server conformance level +- Added identity and server tests to conformance suite + +### v1alpha1 (2026-01-20) + +- Initial draft specification +- Defined core policy schema +- Defined evaluation semantics +- Defined error codes +- Defined audit log format + +--- + +## Appendix C: References + +- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) +- [MCP Authorization (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) +- [RFC 2119 - Key words for use in RFCs](https://www.rfc-editor.org/rfc/rfc2119) +- [RFC 7519 - JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) +- [RFC 8785 - JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785) +- [Unicode NFKC Normalization](https://unicode.org/reports/tr15/) +- [RE2 Syntax](https://github.com/google/re2/wiki/Syntax) +- [Agentic JWT (draft-goswami-agentic-jwt-00)](https://datatracker.ietf.org/doc/html/draft-goswami-agentic-jwt-00) + +--- + +## Appendix D: Future Extensions + +This appendix describes features under consideration for future versions of AIP. + +### D.1 Network Egress Control + +**Status:** Proposed for v1beta1 + +*[Content unchanged from v1alpha1]* + +### D.2 Policy Inheritance + +**Status:** Under Discussion + +Allow policies to extend base policies: + +```yaml +apiVersion: aip.io/v1beta1 +kind: AgentPolicy +metadata: + name: team-policy +spec: + extends: "org-base-policy" # Inherit from another policy + allowed_tools: + - additional_tool # Add to parent's list +``` + +### D.3 External Identity Federation + +**Status:** Proposed for v1beta1 + +Allow policies to integrate with external identity providers: + +```yaml +spec: + identity: + federation: + type: oidc + issuer: "https://accounts.google.com" + client_id: "aip-agent" + required_claims: + email_verified: true + hd: "company.com" +``` + +Supported federation types: +- `oidc` - OpenID Connect providers +- `spiffe` - SPIFFE/SPIRE workload identity + +### D.4 Telemetry and Metrics + +**Status:** Partially implemented in v1alpha2 (metrics endpoint) + +Full telemetry specification: + +```yaml +spec: + telemetry: + metrics: + enabled: true + format: "prometheus" # prometheus | otlp + traces: + enabled: true + endpoint: "http://jaeger:14268/api/traces" + format: "otlp" + sampling_rate: 0.1 +``` + +### D.5 Advanced Policy Expressions + +**Status:** Under Discussion + +Support for CEL (Common Expression Language) or Rego for complex validation: + +```yaml +tool_rules: + - tool: file_write + action: allow + when: | + args.path.startsWith("/allowed/") && + !args.path.contains("..") && + size(args.content) < 1048576 +``` + +### D.6 Agentic JWT Compatibility + +**Status:** Under Discussion for v1beta1 + +Full compatibility with the Agentic JWT specification: + +```yaml +spec: + identity: + agentic_jwt: + enabled: true + # Agent checksum computed from: + # - policy content (tools, rules) + # - metadata (name, version) + include_tool_definitions: true + # Support for workflow binding + workflow: + id: "data-processing-v1" + steps: + - analyze + - transform + - export +``` + +Mapping to Agentic JWT claims: + +| AIP Field | Agentic JWT Claim | +|-----------|-------------------| +| `policy_hash` | `agent_proof.agent_checksum` | +| `session_id` | `intent.workflow_id` | +| `metadata.name` | `sub` (subject) | +| `tool_rules` | Workflow steps | + +--- + +## Appendix E: Implementation Notes + +### E.1 Reference Implementation + +The reference implementation is available at: +https://github.com/ArangoGutierrez/agent-identity-protocol + +It provides: +- Go-based proxy (`aip-proxy`) +- Policy engine (`pkg/policy`) +- DLP scanner (`pkg/dlp`) +- Audit logger (`pkg/audit`) +- Identity manager (`pkg/identity`) *(v1alpha2)* +- HTTP server (`pkg/server`) *(v1alpha2)* + +### E.2 Testing Against Conformance Suite + +```bash +# Clone the spec repository +git clone https://github.com/ArangoGutierrez/agent-identity-protocol + +# Run conformance tests against your implementation +cd agent-identity-protocol/spec/conformance +./run-tests.sh --impl "your-aip-binary" --level "identity" +``` + +### E.3 Token Implementation Guidance + +#### Generating Secure Nonces + +```go +import "crypto/rand" + +func generateNonce() string { + b := make([]byte, 16) + rand.Read(b) + return hex.EncodeToString(b) +} +``` + +#### Computing Policy Hash + +```go +import ( + "crypto/sha256" + "encoding/json" +) + +func computePolicyHash(policy *AgentPolicy) string { + // Remove signature for hashing + policyCopy := *policy + policyCopy.Metadata.Signature = "" + + // Canonical JSON (keys sorted) + canonical, _ := json.Marshal(policyCopy) + + hash := sha256.Sum256(canonical) + return hex.EncodeToString(hash[:]) +} +``` + +### E.4 Registering Your Implementation + +Implementations that pass the conformance suite may be listed in the official registry. Submit a PR to the AIP repository with: +- Implementation name and URL +- Conformance level achieved (Basic/Full/Extended/Identity/Server) +- Platform support matrix From ad7d7a789ee97411181823bc8c016c74208c813e Mon Sep 17 00:00:00 2001 From: James <133906218+yungcero@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:29:30 -0700 Subject: [PATCH 4/4] docs: Add in alpha3 spec, update links --- docs/docs.json | 16 +- docs/layer-1-identity/identity-workflow.mdx | 152 + .../policy-reference.mdx | 4 +- docs/specs/aip-v1alpha3.mdx | 3093 +++++++++++++++++ 4 files changed, 3262 insertions(+), 3 deletions(-) create mode 100644 docs/layer-1-identity/identity-workflow.mdx rename docs/{ => layer-2-enforcement}/policy-reference.mdx (99%) create mode 100644 docs/specs/aip-v1alpha3.mdx diff --git a/docs/docs.json b/docs/docs.json index 096e44d..b562dcf 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -24,12 +24,24 @@ "group": "About AIP", "pages": [ "architecture", - "policy-reference", + { + "group": "Layer 1 - Identity Attestation", + "pages": [ + "layer-1-identity/identity-workflow" + ] + }, + { + "group": "Layer 2 - Enforcement", + "pages": [ + "layer-2-enforcement/policy-reference" + ] + }, { "group": "Specifications", "pages": [ "specs/aip-v1alpha1", - "specs/aip-v1alpha2" + "specs/aip-v1alpha2", + "specs/aip-v1alpha3" ] }, "faq" diff --git a/docs/layer-1-identity/identity-workflow.mdx b/docs/layer-1-identity/identity-workflow.mdx new file mode 100644 index 0000000..aba17c9 --- /dev/null +++ b/docs/layer-1-identity/identity-workflow.mdx @@ -0,0 +1,152 @@ +--- +title: "Identity Workflow" +--- + +Below is the proposed workflow for AIP in action. + +## Agent Registration + +``` +1. Agent generates cryptographic key pair + +2. Agent → Registry + POST /v1/agents/register + + Request Body: + { + "identity": { + "name": "customer_support_agent", + "type": "autonomous_agent", + "created_by": "joe", + "organization_id": "acme123" + }, + "provenance": { + "framework": "langchain", // do we need? + "model": "latest-model", + "build_environment": "production", + "code_hash": ABCD, + }, + "capabilities": [ + "read:customer_tickets", + "write:email_responses" + ] + "public_key": "", + } + +3. Registry validates: + - Public key format and algorithm + - Proof of private key possession (signature verification) + - Organization exists and has capacity + - Capabilities align with org policies + - Code hash is unique (prevents duplicate registrations) + +4. Registry → Agent + Response returns certificate for agent. + { + "agent_id": "1234567", + "version": "1.0.0", + "status": "registered", + "registry_url": "https://registry.viewagents.ai", + "attestation_certificate": "-----BEGIN CERTIFICATE-----\nMIIC...", + "created_at": "2026-02-09" + } + +5. Agent stores: + - agent_id + - Private key (encrypted at rest) + - Attestation cert + - Issuer public key for verification +``` + +## Token Authentication + +``` +1. Agent needs to call an API + +2. Agent → Token Issuer + POST /v1/auth/token + + + Request Body: + { + "agent_id": "abc123", + "requested_scope": ["read:customer_tickets"], + "context": { + "task_id": "task_resolve_ticket_12345", + "target_system": "company_api", + }, + "ttl": 3600 + } + +3. Token Issuer validates: + - Agent signature using stored public key + - Agent is not revoked + - Requested scope ⊆ agent capabilities + - TTL ≤ agent max_token_lifetime + - Context satisfies organizational policies + - Agent hasn't exceeded rate limits + +4. Token Issuer → Agent + Response (200 OK): + { + "access_token": "abcde", + "expires_in": 3600, + "scope": ["read:customer_tickets"], + "issued_at": "2026-02-11T11:00:00Z", + } + +5. Agent stores token in memory (never disk for security) + +6. Token Issuer logs audit event: + { + "event_type": "token_issued", + "agent_id": "abc123", + "scope": ["read:customer_tickets"], + "context": {...}, + "timestamp": "2026-02-11" + } +``` + +## Token Verification + +``` +1. Agent → Resource Server (same as above) + +2. Resource Server → Token Issuer + POST /v1/auth/verify + + Request Body: + { + "token": "abc....", + "required_scope": ["read:customer_tickets"], + "resource_context": { + "resource_type": "customer_ticket", + "resource_id": "12345", + "data_classification": "pii", + "geographic_location": "us-east" + } + } + +3. Token Issuer validates: + - All local checks (signature, expiration, scope) + - Token not revoked (authoritative check) + - Context matches agent constraints (data_residency, etc.) + - Real-time policy evaluation (new policies since token issued) + +4. Token Issuer → Resource Server + Response (200 OK): + { + "valid": true, + "agent_id": "abc123", + "agent_name": "customer_support_agent", + "agent_version": "1.0.0", + "scope": ["read:customer_tickets"], + "expires_at": "2026-02-06T12:00:00Z", + "trust_level": "verified", + "code_hash": "sha256:a3f5b8c9d2e1...", + "constraints_satisfied": true, + "warnings": [] + } + +5. Resource Server proceeds with authorization decision +``` \ No newline at end of file diff --git a/docs/policy-reference.mdx b/docs/layer-2-enforcement/policy-reference.mdx similarity index 99% rename from docs/policy-reference.mdx rename to docs/layer-2-enforcement/policy-reference.mdx index a6d0e87..3a07c22 100644 --- a/docs/policy-reference.mdx +++ b/docs/layer-2-enforcement/policy-reference.mdx @@ -1,4 +1,6 @@ -# Policy Reference +--- +title: "Policy Reference" +--- > **Note**: This is a user-friendly guide to writing AIP policies. For the formal specification, see [spec/aip-v1alpha1.md](../spec/aip-v1alpha1.md). diff --git a/docs/specs/aip-v1alpha3.mdx b/docs/specs/aip-v1alpha3.mdx new file mode 100644 index 0000000..9ccbb09 --- /dev/null +++ b/docs/specs/aip-v1alpha3.mdx @@ -0,0 +1,3093 @@ +--- +title: "AIP v1alpha3" +--- + +# Agent Identity Protocol (AIP) Specification + +**Version:** v1alpha3 +**Status:** Draft +**Last Updated:** 2026-02-19 +**Authors:** Eduardo Arango (arangogutierrez@gmail.com), James Cao (james@montcao.com) + +--- + +## Abstract + +The Agent Identity Protocol (AIP) defines a standard for identity, authentication, and policy-based authorization of AI agent tool calls. AIP enables runtime environments to enforce fine-grained access control over Model Context Protocol (MCP) tool invocations, providing a security boundary between AI agents and external resources. + +This specification defines: +1. The policy document schema (`AgentPolicy`) +2. Evaluation semantics for authorization decisions +3. Agent identity and session management +4. Server-side validation endpoints +5. **Agent Authentication Token (AAT) and the identity layer** *(new in v1alpha3)* +6. **AIP Registry and Token Issuer** *(new in v1alpha3)* +7. **User binding and delegation** *(new in v1alpha3)* +8. Error codes for denied requests +9. Audit log format for compliance + +AIP is designed to be implementation-agnostic. Any MCP-compatible runtime (Cursor, Claude Desktop, VS Code, custom implementations) can implement this specification. + +--- + +## Table of Contents + +1. [Introduction](#1-introduction) +2. [Terminology](#2-terminology) +3. [Policy Document Schema](#3-policy-document-schema) +4. [Evaluation Semantics](#4-evaluation-semantics) +5. [Agent Identity](#5-agent-identity) +6. [Server-Side Validation](#6-server-side-validation) +7. [Agent Authentication Token (AAT)](#7-agent-authentication-token-aat) *(new in v1alpha3)* +8. [AIP Registry](#8-aip-registry) *(new in v1alpha3)* +9. [Token Issuer](#9-token-issuer) *(new in v1alpha3)* +10. [User Binding and Delegation](#10-user-binding-and-delegation) *(new in v1alpha3)* +11. [Error Codes](#11-error-codes) +12. [Audit Log Format](#12-audit-log-format) +13. [Conformance](#13-conformance) +14. [Security Considerations](#14-security-considerations) +15. [IANA Considerations](#15-iana-considerations) + +**Appendices** +- [Appendix A: Complete Schema Reference](#appendix-a-complete-schema-reference) +- [Appendix B: Changelog](#appendix-b-changelog) +- [Appendix C: References](#appendix-c-references) +- [Appendix D: Future Extensions](#appendix-d-future-extensions) +- [Appendix E: Implementation Notes](#appendix-e-implementation-notes) + +--- + +## 1. Introduction + +### 1.1 Motivation + +AI agents operating through the Model Context Protocol (MCP) have access to powerful tools: file systems, databases, APIs, and cloud infrastructure. Without a policy layer, agents operate with unrestricted access to any tool the MCP server exposes. + +Today, agents are granted full permissions to API keys, secrets, and system resources, running as the user with no distinction between human and non-human actions. There is no universal way to distinguish an AI agent from a human actor. This creates systemic gaps: + +- **No audit trail** -- agent actions are indistinguishable from human actions in logs +- **No revocation** -- once an agent has credentials, there is no standard way to revoke them +- **No authorization granularity** -- access is all-or-nothing at the API key level +- **Compliance blind spots** -- SOC 2, GDPR, HIPAA, and SOX requirements are unmet for agentic actions + +AIP addresses these gaps through two interconnected layers: + +- **Layer 1 -- Identity**: Establishes *who* the agent is via cryptographic identities, Agent Authentication Tokens, and a registry-based root of trust *(formalized in v1alpha3)* +- **Layer 2 -- Enforcement**: Decides *what* the agent is allowed to do via the AIP Proxy, policy engine, DLP scanning, and audit logging + +The **Agent Authentication Token (AAT)** bridges these two layers -- issued by Layer 1, enforced by Layer 2. + +AIP introduces: +- **Capability declaration**: Explicit allowlists of permitted tools +- **Argument validation**: Regex-based constraints on tool parameters +- **Human-in-the-loop**: Interactive approval for sensitive operations +- **Audit trail**: Immutable logging of all authorization decisions +- **Agent identity**: Cryptographic binding of policies to agent sessions +- **Server-side validation**: Optional HTTP endpoints for distributed policy enforcement +- **Agent Authentication Tokens**: Signed tokens carrying agent identity, user delegation, and capabilities *(new in v1alpha3)* +- **Registry-based root of trust**: Centralized agent registration with revocation *(new in v1alpha3)* +- **User binding**: Cryptographic proof of which human authorized the agent *(new in v1alpha3)* + +### 1.2 Goals + +1. **Interoperability**: Any MCP runtime can implement AIP +2. **Simplicity**: YAML-based policies readable by security teams +3. **Defense in depth**: Multiple layers (method, tool, argument, identity, AAT) +4. **Fail-closed**: Unknown tools are denied by default +5. **Zero-trust ready**: Support for token-based identity verification +6. **Agent-human distinction**: Separate authentication for agents vs. users *(new in v1alpha3)* +7. **Cryptographic delegation**: Provable chain from user to agent to action *(new in v1alpha3)* + +### 1.3 Non-Goals + +The following are explicitly out of scope for **this version** of the specification: +- Network egress control (see [Appendix D: Future Extensions](#appendix-d-future-extensions)) +- Subprocess sandboxing (implementation-defined) +- Rate limiting algorithms (implementation-defined) +- Policy expression languages beyond regex (CEL/Rego - see [Appendix D](#d5-advanced-policy-expressions)) +- Full OIDC/SPIFFE federation (see [Appendix D](#d3-external-identity-federation)); basic integration points are defined + +### 1.4 Relationship to MCP + +AIP is designed as a security layer for MCP. It intercepts `tools/call` requests and applies policy checks before forwarding to the MCP server. + +``` +┌─────────┐ ┌─────────────┐ ┌─────────────┐ +│ Agent │────▶│ AIP Policy │────▶│ MCP Server │ +│ │◀────│ Engine │◀────│ │ +└─────────┘ └─────────────┘ └─────────────┘ + │ + ▼ + ┌─────────────┐ + │ AIP Server │ (optional) + │ Endpoint │ + └─────────────┘ +``` + +### 1.5 Relationship to MCP Authorization + +MCP defines an optional OAuth 2.1-based authorization layer (MCP 2025-06-18 and later). AIP is **complementary** to MCP authorization: + +| Concern | MCP Authorization | AIP | +|---------|-------------------|-----| +| **Scope** | Transport-level authentication | Tool-level authorization | +| **What it protects** | Access to MCP server | Access to specific tools | +| **Token type** | OAuth 2.1 access tokens | Agent Authentication Tokens (AAT) | +| **Policy language** | OAuth scopes | YAML policy documents | +| **Identity model** | User identity (OAuth) | Agent + user identity (AAT) | + +Implementations MAY use both MCP authorization (for server access) and AIP (for tool access) simultaneously. + +### 1.6 Two-Layer Architecture (v1alpha3) + +v1alpha3 formalizes the complete two-layer architecture: + +``` + LAYER 1 -- IDENTITY LAYER 2 -- ENFORCEMENT + (Who is this agent?) (What can it do?) + ++-------------------+ +-------------------+ +| AIP Registry | (Root of Trust) | AI Client | +| Registers Agents | | Cursor / Claude | +| Signs Certs | +---------+---------+ ++--------+----------+ | tool call + AAT + | Issues Attestation v + v +---------------------------+ ++-------------------+ | AIP Proxy | +| Agent Identity | | | +| Document (AID) | | 1. Verify AAT signature | <-- Registry +| (Public Key) | | 2. Check revocation list | (revocation) ++---------+---------+ | 3. Validate user binding | + | Signs Token Requests | 4. Check AAT capabilities | + v | 5. Evaluate local policy | ++-------------------+ | 6. DLP scan | +| Token Issuer | | 7. Audit log | +| Validates ID | AAT +---------+-----------------+ +| Binds User | ----------------------> | ALLOW / DENY +| Issues AAT | v ++-------------------+ +-------------------+ + | Real Tool | + | Docker/Postgres | + | GitHub / etc. | + +-------------------+ +``` + +**Flow**: +1. Agent registers with the AIP Registry, receiving a key pair and Agent Identity Document (AID) +2. When a user authorizes an agent, the Token Issuer validates the agent's identity and issues an AAT encoding agent ID, user binding, and capabilities +3. On every tool call, the AIP Proxy verifies the AAT, checks the registry revocation list, evaluates policy, performs DLP scanning, and writes an audit log entry +4. A hijacked agent fails at Layer 2 -- its AAT claims do not match the attempted action +5. A revoked agent fails at Layer 2 -- the proxy checks the registry revocation list on every call +6. A legitimate agent passes through both layers with a full audit trail tied to its verified identity + +--- + +## 2. Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +| Term | Definition | +|------|------------| +| **Agent** | An AI system that invokes MCP tools on behalf of a user | +| **Agent Identity Document (AID)** | JSON structure defining an agent's cryptographic identity *(new)* | +| **Agent Authentication Token (AAT)** | A signed token proving agent identity, user delegation, and capabilities at runtime *(new)* | +| **AIP Registry** | Central directory of registered agents and their public keys *(new)* | +| **Token Issuer** | Service that validates agent identity and issues AATs *(new)* | +| **Policy** | A document specifying authorization rules (AgentPolicy) | +| **Tool** | An MCP tool exposed by an MCP server | +| **Decision** | The result of policy evaluation: ALLOW, BLOCK, or ASK | +| **Violation** | A policy rule was triggered (may or may not block) | +| **Session** | A bounded period of agent activity with consistent identity | +| **Identity Token** | A cryptographic token binding policy to session (v1alpha2; superseded by AAT for cross-service use) | +| **Policy Hash** | SHA-256 hash of the canonical policy document | +| **User Binding** | Cryptographic proof linking an agent's actions to an authorizing user *(new)* | +| **Capability** | A declared permission encoded in an AAT (e.g., tool access, resource scope) *(new)* | +| **Delegation Chain** | The provable path from user authorization through agent to action *(new)* | +| **Revocation List** | Registry-maintained list of revoked agents, AATs, and sessions *(new)* | + +--- + +## 3. Policy Document Schema + +### 3.1 Document Structure + +An AIP policy document is a YAML file with the following top-level structure: + +```yaml +apiVersion: aip.io/v1alpha3 +kind: AgentPolicy +metadata: + name: + version: # OPTIONAL + owner: # OPTIONAL + signature: # OPTIONAL +spec: + mode: # OPTIONAL, default: "enforce" + allowed_tools: [] # OPTIONAL + allowed_methods: [] # OPTIONAL + denied_methods: [] # OPTIONAL + tool_rules: [] # OPTIONAL + protected_paths: [] # OPTIONAL + strict_args_default: # OPTIONAL, default: false + dlp: # OPTIONAL + identity: # OPTIONAL + server: # OPTIONAL + registry: # OPTIONAL (v1alpha3) + aat: # OPTIONAL (v1alpha3) +``` + +### 3.2 Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `apiVersion` | string | MUST be `aip.io/v1alpha3` | +| `kind` | string | MUST be `AgentPolicy` | +| `metadata.name` | string | Unique identifier for this policy | + +### 3.3 Metadata + +```yaml +metadata: + name: # REQUIRED - Policy identifier + version: # OPTIONAL - Semantic version (e.g., "1.0.0") + owner: # OPTIONAL - Contact email + signature: # OPTIONAL - Policy signature +``` + +#### 3.3.1 Policy Signature + +The `signature` field provides cryptographic integrity verification for the policy document. + +Format: `:` + +Supported algorithms: +- `ed25519` - Ed25519 signature (RECOMMENDED) + +Example: +```yaml +metadata: + name: production-agent + signature: "ed25519:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo..." +``` + +When present, implementations MUST verify the signature before applying the policy. Signature verification failure MUST result in policy rejection. + +The signature is computed over the **canonical form** of the policy document (see Section 5.2.1). + +### 3.4 Spec Fields + +*[Sections 3.4.1 through 3.4.6 remain unchanged from v1alpha2]* + +#### 3.4.1 mode + +Controls enforcement behavior. + +| Value | Behavior | +|-------|----------| +| `enforce` | Violations are blocked (default) | +| `monitor` | Violations are logged but allowed | + +Implementations MUST support both modes. + +#### 3.4.2 allowed_tools + +A list of tool names that the agent MAY invoke. + +```yaml +allowed_tools: + - github_get_repo + - read_file + - list_directory +``` + +Tool names are subject to normalization (see Section 4.1). + +#### 3.4.3 allowed_methods + +A list of JSON-RPC methods that are permitted. If not specified, implementations MUST use the default safe list: + +```yaml +# Default allowed methods (when not specified) +allowed_methods: + - initialize + - initialized + - ping + - tools/call + - tools/list + - completion/complete + - notifications/initialized + - notifications/progress + - notifications/message + - notifications/resources/updated + - notifications/resources/list_changed + - notifications/tools/list_changed + - notifications/prompts/list_changed + - cancelled +``` + +The wildcard `*` MAY be used to allow all methods. + +#### 3.4.4 denied_methods + +A list of JSON-RPC methods that are explicitly denied. Denied methods take precedence over allowed methods. + +```yaml +denied_methods: + - resources/read + - resources/write +``` + +#### 3.4.5 protected_paths + +A list of file paths that tools MUST NOT access. Any tool argument containing a protected path MUST be blocked. + +```yaml +protected_paths: + - ~/.ssh + - ~/.aws/credentials + - .env +``` + +Implementations MUST: +- Expand `~` to the user's home directory +- Automatically protect the policy file itself + +#### 3.4.6 strict_args_default + +When `true`, tool rules reject any arguments not explicitly declared in `allow_args`. + +Default: `false` + +### 3.5 Tool Rules + +Tool rules provide fine-grained control over specific tools. + +```yaml +tool_rules: + - tool: # REQUIRED - Tool name + action: # OPTIONAL - allow|block|ask (default: allow) + rate_limit: # OPTIONAL - e.g., "10/minute" + strict_args: # OPTIONAL - Override strict_args_default + schema_hash: # OPTIONAL - Tool schema integrity + allow_args: # OPTIONAL + : +``` + +#### 3.5.1 Actions + +| Action | Behavior | +|--------|----------| +| `allow` | Permit (subject to argument validation) | +| `block` | Deny unconditionally | +| `ask` | Require interactive user approval | + +#### 3.5.2 Rate Limiting + +Format: `/` + +| Period | Aliases | +|--------|---------| +| `second` | `sec`, `s` | +| `minute` | `min`, `m` | +| `hour` | `hr`, `h` | + +Example: `"10/minute"`, `"100/hour"`, `"5/second"` + +Rate limiting algorithm is implementation-defined (token bucket, sliding window, etc.). + +#### 3.5.3 Argument Validation + +The `allow_args` field maps argument names to regex patterns. + +```yaml +allow_args: + url: "^https://github\\.com/.*" + query: "^SELECT\\s+.*" +``` + +Implementations MUST: +- Use a regex engine with linear-time guarantees (RE2 or equivalent) +- Match against the string representation of the argument value +- Treat missing constrained arguments as a violation + +#### 3.5.4 Tool Schema Hashing + +The `schema_hash` field provides cryptographic verification of tool definitions to prevent tool poisoning attacks. + +**Format**: `:` + +**Supported algorithms**: +- `sha256` (RECOMMENDED) +- `sha384` +- `sha512` + +**Hash computation**: + +``` +TOOL_SCHEMA_HASH(tool): + schema = { + "name": tool.name, + "description": tool.description, + "inputSchema": tool.inputSchema + } + canonical = JSON_CANONICALIZE(schema) # RFC 8785 + hash = SHA256(canonical) + RETURN "sha256:" + hex_encode(hash) +``` + +| Condition | Behavior | +|-----------|----------| +| `schema_hash` absent | No schema verification (backward compatible) | +| Hash matches | Tool allowed (proceed to argument validation) | +| Hash mismatch | Tool BLOCKED with error -32013 | +| Tool not found | Tool BLOCKED with error -32001 | + +### 3.6 DLP Configuration + +*[Section 3.6 remains unchanged from v1alpha2]* + +Data Loss Prevention (DLP) scans for sensitive data in requests and responses. + +```yaml +dlp: + enabled: # OPTIONAL, default: true when dlp block present + scan_requests: # OPTIONAL, default: false + scan_responses: # OPTIONAL, default: true + detect_encoding: # OPTIONAL, default: false + filter_stderr: # OPTIONAL, default: false + max_scan_size: # OPTIONAL, default: "1MB" + on_request_match: # OPTIONAL, default: "block" + on_redaction_failure: # OPTIONAL, default: "block" + log_original_on_failure: # OPTIONAL, default: false + patterns: + - name: # REQUIRED - Rule identifier + regex: # REQUIRED - Detection pattern + scope: # OPTIONAL, default: "all" (request|response|all) +``` + +When a pattern matches, the matched content MUST be replaced with: +``` +[REDACTED:] +``` + +### 3.7 Identity Configuration + +*[Section 3.7 remains unchanged from v1alpha2]* + +The `identity` section configures agent identity and token management. + +```yaml +spec: + identity: + enabled: # OPTIONAL, default: false + token_ttl: # OPTIONAL, default: "5m" + rotation_interval: # OPTIONAL, default: "4m" + require_token: # OPTIONAL, default: false + session_binding: # OPTIONAL, default: "process" + nonce_window: # OPTIONAL, default: equals token_ttl + policy_transition_grace: # OPTIONAL, default: "0s" + audience: # OPTIONAL, default: policy metadata.name + nonce_storage: # OPTIONAL + keys: # OPTIONAL +``` + +### 3.8 Server Configuration + +*[Section 3.8 remains unchanged from v1alpha2]* + +```yaml +spec: + server: + enabled: # OPTIONAL, default: false + listen: # OPTIONAL, default: "127.0.0.1:9443" + failover_mode: # OPTIONAL, default: "fail_closed" + timeout: # OPTIONAL, default: "5s" + tls: + cert: + key: + endpoints: + validate: # default: "/v1/validate" + revoke: # default: "/v1/revoke" + jwks: # default: "/v1/jwks" + health: # default: "/health" + metrics: # default: "/metrics" +``` + +### 3.9 Registry Configuration (v1alpha3) + +The `registry` section configures how the AIP Proxy connects to the AIP Registry for agent verification and revocation checks. + +```yaml +spec: + registry: + enabled: # OPTIONAL, default: false + endpoint: # REQUIRED if enabled - Registry URL + tls: # OPTIONAL + ca_cert: # Path to CA certificate for registry + client_cert: # Path to client certificate (mTLS) + client_key: # Path to client key (mTLS) + cache: # OPTIONAL + enabled: # default: true + ttl: # default: "5m" + max_entries: # default: 10000 + revocation: # OPTIONAL + check_interval: # default: "30s" + mode: # "online" | "cached" | "crl" + crl_path: # Path to local CRL file (if mode=crl) + auth: # OPTIONAL + type: # "bearer" | "mtls" | "api_key" + token: # Bearer token for registry access + api_key: # API key for registry access +``` + +#### 3.9.1 enabled + +When `true`, the AIP Proxy connects to an AIP Registry for agent identity verification and revocation checking. + +Default: `false` + +When `registry.enabled: true`, the proxy MUST verify AATs against the registry's known agent keys before applying local policy. + +#### 3.9.2 endpoint + +The URL of the AIP Registry. + +Format: `https://:` + +Example: +```yaml +registry: + enabled: true + endpoint: "https://registry.aip.example.com" +``` + +#### 3.9.3 Revocation Modes + +| Mode | Description | Latency | Freshness | +|------|-------------|---------|-----------| +| `online` | Check registry on every AAT validation | High | Real-time | +| `cached` | Cache revocation list, refresh periodically (default) | Low | Eventual | +| `crl` | Load Certificate Revocation List from local file | Lowest | Manual | + +**online** (highest security): +```yaml +registry: + revocation: + mode: "online" +``` +- Every AAT validation queries the registry +- Highest security, highest latency +- Requires reliable network connectivity + +**cached** (RECOMMENDED for production): +```yaml +registry: + revocation: + mode: "cached" + check_interval: "30s" +``` +- Background refresh of revocation list +- Trades freshness for performance +- Revocations take effect within `check_interval` + +**crl** (air-gapped or offline environments): +```yaml +registry: + revocation: + mode: "crl" + crl_path: "/etc/aip/revocation.crl" + check_interval: "5m" # Re-read CRL file +``` +- CRL file updated by external process +- No network dependency +- Manual revocation propagation + +#### 3.9.4 Cache Configuration + +```yaml +registry: + cache: + enabled: true + ttl: "5m" + max_entries: 10000 +``` + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `enabled` | bool | `true` | Cache agent public keys and revocation status | +| `ttl` | duration | `"5m"` | Cache entry time-to-live | +| `max_entries` | int | `10000` | Maximum cached entries (LRU eviction) | + +### 3.10 AAT Configuration (v1alpha3) + +The `aat` section configures how the AIP Proxy validates and uses Agent Authentication Tokens. + +```yaml +spec: + aat: + enabled: # OPTIONAL, default: false + require: # OPTIONAL, default: false + validation: # OPTIONAL + verify_signature: # default: true + verify_user_binding: # default: true + verify_capabilities: # default: true + max_token_age: # default: "1h" + clock_skew: # default: "30s" + capabilities_mode: # OPTIONAL, default: "intersect" + trusted_issuers: [] # OPTIONAL - List of trusted Token Issuer IDs + header_name: # OPTIONAL, default: "X-AIP-AAT" +``` + +#### 3.10.1 enabled + +When `true`, the proxy accepts and validates AATs on incoming requests. + +Default: `false` + +#### 3.10.2 require + +When `true`, all tool calls MUST include a valid AAT. Calls without AATs are rejected with error code -32015. + +Default: `false` + +This enables gradual rollout: start with `require: false` to validate AATs when present without blocking requests that lack them. + +#### 3.10.3 capabilities_mode + +Determines how AAT capabilities interact with local policy. + +| Value | Behavior | +|-------|----------| +| `intersect` | Tool must be allowed by BOTH AAT capabilities and local policy (default) | +| `aat_only` | AAT capabilities replace local `allowed_tools` | +| `policy_only` | Local policy only; AAT used for identity/audit only | + +**intersect** (RECOMMENDED): +```yaml +aat: + capabilities_mode: "intersect" +``` +- Most restrictive: requires both AAT and local policy to allow the tool +- Defense in depth: neither AAT compromise nor policy compromise alone grants access + +**aat_only** (registry-managed environments): +```yaml +aat: + capabilities_mode: "aat_only" +``` +- Centralized capability management via Token Issuer +- Local policy still enforces argument validation, DLP, and rate limiting +- `allowed_tools` in local policy is IGNORED + +**policy_only** (audit-focused deployments): +```yaml +aat: + capabilities_mode: "policy_only" +``` +- AAT used only for identity verification and audit trail enrichment +- Local policy controls all authorization decisions +- Simplest migration path from v1alpha2 + +#### 3.10.4 trusted_issuers + +A list of Token Issuer identifiers whose AATs are accepted by this proxy. + +```yaml +aat: + trusted_issuers: + - "https://issuer.aip.example.com" + - "https://issuer.aip.corp.internal" +``` + +When specified, the proxy MUST reject AATs from issuers not in this list. When not specified, the proxy accepts AATs from any issuer whose public key can be verified via the registry. + +#### 3.10.5 header_name + +The HTTP header or JSON-RPC extension field used to transmit AATs. + +Default: `"X-AIP-AAT"` + +For JSON-RPC transport (stdio), the AAT is included in the request params: +```json +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "read_file", + "arguments": {"path": "/etc/hosts"}, + "_aip_aat": "" + } +} +``` + +For HTTP transport, the AAT is sent as a header: +```http +POST /v1/validate HTTP/1.1 +X-AIP-AAT: +``` + +The `_aip_aat` parameter is reserved by this specification and MUST NOT be forwarded to the MCP server. + +--- + +## 4. Evaluation Semantics + +### 4.1 Name Normalization + +Tool names and method names MUST be normalized before comparison using the following algorithm: + +``` +NORMALIZE(input): + 1. Apply NFKC Unicode normalization + 2. Convert to lowercase + 3. Trim leading/trailing whitespace + 4. Remove non-printable and control characters + 5. Return result +``` + +### 4.2 Method-Level Authorization + +Method authorization is the FIRST line of defense, evaluated BEFORE tool-level checks. + +``` +IS_METHOD_ALLOWED(method): + normalized = NORMALIZE(method) + + IF normalized IN denied_methods: + RETURN DENY + + IF "*" IN allowed_methods: + RETURN ALLOW + + IF normalized IN allowed_methods: + RETURN ALLOW + + RETURN DENY +``` + +### 4.3 Tool-Level Authorization + +Tool authorization applies to `tools/call` requests. + +``` +IS_TOOL_ALLOWED(tool_name, arguments, identity_token, aat): + normalized = NORMALIZE(tool_name) + + # Step 0: Verify identity token (if configured) + IF identity.require_token: + IF identity_token IS EMPTY OR NOT valid_token(identity_token): + RETURN TOKEN_REQUIRED + + # Step 0b: Verify AAT (v1alpha3) + IF aat.require: + IF aat IS EMPTY: + RETURN AAT_REQUIRED + IF NOT valid_aat(aat): + RETURN AAT_INVALID + ELSE IF aat IS PRESENT: + # Validate opportunistically even when not required + IF NOT valid_aat(aat): + LOG_WARNING("Invalid AAT presented but not required") + + # Step 0c: Check AAT capabilities (v1alpha3) + IF aat IS PRESENT AND aat IS VALID: + IF aat.capabilities_mode == "intersect": + IF normalized NOT IN aat.capabilities: + RETURN AAT_CAPABILITY_DENIED + ELSE IF aat.capabilities_mode == "aat_only": + IF normalized NOT IN aat.capabilities: + RETURN AAT_CAPABILITY_DENIED + SKIP local allowed_tools check (Step 4) + + # Step 1: Check rate limiting + IF rate_limiter_exceeded(normalized): + RETURN RATE_LIMITED + + # Step 2: Check protected paths + IF arguments_contain_protected_path(arguments): + RETURN PROTECTED_PATH + + # Step 3: Check tool rules + rule = find_rule(normalized) + IF rule EXISTS: + IF rule.action == "block": + RETURN BLOCK + IF rule.action == "ask": + IF validate_arguments(rule, arguments): + RETURN ASK + ELSE: + RETURN BLOCK + + # Step 4: Check allowed_tools list (skipped if aat_only mode) + IF aat.capabilities_mode != "aat_only": + IF normalized NOT IN allowed_tools: + RETURN BLOCK + + # Step 5: Validate arguments (if rule exists) + IF rule EXISTS AND rule.allow_args NOT EMPTY: + IF NOT validate_arguments(rule, arguments): + RETURN BLOCK + + # Step 6: Strict args check + IF strict_args_enabled(rule): + IF arguments has undeclared keys: + RETURN BLOCK + + RETURN ALLOW +``` + +### 4.4 Decision Outcomes + +| Decision | Mode=enforce | Mode=monitor | +|----------|--------------|--------------| +| ALLOW | Forward request | Forward request | +| BLOCK | Return error | Forward request, log violation | +| ASK | Prompt user | Prompt user | +| RATE_LIMITED | Return error | Return error (always enforced) | +| PROTECTED_PATH | Return error | Return error (always enforced) | +| TOKEN_REQUIRED | Return error | Return error (always enforced) | +| TOKEN_INVALID | Return error | Return error (always enforced) | +| AAT_REQUIRED | Return error | Return error (always enforced) *(new)* | +| AAT_INVALID | Return error | Return error (always enforced) *(new)* | +| AAT_CAPABILITY_DENIED | Return error | Forward request, log violation *(new)* | + +### 4.5 Argument Validation + +``` +VALIDATE_ARGUMENTS(rule, arguments): + FOR EACH (arg_name, pattern) IN rule.allow_args: + IF arg_name NOT IN arguments: + RETURN FALSE # Required argument missing + + value = STRING(arguments[arg_name]) + IF NOT REGEX_MATCH(pattern, value): + RETURN FALSE + + RETURN TRUE +``` + +The `STRING()` function converts values to string representation: +- String -> as-is +- Number -> decimal representation +- Boolean -> "true" or "false" +- Null -> empty string +- Array/Object -> JSON serialization + +--- + +## 5. Agent Identity + +*[Section 5 remains unchanged from v1alpha2]* + +This section defines the local agent identity model introduced in v1alpha2. In v1alpha3, local identity tokens and AATs serve complementary roles: identity tokens bind the proxy session, while AATs carry cross-service agent identity. + +### 5.1 Overview + +Agent identity provides: +1. **Session binding**: Cryptographic proof that requests belong to the same session +2. **Policy integrity**: Verification that the policy hasn't changed mid-session +3. **Replay prevention**: Nonces prevent token reuse across sessions +4. **Audit correlation**: Session IDs link related audit events + +### 5.2 Policy Hash + +#### 5.2.1 Canonical Form + +``` +CANONICALIZE(policy): + 1. Remove metadata.signature field (if present) + 2. Serialize to JSON using RFC 8785 (JSON Canonicalization Scheme) + 3. Return UTF-8 encoded bytes +``` + +#### 5.2.2 Hash Computation + +``` +POLICY_HASH(policy): + canonical = CANONICALIZE(policy) + hash = SHA-256(canonical) + RETURN hex_encode(hash) +``` + +### 5.3 Identity Token Structure + +*[Remains unchanged from v1alpha2]* + +### 5.4 Token Lifecycle + +*[Remains unchanged from v1alpha2]* + +### 5.5 Session Management + +*[Remains unchanged from v1alpha2]* + +### 5.6 Token and Session Revocation + +*[Remains unchanged from v1alpha2]* + +### 5.7 Compatibility with Agentic JWT + +*[Remains unchanged from v1alpha2]* + +### 5.8 Key Management + +*[Remains unchanged from v1alpha2]* + +--- + +## 6. Server-Side Validation + +*[Section 6 remains unchanged from v1alpha2, with the addition of AAT-aware endpoints]* + +### 6.1 Overview + +The AIP server provides: +1. **Remote validation**: Validate tool calls from external systems +2. **Health checks**: Integration with load balancers and orchestrators +3. **Metrics**: Prometheus-compatible metrics export +4. **AAT validation**: Verify Agent Authentication Tokens *(new in v1alpha3)* + +### 6.2 Validation Endpoint + +#### 6.2.1 Request Format + +```http +POST /v1/validate HTTP/1.1 +Host: aip-server:9443 +Content-Type: application/json +Authorization: Bearer +X-AIP-AAT: + +{ + "tool": "", + "arguments": { ... } +} +``` + +When both an identity token and an AAT are present, the proxy MUST validate both. The identity token authenticates the proxy session; the AAT authenticates the agent. + +#### 6.2.2 Response Format + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "decision": "allow|block|ask", + "reason": "", + "violations": [ + { + "type": "", + "field": "", + "message": "" + } + ], + "token_status": { + "valid": true, + "expires_in": 240 + }, + "aat_status": { + "valid": true, + "agent_id": "", + "user_id": "", + "capabilities_checked": true, + "issuer": "" + } +} +``` + +### 6.3 Health Endpoint + +*[Remains unchanged from v1alpha2]* + +### 6.4 Metrics Endpoint + +Updated metrics (v1alpha3 additions): + +| Metric | Type | Description | +|--------|------|-------------| +| `aip_requests_total` | counter | Total validation requests | +| `aip_decisions_total` | counter | Decisions by type (allow/block/ask) | +| `aip_violations_total` | counter | Policy violations by type | +| `aip_token_validations_total` | counter | Identity token validations (valid/invalid) | +| `aip_aat_validations_total` | counter | AAT validations (valid/invalid/expired/revoked) *(new)* | +| `aip_registry_checks_total` | counter | Registry revocation checks *(new)* | +| `aip_registry_latency_seconds` | histogram | Registry check latency *(new)* | +| `aip_revocations_total` | counter | Revocation events by type | +| `aip_active_sessions` | gauge | Currently active sessions | +| `aip_active_agents` | gauge | Currently active agents (by AAT) *(new)* | +| `aip_request_duration_seconds` | histogram | Request latency | +| `aip_policy_hash` | gauge | Current policy hash (as label) | + +### 6.5 Revocation Endpoint + +*[Remains unchanged from v1alpha2]* + +### 6.6 Authentication + +*[Remains unchanged from v1alpha2]* + +--- + +## 7. Agent Authentication Token (AAT) (v1alpha3) + +This section defines the Agent Authentication Token -- the core credential bridging Layer 1 (Identity) and Layer 2 (Enforcement). + +### 7.1 Overview + +The AAT carries signed claims about an agent: +1. **Who issued its identity** -- the Token Issuer +2. **Which user it is acting on behalf of** -- user binding +3. **What capabilities it declared** -- tools and resource scopes +4. **When it was issued and when it expires** -- temporal bounds + +The AAT enables: +- **Per-agent identity**: Every agent has a distinct cryptographic identity, separate from the user +- **User delegation**: Actions are provably linked to the authorizing human +- **Capability-based authorization**: AAT capabilities can drive policy decisions +- **Cross-service portability**: AATs are verifiable by any party with access to the issuer's public key + +### 7.2 AAT Structure + +An AAT is a signed JWT (RFC 7519) with the following claims: + +```json +{ + "aat_version": "aip/v1alpha3", + "iss": "", + "sub": "", + "aud": "", + "iat": 1708300800, + "exp": 1708304400, + "nbf": 1708300800, + "jti": "", + + "agent": { + "id": "", + "name": "", + "public_key_thumbprint": "", + "aid_hash": "" + }, + + "user_binding": { + "user_id": "", + "auth_method": "", + "auth_time": 1708300000, + "delegation_scope": "" + }, + + "capabilities": { + "tools": ["", ...], + "resource_scopes": ["", ...], + "max_calls_per_session": , + "allowed_servers": ["", ...] + }, + + "context": { + "session_id": "", + "policy_hash": "<64-char-hex>", + "registry_id": "" + } +} +``` + +### 7.3 AAT Claims + +#### 7.3.1 Standard JWT Claims + +| Claim | Type | Required | Description | +|-------|------|----------|-------------| +| `aat_version` | string | Yes | MUST be `aip/v1alpha3` | +| `iss` | string | Yes | Token Issuer identifier (URI) | +| `sub` | string | Yes | Agent identifier (from AID) | +| `aud` | string/array | Yes | Intended recipient(s) -- proxy or MCP server | +| `iat` | number | Yes | Issued-at time (Unix timestamp) | +| `exp` | number | Yes | Expiration time (Unix timestamp) | +| `nbf` | number | No | Not-before time (Unix timestamp) | +| `jti` | string | Yes | Unique token identifier (UUID v4) | + +#### 7.3.2 Agent Claims + +The `agent` object identifies the AI agent: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | string | Yes | Unique agent identifier (from registry) | +| `name` | string | No | Human-readable name (e.g., "Cursor IDE Agent") | +| `public_key_thumbprint` | string | Yes | SHA-256 of agent's public key (JWK Thumbprint, RFC 7638) | +| `aid_hash` | string | No | SHA-256 of the Agent Identity Document | + +The `public_key_thumbprint` allows the proxy to verify that the AAT was issued for the specific agent key pair, preventing AAT theft across agents. + +#### 7.3.3 User Binding Claims + +The `user_binding` object links the agent's actions to the authorizing human: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `user_id` | string | Yes | User identifier (email, OIDC sub, or opaque ID) | +| `auth_method` | string | Yes | How the user authenticated (see below) | +| `auth_time` | number | Yes | When the user authenticated (Unix timestamp) | +| `delegation_scope` | string | No | Scope of delegation granted to the agent | + +**auth_method values**: + +| Value | Description | +|-------|-------------| +| `oidc` | OpenID Connect authentication | +| `oauth2` | OAuth 2.0 authorization code flow | +| `api_key` | API key associated with a user | +| `local` | Local system user (process owner) | +| `saml` | SAML assertion | +| `attestation` | Hardware or platform attestation | + +**delegation_scope values**: + +| Value | Description | +|-------|-------------| +| `full` | Agent can perform any action the user could (NOT RECOMMENDED) | +| `tools` | Agent can use specific tools listed in `capabilities.tools` | +| `read_only` | Agent can only use read operations | +| `session` | Delegation valid for this session only | +| `custom:` | Implementation-defined scope | + +#### 7.3.4 Capabilities Claims + +The `capabilities` object declares what the agent is authorized to do: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `tools` | []string | No | List of tool names the agent may invoke | +| `resource_scopes` | []string | No | Resource access scopes (e.g., `repo:read`, `db:write`) | +| `max_calls_per_session` | int | No | Maximum tool calls allowed in this session | +| `allowed_servers` | []string | No | MCP server identifiers this AAT is valid for | + +**Capability resolution** (how AAT capabilities interact with local policy): + +``` +RESOLVE_CAPABILITIES(aat, local_policy, mode): + IF mode == "intersect": + RETURN INTERSECTION(aat.capabilities.tools, local_policy.allowed_tools) + + IF mode == "aat_only": + RETURN aat.capabilities.tools + + IF mode == "policy_only": + RETURN local_policy.allowed_tools +``` + +**resource_scopes**: + +Resource scopes follow a `:` format: + +```json +{ + "resource_scopes": [ + "repo:read", + "repo:write", + "db:read", + "file:/home/user/**:read", + "api:github.com:*" + ] +} +``` + +Resource scopes are advisory in v1alpha3. Future versions MAY make them enforceable. + +#### 7.3.5 Context Claims + +The `context` object provides operational context: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `session_id` | string | Yes | Session UUID (matches identity token session) | +| `policy_hash` | string | No | SHA-256 hash of the policy the issuer approved | +| `registry_id` | string | No | Registry that holds the agent's registration | + +### 7.4 AAT Signing + +AATs MUST be signed using one of the following algorithms (in order of preference): + +| Algorithm | Key Type | Recommendation | +|-----------|----------|----------------| +| `ES256` | ECDSA P-256 | **Default, RECOMMENDED** | +| `ES384` | ECDSA P-384 | High-security environments | +| `EdDSA` | Ed25519 | Performance-critical | +| `RS256` | RSA 2048+ | Legacy compatibility | + +AATs MUST NOT use symmetric algorithms (`HS256`) as they require shared secrets. + +JWT Header: +```json +{ + "alg": "ES256", + "typ": "aat+jwt", + "kid": "" +} +``` + +The `kid` MUST reference a key in the Token Issuer's JWKS endpoint or the AIP Registry's key store. + +### 7.5 AAT Lifecycle + +``` +┌──────────────┐ +│ User Grants │ +│ Delegation │ +└──────┬───────┘ + │ + ▼ +┌──────────────┐ ┌──────────────┐ +│ Token Issuer │────▶│ Active │ +│ Issues AAT │ │ AAT │ +└──────────────┘ └──────┬───────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ Refresh │ │ Expired │ │ Revoked │ +│ (new AAT) │ │ (reject) │ │ (by registry│ +└──────────────┘ └──────────────┘ │ or issuer) │ + └──────────────┘ +``` + +#### 7.5.1 AAT Issuance + +1. Agent authenticates to Token Issuer using its private key (proof of possession) +2. User authorization is verified (OAuth flow, API key lookup, or local attestation) +3. Token Issuer retrieves agent registration from AIP Registry +4. Token Issuer constructs AAT with agent identity, user binding, and capabilities +5. Token Issuer signs AAT with its private key +6. AAT returned to agent + +#### 7.5.2 AAT Refresh + +AATs SHOULD be refreshed before expiry. The refresh flow: + +1. Agent presents current AAT and proof of possession (signed challenge) +2. Token Issuer verifies current AAT is valid (not expired, not revoked) +3. Token Issuer checks registry for revocation +4. New AAT issued with same `session_id`, fresh `jti`, updated `exp` + +**Constraints**: +- Refreshed AAT MUST preserve the `session_id` +- Refreshed AAT MUST have a new `jti` +- Refreshed AAT MAY have different `capabilities` (if policy changed) +- Refresh MUST fail if the agent or session is revoked + +#### 7.5.3 AAT Validation + +The AIP Proxy MUST validate AATs using the following algorithm: + +``` +VALIDATE_AAT(aat): + # Step 1: Parse and verify JWT structure + IF NOT valid_jwt_structure(aat): + RETURN (INVALID, "malformed_aat") + + # Step 2: Verify version + IF aat.aat_version != "aip/v1alpha3": + RETURN (INVALID, "unsupported_version") + + # Step 3: Verify issuer + IF trusted_issuers IS CONFIGURED: + IF aat.iss NOT IN trusted_issuers: + RETURN (INVALID, "untrusted_issuer") + + # Step 4: Verify signature + issuer_key = GET_ISSUER_PUBLIC_KEY(aat.iss, aat.header.kid) + IF issuer_key IS NULL: + RETURN (INVALID, "unknown_signing_key") + IF NOT verify_signature(aat, issuer_key): + RETURN (INVALID, "signature_invalid") + + # Step 5: Check temporal validity + now = current_time() + IF now < aat.nbf - clock_skew: + RETURN (INVALID, "not_yet_valid") + IF now > aat.exp + clock_skew: + RETURN (INVALID, "aat_expired") + + # Step 6: Check audience + IF aat.aud does not match expected_audience: + RETURN (INVALID, "audience_mismatch") + + # Step 7: Check revocation (via registry) + IF registry.enabled: + revocation_status = CHECK_REGISTRY_REVOCATION(aat) + IF revocation_status == REVOKED: + RETURN (INVALID, "aat_revoked") + + # Step 8: Verify agent identity + IF registry.enabled: + agent_record = GET_AGENT_FROM_REGISTRY(aat.agent.id) + IF agent_record IS NULL: + RETURN (INVALID, "unknown_agent") + IF agent_record.public_key_thumbprint != aat.agent.public_key_thumbprint: + RETURN (INVALID, "agent_key_mismatch") + IF agent_record.status != "active": + RETURN (INVALID, "agent_inactive") + + # Step 9: Verify JTI uniqueness (replay prevention) + IF NOT ATOMIC_CHECK_AND_RECORD_JTI(aat.jti): + RETURN (INVALID, "replay_detected") + + RETURN (VALID, nil) +``` + +### 7.6 AAT Transport + +#### 7.6.1 JSON-RPC (stdio) Transport + +For MCP connections over stdio, the AAT is included in the `tools/call` params as a reserved field: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "read_file", + "arguments": {"path": "/etc/hosts"}, + "_aip_aat": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImFhdCtqd3QifQ..." + } +} +``` + +The `_aip_aat` field: +- MUST be stripped by the AIP Proxy before forwarding to the MCP server +- MUST NOT be logged in audit trails (log `jti` instead) +- Is OPTIONAL when `aat.require: false` + +#### 7.6.2 HTTP Transport + +For HTTP-based MCP connections, the AAT is sent as a header: + +```http +POST /mcp HTTP/1.1 +X-AIP-AAT: eyJhbGciOiJFUzI1NiIsInR5cCI6ImFhdCtqd3QifQ... +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "read_file", + "arguments": {"path": "/etc/hosts"} + } +} +``` + +--- + +## 8. AIP Registry (v1alpha3) + +The AIP Registry is the root of trust for agent identities. + +### 8.1 Overview + +The AIP Registry provides: +1. **Agent registration**: Agents register their public keys and metadata +2. **Key attestation**: Registry signs agent certificates (Agent Identity Documents) +3. **Revocation**: Registry maintains lists of revoked agents, AATs, and sessions +4. **Discovery**: Token Issuers and proxies look up agent identities + +### 8.2 Agent Identity Document (AID) + +The AID is a JSON document that defines an agent's cryptographic identity: + +```json +{ + "aid_version": "aip/v1alpha3", + "agent_id": "", + "name": "", + "description": "", + "created_at": "", + "status": "active", + + "public_key": { + "kty": "EC", + "crv": "P-256", + "x": "...", + "y": "...", + "kid": "", + "use": "sig" + }, + + "metadata": { + "platform": "", + "version": "", + "owner": "", + "tags": ["", ...] + }, + + "registry_attestation": { + "registry_id": "", + "signed_at": "", + "expires_at": "", + "signature": "" + } +} +``` + +#### 8.2.1 AID Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `aid_version` | string | Yes | MUST be `aip/v1alpha3` | +| `agent_id` | string | Yes | Globally unique identifier (UUID v4 or URI) | +| `name` | string | Yes | Human-readable name | +| `description` | string | No | Agent description | +| `created_at` | string | Yes | Registration time (ISO 8601) | +| `status` | string | Yes | `active`, `suspended`, or `revoked` | +| `public_key` | JWK | Yes | Agent's public key in JWK format (RFC 7517) | +| `metadata` | object | No | Additional agent metadata | +| `registry_attestation` | object | Yes | Registry's signature over the AID | + +#### 8.2.2 Agent Status + +| Status | Description | AAT Issuance | AAT Validation | +|--------|-------------|--------------|----------------| +| `active` | Agent is registered and operational | Allowed | Valid | +| `suspended` | Temporarily disabled (e.g., security review) | Blocked | Rejected | +| `revoked` | Permanently deactivated | Blocked | Rejected | + +#### 8.2.3 Registry Attestation + +The `registry_attestation` provides the registry's cryptographic endorsement of the AID: + +``` +ATTEST_AID(aid, registry_key): + aid_copy = COPY(aid) + REMOVE aid_copy.registry_attestation + canonical = JSON_CANONICALIZE(aid_copy) # RFC 8785 + signature = SIGN(registry_key, canonical) + RETURN { + "registry_id": registry.id, + "signed_at": now(), + "expires_at": now() + attestation_ttl, + "signature": base64url_encode(signature) + } +``` + +### 8.3 Registry API + +The AIP Registry exposes the following HTTP endpoints: + +#### 8.3.1 Agent Registration + +```http +POST /v1/agents HTTP/1.1 +Host: registry.aip.example.com +Content-Type: application/json +Authorization: Bearer + +{ + "name": "My AI Agent", + "description": "Development assistant for code review", + "public_key": { + "kty": "EC", + "crv": "P-256", + "x": "...", + "y": "..." + }, + "metadata": { + "platform": "cursor", + "version": "1.0.0", + "owner": "dev@example.com" + } +} +``` + +**Response**: +```http +HTTP/1.1 201 Created +Content-Type: application/json + +{ + "agent_id": "ag_550e8400-e29b-41d4-a716-446655440000", + "aid": { ... }, + "registration_token_expires_at": "2026-03-19T00:00:00Z" +} +``` + +#### 8.3.2 Agent Lookup + +```http +GET /v1/agents/{agent_id} HTTP/1.1 +Host: registry.aip.example.com +``` + +**Response**: +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "agent_id": "ag_550e8400-e29b-41d4-a716-446655440000", + "status": "active", + "aid": { ... }, + "public_key": { ... } +} +``` + +#### 8.3.3 Revocation List + +```http +GET /v1/revocations HTTP/1.1 +Host: registry.aip.example.com +If-None-Match: "" +``` + +**Response**: +```http +HTTP/1.1 200 OK +Content-Type: application/json +ETag: "" +Cache-Control: max-age=30 + +{ + "version": 42, + "updated_at": "2026-02-19T10:30:00Z", + "revoked_agents": [ + { + "agent_id": "ag_...", + "revoked_at": "2026-02-19T10:00:00Z", + "reason": "Security incident" + } + ], + "revoked_aats": [ + { + "jti": "...", + "revoked_at": "2026-02-19T10:15:00Z", + "reason": "Token compromise" + } + ], + "revoked_sessions": [ + { + "session_id": "...", + "revoked_at": "2026-02-19T10:20:00Z", + "reason": "User logout" + } + ] +} +``` + +The revocation list supports conditional requests (ETag/If-None-Match) for efficient polling. + +#### 8.3.4 Agent Key Rotation + +```http +POST /v1/agents/{agent_id}/rotate-key HTTP/1.1 +Host: registry.aip.example.com +Content-Type: application/json +Authorization: Bearer + +{ + "new_public_key": { + "kty": "EC", + "crv": "P-256", + "x": "...", + "y": "..." + } +} +``` + +Key rotation: +1. Agent generates new key pair +2. Agent signs rotation request with current private key (proof of possession) +3. Registry verifies signature, updates stored public key +4. Old key remains valid for `key_grace_period` (default: 1 hour) +5. New AID attestation issued + +#### 8.3.5 Registry JWKS + +```http +GET /v1/jwks HTTP/1.1 +Host: registry.aip.example.com +``` + +Returns the registry's public keys for verifying AID attestations: + +```http +HTTP/1.1 200 OK +Content-Type: application/json +Cache-Control: public, max-age=3600 + +{ + "keys": [ + { + "kty": "EC", + "crv": "P-256", + "kid": "registry-key-2026-02", + "use": "sig", + "alg": "ES256", + "x": "...", + "y": "..." + } + ] +} +``` + +### 8.4 Registry Security + +#### 8.4.1 Authentication + +All registry API calls (except JWKS and health) MUST be authenticated. + +| Endpoint | Required Auth | Description | +|----------|---------------|-------------| +| `POST /v1/agents` | Registration token | Initial agent registration | +| `GET /v1/agents/{id}` | Bearer token or mTLS | Agent lookup | +| `GET /v1/revocations` | Bearer token or mTLS | Revocation list | +| `POST /v1/agents/{id}/rotate-key` | Proof of possession | Key rotation | +| `GET /v1/jwks` | None (public) | Registry public keys | + +#### 8.4.2 Rate Limiting + +Implementations MUST rate-limit registration endpoints to prevent abuse. + +#### 8.4.3 Data Integrity + +The registry MUST: +- Store agent public keys with integrity protection (e.g., signed records) +- Maintain an append-only audit log of all registration events +- Never expose private keys (the registry never holds agent private keys) + +--- + +## 9. Token Issuer (v1alpha3) + +The Token Issuer validates agent identities and issues AATs. + +### 9.1 Overview + +The Token Issuer is a service that: +1. Validates an agent's proof of possession (private key ownership) +2. Verifies the agent is registered and active in the AIP Registry +3. Binds the agent's AAT to the authorizing user +4. Issues signed AATs with capabilities derived from policy + +### 9.2 Token Issuance Flow + +``` +┌─────────┐ ┌──────────────┐ ┌──────────────┐ +│ Agent │ │ Token Issuer │ │ AIP Registry │ +└────┬─────┘ └──────┬───────┘ └──────┬───────┘ + │ │ │ + │ 1. Token Request │ │ + │ (signed challenge) │ │ + ├──────────────────────▶│ │ + │ │ 2. Verify agent in │ + │ │ registry │ + │ ├────────────────────────▶│ + │ │ │ + │ │ 3. Agent record + │ + │ │ revocation status │ + │ │◀────────────────────────┤ + │ │ │ + │ │ 4. Verify proof of │ + │ │ possession │ + │ │ │ + │ │ 5. Verify user │ + │ │ authorization │ + │ │ │ + │ 6. AAT │ │ + │◀──────────────────────┤ │ + │ │ │ +``` + +### 9.3 Token Request + +#### 9.3.1 Request Format + +```http +POST /v1/token HTTP/1.1 +Host: issuer.aip.example.com +Content-Type: application/json + +{ + "grant_type": "agent_authentication", + "agent_id": "", + "proof": { + "type": "signed_challenge", + "challenge": "", + "signature": "", + "algorithm": "ES256" + }, + "user_authorization": { + "type": "oauth2_token", + "token": "" + }, + "requested_capabilities": { + "tools": ["read_file", "list_directory", "git_status"], + "resource_scopes": ["repo:read"], + "allowed_servers": ["mcp://localhost:8080"] + }, + "audience": "" +} +``` + +#### 9.3.2 Grant Types + +| Grant Type | Description | Use Case | +|------------|-------------|----------| +| `agent_authentication` | Agent proves identity + user authorization | Standard flow | +| `aat_refresh` | Refresh existing AAT | Token renewal | +| `agent_attestation` | Platform attestation (no user) | Autonomous agents | + +#### 9.3.3 Proof of Possession + +The agent proves ownership of its private key by signing a challenge: + +``` +GENERATE_PROOF(agent_key, challenge): + payload = { + "challenge": challenge, + "agent_id": agent.id, + "timestamp": now() + } + canonical = JSON_CANONICALIZE(payload) + signature = SIGN(agent_key.private, canonical) + RETURN { + "type": "signed_challenge", + "challenge": challenge, + "signature": base64url_encode(signature), + "algorithm": agent_key.algorithm + } +``` + +The Token Issuer: +1. Retrieves the agent's public key from the AIP Registry +2. Verifies the challenge signature using that public key +3. Confirms the challenge was recently issued (prevents replay) + +#### 9.3.4 User Authorization Methods + +| Method | `user_authorization.type` | Description | +|--------|--------------------------|-------------| +| OAuth 2.0 | `oauth2_token` | User's OAuth access token (RECOMMENDED) | +| OIDC ID Token | `oidc_id_token` | OpenID Connect ID token | +| API Key | `api_key` | User's API key | +| Local Attestation | `local_user` | OS-level user identity (localhost only) | + +**OAuth 2.0 flow** (RECOMMENDED): +```json +{ + "user_authorization": { + "type": "oauth2_token", + "token": "ya29.A0ARrdaM..." + } +} +``` + +The Token Issuer validates the OAuth token against the identity provider and extracts the `user_id` claim. + +**Local attestation** (development / localhost): +```json +{ + "user_authorization": { + "type": "local_user", + "uid": 501, + "username": "developer", + "hostname": "dev-machine.local" + } +} +``` + +### 9.4 Token Response + +#### 9.4.1 Success Response + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "aat": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImFhdCtqd3QiLCJraWQiOiJpc3N1ZXIta2V5LTIwMjYtMDIifQ...", + "token_type": "aat+jwt", + "expires_in": 3600, + "refresh_token": "", + "capabilities_granted": { + "tools": ["read_file", "list_directory", "git_status"], + "resource_scopes": ["repo:read"] + }, + "capabilities_denied": { + "tools": [], + "reason": "All requested capabilities granted" + } +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `aat` | string | The signed Agent Authentication Token (JWT) | +| `token_type` | string | MUST be `aat+jwt` | +| `expires_in` | int | Token lifetime in seconds | +| `refresh_token` | string | Opaque token for AAT refresh (OPTIONAL) | +| `capabilities_granted` | object | Capabilities included in the AAT | +| `capabilities_denied` | object | Requested capabilities that were denied | + +#### 9.4.2 Error Response + +```http +HTTP/1.1 400 Bad Request +Content-Type: application/json + +{ + "error": "", + "error_description": "", + "error_details": { ... } +} +``` + +| Error Code | HTTP Status | Description | +|------------|-------------|-------------| +| `invalid_request` | 400 | Malformed request | +| `invalid_proof` | 401 | Proof of possession verification failed | +| `agent_not_found` | 404 | Agent not registered in registry | +| `agent_suspended` | 403 | Agent is suspended | +| `agent_revoked` | 403 | Agent is revoked | +| `user_auth_failed` | 401 | User authorization verification failed | +| `capabilities_denied` | 403 | All requested capabilities denied | +| `issuer_error` | 500 | Internal issuer error | + +### 9.5 Capability Determination + +The Token Issuer determines AAT capabilities based on: + +``` +DETERMINE_CAPABILITIES(requested, agent_record, user_permissions): + # Start with requested capabilities + granted = requested.tools + + # Intersect with agent's registered permissions (from registry) + IF agent_record.allowed_tools IS NOT EMPTY: + granted = INTERSECTION(granted, agent_record.allowed_tools) + + # Intersect with user's delegated permissions + IF user_permissions.delegatable_tools IS NOT EMPTY: + granted = INTERSECTION(granted, user_permissions.delegatable_tools) + + RETURN granted +``` + +The AAT capabilities represent the **maximum** set of tools the agent can invoke. The AIP Proxy MAY further restrict this based on local policy. + +### 9.6 Token Issuer JWKS + +Token Issuers MUST expose a JWKS endpoint for AAT signature verification: + +```http +GET /v1/jwks HTTP/1.1 +Host: issuer.aip.example.com +``` + +```http +HTTP/1.1 200 OK +Content-Type: application/json +Cache-Control: public, max-age=3600 + +{ + "keys": [ + { + "kty": "EC", + "crv": "P-256", + "kid": "issuer-key-2026-02", + "use": "sig", + "alg": "ES256", + "x": "...", + "y": "..." + } + ] +} +``` + +Proxies MUST cache JWKS responses and refresh when encountering unknown `kid` values. + +--- + +## 10. User Binding and Delegation (v1alpha3) + +This section defines how agent actions are cryptographically linked to the authorizing user. + +### 10.1 Motivation + +Without user binding: +- Agent actions are indistinguishable from each other and from human actions +- Compliance frameworks (SOC 2, GDPR, HIPAA) cannot attribute actions to responsible parties +- Revocation of user access does not revoke their agents' access + +User binding solves these by embedding a verifiable link from every agent action back to the authorizing human. + +### 10.2 Delegation Model + +``` +┌────────────┐ Grants Delegation ┌────────────┐ +│ User │─────────────────────────▶│ Agent │ +│ (Human) │ │ (AI) │ +│ │ AAT carries: │ │ +│ user_id │ - user_id │ agent_id │ +│ auth_time │ - auth_method │ public_key│ +│ │ - delegation_scope │ │ +└────────────┘ └─────┬──────┘ + │ + Uses AAT for + tool calls + │ + ▼ + ┌────────────┐ + │ AIP Proxy │ + │ │ + │ Verifies: │ + │ - agent_id │ + │ - user_id │ + │ - scope │ + └────────────┘ +``` + +### 10.3 Delegation Chain Verification + +The AIP Proxy MUST verify the complete delegation chain: + +``` +VERIFY_DELEGATION_CHAIN(aat): + # 1. Verify agent identity (AAT signature + registry check) + IF NOT valid_aat_signature(aat): + RETURN (INVALID, "signature_invalid") + + # 2. Verify user binding is present + IF aat.user_binding IS EMPTY: + RETURN (INVALID, "missing_user_binding") + + # 3. Verify user authentication freshness + max_auth_age = configured_max_auth_age OR 86400 # 24h default + IF now() - aat.user_binding.auth_time > max_auth_age: + RETURN (INVALID, "user_auth_stale") + + # 4. Verify delegation scope covers the requested action + IF aat.user_binding.delegation_scope == "read_only": + IF requested_tool is write_operation: + RETURN (INVALID, "delegation_scope_exceeded") + + RETURN (VALID, nil) +``` + +### 10.4 Audit Trail Integration + +When an AAT with user binding is present, audit log entries MUST include: + +```json +{ + "timestamp": "2026-02-19T10:30:45.123Z", + "direction": "upstream", + "method": "tools/call", + "tool": "write_file", + "decision": "ALLOW", + "policy_mode": "enforce", + "violation": false, + + "agent_id": "ag_550e8400-e29b-41d4-a716-446655440000", + "agent_name": "Cursor IDE Agent", + "user_id": "user@example.com", + "user_auth_method": "oidc", + "delegation_scope": "tools", + "aat_jti": "aat_660e8400-e29b-41d4-a716-446655440001", + "aat_issuer": "https://issuer.aip.example.com", + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "policy_hash": "a3c7f2e8d9b4f1e2c8a7d6f3e9b2c4f1..." +} +``` + +This audit record establishes: +- **Who** authorized the action (user_id, auth_method) +- **What** agent performed the action (agent_id, agent_name) +- **How** the delegation was granted (delegation_scope) +- **When** it happened (timestamp) +- **What** was done (tool, arguments) +- **By whose authority** the AAT was issued (aat_issuer) + +### 10.5 User Binding Revocation + +When a user's authorization is revoked: +1. Token Issuer marks all AATs for that user as revoked +2. Registry propagates revocation to all proxies (via revocation list) +3. Proxies reject tool calls from revoked AATs + +``` +REVOKE_USER_DELEGATION(user_id): + # Find all active AATs for this user + affected_aats = FIND_AATS_BY_USER(user_id) + + FOR EACH aat IN affected_aats: + ADD_TO_REVOCATION_LIST(aat.jti, "user_delegation_revoked") + + # Also revoke all sessions + affected_sessions = FIND_SESSIONS_BY_USER(user_id) + FOR EACH session IN affected_sessions: + ADD_TO_REVOCATION_LIST(session.session_id, "user_delegation_revoked") + + LOG_REVOCATION_EVENT(user_id, affected_aats.count, affected_sessions.count) +``` + +--- + +## 11. Error Codes + +AIP defines the following JSON-RPC error codes: + +| Code | Name | Description | +|------|------|-------------| +| -32001 | Forbidden | Tool not in allowed_tools list | +| -32002 | Rate Limited | Rate limit exceeded | +| -32004 | User Denied | User rejected approval prompt | +| -32005 | User Timeout | Approval prompt timed out | +| -32006 | Method Not Allowed | JSON-RPC method not permitted | +| -32007 | Protected Path | Access to protected path blocked | +| -32008 | Token Required | Identity token required but not provided | +| -32009 | Token Invalid | Identity token validation failed | +| -32010 | Policy Signature Invalid | Policy signature verification failed | +| -32011 | Token Revoked | Token or session explicitly revoked | +| -32012 | Audience Mismatch | Token audience does not match expected value | +| -32013 | Schema Mismatch | Tool schema hash does not match policy | +| -32014 | DLP Redaction Failed | Request redaction produced invalid content | +| -32015 | AAT Required | Agent Authentication Token required but not provided *(new)* | +| -32016 | AAT Invalid | AAT validation failed *(new)* | +| -32017 | AAT Capability Denied | Requested tool not in AAT capabilities *(new)* | +| -32018 | Agent Not Registered | Agent not found in AIP Registry *(new)* | +| -32019 | Delegation Expired | User delegation has expired or been revoked *(new)* | +| -32020 | Issuer Untrusted | AAT issuer not in trusted_issuers list *(new)* | + +### 11.1 Error Response Format + +```json +{ + "jsonrpc": "2.0", + "id": "", + "error": { + "code": "", + "message": "", + "data": { + "tool": "", + "reason": "" + } + } +} +``` + +### 11.2 New Error Codes (v1alpha3) + +#### -32015 AAT Required + +Returned when `aat.require: true` and no AAT is provided. + +```json +{ + "code": -32015, + "message": "AAT required", + "data": { + "tool": "write_file", + "reason": "Agent Authentication Token required for this proxy" + } +} +``` + +#### -32016 AAT Invalid + +Returned when AAT validation fails. + +```json +{ + "code": -32016, + "message": "AAT invalid", + "data": { + "tool": "write_file", + "reason": "AAT signature verification failed", + "aat_error": "signature_invalid" + } +} +``` + +Possible `aat_error` values: +- `malformed_aat` - JWT structure invalid +- `unsupported_version` - `aat_version` not recognized +- `untrusted_issuer` - Issuer not in trusted list +- `unknown_signing_key` - Issuer key not found +- `signature_invalid` - Cryptographic signature failed +- `not_yet_valid` - Token `nbf` is in the future +- `aat_expired` - Token past expiration +- `audience_mismatch` - Token audience wrong +- `aat_revoked` - Token or session revoked +- `unknown_agent` - Agent not in registry +- `agent_key_mismatch` - Agent public key doesn't match +- `agent_inactive` - Agent suspended or revoked +- `replay_detected` - JTI reuse detected + +#### -32017 AAT Capability Denied + +Returned when the requested tool is not in the AAT's capabilities. + +```json +{ + "code": -32017, + "message": "AAT capability denied", + "data": { + "tool": "delete_database", + "reason": "Tool not in AAT capabilities", + "agent_id": "ag_550e8400...", + "granted_capabilities": ["read_file", "list_directory"] + } +} +``` + +#### -32018 Agent Not Registered + +Returned when the agent in the AAT is not found in the registry. + +```json +{ + "code": -32018, + "message": "Agent not registered", + "data": { + "agent_id": "ag_unknown", + "reason": "Agent not found in AIP Registry" + } +} +``` + +#### -32019 Delegation Expired + +Returned when the user binding in the AAT has expired. + +```json +{ + "code": -32019, + "message": "Delegation expired", + "data": { + "tool": "write_file", + "reason": "User delegation has expired", + "user_auth_time": "2026-02-18T10:00:00Z", + "max_auth_age": 86400 + } +} +``` + +#### -32020 Issuer Untrusted + +Returned when the AAT was issued by an issuer not in the `trusted_issuers` list. + +```json +{ + "code": -32020, + "message": "Issuer untrusted", + "data": { + "issuer": "https://unknown-issuer.example.com", + "reason": "AAT issuer not in trusted_issuers configuration" + } +} +``` + +--- + +## 12. Audit Log Format + +### 12.1 Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `timestamp` | ISO 8601 | Time of the decision | +| `direction` | string | `upstream` (client->server) or `downstream` (server->client) | +| `decision` | string | `ALLOW`, `BLOCK`, `ALLOW_MONITOR`, `RATE_LIMITED` | +| `policy_mode` | string | `enforce` or `monitor` | +| `violation` | boolean | Whether a policy violation was detected | + +### 12.2 Optional Fields + +| Field | Type | Description | +|-------|------|-------------| +| `method` | string | JSON-RPC method name | +| `tool` | string | Tool name (for tools/call) | +| `args` | object | Tool arguments (SHOULD be redacted) | +| `failed_arg` | string | Argument that failed validation | +| `failed_rule` | string | Regex pattern that failed | +| `session_id` | string | Session identifier | +| `token_id` | string | Identity token nonce | +| `policy_hash` | string | Policy hash at decision time | +| `agent_id` | string | Agent identifier (from AAT) *(new)* | +| `agent_name` | string | Human-readable agent name (from AAT) *(new)* | +| `user_id` | string | Authorizing user identifier (from AAT) *(new)* | +| `user_auth_method` | string | User authentication method (from AAT) *(new)* | +| `delegation_scope` | string | Delegation scope (from AAT) *(new)* | +| `aat_jti` | string | AAT unique identifier *(new)* | +| `aat_issuer` | string | Token Issuer identifier *(new)* | + +### 12.3 Example + +```json +{ + "timestamp": "2026-02-19T10:30:45.123Z", + "direction": "upstream", + "method": "tools/call", + "tool": "delete_file", + "args": {"path": "/etc/passwd"}, + "decision": "BLOCK", + "policy_mode": "enforce", + "violation": true, + "failed_arg": "path", + "failed_rule": "^/home/.*", + "session_id": "550e8400-e29b-41d4-a716-446655440000", + "agent_id": "ag_550e8400-e29b-41d4-a716-446655440000", + "agent_name": "Cursor IDE Agent", + "user_id": "dev@example.com", + "user_auth_method": "oidc", + "delegation_scope": "tools", + "aat_jti": "aat_660e8400-e29b-41d4-a716-446655440001", + "aat_issuer": "https://issuer.aip.example.com", + "policy_hash": "a3c7f2e8d9b4f1e2c8a7d6f3e9b2c4f1a8e7d3c2b5f4e9a7c3d8f2b6e1a9c4f7" +} +``` + +### 12.4 Identity Events + +*[Token issued/rotated/failed events remain unchanged from v1alpha2]* + +#### AAT Validated (v1alpha3) + +```json +{ + "timestamp": "2026-02-19T10:30:00.000Z", + "event": "AAT_VALIDATED", + "agent_id": "ag_550e8400...", + "user_id": "dev@example.com", + "aat_jti": "aat_660e8400...", + "issuer": "https://issuer.aip.example.com", + "capabilities_granted": ["read_file", "list_directory"] +} +``` + +#### AAT Rejected (v1alpha3) + +```json +{ + "timestamp": "2026-02-19T10:30:00.000Z", + "event": "AAT_REJECTED", + "agent_id": "ag_550e8400...", + "aat_jti": "aat_660e8400...", + "error": "aat_expired", + "tool": "write_file" +} +``` + +#### Registry Revocation Check (v1alpha3) + +```json +{ + "timestamp": "2026-02-19T10:30:00.000Z", + "event": "REGISTRY_REVOCATION_CHECK", + "mode": "cached", + "revocation_list_version": 42, + "agents_revoked": 3, + "aats_revoked": 1, + "sessions_revoked": 2 +} +``` + +--- + +## 13. Conformance + +### 13.1 Conformance Levels + +| Level | Requirements | +|-------|--------------| +| **Basic** | Method authorization, tool allowlist, error codes | +| **Full** | Basic + argument validation, rate limiting, DLP, audit logging | +| **Extended** | Full + Human-in-the-Loop (action=ask) | +| **Identity** | Full + Identity tokens, session management | +| **Server** | Identity + Server-side validation endpoints | +| **AAT** | Server + AAT validation, registry integration *(new)* | +| **Federation** | AAT + Token Issuer, user binding, delegation chain *(new)* | + +### 13.2 Conformance Testing + +Implementations MUST pass the conformance test suite to claim AIP compliance. + +The test suite consists of: +1. **Schema validation tests**: Verify policy parsing +2. **Decision tests**: Input -> expected decision +3. **Normalization tests**: Verify Unicode handling +4. **Error format tests**: Verify JSON-RPC errors +5. **Identity tests**: Token lifecycle, rotation, validation +6. **Server tests**: HTTP endpoint behavior +7. **AAT tests**: AAT structure, validation, capability checking *(new)* +8. **Registry tests**: Agent lookup, revocation checking *(new)* +9. **Delegation tests**: User binding verification, scope enforcement *(new)* + +See `spec/conformance/` for test vectors. + +### 13.3 Implementation Requirements + +Implementations MUST: +- Parse `apiVersion: aip.io/v1alpha3` documents +- Reject documents with unknown `apiVersion` +- Apply NFKC normalization to names +- Return specified error codes +- Support `enforce` and `monitor` modes + +Implementations SHOULD: +- Log decisions in the specified format +- Support DLP scanning +- Support rate limiting +- Support identity tokens (for Identity conformance level) +- Support AAT validation (for AAT conformance level) + +Implementations MAY: +- Use any regex engine with RE2 semantics +- Implement additional security features (egress control, sandboxing) +- Implement server-side validation (for Server conformance level) +- Implement a Token Issuer (for Federation conformance level) +- Implement a Registry (for Federation conformance level) + +--- + +## 14. Security Considerations + +### 14.0 Threat Model + +*[Section 14.0.1-14.0.3 remain unchanged from v1alpha2 with AAT additions]* + +#### 14.0.1 Trust Boundaries + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ UNTRUSTED │ +│ ┌──────────┐ │ +│ │ Agent │ AI agent may be manipulated via prompt injection │ +│ └────┬─────┘ │ +│ │ │ +├───────┼─────────────────────────────────────────────────────────┤ +│ │ TRUST BOUNDARY (AIP) │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ AIP Policy │ Policy engine is TRUSTED │ +│ │ Engine │ Policy file integrity assumed │ +│ └──────┬───────┘ │ +│ │ │ +├─────────┼───────────────────────────────────────────────────────┤ +│ │ TRUST BOUNDARY (MCP) │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ MCP Server │ Server behavior is UNTRUSTED │ +│ │ │ Tool definitions may be malicious │ +│ └──────────────┘ │ +│ UNTRUSTED │ +└─────────────────────────────────────────────────────────────────┘ +``` + +| Component | Trust Level | Rationale | +|-----------|-------------|-----------| +| **User** | Trusted | Defines policy, approves sensitive operations | +| **Policy file** | Trusted | Integrity verified via signature | +| **AIP Engine** | Trusted | Assumed correctly implemented | +| **AIP Registry** | Trusted | Root of trust for agent identities *(new)* | +| **Token Issuer** | Trusted | Issues AATs based on verified identity *(new)* | +| **Agent (LLM)** | Untrusted | Subject to prompt injection, jailbreaks | +| **Agent's AAT** | Conditionally Trusted | Trusted only after cryptographic verification *(new)* | +| **MCP Server** | Untrusted | May be malicious or compromised | +| **Tool definitions** | Untrusted | May contain poisoned descriptions | + +#### 14.0.2 Threats In Scope (v1alpha3 additions) + +| Threat | Attack Vector | AIP Mitigation | +|--------|---------------|----------------| +| **AAT theft** | Stolen AAT used by different agent | Agent key thumbprint verification, JTI replay prevention | +| **AAT forgery** | Attacker creates fake AAT | Cryptographic signature verification via issuer JWKS | +| **User impersonation** | Agent claims different user | User binding verified through Token Issuer's auth flow | +| **Capability escalation** | Agent requests tools beyond delegation | Capability intersection with local policy | +| **Registry poisoning** | Attacker modifies agent records | Registry attestation signatures, mTLS | +| **Issuer compromise** | Attacker issues unauthorized AATs | Trusted issuer list, key rotation, revocation | +| **Delegation abuse** | Agent acts beyond user's intent | Delegation scope enforcement, short-lived AATs | + +#### 14.0.3 Defense in Depth (v1alpha3) + +``` +Request Flow: + + Agent Request (with AAT) + │ + ▼ + ┌────────────────┐ + │ 1. Method │ Block unauthorized JSON-RPC methods + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 2. AAT │ Verify signature, issuer, expiry, + │ Validation │ agent identity, user binding + │ (v1alpha3) │ Check registry revocation list + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 3. AAT │ Check AAT capabilities against + │ Capability │ requested tool + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 4. Identity │ Validate session token, binding + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 5. Rate Limit │ Prevent resource exhaustion + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 6. Tool │ Allowlist enforcement + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 7. Argument │ Regex validation, protected paths + │ Check │ + └───────┬────────┘ + │ + ▼ + ┌────────────────┐ + │ 8. HITL │ Human approval for sensitive ops + │ (if ask) │ + └───────┬────────┘ + │ + ▼ + MCP Server + │ + ▼ + ┌────────────────┐ + │ 9. DLP │ Redact sensitive response data + │ Scan │ + └───────┬────────┘ + │ + ▼ + Agent Response +``` + +### 14.1 Policy File Protection + +The policy file itself MUST be protected from modification by the agent. Implementations MUST automatically add the policy file path to `protected_paths`. + +### 14.2 Regex Denial of Service (ReDoS) + +Implementations MUST use a regex engine that guarantees linear-time matching (RE2 or equivalent). + +### 14.3 Unicode Normalization + +Implementations MUST apply NFKC normalization to prevent homoglyph attacks. + +### 14.4 Monitor Mode Risks + +Monitor mode allows all requests through. Implementations SHOULD warn users when monitor mode is enabled in production environments. + +### 14.5 Audit Log Integrity + +Audit logs SHOULD be written to a location not writable by the agent. + +### 14.6 Identity Token Security + +*[Remains unchanged from v1alpha2]* + +### 14.7 Server Endpoint Security + +*[Remains unchanged from v1alpha2]* + +### 14.8 AAT Security (v1alpha3) + +#### 14.8.1 AAT Storage + +AATs SHOULD be stored in memory only, not persisted to disk. If persistence is required, AATs MUST be encrypted at rest. + +The `_aip_aat` field in JSON-RPC params MUST be stripped before any logging or forwarding. + +#### 14.8.2 AAT Transmission + +AATs transmitted over the network MUST use TLS 1.2 or later. Implementations MUST NOT send AATs over unencrypted connections. + +For HTTP transport, AATs MUST be sent as headers (not query parameters or request body) to prevent leakage in server logs. + +#### 14.8.3 AAT Lifetime + +AAT lifetimes SHOULD be limited: + +| Environment | Recommended `exp - iat` | Rationale | +|-------------|------------------------|-----------| +| Interactive (IDE) | 1 hour | Session-length | +| Batch processing | Duration of job | Tight scoping | +| Long-running service | 15 minutes (with refresh) | Minimize theft window | +| CI/CD pipeline | Duration of pipeline | Job-scoped | + +Implementations SHOULD reject AATs with lifetime greater than 24 hours. + +#### 14.8.4 Replay Prevention + +AAT replay prevention uses the `jti` (JWT ID) claim: + +``` +ATOMIC_CHECK_AND_RECORD_JTI(jti): + # Same atomic semantics as nonce checking + IF ATOMIC_SET_IF_NOT_EXISTS(jti, ttl=max_token_age): + RETURN TRUE # JTI was new + ELSE: + RETURN FALSE # JTI already seen (replay attempt) +``` + +JTI storage requirements follow the same guidelines as nonce storage (Section 5 / v1alpha2). + +#### 14.8.5 Registry Trust + +The AIP Registry is a high-value target. Implementations MUST: +- Use TLS for all registry communications +- Verify the registry's TLS certificate +- Support mTLS for registry authentication +- Cache registry responses with bounded TTL +- Have a fallback strategy when the registry is unreachable (see `failover_mode`) + +#### 14.8.6 Token Issuer Trust + +Token Issuers control what capabilities agents receive. Compromised issuers can grant excessive permissions. Mitigations: +- Use `trusted_issuers` to limit accepted issuers +- Intersect AAT capabilities with local policy (`capabilities_mode: "intersect"`) +- Monitor `aip_aat_validations_total` for anomalies +- Implement issuer key rotation and revocation + +--- + +## 15. IANA Considerations + +This specification requests registration of the following: + +### 15.1 Media Type + +- Type name: application +- Subtype name: vnd.aip.policy+yaml +- Required parameters: None +- File extension: .yaml, .yml + +### 15.2 URI Scheme + +This specification uses the `aip.io` namespace for versioning: +- `aip.io/v1alpha1` - Initial specification +- `aip.io/v1alpha2` - Identity and server-side validation +- `aip.io/v1alpha3` - This specification (AAT, Registry, Token Issuer) + +### 15.3 JWT Type Header (v1alpha3) + +- `typ`: `aat+jwt` for Agent Authentication Tokens + +--- + +## Appendix A: Complete Schema Reference + +```yaml +# Complete AgentPolicy schema (v1alpha3) + +apiVersion: aip.io/v1alpha3 # REQUIRED +kind: AgentPolicy # REQUIRED + +metadata: # REQUIRED + name: string # REQUIRED - Policy identifier + version: string # OPTIONAL - Semantic version + owner: string # OPTIONAL - Contact email + signature: string # OPTIONAL - Policy signature + +spec: # REQUIRED + mode: enforce | monitor # OPTIONAL, default: enforce + + allowed_tools: # OPTIONAL + - string + + allowed_methods: # OPTIONAL + - string + + denied_methods: # OPTIONAL + - string + + protected_paths: # OPTIONAL + - string + + strict_args_default: boolean # OPTIONAL, default: false + + tool_rules: # OPTIONAL + - tool: string # REQUIRED + action: allow|block|ask # OPTIONAL, default: allow + rate_limit: string # OPTIONAL, format: "N/period" + strict_args: boolean # OPTIONAL + schema_hash: string # OPTIONAL - Tool schema integrity + allow_args: # OPTIONAL + : + + dlp: # OPTIONAL + enabled: boolean # OPTIONAL, default: true + scan_requests: boolean # OPTIONAL, default: false + scan_responses: boolean # OPTIONAL, default: true + detect_encoding: boolean # OPTIONAL, default: false + filter_stderr: boolean # OPTIONAL, default: false + max_scan_size: string # OPTIONAL, default: "1MB" + on_request_match: string # OPTIONAL, default: "block" + on_redaction_failure: string # OPTIONAL, default: "block" + log_original_on_failure: boolean # OPTIONAL, default: false + patterns: # REQUIRED if dlp present + - name: string # REQUIRED + regex: string # REQUIRED + scope: string # OPTIONAL, default: "all" + + identity: # OPTIONAL + enabled: boolean # OPTIONAL, default: false + token_ttl: string # OPTIONAL, default: "5m" + rotation_interval: string # OPTIONAL, default: "4m" + require_token: boolean # OPTIONAL, default: false + session_binding: string # OPTIONAL, default: "process" + nonce_window: string # OPTIONAL, default: equals token_ttl + policy_transition_grace: string # OPTIONAL, default: "0s" + audience: string # OPTIONAL, default: metadata.name + nonce_storage: # OPTIONAL + type: string # memory | redis | postgres + address: string + key_prefix: string # default: "aip:nonce:" + clock_skew_tolerance: string # default: "30s" + keys: # OPTIONAL + signing_algorithm: string # default: "ES256" + key_source: string # generate | file | external + key_path: string + rotation_period: string # default: "7d" + jwks_endpoint: string # default: "/v1/jwks" + + server: # OPTIONAL + enabled: boolean # OPTIONAL, default: false + listen: string # OPTIONAL, default: "127.0.0.1:9443" + failover_mode: string # OPTIONAL, default: "fail_closed" + timeout: string # OPTIONAL, default: "5s" + tls: + cert: string + key: string + fail_open_constraints: # OPTIONAL + allowed_tools: + - string + max_duration: string + max_requests: integer + alert_webhook: string + require_local_policy: boolean + endpoints: # OPTIONAL + validate: string # default: "/v1/validate" + revoke: string # default: "/v1/revoke" + jwks: string # default: "/v1/jwks" + health: string # default: "/health" + metrics: string # default: "/metrics" + + registry: # OPTIONAL (v1alpha3) + enabled: boolean # OPTIONAL, default: false + endpoint: string # REQUIRED if enabled + tls: + ca_cert: string + client_cert: string + client_key: string + cache: + enabled: boolean # default: true + ttl: string # default: "5m" + max_entries: integer # default: 10000 + revocation: + check_interval: string # default: "30s" + mode: string # online | cached | crl + crl_path: string + auth: + type: string # bearer | mtls | api_key + token: string + api_key: string + + aat: # OPTIONAL (v1alpha3) + enabled: boolean # OPTIONAL, default: false + require: boolean # OPTIONAL, default: false + validation: + verify_signature: boolean # default: true + verify_user_binding: boolean # default: true + verify_capabilities: boolean # default: true + max_token_age: string # default: "1h" + clock_skew: string # default: "30s" + capabilities_mode: string # intersect | aat_only | policy_only + trusted_issuers: # OPTIONAL + - string + header_name: string # default: "X-AIP-AAT" +``` + +--- + +## Appendix B: Changelog + +### v1alpha3 (2026-02-19) + +**Agent Authentication Token (AAT)** +- Added Section 7: Agent Authentication Token specification + - JWT-based AAT structure with agent, user_binding, capabilities, and context claims + - AAT signing requirements (ES256, EdDSA, RS256; no symmetric algorithms) + - AAT lifecycle: issuance, refresh, validation, revocation + - AAT transport via `_aip_aat` JSON-RPC param or `X-AIP-AAT` HTTP header + - AAT validation algorithm with 9-step verification + - JTI-based replay prevention +- Added `aat` configuration section (Section 3.10) + - `capabilities_mode`: intersect, aat_only, policy_only + - `trusted_issuers` for issuer allowlisting + - Configurable validation strictness + +**AIP Registry** +- Added Section 8: AIP Registry specification + - Agent Identity Document (AID) structure with registry attestation + - Registry API: registration, lookup, revocation list, key rotation, JWKS + - Agent status lifecycle: active, suspended, revoked + - Registry attestation via Ed25519/ES256 signatures +- Added `registry` configuration section (Section 3.9) + - Revocation modes: online, cached, CRL + - Cache configuration for agent keys and revocation status + - mTLS and bearer token authentication + +**Token Issuer** +- Added Section 9: Token Issuer specification + - Token issuance flow with proof of possession + - Grant types: agent_authentication, aat_refresh, agent_attestation + - User authorization methods: OAuth 2.0, OIDC, API key, local attestation + - Capability determination from agent record, user permissions, and requested capabilities + - Token Issuer JWKS endpoint + +**User Binding and Delegation** +- Added Section 10: User Binding and Delegation specification + - Delegation model connecting users to agents to actions + - Delegation chain verification algorithm + - Delegation scope enforcement (full, tools, read_only, session, custom) + - User binding revocation propagation + - Audit trail integration with user identity + +**Evaluation Semantics** +- Updated tool-level authorization (Section 4.3) with AAT capability checks + - Step 0b: AAT validation + - Step 0c: AAT capability intersection +- Added AAT_REQUIRED, AAT_INVALID, AAT_CAPABILITY_DENIED decision outcomes + +**Error Codes** +- Added -32015 AAT Required +- Added -32016 AAT Invalid (with 13 detailed error subtypes) +- Added -32017 AAT Capability Denied +- Added -32018 Agent Not Registered +- Added -32019 Delegation Expired +- Added -32020 Issuer Untrusted + +**Metrics** +- Added `aip_aat_validations_total` counter +- Added `aip_registry_checks_total` counter +- Added `aip_registry_latency_seconds` histogram +- Added `aip_active_agents` gauge + +**Audit Logging** +- Added AAT-enriched audit log fields (agent_id, agent_name, user_id, user_auth_method, delegation_scope, aat_jti, aat_issuer) +- Added AAT_VALIDATED, AAT_REJECTED, REGISTRY_REVOCATION_CHECK event types + +**Security** +- Updated threat model with AAT-specific threats (theft, forgery, impersonation, capability escalation, registry poisoning, issuer compromise, delegation abuse) +- Added Section 14.8: AAT Security (storage, transmission, lifetime, replay prevention, registry trust, issuer trust) +- Defense in depth expanded to 9 layers (added AAT validation, AAT capability check) + +**Conformance** +- Added AAT conformance level +- Added Federation conformance level +- Added AAT, registry, and delegation test categories + +### v1alpha2 (2026-01-24) + +- Added identity configuration section (token generation, rotation, session binding) +- Added server-side validation endpoints +- Added `schema_hash` for tool poisoning prevention +- Added DLP enhancements (scan_requests, max_scan_size, on_request_match) +- Added threat model (Section 10.0) +- Added policy signature (metadata.signature) +- Added error codes -32008 through -32014 +- Added Identity and Server conformance levels + +### v1alpha1 (2026-01-20) + +- Initial draft specification +- Defined core policy schema +- Defined evaluation semantics +- Defined error codes +- Defined audit log format + +--- + +## Appendix C: References + +- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) +- [MCP Authorization (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) +- [RFC 2119 - Key words for use in RFCs](https://www.rfc-editor.org/rfc/rfc2119) +- [RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750) +- [RFC 7517 - JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517) +- [RFC 7519 - JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) +- [RFC 7638 - JSON Web Key (JWK) Thumbprint](https://www.rfc-editor.org/rfc/rfc7638) +- [RFC 8707 - Resource Indicators for OAuth 2.0](https://www.rfc-editor.org/rfc/rfc8707) +- [RFC 8785 - JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785) +- [Unicode NFKC Normalization](https://unicode.org/reports/tr15/) +- [RE2 Syntax](https://github.com/google/re2/wiki/Syntax) +- [Agentic JWT (draft-goswami-agentic-jwt-00)](https://datatracker.ietf.org/doc/html/draft-goswami-agentic-jwt-00) + +--- + +## Appendix D: Future Extensions + +This appendix describes features under consideration for future versions of AIP. + +### D.1 Network Egress Control + +**Status:** Proposed for v1beta1 + +### D.2 Policy Inheritance + +**Status:** Under Discussion + +Allow policies to extend base policies: + +```yaml +apiVersion: aip.io/v1beta1 +kind: AgentPolicy +metadata: + name: team-policy +spec: + extends: "org-base-policy" + allowed_tools: + - additional_tool +``` + +### D.3 External Identity Federation + +**Status:** Proposed for v1beta1 + +Allow policies to integrate with external identity providers: + +```yaml +spec: + identity: + federation: + type: oidc + issuer: "https://accounts.google.com" + client_id: "aip-agent" + required_claims: + email_verified: true + hd: "company.com" +``` + +Supported federation types: +- `oidc` - OpenID Connect providers +- `spiffe` - SPIFFE/SPIRE workload identity + +### D.4 Telemetry and Metrics + +**Status:** Partially implemented in v1alpha2/v1alpha3 (metrics endpoint) + +### D.5 Advanced Policy Expressions + +**Status:** Under Discussion + +Support for CEL (Common Expression Language) or Rego for complex validation: + +```yaml +tool_rules: + - tool: file_write + action: allow + when: | + args.path.startsWith("/allowed/") && + !args.path.contains("..") && + size(args.content) < 1048576 +``` + +### D.6 Agentic JWT Compatibility + +**Status:** Under Discussion for v1beta1 + +Full compatibility with the Agentic JWT specification. + +Mapping to Agentic JWT claims: + +| AIP Field | Agentic JWT Claim | +|-----------|-------------------| +| `aat.agent.id` | `sub` (subject) | +| `aat.context.policy_hash` | `agent_proof.agent_checksum` | +| `aat.context.session_id` | `intent.workflow_id` | +| `aat.capabilities.tools` | Workflow steps | +| `aat.user_binding.user_id` | `azp` (authorized party) | + +### D.7 Multi-Agent Delegation (v1alpha3 future) + +**Status:** Under Discussion + +Support for agent-to-agent delegation chains, where Agent A (with user authorization) delegates a subset of capabilities to Agent B: + +```json +{ + "delegation_chain": [ + { + "delegator": "user@example.com", + "delegatee": "ag_agent-a", + "scope": "tools" + }, + { + "delegator": "ag_agent-a", + "delegatee": "ag_agent-b", + "scope": "read_only" + } + ] +} +``` + +Constraints: +- Each delegation step MUST reduce or maintain scope (never escalate) +- Maximum chain depth: 3 (user -> agent -> sub-agent) +- All delegators must be active and non-revoked + +### D.8 Registry Federation + +**Status:** Under Discussion + +Allow multiple registries to federate, enabling cross-organization agent identity verification: + +```yaml +registry: + federation: + trusted_registries: + - id: "partner-registry" + endpoint: "https://registry.partner.com" + trust_level: "tools_only" # Only trust tool capabilities +``` + +--- + +## Appendix E: Implementation Notes + +### E.1 Reference Implementation + +The reference implementation is available at: +https://github.com/openagentidentityprotocol/aip-go + +It provides: +- Go-based proxy (`aip-proxy`) +- Policy engine (`pkg/policy`) +- DLP scanner (`pkg/dlp`) +- Audit logger (`pkg/audit`) +- Identity manager (`pkg/identity`) +- HTTP server (`pkg/server`) +- AAT validator (`pkg/aat`) *(v1alpha3)* +- Registry client (`pkg/registry`) *(v1alpha3)* + +### E.2 Testing Against Conformance Suite + +```bash +# Clone the spec repository +git clone https://github.com/openagentidentityprotocol/agentidentityprotocol + +# Run conformance tests against your implementation +cd agentidentityprotocol/spec/conformance +./run-tests.sh --impl "your-aip-binary" --level "aat" +``` + +### E.3 AAT Implementation Guidance + +#### Generating Agent Key Pair + +```go +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" +) + +func generateAgentKeyPair() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +} +``` + +#### Computing JWK Thumbprint (RFC 7638) + +```go +import ( + "crypto/sha256" + "encoding/json" +) + +func jwkThumbprint(jwk map[string]interface{}) string { + // For EC keys: {"crv":"...","kty":"EC","x":"...","y":"..."} + required := map[string]interface{}{ + "crv": jwk["crv"], + "kty": jwk["kty"], + "x": jwk["x"], + "y": jwk["y"], + } + canonical, _ := json.Marshal(required) + hash := sha256.Sum256(canonical) + return base64url(hash[:]) +} +``` + +#### Validating an AAT + +```go +func validateAAT(tokenString string, trustedIssuers []string, registry RegistryClient) (*AATClaims, error) { + // 1. Parse JWT header (don't verify yet) + header, err := parseJWTHeader(tokenString) + if err != nil { + return nil, fmt.Errorf("malformed_aat: %w", err) + } + + // 2. Get issuer's public key via JWKS + issuerKey, err := getIssuerKey(header.Kid, trustedIssuers) + if err != nil { + return nil, fmt.Errorf("unknown_signing_key: %w", err) + } + + // 3. Verify signature + claims, err := jwt.ParseWithClaims(tokenString, &AATClaims{}, func(t *jwt.Token) (interface{}, error) { + return issuerKey, nil + }) + if err != nil { + return nil, fmt.Errorf("signature_invalid: %w", err) + } + + // 4. Check registry for agent status and revocation + agent, err := registry.GetAgent(claims.Agent.ID) + if err != nil { + return nil, fmt.Errorf("unknown_agent: %w", err) + } + if agent.Status != "active" { + return nil, fmt.Errorf("agent_inactive: %s", agent.Status) + } + + // 5. Verify agent key thumbprint + if agent.PublicKeyThumbprint != claims.Agent.PublicKeyThumbprint { + return nil, fmt.Errorf("agent_key_mismatch") + } + + // 6. Check JTI for replay + if !atomicRecordJTI(claims.JTI) { + return nil, fmt.Errorf("replay_detected") + } + + return claims, nil +} +``` + +### E.4 Registering Your Implementation + +Implementations that pass the conformance suite may be listed in the official registry. Submit a PR to the AIP repository with: +- Implementation name and URL +- Conformance level achieved (Basic/Full/Extended/Identity/Server/AAT/Federation) +- Platform support matrix