what was done for each generation that needed it: sync_queued | reopened | build_open | paused | approval_required; * or one of: inert (generations cannot run here) | no_generation | current (nothing is missing) */ public function sourceChanged(int $siteId, int $sourceId): array { if ($this->jobs === null) { return ['inert']; } $rows = $this->db->select("SELECT id, state FROM {knowledge_generations} WHERE site_id = ? AND state IN ('building', 'ready', 'active') ORDER BY id", [$siteId]); if ($rows === []) { return ['no_generation']; } $done = []; foreach ($rows as $g) { $id = (int) $g['id']; if (!$this->generations->sourceMissing($siteId, $id, $sourceId)) { continue; } $done[] = match (true) { $g['state'] === IndexGenerations::BUILDING => 'build_open', // an approved build continues on its own chain !$this->control->autoSync($siteId) => 'approval_required', $g['state'] === IndexGenerations::ACTIVE => $this->queueSync($siteId), default => $this->continueReady($siteId, $id), }; } return $done === [] ? ['current'] : array_values(array_unique($done)); } private function queueSync(int $siteId): string { $this->jobs?->requestSync($siteId); return 'sync_queued'; } /** Reopens a complete, inactive generation and queues one build job; also the administrator's "update now" for it. */ public function continueReady(int $siteId, int $generationId): string { if ($this->control->isPaused($siteId, $generationId)) { return 'paused'; } try { $this->generations->reopen($siteId, $generationId); } catch (GenerationException) { return 'build_open'; // activated, ended or already reopened by a concurrent event: nothing to add } $balance = $this->ledger->balance($siteId, EmbeddingUsage::BUCKET); $this->jobs?->enqueueBuild($siteId, $generationId, 'sync-' . $this->clock->now() . '-' . bin2hex(random_bytes(3)), $balance['available'] ?? null); return 'reopened'; } }