298 lines
15 KiB
Markdown
298 lines
15 KiB
Markdown
# 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 |
|