non-linear-docs/04-POLICY-MODEL.md

4.8 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.

Alpha ships with three hardcoded roles only: Owner (all actions), Member (all except delete/manage), Agent (read + comment + change status + triage). The full policy engine described below — custom roles, subtree scoping, per-node policies, resolution order — is introduced post-alpha. The thesis being validated in alpha is "graph structure helps," not "granular permissions work." See 06a-ALPHA-SCOPE.md.

Core Concepts (post-alpha — full policy engine)

Policy

A policy is a single permission rule with four 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. 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

role: owner
policies:
  - action: "*"
    scope: global
    effect: allow

Member

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

role: agent-reader
policies:
  - action: [read_node, read_comments, add_comment, change_status]
    scope: global
    effect: allow

Custom Role Examples

Backend Decomposer Agent

role: backend-decomposer
policies:
  - action: read_node
    scope: global
    effect: allow
  - action: [create_child, add_label, add_comment]
    scope: subtree("Backend API")
    effect: allow

Triage Agent

role: triage-agent
policies:
  - action: [read_node, read_comments, add_comment,
             change_status, change_assignee, add_label, remove_label]
    scope: global
    effect: allow

Resolution Order

  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

Reparenting and Permission Changes

When a node is moved to a different parent (via plan-then-apply):

  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. System warns: "Moving this node will change who can access it"
  4. Specifically flags if any agent loses access to nodes it's currently assigned to

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 but cannot read the target node's details.
  • This enables agents to reason about blockers without leaking information across permission boundaries.