## Board 'New Article' button
- templates/admin/board-page.php: add #prab-board-generate-now button + status div
- includes/admin/class-board-page.php: inject generateNonce + i18n strings into prabBoard;
enqueue companion board-generate.js
- assets/js/board-generate.js: NEW – board button AJAX handler (fires
prautoblogger_generate_now, resets board backoff on success)
- assets/js/board.js: applySnapshot() re-enables button when no active runs
## Chained-cron checkpoint machine (retires SSH workaround)
- includes/core/class-generation-checkpoint-runner.php: NEW (295 lines)
Tick 1 (prautoblogger_gen_orchestrate): orchestrate_only() → persist queue → schedule Tick 2
Tick 2..N (prautoblogger_gen_tick): pop one idea → Article_Worker → reschedule or finalize
Budget/halt contract + lock release on all exit paths
- includes/class-executor.php: on_manual_generation() replaced with 1-line delegate to
Generation_Checkpoint_Runner::on_orchestrate_tick() (SSH workaround retired)
- includes/class-prautoblogger.php: register ORCHESTRATE_ACTION + GENERATE_ACTION hooks
- includes/core/class-pipeline-runner.php: expose orchestrate_only() public wrapper
- includes/core/class-pipeline-status.php: add write_initial() + write_error() helpers
## WP-CLI command
- includes/admin/class-wp-cli-commands.php: wp prautoblogger generate → kick_off()
## IA / perf pass (settings-backed pagination + DB indexes)
- includes/admin/class-board-data-provider.php: replace hardcoded posts_per_page=20 with
get_option('prautoblogger_board_column_limit', DEFAULT)
- includes/admin/class-board-gen-log-query.php: replace LIMIT 10/20 literals with settings-backed values
- includes/admin/class-ideas-browser-query.php: NEW – query helpers extracted from Ideas_Browser
(300-line rule split): query_ideas(), get_type_counts(), load_idea_data(),
find_post_by_run_id(), update_idea_stage()
- includes/admin/class-ideas-browser.php: delegates all query work to Ideas_Browser_Query
- includes/class-activator.php: set_default_options() adds board_column_limit + ideas_per_page
- includes/admin/class-admin-page.php: add new settings to numeric sanitization list
- prautoblogger.php: PRAUTOBLOGGER_DEFAULT_BOARD_COLUMN_LIMIT=20, PRAUTOBLOGGER_DEFAULT_IDEAS_PER_PAGE=30
- includes/class-schema-installer.php: indexes on generation_log + analysis_results (db 1.4.0)
- includes/class-pipeline-schema-installer.php: KEY started_at on runs table
## Guard P2s
- scripts/check-constants.php: --self-test mode + inline CONSTANTS_GUARD_WINDOW comment
- .github/workflows/ci.yml: check-constants-self-test step after main constants check
## PHPUnit test
- tests/unit/Core/GenerationCheckpointRunnerTest.php: NEW (227 lines)
4 tests: kick_off schedules+transient, empty-queue finalizes, halted-run aborts+cleanup,
multi-idea pops-one-and-reschedules
## Docs + version
- prautoblogger.php: Version 0.21.0, PRAUTOBLOGGER_DB_VERSION 1.4.0
- ARCHITECTURE.md: §25 Chained-Cron Checkpoint Generation Path
- CHANGELOG.md: v0.21.0 entry
- INFORMATION-ARCHITECTURE.md: NEW – admin pages, entry points, DB tables, options, CLI
- uninstall.php: purge prautoblogger_gen_orchestrate + prautoblogger_gen_tick cron hooks
Agent-Session: phase2-admin-m4-20260614
181 lines
7.4 KiB
PHP
181 lines
7.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- class naming convention differs from WordPress standard
|
|
*
|
|
* Creates / updates the Pipeline v2 substrate tables via `dbDelta` (db 1.3.0).
|
|
*
|
|
* What: Owns the six pipeline substrate tables — the versioned prompt
|
|
* registry (`prompts`), the audit child tables (`run_sources`,
|
|
* `run_decisions`), and the run ledger / state machine tables
|
|
* (`runs`, `run_stages`), and the immutable stage-input version
|
|
* store (`stage_inputs`, v0.20.0 / db 1.3.0 — edit + re-run forks
|
|
* and idea seeds). Kept separate from the original
|
|
* PRAutoBlogger_Schema_Installer so both classes stay under the
|
|
* 300-line cap and the v1.1.0 schema remains byte-stable.
|
|
* Who triggers it: PRAutoBlogger_Activator::activate() — on activation and
|
|
* on every `prautoblogger_db_version` mismatch (self-healing
|
|
* auto-migration via PRAutoBlogger::on_check_db_version()).
|
|
* Dependencies: WordPress $wpdb, dbDelta() from wp-admin/includes/upgrade.php.
|
|
*
|
|
* @see class-schema-installer.php — v1.1.0 tables (source data, logs, scores).
|
|
* @see class-activator.php — Sole caller.
|
|
* @see core/class-prompt-registry.php — Reads/writes the prompts table.
|
|
* @see core/class-run-state.php — Reads/writes runs + run_stages.
|
|
* @see ARCHITECTURE.md — Database schema section documents each table.
|
|
*/
|
|
class PRAutoBlogger_Pipeline_Schema_Installer {
|
|
|
|
/**
|
|
* Create (or upgrade) the Pipeline v2 substrate tables.
|
|
*
|
|
* `dbDelta` is idempotent: it creates missing tables, adds missing
|
|
* columns and indexes, and never drops anything — matching the
|
|
* plugin's forward-only schema policy. Safe to re-run on every
|
|
* version mismatch.
|
|
*
|
|
* Side effects: up to six CREATE TABLE / ALTER TABLE statements.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function install(): void {
|
|
global $wpdb;
|
|
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
$prefix = $wpdb->prefix . 'prautoblogger_';
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
|
|
|
// Versioned prompt registry. Versions are immutable: writes create
|
|
// new (prompt_key, version) rows; exactly one row per key has
|
|
// active = 1. `prompt_key` (not `key`) because KEY is reserved SQL.
|
|
$sql_prompts = "CREATE TABLE {$prefix}prompts (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
prompt_key VARCHAR(64) NOT NULL,
|
|
version INT UNSIGNED NOT NULL DEFAULT 1,
|
|
body LONGTEXT NOT NULL,
|
|
model VARCHAR(100) DEFAULT NULL,
|
|
params_json LONGTEXT DEFAULT NULL,
|
|
author VARCHAR(100) NOT NULL DEFAULT '',
|
|
created_at DATETIME NOT NULL,
|
|
active TINYINT(1) NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY key_version (prompt_key, version),
|
|
KEY key_active (prompt_key, active)
|
|
) {$charset_collate};";
|
|
|
|
// Research-source audit rows: one row per source a run considered,
|
|
// with the keep/discard decision. Consolidates the old
|
|
// source_ids_json + _prautoblogger_research_sources scatter for
|
|
// new runs (no backfill of historical runs).
|
|
$sql_run_sources = "CREATE TABLE {$prefix}run_sources (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
run_id VARCHAR(36) NOT NULL,
|
|
agent_role VARCHAR(50) NOT NULL DEFAULT '',
|
|
source_url VARCHAR(500) DEFAULT NULL,
|
|
doi VARCHAR(255) DEFAULT NULL,
|
|
kept TINYINT(1) NOT NULL DEFAULT 0,
|
|
reason TEXT DEFAULT NULL,
|
|
quality_score FLOAT DEFAULT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY run_id (run_id)
|
|
) {$charset_collate};";
|
|
|
|
// Stage-decision audit rows: verdict + rationale per pipeline
|
|
// stage. citation_score is nullable until the Phase-2 editorial
|
|
// loop computes it.
|
|
$sql_run_decisions = "CREATE TABLE {$prefix}run_decisions (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
run_id VARCHAR(36) NOT NULL,
|
|
stage VARCHAR(50) NOT NULL,
|
|
verdict VARCHAR(50) NOT NULL,
|
|
rationale TEXT DEFAULT NULL,
|
|
citation_score FLOAT DEFAULT NULL,
|
|
human_modified TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY run_id (run_id)
|
|
) {$charset_collate};";
|
|
|
|
// Per-run ledger + lifecycle row. reserved/settled/ceiling drive the
|
|
// Cost_Governor's atomic reserve-before-call check; pinned_prompts_json
|
|
// freezes the prompt versions a run uses at start; status feeds the
|
|
// run state machine (pending|running|done|failed|halted).
|
|
$sql_runs = "CREATE TABLE {$prefix}runs (
|
|
run_id VARCHAR(36) NOT NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
ceiling_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
|
|
reserved_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
|
|
settled_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
|
|
overage_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
|
|
pinned_prompts_json LONGTEXT DEFAULT NULL,
|
|
started_at DATETIME NOT NULL,
|
|
updated_at DATETIME NOT NULL,
|
|
finished_at DATETIME DEFAULT NULL,
|
|
PRIMARY KEY (run_id),
|
|
KEY status (status),
|
|
KEY started_at (started_at)
|
|
) {$charset_collate};";
|
|
|
|
// Per-run per-stage state rows. The idempotency key is
|
|
// (run_id, stage, agent_role, item_key): agent_role is the Phase-2
|
|
// fan-out dimension (quorum members), item_key scopes article-level
|
|
// stages within multi-article runs ('' for run-level stages).
|
|
// meta_json holds the stage output for resume-without-recharge.
|
|
$sql_run_stages = "CREATE TABLE {$prefix}run_stages (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
run_id VARCHAR(36) NOT NULL,
|
|
stage VARCHAR(50) NOT NULL,
|
|
agent_role VARCHAR(50) NOT NULL DEFAULT '',
|
|
item_key VARCHAR(64) NOT NULL DEFAULT '',
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
attempt SMALLINT UNSIGNED NOT NULL DEFAULT 1,
|
|
cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
|
|
meta_json LONGTEXT DEFAULT NULL,
|
|
human_modified TINYINT(1) NOT NULL DEFAULT 0,
|
|
stale TINYINT(1) NOT NULL DEFAULT 0,
|
|
started_at DATETIME NOT NULL,
|
|
updated_at DATETIME NOT NULL,
|
|
finished_at DATETIME DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY run_stage_role_item (run_id, stage, agent_role, item_key),
|
|
KEY status_updated (status, updated_at)
|
|
) {$charset_collate};";
|
|
|
|
// Immutable stage-input version store (v0.20.0 / db 1.3.0, M3
|
|
// edit + re-run). Rows are INSERT-only: a human edit creates the
|
|
// next (scope, version) row -- the original input (the
|
|
// generation_log.request_json of the executed call) is never
|
|
// overwritten (CPO guardrail 1). source='seed' rows persist the
|
|
// Article_Idea payload at worker start so re-run-from-here can
|
|
// reconstruct the exact idea (item_key hash stability); 'human'
|
|
// rows are edit forks. Human fork bodies are retention-pruned
|
|
// like all heavy payloads; seed rows (~1KB) are structural and
|
|
// kept for the life of the run.
|
|
$sql_stage_inputs = "CREATE TABLE {$prefix}stage_inputs (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
run_id VARCHAR(36) NOT NULL,
|
|
stage VARCHAR(50) NOT NULL DEFAULT '',
|
|
agent_role VARCHAR(50) NOT NULL DEFAULT '',
|
|
item_key VARCHAR(64) NOT NULL DEFAULT '',
|
|
version INT UNSIGNED NOT NULL DEFAULT 1,
|
|
source VARCHAR(20) NOT NULL DEFAULT 'human',
|
|
request_json LONGTEXT DEFAULT NULL,
|
|
author VARCHAR(100) NOT NULL DEFAULT '',
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY scope_version (run_id, stage, agent_role, item_key, version),
|
|
KEY run_id (run_id)
|
|
) {$charset_collate};";
|
|
|
|
dbDelta( $sql_prompts );
|
|
dbDelta( $sql_run_sources );
|
|
dbDelta( $sql_run_decisions );
|
|
dbDelta( $sql_runs );
|
|
dbDelta( $sql_run_stages );
|
|
dbDelta( $sql_stage_inputs );
|
|
}
|
|
}
|