127 lines
6.3 KiB
Markdown
127 lines
6.3 KiB
Markdown
---
|
|
id: "01-core-dependency-relationship"
|
|
title: "Promote dependency edges to a core relationship"
|
|
status: done
|
|
wave: 1
|
|
depends_on: []
|
|
plan: "plan.md"
|
|
spec: "../../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
|
|
|
|
```bash
|
|
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`.
|