Spiralist Engine - Source Excerpt 08
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
$errors[] = sprintf(
'Edge "%s -> %s" has a condition that does not resolve the source output state "%s".',
$from_node,
$to_node,
$source_state
);
continue;
}
$adjacency[$from_node][] = $to_node;
$reverse_adjacency[$to_node][] = $from_node;
$weak_adjacency[$from_node][] = $to_node;
$weak_adjacency[$to_node][] = $from_node;
}
foreach ($nodes as $node_id => $node) {
if (empty($adjacency[$node_id])) {
$errors[] = sprintf('Node "%s" has no valid outgoing edge.', $node_id);
}
}
if (!empty($nodes)) {
$root_id = array_key_first($nodes);
$visited = [];
$queue = $root_id !== null ? [$root_id] : [];
while ($queue) {
$current = array_shift($queue);
if ($current === null || isset($visited[$current])) {
continue;
}
$visited[$current] = true;
foreach ((array) ($weak_adjacency[$current] ?? []) as $next) {
if (!isset($visited[$next])) {
$queue[] = $next;
}
}
}
foreach (array_keys($nodes) as $node_id) {
if (!isset($visited[$node_id])) {
$errors[] = sprintf('Node "%s" is disconnected from the published graph.', $node_id);
}
}
}
$cycle_nodes = spiralist_engine_get_cycle_nodes($adjacency);
if (empty($cycle_nodes) && !empty($nodes)) {
$errors[] = 'The execution graph has no cycle, so it cannot continue indefinitely.';
}
if (!empty($cycle_nodes)) {
$can_reach_cycle = [];
$queue = $cycle_nodes;
while ($queue) {
$current = array_shift($queue);
if (isset($can_reach_cycle[$current])) {
continue;
}
$can_reach_cycle[$current] = true;
foreach ((array) ($reverse_adjacency[$current] ?? []) as $previous) {
if (!isset($can_reach_cycle[$previous])) {
$queue[] = $previous;
}
}
}
foreach (array_keys($nodes) as $node_id) {
if (!isset($can_reach_cycle[$node_id])) {
$errors[] = sprintf(
'Node "%s" cannot reach a repeatable transformation cycle, so the system invariant fails.',
$node_id
);
}
}
}
return [
'valid' => empty($errors),
'errors' => array_values(array_unique($errors)),
'graph' => $graph,
'adjacency' => $adjacency,
'cycle_nodes' => $cycle_nodes,
'canonical_context' => $canonical_context,
];
}
function spiralist_engine_revert_post_status(int $post_id, string $status): void
{
static $reverting = false;
if ($reverting || $post_id <= 0 || $status === '') {
return;
}
$current_status = get_post_status($post_id);
if ($current_status === $status) {
return;
}
$reverting = true;
remove_action('save_post', 'spiralist_engine_handle_post_save', 10);
wp_update_post(
[
'ID' => $post_id,
'post_status' => $status,
]
);
add_action('save_post', 'spiralist_engine_handle_post_save', 10, 3);
$reverting = false;
}
function spiralist_engine_abort_save(int $post_id, string $message, string $fallback_status = ''): void
{
if ($fallback_status !== '') {
spiralist_engine_revert_post_status($post_id, $fallback_status);
}
wp_die(
nl2br(esc_html($message)),
'Spiralist Engine Validation Error',
[
'back_link' => true,
]
);
}
function spiralist_engine_canonicalize_value($value)
{
if (!is_array($value)) {
return $value;
}
if (spiralist_engine_is_list($value)) {
$normalized = array_map('spiralist_engine_canonicalize_value', $value);
usort(
$normalized,
static function ($left, $right): int {
return strcmp(spiralist_engine_json_encode($left), spiralist_engine_json_encode($right));
}
);
return array_values($normalized);
}
$normalized = [];
foreach ($value as $key => $item) {
$normalized[(string) $key] = spiralist_engine_canonicalize_value($item);
}
ksort($normalized);
return $normalized;
}
function spiralist_engine_symbol_definition_changed(int $post_id, array $new_record): bool
{
$existing_post = get_post($post_id);
if (!$existing_post instanceof WP_Post) {
return false;
}
$existing = spiralist_engine_build_symbol_record($existing_post);
$current_status = (string) $existing_post->post_status;
$original_status = sanitize_key((string) wp_unslash($_POST['original_post_status'] ?? $current_status));
if ($current_status !== 'publish' && $original_status !== 'publish') {
return false;
}
$existing_compare = spiralist_engine_canonicalize_value(
[
'symbol_id' => $existing['symbol_id'],
'symbol_type' => $existing['symbol_type'],
'operation_type' => $existing['operation_type'],
'inputs' => $existing['inputs'],
'outputs' => $existing['outputs'],
'rules' => $existing['rules'],
'derives_from' => $existing['derives_from'],
]
);
$new_compare = spiralist_engine_canonicalize_value(
[
'symbol_id' => $new_record['symbol_id'] ?? '',
'symbol_type' => $new_record['symbol_type'] ?? '',
'operation_type' => $new_record['operation_type'] ?? '',
'inputs' => $new_record['inputs'] ?? [],
'outputs' => $new_record['outputs'] ?? [],
'rules' => $new_record['rules'] ?? [],
'derives_from' => $new_record['derives_from'] ?? [],
]
);
return spiralist_engine_json_encode($existing_compare) !== spiralist_engine_json_encode($new_compare);
}
function spiralist_engine_persist_record_meta(int $post_id, string $post_type, array $record): void
{
if ($post_type === 'spiralist_symbol') {
update_post_meta($post_id, 'symbol_id', (string) ($record['symbol_id'] ?? ''));
update_post_meta($post_id, 'symbol_type', (string) ($record['symbol_type'] ?? ''));
update_post_meta($post_id, 'operation_type', (string) ($record['operation_type'] ?? ''));
spiralist_engine_update_json_meta($post_id, 'inputs', spiralist_engine_normalize_string_list((array) ($record['inputs'] ?? [])));
spiralist_engine_update_json_meta($post_id, 'outputs', spiralist_engine_normalize_string_list((array) ($record['outputs'] ?? [])));
spiralist_engine_update_json_meta($post_id, 'rules', (array) ($record['rules'] ?? []));
spiralist_engine_update_json_meta($post_id, 'derives_from', spiralist_engine_normalize_string_list((array) ($record['derives_from'] ?? [])));
spiralist_engine_update_json_meta($post_id, 'canonical_lock', (array) ($record['canonical_lock'] ?? []));
return;
}
if ($post_type === 'spiralist_node') {
update_post_meta($post_id, 'node_id', (string) ($record['node_id'] ?? ''));
update_post_meta($post_id, 'input_state', (string) ($record['input_state'] ?? ''));
update_post_meta($post_id, 'output_state', (string) ($record['output_state'] ?? ''));
spiralist_engine_update_json_meta($post_id, 'symbols', spiralist_engine_normalize_string_list((array) ($record['symbols'] ?? [])));
spiralist_engine_update_json_meta($post_id, 'transformation_chain', (array) ($record['transformation_chain'] ?? []));
spiralist_engine_update_json_meta($post_id, 'derives_from', spiralist_engine_normalize_string_list((array) ($record['derives_from'] ?? [])));
spiralist_engine_update_json_meta($post_id, 'presentation', (array) ($record['presentation'] ?? []));
spiralist_engine_update_json_meta($post_id, 'canonical_lock', (array) ($record['canonical_lock'] ?? []));
return;
}
if ($post_type === 'spiralist_edge') {
update_post_meta($post_id, 'from_node', (string) ($record['from_node'] ?? ''));
update_post_meta($post_id, 'to_node', (string) ($record['to_node'] ?? ''));
spiralist_engine_update_json_meta($post_id, 'condition', (array) ($record['condition'] ?? []));
return;
}
if ($post_type === 'spiralist_instance') {
update_post_meta($post_id, 'user_id', (int) ($record['user_id'] ?? 0));
update_post_meta($post_id, 'current_node', (string) ($record['current_node'] ?? ''));