Files
pr-vision/includes/core/class-prv-call-meta-table.php
peptiderepo 09dc6db9ce P2 pre-merge fixes: io_captured column, reconcile copy, harden security test
Fix 1: Add io_captured TINYINT(1) NOT NULL DEFAULT 0 to prv_call_meta
CREATE (baked into initial schema — table is new in v0.3.0, no separate
migration needed). PRV_Capture_Writer::write_io() sets io_captured=1 via
set_io_captured() on success; error rows and legacy rows keep 0.
Drawer state now driven by io_captured instead of config_version heuristic:
  0         → Legacy/not-captured
  1 + io    → Normal
  1 + no io → Pruned
ARCHITECTURE.md schema section updated.

Fix 2: Soften reconciliation copy in PRV_Costs_Renderer (~line 75):
'matches the Dashboard MTD total' → 'approximately reconciles to the
Dashboard MTD total' — error calls write prv_call_meta (cost 0) but not
prv_ai_visibility, so exact equality is not guaranteed.

Fix 3: Harden security test (test-capture-writer Test 4): inject sentinel
sk-or-TEST-LEAK-CANARY as PRV_OPENROUTER_API_KEY constant; queue a mocked
401 wp_remote_post response; drive PRV_Probe_Runner → PRV_Probe_Run_Executor
→ probe_one() 401 error path end-to-end; assert the sentinel, Bearer, and
Authorization appear in NO stored row (prv_call_meta or prv_call_io).
Original direct-writer security test retained as Test 3.
2026-06-15 09:56:11 +00:00

113 lines
3.6 KiB
PHP

<?php
/**
* Database schema for per-call cost/metadata table (v0.3.0).
*
* @package PrVision
*/
declare(strict_types=1);
/**
* Manages the prv_call_meta table: per-call cost + metadata kept indefinitely.
*
* Two tables separate concerns introduced in v0.3.0:
* prv_call_meta — cost + metadata KEPT (this class).
* prv_call_io — rendered prompt + raw response PRUNED (PRV_Call_Io_Table).
*
* Schema is forward-only: dbDelta adds columns; no destructive ALTER.
* SCHEMA_VERSION bumped to 3 to trigger the migration guard in PRV_Upgrader.
*
* Who triggers: PRV_Upgrader::run() on every plugins_loaded (idempotent).
* Dependencies: $wpdb, dbDelta(), PRV_Table_Manager (existing table retained).
*
* @see class-prv-call-io-table.php — Companion prunable I/O table.
* @see class-prv-upgrader.php — Calls create_table() on upgrade.
* @see class-prv-capture-writer.php — Writes rows to this table.
* @see ARCHITECTURE.md — §Storage v0.3.0.
* @package PrVision
*/
class PRV_Call_Meta_Table {
/**
* Table base name (without $wpdb->prefix).
*/
const TABLE_BASE = 'prv_call_meta';
/**
* Schema version introduced by this table.
*/
const SCHEMA_VERSION = 3;
/**
* Create or upgrade the call-meta table via dbDelta.
*
* Safe to call on every plugins_loaded — dbDelta is idempotent.
* Side effects: Database write; may update prv_schema_version option.
*
* @return void
*/
public static function create_table(): void {
global $wpdb;
$table = $wpdb->prefix . self::TABLE_BASE;
$vis_table = $wpdb->prefix . 'prv_ai_visibility';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
visibility_row BIGINT(20) UNSIGNED NULL DEFAULT NULL,
run_id VARCHAR(36) NOT NULL DEFAULT '',
peptide_slug VARCHAR(200) NOT NULL DEFAULT '',
model VARCHAR(200) NOT NULL DEFAULT '',
intent_label VARCHAR(200) NOT NULL DEFAULT '',
tokens_in INT(11) NULL DEFAULT NULL,
tokens_out INT(11) NULL DEFAULT NULL,
cost_usd DECIMAL(12,8) NOT NULL DEFAULT 0.00000000,
latency_ms INT(11) NULL DEFAULT NULL,
cited TINYINT(1) NULL DEFAULT NULL,
http_status INT(11) NOT NULL DEFAULT 200,
captured_at DATETIME NOT NULL,
config_version INT(11) NULL DEFAULT NULL,
io_captured TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
KEY visibility_row (visibility_row),
KEY run_id (run_id),
KEY peptide_slug (peptide_slug),
KEY model (model),
KEY captured_at (captured_at)
) {$charset_collate};";
if ( ! function_exists( 'dbDelta' ) ) {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
}
dbDelta( $sql );
update_option( 'prv_schema_version', self::SCHEMA_VERSION );
}
/**
* Get the full table name including WP prefix.
*
* @return string
*/
public static function get_table_name(): string {
global $wpdb;
return $wpdb->prefix . self::TABLE_BASE;
}
/**
* Drop the table. Called from uninstall.php only.
*
* Side effects: Permanently destroys all call metadata.
*
* @return void
*/
public static function drop_table(): void {
global $wpdb;
$table = $wpdb->prefix . self::TABLE_BASE;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
}
}