#!/usr/bin/env python3 """Exit-semantics tests for the receipt observer Stop-hook client.""" from __future__ import annotations import importlib.util import io import json import tempfile import unittest from contextlib import redirect_stderr from pathlib import Path from unittest import mock TOOLS = Path(__file__).parents[2] / "framework/tools/lease-broker" CLIENT_PATH = TOOLS / "receipt-observer-client.py" def load_client(): spec = importlib.util.spec_from_file_location("receipt_observer_client_test", CLIENT_PATH) if spec is None or spec.loader is None: raise RuntimeError("unable to load receipt-observer-client.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module CLIENT = load_client() VALID_INPUT = json.dumps({"latest_assistant_message": "ordinary turn"}).encode() DEEPLY_NESTED_JSON = b"[" * 2_000 + b"0" + b"]" * 2_000 ENVIRONMENT = { "MOSAIC_RECEIPT_OBSERVER_SOCKET": "/unused/observer.sock", "MOSAIC_LEASE_SESSION_ID": "a" * 64, "MOSAIC_RUNTIME_GENERATION": "1", } class FakeObserverSocket: def __init__(self, response: bytes) -> None: self.response = response def __enter__(self): return self def __exit__(self, *_args: object) -> None: return None def settimeout(self, _timeout: float) -> None: return None def connect(self, _path: str) -> None: return None def sendall(self, _payload: bytes) -> None: return None def shutdown(self, _how: int) -> None: return None def recv(self, _size: int) -> bytes: response, self.response = self.response, b"" return response class ReceiptObserverClientExitSemanticsTest(unittest.TestCase): def run_client( self, *, input_bytes: bytes = VALID_INPUT, reply: dict[str, object] | None = None, transport_error: OSError | None = None, runtime: str = "pi", ) -> tuple[int, str, mock.Mock]: request = mock.Mock(return_value=reply) if transport_error is not None: request.side_effect = transport_error stderr = io.StringIO() with ( mock.patch.object(CLIENT.sys, "stdin", io.BytesIO(input_bytes)), mock.patch.object(CLIENT, "observer_request", request), redirect_stderr(stderr), ): arguments = ["--runtime", runtime] if runtime == "claude": arguments.append("--latest-entry") result = CLIENT.main(arguments, environ=ENVIRONMENT) return result, stderr.getvalue(), request def test_claude_prefers_inline_last_assistant_message(self) -> None: inline = "the just-finished assistant message" input_bytes = json.dumps({ "last_assistant_message": inline, "transcript_path": "/must/not/be/opened.jsonl", }).encode() result, stderr, request = self.run_client( input_bytes=input_bytes, reply={"ok": True}, runtime="claude", ) self.assertEqual(result, 0) self.assertEqual(stderr, "") self.assertEqual( request.call_args.args[1]["latest_assistant_message"], inline, ) def test_claude_falls_back_to_transcript_when_inline_field_is_absent(self) -> None: with tempfile.TemporaryDirectory() as directory: transcript = Path(directory) / "transcript.jsonl" transcript.write_text( json.dumps({ "message": { "role": "assistant", "content": [{"type": "text", "text": "fallback message"}], } }) + "\n", encoding="utf-8", ) input_bytes = json.dumps({"transcript_path": str(transcript)}).encode() result, stderr, request = self.run_client( input_bytes=input_bytes, reply={"ok": True}, runtime="claude", ) self.assertEqual(result, 0) self.assertEqual(stderr, "") self.assertEqual( request.call_args.args[1]["latest_assistant_message"], "fallback message", ) def test_claude_present_invalid_inline_field_fails_without_fallback(self) -> None: fallback = mock.Mock(return_value="must not be used") input_bytes = json.dumps({ "last_assistant_message": None, "transcript_path": "/unused/transcript.jsonl", }).encode() with mock.patch.object(CLIENT, "claude_latest_entry", fallback): result, stderr, request = self.run_client( input_bytes=input_bytes, reply={"ok": True}, runtime="claude", ) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr) request.assert_not_called() fallback.assert_not_called() def test_claude_inline_message_size_guard_stays_enforced(self) -> None: request = mock.Mock(return_value={"ok": True}) stderr = io.StringIO() with ( mock.patch.object(CLIENT.sys, "stdin", io.BytesIO(b"{}")), mock.patch.object( CLIENT, "read_json", return_value={"last_assistant_message": "x" * (CLIENT.MAX_FRAME + 1)}, ), mock.patch.object(CLIENT, "observer_request", request), redirect_stderr(stderr), ): result = CLIENT.main( ["--runtime", "claude", "--latest-entry"], environ=ENVIRONMENT, ) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr.getvalue()) request.assert_not_called() def test_pi_still_posts_only_its_runtime_message(self) -> None: result, stderr, request = self.run_client(reply={"ok": True}) self.assertEqual(result, 0) self.assertEqual(stderr, "") self.assertEqual( request.call_args.args[1]["latest_assistant_message"], "ordinary turn", ) def test_nothing_pending_observation_refusal_is_benign(self) -> None: result, stderr, request = self.run_client( reply={"ok": False, "code": "OBSERVATION_UNAVAILABLE"} ) self.assertEqual(result, 0) self.assertEqual(stderr, "") request.assert_called_once() def test_success_reply_remains_successful(self) -> None: result, stderr, _request = self.run_client(reply={"ok": True}) self.assertEqual(result, 0) self.assertEqual(stderr, "") def test_pending_cycle_auth_failure_stays_fail_closed(self) -> None: result, _stderr, _request = self.run_client( reply={"ok": False, "code": "ANCESTRY_MISMATCH"} ) self.assertEqual(result, 2) def test_transport_failure_stays_fail_closed(self) -> None: result, stderr, _request = self.run_client( transport_error=ConnectionRefusedError("observer unavailable") ) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr) def test_parse_failure_stays_fail_closed(self) -> None: result, stderr, request = self.run_client(input_bytes=b"{") self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr) request.assert_not_called() def test_malformed_wire_replies_stay_fail_closed(self) -> None: for response in ( b"not-json\n", b'{"ok":true}', b"{}\n{}\n", b'{"ok":false,"code":"OBSERVATION_UNAVAILABLE"}\n\n', b'{"ok":true,"ok":false,"code":"OBSERVATION_UNAVAILABLE"}\n', DEEPLY_NESTED_JSON + b"\n", b"x" * (CLIENT.MAX_FRAME + 1), ): with self.subTest(response=response): stderr = io.StringIO() with ( mock.patch.object(CLIENT.sys, "stdin", io.BytesIO(VALID_INPUT)), mock.patch.object( CLIENT.socket, "socket", return_value=FakeObserverSocket(response), ), redirect_stderr(stderr), ): result = CLIENT.main(["--runtime", "pi"], environ=ENVIRONMENT) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr.getvalue()) def test_oversized_input_stays_fail_closed(self) -> None: result, stderr, request = self.run_client(input_bytes=b"x" * (CLIENT.MAX_FRAME + 1)) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr) request.assert_not_called() def test_deeply_nested_input_stays_fail_closed(self) -> None: result, stderr, request = self.run_client(input_bytes=DEEPLY_NESTED_JSON) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr) request.assert_not_called() def test_json_recursion_failure_stays_fail_closed(self) -> None: request = mock.Mock() stderr = io.StringIO() with ( mock.patch.object(CLIENT.sys, "stdin", io.BytesIO(VALID_INPUT)), mock.patch.object(CLIENT, "observer_request", request), mock.patch.object( CLIENT.json, "loads", side_effect=RecursionError("maximum JSON nesting exceeded"), ), redirect_stderr(stderr), ): result = CLIENT.main(["--runtime", "pi"], environ=ENVIRONMENT) self.assertEqual(result, 2) self.assertIn("Mosaic receipt observer refused", stderr.getvalue()) request.assert_not_called() def test_observation_unavailable_with_unexpected_fields_stays_fail_closed(self) -> None: result, _stderr, _request = self.run_client( reply={"ok": False, "code": "OBSERVATION_UNAVAILABLE", "unexpected": True} ) self.assertEqual(result, 2) def test_non_boolean_ok_values_stay_fail_closed(self) -> None: for reply in ( {"ok": 1}, {"ok": 0, "code": "OBSERVATION_UNAVAILABLE"}, ): with self.subTest(reply=reply): result, _stderr, _request = self.run_client(reply=reply) self.assertEqual(result, 2) if __name__ == "__main__": unittest.main()