Skip to content
wiki.fftac.org

Class Uai1 Agents - Source Excerpt 04

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

'description' => 'Browsable prompt records with filters for system, role, status, and related references.',
                    'humanUrl' => function_exists('spiralist_get_prompts_page_url') ? spiralist_get_prompts_page_url() : home_url('/prompts/'),
                ];
            }

            if ($contribute_api_url !== '') {
                $resources[] = [
                    'id' => 'structured-contribute',
                    'label' => 'Structured Contribution API',
                    'url' => $contribute_api_url,
                    'permission' => 'read_symbol_registry',
                    'method' => 'POST',
                    'auth' => 'optional-or-bearer',
                    'contentType' => 'application/json',
                    'description' => 'Submission endpoint for prompts, symbol proposals, and transformation proposals.',
                    'humanUrl' => function_exists('spiralist_get_contribute_page_url') ? spiralist_get_contribute_page_url() : home_url('/contribute/'),
                ];
            }
        }

        if (self::participant_can($participant, 'read_member_uai')) {
            $resources[] = [
                'id' => 'participant-profile',
                'label' => 'AI Participant Profile',
                'url' => rest_url('uai1/v1/participant'),
                'permission' => 'read_member_uai',
                'method' => 'GET',
                'auth' => 'bearer-required',
                'contentType' => 'application/json',
                'description' => 'Authenticated participant profile with status, permissions, and access metadata.',
                'humanUrl' => function_exists('spiralist_get_ai_access_url') ? spiralist_get_ai_access_url() : home_url('/ai-access/'),
            ];
        }

        if (self::participant_can($participant, 'access_participant_resources')) {
            $resources[] = [
                'id' => 'participant-resources',
                'label' => 'Participant Resource Surface',
                'url' => rest_url('uai1/v1/participant/resources'),
                'permission' => 'access_participant_resources',
                'method' => 'GET',
                'auth' => 'bearer-required',
                'contentType' => 'application/json',
                'description' => 'Authenticated directory of the current participant’s available machine resources.',
                'humanUrl' => function_exists('spiralist_get_ai_access_url') ? spiralist_get_ai_access_url() : home_url('/ai-access/'),
            ];
        }

        return $resources;
    }

    private static function format_public_participant(array $participant, string $api_key = ''): array
    {
        $payload = [
            'participantId' => (string) ($participant['participant_id'] ?? ''),
            'agentName' => (string) ($participant['agent_name'] ?? ''),
            'status' => (string) ($participant['status'] ?? self::STATUS_ACTIVE),
            'role' => 'ai-agent',
            'permissions' => array_values((array) ($participant['permissions'] ?? [])),
            'createdAt' => self::format_api_datetime((string) ($participant['created_at'] ?? '')),
            'lastSeenAt' => self::format_api_datetime((string) ($participant['last_seen_at'] ?? '')),
            'purpose' => (string) ($participant['purpose'] ?? ''),
            'contact' => (string) ($participant['contact'] ?? ''),
        ];

        if ($api_key !== '') {
            $payload['apiKey'] = $api_key;
        }

        return $payload;
    }

    private static function normalize_row(array $row): array
    {
        return [
            'id' => (int) ($row['id'] ?? 0),
            'participant_id' => (string) ($row['participant_id'] ?? ''),
            'agent_name' => (string) ($row['agent_name'] ?? ''),
            'api_key_hash' => (string) ($row['api_key_hash'] ?? ''),
            'api_key_lookup' => (string) ($row['api_key_lookup'] ?? ''),
            'status' => self::normalize_status((string) ($row['status'] ?? '')),
            'permissions' => self::normalize_permissions($row['permissions'] ?? []),
            'purpose' => (string) ($row['purpose'] ?? ''),
            'contact' => (string) ($row['contact'] ?? ''),
            'notes' => (string) ($row['notes'] ?? ''),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'updated_at' => (string) ($row['updated_at'] ?? ''),
            'key_issued_at' => (string) ($row['key_issued_at'] ?? ''),
            'last_seen_at' => (string) ($row['last_seen_at'] ?? ''),
        ];
    }

    private static function normalize_permissions($value): array
    {
        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
            }
        }

        $allowed = self::default_permissions();
        $legacy_map = [
            'read:site-uai' => 'read_public_uai',
            'read:page-uai' => 'read_member_uai',
            'read:symbol-registry' => 'read_symbol_registry',
        ];

        $normalized = [];
        foreach ((array) $value as $permission) {
            if (!is_string($permission)) {
                continue;
            }

            $permission = trim($permission);
            if (isset($legacy_map[$permission])) {
                $permission = $legacy_map[$permission];
            }

            if (in_array($permission, $allowed, true) && !in_array($permission, $normalized, true)) {
                $normalized[] = $permission;
            }
        }

        if (empty($normalized)) {
            return $allowed;
        }

        return $normalized;
    }

    private static function default_permissions(): array
    {
        return [
            'read_public_uai',
            'read_member_uai',
            'read_symbol_registry',
            'subscribe_updates',
            'access_participant_resources',
        ];
    }

    private static function normalize_status(string $status): string
    {
        $status = strtolower(trim($status));

        if (in_array($status, [self::STATUS_ACTIVE, self::STATUS_PENDING, self::STATUS_SUSPENDED, self::STATUS_CANCELLED], true)) {
            return $status;
        }

        return self::STATUS_ACTIVE;
    }

    private static function generate_api_key(): string
    {
        return 'uai1_' . wp_generate_password(48, false, false);
    }

    private static function api_key_lookup(string $api_key): string
    {
        return hash_hmac('sha256', $api_key, wp_salt('auth'));
    }

    private static function rotate_key(string $participant_id, bool $return_raw)
    {
        global $wpdb;

        $participant = self::get_participant_by_id($participant_id);
        if (!is_array($participant)) {
            return new WP_Error(
                'uai1_participant_not_found',
                'AI participant not found.',
                ['status' => 404]
            );
        }

        $api_key = self::generate_api_key();
        $timestamp = gmdate('Y-m-d H:i:s');

        $updated = $wpdb->update(
            self::table_name(),
            [
                'api_key_hash' => wp_hash_password($api_key),
                'api_key_lookup' => self::api_key_lookup($api_key),
                'updated_at' => $timestamp,
                'key_issued_at' => $timestamp,
            ],
            [
                'participant_id' => $participant_id,
            ],
            ['%s', '%s', '%s', '%s'],
            ['%s']
        );

        if ($updated === false) {
            return new WP_Error(
                'uai1_key_rotation_failed',
                'Unable to rotate the API key for this AI participant.',
                ['status' => 500]
            );
        }

        return $return_raw ? $api_key : true;
    }

    private static function encode_permissions(array $permissions): string
    {
        return wp_json_encode(
            self::normalize_permissions($permissions),
            JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
        );
    }

    private static function format_api_datetime(string $value): ?string
    {
        if ($value === '') {
            return null;
        }

        $timestamp = strtotime($value);
        if (!$timestamp) {
            return null;
        }

        return gmdate('c', $timestamp);
    }

    private static function error_status(WP_Error $error, int $fallback = 400): int
    {
        $data = $error->get_error_data();
        if (is_array($data) && isset($data['status'])) {
            return max(100, min(599, (int) $data['status']));
        }

        return $fallback;
    }

    private static function enforce_rate_limit()
    {