feat(data): add passive versioned raw capture independent of control
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../libs/NetzfahrplanV4Messaufnahme.php';
|
||||
use Belevo\EnelixEMS\NetzfahrplanV4Messaufnahme as Capture;
|
||||
|
||||
$n = 0;
|
||||
function ck(bool $ok, string $label): void { global $n; if (!$ok) throw new RuntimeException('FAIL ' . $label); $n++; echo 'PASS ' . $label . "\n"; }
|
||||
function refuses(callable $f, string $label): void { try { $f(); } catch (Throwable $e) { ck(true,$label); return; } throw new RuntimeException('Expected refusal: '.$label); }
|
||||
$now = 1790952000;
|
||||
$roles=['grid','pv','physical_storage','controlled_virtual','external_virtual'];
|
||||
$c=['version'=>1,'installationId'=>'e3a08f9e-af12-4695-99bd-8b51c0520021','managerId'=>17004,
|
||||
'reportedInventorySha256'=>str_repeat('a',64),'accounting'=>['version'=>1,'maxSkewSeconds'=>30,'splitToleranceW'=>100.,'sources'=>[]],
|
||||
'extraSources'=>[['key'=>'soc','variableId'=>106,'parentId'=>200,'ident'=>'soc','unit'=>'percent']]];
|
||||
$values=[3000,5000,2000,1500,500]; $readings=[];
|
||||
foreach($roles as $i=>$role){
|
||||
$key='s'.$i;$id=101+$i;
|
||||
$c['accounting']['sources'][]=['key'=>$key,'role'=>$role,'variableId'=>$id,'parentId'=>200,'ident'=>$key,'factorToW'=>1.,'maxAgeSeconds'=>60,'dependsOn'=>in_array($i,[3,4],true)?['s2']:[]];
|
||||
$readings[$id]=['value'=>$values[$i],'updated'=>$now,'changed'=>$now-30,'parentID'=>200,'ident'=>$key];
|
||||
}
|
||||
$readings[106]=['value'=>0.,'updated'=>$now,'changed'=>0,'parentID'=>200,'ident'=>'soc'];
|
||||
function take(array $c,array $r,int $now): array { return Capture::capture($c,static fn(int $id):array=>$r[$id],static fn():int=>$now); }
|
||||
$r=take($c,$readings,$now);
|
||||
ck($r['assessment']['baseLoadCandidateW']===6000.,'physical battery subtracted exactly once');
|
||||
ck($r['raw']['soc']['value']===0. && $r['raw']['soc']['sourceChangedAt']===0,'real zero and unchanged timestamp retained');
|
||||
ck(!$r['trainingEligible'] && !$r['controlEligible'] && !$r['measurementBoundaryVerified'],'algebra never enables training or control');
|
||||
ck($r['futureSdl']==='unknown_no_assumption','no fabricated future SDL');
|
||||
ck(count($r['raw'])===6,'raw extra sources retained separately');
|
||||
$a=$readings;$a[103]['updated']=$now-90;$a[104]['updated']=$now;
|
||||
$x=take($c,$a,$now);ck($x['assessment']['status']==='quality_hold','old physical measurement quarantines candidate');
|
||||
ck($x['raw']['s2']['sourceUpdatedAt']===$now-90,'original old timestamp preserved');
|
||||
ck(in_array('stale_origin:s3',$x['assessment']['issues'],true),'fresh virtual write cannot mask old dependency');
|
||||
$a=$readings;$a[101]['value']=null;$x=take($c,$a,$now);ck($x['raw']['s0']['value']===null && $x['assessment']===null,'missing source not zero');
|
||||
$a=$readings;$a[101]['value']='sensitive-test-text';$x=take($c,$a,$now);ck(!str_contains(json_encode($x),'sensitive-test-text'),'unexpected strings never logged');
|
||||
foreach([true,NAN,INF,1.0e20] as $v){$a=$readings;$a[101]['value']=$v;$x=take($c,$a,$now);ck($x['raw']['s0']['value']===null && is_string(json_encode($x,JSON_THROW_ON_ERROR)),'invalid numeric represented safely');}
|
||||
$a=$readings;$a[101]['parentID']=999;$x=take($c,$a,$now);ck($x['raw']['s0']['value']===null && $x['assessment']===null,'reused ID not recorded as correct measurement');
|
||||
$a=$readings;$a[102]['updated']=$now+1;$x=take($c,$a,$now);ck($x['assessment']===null && in_array('s1:future_source',$x['issues'],true),'future timestamp marked');
|
||||
$a=$readings;$a[101]['updated']=0;$x=take($c,$a,$now);ck($x['assessment']===null,'absent timestamp not invented');
|
||||
$counts=[];$x=Capture::capture($c,static function($id)use(&$counts,$readings){$r=$readings[$id];$counts[$id]=($counts[$id]??0)+1;if($id===101 && $counts[$id]===2)$r['value']++;return $r;},static fn()=>$now);
|
||||
ck($x['assessment']===null && $x['raw']['s0']['secondValue']===3001,'same-second concurrent change visible');
|
||||
$x=Capture::capture($c,static function($id)use($readings){if($id===101)throw new RuntimeException('do-not-emit-secret');return $readings[$id];},static fn()=>$now);
|
||||
ck(!str_contains(json_encode($x),'do-not-emit-secret') && $x['assessment']===null,'source errors bounded without secret messages');
|
||||
$a=$readings;$a[106]['value']=null;$x=take($c,$a,$now);ck($x['assessment']!==null && !$x['trainingEligible'],'optional SOC error does not destroy available raw powers');
|
||||
$a=$readings;$a[101]['value']=-10000;$x=take($c,$a,$now);ck($x['assessment']['baseLoadCandidateW']<0 && $x['assessment']['status']==='quality_hold','negative candidate not clipped');
|
||||
$a=$c;$a['extraSources'][]=$a['extraSources'][0];refuses(fn()=>take($a,$readings,$now),'duplicate source rejected');
|
||||
$a=$c;$a['accounting']['sources']=array_reverse($a['accounting']['sources']);ck(take($a,$readings,$now)['mappingSha256']===$r['mappingSha256'],'mapping identity stable');
|
||||
$t=0;refuses(fn()=>Capture::capture($c,static fn($id)=>$readings[$id],static function()use(&$t,$now){return $now-$t++;}),'backwards clock rejected');
|
||||
$base=sys_get_temp_dir().'/v4-raw-test-'.bin2hex(random_bytes(5));mkdir($base,0700);$dir=$base.'/data';mkdir($dir,0700);
|
||||
try{
|
||||
$file=Capture::append($dir,$r);$first=file_get_contents($file);Capture::append($dir,$r);
|
||||
ck(file_get_contents($file)===$first.$first,'append retains existing observations');
|
||||
ck(count(file($file))===2,'one record per line');
|
||||
ck((fileperms($file)&0777)===0640,'data permissions restricted');
|
||||
$old=file_get_contents($file);refuses(fn()=>Capture::append($dir,$r,1),'quota refuses without deleting data');ck(file_get_contents($file)===$old,'quota leaves journal unchanged');
|
||||
file_put_contents($file,'partial',FILE_APPEND);refuses(fn()=>Capture::append($dir,$r),'crash-truncated tail not silently extended');ck(str_ends_with(file_get_contents($file),'partial'),'incomplete record retained for review');
|
||||
unlink($file);file_put_contents($base.'/foreign','untouched');symlink($base.'/foreign',$file);refuses(fn()=>Capture::append($dir,$r),'journal symlink refused');ck(file_get_contents($base.'/foreign')==='untouched','unrelated file preserved');unlink($file);
|
||||
$lock=fopen($dir.'/.writer.lock','c+b');flock($lock,LOCK_EX);refuses(fn()=>Capture::append($dir,$r),'parallel writer refused');flock($lock,LOCK_UN);fclose($lock);
|
||||
$a=$r;$a['blob']=str_repeat('x',300000);refuses(fn()=>Capture::append($dir,$a),'oversized record refused');
|
||||
}finally{foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST)as$p){$p->isDir()&&!$p->isLink()?rmdir($p->getPathname()):unlink($p->getPathname());}rmdir($base);}
|
||||
$source=file_get_contents(__DIR__.'/../../libs/NetzfahrplanV4Messaufnahme.php');
|
||||
ck(!preg_match('/\b(?:IPS_|GetValue|SetValue|curl_|RequestAction|shell_exec|exec\s*\()/',$source),'capture core has no device or network calls');
|
||||
echo "TOTAL $n checks passed; synthetic values and temporary files only.\n";
|
||||
Reference in New Issue
Block a user