'zoomzilla.ru','requisites'=>['one'],'custom'=>(object)['flag'=>true,'zero'=>0,'empty'=>(object)[]],'utm'=>(object)[]]; file_put_contents($root . '/site/config.json', json_encode($initial)); $store = new Store($root . '/state'); $target = $store->saveTarget(['id'=>'test','name'=>'Тест','domain'=>'test.invalid','environment'=>'stage','type'=>'local','root'=>$root . '/site','enabled'=>true]); $store->verifyTarget('test');$target=$store->target('test'); $transport = new Transport($store, $target); $before = $transport->readConfig(); $next = Config::patch($before['config'], [['path'=>['host'],'value'=>'example.org']]); check($next->custom->flag === true && $next->custom->zero === 0 && $next->custom->empty instanceof stdClass, 'selected patch preserves unrelated values and types'); $transport->writeConfig($before['version'], Config::encode($next)); $after = $transport->readConfig(); check($after['config']->host === 'example.org','atomic save and reread'); try { $transport->writeConfig($before['version'], Config::encode($initial)); throw new RuntimeException('missed conflict'); } catch (PrelandAdmin\Conflict $e) { check(true,'stale version conflicts'); } check(count($store->backups('test')) === 1,'private backup saved'); $jobs = new Jobs($store); $job = $jobs->create('apply', ['targets'=>[['id'=>'test','version'=>$after['version']],['id'=>'missing','version'=>'x']], 'changes'=>[['path'=>['custom','flag'],'value'=>false]]]); $jobs->run($job['id']); $job=$jobs->get($job['id']); check($job['results'][0]['status']==='success' && $job['results'][1]['status']==='error','one target failure does not undo successful target'); $restored=$transport->readConfig(); $backup=$store->backups('test')[0]; $job=$jobs->create('restore',['target'=>'test','id'=>$backup['id'],'version'=>$restored['version']]);$jobs->run($job['id']); check($jobs->get($job['id'])['results'][0]['status']==='success','backup restores with version protection'); try { $store->saveTarget(['id'=>'escape','type'=>'local','root'=>'/etc','domain'=>'a.invalid']); throw new RuntimeException('unsafe root accepted'); } catch (PrelandAdmin\Problem $e) {check(true,'local root confined to explicit mounts');} try { Config::patch($initial,[['path'=>['__proto__'],'value'=>1]]); throw new RuntimeException('unsafe key accepted'); } catch(PrelandAdmin\Problem $e) {check(true,'unsafe patch path rejected');} echo "Backend contract complete\n";