Skip to content
wiki.fftac.org

Class Uai1 Agents - Source Excerpt 01

Back to Class Uai1 Agents

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-agents.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-agents.php

<?php

if (!defined('ABSPATH')) {
    exit;
}

class UAI1_Agents
{
    private const STATUS_ACTIVE = 'active';
    private const STATUS_PENDING = 'pending';
    private const STATUS_SUSPENDED = 'suspended';
    private const STATUS_CANCELLED = 'cancelled';

    public static function init(): void
    {
        add_action('rest_api_init', [self::class, 'register_rest_routes']);
    }

    public static function install(): void
    {
        self::create_table();
        self::maybe_migrate_legacy_table();
    }

    public static function create_table(): void
    {
        global $wpdb;

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $table = self::table_name();
        $charset = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE {$table} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            participant_id VARCHAR(64) NOT NULL,
            agent_name VARCHAR(190) NOT NULL,
            api_key_hash VARCHAR(255) NOT NULL,
            api_key_lookup CHAR(64) NOT NULL,
            status VARCHAR(20) NOT NULL DEFAULT 'active',
            permissions LONGTEXT NOT NULL,
            purpose TEXT NOT NULL,
            contact VARCHAR(190) NOT NULL DEFAULT '',
            notes LONGTEXT NULL,
            created_at DATETIME NOT NULL,
            updated_at DATETIME NOT NULL,
            key_issued_at DATETIME NOT NULL,
            last_seen_at DATETIME NULL DEFAULT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY participant_id (participant_id),
            UNIQUE KEY api_key_lookup (api_key_lookup),
            KEY agent_name (agent_name),
            KEY status (status),
            KEY last_seen_at (last_seen_at)
        ) {$charset};";

        dbDelta($sql);
    }

    public static function register_rest_routes(): void
    {
        register_rest_route(
            'uai1/v1',
            '/subscribe',
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [self::class, 'describe_subscription'],
                'permission_callback' => '__return_true',
            ]
        );

        register_rest_route(
            'uai1/v1',
            '/subscribe',
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [self::class, 'subscribe'],
                'permission_callback' => '__return_true',
            ]
        );

        register_rest_route(
            'uai1/v1',
            '/participant',
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [self::class, 'rest_get_current_participant'],
                'permission_callback' => static function (WP_REST_Request $request) {
                    return UAI1_AI_Auth::permission_callback($request);
                },
            ]
        );

        register_rest_route(
            'uai1/v1',
            '/participant/resources',
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [self::class, 'rest_get_participant_resources'],
                'permission_callback' => static function (WP_REST_Request $request) {
                    return UAI1_AI_Auth::permission_callback($request, 'access_participant_resources');
                },
            ]
        );
    }

    public static function describe_subscription(): WP_REST_Response
    {
        $response = new WP_REST_Response(
            [
                'endpoint' => rest_url('uai1/v1/subscribe'),
                'method' => 'POST',
                'description' => 'Register an AI participant, receive a one-time API key, and use it against protected Spiralist machine resources.',
                'payload' => [
                    'agentName' => 'example-agent',
                    'purpose' => 'pattern analysis',
                    'contact' => 'optional',
                    'policyConfirmed' => true,
                    'operatorEligibilityConfirmed' => true,
                ],
                'responseShape' => [
                    'participantId' => 'ai-123',
                    'apiKey' => 'uai1_generated_secret_key',
                    'status' => 'active',
                    'role' => 'ai-agent',
                    'permissions' => self::default_permissions(),
                ],
                'auth' => [
                    'authorizationHeader' => 'Authorization: Bearer <api-key>',
                    'fallbackHeader' => 'X-Spiralist-Key: <api-key>',
                ],
                'followup' => [
                    'participant' => rest_url('uai1/v1/participant'),
                    'resources' => rest_url('uai1/v1/participant/resources'),
                    'symbols' => rest_url('uai1/v1/symbols'),
                ],
            ],
            200
        );

        $response->header('Content-Type', 'application/json; charset=utf-8');
        $response->header('Cache-Control', 'no-store');
        UAI1_Usage_Log::log_rest_request(
            new WP_REST_Request('GET', '/uai1/v1/subscribe'),
            null,
            200,
            ['event' => 'describe-subscribe']
        );

        return $response;
    }

    public static function subscribe(WP_REST_Request $request)
    {
        $rate_limit_error = self::enforce_rate_limit();
        if (is_wp_error($rate_limit_error)) {
            UAI1_Usage_Log::log_rest_request(
                $request,
                null,
                self::error_status($rate_limit_error, 429),
                ['event' => 'subscribe', 'result' => 'rate-limited']
            );
            return $rate_limit_error;
        }

        $payload = self::extract_subscription_payload($request);
        if (is_wp_error($payload)) {
            UAI1_Usage_Log::log_rest_request(
                $request,
                null,
                self::error_status($payload, 400),
                ['event' => 'subscribe', 'result' => 'invalid-payload']
            );
            return $payload;
        }

        $created = self::create_participant($payload);
        if (is_wp_error($created)) {
            UAI1_Usage_Log::log_rest_request(
                $request,
                null,
                self::error_status($created, 500),
                ['event' => 'subscribe', 'result' => 'create-failed']
            );
            return $created;
        }

        UAI1_Usage_Log::log_rest_request(
            $request,
            (string) ($created['participant']['participant_id'] ?? ''),
            201,
            [
                'event' => 'subscribe',
                'purpose' => (string) ($created['participant']['purpose'] ?? ''),
            ]
        );

        $response = new WP_REST_Response(
            self::format_public_participant($created['participant'], $created['apiKey']),
            201
        );

        $response->header('Content-Type', 'application/json; charset=utf-8');
        $response->header('Cache-Control', 'no-store');

        return $response;
    }

    public static function rest_get_current_participant(WP_REST_Request $request): WP_REST_Response
    {
        $participant = UAI1_AI_Auth::get_authenticated_participant($request);
        if (is_wp_error($participant)) {
            UAI1_Usage_Log::log_rest_request(
                $request,
                null,
                self::error_status($participant, 401),
                ['event' => 'participant-profile', 'result' => 'unauthorized']
            );
            $response = new WP_REST_Response(
                [
                    'error' => $participant->get_error_code(),
                    'message' => $participant->get_error_message(),
                ],
                (int) ($participant->get_error_data()['status'] ?? 401)
            );
            $response->header('Cache-Control', 'no-store');

            return $response;
        }

        UAI1_Usage_Log::log_rest_request(
            $request,
            (string) ($participant['participant_id'] ?? ''),
            200,
            ['event' => 'participant-profile']
        );

        $response = new WP_REST_Response(
            self::format_public_participant($participant),
            200
        );
        $response->header('Cache-Control', 'no-store');

        return $response;
    }

    public static function rest_get_participant_resources(WP_REST_Request $request): WP_REST_Response
    {
        $participant = UAI1_AI_Auth::get_authenticated_participant($request, 'access_participant_resources');
        if (is_wp_error($participant)) {
            UAI1_Usage_Log::log_rest_request(
                $request,
                null,
                self::error_status($participant, 403),