fix(maintenance): v0.18.2 — four confirmed-live fixes
1. Cost-reporter SQL: phpcs:ignore annotations were embedded inside two SQL string literals (get_daily_spend, get_spend_by_stage), causing MariaDB to reject both queries and break the Metrics & Costs admin page. Moved annotations above the prepare() calls. Regression tests added to CostReporterTest. 2. Collector failure observability: catch block in collect_from_all_sources() now logs structured meta (model slug, HTTP status parsed from exception message, first 200 chars of error body) so outage incidents are diagnosable post-hoc. 3. run_stages.agent_role back-write: Pipeline_Runner, Article_Worker, and Content_Generator were calling Run_Stage_State::start() with agent_role=''. All start() call sites now pass Stage_Display_Map::default_agent_role(stage). Role written at INSERT only; ON DUPLICATE KEY UPDATE does not include agent_role — idempotency-key semantics on resume are unchanged. 4. Runware float deprecation: normalize_seed() applied % to a float (microtime(true) * 1000), triggering PHP 8 deprecation on every image generation. Fixed with (int) round(...) before the modulo operator. Refs: convo/prautoblogger/threads/2026-06-pipeline-v2-phase1/25-cto-v0182-maintenance.md Agent-Session: prab-v0182-maintenance-20260612
This commit is contained in:
@@ -103,7 +103,7 @@ class PRAutoBlogger_Article_Worker {
|
||||
$review = $this->review_with_state( $editor, $content, $idea, $run_id, $item );
|
||||
|
||||
PRAutoBlogger_Pipeline_Status::broadcast( __( 'Saving and publishing…', 'prautoblogger' ) );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'publish', '', $item );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'publish', (string) PRAutoBlogger_Stage_Display_Map::default_agent_role( 'publish' ), $item );
|
||||
$this->publish_or_draft(
|
||||
$content,
|
||||
$idea,
|
||||
@@ -174,7 +174,7 @@ class PRAutoBlogger_Article_Worker {
|
||||
return new PRAutoBlogger_Editorial_Review( $data );
|
||||
}
|
||||
}
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'review', '', $item );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'review', (string) PRAutoBlogger_Stage_Display_Map::default_agent_role( 'review' ), $item );
|
||||
}
|
||||
|
||||
$review = $editor->review( $content, $idea );
|
||||
|
||||
@@ -194,7 +194,7 @@ class PRAutoBlogger_Content_Generator {
|
||||
);
|
||||
return $cached;
|
||||
}
|
||||
PRAutoBlogger_Run_Stage_State::start( $this->run_id, $stage, '', $this->item_key );
|
||||
PRAutoBlogger_Run_Stage_State::start( $this->run_id, $stage, (string) PRAutoBlogger_Stage_Display_Map::default_agent_role( $stage ), $this->item_key );
|
||||
}
|
||||
|
||||
// v0.18.1: stage metadata for the provider's empty-completion
|
||||
|
||||
@@ -65,8 +65,9 @@ class PRAutoBlogger_Cost_Reporter {
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$results = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->prepare(
|
||||
"SELECT DATE(created_at) as day, SUM(estimated_cost) as total_cost // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT DATE(created_at) as day, SUM(estimated_cost) as total_cost
|
||||
FROM {$table}
|
||||
WHERE created_at >= %s AND response_status = 'success'
|
||||
GROUP BY DATE(created_at)
|
||||
@@ -102,11 +103,12 @@ class PRAutoBlogger_Cost_Reporter {
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$results = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->prepare(
|
||||
"SELECT stage,
|
||||
SUM(estimated_cost) as total_cost,
|
||||
SUM(prompt_tokens + completion_tokens) as total_tokens,
|
||||
COUNT(*) as call_count // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
COUNT(*) as call_count
|
||||
FROM {$table}
|
||||
WHERE created_at BETWEEN %s AND %s AND response_status = 'success'
|
||||
GROUP BY stage",
|
||||
|
||||
@@ -185,14 +185,14 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
$source_labels = is_array( $enabled ) ? implode( ', ', $enabled ) : 'reddit';
|
||||
/* translators: %s is a comma-separated list of enabled source names, e.g. "reddit, llm_research". */
|
||||
PRAutoBlogger_Pipeline_Status::broadcast( sprintf( __( 'Collecting sources from %s…', 'prautoblogger' ), $source_labels ) );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'research' );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'research', (string) PRAutoBlogger_Stage_Display_Map::default_agent_role( 'research' ) );
|
||||
( new PRAutoBlogger_Source_Collector() )
|
||||
->set_cost_tracker( $cost_tracker )
|
||||
->collect_from_all_sources();
|
||||
PRAutoBlogger_Run_Stage_State::done( $run_id, 'research' );
|
||||
|
||||
PRAutoBlogger_Pipeline_Status::broadcast( __( 'Analyzing topics and scoring…', 'prautoblogger' ) );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'analysis' );
|
||||
PRAutoBlogger_Run_Stage_State::start( $run_id, 'analysis', (string) PRAutoBlogger_Stage_Display_Map::default_agent_role( 'analysis' ) );
|
||||
$llm = new PRAutoBlogger_OpenRouter_Provider();
|
||||
$analyzer = new PRAutoBlogger_Content_Analyzer( $llm, $cost_tracker );
|
||||
$analysis = $analyzer->analyze_recent_data( max( $target * 2, 6 ) );
|
||||
|
||||
@@ -76,8 +76,33 @@ class PRAutoBlogger_Source_Collector {
|
||||
'collector'
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
PRAutoBlogger_Logger::instance()->error( sprintf( 'Collection %s from %s: %s', get_class( $e ), $source_type, $e->getMessage() ), 'collector' );
|
||||
// Continue with other sources — one failure shouldn't stop everything.
|
||||
// Structured failure meta: model slug, HTTP status (parsed from message),
|
||||
// and first ~200 chars of the error body — enables post-incident diagnosis
|
||||
// without needing to re-run the failing collection. Added v0.18.2.
|
||||
$config_for_log = $this->get_config_for_source( $source_type );
|
||||
$model_slug = (string) ( $config_for_log['model'] ?? '' );
|
||||
$err_msg = $e->getMessage();
|
||||
// Extract HTTP status from "OpenRouter API error (HTTP NNN): ..." pattern.
|
||||
$http_status = 0;
|
||||
if ( preg_match( '/HTTP (\d{3})/', $err_msg, $m ) ) {
|
||||
$http_status = (int) $m[1];
|
||||
}
|
||||
PRAutoBlogger_Logger::instance()->error(
|
||||
sprintf(
|
||||
'Collection %s from %s: %s',
|
||||
get_class( $e ),
|
||||
$source_type,
|
||||
$e->getMessage()
|
||||
),
|
||||
'collector',
|
||||
array(
|
||||
'source' => $source_type,
|
||||
'model' => $model_slug,
|
||||
'http_status' => $http_status,
|
||||
'error_body' => mb_substr( $err_msg, 0, 200 ),
|
||||
)
|
||||
);
|
||||
// Continue with other sources — one failure should not stop everything.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,8 @@ class PRAutoBlogger_Runware_Image_Support {
|
||||
*/
|
||||
public function normalize_seed( ?int $seed ): int {
|
||||
if ( null === $seed || $seed < 1 ) {
|
||||
$derived = (int) ( ( microtime( true ) * 1000 ) % 2147483647 );
|
||||
// Cast to int before modulo — float % int is deprecated in PHP 8 (v0.18.2).
|
||||
$derived = (int) round( microtime( true ) * 1000 ) % 2147483647;
|
||||
return $derived > 0 ? $derived : 1;
|
||||
}
|
||||
return $seed;
|
||||
|
||||
Reference in New Issue
Block a user