initialize(); use think\facade\Db; try { $connection = (string) config('database.default'); $cfg = (array) config('database.connections.' . $connection); if (!in_array((string) ($cfg['hostname'] ?? ''), ['127.0.0.1', 'localhost', '::1'], true) || ($cfg['prefix'] ?? '') !== 'zyt_') { throw new RuntimeException('Local database/prefix guard rejected'); } $active = Db::name('prescription_ai_task')->whereIn('status', ['queued', 'running', 'retry_wait'])->count() + Db::name('prescription_ai_batch')->where('validity', 'current')->whereIn('status', ['preparing', 'waiting_sources', 'queued', 'running', 'retry_wait'])->count(); if ($active > 0) { throw new RuntimeException('Tasks must finish before this local upgrade'); } $snapshot = static function (): array { return [ 'batches' => Db::name('prescription_ai_batch')->count(), 'results' => Db::name('prescription_ai_result')->count(), 'attempts' => Db::name('prescription_ai_attempt')->count(), 'tasks_hash' => hash('sha256', json_encode(Db::name('prescription_ai_task')->field('id,status,attempts,total_attempts,manual_retries,result_id')->order('id')->select()->toArray())), ]; }; $before = $snapshot(); $present = isset(Db::name('prescription_ai_task')->getFields()['progress_json']); $sqlFile = $root . 'database/migrations/2026_09_10_prescription_ai_progress.sql'; $result = ['time' => date('c'), 'local_database' => true, 'column_previously_present' => $present, 'migration_sha256' => hash_file('sha256', $sqlFile), 'applied' => false]; if (in_array('--apply', $argv, true)) { Db::execute('SET SESSION lock_wait_timeout = 5'); foreach (explode(';', preg_replace('/^--.*$/m', '', file_get_contents($sqlFile))) as $statement) { if (trim($statement) !== '') { Db::execute($statement); } } $columns = Db::query("SHOW COLUMNS FROM `zyt_prescription_ai_task` LIKE 'progress_json'"); if (count($columns) !== 1 || $columns[0]['Type'] !== 'varchar(2048)' || $columns[0]['Null'] !== 'YES') { throw new RuntimeException('Unexpected progress column definition'); } $result['applied'] = true; $result['business_state_unchanged'] = $before === $snapshot(); if (!$result['business_state_unchanged']) { throw new RuntimeException('Business task state changed during upgrade'); } } echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL; } catch (Throwable $e) { echo json_encode(['error' => get_class($e), 'code' => $e->getCode()]), PHP_EOL; exit(1); }