3939from autoresearch .prefill .cursor_strategy import CursorStrategyAdapter
4040from autoresearch .prefill .strategy_tournament import StrategyEvent
4141from autoresearch .prefill .orchestration_state import (
42+ BlockedEventType ,
4243 BlockedExitEvent ,
4344 OrchestrationCheckpoint ,
4445 ProofState ,
@@ -1696,6 +1697,73 @@ def route_contract_to_subgoal_generation(
16961697 return True
16971698
16981699
1700+ def is_contract_bound_subgoal_resume (
1701+ checkpoint : OrchestrationCheckpoint | None ,
1702+ ) -> bool :
1703+ """Recognize a typed decomposition resume independent of wrapper candidate."""
1704+ return bool (
1705+ checkpoint is not None
1706+ and checkpoint .proof_state == ProofState .DECOMPOSER
1707+ and checkpoint .target_obligation_id
1708+ and checkpoint .proposition_hash
1709+ and checkpoint .target_context_hash
1710+ and checkpoint .selected_strategy_plan_id
1711+ and checkpoint .research_contract_id
1712+ and not checkpoint .adapter_status
1713+ )
1714+
1715+
1716+ def recover_contract_subgoal_duplicate_block (
1717+ checkpoint : OrchestrationCheckpoint | None ,
1718+ ) -> BlockedExitEvent | None :
1719+ """Repair only the legacy novelty block on a bound decomposition resume."""
1720+ duplicate_reason = (
1721+ "Strategy proposals were duplicates; reuse the current candidate "
1722+ "and unresolved role."
1723+ )
1724+ if (
1725+ checkpoint is None
1726+ or checkpoint .proof_state != ProofState .BLOCKED
1727+ or checkpoint .blocked_reason != duplicate_reason
1728+ or checkpoint .proof_plan_id
1729+ or checkpoint .executable_plan_node_id
1730+ or not checkpoint .research_contract_id
1731+ or not checkpoint .selected_strategy_plan_id
1732+ or not checkpoint .target_context_hash
1733+ ):
1734+ return None
1735+ evidence = next (
1736+ (
1737+ str (item .get ("event_id" , "" ))
1738+ for item in reversed (checkpoint .recovery_events )
1739+ if item .get ("event_type" ) == "RESEARCH_CONTRACT_SUBGOAL_REQUIRED"
1740+ and item .get ("target_obligation_id" )
1741+ == checkpoint .target_obligation_id
1742+ and item .get ("research_contract_id" )
1743+ == checkpoint .research_contract_id
1744+ ),
1745+ "" ,
1746+ )
1747+ if not evidence :
1748+ return None
1749+ event = BlockedExitEvent (
1750+ event_id = hashlib .sha256 (
1751+ (
1752+ "contract-subgoal-duplicate-recovery:"
1753+ + evidence
1754+ + checkpoint .target_context_hash
1755+ ).encode ()
1756+ ).hexdigest (),
1757+ event_type = BlockedEventType .VALIDATED_EVIDENCE_BACKJUMP .value ,
1758+ reason = "target-bound decomposition bypasses candidate novelty" ,
1759+ target_state = ProofState .DECOMPOSER .value ,
1760+ reset_role = ProofState .DECOMPOSER .value ,
1761+ metadata = {"evidence_sha256" : evidence },
1762+ )
1763+ apply_blocked_exit_event (checkpoint , event )
1764+ return event
1765+
1766+
16991767def should_resume_downstream (
17001768 checkpoint : OrchestrationCheckpoint | None ,
17011769 * ,
@@ -1705,9 +1773,12 @@ def should_resume_downstream(
17051773) -> bool :
17061774 """Keep a persisted role unless an explicit Strategy policy overrides it."""
17071775 return bool (
1708- is_resumable_checkpoint (
1709- checkpoint ,
1710- candidate_sha256 = candidate_sha256 ,
1776+ (
1777+ is_resumable_checkpoint (
1778+ checkpoint ,
1779+ candidate_sha256 = candidate_sha256 ,
1780+ )
1781+ or is_contract_bound_subgoal_resume (checkpoint )
17111782 )
17121783 and not (
17131784 checkpoint is not None
@@ -3065,6 +3136,28 @@ def run_supervisor_iterations(args) -> int:
30653136 cause = event .event_type ,
30663137 event_id = event .event_id ,
30673138 )
3139+ recovered_event = recover_contract_subgoal_duplicate_block (checkpoint )
3140+ if recovered_event is not None and orchestration_path is not None :
3141+ save_orchestration_checkpoint (orchestration_path , checkpoint )
3142+ append_blocked_event_journal (
3143+ orchestration_path .with_name (
3144+ "proof_orchestration.journal.jsonl" ,
3145+ ),
3146+ recovered_event ,
3147+ before_state = ProofState .BLOCKED .value ,
3148+ after_state = checkpoint .state ,
3149+ )
3150+ blocked_logger .transition (
3151+ next_state = checkpoint .state ,
3152+ cause = recovered_event .event_type ,
3153+ event_id = recovered_event .event_id ,
3154+ )
3155+ print (
3156+ "[proof-live] stage=backjump "
3157+ "from=BLOCKED to=DECOMPOSER "
3158+ "reason=target-bound-candidate-novelty-bypass" ,
3159+ flush = True ,
3160+ )
30683161 if (
30693162 checkpoint is not None
30703163 and route_contract_to_subgoal_generation (checkpoint )
0 commit comments