Files
kandev/docs/plans/plugin-task-dependency-projection/task-01-dependency-projection.md
T
2026-09-18 14:55:51 +00:00

200 lines
9.0 KiB
Markdown

---
id: "01-dependency-projection"
title: "Expose task dependencies in the plugin and canvas data API"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-PLUGINS-TASK-DEPS-001
- REQ-PLUGINS-TASK-DEPS-002
- REQ-PLUGINS-TASK-DEPS-003
- REQ-PLUGINS-TASK-DEPS-004
- REQ-PLUGINS-TASK-DEPS-005
- REQ-PLUGINS-TASK-DEPS-006
acceptance_criteria:
- AC-PLUGINS-TASK-DEPS-001.1
- AC-PLUGINS-TASK-DEPS-001.2
- AC-PLUGINS-TASK-DEPS-001.3
- AC-PLUGINS-TASK-DEPS-001.4
- AC-PLUGINS-TASK-DEPS-001.5
- AC-PLUGINS-TASK-DEPS-001.6
- AC-PLUGINS-TASK-DEPS-001.7
- AC-PLUGINS-TASK-DEPS-001.8
- AC-PLUGINS-TASK-DEPS-001.9
- AC-PLUGINS-TASK-DEPS-001.10
- AC-PLUGINS-TASK-DEPS-001.11
- AC-PLUGINS-TASK-DEPS-001.12
- AC-PLUGINS-TASK-DEPS-001.13
- AC-PLUGINS-TASK-DEPS-001.14
- AC-PLUGINS-TASK-DEPS-001.15
- AC-PLUGINS-TASK-DEPS-001.16
- AC-PLUGINS-TASK-DEPS-001.17
- AC-PLUGINS-TASK-DEPS-001.18
- AC-PLUGINS-TASK-DEPS-002.1
- AC-PLUGINS-TASK-DEPS-002.2
- AC-PLUGINS-TASK-DEPS-002.3
- AC-PLUGINS-TASK-DEPS-002.4
- AC-PLUGINS-TASK-DEPS-002.5
- AC-PLUGINS-TASK-DEPS-003.1
- AC-PLUGINS-TASK-DEPS-003.2
- AC-PLUGINS-TASK-DEPS-003.3
- AC-PLUGINS-TASK-DEPS-003.4
- AC-PLUGINS-TASK-DEPS-003.5
- AC-PLUGINS-TASK-DEPS-003.6
- AC-PLUGINS-TASK-DEPS-003.7
- AC-PLUGINS-TASK-DEPS-003.8
- AC-PLUGINS-TASK-DEPS-004.1
- AC-PLUGINS-TASK-DEPS-004.2
- AC-PLUGINS-TASK-DEPS-004.3
- AC-PLUGINS-TASK-DEPS-004.4
- AC-PLUGINS-TASK-DEPS-004.5
- AC-PLUGINS-TASK-DEPS-004.6
- AC-PLUGINS-TASK-DEPS-004.7
- AC-PLUGINS-TASK-DEPS-004.8
- AC-PLUGINS-TASK-DEPS-004.9
- AC-PLUGINS-TASK-DEPS-004.10
- AC-PLUGINS-TASK-DEPS-004.11
- AC-PLUGINS-TASK-DEPS-004.12
- AC-PLUGINS-TASK-DEPS-004.13
- AC-PLUGINS-TASK-DEPS-004.14
- AC-PLUGINS-TASK-DEPS-004.15
- AC-PLUGINS-TASK-DEPS-005.1
- AC-PLUGINS-TASK-DEPS-005.2
- AC-PLUGINS-TASK-DEPS-005.3
- AC-PLUGINS-TASK-DEPS-005.4
- AC-PLUGINS-TASK-DEPS-005.5
- AC-PLUGINS-TASK-DEPS-005.6
- AC-PLUGINS-TASK-DEPS-005.7
- AC-PLUGINS-TASK-DEPS-005.8
- AC-PLUGINS-TASK-DEPS-006.1
- AC-PLUGINS-TASK-DEPS-006.2
- AC-PLUGINS-TASK-DEPS-006.3
- AC-PLUGINS-TASK-DEPS-006.4
- AC-PLUGINS-TASK-DEPS-006.5
system_design:
- ../../specs/plugins/system-design/task-dependency-projection.md
- ../../specs/plugins/system-design/task-dependency-refresh.md
- ../../specs/plugins/system-design/task-dependency-edge-ends.md
- ../../specs/plugins/system-design/task-dependency-response-bounds.md
---
# Task 01: Expose task dependencies in the plugin and canvas data API
## Summary
A workspace canvas or gRPC plugin reading the Host Data API's `Task` read model
could not see a single dependency edge, even though the kanban board already
derives and ships a complete view (`blocked`, `blocked_reason`, `depends_on`,
`blocks`, `start_when_unblocked`) via `BuildDependencyViews`. This task adds
the same seven-field, read-only projection to every plugin task read model
across the gRPC surface (proto, SDK, `grpcHostServer`), the canvas JSON
surface (`webapp_protocol_json.go`), and the shared derivation the two reuse
(`internal/task/service/service_dependencies.go`), landing it as a field on
the existing `api_read:tasks` capability with no new grant.
## Scope
- **Field set** (all seven, always present within one host version — absence
is never a third state): `blocked`, `blocked_reason`
(`pending|failed|unknown`), `depends_on[]`, `blocks[]`,
`depends_on_truncated`, `blocks_truncated`, `start_when_unblocked`.
- **Attachment rule**: the projection is derived and attached on every plugin
task read model object a response actually serializes, and on no discarded
or intermediate read — enforced via non-attaching (`fetchTask`,
`fetchTaskForScopeCheck`, `writeTaskUpdate`) vs. attaching
(`taskReader.Get/Update/Move`, `pluginOwnedTaskTreeManager.Preview`) helper
pairs across nine call sites (6 gRPC RPCs, 3 canvas routes).
- **Withheld verdict**: `blocked=true, blocked_reason="unknown"`, empty lists,
both truncation flags false, `start_when_unblocked=false` — the single
fail-closed sentinel for all four derivation-failure paths and for a caller
lacking `api_read:tasks` (indistinguishable from each other by design).
- **Fan-out bound**: `maxDependencyFanOut = 4096` distinct edge ends on
multi-task flows (`ListTasks`, canvas task list, preview RPC), returned as
gRPC `ResourceExhausted`; single-task reads and write RPCs are exempt and
use the unbounded derivation.
- **Edge-end redaction**: canvas surfaces redact `title`/`state` on an edge
end their scope does not admit as directly readable (5-way switch over
scope kind), decided without an additional read; gRPC redacts nothing.
- **Shared-derivation fixes** (Kanban-visible, no migration): ordering
(`ListBlockersForTasks` gains `ORDER BY created_at, blocker_task_id`),
dangling-edge drop extended to the `Blocks` direction, fail-closed on all
four derivation-failure paths, edge-end workspace id carried on the ref,
and chunking both batched id-query families
(`sqliteMaxHostParams = 500`) past SQLite's host-parameter ceiling on the
unpaginated preview flow.
- **Events**: deliberately unchanged — dependency fields are not added to the
canvas SSE allowlist (`webapp_events.go`'s `commonFields`); the contract is
refetch-on-signal off the existing `task.updated` /
`task.dependencies_resolved` / `task.dependency_failed` events, including a
`task.state_changed` refetch rule for any task named in a cached task's
edge lists.
- **No new capability**: the projection is a field set on `api_read:tasks`;
version skew is gated by the manifest's existing `min_kandev_version` floor.
## Acceptance
- `GET ./_kandev/v1/data/tasks` on a workspace canvas and gRPC
`ListTasks`/`GetTask` return the same seven dependency fields, redaction
aside, for the same tasks.
- Listing N tasks issues a constant number of dependency queries, not one per
task (batched `BuildDependencyViews`/`BuildDependencyViewsBounded`).
- A derivation failure or a caller without `api_read:tasks` yields the
withheld verdict, never a fail-open empty/false projection.
- Mapper round-trip (proto ↔ SDK), canvas JSON projection, the redaction-scope
table, the withheld-verdict path, and the fan-out/chunking bound are each
covered by tests in the relevant package.
## Verification
- `go test ./internal/plugins/... ./internal/task/service/... ./internal/task/repository/sqlite/... ./internal/office/repository/sqlite/... ./pkg/pluginsdk/...` — all green.
- `golangci-lint run ./...` (backend) — 0 issues.
- `gofmt -l` on touched files — clean.
- Three independent review legs (code-reviewer, security-reviewer,
test-supervisor) plus a mandatory cross-vendor `codex` pass ran against the
final diff; every finding was independently re-verified against source.
codex's one finding and the security-reviewer's SEC-001 finding were both
confirmed to predate this branch and fall outside its diff (see the task
plan's Review round 3 entry for the full trace); no production defect
survived review. The residual is test-rigor coverage gaps, tracked in the
task plan rather than blocking this PR.
## Files touched
- `apps/backend/proto/kandev/plugin/v1/plugin.proto`,
`apps/backend/pkg/pluginsdk/data_types.go` (+ tests) — `Task` field
additions, `TaskDependencyRef`, proto/SDK conversions.
- `apps/backend/internal/plugins/host.go`, `host_data.go`,
`host_data_dependencies.go` (+ tests), `host_write.go` (+ tests),
`service.go`, `webapp_protocol.go`, `webapp_protocol_data.go`,
`webapp_protocol_json.go` (+ tests), `webapp_events.go` (test only) — gRPC
and canvas attachment, redaction, withheld-verdict, and logging.
- `apps/backend/internal/task/service/service_dependencies.go` (+ tests) —
shared derivation: ordering, dangling-edge drop, fail-closed paths,
workspace id on refs, fan-out bound.
- `apps/backend/internal/office/repository/sqlite/blockers.go` (+ tests),
`apps/backend/internal/task/repository/sqlite/task.go` (+ tests) — ordering
and chunking on the batched id queries.
- `docs/public/canvases.md`, `docs/public/plugins-authoring.md`,
`apps/backend/internal/mcp/canvasskill/files/references/browser-api.md` —
consumer-facing contract docs.
## Output contract
Summary, files changed, and status update here + in `plan.md`.
## Output
**Status: done.** Landed across 9 commits on
`feature/expose-task-dependen-61269a`
(`5d359841a`..`9a5b0b761`), covering the frozen spec's `-001` through `-006`
requirements in full. Spec Review ran 8 rounds to freeze the contract; Build
ran 7 rounds (2 operator-authorized beyond the standing 4-round cap) fixing
findings from Testing and Review; Testing round 7 and Review round 3 both
found no production defect. See the task plan for the full round-by-round
record, including the two review findings adjudicated as pre-existing and
out of this branch's scope (a codex finding about the internal event bus,
and a security-reviewer finding about gRPC plugin event-delivery capability
gating — both confirmed unmodified by this branch and filed as follow-up
candidates rather than blockers).