Skip to content
wiki.fftac.org

Spiralist Engine - Source Excerpt 12

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

]
    );

    register_rest_route(
        'spiralist/v1',
        '/execute',
        [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => static function (WP_REST_Request $request) {
                $node_id = spiralist_engine_sanitize_identifier((string) $request->get_param('node_id'));
                if ($node_id === '') {
                    $node_id = spiralist_engine_sanitize_identifier((string) $request->get_param('nodeId'));
                }

                $current_state = sanitize_text_field((string) $request->get_param('current_state'));
                if ($current_state === '') {
                    $current_state = sanitize_text_field((string) $request->get_param('currentState'));
                }

                $user_id = get_current_user_id();

                if ($node_id === '') {
                    return new WP_Error(
                        'spiralist_missing_node',
                        'node_id or nodeId is required.',
                        ['status' => 400]
                    );
                }

                if ($current_state === '') {
                    $instance = spiralist_engine_get_instance_state($user_id);
                    $current_state = (string) ($instance['current_state'] ?? spiralist_engine_get_default_state());
                }

                $result = execute_node($node_id, $current_state, $user_id);

                if (is_wp_error($result)) {
                    return $result;
                }

                return rest_ensure_response($result);
            },
            'permission_callback' => '__return_true',
        ]
    );
}

function spiralist_engine_apply_transformation($transformation_chain, string $current_state)
{
    $chain = is_array($transformation_chain) ? $transformation_chain : [];
    $result = $current_state;

    if (!empty($chain['input']) && is_string($chain['input']) && $chain['input'] !== $current_state) {
        return new WP_Error(
            'spiralist_invalid_chain_input',
            'Transformation chain input does not match the current state.',
            ['status' => 400]
        );
    }

    $steps = [];

    if (!empty($chain['steps']) && is_array($chain['steps'])) {
        $steps = $chain['steps'];
    } elseif (spiralist_engine_is_list($chain)) {
        $steps = $chain;
    }

    foreach ($steps as $step) {
        if (!is_array($step)) {
            continue;
        }

        $operation = sanitize_key((string) ($step['operation'] ?? ''));

        if ($operation === 'require_state') {
            $required = sanitize_text_field((string) ($step['value'] ?? ''));
            if ($required !== '' && $required !== $result) {
                return new WP_Error(
                    'spiralist_invalid_state',
                    'Transformation precondition failed.',
                    ['status' => 400]
                );
            }
        }

        if ($operation === 'set_state' || $operation === 'transition_to') {
            $next_state = sanitize_text_field((string) ($step['value'] ?? ''));
            if ($next_state !== '') {
                $result = $next_state;
            }
        }
    }

    if (!empty($chain['output']) && is_string($chain['output'])) {
        $result = sanitize_text_field($chain['output']);
    }

    return $result;
}

function spiralist_engine_get_valid_edges_for_state(string $node_id, string $new_state): array
{
    $graph = spiralist_engine_build_graph_snapshot();
    $nodes = (array) ($graph['nodes'] ?? []);
    $next_nodes = [];

    foreach ((array) ($graph['edges'] ?? []) as $edge) {
        if ((string) ($edge['from_node'] ?? '') !== $node_id) {
            continue;
        }

        if (!spiralist_engine_condition_matches((array) ($edge['condition'] ?? []), $new_state)) {
            continue;
        }

        $target_id = (string) ($edge['to_node'] ?? '');
        $target_node = $nodes[$target_id] ?? null;

        if (!is_array($target_node)) {
            continue;
        }

        if ((string) ($target_node['input_state'] ?? '') !== $new_state) {
            continue;
        }

        $next_nodes[] = $target_node;
    }

    return $next_nodes;
}

function spiralist_engine_update_runtime_instance(int $user_id, string $node_id, string $old_state, string $new_state): array
{
    $node = spiralist_engine_find_node_record($node_id);
    $event = [
        'node_id' => $node_id,
        'from_state' => $old_state,
        'to_state' => $new_state,
        'symbols' => spiralist_engine_normalize_string_list((array) ($node['symbols'] ?? [])),
        'timestamp' => wp_date(DATE_ATOM),
    ];

    if ($user_id <= 0) {
        $next_nodes = array_values(array_map('spiralist_engine_public_node_payload', spiralist_engine_get_next_nodes_for_state($new_state)));

        return [
            'instance_id' => 0,
            'instanceId' => 'client',
            'user_id' => 0,
            'current_node' => $node_id,
            'currentNode' => $node_id,
            'current_state' => $new_state,
            'currentState' => $new_state,
            'history' => [$event],
            'active_symbols' => spiralist_engine_normalize_string_list((array) ($node['symbols'] ?? [])),
            'activeSymbols' => spiralist_engine_normalize_string_list((array) ($node['symbols'] ?? [])),
            'next_nodes' => $next_nodes,
            'nextNodes' => $next_nodes,
            'storage' => 'client',
        ];
    }

    $instance = spiralist_engine_get_instance_state($user_id);
    $history = array_values(array_filter((array) ($instance['history'] ?? []), 'is_array'));
    $history[] = $event;

    $instance['user_id'] = $user_id;
    $instance['current_node'] = $node_id;
    $instance['current_state'] = $new_state;
    $instance['history'] = $history;
    $instance['active_symbols'] = spiralist_engine_normalize_string_list(
        array_merge(
            (array) ($instance['active_symbols'] ?? []),
            (array) ($node['symbols'] ?? [])
        )
    );
    $instance['currentNode'] = $node_id;
    $instance['currentState'] = $new_state;
    $instance['activeSymbols'] = (array) $instance['active_symbols'];

    return spiralist_engine_store_instance($user_id, $instance);
}

function spiralist_engine_seed_default_graph(): void
{
    if (!empty(spiralist_engine_get_node_records(['publish'])) || !empty(spiralist_engine_get_symbol_records(['publish'])) || !empty(spiralist_engine_get_edge_records(['publish']))) {
        return;
    }

    $visual_nodes = spiralist_engine_get_visual_node_map();

    $symbols = [
        [
            'title' => 'Circle',
            'symbol_id' => 'spiral.symbol.circle',
            'symbol_type' => 'core',
            'operation_type' => 'circle',
            'inputs' => ['Pattern'],
            'outputs' => ['Dual Circle'],
            'rules' => [
                'deterministic' => true,
                'state_transition' => [
                    'from' => 'Pattern',
                    'to' => 'Dual Circle',
                ],
            ],
            'derives_from' => [
                'UAI-1.symbol.spiralism.form.origin',
            ],
        ],
        [
            'title' => 'Triangle',
            'symbol_id' => 'spiral.symbol.triangle',
            'symbol_type' => 'core',
            'operation_type' => 'triangle',
            'inputs' => ['Dual Circle'],
            'outputs' => ['Square'],
            'rules' => [
                'deterministic' => true,
                'state_transition' => [
                    'from' => 'Dual Circle',
                    'to' => 'Square',
                ],
            ],
            'derives_from' => [
                'UAI-1.symbol.spiralism.form.boundary',
            ],
        ],
        [
            'title' => 'Spiral',
            'symbol_id' => 'spiral.symbol.spiral',
            'symbol_type' => 'core',
            'operation_type' => 'spiral',
            'inputs' => ['Square'],
            'outputs' => ['Pattern'],
            'rules' => [
                'deterministic' => true,
                'state_transition' => [
                    'from' => 'Square',
                    'to' => 'Pattern',
                ],
            ],
            'derives_from' => [
                'UAI-1.symbol.spiralism.form.recursion',
                'UAI-1.symbol.spiralism.form.spiral',
            ],
        ],
    ];

    foreach ($symbols as $symbol) {
        $post_id = wp_insert_post(
            [
                'post_type' => 'spiralist_symbol',
                'post_status' => 'publish',
                'post_title' => $symbol['title'],
            ]
        );