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

191 lines
17 KiB
PHP

<?php
declare(strict_types=1);
// Independent final integration boundary checks. Only synthetic temporary files
// and namespaced transport stubs; no framework, real GET, auth or model call.
namespace PrivateTcmR26 {
function curl_init(): object { ++$GLOBALS['icGets']; return (object) ['options' => []]; }
function curl_setopt_array(object $h, array $options): bool { $h->options = $options; $GLOBALS['icOptions'][] = $options; return true; }
function curl_exec(object $h): bool {
$id = basename($h->options[CURLOPT_URL]);
if (!isset($GLOBALS['icQueries'][$id])) throw new \RuntimeException('Undeclared stub run');
$inputs = $GLOBALS['icInputOverride'] ?? ['sys.query' => $GLOBALS['icQueries'][$id]];
if ($GLOBALS['icSerialized']) $inputs = json_encode($inputs, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$body = json_encode(['id' => $id, 'status' => 'running', 'inputs' => $inputs], JSON_THROW_ON_ERROR);
return ($h->options[CURLOPT_WRITEFUNCTION])($h, $body) === strlen($body);
}
function curl_errno(object $h): int { return 0; }
function curl_getinfo(object $h, int $which): int { return 200; }
function curl_close(object $h): void { ++$GLOBALS['icCloses']; }
}
namespace {
require dirname(__DIR__) . '/app/adminapi/service/AssistantRequestControl.php';
require dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/private_tcm_r26_wire_audit.php';
require dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/private_tcm_r26_run_input_inspector.php';
use app\adminapi\service\AssistantRequestControl as Control;
use PrivateTcmR26\RunInputInspector as Inspector;
$checks = 0; $failures = []; $serial = 0; $configs = 0;
$icGets = $icCloses = 0; $icOptions = $icQueries = []; $icSerialized = true; $icInputOverride = null;
$expect = static function (bool $ok, string $label) use (&$checks, &$failures): void { ++$checks; if (!$ok) $failures[] = $label; };
$json = static fn ($v): string => json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
$base = sys_get_temp_dir() . '/r26c-independent-reader-' . bin2hex(random_bytes(8));
$root = $base . '/server'; $audits = $root . '/runtime/tcm-r26-wire-audits'; $results = $base . '/app/artifacts/ai_concurrency';
mkdir($audits, 0700, true); mkdir($results, 0700, true);
$make = static function (array $modes = ['running'], string $exit = 'natural_eof') use (&$serial, $base, $audits, $results, $json, $expect): array {
$n = ++$serial; $uuid = sprintf('93333333-3333-4333-8333-%012d', $n); $directory = $base . '/control-' . $n;
$control = new Control(815, 11, $uuid, $directory); $expect($control->begin(), 'Fresh temporary control');
$path = $directory . '/' . hash('sha256', '815:11:' . $uuid) . '.json';
$audit = new PrivateTcmR26WireAudit($audits, $uuid, 'independent_qwen', 'qwen', $path);
$tokens = []; $callbacks = []; $pairs = [];
foreach ($modes as $i => $mode) {
$query = "独立合成 query\r\n无明显感染 😀 é " . $n . '-' . $i;
$wire = ['protocol' => 'dify', 'body_sha256' => hash('sha256', 'body:' . $query), 'body_bytes' => strlen('body:' . $query),
'query_sha256' => hash('sha256', $query), 'query_bytes' => strlen($query), 'inputs_sha256' => hash('sha256', '{}'),
'inputs_bytes' => 2, 'report_continuation_query' => $i > 0];
$id = $mode === 'unserialized' ? null : $audit->observe($wire); $callbacks[] = $id;
$token = $mode === 'never' ? '' : $control->beginSubmission('qwen', 'private-user-sentinel'); $tokens[] = $token;
if ($token !== '') { $audit->submitted($token); $audit->captureControl(); }
$task = ''; $run = '';
if (in_array($mode, ['terminal', 'running'], true)) {
$task = 'private-task-' . $n . '-' . $i; $run = 'private-run-' . $n . '-' . $i;
$id($task, ''); $control->bindTask('qwen', 'private-user-sentinel', $task, '', $token);
$id($task, $run); $control->bindTask('qwen', 'private-user-sentinel', $task, $run, $token);
$control->markTerminal($task, 'stopped', $run); $audit->captureControl($task);
$GLOBALS['icQueries'][$run] = $query;
}
$pairs[] = [$task, $run];
$audit->transferClosedCurrent(['transport_closed_ms' => 1, 'transport_exit' => $exit], 5.0 + $i * 10);
$disposition = match ($mode) { 'rejected' => 'rejected_before_execution', 'never' => 'not_submitted', default => 'unconfirmed' };
if ($token !== '') $control->closeSubmittedGeneration($token, $task, $disposition,
static function (array $identity, int $ms) use ($mode, $expect): array {
$expect($ms > 0 && $ms <= 1000, 'Actual Control finite lookup cap');
return ['ok' => true, 'workflow_run_id' => $identity['workflow_run_id'], 'status' => $mode === 'terminal' ? 'stopped' : 'running'];
});
$audit->submissionClosed($token, $task, $disposition, 6.0 + $i * 10); $audit->captureControl();
if ($i + 1 < count($modes)) $expect($control->canStartAttempt(), 'Only resolved predecessor permits fixture successor');
}
$control->finish('done'); $audit->captureControl(); $state = $control->publicState(); $summary = $audit->finish($state);
$row = ['harness_contract' => 'v43r26_private_run_wire', 'synthetic' => true, 'preparation_isolated' => true,
'production_acceptance_path' => true, 'preparation_isolation_verified' => true, 'diagnostic_only' => false, 'case_id' => 11,
'test_label' => 'independent', 'profile' => 'qwen', 'production_hashes' => [
'fixture' => 'b54c47ec11f6a99dc87bdda78629e18c8a6f37a64017e42a72db399c727cb1c6',
'question' => '28fbaee8585d4fbde3be3b1a6ee046c1a60f18a1fe67c0769211b08afdd5d306'],
'private_wire_audit' => $summary, 'private_wire_audit_verified' => true, 'cleanup_independently_confirmed' => $summary['independent_cleanup_confirmed'],
'state' => $state, 'terminal_recovery_after_state' => $state];
$document = ['harness_contract' => 'v43r26_private_run_wire', 'synthetic_only' => true, 'preparation_isolated' => true,
'fixture_sha256' => $row['production_hashes']['fixture'], 'canonical_question_sha256' => $row['production_hashes']['question'], 'rows' => [$row]];
$result = $results . '/tcm_complete_independent_' . $n . '.safe.json'; file_put_contents($result, $json($document));
return ['audit' => $audits . '/' . hash('sha256', $uuid) . '.private.jsonl', 'control' => $path,
'result' => $result, 'document' => $document, 'summary' => $summary, 'tokens' => $tokens, 'pairs' => $pairs];
};
$inspect = static function (array $f) use ($root, &$configs): array {
return Inspector::inspectResultRow($root, ['result_file' => basename($f['result']), 'test_label' => 'independent', 'profile' => 'qwen',
'audit_sha256' => $f['summary']['audit_sha256']], static function () use (&$configs): array {
++$configs; return ['enable' => true, 'base_url' => 'http://synthetic.invalid:8088/v1/chat-messages', 'models' => ['qwen' => ['api_key' => 'private-key-sentinel']]];
});
};
$mutate = static function (array $f, callable $change) use ($json): array {
$rows = array_map(static fn ($l) => json_decode($l, true, 64, JSON_THROW_ON_ERROR), file($f['audit'], FILE_IGNORE_NEW_LINES));
$change($rows, $f);
$raw = implode("\n", array_map($json, $rows)) . "\n"; file_put_contents($f['audit'], $raw);
$f['summary']['audit_bytes'] = strlen($raw); $f['summary']['audit_sha256'] = hash('sha256', $raw);
$f['document']['rows'][0]['private_wire_audit'] = $f['summary']; file_put_contents($f['result'], $json($f['document'])); return $f;
};
$refuse = static function (array $f, string $label) use ($inspect, $expect, &$configs, &$icGets): void {
$before = [$configs, $icGets]; $out = $inspect($f);
$expect(!$out['ok'] && [$configs, $icGets] === $before, 'Fail before config/GET: ' . $label);
};
try {
foreach (['natural_eof', 'timeout', 'local_abort', 'transport_error', 'exception'] as $exit) {
$f = $make(['running'], $exit); $s = $f['summary']; $life = $s['generations'][0]['lifecycle'];
$expect($s['persisted'] && $s['lifecycle_consistent'] && $s['pending_generation_count'] === 1, 'Actual pending accounting');
$expect(abs($life['verification_started_at'] - $life['transfer_closed_at']) > 1000, 'Real wall and monotonic origins differ');
$expect($s['generations'][0]['transfer_exit'] === $exit, 'Actual transport_exit retained: ' . $exit);
$saved = json_decode(file_get_contents($f['result']), true, 64, JSON_THROW_ON_ERROR);
foreach (array_merge($f['tokens'], $f['pairs'][0], ['private-user-sentinel', 'submission_id', '93333333-']) as $private)
$expect(!str_contains($json([$s, $saved['rows'][0]['state']]), $private), 'Actual safe summary and Control publicState exclude identities');
$before = []; foreach (['audit', 'control', 'result'] as $k) $before[$k] = hash_file('sha256', $f[$k]);
foreach ([true, false] as $encoded) {
$icSerialized = $encoded; $out = $inspect($f);
$expect($out['ok'] && $out['runs'][0]['run_status'] === 'running' && $out['saved_pending_generation_count'] === 1, 'Pending diagnostic via exact object/serialized inputs');
$expect(!$out['saved_independent_cleanup_confirmed'] && !$out['authoritative_control_mutated'], 'Fresh GET never clears saved pending');
foreach (array_merge($f['tokens'], $f['pairs'][0], ['private-user-sentinel', 'private-key-sentinel', '93333333-', '独立合成', 'submission_id', 'http://']) as $private)
$expect(!str_contains($json($out), $private), 'Private identity/token/query/config absent in safe result');
}
foreach ($before as $k => $hash) $expect(hash_file('sha256', $f[$k]) === $hash, 'Reader preserves ' . $k);
}
$f = $make(['rejected', 'terminal']); $out = $inspect($f);
$expect($out['ok'] && $out['inspected_run_count'] === 1 && $out['runs'][0]['generation'] === 2, 'Only exact admitted fallback is selected');
$expect($f['summary']['no_execution_generation_count'] === 1 && $f['summary']['independently_terminal_generation_count'] === 1, 'No-execution is separate from terminal');
$f = $make(['terminal', 'running']); $out = $inspect($f);
$expect($out['ok'] && $out['inspected_run_count'] === 2 && $out['saved_pending_generation_count'] === 1, 'Both own generations retain separate original pairs and queries');
foreach (['unknown', 'unserialized', 'never', 'rejected'] as $mode) $refuse($make([$mode]), $mode);
foreach (['token_swap', 'foreign_pair', 'missing_admission', 'foreign_source', 'closure_missing', 'raw_exit', 'wrong_schema', 'count', 'foreign_row', 'foreign_header', 'raw_field', 'wrong_clock', 'elapsed', 'budget'] as $kind) {
$f = $make(['terminal', 'running']);
$f = $mutate($f, static function (array &$rows, array &$f) use ($kind): void {
if ($kind === 'count') { ++$f['summary']['submitted_pending_attempt_count']; return; }
if ($kind === 'foreign_row') { $f['document']['rows'][0]['profile'] = 'openai'; return; }
if ($kind === 'foreign_header') { $rows[0]['request_label'] = 'foreign_qwen'; return; }
if ($kind === 'wrong_schema') { $rows[0]['audit_schema'] = 'v43r26_private_run_wire'; return; }
if ($kind === 'missing_admission' || $kind === 'closure_missing') {
$event = $kind === 'missing_admission' ? 'generation_submission' : 'generation_transfer_closed';
foreach ($rows as $i => $r) if ($r['event'] === $event && $r['generation'] === 2) { array_splice($rows, $i, 1); break; } return;
}
foreach ($rows as &$r) {
if (($r['generation'] ?? null) !== 2) continue;
if ($kind === 'raw_exit') {
if ($r['event'] === 'generation_transfer_closed') { $r['transfer_exit'] = 'private-key-sentinel'; break; }
continue;
}
if ($r['event'] !== 'generation_lifecycle') continue;
if ($kind === 'token_swap') $r['submission_id'] = $f['tokens'][0];
if ($kind === 'foreign_pair') $r['original_workflow_run_id'] = $f['pairs'][0][1];
if ($kind === 'foreign_source') $r['source'] = 'neighbor_control';
if ($kind === 'raw_field') $r['state']['submission_id'] = $f['tokens'][1];
if ($kind === 'wrong_clock') $r['state']['clock_domains']['verification_started_at'] = 'unix_seconds_microtime';
if ($kind === 'elapsed') $r['state']['verification_elapsed_ms'] = -1;
if ($kind === 'budget') { $r['state']['independently_terminal'] = true; $r['state']['verification_elapsed_ms'] = 2001; }
break;
} unset($r);
}); $refuse($f, $kind);
}
$f = $make(['running']); $query = $icQueries[$f['pairs'][0][1]]; $icSerialized = true;
foreach ([['sys.query' => $query, 'query' => $query . ' '], ['sys.query' => ['query' => $query]], ['nested' => ['query' => $query]]] as $inputs) {
$icInputOverride = $inputs; $out = $inspect($f); $expect(!$out['ok'] && $out['runs'][0]['returned_run_id_matches'], 'Input negatives preserve exact run identity without matching query');
} $icInputOverride = null;
foreach ($icOptions as $options) $expect($options[CURLOPT_PROTOCOLS] === CURLPROTO_HTTP && $options[CURLOPT_REDIR_PROTOCOLS] === CURLPROTO_HTTP
&& !$options[CURLOPT_FOLLOWLOCATION] && $options[CURLOPT_PROXY] === '' && $options[CURLOPT_SSL_VERIFYPEER] && $options[CURLOPT_SSL_VERIFYHOST] === 2
&& $options[CURLOPT_TIMEOUT_MS] > 0 && $options[CURLOPT_TIMEOUT_MS] <= 2000, 'Actual configured HTTP with TLS checks/no fallback/proxy/redirect and finite cap');
$directory = $base . '/lock-control'; $uuid = '94444444-4444-4444-8444-444444444444';
$c = new Control(815, 11, $uuid, $directory); $expect($c->begin(), 'Contention control starts');
$path = $directory . '/' . hash('sha256', '815:11:' . $uuid) . '.json'; $before = hash_file('sha256', $path);
$audit = new PrivateTcmR26WireAudit($audits, $uuid, 'lock_qwen', 'qwen', $path);
$code = '$h=fopen($argv[1],"rb");flock($h,LOCK_EX);echo "locked\n";fflush(STDOUT);usleep(350000);flock($h,LOCK_UN);fclose($h);';
$child = 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($child)) throw new RuntimeException('Contention child unavailable');
$expect(trim(fgets($pipes[1])) === 'locked', 'Child owns actual exclusive lock');
$start = hrtime(true); $audit->captureControl(); $lockMs = (hrtime(true) - $start) / 1e6;
foreach ($pipes as $pipe) fclose($pipe); $childExit = proc_close($child);
$summary = $audit->finish($c->publicState());
$expect($childExit === 0 && $lockMs < 150 && !$summary['persisted'] && !$summary['independent_cleanup_confirmed'], 'Optional observer contention promptly refuses certification');
$expect(hash_file('sha256', $path) === $before, 'Contention leaves control untouched');
$expect($icGets === $icCloses, 'Every declared stub handle closed');
foreach (['think\\App', 'think\\facade\\Db', 'think\\facade\\Config'] as $class) $expect(!class_exists($class, false), 'Framework remains unloaded');
echo $json(['checks' => $checks, 'failed' => count($failures), 'failures' => $failures, 'stub_gets' => $icGets,
'stub_handles_closed' => $icCloses, 'actual_lock_capture_ms' => $lockMs, 'lock_child_exit' => $childExit,
'real_network_auth_bootstrap_config_runtime_model_calls' => 0]) . "\n";
} finally {
$real = realpath($base); $temp = realpath(sys_get_temp_dir());
if ($real !== false) {
if ($temp === false || !str_starts_with(str_replace('\\', '/', $real), rtrim(str_replace('\\', '/', $temp), '/') . '/r26c-independent-reader-')) throw new RuntimeException('Cleanup boundary');
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) { if ($file->isLink()) throw new RuntimeException('Unexpected link'); $file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname()); } rmdir($real);
}
}
exit($failures === [] ? 0 : 1);
}