Files
cleveragents-core/features/a2a_facade_wiring.feature
T
brent.edwards 7e9a45044b
CI / integration_tests (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / helm (push) Has been cancelled
CI / security (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / unit_tests (push) Has been cancelled
CI / quality (push) Has been cancelled
CI / benchmark-regression (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / status-check (push) Has been cancelled
CI / coverage (push) Has been cancelled
fix(session): session create does not persist session for subsequent list
The CLI `session create` command created the session via SessionService.create()
(which commits via auto_commit=True), then called _facade_dispatch("session.create")
for A2A protocol bookkeeping. The facade handler unconditionally called
svc.create() on a second PersistentSessionService instance (a new Factory
resolution from the DI container with its own engine), creating a duplicate
session in the database.

The fix makes A2aLocalFacade._handle_session_create() idempotent: when a
session_id is already present in the params, it acknowledges the existing
session without creating a new one. The CLI dispatch params were also fixed
to use the correct key name (actor_name instead of actor).

Changes:
- src/cleveragents/a2a/facade.py: Early return in _handle_session_create when
  session_id is already supplied, preventing duplicate session creation.
- src/cleveragents/cli/commands/session.py: Fixed param key from "actor" to
  "actor_name" for consistency with the facade handler.
- features/a2a_facade_wiring.feature: Added idempotency scenario verifying
  that session.create with an existing session_id does not call svc.create().
- features/steps/a2a_facade_wiring_steps.py: Added step asserting mock
  SessionService.create was not called.
- features/tdd_session_create_persist.feature: Removed @tdd_expected_fail tag
  now that the bug is fixed.
- robot/e2e/e2e_session_create_persist.robot: Removed tdd_expected_fail tag,
  updated documentation.
- .semgrep.yml: Excluded wrapping.py from no-exec/no-compile-exec rules
  (pre-existing sandboxed exec usage for tool transforms).

ISSUES CLOSED: #1141

Co-authored-by: Brent E. Edwards <brent.edwards@cleverthis.com>
Co-committed-by: Brent E. Edwards <brent.edwards@cleverthis.com>
2026-04-02 16:51:04 +00:00

168 lines
8.3 KiB
Gherkin

Feature: A2A local facade wiring to live services
As a client of the A2A local facade
I want operations to route to real application services
So that the facade delegates to live implementations
# ---------------------------------------------------------------
# Session wiring
# ---------------------------------------------------------------
Scenario: session.create delegates to SessionService
Given a wired A2aLocalFacade with a mock SessionService
When I dispatch wired operation "session.create" with params {"actor_name": "local/test-actor"}
Then the wired response status should be "ok"
And wired response data key "session_id" equals "MOCK-SESSION-001"
And wired response data key "status" equals "created"
Scenario: session.create with existing session_id is idempotent
Given a wired A2aLocalFacade with a mock SessionService
When I dispatch wired operation "session.create" with params {"session_id": "EXISTING-SESSION-999"}
Then the wired response status should be "ok"
And wired response data key "session_id" equals "EXISTING-SESSION-999"
And wired response data key "status" equals "created"
And the mock SessionService create should not have been called
Scenario: session.close delegates to SessionService
Given a wired A2aLocalFacade with a mock SessionService
When I dispatch wired operation "session.close" with params {"session_id": "MOCK-SESSION-001"}
Then the wired response status should be "ok"
And wired response data key "status" equals "closed"
Scenario: session.close without session_id returns error
Given a wired A2aLocalFacade with a mock SessionService
When I dispatch wired operation "session.close" with params {}
Then the wired response status should be "error"
# ---------------------------------------------------------------
# Plan lifecycle wiring
# ---------------------------------------------------------------
Scenario: plan.create delegates to PlanLifecycleService
Given a wired A2aLocalFacade with a mock PlanLifecycleService
When I dispatch wired operation "plan.create" with params {"action_name": "local/test-action"}
Then the wired response status should be "ok"
And wired response data key "plan_id" equals "MOCK-PLAN-001"
And wired response data key "status" equals "created"
Scenario: plan.create without action_name returns error
Given a wired A2aLocalFacade with a mock PlanLifecycleService
When I dispatch wired operation "plan.create" with params {}
Then the wired response status should be "error"
Scenario: plan.execute delegates to PlanLifecycleService
Given a wired A2aLocalFacade with a mock PlanLifecycleService
When I dispatch wired operation "plan.execute" with params {"plan_id": "MOCK-PLAN-001"}
Then the wired response status should be "ok"
And wired response data key "plan_id" equals "MOCK-PLAN-001"
Scenario: plan.status delegates to PlanLifecycleService
Given a wired A2aLocalFacade with a mock PlanLifecycleService
When I dispatch wired operation "plan.status" with params {"plan_id": "MOCK-PLAN-001"}
Then the wired response status should be "ok"
And wired response data key "plan_id" equals "MOCK-PLAN-001"
And wired response data key "phase" equals "strategize"
Scenario: plan.diff delegates to PlanLifecycleService
Given a wired A2aLocalFacade with a mock PlanLifecycleService
When I dispatch wired operation "plan.diff" with params {"plan_id": "MOCK-PLAN-001"}
Then the wired response status should be "ok"
And wired response data key "plan_id" equals "MOCK-PLAN-001"
Scenario: plan.apply delegates to PlanLifecycleService
Given a wired A2aLocalFacade with a mock PlanLifecycleService
When I dispatch wired operation "plan.apply" with params {"plan_id": "MOCK-PLAN-001"}
Then the wired response status should be "ok"
And wired response data key "plan_id" equals "MOCK-PLAN-001"
# ---------------------------------------------------------------
# Registry wiring
# ---------------------------------------------------------------
Scenario: registry.list_tools delegates to ToolRegistry
Given a wired A2aLocalFacade with a mock ToolRegistry
When I dispatch wired operation "registry.list_tools" with params {}
Then the wired response status should be "ok"
And wired response data should contain tools list with 2 items
Scenario: registry.list_resources delegates to ResourceRegistryService
Given a wired A2aLocalFacade with a mock ResourceRegistryService
When I dispatch wired operation "registry.list_resources" with params {}
Then the wired response status should be "ok"
And wired response data should contain resources list with 1 items
# ---------------------------------------------------------------
# Context stub
# ---------------------------------------------------------------
Scenario: context.get returns stub pending ACMS pipeline
Given a wired A2aLocalFacade with no services
When I dispatch wired operation "context.get" with params {}
Then the wired response status should be "ok"
And wired response data key "stub" equals "True"
# ---------------------------------------------------------------
# Event wiring
# ---------------------------------------------------------------
Scenario: event.subscribe delegates to A2aEventQueue
Given a wired A2aLocalFacade with a mock A2aEventQueue
When I dispatch wired operation "event.subscribe" with params {}
Then the wired response status should be "ok"
And wired response data key "status" equals "subscribed"
And wired response data key "subscription_id" should not be empty
# ---------------------------------------------------------------
# Error mapping
# ---------------------------------------------------------------
Scenario: NOT_FOUND error code for missing resources
Given a wired A2aLocalFacade with a raising SessionService for not-found
When I dispatch wired operation "session.close" with params {"session_id": "nonexistent"}
Then the wired response status should be "error"
And wired response error code should be "NOT_FOUND"
Scenario: VALIDATION_ERROR code for validation failures
Given a wired A2aLocalFacade with a raising service for validation-error
When I dispatch wired operation "plan.create" with params {"action_name": "bad"}
Then the wired response status should be "error"
And wired response error code should be "VALIDATION_ERROR"
Scenario: PLAN_ERROR code for plan failures
Given a wired A2aLocalFacade with a raising service for plan-error
When I dispatch wired operation "plan.execute" with params {"plan_id": "P1"}
Then the wired response status should be "error"
And wired response error code should be "PLAN_ERROR"
Scenario: INVALID_STATE code for business rule violations
Given a wired A2aLocalFacade with a raising service for invalid-state
When I dispatch wired operation "plan.apply" with params {"plan_id": "P1"}
Then the wired response status should be "error"
And wired response error code should be "INVALID_STATE"
# ---------------------------------------------------------------
# Fallback stubs when no service is wired
# ---------------------------------------------------------------
Scenario: session.create stub when no service wired
Given a wired A2aLocalFacade with no services
When I dispatch wired operation "session.create" with params {}
Then the wired response status should be "ok"
And wired response data key "status" equals "created"
Scenario: plan.create stub when no service wired
Given a wired A2aLocalFacade with no services
When I dispatch wired operation "plan.create" with params {}
Then the wired response status should be "ok"
And wired response data key "status" equals "created"
Scenario: registry.list_tools stub when no service wired
Given a wired A2aLocalFacade with no services
When I dispatch wired operation "registry.list_tools" with params {}
Then the wired response status should be "ok"
Scenario: event.subscribe stub when no service wired
Given a wired A2aLocalFacade with no services
When I dispatch wired operation "event.subscribe" with params {}
Then the wired response status should be "ok"
And wired response data key "status" equals "subscribed"