6.4 KiB
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.
role: owner
policies:
- action: "*"
scope: global
effect: allow
Member
Standard team member. Can do everything except manage policies and delete nodes.
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.
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.
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.
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.
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:
- Direct policy on actor takes precedence over role-inherited policies
- Deny always wins over allow at the same level
- Narrower scope wins over broader scope (single node > subtree > global)
- 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:
- All subtree-scoped policies are re-evaluated against the new position
- Actors may gain or lose access depending on the new subtree
- The system should warn the user: "Moving this node will change who can access it"
- 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