diff --git a/tests/cli_commands/test_source_session_lifetime.py b/tests/cli_commands/test_source_session_lifetime.py index 5817e8a3a..7fdaf593d 100644 --- a/tests/cli_commands/test_source_session_lifetime.py +++ b/tests/cli_commands/test_source_session_lifetime.py @@ -594,7 +594,7 @@ def test_bind_and_unbind_commit_exact_receipts( ] -def test_recreation_retires_bindings_and_fences_stale_bind( +def test_recreation_retires_bindings_fences_stale_operations_and_allows_successor( tmp_path: Path, capsys: pytest.CaptureFixture[str], ) -> None: @@ -608,6 +608,15 @@ def test_recreation_retires_bindings_and_fences_stale_bind( ) assert main(bind_arguments) == 0 capsys.readouterr() + second_bind_arguments = _binding_arguments( + registry_path, + operation="bind", + goal_instance_id=instance_a, + operation_id="bind-session-b", + session_id="session-b", + ) + assert main(second_bind_arguments) == 0 + capsys.readouterr() recreate_arguments = _recreation_arguments( registry_path, goal_instance_id=instance_a, @@ -633,7 +642,10 @@ def test_recreation_retires_bindings_and_fences_stale_bind( ] assert registry["lifetime_receipts"][-1] == recreated["receipt"] assert registry["session_receipts"][-1]["operation"] == "retire_bindings" - assert registry["session_receipts"][-1]["session_ids"] == ["session-a"] + assert registry["session_receipts"][-1]["session_ids"] == [ + "session-a", + "session-b", + ] before_replay = registry_path.read_bytes() assert main(recreate_arguments) == 0 @@ -669,6 +681,43 @@ def test_recreation_retires_bindings_and_fences_stale_bind( assert "stale_goal_instance" in stale_bind_rejection["error"] assert registry_path.read_bytes() == before_replay + stale_unbind = _binding_arguments( + registry_path, + operation="unbind", + goal_instance_id=instance_a, + operation_id="unbind-after-recreate", + ) + assert main(stale_unbind) == 1 + stale_unbind_rejection = json.loads(capsys.readouterr().out) + assert "stale_goal_instance" in stale_unbind_rejection["error"] + assert registry_path.read_bytes() == before_replay + + successor_goal_before = _registry_payload(registry_path)["goals"][0] + successor_bind = _binding_arguments( + registry_path, + operation="bind", + goal_instance_id=instance_b, + operation_id="bind-successor-session", + session_id="successor-session", + ) + assert main(successor_bind) == 0 + successor_bound = json.loads(capsys.readouterr().out) + registry = _registry_payload(registry_path) + assert successor_bound["changed"] is True + assert registry["goals"][0] == successor_goal_before + assert registry["session_bindings"] == [ + { + "session_id": "successor-session", + "foreground_goal_ref": recreated["goal_ref"], + } + ] + assert registry["session_receipts"][-1] == successor_bound["receipt"] + assert not any( + receipt.get("operation_id") + in {"bind-after-recreate", "unbind-after-recreate"} + for receipt in registry["session_receipts"] + ) + def test_paused_bind_cannot_cross_recreation_aba( tmp_path: Path,