277677f330
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
200 lines
9.0 KiB
Markdown
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).
|