196 lines
6.4 KiB
Markdown
196 lines
6.4 KiB
Markdown
# Non-Linear: Policy & Permissions Model
|
|
|
|
## Overview
|
|
|
|
Non-Linear uses a granular, policy-based permission system inspired by AWS S3/IAM. The system is built on fine-grained policies from day one. Roles are named bundles of policies — convenience layers, not hardcoded ceilings.
|
|
|
|
## Core Concepts
|
|
|
|
### Policy
|
|
|
|
A policy is a single permission rule with three dimensions:
|
|
|
|
```
|
|
Policy = Actor + Action + Resource Scope + Effect
|
|
```
|
|
|
|
| Dimension | Description | Examples |
|
|
|-----------|-------------|---------|
|
|
| **Actor** | Who the policy applies to | User, agent, or role |
|
|
| **Action** | What operation is permitted/denied | `read_node`, `create_child`, `change_status` |
|
|
| **Resource Scope** | Where the policy applies in the graph | Global, subtree, single node |
|
|
| **Effect** | Allow or deny | `allow`, `deny` |
|
|
|
|
### Actions (Enumerated)
|
|
|
|
| Action | Description |
|
|
|--------|-------------|
|
|
| `read_node` | View a node's title, description, status, labels |
|
|
| `read_comments` | View comments on a node |
|
|
| `create_child` | Create a new child node |
|
|
| `edit_node` | Modify title, description |
|
|
| `change_status` | Change a node's status |
|
|
| `change_assignee` | Assign/unassign a user or agent |
|
|
| `add_label` | Add labels to a node |
|
|
| `remove_label` | Remove labels from a node |
|
|
| `add_comment` | Post a comment on a node |
|
|
| `create_link` | Create a lateral link (blocks, relates-to) |
|
|
| `remove_link` | Remove a lateral link |
|
|
| `reparent_node` | Move a node to a different parent |
|
|
| `delete_node` | Delete a node (and its subtree) |
|
|
| `manage_policies` | Create/edit/delete policies and roles |
|
|
| `manage_members` | Add/remove project members |
|
|
|
|
### Resource Scopes
|
|
|
|
| Scope | Meaning | Use Case |
|
|
|-------|---------|----------|
|
|
| **Global** | Entire project — all nodes | Default for most human roles |
|
|
| **Subtree** | A specific node and all its descendants | Agent confined to "Backend" component |
|
|
| **Single Node** | Exactly one node | Lock down a sensitive design doc |
|
|
|
|
**Subtree scoping is the key feature.** Because the data model is a decomposition tree, resource scope maps naturally to graph position. "Backend API subtree" means "this node and everything decomposed beneath it." The graph *is* the namespace — no separate resource-naming scheme needed.
|
|
|
|
## Roles
|
|
|
|
A role is a named, reusable collection of policies. Roles are editable — the defaults are starting points, not limits.
|
|
|
|
### Default Presets
|
|
|
|
#### Owner
|
|
|
|
Full control over the project.
|
|
|
|
```yaml
|
|
role: owner
|
|
policies:
|
|
- action: "*"
|
|
scope: global
|
|
effect: allow
|
|
```
|
|
|
|
#### Member
|
|
|
|
Standard team member. Can do everything except manage policies and delete nodes.
|
|
|
|
```yaml
|
|
role: member
|
|
policies:
|
|
- action: [read_node, read_comments, create_child, edit_node,
|
|
change_status, change_assignee, add_label, remove_label,
|
|
add_comment, create_link, remove_link, reparent_node]
|
|
scope: global
|
|
effect: allow
|
|
```
|
|
|
|
#### Agent Reader
|
|
|
|
Default agent role. Read-only plus comments and status changes.
|
|
|
|
```yaml
|
|
role: agent-reader
|
|
policies:
|
|
- action: [read_node, read_comments, add_comment, change_status]
|
|
scope: global
|
|
effect: allow
|
|
```
|
|
|
|
### Custom Role Examples
|
|
|
|
#### Backend Decomposer Agent
|
|
|
|
An AI agent that can break down backend components into tasks, but only within the Backend subtree.
|
|
|
|
```yaml
|
|
role: backend-decomposer
|
|
policies:
|
|
- action: read_node
|
|
scope: global
|
|
effect: allow
|
|
- action: [create_child, add_label, add_comment]
|
|
scope: subtree("Backend API") # scoped to node ID in practice
|
|
effect: allow
|
|
```
|
|
|
|
#### Triage Agent
|
|
|
|
An agent that can reprioritize and reassign, but can't create or delete.
|
|
|
|
```yaml
|
|
role: triage-agent
|
|
policies:
|
|
- action: [read_node, read_comments, add_comment,
|
|
change_status, change_assignee, add_label, remove_label]
|
|
scope: global
|
|
effect: allow
|
|
```
|
|
|
|
#### External Reviewer
|
|
|
|
A stakeholder who can view and comment on high-level nodes only.
|
|
|
|
```yaml
|
|
role: external-reviewer
|
|
policies:
|
|
- action: [read_node, read_comments, add_comment]
|
|
scope: subtree("root") # top two levels only (enforced by depth limit)
|
|
effect: allow
|
|
```
|
|
|
|
## Resolution Order
|
|
|
|
When multiple policies apply to an actor+action+resource combination:
|
|
|
|
1. **Direct policy on actor** takes precedence over role-inherited policies
|
|
2. **Deny always wins** over allow at the same level
|
|
3. **Narrower scope wins** over broader scope (single node > subtree > global)
|
|
4. **Default: deny** — if no policy explicitly allows an action, it is denied
|
|
|
|
### Resolution Examples
|
|
|
|
```
|
|
Scenario: Agent "deploy-bot" has role "agent-reader" (global read + comment + status)
|
|
AND a direct policy: deny change_status on "Production Deploy" node
|
|
|
|
Action: deploy-bot tries to change status on "Production Deploy"
|
|
Result: DENIED — direct deny on single node overrides role's global allow
|
|
|
|
Action: deploy-bot tries to change status on "Backend API / Auth"
|
|
Result: ALLOWED — role's global allow applies, no deny in scope
|
|
```
|
|
|
|
```
|
|
Scenario: Agent "decomposer" has role "backend-decomposer"
|
|
(global read, create_child on Backend subtree)
|
|
|
|
Action: decomposer tries to create a child under "Frontend / Charts"
|
|
Result: DENIED — create_child only allowed in Backend subtree
|
|
|
|
Action: decomposer tries to read "Frontend / Charts"
|
|
Result: ALLOWED — read_node is global scope
|
|
```
|
|
|
|
## Reparenting and Permission Changes
|
|
|
|
When a node is moved to a different parent:
|
|
|
|
1. All subtree-scoped policies are re-evaluated against the new position
|
|
2. Actors may gain or lose access depending on the new subtree
|
|
3. The system should warn the user: *"Moving this node will change who can access it"*
|
|
4. Specifically flag if any agent loses access to nodes it's currently assigned to
|
|
|
|
## Lateral Links Crossing Permission Boundaries
|
|
|
|
When a lateral link connects two nodes and the actor only has access to one:
|
|
|
|
- **Link visible, target opaque.** The actor can see that a link exists and its type ("blocked by node X") but cannot read the target node's details.
|
|
- This enables agents to reason about blockers without leaking information across permission boundaries.
|
|
|
|
## Implementation Notes for v0.1
|
|
|
|
- Build the full granular policy engine underneath
|
|
- Ship the three default presets (owner, member, agent-reader)
|
|
- Expose custom role creation through a settings panel (can be minimal UI)
|
|
- Store policies as structured data (JSON/YAML), not code
|
|
- Policy evaluation should be centralized — every API call goes through the policy engine
|
|
- Audit log: record every policy evaluation for debugging and compliance |