fix(deps): gate the pre-assigned dev path on unmet dependencies

A dev subtask is always pre-assigned (assigned_to=<dev>), so it never
flows through the unassigned claim pool's dependency filter
(list_pending(filter_by_dependencies=True)). Every path that acts on a
pre-assigned pending dev subtask previously ignored dependency_ids: the
orchestrator spawned the dev container, give_me_work offered the task,
and the claim verb accepted it — letting a frontend dev code ahead of an
unfinished UX/UI design.

Hold the pre-assigned dev at each path it actually arrives by, until
every dependency reaches a terminal state:

- orchestrator _validate_task_for_spawn now consults dependency_ids via
  _check_dependencies_terminal and skips the spawn while any dependency
  is non-terminal (fail-closed on an unreadable dependency);
- TaskService.list_pending_for_agent excludes a pre-assigned task with
  unmet dependencies so give_me_work does not offer it;
- the Choreographer claim guard set rejects the claim with a clear
  remediate via a new unmet_dependency_guard.

Add TaskService.unmet_dependency_ids as the single source of truth for
"which dependency IDs are not yet terminal" and route the existing
inherit_unmet_dependencies through it.
This commit is contained in:
Renn F
2026-06-03 20:36:35 +02:00
parent 5462fe3ae6
commit b0a596ecac
5 changed files with 436 additions and 27 deletions
@@ -25,6 +25,7 @@ from roboco.services.gateway.claim_guards import (
already_active_guard,
paused_tasks_guard,
sibling_sequence_guard,
unmet_dependency_guard,
)
from roboco.services.gateway.envelope import Envelope
from roboco.services.gateway.evidence_builder import (
@@ -700,6 +701,11 @@ class Choreographer:
paused = await self.task.list_paused_for_agent(agent_id)
if guard := paused_tasks_guard(paused):
return guard
dep_ids = list(task.dependency_ids)
if dep_ids:
unmet = await self.task.unmet_dependency_ids(dep_ids)
if guard := unmet_dependency_guard(task, unmet):
return guard
if not skip_sequence:
siblings = await self._fetch_siblings(task)
if guard := sibling_sequence_guard(task, siblings):