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

15 KiB

Non-Linear: Data Model

Overview

The data model is organized as four layers stacked on a shared node topology — like layers in a GIS or Photoshop. Each layer answers a different question, has its own update cadence, and can be toggled independently in the UI and the agent API.

Layer Name Question Update Cadence
1 Structure What is the project made of? Stable — changes with major refactors
2 Work What needs to be done? High churn — changes daily
3 Code Connections How do parts depend on each other technically? Changes with the codebase
4 Artifacts What context exists around this? Updated as docs/designs evolve

Two fundamental graph structures span the layers:

  1. Decomposition Tree — strict parent→child hierarchy (DAG). The spine of the product. Carries Layer 1 (component→component edges) and the attachment of Layer 2 nodes to Layer 1 nodes.
  2. Overlay Edges — lateral many-to-many connections with typed edges. Each edge type belongs to a specific layer (work coordination links in Layer 2, technical dependency links in Layer 3, artifact attachments in Layer 4).

The layers share the same spatial structure but carry different information. Layer 1 (Structure) is the base layer — all other layers attach to it.

Node Types

The graph has three node types: components, issues, and artifacts. Components form the skeleton (Layer 1); issues are work attached to that skeleton (Layer 2); artifacts are external context attached to any node (Layer 4). Alpha ships with components and issues only; artifacts are introduced post-alpha.

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

Artifact Node (Layer 4 — post-alpha)

An artifact represents external context — a document, design file, screenshot, or link — attached to any component or issue.

Field Type Description
id UUID Unique identifier
title string Display name
kind enum link, file, embed
url string? External URL (Figma, Google Docs, Confluence, etc.)
file_ref s3_key? MinIO object key (for uploaded files)
mime_type string? MIME type (for uploaded files)
size_bytes int? File size (for uploaded files)
attached_to node_id Component or issue this artifact belongs to
created_by actor_id Uploader / linker
created_at timestamp Creation time

Artifacts do not participate in the decomposition tree — they have no parent/child relationships of their own. They attach to components or issues via a dedicated HAS_ARTIFACT edge and are purely a Layer 4 overlay.

How They Relate

Project (root)                                         Layer 1: Structure
├── Component: auth-service          (→ repo)         ─────────────────
│   ├── Component: oauth-module      (→ dir)
│   │   ├── Issue: implement refresh tokens            Layer 2: Work
│   │   └── Issue: fix token expiry bug               ────────────────
│   │       └── Artifact: "error-screenshot.png"       Layer 4: Artifacts
│   └── Component: session-manager   (→ dir)          ─────────────────
│       └── Issue: add Redis session store
├── Component: frontend-app          (→ repo)
│   ├── Component: dashboard         (→ dir)
│   │   └── Artifact: "dashboard-figma-mockup" (link)  Layer 4
│   └── Component: auth-ui           (→ dir)
│       └── Issue: redesign login page
│
│  [Layer 3 — Code Connections (not shown in tree, these are lateral edges):]
│  auth-service ──IMPORTS──► jwt-lib
│  frontend-app ──CALLS_API──► auth-service

Components are the skeleton (Layer 1) — stable structure that mirrors your codebase. Issues are the work (Layer 2) — they flow through statuses, get assigned, join cycles. Artifacts are the context (Layer 4) — docs, designs, and media that surround the work. Code connections (Layer 3) are lateral edges between components representing technical dependencies. 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

Lateral links are typed edges that do not belong to the decomposition tree. Each link type belongs to a specific layer.

These connect issues to other issues and describe work-level relationships.

Type Semantics Directionality Between
blocks A blocks B Directed Issue → Issue
blocked_by Inverse of blocks Directed (auto-created) Issue → Issue
duplicates A is a duplicate of B Directed Issue → Issue
relates_to General work association Undirected Issue ↔ Issue

Layer 3: Code Connection Links (post-alpha)

These connect components to other components and describe technical dependencies. They can be auto-inferred from code analysis (import graphs, API routes, shared infrastructure) or declared manually. Layer 3 is introduced post-alpha once the component skeleton is stable and teams express a need for dependency visibility.

Type Semantics Directionality Between
depends_on A depends on B at build/runtime Directed Component → Component
imports A imports code from B Directed Component → Component
calls_api A calls B's API at runtime Directed Component → Component
shares_db A and B share a database Undirected Component ↔ Component

Auto-inference sources for Layer 3 links:

  • Import graphs: static analysis of import, require, use statements across repo boundaries
  • API route matching: outbound HTTP calls matched to known service endpoints
  • Shared infrastructure: components linked to the same database, queue, or cache

Auto-inferred links are tagged source: inferred and can be overridden or dismissed manually.

Layer 4: Artifact Attachment Links (post-alpha)

These connect artifacts to the nodes they provide context for. Represented by the HAS_ARTIFACT edge. Introduced alongside Artifact nodes post-alpha.

Every node surfaces inbound lateral links from all layers: "3 tasks blocked by this" (Layer 2), "2 components depend on this" (Layer 3), "1 design attached" (Layer 4). Backlink counts are shown per-layer when layer toggles are active.

Deferred. Will enable cross-project/cross-repo dependency tracking across all layers.

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