# 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). ### 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) 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 (`done` → `in_progress`) and skipping stages (`backlog` → `done`) 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 ### Repo Link 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 ## Overlay Edges (Lateral Links) Lateral links are typed edges that do not belong to the decomposition tree. Each link type belongs to a specific layer. ### Layer 2: Work Coordination Links 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 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. | 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 These connect artifacts to the nodes they provide context for. Represented by the `HAS_ARTIFACT` edge in the decomposition tree (see Artifact Node above). ### Backlinks (Inbound Link Surfacing) 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. ### Cross-Project Links (v0.2+) 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 |