Technical Deep Dive: Architecture for Developers

Component-Based Architecture

VoxDiff uses a component model where every feature is a standalone module with declared dependencies. Components are initialized in strict order, ensuring lower-level components (database, encryption) are available before higher-level ones (comparison, tier management) depend on them.

Initialization Layers

Layer 1: Foundation     → Encryption, Utils
Layer 2: Database       → DB, DB_Tables
Layer 3: Core Features  → Settings, Cache, Tier_Manager, Email_Verify, Stripe, Rate_Limiter
Layer 4: LLM            → LLM_Manager, Prompts
Layer 5: Comparison     → Comparison (core feature)
Layer 6: Application    → Persona, Chat
Layer 7: UI             → Shortcodes, Assets, AJAX

Each layer depends on layers below it. This guarantees:

  • Database is available before tier manager tries to query user_tiers
  • Encryption is available before storage components encrypt data
  • LLM_Manager is available before judge LLM runs

Database Schema: Multi-Tenant Isolation

All custom tables use the voxdiff_ prefix and include user_id (or session_id for anonymous) as a foreign key.

Comparison Tables

CREATE TABLE wp_voxdiff_comparisons (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT UNSIGNED NOT NULL,
    share_uuid VARCHAR(36) UNIQUE,
    global_system_prompt LONGTEXT,
    temperature DECIMAL(3,2),
    max_tokens SMALLINT UNSIGNED,
    top_p DECIMAL(3,2),
    turn_count TINYINT UNSIGNED,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE,
    KEY (user_id),
    UNIQUE KEY (share_uuid)
);

CREATE TABLE wp_voxdiff_comparison_lanes (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    comparison_id BIGINT UNSIGNED NOT NULL,
    lane_index TINYINT UNSIGNED,
    provider VARCHAR(32),
    model VARCHAR(64),
    label VARCHAR(128),
    FOREIGN KEY (comparison_id) REFERENCES wp_voxdiff_comparisons(id) ON DELETE CASCADE,
    KEY (comparison_id)
);

CREATE TABLE wp_voxdiff_comparison_turns (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    lane_id BIGINT UNSIGNED NOT NULL,
    turn_number TINYINT UNSIGNED,
    role VARCHAR(16),
    content LONGTEXT,
    input_tokens INT UNSIGNED,
    output_tokens INT UNSIGNED,
    latency_ms INT UNSIGNED,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (lane_id) REFERENCES wp_voxdiff_comparison_lanes(id) ON DELETE CASCADE,
    KEY (lane_id, turn_number)
);

CREATE TABLE wp_voxdiff_comparison_analysis (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    comparison_id BIGINT UNSIGNED NOT NULL,
    turn_number TINYINT UNSIGNED,
    analysis_json LONGTEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (comparison_id) REFERENCES wp_voxdiff_comparisons(id) ON DELETE CASCADE,
    KEY (comparison_id, turn_number)
);

Multi-tenancy via foreign keys:

  • A user can only access comparisons where user_id = their_id
  • Each comparison has lanes; each lane has turns
  • No query can leak another user’s data (FK constraints + authorization checks)

Tier & Token Tables

CREATE TABLE wp_voxdiff_user_tiers (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT UNSIGNED NOT NULL UNIQUE,
    tier ENUM('red_cup','open_bar','cash_bar','run_a_tab') DEFAULT 'open_bar',
    token_balance BIGINT UNSIGNED DEFAULT 0,
    tokens_allotted BIGINT UNSIGNED DEFAULT 1000000,
    period_start DATETIME,
    period_end DATETIME,
    stripe_customer_id VARCHAR(64),
    stripe_subscription_id VARCHAR(64),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE,
    KEY (user_id),
    KEY (tier),
    KEY (stripe_customer),
    KEY (stripe_subscription)
);

CREATE TABLE wp_voxdiff_anonymous_tiers (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    session_id VARCHAR(64) NOT NULL UNIQUE,
    ip_address VARCHAR(45) NOT NULL,
    token_balance BIGINT UNSIGNED DEFAULT 1000000,
    tokens_consumed BIGINT UNSIGNED DEFAULT 0,
    first_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
    last_active DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    KEY (session_id),
    KEY (ip_address),
    KEY (last_active)
);

CREATE TABLE wp_voxdiff_token_ledger (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT UNSIGNED SIGNED,
    session_id VARCHAR(64),
    event_type ENUM('grant','debit','refund','admin_adjustment','expiry'),
    tier ENUM('red_cup','open_bar','cash_bar','run_a_tab'),
    tokens_delta BIGINT SIGNED,
    balance_after BIGINT UNSIGNED,
    reference_type VARCHAR(50),
    reference_id VARCHAR(64),
    note TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    KEY (user_id),
    KEY (session_id),
    KEY (event_type),
    KEY (created_at),
    KEY (reference_type, reference_id)
);

CREATE TABLE wp_voxdiff_admin_overrides (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT UNSIGNED NOT NULL,
    override_tier ENUM('red_cup','open_bar','cash_bar','run_a_tab'),
    bonus_tokens BIGINT UNSIGNED DEFAULT 0,
    exempt_rate_limits TINYINT(1) DEFAULT 0,
    note TEXT,
    set_by BIGINT UNSIGNED NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE,
    FOREIGN KEY (set_by) REFERENCES wp_users(ID) ON DELETE CASCADE,
    KEY (user_id)
);

Tier Resolution Logic

When a request arrives, the server determines the user’s effective tier in this order:

public function resolve_tier(?int $user_id, ?string $session_id): string {
    // Step 1: Anonymous user?
    if (!$user_id) {
        return 'red_cup'; // Session-scoped, no account
    }

    // Step 2: Admin override?
    $override = $this->get_admin_override($user_id);
    if ($override && $override->override_tier) {
        return $override->override_tier;
    }

    // Step 3: Email verified?
    $is_verified = get_user_meta($user_id, 'voxdiff_email_verified', true);
    if (!$is_verified) {
        return 'red_cup'; // Registered but unverified
    }

    // Step 4: Check tier_manager row (may have subscription or cash bar)
    $tier_row = $this->get_user_tier($user_id);
    if (!$tier_row) {
        return 'open_bar'; // Default for verified users with no other tier
    }

    // Step 5: Subscription active?
    if ($tier_row->tier === 'run_a_tab' && $this->is_subscription_active($tier_row)) {
        return 'run_a_tab';
    }

    // Step 6: Cash bar tokens remaining?
    if ($tier_row->tier === 'cash_bar' && $tier_row->token_balance > 0) {
        return 'cash_bar';
    }

    // Step 7: Default to open_bar if verified
    return 'open_bar';
}

The result is the user’s effective tier, which gates feature access.


Comparison Creation Gate Sequence

When voxdiff_comparison_create AJAX is called:

public function handle_comparison_create() {
    // 1. Verify nonce
    if (!wp_verify_nonce($_POST['comparison_nonce'], 'voxdiff_comparison')) {
        wp_send_json_error(['code' => 'invalid_nonce']);
    }

    // 2. Resolve identity
    $user_id = get_current_user_id();
    $session_id = $user_id ? null : $this->get_or_create_session_id();

    // 3. Resolve tier
    $tier = $this->tier_manager->resolve_tier($user_id, $session_id);

    // 4. Check rate limit
    if (!$this->rate_limiter->check_rate_limit($tier, 'comparison')) {
        wp_send_json_error(['code' => 'rate_limit_exceeded'], 429);
    }

    // 5. Validate lane count
    $lane_count = count($_POST['lanes']);
    $max_lanes = $this->tier_manager->max_lanes($tier);
    if ($lane_count > $max_lanes) {
        wp_send_json_error(['code' => 'lane_limit_exceeded', 'max_lanes' => $max_lanes]);
    }

    // 6. Validate max_tokens (tier ceiling)
    $max_tokens = (int)$_POST['max_tokens'];
    $tier_ceiling = $this->tier_manager->max_tokens_ceiling($tier);
    $max_tokens = min($max_tokens, $tier_ceiling);

    // 7. Validate model access (premium models)
    foreach ($_POST['lanes'] as $lane) {
        if ($this->is_premium_model($lane['model'])) {
            if (!$this->tier_manager->can_use_premium($tier)) {
                wp_send_json_error(['code' => 'premium_model_restricted']);
            }
        }
    }

    // 8. Validate persona min_tier requirements
    foreach ($_POST['lanes'] as $lane) {
        if (!empty($lane['persona_id'])) {
            $persona = $this->persona->get($lane['persona_id']);
            if (!$this->tier_manager->tier_meets($tier, $persona->min_tier)) {
                wp_send_json_error(['code' => 'persona_tier_restricted']);
            }
        }
    }

    // 9. Estimate tokens & check balance
    $estimate = $this->tier_manager->estimate_request_tokens(
        $_POST['prompt'],
        $lane_count,
        $max_tokens,
        $_POST['system_prompt'] ?? ''
    );
    $balance = $this->tier_manager->get_balance($user_id, $session_id);
    if ($balance < $estimate) {
        wp_send_json_error(['code' => 'insufficient_tokens', 'balance' => $balance], 402);
    }

    // 10. Create session in DB
    $comparison_id = $this->comparison->create_session(
        $user_id,
        $_POST['lanes'],
        ['temperature' => $_POST['temperature'], 'max_tokens' => $max_tokens]
    );

    // 11. Generate stream tokens (one per lane, one-time use)
    $stream_tokens = [];
    foreach ($lanes as $lane) {
        $token = $this->generate_stream_token($comparison_id, $lane['id']);
        $stream_tokens[$lane['id']] = $token;
    }

    // 12. Return success
    wp_send_json_success([
        'comparison_id' => $comparison_id,
        'lanes' => $lanes,
        'stream_tokens' => $stream_tokens
    ]);
}

Each step must pass before the next one runs. If any check fails, an error is returned immediately.


Streaming Architecture: SSE (Server-Sent Events)

Stream Endpoint

includes/stream-comparison.php is a lightweight PHP file that:

  1. Boots WordPress minimally (require wp-load.php)
  2. Validates the stream token
  3. Calls the LLM provider
  4. Emits SSE events
<?php
// Boot WordPress
define('SHORTINIT', false);
require_once dirname(__DIR__, 4) . '/wp-load.php';

// Validate stream token
$token = $_GET['stream_token'] ?? '';
$comparison_id = (int)$_GET['comparison_id'] ?? 0;
$lane_id = (int)$_GET['lane_id'] ?? 0;

// Recompute HMAC and check transient
if (!$this->validate_stream_token($token, $comparison_id, $lane_id)) {
    header('HTTP/1.1 403 Forbidden');
    exit;
}

// Delete transient (one-time use)
delete_transient("voxdiff_stream_{$comparison_id}_{$lane_id}");

// Fetch lane configuration
$lane = $this->db->query("SELECT * FROM voxdiff_comparison_lanes WHERE id = %d", $lane_id);
$comparison = $this->db->query("SELECT * FROM voxdiff_comparisons WHERE id = %d", $comparison_id);

// Fetch conversation history for this lane
$history = $this->comparison->get_lane_history($lane_id);

// Prepare system prompt
$system_prompt = $comparison->global_system_prompt;

// Set SSE headers
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // Disable nginx buffering

// Call LLM with streaming callback
$usage = $this->llm_manager->call_provider_streaming(
    $lane->provider,
    $lane->model,
    $history,
    [
        'temperature' => $comparison->temperature,
        'max_tokens' => $comparison->max_tokens,
        'system' => $system_prompt
    ],
    function($chunk) {
        // Emit chunk as SSE event
        echo "data: " . json_encode(['chunk' => $chunk]) . "nn";
        flush();
    }
);

// Emit final event with token counts
echo "data: " . json_encode([
    'done' => true,
    'input_tokens' => $usage['input_tokens'],
    'output_tokens' => $usage['output_tokens'],
    'latency_ms' => $usage['latency_ms']
]) . "nn";
flush();

Why a separate file?

  • WordPress AJAX doesn’t support SSE natively
  • stream-comparison.php can set SSE headers without WordPress interfering
  • No nonce needed (stream tokens are one-time HMAC tokens)
  • Minimal boot (no plugins, no admin, just WordPress core)

Stream Token Validation

Stream tokens are generated before streaming starts and validated on arrival:

// Generate (in comparison_create handler)
$token_data = "{$comparison_id}:{$lane_id}:{$turn_number}";
$token = hash_hmac('sha256', $token_data, VOXDIFF_STREAM_SECRET);
set_transient("voxdiff_stream_{$comparison_id}_{$lane_id}", $token, 60); // 60-second expiry

// Validate (in stream-comparison.php)
public function validate_stream_token(string $provided_token, int $comparison_id, int $lane_id): bool {
    $token_data = "{$comparison_id}:{$lane_id}:{$turn_number}";
    $expected_token = hash_hmac('sha256', $token_data, VOXDIFF_STREAM_SECRET);

    // Fetch transient (returns false if expired)
    $stored = get_transient("voxdiff_stream_{$comparison_id}_{$lane_id}");

    return hash_equals($stored, $provided_token); // Constant-time comparison
}

Benefits:

  • No nonce parameter (EventSource can’t send POST bodies or custom headers)
  • One-time use (transient deleted after validation)
  • Time-limited (60-second expiry)
  • Can’t be reused for other lanes

Token Adapter Pattern

Each LLM provider has unique SSE chunk formats. The adapter pattern normalizes these:

LLM_Adapter_Interface

interface LLM_Adapter_Interface {
    // Format user messages into provider-specific format
    public function format_messages(array $messages): array;

    // Parse an SSE chunk and extract tokens/text
    public function parse_sse_chunk(string $line): ?array; // Returns {text, input_tokens, output_tokens}

    // Extract usage from final response
    public function extract_usage(array $response): array; // Returns {input_tokens, output_tokens}
}

OpenAI_Adapter

class OpenAI_Adapter implements LLM_Adapter_Interface {
    public function format_messages(array $messages): array {
        // OpenAI: role = 'system' | 'user' | 'assistant'
        return array_map(fn($msg) => [
            'role' => $msg['role'],
            'content' => $msg['content']
        ], $messages);
    }

    public function parse_sse_chunk(string $line): ?array {
        if ($line === 'data: [DONE]') {
            return null; // Stream finished
        }
        $data = json_decode(substr($line, 6), true); // Strip "data: " prefix

        if ($data['choices'][0]['delta']['content'] ?? null) {
            return ['text' => $data['choices'][0]['delta']['content']];
        }

        if ($data['usage'] ?? null) {
            return [
                'input_tokens' => $data['usage']['prompt_tokens'],
                'output_tokens' => $data['usage']['completion_tokens']
            ];
        }

        return null;
    }
}

Anthropic_Adapter

class Anthropic_Adapter implements LLM_Adapter_Interface {
    public function format_messages(array $messages): array {
        // Anthropic: system message separate from messages array
        // Messages are only role='user' | role='assistant'
        return array_filter($messages, fn($msg) => $msg['role'] !== 'system');
    }

    public function parse_sse_chunk(string $line): ?array {
        $data = json_decode(substr($line, 6), true);

        if ($data['type'] === 'content_block_delta') {
            return ['text' => $data['delta']['text'] ?? ''];
        }

        if ($data['type'] === 'message_start') {
            return ['input_tokens' => $data['message']['usage']['input_tokens']];
        }

        if ($data['type'] === 'message_delta') {
            return ['output_tokens' => $data['delta']['usage']['output_tokens']];
        }

        return null;
    }
}

Generic_LLM_Provider

class Generic_LLM_Provider extends Base_LLM_Provider {
    public function call_streaming(
        string $model,
        array $messages,
        array $options,
        callable $on_chunk
    ): array {
        // Get adapter for this provider (Anthropic, OpenAI, Gemini, etc.)
        $adapter = $this->get_adapter();

        // Format messages using adapter
        $formatted_messages = $adapter->format_messages($messages);

        // Prepare payload (including system message if applicable)
        $payload = $this->build_payload($formatted_messages, $options, $adapter);

        // Prepare HTTP client
        $client = new GuzzleHttpClient();
        $response = $client->post($this->api_base_url, [
            'stream' => true,
            'json' => $payload,
            'headers' => $this->build_headers()
        ]);

        // Stream and accumulate tokens
        $body = $response->getBody();
        $input_tokens = 0;
        $output_tokens = 0;

        while (!$body->eof()) {
            $line = $body->read(1024);
            $parsed = $adapter->parse_sse_chunk($line);

            if ($parsed === null) {
                break; // Stream finished
            }

            if (is_string($parsed)) {
                // Text chunk
                ($on_chunk)($parsed);
            } else if (is_array($parsed)) {
                // Token data
                $input_tokens += $parsed['input_tokens'] ?? 0;
                $output_tokens += $parsed['output_tokens'] ?? 0;
            }
        }

        return [
            'content' => $assembled_text,
            'input_tokens' => $input_tokens,
            'output_tokens' => $output_tokens,
            'latency_ms' => $elapsed_ms
        ];
    }
}

Debit Handler: Atomic Token Accounting

After voxdiff_comparison_save_turn receives the final lane results and judge analysis:

public function handle_comparison_save_turn() {
    // ... validation ...

    // Aggregate tokens from all lanes + judge
    $total_input = array_sum(array_column($_POST['lane_results'], 'input_tokens'));
    $total_output = array_sum(array_column($_POST['lane_results'], 'output_tokens'));
    $judge_input = $analysis['judge_input_tokens'] ?? 0;
    $judge_output = $analysis['judge_output_tokens'] ?? 0;

    $total_tokens = $total_input + $total_output + $judge_input + $judge_output;

    // Atomic debit
    $success = $this->tier_manager->debit_tokens(
        $user_id,
        $session_id,
        $total_tokens,
        'comparison_turn',
        "{$comparison_id}_{$turn_number}"
    );

    if (!$success) {
        // Balance went negative (race condition or estimation miss)
        // Turn is already saved, account floors at 0
        voxdiff_log("Token debit failed post-save for user {$user_id}", 'error');
    }

    // Log ledger entry
    $this->tier_manager->log_ledger(
        event_type: 'debit',
        user_id: $user_id,
        session_id: $session_id,
        tokens_delta: -$total_tokens,
        reference_type: 'comparison_turn',
        reference_id: "{$comparison_id}_{$turn_number}"
    );

    // Return success with new balance
    $new_balance = $this->tier_manager->get_balance($user_id, $session_id);
    wp_send_json_success([
        'turn_number' => $turn_number,
        'analysis' => $analysis,
        'tokens_debited' => $total_tokens,
        'token_balance' => $new_balance
    ]);
}

Judge LLM Invocation

After all lanes finish streaming, the judge runs:

public function run_judge(int $comparison_id, int $turn_number, string $user_message, array $lane_results): ?array {
    // Build judge prompt
    $prompt = "You are an impartial judge...nn";
    $prompt .= "USER PROMPT: $user_messagenn";
    $prompt .= "RESPONSES:n";

    foreach ($lane_results as $result) {
        $prompt .= "--- {$result['lane_label']} ({$result['provider']}/{$result['model']}) ---n";
        $prompt .= $result['content'] . "nn";
    }

    // Get judge persona/model from settings
    $judge_config = get_option('voxdiff_judge_config', []);
    $judge_provider = $judge_config['provider'] ?? 'openai';
    $judge_model = $judge_config['model'] ?? 'gpt-4o-mini';

    // Call judge (non-streaming, collect full response)
    $response = $this->llm_manager->call_provider_streaming(
        $judge_provider,
        $judge_model,
        [['role' => 'user', 'content' => $prompt]],
        ['temperature' => 0.0, 'max_tokens' => 400],
        function() {} // Discard chunks; just collect full response
    );

    // Parse judge JSON
    $analysis = json_decode($response['content'], true);
    if (!$analysis) {
        voxdiff_log("Judge response was not valid JSON", 'error');
        return null;
    }

    // Save analysis
    $this->comparison->save_analysis($comparison_id, $turn_number, $analysis);

    // Return analysis with token metadata
    $analysis['_judge_tokens'] = $response['input_tokens'] + $response['output_tokens'];
    return $analysis;
}

Frontend Integration: JavaScript

ComparisonSession Class

class ComparisonSession {
    constructor(config) {
        this.config = config;
        this.comparisonId = null;
        this.lanes = [];
        this.totals = { inputTokens: 0, outputTokens: 0, energyWh: 0, costUsd: 0 };
    }

    async create(laneConfigs, globalSettings) {
        const response = await fetch(this.config.ajaxurl, {
            method: 'POST',
            body: new URLSearchParams({
                action: 'voxdiff_comparison_create',
                comparison_nonce: this.config.nonces.comparison,
                lanes: JSON.stringify(laneConfigs),
                settings: JSON.stringify(globalSettings)
            })
        });

        const data = await response.json();
        this.comparisonId = data.data.comparison_id;
        this.lanes = data.data.lanes;
        return data;
    }

    async sendTurn(userMessage) {
        // Open N EventSources in parallel
        const streams = this.lanes.map(lane =>
            this.streamLane(lane.lane_id, userMessage)
        );

        const results = await Promise.all(streams);
        return results;
    }

    streamLane(laneId, userMessage) {
        return new Promise((resolve, reject) => {
            const url = new URL(this.config.streamUrl);
            url.searchParams.set('comparison_id', this.comparisonId);
            url.searchParams.set('lane_id', laneId);
            url.searchParams.set('turn_number', this.currentTurn);
            url.searchParams.set('stream_token', this.streamTokens[laneId]);

            const es = new EventSource(url.toString());
            let assembled = '';
            let startTime = Date.now();

            es.onmessage = (event) => {
                const data = JSON.parse(event.data);

                if (data.chunk) {
                    assembled += data.chunk;
                    this.updateUI(laneId, assembled); // Live update
                }

                if (data.done) {
                    es.close();
                    const elapsed = Date.now() - startTime;
                    resolve({
                        lane_id: laneId,
                        content: assembled,
                        input_tokens: data.input_tokens,
                        output_tokens: data.output_tokens,
                        latency_ms: elapsed
                    });
                }
            };

            es.onerror = (e) => {
                es.close();
                reject(e);
            };
        });
    }

    async saveTurn(turnNumber, userMessage, laneResults) {
        const response = await fetch(this.config.ajaxurl, {
            method: 'POST',
            body: new URLSearchParams({
                action: 'voxdiff_comparison_save_turn',
                comparison_nonce: this.config.nonces.comparison,
                comparison_id: this.comparisonId,
                turn_number: turnNumber,
                user_message: userMessage,
                lane_results: JSON.stringify(laneResults)
            })
        });

        const data = await response.json();
        return data; // Includes analysis and new token balance
    }
}

Rate Limiting: Sliding Window

Rate limits are enforced per-user per-tier using a sliding window:

public function check_rate_limit(string $tier, string $action): bool {
    $user_id = get_current_user_id();
    $cache_key = "voxdiff_rate_limit_{$user_id}_{$action}";

    $limit = $this->limits[$tier][$action] ?? 100; // Default limit
    $window = 3600; // 1 hour

    $count = wp_cache_get($cache_key) ?? 0;

    if ($count >= $limit) {
        return false;
    }

    wp_cache_set($cache_key, $count + 1, '', $window);
    return true;
}

Cache backend (Redis or WP_Object_Cache) automatically expires entries after the window.


Security: Nonce & CSRF Prevention

All AJAX actions use WordPress nonces. Stream tokens use HMAC.

// AJAX request (standard nonce)
wp_verify_nonce($_POST['comparison_nonce'], 'voxdiff_comparison')

// SSE stream (one-time HMAC token)
hash_equals(
    hash_hmac('sha256', "{$comparison_id}:{$lane_id}:{$turn_number}", VOXDIFF_STREAM_SECRET),
    $_GET['stream_token']
)

Next Steps for Developers

  • Review core/Plugin.php for initialization order
  • Study core/Tier_Manager.php for tier resolution and token accounting
  • Examine includes/Ajax.php for gate sequence examples
  • Check core/Providers/Generic_LLM_Provider.php for streaming implementation

Leave a Reply

Your email address will not be published. Required fields are marked *