feat(runs): state machine, idempotent resume, reaper extension, retention
Pipeline v2 Phase 1, commit 4/4. Run_Stage_State persists per-run per-stage state keyed run_id + stage + agent_role + item_key (item_key scopes articles because one run_id spans a whole batch; agent_role is the Phase-2 quorum dimension, schema-only). Done stages snapshot output and are reused on re-entry — writer stages via Content_Generator::set_run_item, the review via its stored verdict, a published item skipped outright — never re-run, never re-charged. Post creation is keyed by _prautoblogger_run_id + new _prautoblogger_idea_hash (check-before-insert in Publisher) so retries cannot duplicate posts. Run-level research/analysis checkpoints + done/failed lifecycle marks ride Pipeline_Runner/Executor (incl. orphan-abort + budget-abort paths). Run_Reaper rides the EXISTING #19 daily cron: stages stuck running >2x expected stage wall-clock and runs silent >2x expected run wall-clock -> failed (filterable), open stages reaped, queryable as 'incomplete'. Retention: request_json + stage output payloads NULLed after R days, R from the new Audit Payload Retention setting (default constant 14, 0 = keep forever, no literal at point of use). Audit_Writer records editorial verdicts (run_decisions) and per-article kept sources (run_sources) for new runs. Under-300: status-transient/summary helpers extracted verbatim to Pipeline_Status (Pipeline_Runner 271; removed the worker's duplicate). Agent-Session: pipeline-v2-phase1-engineer-20260612
This commit is contained in:
@@ -53,10 +53,25 @@ class PRAutoBlogger_Executor {
|
||||
sprintf( 'Daily generation %s: %s', get_class( $e ), $e->getMessage() ),
|
||||
'scheduler'
|
||||
);
|
||||
$this->mark_current_run_failed();
|
||||
PRAutoBlogger_Generation_Lock::release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this process's run failed after a fatal pipeline error
|
||||
* (no-op outside a run; a governor-halted run stays halted — final
|
||||
* states are sticky).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mark_current_run_failed(): void {
|
||||
$run_id = PRAutoBlogger_Run_Context::current_run_id();
|
||||
if ( null !== $run_id ) {
|
||||
PRAutoBlogger_Run_State::mark_status( $run_id, 'failed' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the metrics collection cron event.
|
||||
* Side effects: API calls to GA4, database writes to ab_content_scores.
|
||||
@@ -209,6 +224,7 @@ class PRAutoBlogger_Executor {
|
||||
),
|
||||
self::STATUS_TTL
|
||||
);
|
||||
$this->mark_current_run_failed();
|
||||
PRAutoBlogger_Generation_Lock::release();
|
||||
}
|
||||
}
|
||||
@@ -286,6 +302,12 @@ class PRAutoBlogger_Executor {
|
||||
|
||||
/** Clean up an orphaned generation run and report error. */
|
||||
private function abort_orphaned_run( string $message ): void {
|
||||
// v0.18.0: the dead run renders as failed/"incomplete" in audit
|
||||
// queries instead of silently lingering as 'running'.
|
||||
$queue = get_option( 'prautoblogger_article_queue' );
|
||||
if ( is_array( $queue ) && ! empty( $queue['run_id'] ) ) {
|
||||
PRAutoBlogger_Run_State::mark_status( (string) $queue['run_id'], 'failed' );
|
||||
}
|
||||
delete_transient( self::STATUS_TRANSIENT );
|
||||
delete_option( 'prautoblogger_article_queue' );
|
||||
PRAutoBlogger_Generation_Lock::release();
|
||||
|
||||
Reference in New Issue
Block a user