1f38b6b435
Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.3 KiB
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/task-dependencies/spec.md |
Task 01: Promote Dependency Edges to a Core Relationship
Acceptance
- The dependency edge store is reachable in a Kanban-only install:
SetBlockerRepositoryis wired before theFeatures.Officeearly return ininternal/backendapp/main.go, socreate_taskwithblocked_bysucceeds andlist_related_tasks_kandevpopulatesblockers/blocked_bywithFeatures.Officefalse. - 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.checkCircularBlockerinservice_office.gois deleted, not left as a second path. - A
(blocker_task_id)index ontask_blockersis added through an idempotent migration ininternal/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 CASCADEthe table does not have. Archiving does not: an archived predecessor still blocks its dependents and reads aspending(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, usingstate = COMPLETEDorIsTerminalStepNamefor resolved andFAILED/CANCELLEDfor failed. Archived predecessors arepending. 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, andstart_when_unblocked. Nois_blockedcolumn is persisted. depends_onandblocksentries 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.POSTreturns409with acyclearray on a cycle and400on a self-edge or cross-workspace edge;DELETEof 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.updatedwithfields: ["dependencies"]is published on edge add and remove, through the task event publisher (not a bare repository write).POST /api/v1/tasksandcreate_task_kandevacceptstart_when_unblocked; when true and the request declares at least one dependency, the resolved launch inputs are persisted asmetadata.deferred_launchin the same atomic boundary as the task row and no session, workspace, or executor is prepared.start_when_unblocked: truewith 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 staytask_id/session_idcompatible with the gateway dispatch backstop.
TDD sequence
- Failing test: with
Features.Officefalse,CreateTaskwithblocked_bysucceeds andlist_related_tasks_kandevreturns the edge. - Failing tests for the shared validator: cycles of length 2, 3, and N return
*BlockerCycleErrorwith 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. - 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. - Failing DTO/event tests for the five new fields on HTTP, boot, WS, and MCP,
including the id/title/state shape of
depends_onandblocksentries. - Failing handler tests for the three routes including the
409cyclebody shape and the authorization denials. - 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. - Implement: wiring move, migration/index, validator consolidation, batch
helper, DTO fields, routes, event publication,
start_when_unblockedpersistence.
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.goapps/backend/internal/task/repository/sqlite/base_migrations.goapps/backend/internal/task/repository/sqlite/task.goapps/backend/internal/task/service/service_office.goapps/backend/internal/task/service/service_tasks.goapps/backend/internal/task/service/service_requests.goapps/backend/internal/task/service/handoff_service.goapps/backend/internal/task/dto/dto.go,requests.goapps/backend/internal/task/handlers/task_http_handlers.goapps/backend/internal/office/dashboard/service_tasks.goapps/backend/internal/mcp/handlers/handlers.goapps/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.