non-linear-docs/02-DATA-MODEL.md

10 KiB

Non-Linear: Data Model

Overview

The data model consists of two overlaid graphs on a set of typed nodes:

  1. Decomposition Tree — strict parent→child hierarchy (DAG)
  2. Association Graph — lateral many-to-many connections with typed edges

These are deliberately separated. The decomposition tree is the spine of the product. The association graph is an overlay that adds context without defining structure.

Node Types

The graph has two main node types: components and issues. Components form the skeleton; issues are work attached to that skeleton.

Component Node

A component represents a structural part of the system — a service, a module, a package, a directory. Components are relatively stable and map to code structure.

Field Type Description
id UUID Unique identifier
short_id string Human-readable ID (NL-C12)
type enum Always component
title string Component name
description text What this component does (markdown), stored in Postgres
labels string[] Freeform tags (frontend, backend, infra)
owner actor_id? Team or person responsible
parent_id node_id? Parent in decomposition tree
repo_link repo_ref? Linked repository or directory (see Repo Integration)
created_at timestamp Creation time
updated_at timestamp Last modification

Components can be parents of other components (nesting: service → module → submodule) or parents of issues.

Issue Node

An issue represents work to be done — a task, bug, feature, spike. Issues attach to components (or to the project root, or live in the inbox).

Field Type Description
id UUID Unique identifier
short_id string Human-readable ID (NL-42)
type enum Always issue
title string Short descriptive title
description text Detailed description (markdown), stored in Postgres
status enum Current state (see Status below)
labels string[] Freeform tags for orthogonal concerns
assignee actor_id? User or agent assigned
parent_id node_id? Parent in decomposition tree (null = inbox item)
created_at timestamp Creation time
updated_at timestamp Last modification
created_by actor_id Creator (user or agent)
cycle_id cycle_id? Current cycle membership

How They Relate

Project (root)
├── Component: auth-service          (→ repo: github.com/team/auth)
│   ├── Component: oauth-module      (→ directory: /src/oauth)
│   │   ├── Issue: implement refresh tokens
│   │   └── Issue: fix token expiry bug
│   └── Component: session-manager   (→ directory: /src/sessions)
│       └── Issue: add Redis session store
├── Component: frontend-app          (→ repo: github.com/team/web)
│   ├── Component: dashboard         (→ directory: /src/views/dashboard)
│   └── Component: auth-ui           (→ directory: /src/views/auth)
│       └── Issue: redesign login page

Components are the skeleton — stable structure that mirrors your codebase. Issues are the work — they flow through statuses, get assigned, join cycles. This separation is cleaner than depth-as-type because a component at depth 3 is still a component, not a "task."

Hierarchy Rules

  • Components can be children of: project root, other components
  • Issues can be children of: project root, components, other issues (sub-tasks)
  • Issues cannot be parents of components
  • The tree remains a strict DAG — no cycles, single parent per node

Status (Issues Only)

Default statuses (customizable per project):

  • backlog — not yet planned
  • todo — planned, not started
  • in_progress — actively being worked on
  • in_review — awaiting review
  • done — completed
  • cancelled — abandoned

Components don't have status — their "health" is derived from their children's statuses (e.g., "3/7 issues done, 1 blocked").

Status Transitions (v0.1 Default)

v0.1 ships with a permissive default model — any status can transition to any other status. This avoids blocking agents or humans with rigid workflows before real usage patterns emerge.

backlog ⇄ todo ⇄ in_progress ⇄ in_review ⇄ done
   ↕        ↕         ↕             ↕          ↕
                    cancelled

All transitions are bidirectional. Reopening (donein_progress) and skipping stages (backlogdone) are allowed. The system logs every transition with actor + timestamp for auditability.

v0.2: Custom Transition Graphs. Projects will be able to define a custom state machine that restricts allowed transitions (e.g., "only owners can reopen done issues," "in_review can only move to done or in_progress"). The default permissive model remains available as a preset.

Labels

Labels handle all classification orthogonal to hierarchy and type:

  • Kind (issues): bug, feature, chore, spike
  • Area: frontend, backend, infra, docs
  • Priority: p0, p1, p2, p3
  • Custom: anything the team defines

Repository Integration

A component can be linked to a code source:

Field Type Description
provider enum github, gitlab, bitbucket
repo_url string Repository URL
path string? Subdirectory within repo (null = repo root)
branch string? Default branch to track (null = repo default)

Multi-Repo Support

A project can span multiple repositories. Each repo (or subdirectory) maps to a component node:

  • Monorepo: one repo → multiple component nodes (one per package/service)
  • Multi-repo microservices: each repo → one top-level component node
  • Mixed: some repos map to a single component, others are split

Skeleton Inference (Fast-Start)

When connecting repositories, the system proposes a component tree:

  1. Connect: OAuth via Authentik to GitHub/GitLab
  2. Select repos: pick which repositories belong to this project
  3. Scan: system reads repo structure — directories, package manifests (package.json, pyproject.toml, go.mod, Cargo.toml), major module boundaries
  4. Propose: AI suggests a component tree: "I see 3 services with these modules — here's a proposed skeleton"
  5. Adjust: user reviews, merges, splits, renames, rearranges
  6. Create: skeleton is committed — user starts attaching issues

Code ↔ Component Mapping

Because components are linked to repos/directories:

  • Commits touching files in a component's path are automatically associated with that component
  • PRs surfaced in the right component's activity feed without manual NL-42 tagging (explicit tagging still works for issue-level linking)
  • New files/directories can trigger suggestions: "A new directory /src/notifications appeared — add as a component?"
  • Agent context: agent scoped to a component subtree can reference the actual code structure

Decomposition Tree

The decomposition tree is a strict hierarchy:

  • Single parent: Every node has exactly one parent (except roots)
  • Directed: Edges flow from abstract (parent) to concrete (child)
  • Acyclic: No node can be its own ancestor
  • Single root per project: Each project has one root node (v0.1)
  • Typed: Components form the skeleton, issues attach to it

Reparenting

Moving a node to a different parent is a structural change (plan-then-apply):

  • Permission boundaries may shift
  • For components: repo link association persists, but position in tree changes
  • For issues: moving between components changes which codebase the work is conceptually "in"
  • System warns about consequences before committing
Type Semantics Directionality
blocks A blocks B Directed
blocked_by Inverse of blocks Directed (auto-created)
relates_to General association Undirected
duplicates A is a duplicate of B Directed
depends_on Component A depends on component B Directed

depends_on is specifically for inter-component dependencies — can be inferred from code (import graphs, API calls) or declared manually.

Every node surfaces inbound lateral links: "3 tasks blocked by this," "2 components depend on this."

Deferred. Will enable cross-project/cross-repo dependency tracking.

Triage Inbox

Issues with parent_id = null live in the rootless inbox. With repo integration, triage becomes smarter — the system (or an agent) can suggest which component an inbox issue belongs to based on keywords, file paths, or related commits.

Cycles

A cycle is a named set of issue references with a date range. Components don't join cycles — only issues do.

Change History

Every mutation is a stored event with actor + timestamp. For components, this includes repo link changes and skeleton inference events.

Workspace

A workspace is the top-level organizational container. It groups projects, users, and agents under a single identity boundary.

Field Type Description
id UUID Unique identifier
name string Workspace name
slug string URL-safe identifier (my-team)
projects project_id[] Projects in this workspace
members actor_id[] Users and agents with workspace access
created_at timestamp Creation time
  • Users and agents belong to workspaces. A user can belong to multiple workspaces.
  • Authentik maps to workspace-level identity — one Authentik group per workspace.
  • Policies are per-project, but membership is per-workspace. A workspace member can be granted access to specific projects via project-level roles.
  • Billing (if ever introduced) attaches to the workspace entity.
  • v0.1 supports a single workspace per deployment. The entity exists from day one to avoid a painful migration later.

Project

Field Type Description
id UUID Unique identifier
name string Project name
workspace_id workspace_id Parent workspace
root_id node_id Root node of decomposition tree
repos repo_ref[] Connected repositories
created_at timestamp Creation time
members actor_id[] Users and agents with project-level access