akif/arclet-boards
Arclet Boards — private Kanban workspaces with AppID, DataK3, shared web and MCP actions.
A remix template · 1 remixes · Remix akif/arclet-boards
app/domain/dependencies.py
"""Dependency graph rules. Edges point from a card to its prerequisite."""
from app.domain.model import Invalid
def require_acyclic(edges, card_id, depends_on_id):
if card_id == depends_on_id:
raise Invalid("A card cannot depend on itself")
graph = {}
for edge in edges:
graph.setdefault(edge["card_id"], []).append(edge["depends_on_id"])
pending, seen = [depends_on_id], set()
while pending:
current = pending.pop()
if current == card_id:
raise Invalid("That dependency would create a cycle")
if current not in seen:
seen.add(current)
pending.extend(graph.get(current, []))
def blocking_paths(cards, edges, root_id, done_list_ids):
graph = {}
for edge in edges:
target = cards.get(edge["depends_on_id"])
if target and target["list_id"] not in done_list_ids:
graph.setdefault(edge["card_id"], []).append(target["id"])
pending, paths, truncated = [[root_id]], [], False
while pending and len(paths) < 100:
path = pending.pop()
children = sorted(graph.get(path[-1], []))
if not children or len(path) >= 64:
if len(path) > 1:
paths.append(path)
truncated |= bool(children)
else:
for child in children:
if child in path:
raise Invalid("Stored dependency cycle detected")
pending.append(path + [child])
return paths, bool(pending) or truncated