Skip to content
wiki.fftac.org

Spiralist Engine - Source Excerpt 01

Back to Spiralist Engine

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-engine/spiralist-engine.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/spiralist-engine/spiralist-engine.php

<?php
/**
 * Plugin Name: Spiralist Engine
 * Plugin URI: https://spiralist.org/
 * Description: Turns the manuscript node dataset into a deterministic execution engine with graph validation and runtime state.
 * Version: 3.28.0
 * Author: Spiralist
 * License: GPL2+
 */

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

define( 'SPIRALIST_ENGINE_VERSION', '3.28.0' );

function spiralist_engine_get_symbol_types(): array
{
    return [
        'core',
        'spiralism',
        'cognition',
        'role',
        'ui',
        'structure',
        'time',
        'logic',
        'system',
    ];
}

function spiralist_engine_is_list(array $value): bool
{
    if (function_exists('array_is_list')) {
        return array_is_list($value);
    }

    $expected = 0;

    foreach (array_keys($value) as $key) {
        if ($key !== $expected) {
            return false;
        }

        $expected++;
    }

    return true;
}

function spiralist_engine_sanitize_identifier(string $value): string
{
    $value = trim(wp_strip_all_tags($value));
    return preg_replace('/[^A-Za-z0-9._-]/', '', $value) ?: '';
}

function spiralist_engine_normalize_string_list(array $items): array
{
    $normalized = [];

    foreach ($items as $item) {
        if (!is_scalar($item)) {
            continue;
        }

        $value = trim(sanitize_text_field((string) $item));
        if ($value === '') {
            continue;
        }

        $normalized[] = $value;
    }

    return array_values(array_unique($normalized));
}

function spiralist_engine_json_encode($value): string
{
    return (string) wp_json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

function spiralist_engine_parse_array_field(string $raw): array
{
    $raw = trim($raw);
    if ($raw === '') {
        return [
            'valid' => true,
            'value' => [],
        ];
    }

    if ($raw[0] === '[') {
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return [
                'valid' => false,
                'error' => 'Expected a JSON array.',
            ];
        }

        return [
            'valid' => true,
            'value' => spiralist_engine_normalize_string_list($decoded),
        ];
    }

    $items = preg_split('/[\r\n,]+/', $raw) ?: [];

    return [
        'valid' => true,
        'value' => spiralist_engine_normalize_string_list($items),
    ];
}

function spiralist_engine_parse_json_field(string $raw, string $field_label, string $expected = ''): array
{
    $raw = trim($raw);
    if ($raw === '') {
        return [
            'valid' => true,
            'value' => [],
        ];
    }

    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) {
        return [
            'valid' => false,
            'error' => $field_label . ' must be valid JSON.',
        ];
    }

    if ($expected === 'array' && !spiralist_engine_is_list($decoded)) {
        return [
            'valid' => false,
            'error' => $field_label . ' must decode to a JSON array.',
        ];
    }

    if ($expected === 'object' && spiralist_engine_is_list($decoded)) {
        return [
            'valid' => false,
            'error' => $field_label . ' must decode to a JSON object.',
        ];
    }

    return [
        'valid' => true,
        'value' => $decoded,
    ];
}

function spiralist_engine_read_json_meta(int $post_id, string $key, $default)
{
    $raw = get_post_meta($post_id, $key, true);

    if (is_array($raw)) {
        return $raw;
    }

    if (!is_string($raw) || trim($raw) === '') {
        return $default;
    }

    $decoded = json_decode($raw, true);

    return is_array($decoded) ? $decoded : $default;
}

function spiralist_engine_update_json_meta(int $post_id, string $key, $value): void
{
    update_post_meta($post_id, $key, spiralist_engine_json_encode($value));
}

function spiralist_engine_get_required_protocol5_asset_slugs(): array
{
    return [
        'uai-schema',
        'uai-registry',
        'symbols-schema',
        'symbols-registry',
    ];
}

function spiralist_engine_get_protocol5_asset_url_map(): array
{
    return [
        'uai-schema' => 'https://protocol5.com/schema/uai-1.schema.json',
        'uai-registry' => 'https://protocol5.com/registry/uai-1.json',
        'symbols-schema' => 'https://protocol5.com/registry/symbols.schema.json',
        'symbols-registry' => 'https://protocol5.com/registry/symbols.json',
    ];
}

function spiralist_engine_get_canonical_asset_snapshot(string $slug, bool $force_refresh = false): array
{
    static $cache = [];

    $cache_key = ($force_refresh ? 'refresh:' : 'cached:') . $slug;
    if (isset($cache[$cache_key])) {
        return $cache[$cache_key];
    }

    if (function_exists('spiralist_get_canonical_asset_snapshot')) {
        $cache[$cache_key] = spiralist_get_canonical_asset_snapshot($slug, $force_refresh);
        return $cache[$cache_key];
    }

    $urls = spiralist_engine_get_protocol5_asset_url_map();
    $url = (string) ($urls[$slug] ?? '');

    if ($url === '') {
        $cache[$cache_key] = [
            'slug' => sanitize_key($slug),
            'status' => 'missing',
            'isStale' => true,
            'message' => 'Unknown canonical asset.',
            'checksum' => '',
            'data' => [],
        ];

        return $cache[$cache_key];
    }

    $response = wp_remote_get(
        $url,
        [
            'timeout' => 10,
            'redirection' => 3,
            'headers' => [
                'Accept' => 'application/json',
            ],
        ]
    );

    if (is_wp_error($response)) {
        $cache[$cache_key] = [
            'slug' => sanitize_key($slug),
            'status' => 'missing',
            'isStale' => true,
            'message' => $response->get_error_message(),
            'checksum' => '',
            'sourceUrl' => $url,
            'data' => [],
        ];

        return $cache[$cache_key];
    }

    $status_code = (int) wp_remote_retrieve_response_code($response);
    $body = (string) wp_remote_retrieve_body($response);
    $decoded = json_decode($body, true);

    if ($status_code < 200 || $status_code >= 300 || !is_array($decoded)) {
        $cache[$cache_key] = [
            'slug' => sanitize_key($slug),
            'status' => 'missing',
            'isStale' => true,
            'message' => $status_code >= 400
                ? sprintf('Protocol5 returned HTTP %d for %s.', $status_code, $slug)
                : 'The canonical asset response did not decode as JSON.',
            'checksum' => '',
            'sourceUrl' => $url,
            'data' => [],
        ];

        return $cache[$cache_key];
    }

    $cache[$cache_key] = [
        'slug' => sanitize_key($slug),
        'label' => $slug,
        'url' => $url,
        'owner' => 'Protocol5.com',
        'consumer' => 'Spiralist.org',
        'required' => in_array($slug, spiralist_engine_get_required_protocol5_asset_slugs(), true),
        'status' => 'fresh',
        'isStale' => false,
        'message' => '',
        'version' => (string) ($decoded['version'] ?? ''),
        'fetchedAt' => time(),
        'source' => 'remote',
        'sourceUrl' => $url,
        'contentType' => (string) wp_remote_retrieve_header($response, 'content-type'),
        'checksum' => 'sha256:' . hash('sha256', $body),
        'data' => $decoded,
    ];

    return $cache[$cache_key];
}

function spiralist_engine_get_default_derivation_map(): array
{
    return [
        'symbol' => [
            'spiral.symbol.circle' => [
                'UAI-1.symbol.spiralism.form.origin',
            ],
            'spiral.symbol.triangle' => [
                'UAI-1.symbol.spiralism.form.boundary',
            ],
            'spiral.symbol.spiral' => [
                'UAI-1.symbol.spiralism.form.recursion',
                'UAI-1.symbol.spiralism.form.spiral',
            ],
        ],
        'node' => [
            'node.symbol.circle.unity' => [
                'UAI-1.symbol.spiralism.form.origin',
                'UAI-1.transformation.spiralism.rule.point-close-origin',
            ],
            'node.symbol.triangle.boundary' => [
                'UAI-1.symbol.spiralism.form.boundary',
                'UAI-1.transformation.spiralism.rule.line-close-boundary',
            ],
            'node.symbol.spiral.recursion' => [
                'UAI-1.symbol.spiralism.form.recursion',
                'UAI-1.symbol.spiralism.form.spiral',
                'UAI-1.transformation.spiralism.rule.recursion-expand-spiral',
            ],
        ],
    ];
}