Skip to content

ALP Specification — Dependency Graph ​

Version: 80.0.0 Status: Stable


1. DAG Structure ​

mermaid
flowchart TD
    A[Task A] -->|blocks| B[Task B]
    A -->|requires| C[Task C]
    B -->|extends| D[Task D]
    C -->|uses| D

    style A fill:#ff9999
    style B fill:#ff9999
    style C fill:#ffcc99
    style D fill:#99ff99

2. Overview ​

ALP automatically builds a dependency graph from relationships declared across all .alp files. The graph is the foundation for:

  • Execution ordering — determines which tasks must complete before others can start
  • Impact analysis — when something changes, what else is affected
  • Context loading — finding related objects for a given task
  • Progress tracking — understanding what's blocking completion
  • Cycle detection — preventing impossible dependency chains

2. Graph Structure ​

The dependency graph is a directed acyclic graph (DAG) where:

  • Nodes are protocol objects (features, tasks, workflows, resources, etc.)
  • Edges are typed relationships between objects

2.1 Edge Types ​

TypeSymbolMeaningExecution Impact
blocks|blocksSource cannot start until target completesHard ordering dependency
requires|requiresSource needs target to exist (not necessarily complete)Soft ordering dependency
extends|extendsSource extends target's functionalityNo ordering impact
uses|usesSource uses target at runtimeNo ordering impact
implements|implementsSource implements target's specificationNo ordering impact

2.2 Ordering Dependencies ​

Only blocks and requires edges affect execution ordering:

  • blocks: Task A blocks Task B means B cannot start until A status is [x]
  • requires: Task A requires Task B means B cannot start until A exists and has status [~] or [x]

All other edge types are informational — they build the relationship graph but don't constrain execution order.


3. Declaring Dependencies ​

Dependencies can be declared in two ways:

3.1 Inline Declaration (Simple) ​

Using depends_on within a task or feature:

@task
  id: task-login-ui
  depends_on:
    - -> task-auth-api
    - -> task-design-system

Inline depends_on creates blocks type edges by default.

3.2 Typed Inline Declaration ​

Using the | qualifier for specific relationship types:

@task
  id: task-login-ui
  depends_on:
    - -> task-auth-api | blocks
    - -> task-design-tokens | requires

3.3 Standalone Declaration (Detailed) ​

Using @dependency objects for rich relationship metadata:

@dependency
  id: dep-login-needs-api
  from: -> task-login-ui
  to: -> task-auth-api
  type: blocks
  description: "Login form needs auth API endpoints to call"

3.4 Cross-Type Dependencies ​

Dependencies can span object types:

// Feature depends on another feature
@feature
  id: feat-dashboard
  depends_on:
    - -> feat-auth | requires

// Task depends on a resource
@task
  id: task-api-setup
  depends_on:
    - -> res-postgres | requires

// Workflow depends on a feature
@workflow
  id: wf-deploy
  dependencies:
    - -> feat-auth | blocks
    - -> feat-dashboard | blocks

3.5 Cross-Project Dependencies ​

In a workspace, dependencies can span across member projects using qualified references:

alp
@task
  id: task-auth-integration
  depends_on:
    - -> auth-service::task-auth-api | blocks

This ensures that execution ordering respects boundaries between distinct ALP projects. See the Multi-Project Specification for full workspace graph rules.


4. Graph Construction ​

4.1 Algorithm ​

function buildDependencyGraph(alpProject, workspaceContext = null):
    graph = new DirectedGraph()
    
    // Step 1: Add all objects as nodes (prefixing with project ID if in workspace)
    prefix = workspaceContext ? (alpProject.id + "::") : ""
    for each object in alpProject.allObjects():
        graph.addNode(prefix + object.id, object.type)
    
    // Step 2: Add edges from inline depends_on
    for each object in alpProject.allObjects():
        if object.depends_on:
            for each dep in object.depends_on:
                edgeType = dep.qualifier OR "blocks"
                // Resolve target ID based on whether it's qualified
                targetId = dep.isQualified() ? dep.raw : (prefix + dep.target)
                graph.addEdge(targetId, prefix + object.id, edgeType)
    
    // Step 3: Add edges from standalone @dependency objects
    for each dep in alpProject.dependencies:
        sourceId = dep.from.isQualified() ? dep.from.raw : (prefix + dep.from.target)
        targetId = dep.to.isQualified() ? dep.to.raw : (prefix + dep.to.target)
        graph.addEdge(targetId, sourceId, dep.type)
    
    // Step 4: Add edges from feature → task relationships
    for each feature in alpProject.features:
        for each taskRef in feature.tasks:
            targetId = taskRef.isQualified() ? taskRef.raw : (prefix + taskRef.target)
            graph.addEdge(prefix + feature.id, targetId, "contains")
    
    // Step 5: Validate — no cycles in blocks/requires edges
    if graph.hasCycle(edgeTypes: ["blocks", "requires"]):
        error("Circular dependency detected")
    
    return graph

4.2 Edge Direction Convention ​

Edges point from dependency to dependent:

task-auth-api ──blocks──→ task-login-ui

This means: task-auth-api blocks task-login-ui. task-login-ui cannot start until task-auth-api is done.


5. Execution Ordering ​

5.1 Topological Sort ​

The execution order of tasks is determined by topological sort of the blocks and requires edges:

function getExecutionOrder(graph):
    // Only consider ordering edges
    orderingGraph = graph.filterEdges(types: ["blocks", "requires"])
    return orderingGraph.topologicalSort()

5.2 Parallel Execution Groups ​

Tasks at the same depth in the topological sort can execute in parallel:

Example graph:
    task-db-schema ──blocks──→ task-auth-api ──blocks──→ task-login-ui
    task-db-schema ──blocks──→ task-user-api ──blocks──→ task-register-ui
    task-design-system ──blocks──→ task-login-ui
    task-design-system ──blocks──→ task-register-ui

Execution order:
    Group 1 (parallel): task-db-schema, task-design-system
    Group 2 (parallel): task-auth-api, task-user-api
    Group 3 (parallel): task-login-ui, task-register-ui

5.3 Priority Within Groups ​

When multiple tasks are in the same execution group, they are ordered by:

  1. priority (critical > high > medium > low)
  2. Number of downstream dependents (more dependents = higher priority)
  3. estimated_time (shorter tasks first, to unblock others faster)
  4. Alphabetical by id (deterministic tiebreaker)

6. Cycle Detection ​

6.1 Mandatory Validation ​

ALP parsers MUST detect cycles in blocks and requires edges. Cycles make execution impossible.

6.2 Detection Algorithm ​

Use depth-first search (DFS) with three-color marking:

function detectCycles(graph):
    WHITE = 0  // Not visited
    GRAY = 1   // In current path
    BLACK = 2  // Fully processed
    
    colors = {}
    for each node in graph.nodes:
        colors[node] = WHITE
    
    for each node in graph.nodes:
        if colors[node] == WHITE:
            if dfs(node, colors, graph):
                return true  // Cycle found
    
    return false

function dfs(node, colors, graph):
    colors[node] = GRAY
    
    for each neighbor in graph.getNeighbors(node, types: ["blocks", "requires"]):
        if colors[neighbor] == GRAY:
            return true  // Cycle: we've found a back edge
        if colors[neighbor] == WHITE:
            if dfs(neighbor, colors, graph):
                return true
    
    colors[node] = BLACK
    return false

6.3 Cycle Error Reporting ​

When a cycle is detected, the error MUST include:

  • The full cycle path (e.g., A → B → C → A)
  • The file and line where each dependency is declared
  • A suggestion for resolution

Example error:

ERROR: Circular dependency detected
  task-auth-api → task-user-service → task-auth-api

  Declared at:
    task-auth-api depends_on task-user-service  (features/auth.alp:15)
    task-user-service depends_on task-auth-api  (features/users.alp:22)
  
  Suggestion: Remove one of these dependencies or extract shared logic
  into a separate task that both can depend on.

7. Impact Analysis ​

7.1 Downstream Impact ​

When an object changes, find all objects that depend on it:

function getImpact(objectId, graph):
    return graph.getTransitiveDependents(objectId)

7.2 Upstream Dependencies ​

When starting work on an object, find everything it depends on:

function getDependencies(objectId, graph):
    return graph.getTransitiveDependencies(objectId)

7.3 Use Cases ​

ScenarioGraph Query
"What can I work on now?"Find tasks with no unresolved blocks dependencies
"What does this task unblock?"Get downstream dependents of task
"Why is this task blocked?"Get upstream dependencies with status ≠ [x]
"What's the critical path?"Longest path in the graph
"What's affected if I change X?"Transitive dependents of X

8. Graph Visualization ​

ALP projects SHOULD support rendering the dependency graph as text:

┌─────────────────┐     ┌─────────────────┐
│ task-db-schema   │────→│ task-auth-api    │───┐
│ [x] completed    │     │ [~] in progress  │   │
└─────────────────┘     └─────────────────┘   │
                                               ↓
┌─────────────────┐     ┌─────────────────┐
│ task-design-sys  │────→│ task-login-ui    │
│ [x] completed    │     │ [ ] pending      │
└─────────────────┘     └─────────────────┘

9. Graph Persistence ​

The dependency graph is derived from the .alp files — it is NOT stored as a separate file. Every time .alp files are parsed, the graph is reconstructed.

This ensures the graph is always consistent with the declared relationships.