0, '1' => 1] || ($focus['default'] ?? null) !== '1') { $failures[] = 'Рабочий config.json должен фиксировать явно согласованный focus=face.'; } foreach (['indpic', 'theme', 'degr', 'reg', 'banch', 'cons', 'tp'] as $key) { $setting = $config['utm'][$key] ?? null; if (!is_array($setting) || !is_array($setting['weights'] ?? null) || !is_string($setting['default'] ?? null)) { $failures[] = "Рабочий config.json должен хранить валидный stage-снимок UTM {$key}."; } } } $workflow = (string) file_get_contents($root . '/.gitea/workflows/deploy.yml'); $stageStart = strpos($workflow, ' deploy-zoomstage:'); $stageBlock = $stageStart === false ? '' : substr($workflow, $stageStart); if ($stageBlock === '' || !str_contains($stageBlock, 'config\\.json$') || !str_contains($stageBlock, 'ensure-utm-config.php') || !str_contains($stageBlock, 'Migrate numeric UTM in preserved host config') || str_contains($stageBlock, 'php bin/ensure-focus-config.php') || str_contains($stageBlock, 'php bin/ensure-cons-config.php')) { $failures[] = 'Stage обязан сохранять хостовый config.json и согласованный focus-контракт.'; } if (preg_match_all('/^ [a-z][a-z0-9-]*:/m', substr($workflow, strpos($workflow, 'jobs:')), $jobs) !== 1 || !str_contains($workflow, " - staging") || str_contains($workflow, " - main") || str_contains($workflow, 'deploy-zoompixel-production:') || str_contains($workflow, 'barbariskatest')) { $failures[] = 'По решению владельца автодеплой должен содержать только staging/zoomstage.online.'; } if (is_file($root . '/config.json.example')) { $failures[] = 'config.json.example не должен существовать: рабочий config.json единственный.'; } if ($failures !== []) { fwrite(STDERR, "FAILED\n- " . implode("\n- ", $failures) . "\n"); exit(1); } fwrite(STDOUT, "OK: host config preserved; deploy targets stage only\n");