invoke(null,$valid); $target=Config::decode('{"host":"before.example","notes":"keep","utm":{"part":{"default":"1","weights":{"1":1},"metadata":"keep"},"unknown":{"keep":true}}}'); $patched=Config::patch($target,$changes); partCheck($patched->utm->part->default==='2'&&$patched->utm->part->weights->{'2'}===100,'copy includes part default and weights'); partCheck($patched->host==='before.example'&&$patched->notes==='keep'&&$patched->utm->part->metadata==='keep'&&$patched->utm->unknown->keep===true,'copy preserves metadata and unknown groups'); $root=sys_get_temp_dir().'/part347-'.bin2hex(random_bytes(6));$store=new Store($root); $notes=new Notes($store);$old=$notes->all();unset($old['part']);$store->write('utm-notes.json',$old); $loaded=$notes->all();partCheck(isset($loaded['part'])&&array_diff_key($loaded,['part'=>true])===$old,'old notes gain empty part without losing notes/versions'); $notes->save(['marker'=>'part','text'=>'Видимость ссылки на сайте','version'=>$loaded['part']['version']]); partCheck($notes->all()['part']['text']==='Видимость ссылки на сайте','part note saves with CAS'); $store->write('targets.json',['fixture'=>['id'=>'fixture','domain'=>'fixture.example']]); $oldSelection=['indpic'=>'1','theme'=>'1','degr'=>'1','reg'=>'2','banch'=>'2','tp'=>'1','cons'=>'2','focus'=>'1']; $params=[];foreach($oldSelection as $key=>$value)$params[$key]='abcd'.$value.'abcdefabcdef'; $store->write('link-builder-fixture.json',['selection'=>$oldSelection,'url'=>'https://fixture.example/?'.http_build_query($params),'saved_at'=>'2026-10-28T01:00:00+00:00']); $builder=new LinkBuilder($store);$saved=$builder->get('fixture'); partCheck($saved['selection']===$oldSelection+['part'=>'1'],'old saved builder choice gains default1'); parse_str(parse_url($saved['url'],PHP_URL_QUERY),$newParams); partCheck(array_intersect_key($newParams,$params)===$params&&$newParams['part'][4]==='1','migration preserves old noise and adds masked part'); partCheck($builder->get('fixture')===$saved,'migrated builder reload preserves exact noise'); foreach(['1','2','rnd'] as $part)partCheck($builder->save('fixture',array_replace($saved['selection'],['part'=>$part]))['selection']['part']===$part,'builder supports part '.$part); try{$builder->save('fixture',array_replace($saved['selection'],['part'=>'3']));throw new RuntimeException('invalid builder part accepted');}catch(Problem){} echo "Part admin contract complete\n";