getMessage() ); } catch ( \Throwable $e ) { PRAutoBlogger_Run_Stage_State::fail( $run_id, $stage, $agent_role, $item_key ); PRAutoBlogger_Run_State::mark_status( $run_id, 'failed' ); PRAutoBlogger_Logger::instance()->error( sprintf( 'Stage replay %s for run %s/%s: %s', get_class( $e ), $run_id, $stage, $e->getMessage() ), 'rerun' ); PRAutoBlogger_Rerun_Job_Support::finish_error( $e->getMessage() ); } finally { PRAutoBlogger_Generation_Lock::release(); } } /** * Cron handler: re-run from a stage — demote it + downstream, then * re-enter the worker so demoted stages rebuild from current * upstream snapshots. * * Side effects: run_stages/runs mutations, worker LLM calls * (governed), post refresh, status transient, lock acquire/release. * * @param string $run_id Run UUID. * @param int $post_id Post the dossier acted on. * @param string $stage Target stage (start of the rebuild set). * @param string $item_key Item key. * @return void */ public function on_rebuild_job( string $run_id, int $post_id, string $stage, string $item_key ): void { PRAutoBlogger_Rerun_Job_Support::harden_process(); if ( ! PRAutoBlogger_Rerun_Job_Support::acquire_lock_or_report() ) { return; } try { // require_idle_lock=false: THIS handler owns the lock. $check = PRAutoBlogger_Rerun_Eligibility::check_rebuild( $run_id, $post_id, $stage, $item_key, false ); if ( ! $check['ok'] ) { PRAutoBlogger_Rerun_Job_Support::finish_error( $check['reason'] ); return; } if ( PRAutoBlogger_Rerun_Job_Support::monthly_budget_exceeded() ) { PRAutoBlogger_Rerun_Job_Support::finish_error( __( 'Monthly API budget exceeded. Re-run not started.', 'prautoblogger' ) ); return; } $idea_json = PRAutoBlogger_Stage_Input_Store::get_seed( $run_id, $item_key ); $idea_data = null !== $idea_json ? json_decode( $idea_json, true ) : null; if ( ! is_array( $idea_data ) ) { PRAutoBlogger_Rerun_Job_Support::finish_error( __( 'The stored idea seed could not be decoded.', 'prautoblogger' ) ); return; } $idea = new PRAutoBlogger_Article_Idea( $idea_data ); $job_start = current_time( 'mysql' ); $demoted = PRAutoBlogger_Rerun_Eligibility::rebuild_set( $stage ); if ( ! PRAutoBlogger_Run_State::reopen( $run_id ) ) { PRAutoBlogger_Rerun_Job_Support::finish_error( __( 'The run could not be reopened for new spend.', 'prautoblogger' ) ); return; } PRAutoBlogger_Run_Stage_Rerun_State::demote_to_pending( $run_id, $item_key, $demoted ); PRAutoBlogger_Rerun_Job_Support::broadcast( sprintf( /* translators: %s: stage display label. */ __( 'Re-running the pipeline from %s…', 'prautoblogger' ), PRAutoBlogger_Stage_Display_Map::label( $stage ) ) ); $tracker = new PRAutoBlogger_Cost_Tracker(); $tracker->set_run_id( $run_id ); $result = ( new PRAutoBlogger_Article_Worker( $tracker ) )->generate( $idea ); // The worker traps its own failures (fail_open_for_item); // publish-done is the success signal. $succeeded = PRAutoBlogger_Run_Stage_State::is_done( $run_id, 'publish', '', $item_key ); // Decisions recorded while rebuilding a human-modified item // derive from edited content — flag them (guardrail 2). if ( PRAutoBlogger_Run_Stage_Rerun_State::item_has_human_modified( $run_id, $item_key ) ) { PRAutoBlogger_Audit_Writer::flag_decisions_human_modified( $run_id, $demoted, $job_start ); } PRAutoBlogger_Run_State::mark_status( $run_id, $succeeded ? 'done' : 'failed' ); if ( $succeeded ) { PRAutoBlogger_Rerun_Job_Support::finish_complete( (float) $result['cost'] ); } else { PRAutoBlogger_Rerun_Job_Support::finish_error( __( 'The re-run did not complete — check the Activity Log and the dossier stage states.', 'prautoblogger' ) ); } } catch ( \Throwable $e ) { PRAutoBlogger_Run_State::mark_status( $run_id, 'failed' ); PRAutoBlogger_Logger::instance()->error( sprintf( 'Rebuild from %s %s for run %s: %s', $stage, get_class( $e ), $run_id, $e->getMessage() ), 'rerun' ); PRAutoBlogger_Rerun_Job_Support::finish_error( $e->getMessage() ); } finally { PRAutoBlogger_Generation_Lock::release(); } } }