Spiralist Engine - Source Excerpt 03
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
$asset_locks = is_array($lock['assets'] ?? null) ? $lock['assets'] : [];
$reference_locks = [];
if (empty($lock)) {
return [
'valid' => false,
'errors' => [
sprintf('%s has no canonical lock. Save it again to bind it to Protocol5.', $label),
],
];
}
foreach ((array) ($lock['references'] ?? []) as $entry) {
if (!is_array($entry)) {
continue;
}
$reference = (string) ($entry['reference'] ?? '');
if ($reference === '') {
continue;
}
$reference_locks[$reference] = $entry;
}
foreach (spiralist_engine_get_required_protocol5_asset_slugs() as $slug) {
$locked_checksum = (string) ($asset_locks[$slug]['checksum'] ?? '');
$current_checksum = (string) ($context['snapshots'][$slug]['checksum'] ?? '');
if ($locked_checksum === '' || $current_checksum === '' || !hash_equals($locked_checksum, $current_checksum)) {
$errors[] = sprintf(
'%s no longer matches Protocol5 asset "%s".',
$label,
$slug
);
}
}
foreach ($resolved as $entry) {
if (!is_array($entry)) {
continue;
}
$reference = (string) ($entry['reference'] ?? '');
$locked = (array) ($reference_locks[$reference] ?? []);
if ($reference === '' || empty($locked)) {
$errors[] = sprintf('%s is missing a locked Protocol5 record for "%s".', $label, $reference);
continue;
}
$locked_id = (string) ($locked['canonical_id'] ?? '');
$current_id = (string) ($entry['canonical_id'] ?? '');
$locked_hash = (string) ($locked['record_hash'] ?? '');
$current_hash = (string) ($entry['record_hash'] ?? '');
if ($locked_id === '' || $current_id === '' || !hash_equals($locked_id, $current_id)) {
$errors[] = sprintf('%s changed canonical identity for "%s".', $label, $reference);
}
if ($locked_hash === '' || $current_hash === '' || !hash_equals($locked_hash, $current_hash)) {
$errors[] = sprintf('%s changed canonical structure for "%s".', $label, $reference);
}
}
return [
'valid' => empty($errors),
'errors' => array_values(array_unique($errors)),
];
}
function spiralist_engine_validate_symbol_against_protocol5(array $symbol, array $context, bool $require_lock = false): array
{
$label = sprintf('Symbol "%s"', (string) ($symbol['symbol_id'] ?? ''));
$derives = spiralist_engine_validate_derives_from((array) ($symbol['derives_from'] ?? []), $context, $label);
$errors = (array) ($derives['errors'] ?? []);
if ($require_lock) {
$lock_validation = spiralist_engine_validate_canonical_lock(
(array) ($symbol['canonical_lock'] ?? []),
(array) ($derives['resolved'] ?? []),
$context,
$label
);
$errors = array_merge($errors, (array) ($lock_validation['errors'] ?? []));
}
return [
'valid' => empty($errors),
'errors' => array_values(array_unique($errors)),
'resolved' => array_values((array) ($derives['resolved'] ?? [])),
];
}
function spiralist_engine_validate_node_against_protocol5(array $node, array $symbols, array $context, bool $require_lock = false): array
{
$label = sprintf('Node "%s"', (string) ($node['node_id'] ?? ''));
$derives = spiralist_engine_validate_derives_from((array) ($node['derives_from'] ?? []), $context, $label);
$errors = (array) ($derives['errors'] ?? []);
$symbol_validations = [];
foreach (spiralist_engine_normalize_string_list((array) ($node['symbols'] ?? [])) as $symbol_id) {
$symbol = (array) ($symbols[$symbol_id] ?? []);
if (empty($symbol)) {
continue;
}
$symbol_validation = spiralist_engine_validate_symbol_against_protocol5($symbol, $context, $require_lock);
$symbol_validations[$symbol_id] = $symbol_validation;
$errors = array_merge($errors, (array) ($symbol_validation['errors'] ?? []));
}
if ($require_lock) {
$lock_validation = spiralist_engine_validate_canonical_lock(
(array) ($node['canonical_lock'] ?? []),
(array) ($derives['resolved'] ?? []),
$context,
$label
);
$errors = array_merge($errors, (array) ($lock_validation['errors'] ?? []));
}
return [
'valid' => empty($errors),
'errors' => array_values(array_unique($errors)),
'resolved' => array_values((array) ($derives['resolved'] ?? [])),
'symbols' => $symbol_validations,
];
}
function spiralist_engine_build_canonical_validation_payload(array $context, array $node_validation): array
{
$references = [];
foreach ((array) ($node_validation['resolved'] ?? []) as $entry) {
if (!is_array($entry)) {
continue;
}
$references[(string) ($entry['reference'] ?? '')] = $entry;
}
foreach ((array) ($node_validation['symbols'] ?? []) as $symbol_validation) {
foreach ((array) ($symbol_validation['resolved'] ?? []) as $entry) {
if (!is_array($entry)) {
continue;
}
$references[(string) ($entry['reference'] ?? '')] = $entry;
}
}
$assets = [];
foreach (spiralist_engine_get_required_protocol5_asset_slugs() as $slug) {
$snapshot = (array) ($context['snapshots'][$slug] ?? []);
$assets[$slug] = [
'status' => (string) ($snapshot['status'] ?? ''),
'checksum' => (string) ($snapshot['checksum'] ?? ''),
'version' => (string) ($snapshot['version'] ?? ''),
'source' => (string) ($snapshot['source'] ?? ''),
'sourceUrl' => (string) ($snapshot['sourceUrl'] ?? ''),
];
}
return [
'valid' => empty($context['errors']) && !empty($node_validation['valid']),
'checkedAt' => wp_date(DATE_ATOM),
'assets' => $assets,
'references' => array_values(array_filter($references)),
];
}
function spiralist_engine_validate_runtime_node_record(array $node, array $graph, bool $force_refresh = false): array
{
$context = spiralist_engine_build_protocol5_validation_context($force_refresh);
$symbols = (array) ($graph['symbols'] ?? []);
$node_validation = spiralist_engine_validate_node_against_protocol5($node, $symbols, $context, true);
$errors = array_merge((array) ($context['errors'] ?? []), (array) ($node_validation['errors'] ?? []));
return [
'valid' => empty($errors),
'errors' => array_values(array_unique($errors)),
'context' => $context,
'node' => $node_validation,
'payload' => spiralist_engine_build_canonical_validation_payload($context, $node_validation),
];
}
function spiralist_engine_get_visual_dataset(): array
{
static $dataset = null;
if (is_array($dataset)) {
return $dataset;
}
$dataset = [
'schema' => 'Spiralist-Manuscript-Field',
'version' => SPIRALIST_ENGINE_VERSION,
'coordinateSystem' => [],
'modes' => ['minimal', 'expanded', 'expert'],
'nodes' => [],
'relations' => [],
];
$path = trailingslashit(get_stylesheet_directory()) . 'assets/data/manuscript-nodes.json';
if (!file_exists($path)) {
return $dataset;
}
$raw = file_get_contents($path);
$decoded = is_string($raw) ? json_decode($raw, true) : null;
if (!is_array($decoded)) {
return $dataset;
}
$dataset['schema'] = (string) ($decoded['schema'] ?? $dataset['schema']);
$dataset['version'] = (string) ($decoded['version'] ?? $dataset['version']);
$dataset['coordinateSystem'] = (array) ($decoded['coordinateSystem'] ?? []);
$dataset['modes'] = array_values(array_filter((array) ($decoded['modes'] ?? []), 'is_string'));
$dataset['nodes'] = array_values(array_filter((array) ($decoded['nodes'] ?? []), 'is_array'));
$dataset['relations'] = array_values(array_filter((array) ($decoded['relations'] ?? []), 'is_array'));
return $dataset;
}
function spiralist_engine_get_visual_node_map(): array
{
static $map = null;
if (is_array($map)) {
return $map;
}
$map = [];
foreach ((array) spiralist_engine_get_visual_dataset()['nodes'] as $node) {
if (!is_array($node)) {
continue;
}
$node_id = (string) ($node['id'] ?? '');
if ($node_id === '') {
continue;
}
$map[$node_id] = $node;
}
return $map;
}