@@ -778,6 +778,37 @@ def test_peer_update_rejects_another_results_read_and_consumption_receipts(scena
778778 consumed .write_bytes ((folder / "conclusion.consumed.json" ).read_bytes ())
779779 with pytest .raises (ValueError , match = "receipt scope" ):
780780 returns (root , "delivery" , "builder" )
781+
782+
783+ def test_a_failed_input_readiness_leaves_no_read_receipt (scenario , monkeypatch ):
784+ """A read that did not complete is not a delivered read.
785+
786+ #5511 reported that the Windows readiness crash left the request already
787+ looking read in `reads/`. That ordering was real: 025be07e3 later committed
788+ inbox reads after response validation instead of before it, and nothing has
789+ held the new ordering since. A receipt is evidence of a completed read only,
790+ so a raising readiness probe must leave `reads/` untouched and the next
791+ completed read must record it.
792+ """
793+
794+ from loopx .control_plane .collaboration import peers
795+ from loopx .control_plane .collaboration .inbox import _root
796+
797+ root , registry , _ , _ , _ , _ , request_id = scenario
798+ receipt = _root (root ) / "reads" / f"{ request_id } .json"
799+
800+ def _fail_readiness (* args , ** kwargs ):
801+ raise AttributeError ("module 'os' has no attribute 'O_NONBLOCK'" )
802+
803+ monkeypatch .setattr (peers , "_input_readiness_for_goal" , _fail_readiness )
804+ with pytest .raises (AttributeError , match = "O_NONBLOCK" ):
805+ peers .read_inbox (root , registry , "delivery" , "builder" )
806+ assert not receipt .exists ()
807+
808+ monkeypatch .undo ()
809+ assert peers .read_inbox (root , registry , "delivery" , "builder" )["items" ]
810+ assert receipt .is_file ()
811+
781812@pytest .mark .skipif (sys .platform != "win32" , reason = "Win32 extended path regression" )
782813def test_peer_exchange_survives_long_private_store_paths (scenario ):
783814 root , registry , brief , * _ = scenario
0 commit comments