# 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 |