241 lines
10 KiB
Markdown
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 |
|