diff --git a/tests/control_plane/test_todo_claim_argument_recovery.py b/tests/control_plane/test_todo_claim_argument_recovery.py index 08d0f8125c..fe974441f1 100644 --- a/tests/control_plane/test_todo_claim_argument_recovery.py +++ b/tests/control_plane/test_todo_claim_argument_recovery.py @@ -210,3 +210,22 @@ def test_ordinary_claim_progress_completion_need_no_lease_flags(claim_cli): replay = run([*prefix, "todo", "complete", *common, "--evidence", "evidence:validated-output"]) assert replay["ok"] is True assert snapshot() == before + + +@pytest.mark.parametrize("command", ["update", "complete"]) +def test_empty_managed_reference_is_rejected_without_mutation(claim_cli, command): + _, run, snapshot, prefix, claim, _, _, _ = claim_cli + accepted = run([*prefix, *claim, "--claimed-by", "peer-a", + "--claim-operation-id", "valid-managed-reference"]) + assert accepted["ok"] and accepted["execution_managed"] + before = snapshot() + arguments = [*prefix, "todo", command, "--goal-id", GOAL, "--todo-id", TODO, + "--agent-id", "peer-a", "--claim-operation-id", ""] + arguments += (["--note", "Only a valid original execution may update this task."] + if command == "update" else ["--evidence", "evidence:validated-output"]) + rejected = run(arguments, expected_exit=1) + assert rejected["error_code"] == ( + "invalid_local_coordination_todo_update_request" if command == "update" + else "invalid_local_coordination_todo_terminal_lifecycle_request") + assert rejected["error"] == "claim_operation_id must be a non-empty trimmed string" + assert snapshot() == before diff --git a/tests/test_cli_argument_diagnostics.py b/tests/test_cli_argument_diagnostics.py index c3b06e4847..3991d4b80d 100644 --- a/tests/test_cli_argument_diagnostics.py +++ b/tests/test_cli_argument_diagnostics.py @@ -557,7 +557,7 @@ def test_todo_claim_validation_preserves_exact_diagnostics( assert str(exc_info.value) == expected -@pytest.mark.parametrize("command", ["add", "list", "update", "complete", "supersede"]) +@pytest.mark.parametrize("command", ["add", "list", "supersede"]) @pytest.mark.parametrize("operation_id", ["retry-one", ""]) def test_claim_operation_id_is_not_silently_ignored_by_other_commands(command, operation_id): args = build_parser().parse_args([ @@ -567,6 +567,18 @@ def test_claim_operation_id_is_not_silently_ignored_by_other_commands(command, o validate_shared_todo_options(args) +@pytest.mark.parametrize("command", ["update", "complete"]) +@pytest.mark.parametrize("operation_id", ["retry-one", ""]) +def test_managed_claim_reference_is_retained_for_the_execution_owner(command, operation_id): + args = build_parser().parse_args([ + "todo", command, "--goal-id", "example-goal", "--claim-operation-id", operation_id, + ]) + validate_shared_todo_options(args) + # Scope validation admits the supported caller without dropping its raw + # reference. The native owner still rejects an invalid empty value. + assert args.claim_operation_id == operation_id + + @pytest.mark.parametrize( ("extra_args", "expected"), [