diff --git a/strix/tools/reporting/tool.py b/strix/tools/reporting/tool.py index c490e47c..2d245b69 100644 --- a/strix/tools/reporting/tool.py +++ b/strix/tools/reporting/tool.py @@ -1496,38 +1496,46 @@ async def update_vulnerability_report( default=str, ) + fields = { + "title": title, + "description": description, + "impact": impact, + "target": target, + "technical_analysis": technical_analysis, + "poc_description": poc_description, + "poc_script_code": poc_script_code, + "remediation_steps": remediation_steps, + "evidence": evidence, + "assumptions": assumptions, + "counterevidence": counterevidence, + "confidence": confidence, + "confidence_rationale": confidence_rationale, + "severity_change_conditions": severity_change_conditions, + "fix_effort": fix_effort, + "cvss_breakdown": cvss_breakdown, + "endpoint": endpoint, + "method": method, + "cve": cve, + "cwe": cwe, + "code_locations": code_locations, + "http_exchange_ids": http_exchange_ids, + "fix_verification": fix_verification, + "fix_pr_body": fix_pr_body, + "contextual_cvss_reasoning": contextual_cvss_reasoning, + } + if http_exchange_warning and all(value is None for value in fields.values()): + return json.dumps( + {"success": False, "error": http_exchange_warning, "report_id": report_id}, + ensure_ascii=False, + default=str, + ) + agent_id, agent_name = _caller_identity(ctx) result = await asyncio.to_thread( _do_update, report_id=report_id, update_reason=update_reason, - fields={ - "title": title, - "description": description, - "impact": impact, - "target": target, - "technical_analysis": technical_analysis, - "poc_description": poc_description, - "poc_script_code": poc_script_code, - "remediation_steps": remediation_steps, - "evidence": evidence, - "assumptions": assumptions, - "counterevidence": counterevidence, - "confidence": confidence, - "confidence_rationale": confidence_rationale, - "severity_change_conditions": severity_change_conditions, - "fix_effort": fix_effort, - "cvss_breakdown": cvss_breakdown, - "endpoint": endpoint, - "method": method, - "cve": cve, - "cwe": cwe, - "code_locations": code_locations, - "http_exchange_ids": http_exchange_ids, - "fix_verification": fix_verification, - "fix_pr_body": fix_pr_body, - "contextual_cvss_reasoning": contextual_cvss_reasoning, - }, + fields=fields, agent_id=agent_id, agent_name=agent_name, ) diff --git a/tests/test_reporting_fields.py b/tests/test_reporting_fields.py index 4a62a90d..aaac7582 100644 --- a/tests/test_reporting_fields.py +++ b/tests/test_reporting_fields.py @@ -2,9 +2,11 @@ from __future__ import annotations +import json from typing import TYPE_CHECKING, Any, cast import pytest +from agents.tool_context import ToolContext from strix.report.dedupe import ( _check_dependency_duplicate, @@ -1501,6 +1503,46 @@ async def test_create_reports_persistence_failure_as_tool_error( assert report_state.vulnerability_reports == [] +async def test_evidence_only_update_reports_proxy_outage_as_retryable( + report_state: ReportState, + monkeypatch: pytest.MonkeyPatch, +) -> None: + _seed_weak_report(report_state) + original = dict(report_state.vulnerability_reports[0]) + + async def existing_request_ids( + _ctx: Any, + _request_ids: list[str], + ) -> set[str]: + raise RuntimeError("Caido client is not available") + + monkeypatch.setattr(reporting_tool, "existing_request_ids", existing_request_ids) + + ctx = ToolContext( + context={"agent_id": "root"}, + tool_name="update_vulnerability_report", + tool_call_id="call-1", + tool_arguments="{}", + ) + raw = await update_vulnerability_report.on_invoke_tool( + ctx, + json.dumps( + { + "report_id": "vuln-0009", + "update_reason": "A replay produced a clearer proving exchange.", + "http_exchange_ids": ["204"], + } + ), + ) + result = json.loads(raw) + + assert result["success"] is False + assert "No fields to update" not in result["error"] + assert "update_vulnerability_report" in result["error"] + assert result["report_id"] == "vuln-0009" + assert report_state.vulnerability_reports[0] == original + + def test_update_reports_persistence_failure_as_tool_error(report_state: ReportState) -> None: _seed_weak_report(report_state) original = dict(report_state.vulnerability_reports[0])