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

241 lines
10 KiB
Markdown

# 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 (`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
## Association Graph (Lateral Links)
### Link Types
| 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.
### Backlinks (Inbound Link Surfacing)
Every node surfaces inbound lateral links: "3 tasks blocked by this," "2 components depend on this."
### Cross-Project Links (v0.2+)
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 |