Files
kandev/docs/plans/task-dependencies/task-01-core-dependency-relationship.md
T
2026-08-23 17:34:52 +00:00

6.3 KiB

id, title, status, wave, depends_on, plan, spec
id title status wave depends_on plan spec
01-core-dependency-relationship Promote dependency edges to a core relationship done 1
plan.md ../../specs/tasks/system-design/task-dependencies.md

Task 01: Promote Dependency Edges to a Core Relationship

Acceptance

  • The dependency edge store is reachable in a Kanban-only install: SetBlockerRepository is wired before the Features.Office early return in internal/backendapp/main.go, so create_task with blocked_by succeeds and list_related_tasks_kandev populates blockers / blocked_by with Features.Office false.
  • Exactly one edge validator exists. Self-edge, cross-workspace, and BFS cycle detection (with the existing walk limit and *BlockerCycleError{Path}) live in the task domain, and both the new task-scoped routes and the existing Office routes call it. checkCircularBlocker in service_office.go is deleted, not left as a second path.
  • A (blocker_task_id) index on task_blockers is added through an idempotent migration in internal/task/repository/sqlite/base_migrations.go.
  • Deleting a task removes its edges in both directions, on SQLite and on PostgreSQL, via explicit repository work rather than an ON DELETE CASCADE the table does not have. Archiving does not: an archived predecessor still blocks its dependents and reads as pending (Task 04), so its edges survive and reappear if the task is unarchived.
  • One batch service helper resolves, for a list of task IDs, each task's direct predecessors and direct dependents, and a per-predecessor verdict of resolved / failed / pending, using state = COMPLETED or IsTerminalStepName for resolved and FAILED / CANCELLED for failed. Archived predecessors are pending. The reverse direction uses the (blocker_task_id) index added above; it is one batched query, not one per task.
  • Task DTOs over HTTP, WebSocket boot, WebSocket events, and MCP carry blocked, blocked_reason, depends_on, blocks, and start_when_unblocked. No is_blocked column is persisted.
  • depends_on and blocks entries carry id, title, and state so the dependency chip renders without a per-entry fetch. Neither list is transitive.
  • New task-scoped routes exist and behave as specified: POST /api/v1/tasks/:id/dependencies, DELETE /api/v1/tasks/:id/dependencies/:depId. POST returns 409 with a cycle array on a cycle and 400 on a self-edge or cross-workspace edge; DELETE of an absent edge succeeds. Both return the mutated task's dependency projection so a caller does not re-fetch. There is no graph-wide listing route: every reader gets its edges from the dependency fields already on the task payload.
  • task.updated with fields: ["dependencies"] is published on edge add and remove, through the task event publisher (not a bare repository write).
  • POST /api/v1/tasks and create_task_kandev accept start_when_unblocked; when true and the request declares at least one dependency, the resolved launch inputs are persisted as metadata.deferred_launch in the same atomic boundary as the task row and no session, workspace, or executor is prepared. start_when_unblocked: true with no dependencies records no intent: the task is already unblocked, so an agent-start request on it launches immediately exactly as it would without the flag, and a create with neither launches nothing. The flag defers a start; it never invents one.
  • Edge routes authorize both tasks with the existing authorize* helpers, and the payload field names stay task_id / session_id compatible with the gateway dispatch backstop.

TDD sequence

  1. Failing test: with Features.Office false, CreateTask with blocked_by succeeds and list_related_tasks_kandev returns the edge.
  2. Failing tests for the shared validator: cycles of length 2, 3, and N return *BlockerCycleError with the full path; self-edge and cross-workspace return the typed errors; a duplicate edge is idempotent; both the task-scoped and Office entry points hit the same validator.
  3. Failing tests for the batch derived-state helper across pending, resolved-by-state, resolved-by-terminal-step, failed, cancelled, and archived predecessors, asserting the batch result equals the per-task result, and that the dependents (blocks) direction is returned for the same call.
  4. Failing DTO/event tests for the five new fields on HTTP, boot, WS, and MCP, including the id/title/state shape of depends_on and blocks entries.
  5. Failing handler tests for the three routes including the 409 cycle body shape and the authorization denials.
  6. Failing tests for edge cleanup on task delete, for edge survival across archive, and for zero-dependency start_when_unblocked, plus the environment-gated PostgreSQL equivalents.
  7. Implement: wiring move, migration/index, validator consolidation, batch helper, DTO fields, routes, event publication, start_when_unblocked persistence.

Verification

cd apps/backend
go test -tags fts5 ./internal/task/... ./internal/office/dashboard ./internal/mcp/... ./internal/backendapp -run 'Test.*(Dependenc|Blocker|BlockedBy|StartWhenUnblocked)' -count=1
golangci-lint run ./... --new-from-rev=origin/main --timeout=5m

Files likely touched

  • apps/backend/internal/backendapp/main.go
  • apps/backend/internal/task/repository/sqlite/base_migrations.go
  • apps/backend/internal/task/repository/sqlite/task.go
  • apps/backend/internal/task/service/service_office.go
  • apps/backend/internal/task/service/service_tasks.go
  • apps/backend/internal/task/service/service_requests.go
  • apps/backend/internal/task/service/handoff_service.go
  • apps/backend/internal/task/dto/dto.go, requests.go
  • apps/backend/internal/task/handlers/task_http_handlers.go
  • apps/backend/internal/office/dashboard/service_tasks.go
  • apps/backend/internal/mcp/handlers/handlers.go
  • apps/backend/pkg/api/v1/task.go
  • focused service, repository, handler, DTO, and MCP tests

Dependencies

None.

Parallelism

sequential

Output contract

Mark this task in_progress before the RED tests and done only after the listed commands pass. Record where the single validator now lives, the exact migration/index statements, the batch helper's query shape, and test results in this file and plan.md.