ff9c6540a2
Implement entity synchronization between client and server using the _cleveragents/sync/* A2A extension methods per the specification. Sync models (src/cleveragents/a2a/sync_models.py): - Pydantic v2 models for pull/push/status requests and responses - VectorClock with increment, merge, happens-before, and concurrency detection for distributed conflict detection - SyncEntitySnapshot, SyncConflict, SyncState, SyncQueueEntry models - SyncEntityType enum: actor, skill, action, plan, session - ConflictResolution enum: last_writer_wins, manual, server_wins, client_wins SyncService (src/cleveragents/application/services/sync_service.py): - pull(): download server namespace entities to local cache with incremental sync (since timestamps) and force-overwrite option - push(): push local entity definitions to server namespace with configurable conflict resolution strategy - status(): compare local and server entity versions, detect drift, report local-ahead, server-ahead, and concurrent conflicts - resolve_conflict(): manually resolve detected conflicts with winner selection (local or server) - Offline queue: enqueue_offline() and process_offline_queue() for retry on reconnect, with max-retries enforcement - The local/ namespace is never synced per specification A2A facade (src/cleveragents/a2a/facade.py): - Replaced sync stub handlers with real _handle_sync_pull, _handle_sync_push, _handle_sync_status routing to SyncService - Added sync_service property and TYPE_CHECKING import - Facade returns stubs when no SyncService is registered Tests: - Behave BDD: features/entity_sync.feature (65 scenarios, 247 steps) covering models, vector clocks, pull/push/status, conflict resolution, offline queue, facade integration, constructor validation, edge cases - Robot Framework: robot/entity_sync.robot (8 integration tests) ISSUES CLOSED: #866
368 lines
20 KiB
Gherkin
368 lines
20 KiB
Gherkin
Feature: A2A local facade coverage — uncovered handler and edge-case paths
|
|
As a developer maintaining the A2A local facade
|
|
I want all handler branches and error paths exercised
|
|
So that coverage gaps are closed and regressions are caught
|
|
|
|
# -------------------------------------------------------------------
|
|
# Session close — devcontainer cleanup exception path (lines 359-363)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Session close catches devcontainer cleanup failure gracefully
|
|
Given a facade-cov facade with a mock SessionService
|
|
When I dispatch facade-cov operation "session.close" with params {"session_id": "sess-cleanup-fail"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "closed"
|
|
|
|
Scenario: Session close without session service still runs cleanup
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "session.close" with params {"session_id": "orphan-sess"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "closed"
|
|
|
|
Scenario: Session close with empty session_id and no service
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "session.close" with params {"session_id": ""}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "closed"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan cancel — with service wired (lines 501-502)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan cancel with wired service calls cancel_plan
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/cancel" with params {"plan_id": "PLAN-CANCEL-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "cancelled"
|
|
And the facade-cov response data should not contain key "stub"
|
|
|
|
Scenario: Plan cancel with empty plan_id returns stub
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/cancel" with params {"plan_id": ""}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan tree — full method (lines 504-514)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan tree with wired service returns plan info
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/tree" with params {"plan_id": "PLAN-TREE-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "plan_id" should equal "PLAN-TREE-001"
|
|
And the facade-cov response data should contain key "decision_root_id"
|
|
|
|
Scenario: Plan tree with empty plan_id returns stub
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/tree" with params {"plan_id": ""}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Plan tree with no service returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/tree" with params {"plan_id": "PLAN-TREE-002"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan explain (lines 516-524)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan explain returns stub with plan_id and decision_id
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/explain" with params {"plan_id": "PLAN-EXP-001", "decision_id": "DEC-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "plan_id" should equal "PLAN-EXP-001"
|
|
And the facade-cov response data key "decision_id" should equal "DEC-001"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan correct (lines 526-528)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan correct returns stub response
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/correct" with params {"plan_id": "PLAN-COR-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "corrected"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan artifacts — both paths (lines 530-540)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan artifacts with wired service returns changeset info
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/artifacts" with params {"plan_id": "PLAN-ART-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "plan_id" should equal "PLAN-ART-001"
|
|
And the facade-cov response data should contain key "changeset_id"
|
|
And the facade-cov response data should contain key "sandbox_refs"
|
|
|
|
Scenario: Plan artifacts with no service returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/artifacts" with params {"plan_id": "PLAN-ART-002"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Plan artifacts with empty plan_id returns stub
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/artifacts" with params {"plan_id": ""}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan prompt (lines 542-544)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan prompt returns stub response
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/prompt" with params {"plan_id": "PLAN-PRM-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "guidance_injected"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan rollback (lines 546-548)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan rollback returns stub response
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/rollback" with params {"plan_id": "PLAN-RB-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "rolled_back"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan list — with service wired (lines 550-560)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan list with wired service returns plan summaries
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "_cleveragents/plan/list" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data should contain key "plans"
|
|
And the facade-cov response data should contain key "total"
|
|
And the facade-cov response data should not contain key "stub"
|
|
|
|
Scenario: Plan list with no service returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/plan/list" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Registry list stubs (lines 566-567)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Registry actor list returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/registry/actor/list" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
And the facade-cov response data should contain key "items"
|
|
|
|
Scenario: Registry skill list returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/registry/skill/list" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Registry action list returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/registry/action/list" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Registry project list returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/registry/project/list" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Context stubs (lines 573-574)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Context inspect returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/context/inspect" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Context simulate returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/context/simulate" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Context set returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/context/set" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Health and diagnostics (lines 580-584)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Diagnostics run returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/diagnostics/run" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
And the facade-cov response data key "status" should equal "ok"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Sync stubs (lines 590-591)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Sync push returns not_implemented stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/sync/push" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
And the facade-cov response data key "status" should equal "no_sync_service"
|
|
|
|
Scenario: Sync status returns not_implemented stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/sync/status" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Namespace stubs (lines 597-598)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Namespace show returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/namespace/show" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
Scenario: Namespace members returns stub
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "_cleveragents/namespace/members" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "stub" should be true
|
|
|
|
# -------------------------------------------------------------------
|
|
# Dispatch error path — domain exception mapped to A2A error
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Dispatch maps domain exception to error response
|
|
Given a facade-cov facade with a raising PlanLifecycleService
|
|
When I dispatch facade-cov operation "plan.execute" with params {"plan_id": "PLAN-FAIL"}
|
|
Then the facade-cov response status should be "error"
|
|
And the facade-cov response error should not be None
|
|
|
|
# -------------------------------------------------------------------
|
|
# Dispatch TypeError — non-request argument
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Dispatch with non-A2aRequest argument raises TypeError
|
|
Given a facade-cov facade with no services
|
|
When I try to dispatch a non-A2aRequest object via the facade-cov facade
|
|
Then a facade-cov TypeError should be raised with message "request must be an A2aRequest instance"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Register service and handler map invalidation
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Registering a service invalidates the handler cache
|
|
Given a facade-cov facade with no services
|
|
When I dispatch facade-cov operation "session.create" with params {}
|
|
And I register a facade-cov service "session_service" with a mock
|
|
And I dispatch facade-cov operation "session.create" with params {"actor_name": "test-actor"}
|
|
Then the second facade-cov session create should use the new service
|
|
|
|
Scenario: Register service with empty name raises ValueError
|
|
Given a facade-cov facade with no services
|
|
When I try to register a facade-cov service with empty name
|
|
Then a facade-cov ValueError should be raised
|
|
|
|
# -------------------------------------------------------------------
|
|
# Session create with wired service (lines 315-317)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Session create with wired service delegates to service
|
|
Given a facade-cov facade with a mock SessionService
|
|
When I dispatch facade-cov operation "session.create" with params {"actor_name": "my-actor"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "created"
|
|
And the facade-cov response data key "session_id" should equal "MOCK-SESSION-001"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Session close with wired service and valid session_id (lines 329-335)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Session close with wired service and empty session_id raises error
|
|
Given a facade-cov facade with a mock SessionService
|
|
When I dispatch facade-cov operation "session.close" with params {"session_id": ""}
|
|
Then the facade-cov response status should be "error"
|
|
|
|
Scenario: Session close with wired service and valid session_id
|
|
Given a facade-cov facade with a mock SessionService
|
|
When I dispatch facade-cov operation "session.close" with params {"session_id": "SESS-001"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "closed"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Event subscribe with wired queue (lines 486-490)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Event subscribe with wired queue calls subscribe_local
|
|
Given a facade-cov facade with a mock EventQueue
|
|
When I dispatch facade-cov operation "event.subscribe" with params {}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "subscribed"
|
|
And the facade-cov response data should contain key "subscription_id"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Registry list tools with wired service (lines 441-445)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Registry list tools with wired service returns tool specs
|
|
Given a facade-cov facade with a mock ToolRegistry
|
|
When I dispatch facade-cov operation "registry.list_tools" with params {"namespace": "local"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data should contain key "tools"
|
|
And the facade-cov response tools list should have 2 items
|
|
|
|
# -------------------------------------------------------------------
|
|
# Registry list resources with wired service (lines 451-462)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Registry list resources with wired service returns resources
|
|
Given a facade-cov facade with a mock ResourceRegistryService
|
|
When I dispatch facade-cov operation "registry.list_resources" with params {"type_name": "git-checkout"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data should contain key "resources"
|
|
And the facade-cov response resources list should have 1 items
|
|
|
|
# -------------------------------------------------------------------
|
|
# Plan create with wired service — validation (lines 374-382)
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Plan create with wired service and missing action_name errors
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "plan.create" with params {"action_name": ""}
|
|
Then the facade-cov response status should be "error"
|
|
|
|
Scenario: Plan create with wired service and valid action_name succeeds
|
|
Given a facade-cov facade with a full mock PlanLifecycleService
|
|
When I dispatch facade-cov operation "plan.create" with params {"action_name": "build", "arguments": {"target": "all"}, "created_by": "test-user"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "plan_id" should equal "MOCK-PLAN-001"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Cleanup devcontainers — exception path exercised via mock
|
|
# -------------------------------------------------------------------
|
|
|
|
Scenario: Devcontainer cleanup exception is caught and logged
|
|
Given a facade-cov facade with no services and a failing cleanup service
|
|
When I dispatch facade-cov operation "session.close" with params {"session_id": "sess-with-failing-cleanup"}
|
|
Then the facade-cov response status should be "ok"
|
|
And the facade-cov response data key "status" should equal "closed"
|