non-linear-docs/policy-model.md
2026-05-05 00:50:12 +03:00

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:

  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

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