Add policy-model.md
This commit is contained in:
parent
c3ef169a6c
commit
aa5e1408cf
196
policy-model.md
Normal file
196
policy-model.md
Normal file
@ -0,0 +1,196 @@
|
||||
# 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
|
||||
Loading…
x
Reference in New Issue
Block a user