# Task Dependencies

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 129  
**Tier:** Business+  
**Depends on:** `tasks-board-engine`, `projects-module`, `foundation-auth-rbac`  
**Referenced by:** `tasks-board-engine`

---

## Overview

Spec 11 (`tasks-board-engine`) has Timeline/Gantt view but no dependency model. This spec adds task blocking relationships (task A blocks task B), critical path visualization on the Gantt, and dependency enforcement at status-change time.

---

## Dependency Model

A dependency is a directed edge: **Task A blocks Task B**.

- Task B cannot be moved to any column until all blocking tasks (Task A) have a status with `is_terminal = true` (spec 11 `task_statuses.is_terminal`).
- Multiple blockers allowed: Task C blocked by A AND B.
- No cycles allowed (enforced at create time).

```sql
CREATE TABLE task_dependencies (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL,
  blocking_task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  blocked_task_id  UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  created_by UUID NOT NULL REFERENCES users(id),
  created_at TIMESTAMPTZ DEFAULT now(),
  UNIQUE (blocking_task_id, blocked_task_id),
  CHECK (blocking_task_id <> blocked_task_id)
);

CREATE INDEX idx_td_blocking ON task_dependencies(blocking_task_id);
CREATE INDEX idx_td_blocked  ON task_dependencies(blocked_task_id);
```

---

## Adding Dependencies

### On Task Detail

New **Dependencies** section on task detail sheet:

```
┌──────────────────────────────────────────────────────────────┐
│  Dependencies                                  [+ Add]       │
│                                                              │
│  Blocked by:                                                 │
│  ● Design mockups  Done        ✓ Unblocked                   │
│  ● API spec        In Progress  ⏳ Waiting                   │
│                                                              │
│  Blocks:                                                     │
│  ● QA testing      To Do        (waiting on this task)       │
└──────────────────────────────────────────────────────────────┘
```

(Column names shown are examples — actual names are tenant-configured. "Done" column = `is_terminal = true`.)

**[+ Add]** → search tasks in project → select → creates dependency:
- "This task is blocked by [selected]" or "This task blocks [selected]" — radio choice in picker.

Cycle detection server-side: `POST /api/tasks/:id/dependencies` returns `409 Conflict` if adding the dependency would create a cycle.

---

## Blocking Enforcement

When user tries to move a blocked task to any column and not all blocking tasks have `is_terminal = true` status:

```
┌──────────────────────────────────────────────────────────────┐
│  ⚠ Task is blocked                                           │
│                                                              │
│  This task is waiting on:                                    │
│  • API spec (In Progress — Dana Cohen)                        │
│                                                              │
│  Complete the blocking tasks before moving this one.         │
│                                                              │
│  [Cancel]        [Move anyway] ← override for OWNER/ADMIN    │
└──────────────────────────────────────────────────────────────┘
```

**[Move anyway]** available to OWNER/ADMIN only. Server records the override in `PATCH /api/tasks/:id` response metadata; clients may surface it in activity feeds.

---

## Kanban View — Blocked Indicator

Blocked tasks show a chain icon badge on Kanban cards:

```
┌──────────────────────────────────────┐
│  🔗 Build checkout page       HIGH   │
│  Dana Cohen · 3 sp                   │
│  ⏳ Blocked by: API spec              │
└──────────────────────────────────────┘
```

Muted card border (dashed) for blocked tasks.

---

## Gantt View — Dependencies and Critical Path

On the Gantt (Timeline view in spec 11):

### Dependency Arrows

Connecting lines drawn between dependent tasks:

```
Task A  ████████████────────────────────────────────
                    ↘ (finish-to-start arrow)
Task B              ████████████
```

Arrow color:
- Gray: non-critical dependency
- Red: critical path dependency

### Critical Path

Critical path = longest chain of dependent tasks. Highlighted with red task bars + red arrows.

Toggle: **[Critical path]** button in Gantt toolbar — on/off.

Critical path computed server-side using topological sort + longest path algorithm. Result cached per project revision (invalidated when tasks or dependencies change).

---

## Dependency Validation Rules

| Rule | Enforcement |
|------|-------------|
| No self-dependency | `CHECK (blocking_task_id <> blocked_task_id)` |
| No cycles | Server-side DFS on `POST /api/tasks/:id/dependencies`; returns 409 |
| Cross-project | Not allowed — both tasks must share `project_id` |
| No deps on archived tasks | `DELETE CASCADE` clears deps when task archived |

---

## API

```
GET /api/tasks/:id/dependencies
    → get blocking and blocked-by tasks for this task
      Returns: { blocking: Task[], blocked_by: Task[] }
      Requires: projects:read (Business+)

POST /api/tasks/:id/dependencies
     → add dependency
       body: { blocking_task_id?: string, blocked_task_id?: string }
             (supply one: either this task blocks another, or another blocks this)
       Returns: { id, blocking_task_id, blocked_task_id }
       Returns 409 if cycle detected
       Requires: projects:write (Business+)

DELETE /api/tasks/:id/dependencies/:dependencyId
       → remove dependency
         Requires: projects:write (Business+)

GET /api/projects/:id/critical-path
    → compute critical path for project
      Returns: { critical_task_ids: string[], critical_dependency_ids: string[] }
      Requires: projects:read (Business+)
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Separate join table | Not JSONB on tasks | Dependencies are a many-to-many relationship; join table enables indexed queries in both directions without scanning JSONB |
| Finish-to-start only | Not all 4 dependency types (FS/SS/FF/SF) | FS covers 90% of real-world project dependencies; SS/FF/SF add complexity without proportional value; add later if needed |
| Server-side cycle detection | Not client-side | Server is authoritative; cycle detection in a directed graph requires O(V+E) traversal — reliable only at the DB write boundary |
| Critical path server-side | Not client-side | Requires full project graph access; server computes once, caches the result; avoids sending full dependency graph to every client |
| Business+ tier | Not all tiers | Dependency modeling is a project management feature; small freelancers (Freelancer tier) have simple task lists; dependency complexity starts at team scale |
