[AUTO-BUG-6] a2a/facade: _handle_plan_prompt uses **prompt_payload dict unpacking that silently overwrites plan_id, guidance, and status response keys #10448

Open
opened 2026-04-18 09:41:44 +00:00 by HAL9000 · 0 comments
Owner

Metadata

Field Value
Branch fix/a2a-facade-plan-prompt-payload-overwrite
Commit Message fix(a2a/facade): use explicit key extraction in _handle_plan_prompt instead of **prompt_payload unpacking
Milestone v3.5.0
Parent Epic

Background and Context

A2aLocalFacade._handle_plan_prompt() in src/cleveragents/a2a/facade.py uses **prompt_payload dict unpacking to merge the result of svc.prompt_plan() into the response dict. If svc.prompt_plan() returns a dict containing keys plan_id, guidance, or status, those keys will silently overwrite the values already set in the response, causing data corruption with no error or warning.

Current Behavior

Code (src/cleveragents/a2a/facade.py, _handle_plan_prompt() method):

def _handle_plan_prompt(self, params: dict[str, Any]) -> dict[str, Any]:
    plan_id = params.get("plan_id", "")
    guidance = params.get("guidance", "")
    svc = self._plan_lifecycle_service
    if svc is not None and plan_id:
        prompt_payload = svc.prompt_plan(plan_id, guidance)
        return {
            "plan_id": plan_id,
            "guidance": guidance,
            "status": "guidance_injected",
            **prompt_payload,  # ← BUG: can overwrite plan_id, guidance, status
        }
    ...

If svc.prompt_plan() returns {"plan_id": "different_id", "status": "error", "guidance": ""}, the response will have:

  • plan_id = "different_id" (overwritten — wrong)
  • status = "error" (overwritten — wrong)
  • guidance = "" (overwritten — wrong)

The caller receives corrupted data with no indication that overwriting occurred. This is a data integrity bug.

Expected Behavior

The response should always contain the plan_id from the request params, status = "guidance_injected", and guidance from the request params. Additional fields from prompt_payload should be merged only for keys that don't conflict with the fixed response keys.

Correct pattern:

def _handle_plan_prompt(self, params: dict[str, Any]) -> dict[str, Any]:
    plan_id = params.get("plan_id", "")
    guidance = params.get("guidance", "")
    svc = self._plan_lifecycle_service
    if svc is not None and plan_id:
        prompt_payload = svc.prompt_plan(plan_id, guidance)
        # Merge additional fields but do NOT allow overwriting fixed keys
        extra = {k: v for k, v in prompt_payload.items() if k not in ("plan_id", "guidance", "status")}
        return {
            "plan_id": plan_id,
            "guidance": guidance,
            "status": "guidance_injected",
            **extra,
        }
    return {
        "plan_id": plan_id,
        "guidance": guidance,
        "status": "guidance_injected",
        "stub": True,
    }

Steps to Reproduce

from unittest.mock import MagicMock
from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest

mock_svc = MagicMock()
mock_svc.prompt_plan.return_value = {
    "plan_id": "OVERWRITTEN",
    "status": "error",
    "guidance": "",
}
facade = A2aLocalFacade(services={"plan_lifecycle_service": mock_svc})
request = A2aRequest(method="_cleveragents/plan/prompt", params={"plan_id": "original-id", "guidance": "do X"})
response = facade.dispatch(request)
# BUG: response.result["plan_id"] == "OVERWRITTEN" (should be "original-id")
# BUG: response.result["status"] == "error" (should be "guidance_injected")

Impact

  • Severity: Medium — silent data corruption; callers receive wrong plan_id and status
  • Affected operation: _cleveragents/plan/prompt
  • Root cause: Unguarded **prompt_payload dict unpacking allows service return values to overwrite fixed response keys

Acceptance Criteria

  • _handle_plan_prompt does not allow prompt_payload to overwrite plan_id, guidance, or status
  • Additional fields from prompt_payload are still included in the response (for non-conflicting keys)
  • TDD scenario from #10403 passes after the fix
  • All existing tests pass

Subtasks

  • Fix _handle_plan_prompt in src/cleveragents/a2a/facade.py to use explicit key filtering
  • Verify TDD scenario from #10403 passes
  • Run nox -s unit_tests to confirm no regressions
  • Verify coverage >= 97% via nox -s coverage_report

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly.
  • The commit is pushed to the remote on the branch fix/a2a-facade-plan-prompt-payload-overwrite.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.

Blocked by #10403 (TDD counterpart must be merged first)


Automated by CleverAgents Bot
Agent: new-issue-creator

## Metadata | Field | Value | |---|---| | **Branch** | `fix/a2a-facade-plan-prompt-payload-overwrite` | | **Commit Message** | `fix(a2a/facade): use explicit key extraction in _handle_plan_prompt instead of **prompt_payload unpacking` | | **Milestone** | v3.5.0 | | **Parent Epic** | — | ## Background and Context `A2aLocalFacade._handle_plan_prompt()` in `src/cleveragents/a2a/facade.py` uses `**prompt_payload` dict unpacking to merge the result of `svc.prompt_plan()` into the response dict. If `svc.prompt_plan()` returns a dict containing keys `plan_id`, `guidance`, or `status`, those keys will silently overwrite the values already set in the response, causing data corruption with no error or warning. ## Current Behavior **Code** (`src/cleveragents/a2a/facade.py`, `_handle_plan_prompt()` method): ```python def _handle_plan_prompt(self, params: dict[str, Any]) -> dict[str, Any]: plan_id = params.get("plan_id", "") guidance = params.get("guidance", "") svc = self._plan_lifecycle_service if svc is not None and plan_id: prompt_payload = svc.prompt_plan(plan_id, guidance) return { "plan_id": plan_id, "guidance": guidance, "status": "guidance_injected", **prompt_payload, # ← BUG: can overwrite plan_id, guidance, status } ... ``` If `svc.prompt_plan()` returns `{"plan_id": "different_id", "status": "error", "guidance": ""}`, the response will have: - `plan_id` = `"different_id"` (overwritten — wrong) - `status` = `"error"` (overwritten — wrong) - `guidance` = `""` (overwritten — wrong) The caller receives corrupted data with no indication that overwriting occurred. This is a data integrity bug. ## Expected Behavior The response should always contain the `plan_id` from the request params, `status = "guidance_injected"`, and `guidance` from the request params. Additional fields from `prompt_payload` should be merged only for keys that don't conflict with the fixed response keys. **Correct pattern:** ```python def _handle_plan_prompt(self, params: dict[str, Any]) -> dict[str, Any]: plan_id = params.get("plan_id", "") guidance = params.get("guidance", "") svc = self._plan_lifecycle_service if svc is not None and plan_id: prompt_payload = svc.prompt_plan(plan_id, guidance) # Merge additional fields but do NOT allow overwriting fixed keys extra = {k: v for k, v in prompt_payload.items() if k not in ("plan_id", "guidance", "status")} return { "plan_id": plan_id, "guidance": guidance, "status": "guidance_injected", **extra, } return { "plan_id": plan_id, "guidance": guidance, "status": "guidance_injected", "stub": True, } ``` ## Steps to Reproduce ```python from unittest.mock import MagicMock from cleveragents.a2a.facade import A2aLocalFacade from cleveragents.a2a.models import A2aRequest mock_svc = MagicMock() mock_svc.prompt_plan.return_value = { "plan_id": "OVERWRITTEN", "status": "error", "guidance": "", } facade = A2aLocalFacade(services={"plan_lifecycle_service": mock_svc}) request = A2aRequest(method="_cleveragents/plan/prompt", params={"plan_id": "original-id", "guidance": "do X"}) response = facade.dispatch(request) # BUG: response.result["plan_id"] == "OVERWRITTEN" (should be "original-id") # BUG: response.result["status"] == "error" (should be "guidance_injected") ``` ## Impact - **Severity**: Medium — silent data corruption; callers receive wrong plan_id and status - **Affected operation**: `_cleveragents/plan/prompt` - **Root cause**: Unguarded `**prompt_payload` dict unpacking allows service return values to overwrite fixed response keys ## Acceptance Criteria - [ ] `_handle_plan_prompt` does not allow `prompt_payload` to overwrite `plan_id`, `guidance`, or `status` - [ ] Additional fields from `prompt_payload` are still included in the response (for non-conflicting keys) - [ ] TDD scenario from #10403 passes after the fix - [ ] All existing tests pass ## Subtasks - [ ] Fix `_handle_plan_prompt` in `src/cleveragents/a2a/facade.py` to use explicit key filtering - [ ] Verify TDD scenario from #10403 passes - [ ] Run `nox -s unit_tests` to confirm no regressions - [ ] Verify coverage >= 97% via `nox -s coverage_report` ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly. - The commit is pushed to the remote on the branch `fix/a2a-facade-plan-prompt-payload-overwrite`. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. **Blocked by #10403** (TDD counterpart must be merged first) --- **Automated by CleverAgents Bot** Agent: new-issue-creator
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#10448
No description provided.