@@ -677,6 +677,140 @@ def test_independent_inclusive_240_420_deadlines_have_one_final_read(self) -> No
677677 self .assertFalse (any (value > 420 for value in gateway .observation_times ["worker" ]))
678678 self .assertEqual (gateway .current , 420 )
679679
680+ def test_schema1_recovery_proves_terminal_receipts_before_legacy_public_health (self ) -> None :
681+ gateway = CooperativeRecoveryGateway (complete_at = {"web" : 0 , "worker" : 0 })
682+ legacy_identity = ReleaseIdentity .legacy (SHA_A , DIGEST_A , REPOSITORY )
683+ original_update = gateway .update_service
684+
685+ def update_legacy (* args , ** kwargs ): # type: ignore[no-untyped-def]
686+ receipt = original_update (* args , ** kwargs )
687+ workload = receipt .workload
688+ snapshot = gateway .snapshots [workload ]
689+ gateway .snapshots [workload ] = ServiceSnapshot (
690+ service_name = snapshot .service_name ,
691+ task_definition_arn = snapshot .task_definition_arn ,
692+ desired_count = snapshot .desired_count ,
693+ running_count = snapshot .running_count ,
694+ pending_count = snapshot .pending_count ,
695+ source_sha = legacy_identity .source_sha ,
696+ image_digest = legacy_identity .image_digest ,
697+ primary_deployment_id = snapshot .primary_deployment_id ,
698+ version = legacy_identity .version ,
699+ identity_schema = legacy_identity .identity_schema ,
700+ )
701+ return receipt
702+
703+ gateway .update_service = update_legacy # type: ignore[method-assign]
704+ targets , terminal , attempted = self .restore_phase ()
705+ attempted_states : dict [str , ServiceTarget | ServicePredecessor ] = dict (attempted )
706+
707+ _compensate (
708+ gateway ,
709+ targets ,
710+ terminal ,
711+ legacy_identity ,
712+ attempted_states ,
713+ )
714+
715+ terminal_index = gateway .operations .index ("terminal-at:0" )
716+ health_index = gateway .operations .index ("health-at:0" )
717+ self .assertLess (terminal_index , health_index )
718+ self .assertIn (f"health:{ SHA_A } :{ SHA_A } " , gateway .operations )
719+
720+ def test_schema1_mixed_worker_never_attempts_or_passes_public_health (self ) -> None :
721+ gateway = CooperativeRecoveryGateway (complete_at = {"web" : 0 , "worker" : 0 })
722+ legacy_identity = ReleaseIdentity .legacy (SHA_A , DIGEST_A , REPOSITORY )
723+ original_update = gateway .update_service
724+
725+ def update_mixed_worker (* args , ** kwargs ): # type: ignore[no-untyped-def]
726+ receipt = original_update (* args , ** kwargs )
727+ workload = receipt .workload
728+ snapshot = gateway .snapshots [workload ]
729+ source_sha = "f" * 40 if workload == "worker" else legacy_identity .source_sha
730+ gateway .snapshots [workload ] = ServiceSnapshot (
731+ service_name = snapshot .service_name ,
732+ task_definition_arn = snapshot .task_definition_arn ,
733+ desired_count = snapshot .desired_count ,
734+ running_count = snapshot .running_count ,
735+ pending_count = snapshot .pending_count ,
736+ source_sha = source_sha ,
737+ image_digest = legacy_identity .image_digest ,
738+ primary_deployment_id = snapshot .primary_deployment_id ,
739+ version = source_sha ,
740+ identity_schema = legacy_identity .identity_schema ,
741+ )
742+ return receipt
743+
744+ gateway .update_service = update_mixed_worker # type: ignore[method-assign]
745+ targets , terminal , attempted = self .restore_phase ()
746+ attempted_states : dict [str , ServiceTarget | ServicePredecessor ] = dict (attempted )
747+ Path (".tmp" ).mkdir (exist_ok = True )
748+ with tempfile .TemporaryDirectory (dir = ".tmp" ) as directory :
749+ evidence_path = Path (directory ) / "recovery-evidence.json"
750+ with self .assertRaises (CompensationError ):
751+ _compensate (
752+ gateway ,
753+ targets ,
754+ terminal ,
755+ legacy_identity ,
756+ attempted_states ,
757+ evidence_path = evidence_path ,
758+ )
759+ stages = json .loads (evidence_path .read_text ())["stages" ]
760+
761+ self .assertFalse (any (operation .startswith ("health" ) for operation in gateway .operations ))
762+ terminal_evidence = next (
763+ item for item in stages if item ["stage" ] == "recovery_terminal_pair"
764+ )
765+ public_evidence = next (item for item in stages if item ["stage" ] == "recovery_public_health" )
766+ total_evidence = next (item for item in stages if item ["stage" ] == "recovery_total" )
767+ self .assertEqual (terminal_evidence ["result" ], "contract_contradiction" )
768+ self .assertEqual (public_evidence ["result" ], "not_attempted" )
769+ self .assertEqual (public_evidence ["proof" ]["attempted" ], False )
770+ self .assertEqual (public_evidence ["proof" ]["exact_prior_sha_ready" ], False )
771+ self .assertEqual (total_evidence ["result" ], "contract_contradiction" )
772+ self .assertEqual (total_evidence ["proof" ]["terminal_pair" ], False )
773+ self .assertEqual (total_evidence ["proof" ]["public_health" ], False )
774+
775+ def test_retained_receipt_error_blocks_schema2_public_health_after_terminal_read (self ) -> None :
776+ gateway = CooperativeRecoveryGateway (complete_at = {"web" : 0 , "worker" : 0 })
777+ original_observe = gateway .observe_recovery_receipt
778+
779+ def observe_with_worker_error (
780+ receipt : ServiceUpdateReceipt ,
781+ * ,
782+ workload_deadline : float ,
783+ phase_deadline : float ,
784+ ) -> bool :
785+ if receipt .workload == "worker" :
786+ raise ReleaseContractError ("injected worker receipt contradiction" )
787+ return original_observe (
788+ receipt ,
789+ workload_deadline = workload_deadline ,
790+ phase_deadline = phase_deadline ,
791+ )
792+
793+ gateway .observe_recovery_receipt = observe_with_worker_error # type: ignore[method-assign]
794+ Path (".tmp" ).mkdir (exist_ok = True )
795+ with tempfile .TemporaryDirectory (dir = ".tmp" ) as directory :
796+ evidence_path = Path (directory ) / "recovery-evidence.json"
797+ with self .assertRaises (CompensationError ):
798+ self .compensate (gateway , evidence_path = evidence_path )
799+ stages = json .loads (evidence_path .read_text ())["stages" ]
800+
801+ self .assertFalse (any (operation .startswith ("health" ) for operation in gateway .operations ))
802+ terminal_evidence = next (
803+ item for item in stages if item ["stage" ] == "recovery_terminal_pair"
804+ )
805+ public_evidence = next (item for item in stages if item ["stage" ] == "recovery_public_health" )
806+ total_evidence = next (item for item in stages if item ["stage" ] == "recovery_total" )
807+ self .assertEqual (terminal_evidence ["result" ], "passed" )
808+ self .assertEqual (public_evidence ["result" ], "not_attempted" )
809+ self .assertEqual (public_evidence ["proof" ]["attempted" ], False )
810+ self .assertEqual (public_evidence ["proof" ]["exact_prior_sha_ready" ], False )
811+ self .assertEqual (total_evidence ["result" ], "contract_contradiction" )
812+ self .assertEqual (total_evidence ["proof" ]["public_health" ], False )
813+
680814 def test_worker_deadline_cannot_be_rescued_by_a_later_terminal_fixture (self ) -> None :
681815 gateway = CooperativeRecoveryGateway (complete_at = {"web" : 10 , "worker" : 430 })
682816
0 commit comments