Governancemaintenance - Source Excerpt 01
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Services/GovernanceMaintenance.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Services/GovernanceMaintenance.php
<?php
namespace SpiralistWorkspace\Services;
use SpiralistWorkspace\Domain\AppealRepository;
use SpiralistWorkspace\Domain\AuditRepository;
use SpiralistWorkspace\Domain\PromptRepository;
use SpiralistWorkspace\Domain\ReputationRepository;
use SpiralistWorkspace\Domain\ReviewRepository;
use SpiralistWorkspace\Support\PromptGovernanceSummary;
use SpiralistWorkspace\Support\PromptMetaRegistry;
if (!defined('ABSPATH')) {
exit;
}
/**
* Runs scheduled self-operation maintenance for prompt governance state.
*/
class GovernanceMaintenance
{
public const HOOK = 'spiralist_workspace_governance_maintenance';
private const LAST_RUN_OPTION = 'spiralist_workspace_governance_maintenance_last_run';
private const PEER_ENDORSE_QUORUM = 2;
private const PEER_RESTRICT_QUORUM = 2;
private const PEER_REVERT_QUORUM = 2;
private const REPORT_RESTRICTION_QUORUM = 3;
private const STALE_REPORTED_RESTRICTION_HOURS = 72;
private const URGENT_REPORTED_RESTRICTION_HOURS = 168;
private const URGENT_RISK_RESTRICTION_HOURS = 168;
private const HIGH_RISK_LABELS = [
'identity_overwrite',
'dependency_cue',
'coercive_certainty',
'privacy_risk',
'high_stakes_unsuitable',
'viral_propagation',
];
private PromptRepository $prompts;
private ReviewRepository $reviews;
private AppealRepository $appeals;
private ReputationRepository $reputation;
private AuditRepository $audit;
public function __construct(
PromptRepository $prompts,
ReviewRepository $reviews,
AppealRepository $appeals,
ReputationRepository $reputation,
AuditRepository $audit
) {
$this->prompts = $prompts;
$this->reviews = $reviews;
$this->appeals = $appeals;
$this->reputation = $reputation;
$this->audit = $audit;
}
/**
* Ensures the recurring maintenance event exists.
*/
public function schedule(): void
{
if (!wp_next_scheduled(self::HOOK)) {
wp_schedule_event(time() + HOUR_IN_SECONDS, 'hourly', self::HOOK);
}
}
/**
* Clears the recurring maintenance event.
*/
public static function unschedule(): void
{
$timestamp = wp_next_scheduled(self::HOOK);
while ($timestamp) {
wp_unschedule_event($timestamp, self::HOOK);
$timestamp = wp_next_scheduled(self::HOOK);
}
}
/**
* Returns the last stored maintenance summary.
*
* @return array<string,mixed>
*/
public function status(): array
{
$status = get_option(self::LAST_RUN_OPTION, []);
return is_array($status) ? $status : [];
}
/**
* Recalculates participant state and records current queue health.
*
* @return array<string,mixed>
*/
public function run(int $limit = 250): array
{
$limit = max(25, min(500, $limit));
$prompts = $this->prompts->list(['limit' => $limit], 0);
$user_ids = [];
$queue_counts = [
'provisional_public' => 0,
'restricted_review' => 0,
'reverted' => 0,
'archived' => 0,
'peer_reviewed' => 0,
'official_canon' => 0,
'open_appeals' => 0,
'stale_review_items' => 0,
'urgent_review_items' => 0,
'stale_open_appeals' => 0,
'automatic_fallback_actions' => 0,
'auto_promoted_by_quorum' => 0,
'auto_restricted_by_quorum' => 0,
'auto_reverted_by_quorum' => 0,
'auto_archived_by_appeal' => 0,
'auto_restricted_by_absence' => 0,
];
$stale_review_items = [];
$urgent_review_items = [];
$stale_appeal_items = [];
$automatic_fallback_items = [];
foreach ($prompts as $prompt) {
if (!is_array($prompt) || !empty($prompt['soft_deleted_at'])) {
continue;
}
$owner_user_id = (int) ($prompt['owner_user_id'] ?? 0);
if ($owner_user_id > 0) {
$user_ids[$owner_user_id] = true;
}
if ((string) ($prompt['visibility'] ?? '') !== 'public' || (string) ($prompt['prompt_status'] ?? '') !== 'published') {
continue;
}
$status = PromptMetaRegistry::normalize_moderation_status((string) ($prompt['moderation_status'] ?? 'draft'));
$prompt_id = (int) ($prompt['id'] ?? 0);
$appeal_summary = $this->appeals->summary_for_prompt($prompt_id);
$review_summary = $this->current_review_summary($prompt_id, $appeal_summary);
$governance_summary = PromptGovernanceSummary::build($prompt, $review_summary, $appeal_summary);
$automatic_fallback = $this->apply_automatic_fallback($prompt, $review_summary, $appeal_summary, $governance_summary);
if (!empty($automatic_fallback)) {
$prompt = (array) ($automatic_fallback['prompt'] ?? $prompt);
$status = PromptMetaRegistry::normalize_moderation_status((string) ($prompt['moderation_status'] ?? $status));
$governance_summary = PromptGovernanceSummary::build($prompt, $review_summary, $appeal_summary);
$queue_counts['automatic_fallback_actions']++;
$action_kind = (string) ($automatic_fallback['action_kind'] ?? '');
if (isset($queue_counts[$action_kind])) {
$queue_counts[$action_kind]++;
}
$automatic_fallback_items[] = $this->automatic_fallback_item($prompt, $automatic_fallback, $governance_summary);
}
if (isset($queue_counts[$status])) {
$queue_counts[$status]++;
}
if (in_array($status, ['provisional_public', 'restricted_review', 'reverted'], true)) {
$queue_age = is_array($governance_summary['queue_age'] ?? null) ? (array) $governance_summary['queue_age'] : [];
if (!empty($queue_age['is_stale'])) {
$queue_counts['stale_review_items']++;
$stale_review_items[] = $this->review_queue_item($prompt, $governance_summary);
}
if (!empty($queue_age['is_urgent'])) {
$queue_counts['urgent_review_items']++;
$urgent_review_items[] = $this->review_queue_item($prompt, $governance_summary);
}
foreach ($this->reviews->list_for_prompt($prompt_id, 100) as $review) {
$reviewer_user_id = (int) ($review['reviewer_user_id'] ?? 0);
if ($reviewer_user_id > 0) {
$user_ids[$reviewer_user_id] = true;
}
}
}
}
foreach ($this->appeals->list_open($limit) as $appeal) {
$queue_counts['open_appeals']++;
$appellant_user_id = (int) ($appeal['appellant_user_id'] ?? 0);
if ($appellant_user_id > 0) {
$user_ids[$appellant_user_id] = true;
}
$prompt = $this->prompts->get((int) ($appeal['prompt_post_id'] ?? 0), 0);
$owner_user_id = (int) ($prompt['owner_user_id'] ?? 0);
if ($owner_user_id > 0) {
$user_ids[$owner_user_id] = true;
}
$appeal_age_hours = $this->hours_since((string) ($appeal['updated_utc'] ?? ($appeal['created_utc'] ?? '')));
if ($appeal_age_hours >= 72) {
$queue_counts['stale_open_appeals']++;
$stale_appeal_items[] = [
'appeal_id' => (int) ($appeal['id'] ?? 0),
'prompt_id' => (int) ($appeal['prompt_post_id'] ?? 0),
'prompt_title' => (string) ($prompt['title'] ?? ''),
'prompt_slug' => (string) ($prompt['slug'] ?? ''),
'moderation_status' => (string) ($prompt['moderation_status'] ?? ''),
'hours_since_activity' => $appeal_age_hours,
'duration_label' => $this->duration_label($appeal_age_hours),
'updated_utc' => (string) ($appeal['updated_utc'] ?? ''),
];
}
}
$refreshed = 0;
foreach (array_slice(array_keys($user_ids), 0, $limit) as $user_id) {
$this->reputation->refresh((int) $user_id);
$refreshed++;
}
$summary = [
'ran_utc' => gmdate('c'),
'prompt_sample_size' => count($prompts),
'participants_considered' => count($user_ids),
'participants_refreshed' => $refreshed,