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