fix(schemas): pre-gateway-style cross-field validators on DelegateRequest

Pre-gateway parity for G7 of the 2026-05-11 design. The pre-gateway
TaskCreateInput at 254cc93:roboco/mcp/schemas/__init__.py:210-235 had
@field_validator hooks that caught the most common LLM-vs-schema
confusions with helpful "did you mean X?" hints. Those validators were
lost in the gateway refactor.

Three validators added to DelegateRequest:

- estimated_complexity: rejects ints (some agents send 1/2/3 thinking
  it's a priority), enforces enum {low|medium|high|critical}. Hint
  steers them to drop priority (which isn't a delegate parameter).

- nature: rejects invented values like the 2026-05-11 'standard'
  regression. Enum is {technical|non_technical}. Hint explicitly cites
  the regression so the LLM knows why this is enforced.

- task_type: rejects invented task_type values. Enum is {code,
  documentation, research, planning, design, administrative}.

Fail-fast at the Pydantic boundary returns a 422 with the structured
hint inline, so the agent loops a single retry instead of leaking a
TaskCompletenessError up the stack.

Existing tests in tests/unit/api/routes/v2/test_flow_*.py used
"nature": "feature" — a value that the gateway's TaskNature enum
never accepted, so it would have been rejected at completeness check
anyway. Updated both to "technical".

Spec ref: docs/superpowers/specs/2026-05-11-pre-gateway-parity-design.md
This commit is contained in:
Renn F
2026-05-11 06:00:58 +02:00
parent 72e01a7f13
commit bd52e3d0c3
3 changed files with 51 additions and 3 deletions
+49 -1
View File
@@ -2,7 +2,7 @@
from uuid import UUID
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, field_validator
class GiveMeWorkRequest(BaseModel):
@@ -133,6 +133,54 @@ class DelegateRequest(BaseModel):
# also denylist-checks each item against placeholder phrases.
acceptance_criteria: list[str] = Field(..., min_length=1)
# Pre-gateway parity: cross-field validators that catch the most common
# LLM-vs-schema confusions. Pre-gateway lived in
# roboco/mcp/schemas/__init__.py::TaskCreateInput at 0c3d15a.
@field_validator("estimated_complexity", mode="before")
@classmethod
def _complexity_must_be_string(cls, v: object) -> object:
"""Reject ints — agents sometimes send 1/2/3 thinking it's priority."""
if isinstance(v, int) and not isinstance(v, bool):
raise ValueError(
f"estimated_complexity must be a string "
f"(low|medium|high|critical), got int {v!r}. "
f"Priority is not a delegate parameter — drop it."
)
if isinstance(v, str) and v.lower() not in {
"low", "medium", "high", "critical",
}:
raise ValueError(
f"estimated_complexity must be one of: low, medium, high, "
f"critical. Got {v!r}."
)
return v
@field_validator("nature", mode="before")
@classmethod
def _nature_must_be_known(cls, v: object) -> object:
"""Reject invented nature values (e.g., 'standard') with the enum hint."""
if isinstance(v, str) and v.lower() not in {"technical", "non_technical"}:
raise ValueError(
f"nature must be one of: technical | non_technical. Got {v!r}. "
f"This was the 2026-05-11 'standard' regression — drop the "
f"invented value and use the enum."
)
return v
@field_validator("task_type", mode="before")
@classmethod
def _task_type_must_be_known(cls, v: object) -> object:
"""Reject invented task_type values with the enum hint."""
if isinstance(v, str) and v.lower() not in {
"code", "documentation", "research",
"planning", "design", "administrative",
}:
raise ValueError(
f"task_type must be one of: code | documentation | research | "
f"planning | design | administrative. Got {v!r}."
)
return v
class SubmitUpRequest(BaseModel):
task_id: UUID
@@ -259,7 +259,7 @@ async def test_delegate_dispatches_inputs_bundle() -> None:
"assigned_to": "be-dev-1",
"team": "backend",
"task_type": "code",
"nature": "feature",
"nature": "technical",
"estimated_complexity": "medium",
"acceptance_criteria": ["GET /v1/foo returns 200 with body"],
},
@@ -230,7 +230,7 @@ async def test_delegate_to_cell_pm_dispatches_inputs_bundle() -> None:
"assigned_to": "be-pm",
"team": "backend",
"task_type": "planning",
"nature": "feature",
"nature": "technical",
"estimated_complexity": "high",
"acceptance_criteria": [
"all subtasks created with acceptance criteria",