v0.3.0 — cost accountability + LLM call audit trail (#7)

Per-call recording of cost and I/O for every LLM probe. New tables prv_call_meta (schema v3) and prv_call_io. New admin pages: Costs (spend drill-down vs $5 cap) and Call Log (paginated + detail drawer). 90-day I/O retention prune via prv_daily_prune cron. P0 security: key never stored; gateway client exposes HTTP status only.

QA APPROVED 2026-06-15 SHA 09dc6db9ce
This commit is contained in:
peptiderepo
2026-06-15 17:07:08 +07:00
committed by GitHub
parent 2f4c1b2c72
commit 2e2c72cac6
37 changed files with 3246 additions and 325 deletions

View File

@@ -0,0 +1,88 @@
<?php
/**
* Database schema for the per-call I/O table (v0.3.0) — prunable.
*
* @package PrVision
*/
declare(strict_types=1);
/**
* Manages the prv_call_io table: rendered prompt + raw response, PRUNED.
*
* Only the body-level content is stored here — never Authorization headers,
* api keys, or raw HTTP request objects. The prune cron deletes rows older
* than the retention window; the companion prv_call_meta table is kept.
*
* Who triggers: PRV_Upgrader::run() on every plugins_loaded (idempotent).
* Dependencies: $wpdb, dbDelta(), PRV_Call_Meta_Table (FK source).
*
* @see class-prv-call-meta-table.php — FK source: prv_call_meta.id.
* @see class-prv-capture-writer.php — Writes rows to this table.
* @see class-prv-prune-cron.php — Deletes aged rows from this table only.
* @see ARCHITECTURE.md — §Storage v0.3.0.
* @package PrVision
*/
class PRV_Call_Io_Table {
/**
* Table base name (without $wpdb->prefix).
*/
const TABLE_BASE = 'prv_call_io';
/**
* Create or upgrade the call-io table via dbDelta.
*
* Safe to call on every plugins_loaded — dbDelta is idempotent.
* Side effects: Database write.
*
* @return void
*/
public static function create_table(): void {
global $wpdb;
$table = $wpdb->prefix . self::TABLE_BASE;
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
call_id BIGINT(20) UNSIGNED NOT NULL,
prompt_text LONGTEXT NOT NULL DEFAULT '',
response_text LONGTEXT NOT NULL DEFAULT '',
captured_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY call_id (call_id),
KEY captured_at (captured_at)
) {$charset_collate};";
if ( ! function_exists( 'dbDelta' ) ) {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
}
dbDelta( $sql );
}
/**
* 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 I/O records.
*
* @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}" );
}
}