Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/agent-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"polizy": minor
---

Add comprehensive Agent Skills for polizy authorization library

Create 6 specialized Agent Skills (24 markdown files, 8,765 lines) to help AI agents effectively use the polizy library:

- **polizy**: Router skill for context-aware detection and routing
- **polizy-setup**: Installation and initial configuration guides
- **polizy-schema**: Schema design patterns with 10+ domain-specific examples
- **polizy-patterns**: 7 implementation patterns (direct, groups, hierarchy, field-level, time-limited, revocation, multi-tenant)
- **polizy-storage**: Database adapters (Prisma, custom) and performance optimization
- **polizy-troubleshooting**: Debugging guide with check algorithm explanation and anti-patterns

All skills follow the Agent Skills specification with progressive disclosure (SKILL.md under 500 lines, detailed content in references/).
322 changes: 322 additions & 0 deletions skills/polizy-patterns/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
---
name: polizy-patterns
description: Implementation patterns for polizy authorization. Use when implementing team access, folder inheritance, field-level permissions, temporary access, revocation, or any specific authorization scenario.
license: MIT
metadata:
author: polizy
version: "1.0.0"
---

# Polizy Implementation Patterns

Copy-paste patterns for common authorization scenarios.

## When to Apply

- User says "how do I implement X"
- User says "give team access to project"
- User says "make files inherit folder permissions"
- User says "grant temporary access"
- User says "revoke all permissions"
- User wants to implement a specific authorization scenario

## Pattern Selection Guide

| Scenario | Pattern | Reference |
|----------|---------|-----------|
| Specific user → specific resource | Direct Permissions | [DIRECT-PERMISSIONS.md](references/DIRECT-PERMISSIONS.md) |
| Team/group access | Group Access | [GROUP-ACCESS.md](references/GROUP-ACCESS.md) |
| Folder/file inheritance | Hierarchy | [HIERARCHY.md](references/HIERARCHY.md) |
| Sensitive fields (salary, PII) | Field-Level | [FIELD-LEVEL.md](references/FIELD-LEVEL.md) |
| Contractor/expiring access | Time-Limited | [TIME-LIMITED.md](references/TIME-LIMITED.md) |
| Removing access | Revocation | [REVOCATION.md](references/REVOCATION.md) |
| Tenant isolation | Multi-Tenant | [MULTI-TENANT.md](references/MULTI-TENANT.md) |

---

## Pattern 1: Direct Permissions

Grant specific user access to specific resource.

```typescript
// Grant permission
await authz.allow({
who: { type: "user", id: "alice" },
toBe: "owner",
onWhat: { type: "document", id: "doc1" }
});

// Check permission
const canEdit = await authz.check({
who: { type: "user", id: "alice" },
canThey: "edit",
onWhat: { type: "document", id: "doc1" }
});
```

---

## Pattern 2: Team-Based Access

Grant access through group membership.

```typescript
// 1. Add users to team
await authz.addMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" }
});

await authz.addMember({
member: { type: "user", id: "bob" },
group: { type: "team", id: "engineering" }
});

// 2. Grant team access to resource
await authz.allow({
who: { type: "team", id: "engineering" },
toBe: "editor",
onWhat: { type: "project", id: "project1" }
});

// 3. Team members can now access
const canAliceEdit = await authz.check({
who: { type: "user", id: "alice" },
canThey: "edit",
onWhat: { type: "project", id: "project1" }
}); // true
```

**Schema requirement:**
```typescript
relations: {
member: { type: "group" }, // Required!
editor: { type: "direct" },
}
```

---

## Pattern 3: Folder/File Hierarchy

Inherit permissions from parent resources.

```typescript
// 1. Set up hierarchy
await authz.setParent({
child: { type: "document", id: "doc1" },
parent: { type: "folder", id: "folder1" }
});

// 2. Grant access at folder level
await authz.allow({
who: { type: "user", id: "alice" },
toBe: "viewer",
onWhat: { type: "folder", id: "folder1" }
});

// 3. Document inherits folder permission
const canView = await authz.check({
who: { type: "user", id: "alice" },
canThey: "view",
onWhat: { type: "document", id: "doc1" }
}); // true
```

**Schema requirement:**
```typescript
relations: {
parent: { type: "hierarchy" }, // Required!
viewer: { type: "direct" },
},
hierarchyPropagation: {
view: ["view"], // CRITICAL: Without this, no inheritance!
}
```

---

## Pattern 4: Field-Level Permissions

Protect sensitive fields within records.

```typescript
// Grant general record access
await authz.allow({
who: { type: "user", id: "employee" },
toBe: "viewer",
onWhat: { type: "profile", id: "emp123" }
});

// Grant specific field access
await authz.allow({
who: { type: "user", id: "hr_manager" },
toBe: "viewer",
onWhat: { type: "profile", id: "emp123#salary" }
});

// Employee can view profile, but not salary
await authz.check({
who: { type: "user", id: "employee" },
canThey: "view",
onWhat: { type: "profile", id: "emp123" }
}); // true

await authz.check({
who: { type: "user", id: "employee" },
canThey: "view",
onWhat: { type: "profile", id: "emp123#salary" }
}); // false

// HR can view salary
await authz.check({
who: { type: "user", id: "hr_manager" },
canThey: "view",
onWhat: { type: "profile", id: "emp123#salary" }
}); // true
```

---

## Pattern 5: Temporary Access

Grant time-limited permissions.

```typescript
// Access valid for 30 days
await authz.allow({
who: { type: "user", id: "contractor" },
toBe: "editor",
onWhat: { type: "project", id: "project1" },
when: {
validSince: new Date(),
validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
}
});

// Scheduled future access
await authz.allow({
who: { type: "user", id: "new_hire" },
toBe: "viewer",
onWhat: { type: "onboarding", id: "docs" },
when: {
validSince: new Date("2024-02-01") // Starts Feb 1
}
});
```

---

## Pattern 6: Revocation

Remove permissions.

```typescript
// Remove specific permission
await authz.disallowAllMatching({
who: { type: "user", id: "bob" },
was: "editor",
onWhat: { type: "document", id: "doc1" }
});

// Remove all user permissions on a resource
await authz.disallowAllMatching({
who: { type: "user", id: "bob" },
onWhat: { type: "document", id: "doc1" }
});

// Remove all permissions on a resource (when deleting it)
await authz.disallowAllMatching({
onWhat: { type: "document", id: "doc1" }
});

// Remove user from group
await authz.removeMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" }
});
```

---

## Pattern 7: Listing Accessible Objects

Find what a user can access.

```typescript
// List all documents alice can access
const result = await authz.listAccessibleObjects({
who: { type: "user", id: "alice" },
ofType: "document"
});

// Result:
// {
// accessible: [
// { object: { type: "document", id: "doc1" }, actions: ["edit", "view", "delete"] },
// { object: { type: "document", id: "doc2" }, actions: ["view"] },
// ]
// }

// Filter by action
const editableOnly = await authz.listAccessibleObjects({
who: { type: "user", id: "alice" },
ofType: "document",
canThey: "edit" // Only return editable documents
});
```

---

## Pattern 8: Combining Patterns

Real apps often combine multiple patterns:

```typescript
// Organizational structure (groups)
await authz.addMember({ member: alice, group: frontend });
await authz.addMember({ member: frontend, group: engineering });

// Resource hierarchy
await authz.setParent({ child: codeFile, parent: srcFolder });
await authz.setParent({ child: srcFolder, parent: projectRoot });

// Team access at project level
await authz.allow({ who: engineering, toBe: "editor", onWhat: projectRoot });

// Alice can now edit codeFile through:
// alice → member → frontend → member → engineering → editor → projectRoot ← parent ← srcFolder ← parent ← codeFile

await authz.check({ who: alice, canThey: "edit", onWhat: codeFile }); // true
```

---

## Common Mistakes

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Missing `member: { type: "group" }` | `addMember()` throws | Add group relation to schema |
| Missing `parent: { type: "hierarchy" }` | `setParent()` throws | Add hierarchy relation to schema |
| Missing `hierarchyPropagation` | Parent permissions don't flow | Add propagation config |
| Relation not in `actionToRelations` | `check()` returns false | Add relation to action's array |
| Checking wrong action | `check()` returns false | Verify action name matches schema |

---

## References

Each pattern has detailed documentation:

- [DIRECT-PERMISSIONS.md](references/DIRECT-PERMISSIONS.md) - Simple user-resource access
- [GROUP-ACCESS.md](references/GROUP-ACCESS.md) - Teams, departments, nested groups
- [HIERARCHY.md](references/HIERARCHY.md) - Folders, projects, inheritance
- [FIELD-LEVEL.md](references/FIELD-LEVEL.md) - PII, sensitive data protection
- [TIME-LIMITED.md](references/TIME-LIMITED.md) - Contractors, expiring access
- [REVOCATION.md](references/REVOCATION.md) - Removing access patterns
- [MULTI-TENANT.md](references/MULTI-TENANT.md) - Tenant isolation strategies

## Related Skills

- [polizy-schema](../polizy-schema/SKILL.md) - Schema design
- [polizy-troubleshooting](../polizy-troubleshooting/SKILL.md) - When things go wrong
Loading