$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']; } } namespace { require dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/private_tcm_r25_run_input_inspector.php'; use PrivateTcmR25\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-r25-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); } [$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 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-r25-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); } }