{"_id":"@affectively/auth","_rev":"3-429c866d7bdeea56433f07f1c560546a","name":"@affectively/auth","dist-tags":{"latest":"5.0.0"},"versions":{"0.1.0":{"name":"@affectively/auth","version":"0.1.0","keywords":["aeon","flux","ucan","zk","authorization","capability","zero-knowledge","authentication","decentralized","web3"],"author":{"name":"AFFECTIVELY","email":"hello@affectively.com"},"license":"MIT","_id":"@affectively/auth@0.1.0","maintainers":[{"name":"buley","email":"buley@outlook.com"}],"homepage":"https://github.com/affectively-ai/auth#readme","bugs":{"url":"https://github.com/affectively-ai/auth/issues"},"dist":{"shasum":"7abd100f87a86f31f6985282cdbfb5b8a31f2fa9","tarball":"https://registry.npmjs.org/@affectively/auth/-/auth-0.1.0.tgz","fileCount":5,"integrity":"sha512-RXW+WhVlWSMBDy3YkBhFBHu3KJS5jMyiSLv9ykssl5SkxCSJMVzarufIUjqT5Giv0IvXn2W81HuGKqBMftXnAw==","signatures":[{"sig":"MEQCIF/AuymoO4m21hTz7BMKOnkc1SRcrpCC2bjLzO2/inA4AiAGSxNtyJkrUbmBmBEb8xKl/rcthqsNe4qZhS14FkfkRg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16942},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"bun build ./src/index.ts --outdir dist --target bun && bun run build:types","typecheck":"tsc --noEmit","build:types":"tsc --emitDeclarationOnly --declaration --outDir dist","prepublishOnly":"bun run build"},"_npmUser":{"name":"buley","email":"buley@outlook.com"},"repository":{"url":"git+https://github.com/affectively-ai/auth.git","type":"git"},"_npmVersion":"11.8.0","description":"UCAN + ZK authorization - capability-based auth with zero-knowledge proofs","directories":{},"_nodeVersion":"25.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.0"},"peerDependencies":{"@affectively/zk":">=0.1.0","@affectively/ucan":">=0.1.0"},"peerDependenciesMeta":{"@affectively/zk":{"optional":true},"@affectively/ucan":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/auth_0.1.0_1770089319191_0.8406513872998507","host":"s3://npm-registry-packages-npm-production"},"deprecated":"This package is deprecated and no longer supported. Do not use."},"5.0.0":{"name":"@affectively/auth","version":"5.0.0","keywords":["ucan","crypto","ecdsa","auth"],"author":{"url":"https://buley.fyi","name":"Taylor William Buley"},"license":"UNLICENSED","_id":"@affectively/auth@5.0.0","maintainers":[{"name":"buley","email":"buley@outlook.com"}],"dist":{"shasum":"47f54517ae331b5c6e717e34dc62211640079d80","tarball":"https://registry.npmjs.org/@affectively/auth/-/auth-5.0.0.tgz","fileCount":31,"integrity":"sha512-RJ6mloYAacPggzpU6McmGEMKxtJ8OTSBAiEl6AvgOYTRMetZeAN94aPpXjAlGuz8TAXTxDcejIynf+iX5EC/pw==","signatures":[{"sig":"MEUCICUMJAxkkynbjWGF52/+woYN2/5HAs0KHEHMwjXBpbEpAiEAopf6Wbng3BUBZMSMBBCPpZzIP37/5CGueGQoXTDenKI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":589987},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"},"./*":{"types":"./src/*","default":"./src/*.ts"}},"gitHead":"ae54b0df6ac98ed86b037accafa16cf218dda541","scripts":{"dev":"tsup src/index.ts --format cjs,esm --watch --dts","lint":"eslint src/**/*.ts","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts --clean"},"_npmUser":{"name":"buley","email":"buley@outlook.com"},"_npmVersion":"11.8.0","description":"Shared authentication and cryptographic utilities for AFFECTIVELY","directories":{},"_nodeVersion":"25.5.0","dependencies":{"@noble/curves":"^1.8.1"},"typesVersions":{"*":{"*":["src/*","src/*.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.0.0","typescript":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/auth_5.0.0_1773156177192_0.8598663147282386","host":"s3://npm-registry-packages-npm-production"},"deprecated":"This package is deprecated and no longer supported. Do not use."}},"time":{"created":"2026-02-03T03:28:39.110Z","modified":"2026-03-27T20:03:11.015Z","0.1.0":"2026-02-03T03:28:39.321Z","5.0.0":"2026-03-10T15:22:57.380Z"},"author":{"url":"https://buley.fyi","name":"Taylor William Buley"},"license":"UNLICENSED","keywords":["ucan","crypto","ecdsa","auth"],"description":"Shared authentication and cryptographic utilities for AFFECTIVELY","maintainers":[{"name":"buley","email":"buley@outlook.com"}],"readme":"# @affectively/auth (Aegis Auth)\n\n**Decentralized Identity, Capabilities & Fine-Grained Access Control**\n\nA zero-dependency authentication and authorization library built on Web Crypto API. Implements UCAN (User Controlled Authorization Networks) with XPath-like node selection for surgical access control over your data tree, alongside custodial transaction-signing contracts for world-facing services.\n\n> **Architectural Note:** Why is auth separate from encryption?\n>\n> In the Affectively ecosystem, we separate **Integrity & Identity** (this package) from **Confidentiality** ([@affectively/zk-encryption](https://github.com/affectively-ai/zk-encryption)). \n> * Use `@affectively/auth` when you need to answer *\"Who are you and what can you do?\"* (UCAN tokens, DIDs, ECDSA Signatures, XPath Data Authorization).\n> * Use `@affectively/zk-encryption` when you need to answer *\"Who can read this data?\"* (Zero-Knowledge E2EE, ECIES, AES). \n> Keeping these separate ensures edge workers that only need to route UCAN traffic aren't forced to load heavy payload-encryption libraries.\n\n## Parent\n\n- [Open Source README](../README.md)\n\n## Child\n\n- [Source README](./src/README.md)\n\n---\n\n## What Lives Here\n\n- **Data Authorization Engine**: Fine-grained access control with priority-based rules (`access.ts`)\n- **XPath Node Selection**: Query complex JSON data trees using XPath (`xpath.ts`)\n- **Firebase-Style Rules Engine**: Evaluate declarative string-based rules against data trees (`rules.ts`)\n- **Deep UCAN Capabilities**: Full UCAN issuance, attenuation, and delegation chains (`token.ts`, `delegation.ts`)\n- **Revocation-Aware Checks**: Token/device revocation primitives (`ucanAuth.ts`)\n- **Custodial Signer Contract**: Canonical contract types for action-scoped custodial signing (`custodialSigner.ts`)\n\n---\n\n## Custodial Signer Contract\n\n`src/custodialSigner.ts` is the canonical Aegis contract for action-scoped\ncustodial signing across server and worker runtimes. It defines:\n\n- Action allowlist names\n- Typed payload contracts per action\n- Execute/health/signer metadata response shapes\n- Shared error-code surface for fail-closed callers\n\n**World Reuse Pattern:**\nUse this package as the single contract source, then pair it with:\n- Cloud Run signer service implementation in `apps/custodial-signer`\n- Shared typed client in `shared-utils/src/crypto/custodial-signer`\n- Runtime fail-closed callers (workers/server) that only send allowlisted actions\n\n---\n\n## Data Authorization Engine Features\n\n- **Zero Dependencies** - Uses only Web Crypto API\n- **DID Support** - Generate and manage `did:key` identifiers\n- **UCAN Tokens** - Create, parse, verify, and delegate capabilities\n- **XPath Selection** - Target specific nodes in your data tree\n- **Access Control** - Fine-grained per-user, per-node, per-operation rules\n- **Firebase-Style Rules** - Declarative security rules\n- **Sandboxing** - Designate collaborative areas\n\n---\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Real-World CMS Example](#real-world-cms-example)\n- [XPath Node Selection](#xpath-node-selection)\n- [Access Control](#access-control)\n- [Firebase-Style Rules](#firebase-style-rules)\n- [Identity & UCAN Tokens](#identity--ucan-tokens)\n- [API Reference](#api-reference)\n\n---\n\n## Installation\n\n```bash\nnpm install @affectively/auth\n# or\nbun add @affectively/auth\n```\n\n---\n\n## Quick Start\n\n```typescript\nimport { generateIdentity, AccessControl } from '@affectively/auth';\n\n// Create identity\nconst author = await generateIdentity({ displayName: 'Jane Author' });\n\n// Set up access control\nconst ac = new AccessControl();\nac.grant(author.did, '//users/jane/**', ['read', 'write', 'delete']);\nac.createSandbox('//drafts/**');\n\n// Check access\nac.check(author.did, '/users/jane/profile', 'write', data);\n// => { allowed: true }\n```\n\n---\n\n## Real-World CMS Example\n\nA complete content management system with pages, posts, media, embeds, and users:\n\n```typescript\nconst cmsData = {\n  // ===================\n  // PAGES\n  // ===================\n  pages: {\n    'home': {\n      id: 'home',\n      title: 'Welcome to Our Site',\n      slug: '/',\n      status: 'published',\n      template: 'homepage',\n      meta: {\n        description: 'The best site on the internet',\n        ogImage: '/media/og-home.jpg',\n        robots: 'index,follow',\n      },\n      sections: [\n        {\n          type: 'hero',\n          heading: 'Hello World',\n          subheading: 'We build amazing things',\n          backgroundImage: '/media/hero-bg.jpg',\n          cta: { text: 'Learn More', url: '/about' },\n        },\n        {\n          type: 'features',\n          items: [\n            { icon: 'rocket', title: 'Fast', description: 'Lightning quick' },\n            { icon: 'shield', title: 'Secure', description: 'Bank-level security' },\n          ],\n        },\n      ],\n      author: 'did:key:alice',\n      createdAt: '2024-01-15T10:00:00Z',\n      updatedAt: '2024-01-20T14:30:00Z',\n    },\n\n    'about': {\n      id: 'about',\n      title: 'About Us',\n      slug: '/about',\n      status: 'published',\n      template: 'default',\n      body: '<p>We are a team of passionate developers...</p>',\n      sidebar: {\n        widgets: ['team-members', 'contact-form'],\n      },\n      author: 'did:key:alice',\n    },\n  },\n\n  // ===================\n  // BLOG POSTS\n  // ===================\n  posts: {\n    'hello-world': {\n      id: 'hello-world',\n      title: 'Hello World: Our First Post',\n      slug: '/blog/hello-world',\n      status: 'published',\n      excerpt: 'Welcome to our new blog...',\n      body: `\n        <p>We're excited to launch our new blog!</p>\n        <p>Stay tuned for more updates.</p>\n      `,\n      // Featured image\n      featuredImage: {\n        url: '/media/posts/hello-world-hero.jpg',\n        alt: 'Hello World banner',\n        width: 1200,\n        height: 630,\n        caption: 'Photo by Jane Doe',\n      },\n      // Image gallery (multiple images)\n      gallery: [\n        { url: '/media/posts/gallery-1.jpg', alt: 'Team meeting' },\n        { url: '/media/posts/gallery-2.jpg', alt: 'Office space' },\n        { url: '/media/posts/gallery-3.jpg', alt: 'Product demo' },\n      ],\n      categories: ['announcements', 'company'],\n      tags: ['launch', 'blog', 'news'],\n      author: 'did:key:alice',\n      coAuthors: ['did:key:bob'],\n      publishedAt: '2024-01-15T10:00:00Z',\n      // SEO metadata\n      meta: {\n        title: 'Hello World - Our Blog',\n        description: 'Read our first blog post...',\n        canonical: 'https://example.com/blog/hello-world',\n      },\n      // Comments section\n      comments: {\n        enabled: true,\n        moderation: 'auto',\n        items: [\n          {\n            id: 'comment-1',\n            author: 'did:key:visitor1',\n            authorName: 'John Visitor',\n            body: 'Great post!',\n            status: 'approved',\n            createdAt: '2024-01-16T08:00:00Z',\n          },\n        ],\n      },\n    },\n\n    'product-launch': {\n      id: 'product-launch',\n      title: 'Announcing Our New Product',\n      slug: '/blog/product-launch',\n      status: 'published',\n      body: '<p>Today we announce...</p>',\n      // Embedded content (inflated oEmbeds)\n      embeds: [\n        {\n          type: 'youtube',\n          url: 'https://youtube.com/watch?v=abc123',\n          oembed: {\n            title: 'Product Demo Video',\n            thumbnail_url: 'https://img.youtube.com/vi/abc123/maxresdefault.jpg',\n            html: '<iframe src=\"https://youtube.com/embed/abc123\" allowfullscreen></iframe>',\n            width: 560,\n            height: 315,\n            provider_name: 'YouTube',\n          },\n        },\n        {\n          type: 'twitter',\n          url: 'https://twitter.com/user/status/123456',\n          oembed: {\n            html: '<blockquote class=\"twitter-tweet\">...</blockquote>',\n            author_name: '@user',\n            provider_name: 'Twitter',\n          },\n        },\n        {\n          type: 'spotify',\n          url: 'https://open.spotify.com/track/xyz',\n          oembed: {\n            title: 'Launch Day Playlist',\n            html: '<iframe src=\"https://open.spotify.com/embed/track/xyz\"></iframe>',\n            provider_name: 'Spotify',\n          },\n        },\n      ],\n      author: 'did:key:bob',\n    },\n  },\n\n  // ===================\n  // MEDIA LIBRARY\n  // ===================\n  media: {\n    'hero-bg.jpg': {\n      id: 'hero-bg.jpg',\n      filename: 'hero-bg.jpg',\n      url: '/uploads/hero-bg.jpg',\n      mimeType: 'image/jpeg',\n      size: 245000,\n      width: 1920,\n      height: 1080,\n      alt: 'Hero background',\n      folder: 'backgrounds',\n      uploadedBy: 'did:key:alice',\n      uploadedAt: '2024-01-10T09:00:00Z',\n      // Responsive image variants\n      variants: {\n        thumbnail: { url: '/uploads/hero-bg-thumb.jpg', width: 150, height: 84 },\n        medium: { url: '/uploads/hero-bg-medium.jpg', width: 800, height: 450 },\n        large: { url: '/uploads/hero-bg-large.jpg', width: 1200, height: 675 },\n      },\n    },\n\n    'document.pdf': {\n      id: 'document.pdf',\n      filename: 'annual-report-2024.pdf',\n      url: '/uploads/annual-report-2024.pdf',\n      mimeType: 'application/pdf',\n      size: 1500000,\n      folder: 'documents',\n      uploadedBy: 'did:key:alice',\n      visibility: 'private',\n      allowedUsers: ['did:key:alice', 'did:key:bob'],\n    },\n  },\n\n  // ===================\n  // USERS\n  // ===================\n  users: {\n    'did:key:alice': {\n      did: 'did:key:alice',\n      displayName: 'Alice Smith',\n      email: 'alice@example.com',\n      avatar: '/media/avatars/alice.jpg',\n      role: 'admin',\n      bio: 'Founder and CEO',\n      social: { twitter: '@alicesmith', linkedin: 'alicesmith' },\n      preferences: {\n        theme: 'dark',\n        notifications: { email: true, push: false },\n      },\n    },\n    'did:key:bob': {\n      did: 'did:key:bob',\n      displayName: 'Bob Jones',\n      role: 'editor',\n    },\n    'did:key:charlie': {\n      did: 'did:key:charlie',\n      displayName: 'Charlie Brown',\n      role: 'author',\n    },\n  },\n\n  // ===================\n  // NAVIGATION\n  // ===================\n  navigation: {\n    main: {\n      id: 'main',\n      items: [\n        { label: 'Home', url: '/' },\n        { label: 'About', url: '/about' },\n        { label: 'Products', url: '/products', children: [\n          { label: 'Product A', url: '/products/a' },\n          { label: 'Product B', url: '/products/b' },\n        ]},\n        { label: 'Blog', url: '/blog' },\n      ],\n    },\n  },\n\n  // ===================\n  // SETTINGS\n  // ===================\n  settings: {\n    site: { name: 'My Site', tagline: 'Building the future' },\n    seo: { defaultTitle: 'My Site', titleTemplate: '%s | My Site' },\n    integrations: {\n      mailchimp: { apiKey: '***', listId: 'abc123' },\n      stripe: { publicKey: 'pk_***' },\n    },\n  },\n\n  // ===================\n  // FORMS\n  // ===================\n  forms: {\n    contact: {\n      id: 'contact',\n      name: 'Contact Form',\n      fields: [\n        { name: 'name', type: 'text', required: true },\n        { name: 'email', type: 'email', required: true },\n        { name: 'message', type: 'textarea', required: true },\n      ],\n      submissions: [\n        { id: 'sub-1', data: { name: 'Jane', email: 'jane@ex.com', message: 'Hi!' }, status: 'unread' },\n      ],\n    },\n  },\n};\n```\n\n### Access Control for the CMS\n\n```typescript\nimport { AccessControl } from '@affectively/auth';\n\nconst ac = new AccessControl();\n\n// =====================\n// PUBLIC ACCESS\n// =====================\n\n// Published pages and posts\nac.grantPublic('//pages/*[status=\"published\"]', 'read');\nac.grantPublic('//posts/*[status=\"published\"]', 'read');\n\n// Public media (not private)\nac.grantPublic('//media/*[visibility!=\"private\"]', 'read');\n\n// Navigation and site settings\nac.grantPublic('//navigation/**', 'read');\nac.grantPublic('//settings/site', 'read');\nac.grantPublic('//settings/seo', 'read');\n\n// =====================\n// AUTHENTICATED USERS\n// =====================\n\n// Users can edit their own profile\nac.grant('did:key:alice', '//users/did:key:alice/**', ['read', 'write']);\nac.grant('did:key:bob', '//users/did:key:bob/**', ['read', 'write']);\n\n// Anyone can submit forms\nac.grant('*', '//forms/*/submissions', 'write', { constraints: { requireAuth: true } });\n\n// =====================\n// AUTHORS\n// =====================\n\n// Authors can create/edit their own posts\nac.grant('did:key:charlie', '//posts/*[author=\"did:key:charlie\"]/**', ['read', 'write']);\n\n// Authors can read all posts (for reference)\nac.grant('did:key:charlie', '//posts/**', 'read');\n\n// Authors can upload media\nac.grant('did:key:charlie', '//media/**', ['read', 'write']);\n\n// Authors manage comments on their posts\nac.grant('did:key:charlie', '//posts/*[author=\"did:key:charlie\"]/comments/**', ['read', 'write', 'delete']);\n\n// =====================\n// EDITORS\n// =====================\n\n// Editors can edit post CONTENT (title, body, excerpt, gallery, embeds)\nac.grant('did:key:bob', '//posts/**/body', ['read', 'write']);\nac.grant('did:key:bob', '//posts/**/title', ['read', 'write']);\nac.grant('did:key:bob', '//posts/**/excerpt', ['read', 'write']);\nac.grant('did:key:bob', '//posts/**/gallery/**', ['read', 'write']);\nac.grant('did:key:bob', '//posts/**/embeds/**', ['read', 'write']);\nac.grant('did:key:bob', '//posts/**/featuredImage/**', ['read', 'write']);\n\n// Editors can manage all comments\nac.grant('did:key:bob', '//posts/**/comments/**', ['read', 'write', 'delete']);\n\n// Editors can edit pages\nac.grant('did:key:bob', '//pages/**', ['read', 'write']);\n\n// Editors can manage media\nac.grant('did:key:bob', '//media/**', ['read', 'write', 'delete']);\n\n// Editors CANNOT change ownership or status (higher priority deny)\nac.deny('did:key:bob', '//posts/*/author', 'write', { priority: 50 });\nac.deny('did:key:bob', '//posts/*/status', 'write', { priority: 50 });\n\n// =====================\n// ADMINS\n// =====================\n\n// Full access\nac.grant('did:key:alice', '//**', '*');\n\n// Even admins can't delete the homepage\nac.deny('*', '//pages/home', 'delete', { priority: 100 });\n\n// =====================\n// SENSITIVE DATA\n// =====================\n\n// API keys are admin-only\nac.deny('*', '//settings/integrations/**', '*');\nac.grant('did:key:alice', '//settings/integrations/**', '*', { priority: 50 });\n\n// Form submissions are editor+ only\nac.deny('*', '//forms/*/submissions/**', 'read');\nac.grant('did:key:bob', '//forms/*/submissions/**', ['read', 'write']);\n```\n\n### XPath Queries for the CMS\n\n```typescript\nimport { select, getLeaves, getBranches } from '@affectively/auth';\n\n// Get all published posts\nselect('//posts/*[status=\"published\"]', cmsData);\n\n// Get all images in galleries\nselect('//gallery/*', cmsData);\n\n// Get all oEmbed data\nselect('//embeds/*/oembed', cmsData);\n\n// Get all YouTube embeds specifically\nselect('//embeds/*[type=\"youtube\"]', cmsData);\n\n// Get all URLs in the entire CMS\nselect('//url', cmsData);\n\n// Get all user-editable text\nselect('//title', cmsData);\nselect('//body', cmsData);\nselect('//excerpt', cmsData);\n\n// Get all images (featured + gallery + variants)\nselect('//featuredImage', cmsData);\nselect('//gallery/*', cmsData);\nselect('//variants/*', cmsData);\n\n// Get media by uploader\nselect('//media/*[uploadedBy=\"did:key:alice\"]', cmsData);\n\n// Get pending comments\nselect('//comments/items/*[status=\"pending\"]', cmsData);\n\n// Get all form submissions\nselect('//forms/*/submissions/*', cmsData);\n\n// Get all leaf values (for search indexing)\ngetLeaves(cmsData);\n\n// Get all objects/sections (for editing)\ngetBranches(cmsData);\n```\n\n---\n\n## XPath Node Selection\n\nSelect specific nodes in your data tree using XPath-inspired expressions.\n\n### Axes\n\n| Syntax | Description | Example |\n|--------|-------------|---------|\n| `/` | Direct children | `/posts/hello-world` |\n| `//` | Any depth (descendant) | `//email` |\n| `*` | Any single segment | `/users/*/profile` |\n| `**` | Any path (recursive) | `//settings/**` |\n\n### Node Type Functions\n\n| Function | Matches | Use Case |\n|----------|---------|----------|\n| `leaf()` | Strings, numbers, booleans | All content values |\n| `branch()` | Objects | All sections/containers |\n| `array()` | Arrays | All lists/galleries |\n| `text()` | Strings only | Text content |\n| `node()` | Everything | All nodes |\n\n```typescript\n// All text in a post\nselect('/posts/hello-world//text()', data);\n\n// All arrays (galleries, navigation items)\nselect('//array()', data);\n\n// All sections (objects) on homepage\nselect('/pages/home/sections//branch()', data);\n```\n\n### Predicates\n\n| Syntax | Description | Example |\n|--------|-------------|---------|\n| `[prop=\"value\"]` | Equals | `/*[status=\"published\"]` |\n| `[prop!=\"value\"]` | Not equals | `/*[role!=\"guest\"]` |\n| `[prop>value]` | Greater than | `/*[price>100]` |\n| `[prop]` | Property exists | `/*[featuredImage]` |\n| `[0]` | First item | `/items[0]` |\n| `[-1]` | Last item | `/items[-1]` |\n| `[contains(p,\"x\")]` | Contains | `/*[contains(tags,\"featured\")]` |\n| `[startsWith(p,\"x\")]` | Starts with | `/*[startsWith(slug,\"/blog\")]` |\n| `[matches(p,\"re\")]` | Regex | `/*[matches(email,\"@company\\\\.com\")]` |\n\n```typescript\n// Posts by author\nselect('//posts/*[author=\"did:key:alice\"]', data);\n\n// Posts with galleries\nselect('//posts/*[gallery]', data);\n\n// Large files\nselect('//media/*[size>1000000]', data);\n\n// Posts in category\nselect('//posts/*[contains(categories,\"news\")]', data);\n```\n\n---\n\n## Access Control\n\n### Operations\n\n| Operation | Use For |\n|-----------|---------|\n| `read` | Viewing, fetching |\n| `write` | Creating, updating |\n| `delete` | Removing |\n| `admin` | Administrative actions |\n| `*` | All operations |\n\n### Methods\n\n```typescript\nconst ac = new AccessControl();\n\n// Grant access\nac.grant(did, '//path/**', ['read', 'write'], { priority: 10, expiresIn: 86400000 });\nac.grantPublic('//path/**', 'read');\n\n// Deny access\nac.deny(did, '//path/**', ['write', 'delete'], { priority: 100 });\nac.denyPublic('//admin/**', '*');\n\n// Check access\nconst result = ac.check(did, '/users/alice', 'write', data);\n// => { allowed: true, reason: 'Granted by rule: //users/alice/**' }\n\n// Get accessible nodes\nconst nodes = ac.getAccessibleNodes(did, 'read', data);\n\n// Patterns\nac.createSandbox('//drafts/**');           // Public read/write\nac.createPublicReadOnly('//docs/**');      // Public read, no write\nac.createUserOwned(did, '//users/me/**');  // Full control\n\n// Expire rules\nac.grant(did, '//premium/**', 'read', { expiresIn: 7 * 24 * 60 * 60 * 1000 });\n\n// Export/import\nconst json = ac.exportRules();\nac.importRules(json);\n```\n\n### Priority\n\nHigher priority rules are checked first. Default: `grant=0`, `deny=100`.\n\n```typescript\n// Priority 100: Deny everyone\nac.deny('*', '//admin/**', '*', { priority: 100 });\n\n// Priority 50: Allow admin user\nac.grant(adminDID, '//admin/**', '*', { priority: 50 });\n```\n\n---\n\n## Firebase-Style Rules\n\n```typescript\nimport { parseRules, evaluateRules } from '@affectively/auth';\n\nconst rules = parseRules({\n  rules: {\n    pages: {\n      '$pageId': {\n        '.read': 'resource.status === \"published\"',\n        '.write': 'auth.role === \"admin\" || auth.role === \"editor\"',\n      }\n    },\n    posts: {\n      '$postId': {\n        '.read': 'resource.status === \"published\" || auth.did === resource.author',\n        '.write': 'auth.did === resource.author || auth.role === \"editor\"',\n        comments: {\n          '.read': true,\n          '.write': 'auth !== null',\n        }\n      }\n    },\n    settings: {\n      site: { '.read': true, '.write': 'auth.role === \"admin\"' },\n      integrations: {\n        '.read': 'auth.role === \"admin\"',\n        '.write': 'auth.role === \"admin\"',\n      },\n    },\n  }\n});\n\nconst result = evaluateRules(rules, '/posts/hello-world', 'read', {\n  auth: { did: 'did:key:visitor', role: 'user' },\n  resource: { status: 'published', author: 'did:key:alice' }\n});\n// => { allowed: true }\n```\n\n### Expression Syntax\n\n```typescript\n// Boolean\n'.read': true\n\n// Comparisons\n'.read': 'auth.did === $userId'\n'.read': 'resource.count > 10'\n\n// Boolean operators\n'.read': 'auth !== null && auth.verified'\n'.read': 'auth.role === \"admin\" || auth.role === \"editor\"'\n\n// Methods\n'.read': 'auth.capabilities.includes(\"read\")'\n'.read': 'resource.tags.includes(\"public\")'\n'.read': 'auth.email.endsWith(\"@company.com\")'\n```\n\n---\n\n## Identity & UCAN Tokens\n\n### Generate Identity\n\n```typescript\nimport { generateIdentity, sign, verify } from '@affectively/auth';\n\nconst alice = await generateIdentity({\n  algorithm: 'ES256',\n  displayName: 'Alice',\n  includeEncryptionKey: true,\n});\n\nconsole.log(alice.did); // did:key:z6Mkf...\n\n// Sign data\nconst sig = await sign(alice, new TextEncoder().encode('Hello'));\n\n// Verify\nconst valid = await verify(alice.signingKey.publicKey, sig, data);\n```\n\n### UCAN Tokens\n\n```typescript\nimport { createUCAN, verifyUCAN, delegateCapabilities } from '@affectively/auth';\n\n// Create token\nconst token = await createUCAN(\n  alice,\n  bobDID,\n  [\n    { can: 'file/read', with: 'storage://bucket/*' },\n    { can: 'file/write', with: 'storage://bucket/uploads/*' },\n  ],\n  { expirationSeconds: 3600 }\n);\n\n// Verify\nconst result = await verifyUCAN(token, alice.signingKey.publicKey, {\n  audience: bobDID,\n  requiredCapabilities: [{ can: 'file/read', with: '*' }]\n});\n\n// Delegate (attenuate)\nconst childToken = await delegateCapabilities(\n  token,\n  bob,\n  charlieDID,\n  [{ can: 'file/read', with: 'storage://bucket/docs/*' }]\n);\n```\n\n---\n\n## API Reference\n\n### Identity\n- `generateIdentity(options?)` - Create identity\n- `sign(identity, data)` - Sign data\n- `verify(publicKey, signature, data)` - Verify signature\n- `deriveDID(publicKey)` - Derive DID from key\n\n### UCAN\n- `createUCAN(issuer, audience, capabilities, options?)` - Create token\n- `verifyUCAN(token, publicKey, options?)` - Verify token\n- `parseUCAN(token)` - Parse without verification\n- `delegateCapabilities(parent, issuer, audience, caps, options?)` - Delegate\n\n### XPath\n- `select(expression, data)` - Select nodes\n- `compile(expression)` - Compile selector\n- `getLeaves(data)` - All terminal values\n- `getBranches(data)` - All objects\n- `getValue(data, path)` - Get value\n- `pathExists(data, path)` - Check existence\n\n### Access Control\n- `ac.grant(subject, selector, operations, options?)` - Grant\n- `ac.deny(subject, selector, operations?, options?)` - Deny\n- `ac.check(subject, path, operation, data?)` - Check\n- `ac.getAccessibleNodes(subject, operation, data)` - List accessible\n- `ac.createSandbox(selector)` - Public sandbox\n- `ac.createPublicReadOnly(selector)` - Read-only area\n- `ac.createUserOwned(subject, selector)` - User area\n\n### Firebase Rules\n- `parseRules(json)` - Parse rules\n- `evaluateRules(rules, path, operation, context)` - Evaluate\n- `rules()` - Builder\n\n### Storage\n- `MemoryKeyStorage`, `MemoryIdentityStorage` - Volatile\n- `IndexedDBKeyStorage`, `IndexedDBIdentityStorage` - Persistent\n- `createStorage()` - Auto-detect best option\n\n---\n\n## License\n\nMIT\n\n## Related\n\n- [@affectively/aeon](https://github.com/affectively/aeon) - Distributed sync\n- [@affectively/zk-encryption](https://github.com/affectively/zk-encryption) - Encryption\n- [UCAN Spec](https://ucan.xyz/)","readmeFilename":"README.md"}