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.
- In self-hosted mode, namespaces auto-create on first access.
- In hosted mode, namespaces require explicit creation via
POST /v1/namespaces(see configuration.md). - Deletion via
DELETE /v1/namespaces/{namespace}removes the namespace and all data.
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):
id: Unique string within the namespace (lowercase alphanumeric + hyphens; no/).label: Human-readable name (optional).state: One ofgreen,yellow,red(defaults toyellow).default_state: Initial state for YAML imports (optional).meta: JSON object for custom data (e.g.,{"url": "https://example.com"}).reason,solution: Optional strings for failures (set viaX-Reason/X-Solutionheaders).ttl: Seconds until expiry (optional; parsed from"10m","1h"via [parseTtl](../src/db.ts)).last_state_write: Timestamp of last state update (resets TTL clock).state_changed_at,updated_at: Timestamps.
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.
- Set via
depends_onarray in node PUT. - Auto-creates missing dependencies as
yellownodes. - Cycles rejected (409): [wouldCreateCycle](../src/graph/cycle.ts) traverses from
to_nodeto detect paths back tofrom_node.
Edges support transitive traversal (BFS) for effective states and subgraphs.
Effective State
A node's effective_state is the worst (highest priority) among:
- Its resolved own
state(TTL-adjusted). - 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
- Graph APIs:
GET /v1/graph/{namespace}exports nodes/edges with effective states (see graph-computation.md, graph-visualization.md). - Notifications: Trigger on
effective_statechanges (see notifications.md). - YAML: Declarative spec imports structure, preserves states (see configuration.md).
- Events: Audit trail in
eventstable (see events-auditing.md). - Schema: Full details in database-schema.md.
This model scales via SQLite WAL: concurrent reads during traversals, single-writer safety.
Recent changes
- Created: New page core-model.md explains depends.cc's DAG-based core model in namespaces