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

309 lines
27 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
// NEW independent r25b derivative review; retains the original independent
// boundary controls and adds serialized-input/source-equivalence controls.
// In-process GET/config/clock stubs and handcrafted synthetic audit fixtures.
// Do not load the probe, application, original credentials or runtime audits.
namespace PrivateTcmR25b {
function hrtime(bool $number = false): int { return $GLOBALS['iiClock']; }
function curl_init(): object {
++$GLOBALS['iiCreated'];
$scenario = array_shift($GLOBALS['iiScenarios']);
if (!is_array($scenario)) throw new \RuntimeException('Unexpected synthetic GET');
return (object) ['scenario' => $scenario, 'options' => [], 'errno' => 0];
}
function curl_setopt_array(object $handle, array $options): bool {
$handle->options = $options; $GLOBALS['iiOptions'][] = $options;
$GLOBALS['iiClock'] += $handle->scenario['setup_ns'] ?? 0;
return ($handle->scenario['set_ok'] ?? true) === true;
}
function curl_exec(object $handle): bool {
++$GLOBALS['iiExecuted'];
$GLOBALS['iiClock'] += $handle->scenario['advance_ns'] ?? 0;
if ($handle->scenario['throw'] ?? false) throw new \RuntimeException('private-independent-provider-error');
foreach ($handle->scenario['chunks'] ?? [$handle->scenario['body']] as $chunk) {
if (($handle->options[CURLOPT_WRITEFUNCTION])($handle, $chunk) !== strlen($chunk)) {
$handle->errno = CURLE_WRITE_ERROR; return false;
}
}
return true;
}
function curl_errno(object $handle): int { return $handle->errno; }
function curl_getinfo(object $handle, int $option): int { return $handle->scenario['status'] ?? 200; }
function curl_close(object $handle): void {
++$GLOBALS['iiClosed'];
$GLOBALS['iiClock'] += $handle->scenario['close_ns'] ?? 0;
}
}
namespace {
require dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/private_tcm_r25b_run_input_inspector.php';
use PrivateTcmR25b\RunInputInspector as Inspector;
$checks = 0; $iiClock = 10000000000; $iiCreated = 0; $iiClosed = 0; $iiExecuted = 0; $iiScenarios = []; $iiOptions = [];
$expect = static function (bool $value, string $label) use (&$checks): void { ++$checks; if (!$value) throw new RuntimeException($label); };
$json = static fn (mixed $value): string => json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
$base = sys_get_temp_dir() . '/tcm-r25b-inspector-independent-' . bin2hex(random_bytes(8));
$root = $base . '/server'; $auditDir = $root . '/runtime/tcm-r25-wire-audits'; $resultDir = $base . '/app/artifacts/ai_concurrency';
mkdir($auditDir, 0700, true); mkdir($resultDir, 0700, true);
$number = 0;
$query = "independent-private-query/中文\nsecond-line\r\n";
$config = ['enable' => true, 'base_url' => 'http://independent.synthetic.invalid:8088/v1/chat-messages',
'models' => ['qwen' => ['api_key' => 'private-independent-qwen-key']]];
$fixtureHash = 'b54c47ec11f6a99dc87bdda78629e18c8a6f37a64017e42a72db399c727cb1c6';
$questionHash = '28fbaee8585d4fbde3be3b1a6ee046c1a60f18a1fe67c0769211b08afdd5d306';
$make = static function (int $count = 1) use (&$number, $query, $fixtureHash, $questionHash): array {
$id = sprintf('44444444-4444-4444-8444-%012d', ++$number);
$records = [['event' => 'request', 'contract' => 'v43r25_private_run_wire', 'request_id' => $id,
'request_label' => 'independent_row_qwen', 'profile' => 'qwen']];
for ($i = 1; $i <= $count; ++$i) {
$body = json_encode(['inputs' => (object) [], 'query' => $query], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$records[] = ['event' => 'serialized_request', 'generation' => $i, 'report_stage' => $i === 1 ? 1 : 2,
'stage_attempt' => $i <= 2 ? 1 : $i - 1, 'protocol' => 'dify', 'body_sha256' => hash('sha256', $body), 'body_bytes' => strlen($body),
'query_sha256' => hash('sha256', $query), 'query_bytes' => strlen($query),
'inputs_sha256' => hash('sha256', '{}'), 'inputs_bytes' => 2, 'report_continuation_query' => $i > 1];
$records[] = ['event' => 'original_identity', 'generation' => $i, 'original_task_id' => 'private-independent-task-' . $i, 'original_workflow_run_id' => ''];
$records[] = ['event' => 'original_identity', 'generation' => $i, 'original_task_id' => 'private-independent-task-' . $i, 'original_workflow_run_id' => 'private-independent-run-' . $i];
}
$records[] = ['event' => 'after_control_cleanup', 'state' => ['transport_closed' => true, 'upstream_terminal_confirmed' => true,
'upstream_stop_confirmed' => false, 'cancel_requested' => false, 'timeout_reached' => false, 'upstream_pending_task_count' => 0]];
return ['records' => $records, 'summary' => ['persisted' => true, 'serialized_request_count' => $count,
'original_task_count' => $count, 'original_run_count' => $count], 'id' => $id, 'number' => $number,
'fixture' => $fixtureHash, 'question' => $questionHash];
};
$save = static function (array $fixture) use ($auditDir, $resultDir, $json): array {
$content = implode("\n", array_map($json, $fixture['records'])) . "\n";
$summary = $fixture['summary'] + ['audit_bytes' => strlen($content), 'audit_sha256' => hash('sha256', $content)];
$auditPath = $auditDir . '/' . hash('sha256', $fixture['id']) . '.private.jsonl';
file_put_contents($auditPath, $content);
$row = ['test_label' => 'independent_row', 'profile' => 'qwen', 'private_wire_audit' => $summary,
'harness_contract' => 'v43r25_private_run_wire', 'synthetic' => true, 'preparation_isolated' => true,
'preparation_isolation_verified' => true, 'production_acceptance_path' => true, 'diagnostic_only' => false, 'case_id' => 11,
'production_hashes' => ['fixture' => $fixture['fixture'], 'question' => $fixture['question']]];
$document = ['harness_contract' => 'v43r25_private_run_wire', 'synthetic_only' => true, 'preparation_isolated' => true,
'fixture_sha256' => $fixture['fixture'], 'canonical_question_sha256' => $fixture['question'], 'rows' => [$row]];
$resultFile = 'tcm_complete_independent_inspector_' . $fixture['number'] . '.safe.json';
$resultPath = $resultDir . '/' . $resultFile; file_put_contents($resultPath, $json($document));
return [['result_file' => $resultFile, 'test_label' => 'independent_row', 'profile' => 'qwen', 'audit_sha256' => $summary['audit_sha256']], $auditPath, $resultPath];
};
$response = static fn (int $i = 1): array => ['id' => 'private-independent-run-' . $i, 'status' => 'succeeded',
'inputs' => ['sys.query' => $query], 'outputs' => ['secret' => 'private-independent-output']];
$call = static function (array $selector, array $scenarios, ?array $override = null) use ($root, $config): array {
$GLOBALS['iiScenarios'] = $scenarios; $configCalls = 0;
$value = Inspector::inspectResultRow($root, $selector, static function () use (&$configCalls, $config, $override): array { ++$configCalls; return $override ?? $config; });
return [$value, $configCalls];
};
$privacy = static function (array $value) use ($expect, $json, $query): void {
$text = $json($value);
foreach (['private-independent-', 'independent.synthetic', 'independent_row', '44444444-', $query] as $secret)
$expect(!str_contains($text, $secret), 'safe reply excludes private identity/body/config/selector text');
$expect(($value['model_calls'] ?? null) === 0 && ($value['llm_expanded_prompt_observed'] ?? null) === false, 'query evidence never becomes model/node visibility');
};
try {
[$selected, , $resultPath] = $save($make(2));
[$ok, $configured] = $call($selected, [['body' => $json($response())], ['body' => $json($response(2))]]);
$expect($ok['ok'] && $configured === 1 && $ok['inspected_run_count'] === 2 && $ok['matched_run_count'] === 2, 'full saved-row bridge selects exactly two original runs');
$expect($ok['result_file_sha256'] === hash_file('sha256', $resultPath) && $ok['audit_sha256'] === $selected['audit_sha256'], 'whole result and audit digests retained');
$privacy($ok);
foreach ($iiOptions as $options) {
$expect($options[CURLOPT_HTTPGET] === true && !isset($options[CURLOPT_POSTFIELDS]) && !isset($options[CURLOPT_POST]), 'GET only');
$expect($options[CURLOPT_PROTOCOLS] === CURLPROTO_HTTP && $options[CURLOPT_REDIR_PROTOCOLS] === CURLPROTO_HTTP
&& str_starts_with($options[CURLOPT_URL], 'http://independent.synthetic.invalid:8088/v1/workflows/run/'), 'exact configured HTTP scheme/host/port/path only');
$expect($options[CURLOPT_TIMEOUT_MS] > 0 && $options[CURLOPT_TIMEOUT_MS] <= 2000 && $options[CURLOPT_CONNECTTIMEOUT_MS] <= 2000
&& !$options[CURLOPT_FOLLOWLOCATION] && $options[CURLOPT_MAXREDIRS] === 0, 'bounded deadline and redirects disabled');
}
foreach (['open_transport', 'no_terminal', 'pending', 'pending_string', 'extra_footer', 'header_upper_uuid', 'header_neighbor_label',
'identity_downgrade', 'identity_other_task', 'identity_before_serialization', 'wrong_query_hash_type', 'stage_backwards', 'non_dify',
'summary_bool_count', 'summary_byte_mismatch', 'summary_run_mismatch', 'too_many_runs'] as $kind) {
$fixture = $make(in_array($kind, ['stage_backwards', 'too_many_runs'], true) ? 3 : 1);
$last = count($fixture['records']) - 1;
if ($kind === 'open_transport') $fixture['records'][$last]['state']['transport_closed'] = false;
if ($kind === 'no_terminal') $fixture['records'][$last]['state']['upstream_terminal_confirmed'] = false;
if ($kind === 'pending') $fixture['records'][$last]['state']['upstream_pending_task_count'] = 1;
if ($kind === 'pending_string') $fixture['records'][$last]['state']['upstream_pending_task_count'] = '0';
if ($kind === 'extra_footer') $fixture['records'][$last]['state']['raw_body'] = 'private-independent-injection';
if ($kind === 'header_upper_uuid') $fixture['records'][0]['request_id'] = strtoupper(str_replace('44444444', 'abcdefab', $fixture['id']));
if ($kind === 'header_neighbor_label') $fixture['records'][0]['request_label'] = 'neighbor_qwen';
if ($kind === 'identity_downgrade') $fixture['records'][3]['original_workflow_run_id'] = '';
if ($kind === 'identity_other_task') $fixture['records'][3]['original_task_id'] = 'private-independent-other-task';
if ($kind === 'identity_before_serialization') [$fixture['records'][1], $fixture['records'][2]] = [$fixture['records'][2], $fixture['records'][1]];
if ($kind === 'wrong_query_hash_type') $fixture['records'][1]['query_sha256'] = ['private-independent-injection'];
if ($kind === 'stage_backwards') { $fixture['records'][7]['report_stage'] = 1; $fixture['records'][7]['report_continuation_query'] = false; }
if ($kind === 'non_dify') $fixture['records'][1]['protocol'] = 'openai';
if ($kind === 'summary_bool_count') $fixture['summary']['original_task_count'] = true;
if ($kind === 'summary_byte_mismatch') $fixture['summary']['audit_bytes'] = 1;
if ($kind === 'summary_run_mismatch') $fixture['summary']['original_run_count'] = 0;
[$selector] = $save($fixture); $before = $iiExecuted;
[$value, $configured] = $call($selector, []);
$expect(!$value['ok'] && $configured === 0 && $iiExecuted === $before, 'invalid exact-own bridge fails before config/GET: ' . $kind);
$privacy($value);
}
foreach (['object_sys', 'lookalike_dot', 'trailing_space', 'wrong_case', 'array_inputs', 'null_query', 'different_crlf',
'different_unicode', 'wrong_byte_count', 'optional_null_run', 'optional_null_task', 'output_only', 'both_same', 'running_input_only'] as $kind) {
$fixture = $make(); $value = $response();
if ($kind === 'object_sys') $value['inputs'] = ['sys' => ['query' => $query]];
if ($kind === 'lookalike_dot') $value['inputs'] = ['sys.query' => $query];
if ($kind === 'trailing_space') $value['inputs'] = ['sys.query ' => $query];
if ($kind === 'wrong_case') $value['inputs'] = ['SYS.QUERY' => $query];
if ($kind === 'array_inputs') $value['inputs'] = [['sys.query' => $query]];
if ($kind === 'null_query') $value['inputs']['query'] = null;
if ($kind === 'different_crlf') $value['inputs']['sys.query'] = str_replace("\r\n", "\n", $query);
if ($kind === 'different_unicode') $value['inputs']['sys.query'] .= "\u{0301}";
if ($kind === 'wrong_byte_count') ++$fixture['records'][1]['query_bytes'];
if ($kind === 'optional_null_run') $value['workflow_run_id'] = null;
if ($kind === 'optional_null_task') $value['task_id'] = null;
if ($kind === 'output_only') { $value['inputs'] = (object) []; $value['outputs']['query'] = $query; }
if ($kind === 'both_same') $value['inputs']['query'] = $query;
if ($kind === 'running_input_only') $value['status'] = 'running';
[$selector] = $save($fixture); $before = $iiExecuted;
[$answer] = $call($selector, [['body' => $json($value)]]);
$expect($answer['ok'] === in_array($kind, ['both_same', 'running_input_only'], true) && $iiExecuted === $before + 1,
'only exact whitelisted byte-identical query evidence: ' . $kind);
if ($kind === 'running_input_only') $expect($answer['runs'][0]['run_status'] === 'running', 'matching query does not assert a fresh terminal or cleanup proof');
$privacy($answer);
}
[$selector] = $save($make(2)); $before = $iiExecuted;
[$answer] = $call($selector, [['body' => $json($response()), 'advance_ns' => 2000000000], ['body' => $json($response(2))]]);
$expect(!$answer['ok'] && $iiExecuted === $before + 2 && $answer['matched_run_count'] === 1
&& $answer['runs'][0]['status'] === 'run_timeout' && $answer['runs'][1]['query_matches_wire'], 'timeout plus successor uses exactly two original GETs and cannot hide first failure');
$privacy($answer);
foreach (['setup_deadline', 'setopt_failure', 'chunk_overflow', 'throw'] as $kind) {
[$selector] = $save($make()); $scenario = ['body' => $json($response())];
if ($kind === 'setup_deadline') $scenario['setup_ns'] = 2000000000;
if ($kind === 'setopt_failure') $scenario['set_ok'] = false;
if ($kind === 'chunk_overflow') $scenario['chunks'] = [str_repeat('x', 1048576), str_repeat('x', 1048576), 'x'];
if ($kind === 'throw') $scenario['throw'] = true;
$before = $iiExecuted; [$answer] = $call($selector, [$scenario]);
$expect(!$answer['ok'] && $iiExecuted === $before + (in_array($kind, ['setup_deadline', 'setopt_failure'], true) ? 0 : 1), 'bounded setup/write/error closes without retry: ' . $kind);
$privacy($answer);
}
// NEW r25b controls: retain all old independent expectations above.
// Handcrafted body metadata is recomputed for these distinct strings;
// no private audit writer is used to derive expected query values.
$queryFixture = static function (array $fixture, array $queries) use ($json): array {
foreach ($fixture['records'] as &$record) {
if ($record['event'] !== 'serialized_request') continue;
$actual = $queries[$record['generation'] - 1];
$body = $json(['inputs' => (object) [], 'query' => $actual]);
$record['query_sha256'] = hash('sha256', $actual); $record['query_bytes'] = strlen($actual);
$record['body_sha256'] = hash('sha256', $body); $record['body_bytes'] = strlen($body);
}
unset($record);
return $fixture;
};
$exact = "literal\\u660e\\n/中文 \"quote\" <tag>& é e\u{0301} 😀\r\nNUL:\0\t ";
foreach ([0, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT, JSON_PRETTY_PRINT] as $number => $flags) {
[$selector] = $save($queryFixture($make(), [$exact]));
$value = $response(); $value['status'] = 'running';
$value['inputs'] = json_encode(['sys.query' => $exact, 'query' => $exact], $flags | JSON_THROW_ON_ERROR);
$before = $iiExecuted; [$answer] = $call($selector, [['body' => json_encode($value, $flags | JSON_THROW_ON_ERROR)]]);
$row = $answer['runs'][0];
$expect($answer['ok'] && $iiExecuted === $before + 1 && $row['query_candidate_count'] === 2, 'two JSON transport layers recover identical exact aliases ' . $number);
$expect($row['observed_query_sha256'] === hash('sha256', $exact) && $row['observed_query_bytes'] === strlen($exact), 'transport escapes preserve original hash and bytes ' . $number);
$expect($row['inputs_encoding'] === 'json_string' && $row['inputs_availability'] === 'available'
&& $row['returned_run_id_matches'] && $row['run_status'] === 'running', 'encoding/status metadata does not become terminal ' . $number);
$privacy($answer);
$expect(!str_contains($json($answer), $exact), 'new escaped query text remains private ' . $number);
}
foreach (['escaped_key', 'double_inputs', 'nested_inputs', 'literal_json_query', 'decoded_query_different',
'different_alias', 'bad_bytes', 'bad_hash', 'foreign_id', 'foreign_alias', 'metadata_in_inputs'] as $kind) {
$fixture = $queryFixture($make(), [$exact]);
$value = $response(); $value['status'] = 'running';
$value['inputs'] = $json(['query' => $exact]);
$wanted = 'query_match';
if ($kind === 'escaped_key') $value['inputs'] = '{"sys\\u002equery":' . $json($exact) . '}';
if ($kind === 'double_inputs') { $value['inputs'] = $json($value['inputs']); $wanted = 'query_input_invalid'; }
if ($kind === 'nested_inputs') { $value['inputs'] = $json(['inputs' => $value['inputs']]); $wanted = 'query_input_unavailable'; }
if ($kind === 'literal_json_query' || $kind === 'decoded_query_different') {
$literal = $json(['query' => $exact]);
$fixture = $queryFixture($make(), [$literal]);
$value['inputs'] = $json(['query' => $kind === 'literal_json_query' ? $literal : $exact]);
if ($kind === 'decoded_query_different') $wanted = 'query_mismatch';
}
if ($kind === 'different_alias') { $value['inputs'] = $json(['sys.query' => $exact, 'query' => $json($exact)]); $wanted = 'query_input_ambiguous'; }
if ($kind === 'bad_bytes') { ++$fixture['records'][1]['query_bytes']; $wanted = 'query_mismatch'; }
if ($kind === 'bad_hash') { $fixture['records'][1]['query_sha256'] = hash('sha256', $exact . 'x'); $wanted = 'query_mismatch'; }
if ($kind === 'foreign_id') { $value['id'] = 'private-independent-foreign-run'; $wanted = 'returned_run_mismatch'; }
if ($kind === 'foreign_alias') { $value['task_id'] = 'private-independent-foreign-task'; $wanted = 'returned_run_mismatch'; }
if ($kind === 'metadata_in_inputs') $value['inputs'] = $json(['query' => $exact, 'run_status' => 'succeeded',
'returned_run_id_matches' => false, 'inputs_availability' => 'missing', 'status' => 'private-independent-status']);
[$selector] = $save($fixture); [$answer] = $call($selector, [['body' => $json($value)]]); $row = $answer['runs'][0];
$expect($row['status'] === $wanted && $answer['ok'] === ($wanted === 'query_match'), 'one decode and per-field exact comparison ' . $kind);
if (in_array($kind, ['foreign_id', 'foreign_alias'], true)) {
$expect($row['inputs_encoding'] === 'unobserved' && $row['inputs_availability'] === 'unobserved'
&& $row['returned_run_id_matches'] === ($kind === 'foreign_alias'), 'identity gate precedes any decoded input observation ' . $kind);
$expect(!array_key_exists('run_status', $row) && !array_key_exists('observed_query_sha256', $row), 'foreign identity cannot export query/status observations ' . $kind);
} else $expect($row['run_status'] === 'running', 'valid original running status independent of query decision ' . $kind);
if ($kind === 'metadata_in_inputs') $expect($row['returned_run_id_matches'] && $row['inputs_availability'] === 'available', 'input object cannot override safe metadata');
if (in_array($kind, ['double_inputs', 'nested_inputs', 'different_alias'], true))
$expect(!array_key_exists('observed_query_sha256', $row), 'unavailable or ambiguous query never invents a hash ' . $kind);
$privacy($answer);
}
$secondQuery = $exact . 'sole-continuation';
foreach (['own', 'swapped', 'one_wrong'] as $kind) {
[$selector] = $save($queryFixture($make(2), [$exact, $secondQuery]));
$first = $response(); $second = $response(2); $first['status'] = $second['status'] = 'running';
$first['inputs'] = $json(['sys.query' => $kind === 'swapped' ? $secondQuery : $exact]);
$second['inputs'] = $json(['query' => $kind === 'own' ? $secondQuery : $exact]);
$before = $iiExecuted; [$answer] = $call($selector, [['body' => $json($first)], ['body' => $json($second)]]);
$expect($iiExecuted === $before + 2 && $answer['inspected_run_count'] === 2, 'exactly two original generation GETs without recovery/fallback ' . $kind);
$expect($answer['matched_run_count'] === match ($kind) { 'own' => 2, 'swapped' => 0, 'one_wrong' => 1 }
&& $answer['ok'] === ($kind === 'own'), 'each serialized query bound to its own generation ' . $kind);
$expect(array_column($answer['runs'], 'run_status') === ['running', 'running'], 'matching or mismatching input never rewrites terminal state ' . $kind);
$privacy($answer);
}
[$selector] = $save($queryFixture($make(), [$exact]));
$value = $response(); $value['status'] = 'running'; $value['inputs'] = $json(['query' => $exact]);
[$answer] = $call($selector, [['body' => $json($value), 'close_ns' => 2000000000]]);
$row = $answer['runs'][0];
$expect(!$answer['ok'] && $row['status'] === 'run_timeout' && !$row['query_matches_wire'], 'outer total per-run budget invalidates a comparison that finished before slow close');
$expect($row['run_status'] === 'running' && $row['returned_run_id_matches']
&& $row['inputs_encoding'] === 'json_string' && $row['inputs_availability'] === 'available', 'outer timeout retains obtained safe observations without terminal override');
$privacy($answer);
// Exact source equivalence is stronger than middleware-name presence:
// the original saved-row/audit/config code remains mechanically equal.
$artifact = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/';
require $artifact . 'private_tcm_r25_run_input_inspector.php';
$methodSource = static function (string $class, string $method): string {
$reflection = new ReflectionMethod($class, $method);
$lines = file($reflection->getFileName());
return str_replace("\r\n", "\n", implode('', array_slice($lines, $reflection->getStartLine() - 1,
$reflection->getEndLine() - $reflection->getStartLine() + 1)));
};
foreach (['inspectResultRow', 'readAudit', 'connection', 'keys', 'digest', 'id', 'samePath', 'error'] as $method) {
$expect($methodSource(Inspector::class, $method) === $methodSource('PrivateTcmR25\\RunInputInspector', $method), 'canonical old audit/result/connection method exact source equality ' . $method);
}
$oldProbe = str_replace("\r\n", "\n", file_get_contents($artifact . 'private_tcm_r25_run_input_probe.php'));
$newProbe = str_replace("\r\n", "\n", file_get_contents($artifact . 'private_tcm_r25b_run_input_probe.php'));
$expectedProbe = str_replace(['runInputFinish', 'private_tcm_r25_run_input_', '\\PrivateTcmR25\\'],
['runInputR25bFinish', 'private_tcm_r25b_run_input_', '\\PrivateTcmR25b\\'], $oldProbe);
$expectedProbe = str_replace('// POST: token,', "// New r25b derivative; the original r25 probe/helper remain immutable.\n// POST: token,", $expectedProbe);
$expect($newProbe === $expectedProbe, 'entire canonical private probe auth bridge unchanged except exact derivative naming/comment');
foreach (['private_tcm_r25_run_input_inspector.php' => '9f5f163e529ecdbb5e9b4d2b1ab1d04d8c4292b371c72c66034554b3b4860381',
'private_tcm_r25_run_input_probe.php' => '18ffca2a320c683c7e797ec6ac2e1fe606d0103fc59c701c95c818f49c8a4a0e',
'private_tcm_r25b_run_input_inspector.php' => 'f0f6ef018e391ea5f1f219b0d5fc931f59f8fa84fe7431d8d5ca29efab410a61',
'private_tcm_r25b_run_input_probe.php' => '6c19b575ae7fd88554dd68f12d8f258e15851e5e16246a52ae705e6840ad782a'] as $name => $sha) {
$expect(hash_file('sha256', $artifact . $name) === $sha, 'exact original/derivative source pin ' . $name);
}
[$selector, $auditPath] = $save($make());
$lock = fopen($auditPath, 'rb');
$expect(flock($lock, LOCK_EX | LOCK_NB), 'synthetic exclusive lock acquired');
$before = $iiExecuted; [$answer, $configured] = $call($selector, []);
$expect(!$answer['ok'] && $configured === 0 && $iiExecuted === $before, 'locked exact private audit never reaches config/GET');
flock($lock, LOCK_UN); fclose($lock);
$expect($iiCreated === $iiClosed, 'every fake GET handle closed');
$expect($iiScenarios === [], 'all and only declared fake scenarios consumed');
$expect(!class_exists('think\\App', false), 'application bootstrap never loaded');
echo "Independent r25b serialized exact-run input inspector: {$checks} checks passed (handcrafted private fixtures, in-memory GET/config/time; no auth/network/bootstrap).\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), '/') . '/tcm-r25b-inspector-independent-'))
throw new RuntimeException('Independent synthetic cleanup boundary');
$entries = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
foreach ($entries as $entry) { if ($entry->isLink()) throw new RuntimeException('Independent synthetic cleanup symlink'); if ($entry->isDir()) rmdir($entry->getPathname()); else unlink($entry->getPathname()); }
rmdir($real);
}
}