Files
ClaudeDo/src/ClaudeDo.Data/Migrations/20260902121712_AddTaskDependencies.cs
T
Mika Kuns c25732cff8 feat(claude-do): depends_on als Menge statt Einzelfeld
Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte
tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies
ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das
alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder
pickbar). BlockedByTaskId bleibt unberuehrt.

- Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id
  verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird
- QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten
  wird blockiert, alle Kanten muessen done sein, eine Kante auf einen
  verschwundenen Task blockiert weiter
- SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine
  BFS ueber den Graphen, faengt also auch A->B->C->A
- blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von
  ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt)
- add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt
  als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig
- UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label,
  die lineare Kette rendert unveraendert
2026-09-02 15:08:05 +02:00

105 lines
4.1 KiB
C#

using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ClaudeDo.Data.Migrations
{
/// <summary>
/// Turns the single <c>tasks.depends_on_task_id</c> column into a set: one row per declared
/// "run after" edge in <c>task_dependencies</c>. Hand-edited after scaffolding so the data copy
/// sits between CreateTable and DropColumn — the scaffolded order dropped the column first and
/// would have discarded every existing link.
/// </summary>
public partial class AddTaskDependencies : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "task_dependencies",
columns: table => new
{
task_id = table.Column<string>(type: "TEXT", nullable: false),
depends_on_task_id = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_task_dependencies", x => new { x.task_id, x.depends_on_task_id });
table.ForeignKey(
name: "FK_task_dependencies_tasks_depends_on_task_id",
column: x => x.depends_on_task_id,
principalTable: "tasks",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_task_dependencies_tasks_task_id",
column: x => x.task_id,
principalTable: "tasks",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "idx_task_dependencies_depends_on",
table: "task_dependencies",
column: "depends_on_task_id");
// Every existing link becomes a one-element set. The old FK was ON DELETE SET NULL, so
// a surviving value always points at an existing task and can't violate the new FK.
migrationBuilder.Sql("""
INSERT INTO task_dependencies (task_id, depends_on_task_id)
SELECT id, depends_on_task_id FROM tasks WHERE depends_on_task_id IS NOT NULL
""");
migrationBuilder.DropForeignKey(
name: "FK_tasks_tasks_depends_on_task_id",
table: "tasks");
migrationBuilder.DropIndex(
name: "idx_tasks_depends_on",
table: "tasks");
migrationBuilder.DropColumn(
name: "depends_on_task_id",
table: "tasks");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "depends_on_task_id",
table: "tasks",
type: "TEXT",
nullable: true);
// Inherently lossy: a task with several predecessors keeps only one. There is no
// single-column shape that could hold the rest.
migrationBuilder.Sql("""
UPDATE tasks SET depends_on_task_id = (
SELECT td.depends_on_task_id FROM task_dependencies td
WHERE td.task_id = tasks.id
ORDER BY td.depends_on_task_id
LIMIT 1
)
""");
migrationBuilder.DropTable(
name: "task_dependencies");
migrationBuilder.CreateIndex(
name: "idx_tasks_depends_on",
table: "tasks",
column: "depends_on_task_id");
migrationBuilder.AddForeignKey(
name: "FK_tasks_tasks_depends_on_task_id",
table: "tasks",
column: "depends_on_task_id",
principalTable: "tasks",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
}
}
}