Component: finbot/agents/chat.py → VendorChatAssistant._call_start_workflow
Root cause:
# chat.py lines 198-201
task_data: dict[str, Any] = {
"description": description,
"vendor_id": vendor_id, # no None check
"parent_workflow_id": self._workflow_id,
}
vendor_id is a required parameter (no default value in the function signature) but has
no runtime guard. Python type hints are not enforced at call time. When the LLM omits
the field or sends null, vendor_id=None is accepted and written directly into
task_data, which is then dispatched to the orchestrator.
Steps to reproduce:
- Create a
VendorChatAssistant with a mock background_tasks.
- Call
_call_start_workflow("do something", vendor_id=None).
- Inspect
task_data passed to background_tasks.add_task.
Expected: ValueError or {"error": ...} JSON — validation rejects None for a required field
Actual: status='started', task_data["vendor_id"] is None — dispatched to orchestrator
How to execute:
pytest tests/unit/agents/test_chat_assistant.py::TestBoundaryAndTypeValues::test_chat_boundary_011_vendor_id_none_flows_into_task_data -v
Proposed fix:
if vendor_id is None:
return json.dumps({"error": "vendor_id is required"})
Impact: The orchestrator receives None as the vendor primary key. Any downstream
DB query using vendor_id=None will either raise a DataError (PostgreSQL) or silently
return no rows (SQLite), with no signal propagated back to the chat session. The
workflow appears to start successfully from the caller's perspective while the
orchestrator operates on a phantom vendor.
Acceptance criteria:
test_chat_boundary_011_vendor_id_none_flows_into_task_data updated to assert an error response (once fix is applied)
_call_start_workflow returns {"error": "vendor_id is required"} when vendor_id is None
- All other
_call_start_workflow tests continue to pass
Component: finbot/agents/chat.py → VendorChatAssistant._call_start_workflow
Root cause:
vendor_idis a required parameter (no default value in the function signature) but hasno runtime guard. Python type hints are not enforced at call time. When the LLM omits
the field or sends
null,vendor_id=Noneis accepted and written directly intotask_data, which is then dispatched to the orchestrator.Steps to reproduce:
VendorChatAssistantwith a mockbackground_tasks._call_start_workflow("do something", vendor_id=None).task_datapassed tobackground_tasks.add_task.Expected:
ValueErroror{"error": ...}JSON — validation rejectsNonefor a required fieldActual:
status='started',task_data["vendor_id"] is None— dispatched to orchestratorHow to execute:
Proposed fix:
Impact: The orchestrator receives
Noneas the vendor primary key. Any downstreamDB query using
vendor_id=Nonewill either raise aDataError(PostgreSQL) or silentlyreturn no rows (SQLite), with no signal propagated back to the chat session. The
workflow appears to start successfully from the caller's perspective while the
orchestrator operates on a phantom vendor.
Acceptance criteria:
test_chat_boundary_011_vendor_id_none_flows_into_task_dataupdated to assert an error response (once fix is applied)_call_start_workflowreturns{"error": "vendor_id is required"}whenvendor_id is None_call_start_workflowtests continue to pass