Core Model

The core model of depends.cc forms a directed acyclic graph (DAG) known as a depends tree. Each tree resides within a namespace and models dependencies between nodes. Nodes represent arbitrary entities such as services, tasks, deployments, or build artifacts. Services push their states into the system; depends.cc computes effective states on demand and propagates changes via notifications.

This design ensures simplicity: depends.cc acts as a passive receiver, avoiding polling or proactive checks. States propagate transitively through dependencies without requiring updates to dependent nodes. Effective states are computed dynamically during reads, not stored, to guarantee consistency and minimize storage.

Namespaces

Namespaces provide top-level isolation for depends trees. A namespace groups related nodes and edges, preventing cross-contamination (e.g., acme-corp for one team, my-project for another). Full node identifiers use the form namespace/node-id.

Namespaces map to rows in the namespaces table, with foreign keys cascading deletes to child data.

Nodes

A node is a named entity with a configurable state, label, metadata, and optional dependencies. Nodes auto-create on first state update or reference.

Key fields (from nodes table):

State Updates

Full updates use PUT /v1/nodes/{namespace}/{node-id} with JSON body (patch semantics: optional fields):

{
  "state": "green",
  "label": "Payment Service",
  "depends_on": ["database", "auth-service"],
  "ttl": "10m",
  "meta": { "url": "https://pay.example.com", "owner": "backend-team" },
  "reason": "disk full",
  "solution": "clear logs"
}

Shorthand for state-only: PUT /v1/state/{namespace}/{node-id}/{state} (no body; 204 No Content):

curl -X PUT https://depends.cc/v1/state/acme/api-server/red \
  -H "Authorization: Bearer $DEPENDS_TOKEN" \
  -H "X-Reason: disk full" \
  -H "X-Solution: df -h"

TTL expiry checks occur during effective state computation: if state is green, elapsed > ttl, sets to yellow (never red).

Retrieval

GET /v1/nodes/{namespace}/{node-id} returns the node with computed effective_state, depends_on, depended_on_by:

{
  "id": "payment-service",
  "namespace": "acme",
  "state": "green",
  "effective_state": "red",
  "depends_on": ["database", "auth-service"],
  "depended_on_by": ["checkout-flow"],
  ...
}

List all: GET /v1/nodes/{namespace}.

Dependencies (Edges)

Dependencies form directed edges: "from_node" depends_on "to_node" (A → B means A's state worsens if B worsens). Stored in edges table.

Edges support transitive traversal (BFS) for effective states and subgraphs.

Effective State

A node's effective_state is the worst (highest priority) among:

  1. Its resolved own state (TTL-adjusted).
  2. Effective states of all transitive dependencies.

Priorities: red (2) > yellow (1) > green (0).

// src/graph/effective.ts
const STATE_PRIORITY: Record<string, number> = {
  green: 0, yellow: 1, red: 2,
};

function worstState(a: string, b: string): string {
  return STATE_PRIORITY[a] >= STATE_PRIORITY[b] ? a : b;
}

export function computeEffectiveState(db: Database, nsId: number, nodeId: string): string {
  // Fetch node, resolve TTL
  let worst = resolveNodeState(node);
  // BFS queue over dependencies
  const visited = new Set<string>();
  const queue = [nodeId];
  while (queue.length > 0) {
    // ... fetch deps, worst = worstState(worst, depEffective)
  }
  return worst;
}

Computed on every read (node GET, graph, notifications). Not stored: ensures freshness, avoids staleness on concurrent writes. For efficiency, uses indexed queries (idx_edges_to_node).

Design: Propagation is read-time, not write-time. A dependency change affects dependents instantly on next query. State writes log to events table for history.

Integration

This model scales via SQLite WAL: concurrent reads during traversals, single-writer safety.

Recent changes