diff --git a/alembic/versions/m9_001_session_name_column.py b/alembic/versions/m9_001_session_name_column.py new file mode 100644 index 000000000..d19339f91 --- /dev/null +++ b/alembic/versions/m9_001_session_name_column.py @@ -0,0 +1,37 @@ +"""Add name column to sessions table. + +The ``name`` column was added to the ``SessionModel`` ORM class but the +corresponding Alembic migration was missing, causing ``session create`` +to fail with ``OperationalError: table sessions has no column named name`` +when the database was created via Alembic migrations (as opposed to +``Base.metadata.create_all()`` used by the template DB). + +Revision ID: m9_001_session_name_column +Revises: m6_006_estimation_report_json +Create Date: 2026-04-04 12:00:00 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "m9_001_session_name_column" +down_revision: str | Sequence[str] | None = "m6_006_estimation_report_json" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Add ``name`` column to ``sessions`` table.""" + op.add_column( + "sessions", + sa.Column("name", sa.String(255), nullable=True), + ) + + +def downgrade() -> None: + """Remove ``name`` column from ``sessions`` table.""" + op.drop_column("sessions", "name") diff --git a/robot/helper_audit_wiring.py b/robot/helper_audit_wiring.py index 0f2126ce2..3bad1f020 100644 --- a/robot/helper_audit_wiring.py +++ b/robot/helper_audit_wiring.py @@ -125,10 +125,8 @@ def ignore_non_security() -> None: def container_wiring() -> None: """Verify DI container registers a functional AuditEventSubscriber. - Verifies the subscriber type is correct and that the subscriber is - wired to both the event bus and audit service from the container. - The functional end-to-end audit pipeline is tested separately in - e2e_plan_lifecycle (which uses a controlled, non-container wiring). + Goes beyond type checking: emits an event through the container's + EventBus and verifies it reaches the AuditService via the subscriber. """ from dependency_injector import providers @@ -143,7 +141,6 @@ def container_wiring() -> None: # requires the .cleveragents/ directory to exist). container.database_url.override(providers.Object("sqlite:///:memory:")) - # Verify subscriber type subscriber = container.audit_event_subscriber() if not isinstance(subscriber, AuditEventSubscriber): print( @@ -152,22 +149,29 @@ def container_wiring() -> None: ) sys.exit(1) - # Verify the subscriber has the expected audit_service and event_bus attributes + # Functional verification: emit through container's EventBus + # and verify the event reaches the AuditService. audit_svc = container.audit_service() - bus = container.event_bus() - if subscriber._audit_service is not audit_svc: - print( - "FAIL: subscriber._audit_service is not container.audit_service()", - file=sys.stderr, - ) - sys.exit(1) - if subscriber._event_bus is not bus: - print( - "FAIL: subscriber._event_bus is not container.event_bus()", - file=sys.stderr, - ) - sys.exit(1) + # The container's in-memory DB has no tables yet — create them + # so the audit service can persist entries. + db_session = audit_svc._ensure_session() + audit_engine = db_session.get_bind() + Base.metadata.create_all(audit_engine) + + # Disable async mode so writes happen synchronously and we can + # verify the count immediately without race conditions. + audit_svc._async_mode = False + + bus = container.event_bus() + bus.emit(DomainEvent(event_type=EventType.PLAN_APPLIED, plan_id="WIRE-001")) + count = audit_svc.count() + if count < 1: + print( + f"FAIL: expected >=1 audit entries after emit, got {count}", + file=sys.stderr, + ) + sys.exit(1) print("CONTAINER_OK") diff --git a/robot/helper_m6_autonomy_acceptance.py b/robot/helper_m6_autonomy_acceptance.py index a09bc25db..ea9acb23f 100644 --- a/robot/helper_m6_autonomy_acceptance.py +++ b/robot/helper_m6_autonomy_acceptance.py @@ -47,7 +47,7 @@ def facade_session() -> None: facade = A2aLocalFacade() resp_create = facade.dispatch(A2aRequest(method="session.create", params={})) - assert resp_create.result is not None, f"got error: {resp_create.error}" + assert resp_create.result is not None, f"Expected ok, got {resp_create.status}" assert "session_id" in resp_create.result resp_close = facade.dispatch(A2aRequest(method="session.close", params={})) diff --git a/robot/helper_session_cli.py b/robot/helper_session_cli.py index 94d7d06cf..6704db604 100644 --- a/robot/helper_session_cli.py +++ b/robot/helper_session_cli.py @@ -148,7 +148,7 @@ def show_valid() -> None: try: result = runner.invoke(session_app, ["show", sid]) assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}" - assert "Session Details" in result.output + assert "Session Summary" in result.output print("session-cli-show-valid-ok") finally: _teardown() diff --git a/robot/helper_wf03_plan_prompt_confidence.py b/robot/helper_wf03_plan_prompt_confidence.py index fc505a680..0a822bf07 100644 --- a/robot/helper_wf03_plan_prompt_confidence.py +++ b/robot/helper_wf03_plan_prompt_confidence.py @@ -70,15 +70,15 @@ def wf03_plan_prompt_facade() -> None: guidance = "Use separate models/ directory instead of inline" request = A2aRequest( - operation="_cleveragents/plan/prompt", + method="_cleveragents/plan/prompt", params={"plan_id": plan_id, "guidance": guidance}, ) response = facade.dispatch(request) - if response.status != "ok": - _fail(f"Expected status 'ok', got '{response.status}'") + if response.result is None: + _fail(f"Expected result, got error: {response.error}") - data = response.data + data = response.result if data.get("status") != "guidance_injected": _fail(f"Expected data.status 'guidance_injected', got: {data}") if data.get("plan_id") != plan_id: @@ -254,7 +254,7 @@ def wf03_pause_and_resume() -> None: "This follows the project convention." ) prompt_request = A2aRequest( - operation="_cleveragents/plan/prompt", + method="_cleveragents/plan/prompt", params={ "plan_id": paused_plan_id, "guidance": guidance_text, @@ -262,19 +262,19 @@ def wf03_pause_and_resume() -> None: ) prompt_response = facade.dispatch(prompt_request) - if prompt_response.status != "ok": + if prompt_response.result is None: _fail(f"Step 3: plan prompt failed: {prompt_response}") - if prompt_response.data.get("status") != "guidance_injected": - _fail(f"Step 3: Expected guidance_injected: {prompt_response.data}") + if prompt_response.result.get("status") != "guidance_injected": + _fail(f"Step 3: Expected guidance_injected: {prompt_response.result}") # Verify guidance propagates through the dispatch path - actual_guidance = prompt_response.data.get("guidance") + actual_guidance = prompt_response.result.get("guidance") if actual_guidance != guidance_text: _fail(f"Step 3: Expected guidance '{guidance_text}', got: {actual_guidance}") # Verify the plan_id from response matches paused context - actual_plan_id = prompt_response.data.get("plan_id") + actual_plan_id = prompt_response.result.get("plan_id") if actual_plan_id != paused_plan_id: _fail(f"Step 3: Expected plan_id '{paused_plan_id}', got: {actual_plan_id}") diff --git a/robot/wf02_test_generation_artifacts.py b/robot/wf02_test_generation_artifacts.py index 068cb6772..ccafe3b2a 100644 --- a/robot/wf02_test_generation_artifacts.py +++ b/robot/wf02_test_generation_artifacts.py @@ -181,12 +181,12 @@ def wf02_mocked_generation_artifacts() -> None: ) artifacts_response = facade.dispatch( A2aRequest( - operation="_cleveragents/plan/artifacts", + method="_cleveragents/plan/artifacts", params={"plan_id": plan_id}, ) ) - assert artifacts_response.status == "ok", artifacts_response.error - response_data = artifacts_response.data + assert artifacts_response.result is not None, artifacts_response.error + response_data = artifacts_response.result assert response_data.get("plan_id") == plan_id assert response_data.get("changeset_id") == changeset_id sandbox_refs = response_data.get("sandbox_refs")