<?php
declare(strict_types=1);

// A/B/C one-route SELECT-backed HTTP acceptance. Default deny; --preflight is SELECT-only.
// Never print credentials, department names, UUIDs, process environments, or response bodies.
set_exception_handler(static function (Throwable $e): void {
    echo json_encode(['status' => 'halt', 'class' => get_class($e),
        'code' => (string)$e->getCode(),
        'message_sha256' => hash('sha256', $e->getMessage())], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(3);
});

$mode = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
if (!in_array($mode, ['--preflight', '--execute'], true)
    || !in_array($lane, ['A', 'B', 'C'], true) || count($argv) !== 3) {
    fwrite(STDERR, "DENIED: exact --preflight|--execute and A|B|C required\n");
    exit(2);
}
if ($mode === '--execute') {
    $approved = (string)getenv('CDX_BANK_TOPDEPT_APPROVED');
    $approvedLane = (string)getenv('CDX_BANK_TOPDEPT_LANE');
    $approvedSha = (string)getenv('CDX_BANK_TOPDEPT_SCRIPT_SHA');
    if ($approved !== 'BANK_TOPDEPARTMENTS_READ_V1'
        || $approvedLane !== $lane || $approvedSha === ''
        || !hash_equals(hash_file('sha256', __FILE__), $approvedSha)) {
        fwrite(STDERR, "DENIED: exact reviewed script SHA and lane approval required\n");
        exit(2);
    }
    if ($lane === 'B'
        && (!preg_match('/^[a-f0-9]{64}$/', (string)getenv('CDX_BANK_EXPECT_A_SOURCE_SHA'))
            || !preg_match('/^[a-f0-9]{64}$/', (string)getenv('CDX_BANK_EXPECT_A_RESPONSE_SHA')))) {
        fwrite(STDERR, "DENIED: A successful source and response hashes required before B\n");
        exit(2);
    }
    if ($lane === 'C'
        && ((string)getenv('CDX_BANK_AB_PARITY_VERIFIED') !== '1'
            || !preg_match('/^[a-f0-9]{64}$/', (string)getenv('CDX_BANK_EXPECT_B_RESPONSE_SHA')))) {
        fwrite(STDERR, "DENIED: A/B parity evidence required before C\n");
        exit(2);
    }
}

const BANK_SHA = '5a0569974c11672eb3a62ef6f290a2f2cda5ba7165888f71c5905e8c3eab692b';
const BANK_SERVICE_SHA = 'acb09ee3c5d9e75d1b7d677d97e30c4b4bc8804872d0632fbaae60fc68054649';
const STUB_SHA = 'ed0ea9f23c70e504cf5331b7a95fe2a9fd6704beabf254a474efbe8ecb4d59a0';
const CACHE_SHA = 'ccf1c56eb4178f3eaad8775e26a27742b2cacf662b46fb058cbb91d23783b8c6';
const LOG_CONFIG_SHA = '16025200f4a396d32a181151da621a017352c1e8c648fe3c84e66c961d878e9a';
const ROUTE_SHA = 'e546d4eda097f7b0d69b0a9fc91d42304adf3b92d3e127f3e071e534ce31a7cf';
const GLOBAL_MIDDLEWARE_SHA = 'e9f5d4d89fca156615f29e0d9d03a43748300b64e4dbb1df6af7f92bb290ecf9';
const APP_LOG_SHA = 'f42505e34c0f4d52f5758b77102197357043ec2426bada2510b858d52b5fc62f';
const BANK_CONTROLLER_METHOD_SHA = '635424306f3326dcbf51da0a094dde57a66079d8b543e948aa9f79c4354dce45';
const BANK_SERVICE_METHOD_SHA = '8c9d43cf4ad3d8a891f036d9548125b97f0dde4b485a225d270c69db92ba0046';
const RESTORED_B_CONFIG_TREE_SHA = 'e4c02e8067e7d5038a1db68a059d2160cfad00d28ca1a82d07cb4ff19de99dd7';
const DEV_AI_FINGERPRINT = '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b';
const DEV_STORE_FINGERPRINT = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';

$specs = [
    'A' => ['pid' => 13109, 'port' => 18105,
        'socket' => '0x8baeb09c9a6de52f',
        'root' => '/private/tmp/zhct-http-ai-macos.zjaU8h',
        'source' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/baseline-a-ai-api-V4.0.40',
        'log' => '/private/tmp/zhct-ai-http-log.yaHfja/A-18105.log',
        'db_config_sha' => 'e7402b1ad50fcf4205326d93dc22e4f491c5052c60fcbe36214cd4e07b55343c',
        'expected_count' => 19],
    'B' => ['pid' => 13127, 'port' => 18101,
        'socket' => '0xc1b5193ecc2ef2dc',
        'root' => '/private/tmp/zhct-http-ai-macos.x2CCZX',
        'source' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/ai_api',
        'log' => '/private/tmp/zhct-ai-http-log.yaHfja/B-18101.log',
        'db_config_sha' => '8c46a6d9cd277f2950818d09162f639a62f135a5eb6553e6c61ce2c19167fd02',
        'expected_count' => 19],
    'C' => ['pid' => 53772, 'port' => 18102,
        'socket' => '441267',
        'root' => '/tmp/zhct-ai-http-20260929.3XDj3w',
        'source' => '/tmp/zhct-dm-poc-20260928.fcpiiN/ai_api',
        'log' => '/tmp/zhct-ai-http-20260929.3XDj3w/server-18102.log',
        'db_config_sha' => 'e7402b1ad50fcf4205326d93dc22e4f491c5052c60fcbe36214cd4e07b55343c',
        'expected_count' => 5],
];
$spec = $specs[$lane];
if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 70500) {
    throw new RuntimeException('expected isolated AI PHP7.4 CLI');
}
if (($lane === 'C') !== (PHP_OS_FAMILY === 'Linux')) {
    throw new RuntimeException('lane and host platform mismatch');
}

function privateMode(string $path, int $mode): bool
{
    return is_dir($path) && (fileperms($path) & 0777) === $mode;
}

function restoredBConfigTreeSha(string $root): string
{
    $dir = $root . '/config';
    $names = array_values(array_diff(scandir($dir) ?: [], ['.', '..']));
    sort($names);
    if (count($names) !== 23) throw new RuntimeException('B restored config file count drift');
    $tree = [];
    foreach ($names as $name) {
        $path = $dir . '/' . $name;
        if (!preg_match('/^[a-z_]+\.php$/', $name) || !is_file($path)
            || is_link($path) || (fileperms($path) & 0777) !== 0644) {
            throw new RuntimeException('B restored config object or mode drift');
        }
        $tree[] = $name . "\t" . hash_file('sha256', $path) . "\t0644";
    }
    sort($tree);
    return hash('sha256', implode("\n", $tree));
}

function processEnvironment(array $spec, string $lane): array
{
    $pid = $spec['pid'];
    $root = $spec['root'];
    $port = $spec['port'];
    if ($lane === 'C') {
        $raw = @file_get_contents('/proc/' . $pid . '/environ');
        $command = @file_get_contents('/proc/' . $pid . '/cmdline');
        if ($raw === false || $command === false) throw new RuntimeException('C fixed process gone');
        $env = [];
        foreach (explode("\0", $raw) as $part) {
            $position = strpos($part, '=');
            if ($position !== false) $env[substr($part, 0, $position)] = substr($part, $position + 1);
        }
        if (strpos($command, '-S' . "\0" . '127.0.0.1:' . $port) === false
            || strpos($command, $root . '/public') === false
            || isset($env['PHP_CLI_SERVER_WORKERS'])) {
            throw new RuntimeException('C command or worker boundary changed');
        }
        $socket = (string)shell_exec("ss -H -ltnpe '( sport = :18102 )' 2>/dev/null");
        if (substr_count(trim($socket), "\n") !== 0
            || strpos($socket, '127.0.0.1:18102') === false
            || strpos($socket, 'pid=' . $pid . ',') === false
            || !preg_match('/\bino:(\d+)\b/', $socket, $m)
            || $m[1] !== $spec['socket']
            || @readlink('/proc/' . $pid . '/fd/3') !== 'socket:[' . $m[1] . ']') {
            throw new RuntimeException('C socket inode ownership mismatch');
        }
        return $env;
    }
    $command = (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=');
    $owners = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:' . $port . ' -sTCP:LISTEN 2>/dev/null'));
    $socketLine = (string)shell_exec('/usr/sbin/lsof -nP -iTCP:' . $port . ' -sTCP:LISTEN 2>/dev/null');
    if ($owners !== (string)$pid || strpos($socketLine, $spec['socket']) === false
        || strpos($command, '-S 127.0.0.1:' . $port) === false
        || (strpos($command, $root . '/public') === false
            && strpos($command, str_replace('/private/tmp/', '/tmp/', $root) . '/public') === false)
        || strpos($command, 'PHP_CLI_SERVER_WORKERS=') !== false) {
        throw new RuntimeException('A/B fixed socket owner or command changed');
    }
    $env = [];
    foreach (preg_split('/\s+/', $command) as $part) {
        if (strpos($part, 'POC_') !== 0) continue;
        $position = strpos($part, '=');
        if ($position !== false) $env[substr($part, 0, $position)] = substr($part, $position + 1);
    }
    return $env;
}

function checkFilesAndLog(array $spec, string $lane): void
{
    $root = $spec['root'];
    $log = $spec['log'];
    if (!privateMode($root, 0700) || is_link($root)
        || !is_file($root . '/public/index.php')
        || ($lane !== 'C' && realpath($root . '/app/api/controller/Bank.php')
            !== realpath($spec['source'] . '/app/api/controller/Bank.php'))
        || hash_file('sha256', $root . '/app/api/controller/Bank.php') !== BANK_SHA
        || hash_file('sha256', $root . '/app/api/service/Bank.php') !== BANK_SERVICE_SHA
        || hash_file('sha256', $root . '/external_stubs.php') !== STUB_SHA
        || hash_file('sha256', $root . '/config/cache.php') !== CACHE_SHA
        || hash_file('sha256', $root . '/config/log.php') !== LOG_CONFIG_SHA
        || hash_file('sha256', $root . '/route/app.php') !== ROUTE_SHA
        || hash_file('sha256', $root . '/app/middleware.php') !== GLOBAL_MIDDLEWARE_SHA
        || hash_file('sha256', $root . '/cores/middleware/AppLog.php') !== APP_LOG_SHA
        || hash_file('sha256', $root . '/config/database.php') !== $spec['db_config_sha']) {
        throw new RuntimeException('fixed source, config or stub drift');
    }
    if (strpos((string)file_get_contents($root . '/route/app.php'),
        "Route::rule('bank/topDepartments', 'api/Bank/topDepartments', 'POST');") === false) {
        throw new RuntimeException('exact sole POST route drift');
    }
    if ($lane === 'B' && restoredBConfigTreeSha($root) !== RESTORED_B_CONFIG_TREE_SHA) {
        throw new RuntimeException('B 23-file restored config tree drift');
    }
    if (!is_file($log) || is_link($log) || !privateMode(dirname($log), 0700)
        || ($lane !== 'C' && (fileperms($log) & 0777) !== 0600)
        || ($lane === 'C' && strpos(realpath($log), realpath($root) . '/') !== 0)) {
        throw new RuntimeException('private service log boundary drift');
    }
    if ($lane === 'C') {
        $fd1 = @readlink('/proc/' . $spec['pid'] . '/fd/1');
        $fd2 = @readlink('/proc/' . $spec['pid'] . '/fd/2');
        if ($fd1 !== $log || $fd2 !== $log) throw new RuntimeException('C process log FD drift');
    } else {
        $fds = (string)shell_exec('/usr/sbin/lsof -a -p ' . $spec['pid'] . ' -d 1,2 -Fn 2>/dev/null');
        if (substr_count($fds, 'n' . $log) !== 2) {
            throw new RuntimeException('A/B process log FD drift');
        }
    }
    // Composer's real autoload mapping, not merely docroot's apparent source.
    require_once $root . '/vendor/autoload.php';
    foreach ([\app\api\controller\Bank::class => ['api/controller/Bank.php', BANK_SHA,
            BANK_CONTROLLER_METHOD_SHA],
        \app\api\service\Bank::class => ['api/service/Bank.php', BANK_SERVICE_SHA,
            BANK_SERVICE_METHOD_SHA]] as $class => $definition) {
        $reflection = new ReflectionClass($class);
        $actual = realpath((string)$reflection->getFileName());
        $expected = realpath($spec['source'] . '/app/' . $definition[0]);
        if ($actual === false || $actual !== $expected
            || hash_file('sha256', $actual) !== $definition[1]) {
            throw new RuntimeException('actual Composer class source drift');
        }
        $method = new ReflectionMethod($class, 'topDepartments');
        $methodText = implode('', array_slice(file($actual), $method->getStartLine() - 1,
            $method->getEndLine() - $method->getStartLine() + 1));
        if (hash('sha256', $methodText) !== $definition[2]
            || preg_match('/\b(?:Queue|Serve|PsbcApi|curl_exec|file_put_contents)\b|\bMessage\s*::|\bnew\s+Message\b|->\s*(?:sendMessage|push|save|delete|update|insert)\s*\(/i',
                $methodText) === 1) {
            throw new RuntimeException('sole read method or no-queue/no-message chain drift');
        }
        if ($class === \app\api\service\Bank::class
            && (strpos($methodText, "connect('mysql_zhct')") === false
                || strpos($methodText, "->name('department')") === false
                || strpos($methodText, "->field('uuid,name')") === false
                || strpos($methodText, '->select()') === false)) {
            throw new RuntimeException('Store named pure department SELECT drift');
        }
    }
}

function mysqlConnection(array $section, string $expectedFingerprint): PDO
{
    if (strtolower((string)($section['TYPE'] ?? '')) !== 'mysql') {
        throw new RuntimeException('local connection dialect drift');
    }
    $db = new PDO('mysql:host=' . $section['HOSTNAME'] . ';port=' . (int)$section['HOSTPORT']
        . ';dbname=' . $section['DATABASE'] . ';charset=' . ($section['CHARSET'] ?? 'utf8'),
        $section['USERNAME'], $section['PASSWORD'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5,
            PDO::ATTR_EMULATE_PREPARES => false]);
    $identity = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
        ->fetchColumn();
    if (!hash_equals($expectedFingerprint, hash('sha256', $identity))) {
        throw new RuntimeException('code-local development database fingerprint drift');
    }
    return $db;
}

function dmConnection(string $port, string $schema, string $user, string $password): PDO
{
    $db = new PDO('dm:host=127.0.0.1;port=' . $port . ';schema=' . $schema,
        $user, $password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $identity = $db->query("SELECT USER AS LOGIN_USER, SYS_CONTEXT('USERENV','CURRENT_SCHEMA') AS CURRENT_SCHEMA FROM DUAL")
        ->fetch(PDO::FETCH_ASSOC);
    if (!is_array($identity) || strtoupper((string)$identity['login_user']) !== $user
        || strtoupper((string)$identity['current_schema']) !== $schema) {
        throw new RuntimeException('C database user/schema drift');
    }
    return $db;
}

function departmentRows(PDO $db, bool $dm): array
{
    $table = $dm ? 'YDY_DEPARTMENT' : 'ydy_department';
    if ($dm) {
        $meta = $db->query("SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YDY_DEPARTMENT' "
            . "AND COLUMN_NAME IN ('UUID','NAME','PUUID','STATUS','IS_SHOW')")
            ->fetchAll(PDO::FETCH_COLUMN);
    } else {
        $meta = $db->query("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS "
            . "WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='ydy_department' "
            . "AND COLUMN_NAME IN ('uuid','name','puuid','status','is_show')")
            ->fetchAll(PDO::FETCH_COLUMN);
    }
    if (count($meta) !== 5) throw new RuntimeException('department source fields changed');
    $rows = $db->query('SELECT UUID,NAME FROM ' . $table
        . " WHERE PUUID='0' AND STATUS=1 AND IS_SHOW=1 ORDER BY UUID,NAME")
        ->fetchAll(PDO::FETCH_ASSOC);
    return normalizeRows($rows);
}

function normalizeRows(array $rows): array
{
    $normalized = [];
    foreach ($rows as $row) {
        $row = array_change_key_case($row, CASE_LOWER);
        if (!isset($row['uuid'], $row['name'])
            || !is_string($row['uuid']) || !is_string($row['name'])
            || $row['uuid'] === '' || $row['name'] === '') {
            throw new RuntimeException('department row contract invalid');
        }
        $normalized[] = ['uuid' => $row['uuid'], 'name' => $row['name']];
    }
    usort($normalized, static function (array $a, array $b): int {
        return strcmp($a['uuid'], $b['uuid']) ?: strcmp($a['name'], $b['name']);
    });
    return $normalized;
}

$env = processEnvironment($spec, $lane);
checkFilesAndLog($spec, $lane);
if (($env['POC_DIALECT'] ?? '') !== ($lane === 'C' ? 'dm' : 'mysql')
    || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1') {
    throw new RuntimeException('external stub or dialect boundary drift');
}
$rootAlias = str_replace('/private/tmp/', '/tmp/', $spec['root']);
if (!in_array((string)($env['POC_TARGET_ROOT'] ?? ''), [$spec['root'], $rootAlias], true)) {
    throw new RuntimeException('target runtime root drift');
}
if ($lane === 'C') {
    foreach (['POC_HOST' => '127.0.0.1', 'POC_PORT' => '5237',
        'POC_SCHEMA' => 'ZHCT_APP', 'POC_USER' => 'ZHCT_APP',
        'POC_AI_HOST' => '127.0.0.1', 'POC_AI_PORT' => '5238',
        'POC_AI_SCHEMA' => 'AIZHCT_APP', 'POC_AI_USER' => 'AIZHCT_APP'] as $key => $value) {
        if (($env[$key] ?? '') !== $value) throw new RuntimeException('C fixed nonprod target drift');
    }
    if (empty($env['POC_PASSWORD']) || empty($env['POC_AI_PASSWORD'])) {
        throw new RuntimeException('C private credentials unavailable');
    }
    $ai = dmConnection('5238', 'AIZHCT_APP', $env['POC_AI_USER'], $env['POC_AI_PASSWORD']);
    $store = dmConnection('5237', 'ZHCT_APP', $env['POC_USER'], $env['POC_PASSWORD']);
} else {
    $path = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';
    $config = parse_ini_file($path, true, INI_SCANNER_RAW);
    if (!is_array($config) || !isset($config['DATABASE'], $config['DATABASE_ZHCT'])) {
        throw new RuntimeException('code-local development configuration unavailable');
    }
    foreach (['DATABASE' => 'POC_AI_', 'DATABASE_ZHCT' => 'POC_'] as $section => $prefix) {
        foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
            'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
            if ((string)($config[$section][$from] ?? '') === ''
                || ($env[$prefix . $to] ?? null) !== (string)$config[$section][$from]) {
                throw new RuntimeException('A/B process env differs from code .env.local');
            }
        }
    }
    $ai = mysqlConnection($config['DATABASE'], DEV_AI_FINGERPRINT);
    $store = mysqlConnection($config['DATABASE_ZHCT'], DEV_STORE_FINGERPRINT);
}
$before = departmentRows($store, $lane === 'C');
$sourceSha = hash('sha256', json_encode($before, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
if (count($before) !== $spec['expected_count']) {
    throw new RuntimeException('approved natural nonempty source count changed');
}
$preflight = ['status' => 'source-preflight-pass', 'lane' => $lane,
    'port' => $spec['port'], 'pid' => $spec['pid'],
    'source_count' => count($before), 'source_sha256' => $sourceSha,
    'bank_controller_sha256' => BANK_SHA, 'bank_service_sha256' => BANK_SERVICE_SHA,
    'ai_and_store_identity' => 'approved', 'private_log_boundary' => true,
    'external_stubs' => true];
if ($mode === '--preflight') {
    echo json_encode($preflight, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

// Explicitly approved, sole business request. Never follow redirects or send auth tokens.
$httpError = null;
$body = null;
$returned = null;
$elapsedMs = null;
try {
    $url = 'http://127.0.0.1:' . $spec['port']
        . '/api/bank/topDepartments?storeId=10001';
    $context = stream_context_create(['http' => ['method' => 'POST', 'timeout' => 15,
        'follow_location' => 0, 'ignore_errors' => true,
        'header' => "Content-Type: application/json;charset=utf-8\r\nplatform: POC\r\n",
        'content' => '{}']]);
    $started = microtime(true);
    $handle = @fopen($url, 'rb', false, $context);
    if ($handle === false) throw new RuntimeException('approved loopback HTTP opening failed');
    $raw = stream_get_contents($handle, 1048577);
    $meta = stream_get_meta_data($handle);
    fclose($handle);
    $elapsedMs = (int)round((microtime(true) - $started) * 1000);
    if (!is_string($raw) || strlen($raw) > 1048576 || !empty($meta['timed_out'])
        || $elapsedMs > 15000) throw new RuntimeException('HTTP timeout or 1MiB response cap exceeded');
    $headers = $meta['wrapper_data'] ?? [];
    $statusLine = is_array($headers) ? (string)($headers[0] ?? '') : '';
    if (!preg_match('/^HTTP\/\S+\s+(\d{3})/', $statusLine, $matches)
        || (int)$matches[1] !== 200) throw new RuntimeException('HTTP status not 200');
    $body = json_decode($raw, true);
    if (!is_array($body) || !array_key_exists('trace_id', $body)
        || !is_int($body['status'] ?? null) || $body['status'] !== 200
        || !is_string($body['message'] ?? null) || !is_array($body['data'] ?? null)
        || count($body['data']) !== count($before)) {
        throw new RuntimeException('business status or top-level JSON contract mismatch');
    }
    $returned = normalizeRows($body['data']);
    if ($returned !== $before) throw new RuntimeException('response differs from same-source original rows');
} catch (Throwable $e) {
    $httpError = $e;
}
$after = departmentRows($store, $lane === 'C');
$afterSha = hash('sha256', json_encode($after, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
if (count($after) !== count($before) || !hash_equals($sourceSha, $afterSha)) {
    throw new RuntimeException('department original table changed after read request');
}
if ($httpError !== null) throw $httpError;
$traceIdType = gettype($body['trace_id']);
unset($body['trace_id']);
$responseSha = hash('sha256', json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
if ($lane === 'B') {
    $aSource = (string)getenv('CDX_BANK_EXPECT_A_SOURCE_SHA');
    $aResponse = (string)getenv('CDX_BANK_EXPECT_A_RESPONSE_SHA');
    if (!preg_match('/^[a-f0-9]{64}$/', $aSource)
        || !preg_match('/^[a-f0-9]{64}$/', $aResponse)
        || !hash_equals($aSource, $sourceSha)
        || !hash_equals($aResponse, $responseSha)) {
        throw new RuntimeException('A/B same-MySQL response/source SHA parity failed');
    }
}
echo json_encode(['status' => 'bank-top-departments-http-pass', 'lane' => $lane,
    'http' => 200, 'business_status' => 200, 'elapsed_ms' => $elapsedMs,
    'source_count' => count($before), 'response_count' => count($returned),
    'uuid_json_type' => 'string', 'name_json_type' => 'string',
    'trace_id_type' => $traceIdType,
    'source_before_after_sha256' => $sourceSha,
    'response_without_trace_id_sha256' => $responseSha,
    'original_table_unchanged' => true], JSON_UNESCAPED_SLASHES), PHP_EOL;
