Files
create/docs/src/silo-server/DAG.md
Kindred Bot d497471ed3
Some checks failed
Deploy Docs / build-and-deploy (push) Successful in 48s
Build and Test / build (push) Has been cancelled
docs: sync Silo server documentation
Auto-synced from kindred/silo main branch.
2026-02-15 23:12:55 +00:00

9.7 KiB

Dependency DAG Specification

Status: Draft Last Updated: 2026-02-13


1. Purpose

The Dependency DAG is a server-side graph that tracks how features, constraints, and assembly relationships depend on each other. It enables three capabilities described in MULTI_USER_EDITS.md:

  1. Interference detection -- comparing dependency cones of concurrent edit sessions to classify conflicts as none, soft, or hard before the user encounters them.
  2. Incremental validation -- marking changed nodes dirty and propagating only through the affected subgraph, using input-hash memoization to stop early when inputs haven't changed.
  3. Structured merge safety -- walking the DAG to determine whether concurrent edits share upstream dependencies, deciding if auto-merge is safe or manual review is required.

2. Two-Tier Model

Silo maintains two levels of dependency graph:

2.1 BOM DAG (existing)

The assembly-to-part relationship graph already stored in the relationships table. Each row represents a parent item containing a child item with a quantity and relationship type (component, alternate, reference). This graph is queried via GetBOM, GetExpandedBOM, GetWhereUsed, and HasCycle in internal/db/relationships.go.

The BOM DAG is not modified by this specification. It continues to serve its existing purpose.

2.2 Feature DAG (new)

A finer-grained graph stored in dag_nodes and dag_edges tables. Each node represents a feature within a single item's revision -- a sketch, pad, fillet, pocket, constraint, body, or part-level container. Edges represent "depends on" relationships: if Pad003 depends on Sketch001, an edge runs from Sketch001 to Pad003.

The feature DAG is populated by clients (silo-mod) when users save, or by runners after compute jobs. Silo stores and queries it but does not generate it -- the Create client has access to the feature tree and is the authoritative source.

2.3 Cross-Item Edges

Assembly constraints often reference geometry on child parts (e.g., "mate Face6 of PartA to Face2 of PartB"). These cross-item dependencies are stored in dag_cross_edges, linking a node in one item to a node in another. Each cross-edge optionally references the relationships row that establishes the BOM connection.


3. Data Model

3.1 dag_nodes

Column Type Description
id UUID Primary key
item_id UUID FK to items.id
revision_number INTEGER Revision this DAG snapshot belongs to
node_key TEXT Feature name from Create (e.g., Sketch001, Pad003, Body)
node_type TEXT One of: sketch, pad, pocket, fillet, chamfer, constraint, body, part, datum, mirror, pattern, boolean
properties_hash TEXT SHA-256 of the node's parametric inputs (sketch coordinates, fillet radius, constraint values). Used for memoization -- if the hash hasn't changed, validation can skip this node.
validation_state TEXT One of: clean, dirty, validating, failed
validation_msg TEXT Error message when validation_state = 'failed'
metadata JSONB Type-specific data (sketch coords, feature params, constraint definitions)
created_at TIMESTAMPTZ Row creation time
updated_at TIMESTAMPTZ Last state change

Uniqueness: (item_id, revision_number, node_key) -- one node per feature per revision.

3.2 dag_edges

Column Type Description
id UUID Primary key
source_node_id UUID FK to dag_nodes.id -- the upstream node
target_node_id UUID FK to dag_nodes.id -- the downstream node that depends on source
edge_type TEXT depends_on (default), references, constrains
metadata JSONB Optional edge metadata

Direction convention: An edge from A to B means "B depends on A". A is upstream, B is downstream. Forward-cone traversal from A walks edges where A is the source.

Uniqueness: (source_node_id, target_node_id, edge_type).

Constraint: source_node_id != target_node_id (no self-edges).

3.3 dag_cross_edges

Column Type Description
id UUID Primary key
source_node_id UUID FK to dag_nodes.id -- node in item A
target_node_id UUID FK to dag_nodes.id -- node in item B
relationship_id UUID FK to relationships.id (nullable) -- the BOM entry connecting the two items
edge_type TEXT assembly_ref (default)
metadata JSONB Reference details (face ID, edge ID, etc.)

Uniqueness: (source_node_id, target_node_id).


4. Validation States

Each node has a validation_state that tracks whether its computed geometry is current:

State Meaning
clean Node's geometry matches its properties_hash. No recompute needed.
dirty An upstream change has propagated to this node. Recompute required.
validating A compute job is currently revalidating this node.
failed Recompute failed. validation_msg contains the error.

4.1 State Transitions

clean → dirty       (upstream change detected, or MarkDirty called)
dirty → validating  (compute job claims this node)
validating → clean  (recompute succeeded, properties_hash updated)
validating → failed (recompute produced an error)
failed → dirty      (upstream change detected, retry possible)
dirty → clean       (properties_hash matches previous -- memoization shortcut)

4.2 Dirty Propagation

When a node is marked dirty, all downstream nodes in its forward cone are also marked dirty. This is done atomically in a single recursive CTE:

WITH RECURSIVE forward_cone AS (
    SELECT $1::uuid AS node_id
    UNION
    SELECT e.target_node_id
    FROM dag_edges e
    JOIN forward_cone fc ON fc.node_id = e.source_node_id
)
UPDATE dag_nodes SET validation_state = 'dirty', updated_at = now()
WHERE id IN (SELECT node_id FROM forward_cone)
  AND validation_state = 'clean';

4.3 Memoization

Before marking a node dirty, the system can compare the new properties_hash against the stored value. If they match, the change did not affect this node's inputs, and propagation stops. This is the memoization boundary described in MULTI_USER_EDITS.md Section 5.2.


5. Graph Queries

5.1 Forward Cone

Returns all nodes downstream of a given node -- everything that would be affected if the source node changes. Used for interference detection: if two users' forward cones overlap, there is potential interference.

WITH RECURSIVE forward_cone AS (
    SELECT target_node_id AS node_id
    FROM dag_edges WHERE source_node_id = $1
    UNION
    SELECT e.target_node_id
    FROM dag_edges e
    JOIN forward_cone fc ON fc.node_id = e.source_node_id
)
SELECT n.* FROM dag_nodes n JOIN forward_cone fc ON n.id = fc.node_id;

5.2 Backward Cone

Returns all nodes upstream of a given node -- everything the target node depends on.

5.3 Dirty Subgraph

Returns all nodes for a given item where validation_state != 'clean', along with their edges. This is the input to an incremental validation job.

5.4 Cycle Detection

Before adding an edge, check that it would not create a cycle. Uses the same recursive ancestor-walk pattern as HasCycle in internal/db/relationships.go.


6. DAG Sync

Clients push the full feature DAG to Silo via PUT /api/items/{partNumber}/dag. The sync payload is a JSON document:

{
  "revision": 3,
  "nodes": [
    {
      "key": "Sketch001",
      "type": "sketch",
      "properties_hash": "a1b2c3...",
      "metadata": {
        "coordinates": [[0, 0], [10, 0], [10, 5]],
        "constraints": ["horizontal", "vertical"]
      }
    },
    {
      "key": "Pad003",
      "type": "pad",
      "properties_hash": "d4e5f6...",
      "metadata": {
        "length": 15.0,
        "direction": [0, 0, 1]
      }
    }
  ],
  "edges": [
    {
      "source": "Sketch001",
      "target": "Pad003",
      "type": "depends_on"
    }
  ]
}

The server processes this within a single transaction:

  1. Upsert all nodes (matched by item_id + revision_number + node_key).
  2. Replace all edges for this item/revision.
  3. Compare new properties_hash values against stored values to detect changes.
  4. Mark changed nodes and their forward cones dirty.
  5. Publish dag.updated SSE event.

7. Interference Detection

When a user registers an edit context (MULTI_USER_EDITS.md Section 3.1), the server:

  1. Looks up the node(s) being edited by node_key within the item's current revision.
  2. Computes the forward cone for those nodes.
  3. Compares the cone against all active edit sessions' cones.
  4. Classifies interference:
    • No overlap → no interference, fully concurrent.
    • Overlap, different objects → soft interference, visual indicator via SSE.
    • Same object, same edit type → hard interference, edit blocked.

8. REST API

All endpoints are under /api/items/{partNumber} and require authentication.

Method Path Auth Description
GET /dag viewer Get full feature DAG for current revision
GET /dag/forward-cone/{nodeKey} viewer Get forward dependency cone
GET /dag/dirty viewer Get dirty subgraph
PUT /dag editor Sync full feature tree (from client or runner)
POST /dag/mark-dirty/{nodeKey} editor Manually mark a node and its cone dirty

9. References