Files
zyt/server/tests/PrivateTcmR26HarnessIndependentTest.php
T
2026-10-27 14:03:35 +08:00

112 lines
9.7 KiB
PHP

<?php
declare(strict_types=1);
// Pure private audit helper review. No production class/autoloader is loaded.
require dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/private_tcm_r26_wire_audit.php';
$checks = 0; $failures = []; $observations = []; $number = 0;
$expect = static function (bool $ok, string $label) use (&$checks, &$failures): void { ++$checks; if (!$ok) $failures[] = $label; };
$base = sys_get_temp_dir() . '/r26-independent-audit-' . bin2hex(random_bytes(8));
mkdir($base, 0700, true);
$json = static fn (mixed $value): string => json_encode($value, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
$wire = static fn (bool $continuation = false): array => ['protocol' => 'dify', 'body_sha256' => hash('sha256', 'private-body'), 'body_bytes' => 12,
'query_sha256' => hash('sha256', 'private-query'), 'query_bytes' => 13, 'inputs_sha256' => hash('sha256', '{}'), 'inputs_bytes' => 2,
'report_continuation_query' => $continuation];
$record = static function (string $task, string $run, int $generation, string $status = 'stopped'): array {
return ['upstream' => ['task_id' => $task, 'workflow_run_id' => $run, 'profile' => 'qwen', 'user' => 'private-user', 'generation' => $generation],
'producer_terminal_status' => 'stopped', 'producer_terminal_observed_at' => 10.0,
'persisted_run_status' => $status, 'persisted_run_observed_at' => 12.0,
'persisted_terminal_confirmed' => $status !== 'running', 'upstream_terminal_confirmed' => $status !== 'running',
'upstream_stop_confirmed' => true, 'stop_accepted_at' => 9.0, 'transfer_closed' => true, 'transfer_closed_at' => 11.0,
'verification_attempts' => 1, 'verification_started_at' => 11.0, 'generation_verification_elapsed_ms' => 20,
'lifecycle_conflict' => $status !== 'stopped',
'run_status_observations' => [['status' => $status, 'observed_at' => 12.0, 'within_budget' => true]]];
};
$finishState = static fn (int $pending, bool $terminal): array => ['transport_closed' => true, 'upstream_terminal_confirmed' => $terminal,
'upstream_stop_confirmed' => true, 'cancel_requested' => false, 'timeout_reached' => false,
'lifecycle_conflict' => $pending > 0, 'upstream_pending_task_count' => $pending];
$make = static function () use ($base, &$number): array {
$uuid = sprintf('d5678901-1234-4234-8234-%012d', ++$number);
$control = $base . '/control-' . $number . '.json'; file_put_contents($control, '{}');
return [new PrivateTcmR26WireAudit($base . '/audits', $uuid, 'independent_case_' . $number, 'qwen', $control),
$control, $base . '/audits/' . hash('sha256', $uuid) . '.private.jsonl'];
};
$private = static function (array $summary) use ($expect, $json): void {
foreach (['private-task', 'private-run', 'private-user', 'd5678901-', 'private-body', 'private-query'] as $text)
$expect(!str_contains($json($summary), $text), 'safe summary excludes raw identity/body sentinel');
};
try {
// The preserved real transport contract calls this field transport_exit.
// Do not use the helper's own spelling as the expected fixture.
foreach (['natural_eof', 'timeout', 'local_abort', 'transport_error', 'exception'] as $exit) {
[$a, $path, $auditPath] = $make(); $identity = $a->observe($wire()); $identity('private-task', 'private-run');
$a->transferClosed(1, ['transport_closed_ms' => 5, 'transport_exit' => $exit], 20.0);
file_put_contents($path, $json(['completed_generations' => [7 => $record('private-task', 'private-run', 7)]]));
$a->captureControl('private-task'); $summary = $a->finish($finishState(0, true));
$expect($summary['persisted'] && $summary['independent_cleanup_confirmed'], 'valid exact private archive certifies only terminal generation');
$expect($summary['generations'][0]['transfer_exit'] === $exit, 'real transport exit enum retained ' . $exit);
$rows = array_map(static fn (string $line): array => json_decode($line, true, 64, JSON_THROW_ON_ERROR), file($auditPath, FILE_IGNORE_NEW_LINES));
$closed = array_values(array_filter($rows, static fn (array $row): bool => $row['event'] === 'generation_transfer_closed'));
$expect($closed[0]['transfer_exit'] === $exit, 'private closure row retains real transport exit enum ' . $exit);
$expect(hash_file('sha256', $auditPath) === $summary['audit_sha256'], 'durable digest includes exact closure and footer');
$private($summary);
$observations[] = ['label' => 'transport_exit_' . $exit, 'expected' => $exit, 'actual' => $summary['generations'][0]['transfer_exit']];
}
[$a, $path, $auditPath] = $make();
$first = $a->observe($wire()); $second = $a->observe($wire(true));
$second('private-task-2', 'private-run-2'); $first('private-task-1'); $first('private-task-1', 'private-run-1');
$a->transferClosed(1, ['transport_closed_ms' => 2, 'transport_exit' => 'natural_eof'], 10.0);
$a->transferClosed(2, ['transport_closed_ms' => 3, 'transport_exit' => 'natural_eof'], 30.0);
file_put_contents($path, $json($record('private-task-2', 'private-run-2', 9, 'running')
+ ['completed_generations' => [8 => $record('private-task-1', 'private-run-1', 8)]]));
$a->captureControl('private-task-2'); $a->captureControl('private-task-1'); $a->captureControl();
$summary = $a->finish($finishState(1, false));
$expect($summary['persisted'] && $summary['lifecycle_consistent'] && !$summary['independent_cleanup_confirmed'], 'durable two-generation pending evidence is not independent cleanup');
$expect($summary['independently_terminal_generation_count'] === 1 && $summary['pending_generation_count'] === 1
&& array_column($summary['generations'], 'generation') === [1, 2], 'direct identity closures never substitute last task or control ordinal');
$expect($summary['generations'][0]['lifecycle']['persisted_run_status'] === 'stopped'
&& $summary['generations'][1]['lifecycle']['persisted_run_status'] === 'running', 'archive and active original pair map to their own wire generation');
$expect($a->finish($finishState(0, true)) === $summary, 'finished audit and pending footer immutable');
$private($summary);
foreach (['unknown', 'task_only', 'foreign_control', 'false_footer', 'oversize_control', 'malformed_control'] as $kind) {
[$a, $path, $auditPath] = $make(); $identity = $a->observe($wire());
if ($kind !== 'unknown') $identity('private-task', $kind === 'task_only' ? '' : 'private-run');
$a->transferClosed(1, ['transport_closed_ms' => 3, 'transport_exit' => 'natural_eof'], 20.0);
$r = $record('private-task', 'private-run', 1, 'running');
if ($kind === 'foreign_control') $r['upstream']['workflow_run_id'] = 'foreign-private-run';
file_put_contents($path, $kind === 'oversize_control' ? str_repeat('x', 1048577) : ($kind === 'malformed_control' ? '{' : $json($r)));
$a->captureControl(); $summary = $a->finish($finishState($kind === 'unknown' ? 0 : 1, $kind === 'false_footer'));
$expect(!$summary['independent_cleanup_confirmed'] && $summary['pending_generation_count'] === 1, 'unknown/foreign/malformed evidence cannot certify terminal ' . $kind);
if (in_array($kind, ['oversize_control', 'malformed_control'], true)) $expect(!$summary['persisted'], 'bounded control parser fails audit statically ' . $kind);
else $expect($summary['persisted'], 'unconfirmed valid private evidence remains durable ' . $kind);
$private($summary);
}
// An optional audit observer must not wait indefinitely on a peer's control
// lock. The child holds only this freshly created synthetic file, then exits.
[$a, $path, $auditPath] = $make();
$code = '$h=fopen($argv[1],"rb");flock($h,LOCK_EX);echo "locked\\n";fflush(STDOUT);usleep(350000);flock($h,LOCK_UN);fclose($h);';
$pipes = [];
$process = proc_open([PHP_BINARY, '-r', $code, $path], [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, null, ['bypass_shell' => true]);
if (!is_resource($process)) throw new RuntimeException('Synthetic lock child unavailable');
fclose($pipes[0]); $ready = fgets($pipes[1]);
$before = hrtime(true); $a->captureControl(); $elapsed = (hrtime(true) - $before) / 1e6;
$stderr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); $childExit = proc_close($process);
$expect($ready === "locked\n" && $stderr === '' && $childExit === 0, 'synthetic lock child completed and joined');
$expect($elapsed < 100.0, 'optional control audit read does not block on an exclusively held peer file lock');
$a->finish($finishState(0, false));
$observations[] = ['label' => 'exclusive_control_lock', 'capture_elapsed_ms' => $elapsed, 'child_exit' => $childExit];
$expect(!class_exists('think\\App', false) && !class_exists('app\\adminapi\\service\\AssistantRequestControl', false), 'pure helper review never imports moving production or framework');
echo $json(['scope' => 'independent immutable r26 audit helper review', 'assertions' => $checks, 'failures' => $failures,
'observations' => $observations, 'real_network_calls' => 0, 'real_model_calls' => 0]), "\n";
} finally {
$real = realpath($base); $temp = realpath(sys_get_temp_dir());
if ($real === false || $temp === false || !str_starts_with(str_replace('\\', '/', $real), rtrim(str_replace('\\', '/', $temp), '/') . '/r26-independent-audit-')) throw new RuntimeException('Synthetic cleanup boundary');
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iterator as $entry) { if ($entry->isLink()) throw new RuntimeException('Synthetic cleanup symlink'); $entry->isDir() ? rmdir($entry->getPathname()) : unlink($entry->getPathname()); }
rmdir($real);
}
exit($failures === [] ? 0 : 1);