From e93a77e52b530d243f55bb483c9379ac22ff731b Mon Sep 17 00:00:00 2001 From: Renn F Date: Sun, 28 Jun 2026 23:12:32 +0200 Subject: [PATCH] [F118] coerce a lone-string where_to_look into a list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit where_to_look is a list-typed handoff field like consequences/next_steps but was the only one NOT in the _wrap_scalar_in_list field_validator. A well-intentioned where_to_look='src/api/' 422'd at the route with no remediation envelope, and the agent's retry loop tripped the do-server circuit breaker — the exact failure mode the other list fields were hardened against. Add it to the mode='before' validator so a lone string is wrapped into a one-element list before type coercion. --- roboco/api/schemas/v1/do.py | 4 +++- .../unit/api/schemas/v1/test_note_request_no_null.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/roboco/api/schemas/v1/do.py b/roboco/api/schemas/v1/do.py index aa658b51..1121ddc3 100644 --- a/roboco/api/schemas/v1/do.py +++ b/roboco/api/schemas/v1/do.py @@ -91,7 +91,9 @@ class NoteRequest(BaseModel): # 422'd at the route and the agent's retry loop tripped the circuit # breaker. ``mode="before"`` runs ahead of type coercion so # the wrapped value satisfies the declared ``list[...]`` type. - @field_validator("options", "consequences", "next_steps", mode="before") + @field_validator( + "options", "consequences", "next_steps", "where_to_look", mode="before" + ) @classmethod def _wrap_scalar_in_list(cls, value: Any) -> Any: return _coerce_to_list(value) diff --git a/tests/unit/api/schemas/v1/test_note_request_no_null.py b/tests/unit/api/schemas/v1/test_note_request_no_null.py index a6298eeb..50f9c88a 100644 --- a/tests/unit/api/schemas/v1/test_note_request_no_null.py +++ b/tests/unit/api/schemas/v1/test_note_request_no_null.py @@ -116,6 +116,18 @@ def test_note_request_coerces_string_next_steps_to_list() -> None: assert req.next_steps == ["wait for QA"] +def test_note_request_coerces_string_where_to_look_to_list() -> None: + """F118: a single string for where_to_look is wrapped into a one-element + list. It is a list-typed handoff field like consequences/next_steps and + must tolerate a lone scalar — without this a well-intentioned + ``where_to_look="src/api/"`` 422'd at the route (no remediation envelope) + and the agent's retry loop tripped the do-server circuit breaker.""" + req = NoteRequest.model_validate( + {"text": "x", "scope": "handoff", "where_to_look": "src/api/auth.py"} + ) + assert req.where_to_look == ["src/api/auth.py"] + + def test_note_request_coerces_single_option_dict_to_list() -> None: """A single option dict (not wrapped in a list) is wrapped into a list.""" req = NoteRequest.model_validate(