"""Source crafting planner, from field HUD to exact current recipe submission.

Returns ordinary input proposals; it never sends them. A submission receipt
is not completion and cannot be replayed merely because the recipe stays open.
"""
from copy import deepcopy
from datetime import datetime
from .craft_observer import recipe_candidates,craft_request_node,current_craft_program

def _stamp(value):
    result=datetime.fromisoformat(value.replace('Z','+00:00'))
    if result.tzinfo is None:raise ValueError('craft_timestamp_timezone')
    return result

def _owner(scene,snapshot):
    owner=snapshot.get('input_owner') or {}
    identity=owner.get('owner') or owner
    if owner.get('continuous') is not True or owner.get('epoch') is None or not owner.get('source') or not owner.get('observed_at'):
        raise ValueError('continuous_current_craft_input_owner_required')
    if any(identity.get(k)!=snapshot.get(k) for k in ('character_id','session_id')):raise ValueError('craft_input_owner_scope')
    if owner.get('owner') and any(k in owner and owner[k]!=identity.get(k) for k in ('character_id','session_id')):raise ValueError('craft_input_owner_conflict')
    if scene.get('input_owner')!=owner:raise ValueError('craft_observer_owner_changed')
    return dict(epoch=owner['epoch'],**{k:identity[k] for k in ('character_id','session_id')})

def _validate_pending(node,snapshot,receipt,scene,owner,*,allow_attempted=False):
    pending=snapshot.get('pending_input') or {};saved=pending.get('planner_receipt') or {};sent=pending.get('input_receipt') or {}
    statuses=('sent_unconfirmed','unknown') if allow_attempted else ('sent_unconfirmed',)
    if (pending.get('status') not in statuses or sent.get('status') not in statuses or not pending.get('operation_id')
            or sent.get('attempted') is False or sent.get('status')=='unknown' and sent.get('attempted') is not True):
        raise ValueError('complete_owned_craft_input_receipt_required')
    if saved!=receipt or any(saved.get(k)!=snapshot.get(k) for k in ('session_id','character_id')) or saved.get('node_id')!=node['id']:
        raise ValueError('craft_pending_receipt_scope_changed')
    child=receipt.get('child_receipt') or {}
    if child.get('craft_owner')!=owner or child.get('recipe_id')!=scene['recipe_id'] or child.get('window_signature')!=scene.get('window_signature'):
        raise ValueError('craft_selection_ownership_changed')
    expected=child.get('actions',[]);actual=sent.get('receipts',[])
    if not expected or len(expected)!=len(actual):raise ValueError('craft_receipt_incomplete')
    for command,row in zip(expected,actual):
        observed=row.get('command',{})
        if (row.get('status') not in statuses or observed.get('kind')!=command['kind'] or row.get('attempted') is False
                or row.get('status')=='unknown' and row.get('attempted') is not True):raise ValueError('craft_receipt_step_not_sent')
        if not _stamp(receipt['observed_at'])<=_stamp(row['observed_at'])<=_stamp(scene['observed_at']):raise ValueError('craft_receipt_time_order')
        if observed.get('target_id')!=command['role']:raise ValueError('craft_receipt_target_changed')
        if command['kind']=='key' and observed.get('key')!=command['key']:raise ValueError('craft_receipt_key_changed')
        if command['kind'] in ('click','scroll','pointer_probe','pointer_click','pointer_scroll') and [observed.get('x'),observed.get('y')]!=command['point']:raise ValueError('craft_receipt_point_changed')
        if command['kind'] in ('pointer_probe','pointer_click','pointer_scroll') and (observed.get('probe_context')!=command['probe_context'] or observed.get('input_epoch')!=command['input_epoch']):
            raise ValueError('craft_pointer_receipt_context_changed')
        if command['kind'] in ('scroll','pointer_scroll') and (observed.get('delta')!=command['delta'] or list(observed.get('safe_rect') or [])!=command['safe_rect']):raise ValueError('craft_receipt_scroll_changed')
        if command['kind']=='craft_text' and (observed.get('text'),observed.get('text_scope'))!=(command['text'],command['text_scope']):raise ValueError('craft_receipt_quantity_changed')
    return child

def _cleanup_proofs(node,snapshot,child):
    from .arbeit_operations import _fresh
    rows=snapshot.get('result_cleanup_evidence') or (snapshot.get('craft_read') or {}).get('result_cleanup_evidence') or []
    operation=(snapshot.get('pending_input') or {}).get('operation_id')
    return [p for p in rows if _fresh(p,snapshot) and p.get('confirmed') is True
        and p.get('node_id')==node['id'] and p.get('recipe_id')==child['recipe_id']
        and p.get('operation_id')==operation and p.get('window_signature')==child['window_signature']
        and p.get('input_owner')==snapshot.get('input_owner') and p.get('active_crafting_ui_types')==[]
        and p.get('result_ui_present') is False and p.get('field_hud') is True
        and p.get('basis')=='current typed active UIInstanceHandleTable enumeration + current confirmed field HUD']

def craft_bag_materials(recipe,batches,snapshot,*,node_id):
    """Recheck actual fresh usable bag immediately before a consuming proposal."""
    from .material_supply import _bag
    from .goal_reservations import goal_commitment_for_item
    proofs=[]
    for material in recipe['consumable_ingredients']:
        item=dict(item_id=material['raw_id'],table_domain='ConsumableSheet',name=material['name'],source_ref=recipe['source_ref'])
        proof=_bag(item,snapshot);required=material['amount']*batches
        if proof['available_quantity']<required:raise ValueError('craft_material_bag_shortage:'+material['name'])
        commitment=goal_commitment_for_item(snapshot,item,exclude_node_id=node_id)
        free=max(0,proof['available_quantity']-commitment['required_quantity'])
        if free<required:raise ValueError('craft_material_goal_reserved_shortage:'+material['name'])
        proofs.append(dict(proof,required_quantity=required,batches=batches,goal_commitment=commitment,
            uncommitted_quantity=free,inventory_quantity_modified=False))
    if recipe['fake_item_ingredients']:
        from .objective_inventory import fake_item_craft_materials
        proofs.extend(fake_item_craft_materials(recipe,batches,snapshot,node_id=node_id))
    return proofs

def _production_baseline(node,snapshot,request=None):
    from .arbeit_operations import _fresh,_scope
    if node.get('params',{}).get('material_requirement') is not None:
        _,request=craft_request_node(node,snapshot);proof=request['current_inventory_evidence']
        return dict(kind='material_bag',count=proof['available_quantity'],goal=request['required_quantity'],
            observed_at=proof['observed_at'],proof=deepcopy(request))
    rows=[p for p in snapshot.get('objective_evidence',[]) if _fresh(p,snapshot) and _scope(p,node,objective=True)
        and p.get('confirmed') is True and p.get('semantics')=='current_quest_objective_native_IsAchieved'
        and type(p.get('count')) is int and type(p.get('goal')) is int and p['goal']>0 and 0<=p['count']<p['goal']
        and p.get('complete') is False]
    if len(rows)!=1:raise ValueError('craft_current_native_count_goal_baseline_required')
    return dict(kind='native_objective',count=rows[0]['count'],goal=rows[0]['goal'],observed_at=rows[0]['observed_at'],proof=deepcopy(rows[0]))

def _step_termination(node,snapshot,child):
    from .arbeit_operations import _fresh
    scope=child.get('goal_scope') or {}
    if not all(scope.get(k) is not None for k in ('quest_id','instance_id','accept_time','step_id','process_started_at')):return None
    rows=[p for p in snapshot.get('state_proofs',[]) if _fresh(p,snapshot) and p.get('confirmed') is True
        and p.get('state')=='step_complete' and all(p.get(k)==scope[k] for k in ('quest_id','instance_id','accept_time','step_id','process_started_at'))
        and p.get('basis') in ('native_IsQuestStepCompletedBefore_with_current_runtime_StepIds','native_IsAllStepsCompleted_and_current_runtime_StepIds')]
    return rows[0] if len(rows)==1 else None

def _batch_progress(node,snapshot,child):
    from .arbeit_operations import _fresh,_scope
    baseline=child.get('production_baseline') or {};old=baseline.get('proof') or {}
    if baseline.get('kind')=='material_bag':
        _,request=craft_request_node(node,snapshot);proof=request['current_inventory_evidence']
        identity=('table_domain','item_id','name','source_ref')
        if any(request['item'].get(k)!=old['item'].get(k) for k in identity) or request['required_quantity']!=baseline['goal']:
            raise ValueError('craft_batch_material_identity_changed')
        old_category=old['item'].get('category');new_category=request['item'].get('category')
        if old_category is not None and new_category is not None and old_category!=new_category:
            raise ValueError('craft_batch_material_observed_category_changed')
        count=proof['available_quantity'];observed=proof['observed_at'];evidence=request
    elif baseline.get('kind')=='native_objective':
        scope=('quest_id','instance_id','accept_time','step_id','step_index','objective_index','process_started_at')
        rows=[p for p in snapshot.get('objective_evidence',[]) if _fresh(p,snapshot) and _scope(p,node,objective=True)
            and p.get('confirmed') is True and p.get('semantics')=='current_quest_objective_native_IsAchieved'
            and all(p.get(k)==old.get(k) for k in scope) and p.get('goal')==baseline['goal'] and type(p.get('count')) is int]
        if len(rows)!=1:raise ValueError('craft_same_occurrence_batch_count_required')
        evidence=rows[0];count=evidence['count'];observed=evidence['observed_at']
    else:raise ValueError('craft_owned_production_baseline_required')
    if _stamp(observed)<=_stamp(baseline['observed_at']) or count<=baseline['count']:
        raise ValueError('craft_batch_count_or_usable_bag_delta_not_observed')
    return deepcopy(evidence)

def _result_action(node,snapshot,receipt,scene,child,owner):
    from .arbeit_operations import _authored_action,_waiting
    wait=lambda why:_waiting(node,why,scene.get('missing') or [why],receipt)
    if receipt.get('phase')=='craft.result.ack':return wait('craft_result_ack_awaiting_typed_empty_UI_and_HUD')
    if receipt.get('phase') not in ('craft.submit','craft.result.probe'):return wait('craft_result_without_owned_submit')
    pointer=scene.get('result_pointer') or {};target=scene.get('result_target') or {}
    kind=pointer.get('input_kind');context=pointer.get('probe_context') or {}
    if kind not in ('pointer_probe','pointer_click') or not context:return wait('current_craft_result_pointer_binding_required')
    if (context.get('target_ui_pointer')!=target.get('ui_pointer') or context.get('control_pointer')!=target.get('control_pointer')
            or context.get('model_event_id')!=target.get('model_event_id') or context.get('input_epoch')!=owner['epoch']
            or context.get('window_signature')!=scene['window_signature']):return wait('craft_result_pointer_identity_changed')
    if kind=='pointer_probe':
        if pointer.get('pointer_probe_ready') is not True or receipt['phase']=='craft.result.probe':return wait('craft_result_probe_effect_not_observed')
        phase='craft.result.probe'
    else:
        if receipt['phase']!='craft.result.probe' or pointer.get('pointer_verified',{}).get('confirmed') is not True:return wait('craft_result_owned_probe_required')
        if context!=(child.get('actions') or [{}])[0].get('probe_context'):return wait('craft_result_probe_target_changed')
        phase='craft.result.ack'
    x,y,w,h=target['rect_client'];width,height=scene['viewport']
    point=pointer['point_client']
    safe=scene.get('result_safe_rect')
    if not isinstance(safe,(list,tuple)) or len(safe)!=4:return wait('craft_result_current_safe_area_required')
    if not(0<=safe[0]<=x<=point[0]<x+w<=safe[2]<=width and 0<=safe[1]<=y<=point[1]<y+h<=safe[3]<=height):return wait('craft_result_target_clipped')
    action=dict(kind=kind,role='craft.result.confirm',point=point,source_rect=[x,y,x+w,y+h],safe_rect=[safe[0],safe[1],safe[2]-safe[0],safe[3]-safe[1]],
        input_epoch=owner['epoch'],probe_context=deepcopy(context))
    if kind=='pointer_click':action['pointer_verified']=deepcopy(pointer['pointer_verified'])
    saved=deepcopy(child);saved.update(actions=[action],stage_before='result')
    saved.setdefault('result_observations',[]).append(dict(source=scene['source'],observed_at=scene['observed_at'],
        session_id=scene['session_id'],character_id=scene['character_id'],ui_pointer=target['ui_pointer'],
        control_pointer=target['control_pointer'],model_event_id=target['model_event_id'],
        result_observed_at=scene.get('result_observed_at'),
        equipment_reward_bindings=deepcopy(scene.get('equipment_reward_bindings',[]))))
    return _authored_action(node,snapshot,[action],scene['source_refs'],phase,receipt,child=saved,
        observed_at=scene['observed_at'],prior_effect=True)

def craft_completion_boundary(node,snapshot,receipt,effect):
    """Goal effect cannot leave a still-owned crafting/result window behind."""
    from .arbeit_operations import _waiting,_complete,_fresh,_scope
    if not ((receipt or {}).get('phase') or '').startswith('craft.'):return None
    from .supply_selection import CRAFT_NAVIGATION,plan_supply_cleanup
    if receipt.get('phase') in (*CRAFT_NAVIGATION,'craft.supply_cleanup'):
        cleanup=plan_supply_cleanup(node,snapshot,receipt,effect=effect)
        if cleanup is not None:return cleanup
    wait=lambda why:_waiting(node,why,[why],receipt)
    try:
        scene=snapshot.get('crafting') or {}
        if scene.get('confirmed') is not True or not _fresh(scene,snapshot):return wait('craft_goal_reached_current_cleanup_observation_required')
        owner=_owner(scene,snapshot);child=_validate_pending(node,snapshot,receipt,scene,owner)
        if scene.get('stage')=='result':return _result_action(node,snapshot,receipt,scene,child,owner)
        scope=child.get('goal_scope') or {}
        native=[p for p in snapshot.get('objective_evidence',[]) if _fresh(p,snapshot) and _scope(p,node,objective=True)
            and p.get('confirmed') is True and p.get('complete') is True
            and p.get('semantics')=='current_quest_objective_native_IsAchieved'
            and all(p.get(k)==value for k,value in scope.items())]
        if node.get('params',{}).get('material_requirement') is not None:
            _,request=craft_request_node(node,snapshot)
            inventory=request.get('current_inventory_evidence') or {}
            if inventory.get('available_quantity',-1)<request['required_quantity']:return wait('craft_cleanup_material_bag_requirement_incomplete')
            native=[request]
        termination=_step_termination(node,snapshot,child)
        if len(native)!=1 and termination is None:return wait('craft_cleanup_same_occurrence_native_goal_complete_required')
        proofs=_cleanup_proofs(node,snapshot,child)
        if len(proofs)!=1:return wait('craft_goal_reached_owned_result_or_crafting_ui_not_cleaned')
        result=_complete(node,effect,receipt);result['effect_evidence']+=[deepcopy(native[0] if len(native)==1 else termination),deepcopy(proofs[0])]
        if len(native)!=1:result.update(completion_basis='current native step termination after owned UI cleanup',individual_craft_success_verified=False)
        return result
    except (ValueError,KeyError,TypeError,AttributeError) as exc:return wait(str(exc))

def plan_crafting(node,snapshot,receipt=None):
    from .arbeit_operations import _authored_action,_waiting,_complete,_fresh,_objective_effect,_scope
    wait=lambda why,missing=None:_waiting(node,why,missing or [why],receipt)
    try:
        if node.get('op') not in ('life.craft','inventory.hold'):return wait('craft_operation_not_supported')
        effect=_objective_effect(node,snapshot,receipt)
        if effect:return craft_completion_boundary(node,snapshot,receipt,effect) or _complete(node,effect,receipt)
        scene=snapshot.get('crafting') or {}
        if scene.get('confirmed') is not True or not _fresh(scene,snapshot):return wait('current_crafting_observation_required')
        if (receipt or {}).get('phase') in ('craft.submit','craft.result.probe','craft.result.ack'):
            owner=_owner(scene,snapshot);child=_validate_pending(node,snapshot,receipt,scene,owner)
            if scene.get('stage')=='result':return _result_action(node,snapshot,receipt,scene,child,owner)
            termination=_step_termination(node,snapshot,child)
            if termination:return craft_completion_boundary(node,snapshot,receipt,termination)
        bound,hold_request=craft_request_node(node,snapshot)
        recipes=[r for r in recipe_candidates(bound) if r['id']==scene.get('recipe_id')]
        if len(recipes)!=1:return wait('current_recipe_not_source_output_candidate')
        recipe=recipes[0];owner=_owner(scene,snapshot);stage=scene['stage'];child=None
        current_program=current_craft_program(node,snapshot) if node['op']=='life.craft' else None
        current_scope=hold_request or current_program or snapshot.get('current_quest') or {}
        scope_keys=('quest_id','instance_id','accept_time','step_id','step_index','process_started_at')
        goal_scope={k:current_scope[k] for k in scope_keys} if all(current_scope.get(k) is not None for k in scope_keys) else None
        if receipt:
            child=_validate_pending(node,snapshot,receipt,scene,owner)
            if child.get('goal_scope') is not None and child['goal_scope']!=goal_scope:return wait('craft_owned_goal_occurrence_changed')
            if stage=='result':return _result_action(node,snapshot,receipt,scene,child,owner)
            if receipt['phase'] in ('craft.submit','craft.result.ack') and stage=='field_hud':
                if len(_cleanup_proofs(node,snapshot,child))!=1:return wait('craft_batch_owned_UI_cleanup_required')
                progress=_batch_progress(node,snapshot,child)
                if child['production_baseline']['kind']=='native_objective' and progress.get('complete') is not False:return wait('craft_batch_goal_completion_requires_cleanup_boundary')
                if child['production_baseline']['kind']=='material_bag' and progress['current_inventory_evidence']['available_quantity']>=progress['required_quantity']:
                    return wait('craft_material_goal_completion_requires_cleanup_boundary')
                action=dict(kind='key',key='K',role='craft.open');saved=deepcopy(child)
                saved.update(actions=[action],stage_before='field_hud',completed_batch_evidence=progress,cleanup_evidence=_cleanup_proofs(node,snapshot,child)[0])
                saved.pop('production_baseline',None)
                return _authored_action(node,snapshot,[action],scene['source_refs'],'craft.open',receipt,child=saved,
                    observed_at=scene['observed_at'],prior_effect=True)
            if receipt['phase']=='craft.submit':return wait('craft_submission_awaiting_current_effect_or_result',scene.get('missing') or ['current exact objective/inventory effect; never infer success from sent input'])
            expected={'craft.open':('hub','facility','detail'),'craft.tab':('hub',),'craft.category':('facility','detail'),'craft.recipe':('detail',),'craft.quantity':('detail',),'craft.scroll':('hub','facility'),'craft.scroll.probe':('hub','facility')}
            if stage not in expected.get(receipt.get('phase'),()):return wait('craft_owned_transition_not_observed')
            if receipt.get('phase')=='craft.scroll.probe':
                scroll=(scene.get('actions') or {}).get('scroll') or {}
                old=(child.get('actions') or [{}])[0]
                if (scroll.get('kind')!='pointer_scroll' or scroll.get('pointer_verified',{}).get('confirmed') is not True
                        or scroll.get('probe_context')!=old.get('probe_context')):
                    return wait('craft_scroll_probe_effect_not_observed')
            if receipt.get('phase')=='craft.scroll':
                previous=child.get('scroll_observation') or {};current=scene.get('scroll_observation') or {}
                if not _fresh(current,snapshot) or not previous.get('digest') or current.get('window_id')!=previous.get('window_id') or current.get('digest')==previous['digest']:
                    return wait('craft_scroll_effect_not_observed')
        elif snapshot.get('pending_input'):return wait('foreign_pending_action_before_craft')
        actions=scene.get('actions',{});action=None;phase=None
        if stage=='field_hud':action=actions.get('open');phase='craft.open'
        elif stage=='hub':
            for name in ('tab','category','recipe','scroll'):
                if actions.get(name):action=actions[name];phase='craft.'+name;break
        elif stage=='facility':
            action=actions.get('recipe') or actions.get('scroll');phase='craft.recipe' if actions.get('recipe') else 'craft.scroll'
        elif stage=='detail':
            if scene.get('selected_recipe_id')!=recipe['id']:return wait('selected_native_craft_recipe_mismatch')
            # A currently opened detail is not ownership of a prior selection.
            if not receipt or receipt.get('phase') not in ('craft.recipe','craft.category','craft.open','craft.quantity'):
                return wait('owned_recipe_selection_receipt_required')
            count=(scene.get('quantity') or {}).get('value')
            if type(count) is not int or count<=0:return wait('current_craft_quantity_required')
            request=snapshot.get('craft_request')
            batches=1
            if request is not None:
                if not _fresh(request,snapshot) or request.get('node_id')!=node['id'] or request.get('recipe_id')!=recipe['id'] or type(request.get('batches')) is not int or request['batches']<=0:
                    return wait('current_craft_batch_request_scope')
                batches=request['batches']
            if count!=batches:
                if receipt.get('phase')=='craft.quantity':return wait('current_quantity_effect_not_observed')
                spinner=scene['quantity'];focus=actions.get('quantity')
                if not focus or not all(isinstance(spinner.get(k),dict) and spinner[k].get('event_present') is True for k in ('onFocus','onChange','onComplete')):
                    return wait('current_craft_spinner_events_or_rectangle_required')
                # Existing count-select/type workflow; MMML source wires integer
                # input onComplete. Its effect is checked on the next frame.
                action=[focus,dict(kind='craft_text',role='craft.quantity.text',text=str(batches),text_scope='craft.quantity'),
                        dict(kind='key',role='craft.quantity.complete',key='ENTER')];phase='craft.quantity'
            else:
                semantics=[p for p in snapshot.get('objective_evidence',[]) if _fresh(p,snapshot) and _scope(p,node,objective=True)
                    and p.get('confirmed') is True and p.get('semantics')==node['op'] and p.get('complete') is False]
                if node['op']=='inventory.hold' and hold_request is not None:
                    native=hold_request.get('native_effect') or {}
                    if hold_request.get('requirement_origin')=='source_recipe_material':
                        evidence=hold_request.get('current_inventory_evidence') or {}
                        semantics=[hold_request] if evidence.get('available_quantity',-1)<hold_request['required_quantity'] else []
                    else:semantics=[hold_request] if native.get('confirmed') is True and native.get('complete') is False else []
                elif node['op']=='life.craft':
                    program=current_program
                    semantics=[p for p in snapshot.get('objective_evidence',[]) if program is not None
                        and program['recipe_id']==recipe['id'] and _fresh(p,snapshot) and _scope(p,node,objective=True)
                        and p.get('confirmed') is True and p.get('complete') is False
                        and p.get('semantics')=='current_quest_objective_native_IsAchieved'
                        and all(p.get(k)==program.get(k) for k in ('quest_id','instance_id','accept_time','step_id','step_index','objective_index','process_started_at'))]
                if len(semantics)!=1:return wait('current_typed_craft_or_hold_semantics_required_before_consumption')
                if node['op']=='inventory.hold' and recipe['equipment_result_id']:
                    if hold_request is None:return wait('current_native_equipment_hold_constraints_required')
                    from .objective_inventory_policy import equipment_crafting_policy
                    equipment_policy=equipment_crafting_policy(node,recipe,hold_request,snapshot)
                    if equipment_policy['input_eligible'] is not True:
                        result=wait(equipment_policy['reason'],equipment_policy['missing'])
                        result['equipment_output_policy']=equipment_policy
                        return result
                confirm=scene.get('confirm') or {}
                if confirm.get('disabled') is not False or not isinstance(confirm.get('onclick'),dict) or confirm['onclick'].get('event_present') is not True:
                    return wait('current_craft_confirm_disabled_or_unbound')
                if scene.get('facility_level_low') is not False or scene.get('decor_score_insufficient') is not False:return wait('current_craft_facility_or_decor_constraint')
                materials=scene.get('materials',[]);expected=recipe['consumable_ingredients']+[
                    dict(r,name=r['display_name']) for r in recipe['fake_item_ingredients']]
                if len(materials)!=len(expected):return wait('current_craft_material_row_count_differs')
                used=set()
                for need in expected:
                    matches=[(i,r) for i,r in enumerate(materials) if r.get('item',{}).get('displayName')==need['name']]
                    if len(matches)!=1 or matches[0][0] in used:return wait('current_craft_material_identity_ambiguous')
                    i,row=matches[0];used.add(i)
                    if type(row.get('requiredCount')) is not int or type(row.get('inventoryCount')) is not int or row['requiredCount']!=need['amount']*batches or row['inventoryCount']<row['requiredCount']:
                        return wait('current_craft_material_shortage_or_amount_changed')
                if 'price' not in confirm:return wait('current_craft_price_descriptor_required')
                bag_proofs=craft_bag_materials(recipe,batches,snapshot,node_id=node['id'])
                production_baseline=_production_baseline(node,snapshot,hold_request)
                action=actions.get('submit');phase='craft.submit'
        elif stage=='result':return wait('current_craft_result_requires_source_ack_binding',scene.get('missing'))
        if phase=='craft.scroll' and action:
            if action.get('kind')=='pointer_probe':phase='craft.scroll.probe'
            elif action.get('kind')=='pointer_scroll' and (not receipt or receipt.get('phase')!='craft.scroll.probe'):
                return wait('craft_scroll_owned_probe_required')
        if not action:return wait('current_craft_action_rectangle_or_model_binding_required',scene.get('missing') or ['current source bound action'])
        if receipt and receipt.get('phase')==phase and phase!='craft.scroll':return wait('craft_owned_transition_not_observed')
        rows=deepcopy(action if isinstance(action,list) else [action])
        saved=dict(craft_owner=owner,recipe_id=recipe['id'],window_signature=scene['window_signature'],actions=rows,
            stage_before=stage,batches=locals().get('batches'),source_refs=scene['source_refs'],goal_scope=goal_scope,
            scroll_observation=deepcopy(scene.get('scroll_observation')))
        if phase=='craft.submit':
            saved.update(production_baseline=production_baseline,material_bag_evidence=bag_proofs)
            if 'equipment_policy' in locals():saved['equipment_output_policy']=equipment_policy
        return _authored_action(node,snapshot,rows,scene['source_refs'],phase,receipt,child=saved,observed_at=scene['observed_at'],prior_effect=bool(receipt))
    except (ValueError,KeyError,TypeError,AttributeError) as exc:return wait(str(exc))
