<?php
declare(strict_types=1);

// Test-only A/B/C score-rank lifecycle. Every phase defaults to deny.
// Never print raw department UUID, user IDs, names, database secrets or bodies.
const SCORE_ASSET_HASHES = [
    'score_rank_candidate_lib.php' => '34ef24773101dcf09631dcc186940d060b1ebadf730bf94671552a81b0498004',
    'score_rank_router.php' => '9e89ba5ebecfb39bee5dc1ff7eb8d4595aacec7c5ea9a50b546a1c657289da63',
    'score_rank_queue.disabled.php' => 'b713abd28d7e570773be5db6e16ac84d76d95566956dd09e2112f515ec36666c',
    'score_rank_message.disabled.php' => '09dea10eb3c6ae196ea43f887cfcbffdcf67cd408191d3781e5be607cad0b781',
];
const SCORE_SOURCE_HASHES = [
    'app/api/controller/user/Score.php' => '6260b430fb796fb01bb259ef97d1b61649ded459f40fae1a0b7cc40fb611bcc7',
    'app/common/model/zhct/Staff.php' => '43488d567bf658f78b8131043711fe1ab37baa8699b053a22d2540de144468d8',
    'app/common/model/user/UserScore.php' => '3cc36a9b65dcde9cc1d7faf936b7ca6e13794362fede343cf4170165afb11136',
    'app/api/controller/Controller.php' => '3bf4829abc99399472e8f7f9a6b95c56dc814f656c0aab3fceb606cf1dfab07c',
    'app/common/model/wxapp/Setting.php' => '7751d2dc29d5856080ec693ffef06468739e5c833975324ea13af56b8e69af0f',
    'app/common/model/Store.php' => '0798837a2f1c838037bc73a219aed11c4e26b3a768410725e2c9caa211a30390',
    'app/common/model/UploadFile.php' => '80a98f405f3321a36adb8493f243311fdb22f24a6e0bdb59830658ac9f9531a4',
    'cores/middleware/AppLog.php' => 'f42505e34c0f4d52f5758b77102197357043ec2426bada2510b858d52b5fc62f',
    'cores/AppService.php' => '6f725bbc65c57039856f0e6554b000b764233bd39d2b2289b7056ede5fe17615',
    'app/middleware.php' => 'e9f5d4d89fca156615f29e0d9d03a43748300b64e4dbb1df6af7f92bb290ecf9',
    'app/event.php' => '003c4e9fc01c48be80733e3511d34e5f28c1b48af812b34a2c30b195c4070f73',
    'app/service.php' => '341a6e1257da720aff177c6cc324b6a71511d625331b20381d5530741b6a5233',
    'think' => '05ea09f4b8d9f6536f050db09c76c772134e8153ca8504743dd4b9de52487f97',
    'vendor/topthink/framework/src/think/Env.php' => '1ec1832f9e80fe6a33d359aec1c97d145faa64683a8f0e30823ff5bbebcf7e2d',
];
// A is the immutable pre-compatibility source. B/C must load only the reviewed
// one-line alias-scope fix; these hashes are checked before every live phase.
const SCORE_RANK_MODEL_HASHES = [
    'A' => 'd155c61841a9dd84650d2dbdbb428f077b1f75462ea183dd4866f8c70fbaee9f',
    'B' => '7a5091ecad4a8ee4d55630da309114a9d927f6185bbf7313859ede17903ea025',
    'C' => '7a5091ecad4a8ee4d55630da309114a9d927f6185bbf7313859ede17903ea025',
];
const SCORE_CACHE_SHA = 'ccf1c56eb4178f3eaad8775e26a27742b2cacf662b46fb058cbb91d23783b8c6';
const SCORE_LOG_SHA = '16025200f4a396d32a181151da621a017352c1e8c648fe3c84e66c961d878e9a';
const SCORE_STUB_SHA = 'ed0ea9f23c70e504cf5331b7a95fe2a9fd6704beabf254a474efbe8ecb4d59a0';
const SCORE_C_ROUTE_FILE_SHA = 'e546d4eda097f7b0d69b0a9fc91d42304adf3b92d3e127f3e071e534ce31a7cf';
const SCORE_C_ROUTE_TREE_SHA = 'ec1a8a86d33691e8ec875f4ed67681a461e271ca4dfd7363c18e57c6ba0a8c94';
const SCORE_DEV_AI_SHA = '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b';
const SCORE_DEV_STORE_SHA = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';

$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$approvals = [
    '--plan' => ['CDX_SCORE_PLAN_APPROVED', 'PLAN_ONLY_V1'],
    '--start' => ['CDX_SCORE_START_APPROVED', 'START_NEW_ISOLATED_RUNTIME_V1'],
    '--health' => ['CDX_SCORE_HEALTH_APPROVED', 'BOUNDARY_ONLY_V1'],
    '--test' => ['CDX_SCORE_TEST_APPROVED', 'THREE_GET_ONLY_V1'],
    '--stop' => ['CDX_SCORE_STOP_APPROVED', 'STOP_OWN_NEW_PID_ONLY_V1'],
    '--archive' => ['CDX_SCORE_ARCHIVE_APPROVED', 'ARCHIVE_STOPPED_ROOT_ONLY_V1'],
];
if (count($argv) !== 3 || !isset($approvals[$phase])
    || !in_array($lane, ['A', 'B', 'C'], true)
    || getenv($approvals[$phase][0]) !== $approvals[$phase][1]
    || !hash_equals(hash_file('sha256', __FILE__),
        (string)getenv('CDX_SCORE_LIFECYCLE_SHA'))
    || (string)getenv('CDX_SCORE_LIFECYCLE_LANE') !== $lane) {
    fwrite(STDERR, "DENIED: exact phase, lane and reviewed SHA required\n");
    exit(78);
}
if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 70500) {
    fwrite(STDERR, "DENIED: PHP7.4 required\n");
    exit(78);
}

$specs = [
    'A' => ['port' => 18205,
        'root' => '/Users/wiscourper/.codex/score-rank-isolated-20261004/A',
        'source' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/baseline-a-ai-api-V4.0.40',
        'template' => '/private/tmp/zhct-http-ai-macos.zjaU8h',
        'old_pid' => 13109, 'old_port' => 18105,
        'old_socket' => '0x8baeb09c9a6de52f',
        'database_sha' => 'e7402b1ad50fcf4205326d93dc22e4f491c5052c60fcbe36214cd4e07b55343c',
        'department_sha' => '9b392fa3664b286bfcd27b1f8c6bc3f8e5cc0f4ab0ea495d764edf2e40251779',
        'counts' => [1 => 14, 2 => 10, 3 => 5],
        'source_users' => [1 => 26, 2 => 18, 3 => 7], 'links' => 39],
    'B' => ['port' => 18201,
        'root' => '/Users/wiscourper/.codex/score-rank-isolated-20261004/B',
        'source' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/ai_api',
        'template' => '/private/tmp/zhct-http-ai-macos.x2CCZX',
        'old_pid' => 13127, 'old_port' => 18101,
        'old_socket' => '0xc1b5193ecc2ef2dc',
        'database_sha' => '8c46a6d9cd277f2950818d09162f639a62f135a5eb6553e6c61ce2c19167fd02',
        'department_sha' => '9b392fa3664b286bfcd27b1f8c6bc3f8e5cc0f4ab0ea495d764edf2e40251779',
        'counts' => [1 => 14, 2 => 10, 3 => 5],
        'source_users' => [1 => 26, 2 => 18, 3 => 7], 'links' => 39],
    'C' => ['port' => 18202, 'root' => '/tmp/cdx-score-rank-isolated-20261004/C',
        'source' => '/tmp/zhct-dm-poc-20260928.fcpiiN/ai_api',
        'template' => '/tmp/zhct-ai-http-20260929.3XDj3w',
        'old_pid' => 53772, 'old_port' => 18102,
        'old_socket' => '441267',
        'database_sha' => 'e7402b1ad50fcf4205326d93dc22e4f491c5052c60fcbe36214cd4e07b55343c',
        'department_sha' => 'aab4b9cd1be38c469958babb5eaf7780e09f03f9c36d3cb431d98afa99a1ffd9',
        'counts' => [1 => 2, 2 => 3, 3 => 1],
        'source_users' => [1 => 50, 2 => 50, 3 => 8], 'links' => 84],
];
$spec = $specs[$lane];
$spec['lane'] = $lane;
$stage = 'static-boundary';
$recovery = [];
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted warning', 0, $severity);
});
set_exception_handler(static function (Throwable $error) use (&$stage, &$recovery): void {
    $result = ['status' => 'halt', 'stage' => $stage,
        'class' => get_class($error), 'code' => (string)$error->getCode(),
        'error_sha256' => hash('sha256', $error->getMessage())];
    if ($recovery !== []) $result['manual_recovery'] = $recovery;
    echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(3);
});

function cdxScoreBootstrapTarget(array $spec, string $name): string
{
    if (($spec['lane'] ?? '') === 'C' && in_array($name, ['think', 'route'], true)) {
        return $spec['template'] . '/' . $name;
    }
    return $spec['source'] . '/' . $name;
}

function cdxScoreCPrivateBootstrap(array $spec): void
{
    if (($spec['lane'] ?? '') !== 'C') return;
    clearstatcache(true);
    $route = $spec['template'] . '/route';
    $think = $spec['template'] . '/think';
    $uid = posix_geteuid();
    if (!is_dir($route) || is_link($route) || fileowner($route) !== $uid
        || !in_array(fileperms($route) & 0777, [0700, 0750, 0755], true)) {
        throw new RuntimeException('C private route directory owner/mode drift');
    }
    $names = array_values(array_diff(scandir($route) ?: [], ['.', '..']));
    sort($names, SORT_STRING);
    if ($names !== ['app.php']) {
        throw new RuntimeException('C private route full tree drift');
    }
    $routeFile = $route . '/app.php';
    if (!is_file($routeFile) || is_link($routeFile)
        || fileowner($routeFile) !== $uid
        || !in_array(fileperms($routeFile) & 0777, [0600, 0640, 0644], true)
        || hash_file('sha256', $routeFile) !== SCORE_C_ROUTE_FILE_SHA
        || hash('sha256', json_encode(['app.php' => SCORE_C_ROUTE_FILE_SHA],
                JSON_UNESCAPED_SLASHES)) !== SCORE_C_ROUTE_TREE_SHA) {
        throw new RuntimeException('C private route file tree SHA/type drift');
    }
    if (!is_file($think) || is_link($think)
        || fileowner($think) !== $uid
        || !in_array(fileperms($think) & 0777, [0600, 0640, 0644], true)
        || hash_file('sha256', $think) !== SCORE_SOURCE_HASHES['think']) {
        throw new RuntimeException('C private think file SHA/type drift');
    }
}

function cdxScoreAssetsAndSource(array $spec, string $lane): void
{
    if (($lane === 'C') !== (PHP_OS_FAMILY === 'Linux')) {
        throw new RuntimeException('lane and host platform mismatch');
    }
    foreach (SCORE_ASSET_HASHES as $name => $sha) {
        if (!is_file(__DIR__ . '/' . $name)
            || hash_file('sha256', __DIR__ . '/' . $name) !== $sha) {
            throw new RuntimeException('reviewed test-only asset drift');
        }
    }
    foreach (SCORE_SOURCE_HASHES as $name => $sha) {
        if ($lane === 'C' && $name === 'think') continue;
        if (!is_file($spec['source'] . '/' . $name)
            || hash_file('sha256', $spec['source'] . '/' . $name) !== $sha) {
            throw new RuntimeException('actual A/B/C Composer source drift');
        }
    }
    $rankModel = $spec['source'] . '/app/api/model/user/UserScore.php';
    if (!is_file($rankModel) || is_link($rankModel)
        || hash_file('sha256', $rankModel) !== SCORE_RANK_MODEL_HASHES[$lane]) {
        throw new RuntimeException('lane-specific rank model source drift');
    }
    if (hash_file('sha256', $spec['template'] . '/config/database.php')
            !== $spec['database_sha']
        || hash_file('sha256', $spec['template'] . '/config/cache.php') !== SCORE_CACHE_SHA
        || hash_file('sha256', $spec['template'] . '/config/log.php') !== SCORE_LOG_SHA
        || hash_file('sha256', $spec['template'] . '/external_stubs.php') !== SCORE_STUB_SHA) {
        throw new RuntimeException('private template DB/cache/log/stub drift');
    }
    $files = glob($spec['template'] . '/config/*.php') ?: [];
    if (count($files) !== 23) throw new RuntimeException('template 23-file config tree drift');
    cdxScoreCPrivateBootstrap($spec);
    $oldOwners = cdxScoreListenerPids($spec['old_port'], $lane);
    if ($oldOwners !== [(int)$spec['old_pid']]) {
        throw new RuntimeException('original POC process ownership drift');
    }
    if ($lane === 'C') {
        $machineId = trim((string)@file_get_contents('/etc/machine-id'));
        $host = gethostname();
        $socket = (string)shell_exec("ss -H -ltnpe '( sport = :18102 )' 2>/dev/null");
        if ($machineId === '' || $host === false
            || hash('sha256', $host . '|' . $machineId)
                !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9'
            || strpos($socket, 'ino:' . $spec['old_socket']) === false
            || @readlink('/proc/' . $spec['old_pid'] . '/fd/3')
                !== 'socket:[' . $spec['old_socket'] . ']') {
            throw new RuntimeException('C nonproduction host/socket identity drift');
        }
    } else {
        $socket = (string)shell_exec('/usr/sbin/lsof -nP -iTCP:'
            . $spec['old_port'] . ' -sTCP:LISTEN 2>/dev/null');
        if (strpos($socket, $spec['old_socket']) === false) {
            throw new RuntimeException('A/B original POC socket identity drift');
        }
    }
}

function cdxScoreListenerPids(int $port, string $lane): array
{
    $command = $lane === 'C'
        ? "ss -H -ltnp '( sport = :" . $port . " )' 2>/dev/null"
        : '/usr/sbin/lsof -nP -t -iTCP:' . $port . ' -sTCP:LISTEN 2>/dev/null';
    $raw = trim((string)shell_exec($command));
    if ($raw === '') return [];
    if ($lane === 'C') {
        preg_match_all('/\bpid=(\d+)\b/', $raw, $matches);
        return array_values(array_unique(array_map('intval', $matches[1])));
    }
    return array_values(array_unique(array_map('intval',
        array_filter(explode("\n", $raw), 'ctype_digit'))));
}

function cdxScorePrivateEnvironment(array $spec, string $lane): array
{
    if ($lane !== 'C') {
        $path = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';
        $ini = parse_ini_file($path, true, INI_SCANNER_RAW);
        if (realpath($path) !== $path || !is_array($ini)
            || !isset($ini['DATABASE'], $ini['DATABASE_ZHCT'])) {
            throw new RuntimeException('code-local developer connections unavailable');
        }
        foreach (['DATABASE', 'DATABASE_ZHCT'] as $section) {
            if (strtolower((string)($ini[$section]['CHARSET'] ?? '')) !== 'utf8') {
                throw new RuntimeException('reviewed code-local connection charset drift');
            }
        }
        $env = ['POC_DIALECT' => 'mysql', 'POC_EXTERNAL_STUBS' => '1'];
        foreach (['DATABASE' => 'POC_AI_', 'DATABASE_ZHCT' => 'POC_'] as $section => $prefix) {
            foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT',
                'DATABASE' => 'SCHEMA', 'USERNAME' => 'USER',
                'PASSWORD' => 'PASSWORD'] as $from => $to) {
                $value = (string)($ini[$section][$from] ?? '');
                if ($value === '') throw new RuntimeException('developer DB setting incomplete');
                $env[$prefix . $to] = $value;
            }
        }
        return $env;
    }
    $raw = @file_get_contents('/proc/' . $spec['old_pid'] . '/environ');
    if ($raw === false) throw new RuntimeException('fixed C process env unavailable');
    $all = [];
    foreach (explode("\0", $raw) as $part) {
        $at = strpos($part, '=');
        if ($at !== false) $all[substr($part, 0, $at)] = substr($part, $at + 1);
    }
    foreach (['POC_DIALECT' => 'dm', 'POC_EXTERNAL_STUBS' => '1',
        'POC_AI_HOST' => '127.0.0.1', 'POC_AI_PORT' => '5238',
        'POC_AI_SCHEMA' => 'AIZHCT_APP', 'POC_AI_USER' => 'AIZHCT_APP',
        'POC_HOST' => '127.0.0.1', 'POC_PORT' => '5237',
        'POC_SCHEMA' => 'ZHCT_APP', 'POC_USER' => 'ZHCT_APP'] as $key => $value) {
        if (($all[$key] ?? '') !== $value) throw new RuntimeException('C DB identity env drift');
    }
    $out = [];
    foreach (['POC_DIALECT', 'POC_EXTERNAL_STUBS', 'POC_AI_HOST',
        'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER', 'POC_AI_PASSWORD',
        'POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER', 'POC_PASSWORD',
        'LD_LIBRARY_PATH', 'DM_SVC_PATH'] as $name) {
        if (isset($all[$name])) $out[$name] = $all[$name];
    }
    if (empty($out['POC_AI_PASSWORD']) || empty($out['POC_PASSWORD'])) {
        throw new RuntimeException('C private DB credentials missing');
    }
    return $out;
}

function cdxScoreConnections(array $env, string $lane): array
{
    if ($lane === 'C') {
        $ai = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
            $env['POC_AI_USER'], $env['POC_AI_PASSWORD'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_CASE => PDO::CASE_LOWER]);
        $store = new PDO('dm:host=127.0.0.1;port=5237;schema=ZHCT_APP',
            $env['POC_USER'], $env['POC_PASSWORD'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_CASE => PDO::CASE_LOWER]);
        foreach ([[$ai, 'AIZHCT_APP'], [$store, 'ZHCT_APP']] as [$db, $schema]) {
            $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']) !== $schema
                || strtoupper((string)$identity['current_schema']) !== $schema) {
                throw new RuntimeException('C AI/Store schema identity drift');
            }
        }
        return [$ai, $store];
    }
    $connections = [];
    foreach (['POC_AI_' => SCORE_DEV_AI_SHA, 'POC_' => SCORE_DEV_STORE_SHA] as $prefix => $sha) {
        $db = new PDO('mysql:host=' . $env[$prefix . 'HOST']
            . ';port=' . (int)$env[$prefix . 'PORT'] . ';dbname=' . $env[$prefix . 'SCHEMA']
            . ';charset=utf8', $env[$prefix . 'USER'], $env[$prefix . 'PASSWORD'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
        $identity = (string)$db->query("SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
            ->fetchColumn();
        if (!hash_equals($sha, hash('sha256', $identity))) {
            throw new RuntimeException('developer AI/Store DB identity drift');
        }
        $connections[] = $db;
    }
    return $connections;
}

function cdxScoreJournalPath(array $spec): string
{
    return $spec['root'] . '/runtime/lifecycle.ndjson';
}

function cdxScoreAppend(array $spec, array $event): void
{
    $path = cdxScoreJournalPath($spec);
    $file = fopen($path, 'a');
    if ($file === false) throw new RuntimeException('private lifecycle journal unavailable');
    chmod($path, 0600);
    $text = json_encode($event, JSON_UNESCAPED_SLASHES) . "\n";
    if (fwrite($file, $text) !== strlen($text) || !fflush($file)) {
        throw new RuntimeException('private lifecycle journal append failed');
    }
    fclose($file);
}

function cdxScoreLastState(array $spec): array
{
    $path = cdxScoreJournalPath($spec);
    if (!is_file($path) || is_link($path)
        || (fileperms($path) & 0777) !== 0600) {
        throw new RuntimeException('private lifecycle journal missing');
    }
    $rows = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if (!is_array($rows) || !$rows) throw new RuntimeException('empty lifecycle journal');
    $spawned = null;
    $started = null;
    $last = null;
    foreach ($rows as $row) {
        $entry = json_decode($row, true);
        if (!is_array($entry)) throw new RuntimeException('lifecycle journal parse drift');
        if (($entry['event'] ?? '') === 'spawned') $spawned = $entry;
        if (($entry['event'] ?? '') === 'started') $started = $entry;
        $last = $entry;
    }
    if ($started === null && $spawned === null) {
        throw new RuntimeException('no reviewed child spawn record');
    }
    return [$started ?? $spawned, $last];
}

function cdxScoreOwnProcess(array $spec, string $lane, int $pid): void
{
    if ($pid <= 0 || $pid === $spec['old_pid']
        || cdxScoreListenerPids($spec['port'], $lane) !== [$pid]) {
        throw new RuntimeException('new listener PID ownership drift');
    }
    $command = $lane === 'C'
        ? (string)@file_get_contents('/proc/' . $pid . '/cmdline')
        : (string)shell_exec('/bin/ps -p ' . $pid . ' -o command=');
    if (strpos($command, '127.0.0.1:' . $spec['port']) === false
        || strpos($command, $spec['root'] . '/public') === false) {
        throw new RuntimeException('new PHP command/root mismatch');
    }
}

function cdxScoreStoppableChild(array $spec, string $lane, int $pid): bool
{
    if ($pid <= 0 || $pid === $spec['old_pid']) {
        throw new RuntimeException('stop target is not this run child');
    }
    $owners = cdxScoreListenerPids($spec['port'], $lane);
    if ($owners !== [] && $owners !== [$pid]) {
        throw new RuntimeException('new port owned by different process');
    }
    if (!posix_kill($pid, 0)) return false;
    $command = $lane === 'C'
        ? (string)@file_get_contents('/proc/' . $pid . '/cmdline')
        : (string)shell_exec('/bin/ps -p ' . $pid . ' -o command=');
    if (strpos($command, '127.0.0.1:' . $spec['port']) === false
        || strpos($command, $spec['root'] . '/public') === false) {
        throw new RuntimeException('own spawned PID command/root drift');
    }
    return true;
}

function cdxScoreBuildRoot(array $spec): void
{
    $root = $spec['root'];
    if (file_exists($root) || is_link($root)) {
        throw new RuntimeException('new private runtime root already exists');
    }
    $parent = dirname($root);
    if (!is_dir($parent) && !mkdir($parent, 0700, true)) {
        throw new RuntimeException('private runtime parent creation failed');
    }
    if (is_link($parent) || fileowner($parent) !== posix_geteuid()
        || (fileperms($parent) & 0777) !== 0700
        || !mkdir($root, 0700) || !chmod($root, 0700)) {
        throw new RuntimeException('new private runtime root creation failed');
    }
    foreach (['public', 'config', 'runtime', 'runtime/cache', 'runtime/log',
        'runtime/api', 'runtime/api/log'] as $name) {
        if (!mkdir($root . '/' . $name, 0700)) {
            throw new RuntimeException('new private runtime directory creation failed');
        }
    }
    foreach (['app', 'cores', 'route', 'think', 'vendor'] as $name) {
        if (!symlink(cdxScoreBootstrapTarget($spec, $name), $root . '/' . $name)) {
            throw new RuntimeException('reviewed source symlink creation failed');
        }
    }
    foreach (glob($spec['template'] . '/config/*.php') ?: [] as $source) {
        $name = basename($source);
        $replacement = $name === 'queue.php' ? __DIR__ . '/score_rank_queue.disabled.php'
            : ($name === 'message.php' ? __DIR__ . '/score_rank_message.disabled.php' : $source);
        if (!copy($replacement, $root . '/config/' . $name)
            || !chmod($root . '/config/' . $name, 0600)) {
            throw new RuntimeException('reviewed isolated config copy failed');
        }
    }
    foreach ([__DIR__ . '/score_rank_router.php' => $root . '/public/index.php',
        $spec['template'] . '/external_stubs.php' => $root . '/external_stubs.php'] as $src => $dst) {
        if (!copy($src, $dst) || !chmod($dst, 0600)) {
            throw new RuntimeException('reviewed test router/stub copy failed');
        }
    }
    $log = $root . '/runtime/server.log';
    $file = fopen($log, 'x');
    if ($file === false) throw new RuntimeException('private server log creation failed');
    fclose($file);
    chmod($log, 0600);
}

// Freeze every copied config file, including ones not directly used by Score.
function cdxScoreTemplateConfigManifest(array $spec): array
{
    clearstatcache(true);
    $dir = $spec['template'] . '/config';
    if (!is_dir($dir) || is_link($dir)) {
        throw new RuntimeException('private template config directory drift');
    }
    $names = array_values(array_diff(scandir($dir) ?: [], ['.', '..']));
    sort($names, SORT_STRING);
    if (count($names) !== 23) {
        throw new RuntimeException('private template config file count drift');
    }
    $result = [];
    foreach ($names as $name) {
        $path = $dir . '/' . $name;
        if (!preg_match('/^[a-zA-Z0-9_]+\.php$/D', $name)
            || !is_file($path) || is_link($path)) {
            throw new RuntimeException('private template config file type drift');
        }
        $result[$name] = hash_file('sha256', $path);
    }
    foreach (['database.php' => $spec['database_sha'],
        'cache.php' => SCORE_CACHE_SHA, 'log.php' => SCORE_LOG_SHA] as $name => $sha) {
        if (($result[$name] ?? '') !== $sha) {
            throw new RuntimeException('private template critical config drift');
        }
    }
    return $result;
}

// Runtime/cache/log contents change normally; only executable/config bytes,
// symlink targets and enclosing ownership/permissions are frozen here.
function cdxScoreRootCodeManifest(array $spec, array $template): array
{
    clearstatcache(true);
    cdxScoreCPrivateBootstrap($spec);
    $root = $spec['root'];
    $uid = posix_geteuid();
    $dirs = ['.', 'public', 'config', 'runtime', 'runtime/cache',
        'runtime/log', 'runtime/api', 'runtime/api/log'];
    foreach ($dirs as $name) {
        $path = $name === '.' ? $root : $root . '/' . $name;
        if (!is_dir($path) || is_link($path) || fileowner($path) !== $uid
            || (fileperms($path) & 0777) !== 0700) {
            throw new RuntimeException('new root directory ownership or mode drift');
        }
    }
    $names = array_values(array_diff(scandir($root . '/config') ?: [], ['.', '..']));
    sort($names, SORT_STRING);
    $expectedNames = array_keys($template);
    sort($expectedNames, SORT_STRING);
    if ($names !== $expectedNames) {
        throw new RuntimeException('new root complete config tree differs');
    }
    $files = [];
    foreach ($expectedNames as $name) {
        $expected = $name === 'queue.php'
            ? SCORE_ASSET_HASHES['score_rank_queue.disabled.php']
            : ($name === 'message.php'
                ? SCORE_ASSET_HASHES['score_rank_message.disabled.php']
                : $template[$name]);
        $path = $root . '/config/' . $name;
        if (!is_file($path) || is_link($path) || fileowner($path) !== $uid
            || (fileperms($path) & 0777) !== 0600
            || hash_file('sha256', $path) !== $expected) {
            throw new RuntimeException('new root copied config bytes/mode drift');
        }
        $files['config/' . $name] = $expected;
    }
    foreach (['public/index.php' => SCORE_ASSET_HASHES['score_rank_router.php'],
        'external_stubs.php' => SCORE_STUB_SHA] as $name => $sha) {
        $path = $root . '/' . $name;
        if (!is_file($path) || is_link($path) || fileowner($path) !== $uid
            || (fileperms($path) & 0777) !== 0600
            || hash_file('sha256', $path) !== $sha) {
            throw new RuntimeException('new root router/stub bytes/mode drift');
        }
        $files[$name] = $sha;
    }
    foreach (['app', 'cores', 'route', 'think', 'vendor'] as $name) {
        $path = $root . '/' . $name;
        if (!is_link($path) || readlink($path) !== cdxScoreBootstrapTarget($spec, $name)
            || ($name === 'think'
                ? (!is_file($path) || hash_file('sha256', $path)
                    !== SCORE_SOURCE_HASHES['think'])
                : !is_dir($path))) {
            throw new RuntimeException('new root source symlink target drift');
        }
    }
    $log = $root . '/runtime/server.log';
    if (!is_file($log) || is_link($log) || fileowner($log) !== $uid
        || (fileperms($log) & 0777) !== 0600) {
        throw new RuntimeException('new root private server log drift');
    }
    ksort($files, SORT_STRING);
    return ['files' => $files, 'directories_mode' => '0700',
        'files_mode' => '0600', 'owner_uid' => $uid,
        'source_root' => $spec['source'],
        'c_route_tree_sha256' => ($spec['lane'] ?? '') === 'C'
            ? SCORE_C_ROUTE_TREE_SHA : null];
}

function cdxScoreSaveCodeManifest(array $spec, array $manifest): string
{
    $path = $spec['root'] . '/runtime/code-manifest.json';
    $json = json_encode($manifest, JSON_UNESCAPED_SLASHES);
    if (!is_string($json)) throw new RuntimeException('code manifest encoding failed');
    $file = fopen($path, 'x');
    if ($file === false) throw new RuntimeException('new code manifest exists');
    if (!chmod($path, 0600) || fwrite($file, $json) !== strlen($json)
        || !fflush($file)) {
        throw new RuntimeException('new code manifest write failed');
    }
    fclose($file);
    return hash('sha256', $json);
}

function cdxScoreCheckCodeManifest(array $spec, array $record): void
{
    clearstatcache(true);
    $path = $spec['root'] . '/runtime/code-manifest.json';
    if (!is_file($path) || is_link($path)
        || fileowner($path) !== posix_geteuid()
        || (fileperms($path) & 0777) !== 0600) {
        throw new RuntimeException('private code manifest missing or mode drift');
    }
    $json = file_get_contents($path);
    if (!is_string($json) || !preg_match('/^[a-f0-9]{64}$/D',
            (string)($record['code_manifest_sha256'] ?? ''))
        || !hash_equals($record['code_manifest_sha256'], hash('sha256', $json))) {
        throw new RuntimeException('saved code manifest SHA drift');
    }
    $template = cdxScoreTemplateConfigManifest($spec);
    $templateSha = hash('sha256', json_encode($template, JSON_UNESCAPED_SLASHES));
    if (!hash_equals((string)($record['template_manifest_sha256'] ?? ''),
            $templateSha)
        || !hash_equals(hash('sha256', json_encode(
                cdxScoreRootCodeManifest($spec, $template), JSON_UNESCAPED_SLASHES)),
            hash('sha256', $json))) {
        throw new RuntimeException('actual source/template/new root drift');
    }
}

function cdxScoreAssertNoStubHits(array $spec): void
{
    $path = $spec['root'] . '/runtime/poc-stub-hits.log';
    clearstatcache(true, $path);
    if (file_exists($path) || is_link($path)) {
        if (!is_file($path) || is_link($path)
            || fileowner($path) !== posix_geteuid()
            || (int)filesize($path) !== 0) {
            throw new RuntimeException('unexpected external/queue stub invocation');
        }
    }
}

function cdxScoreGet(string $url, int $limit): array
{
    $context = stream_context_create(['http' => ['method' => 'GET',
        'timeout' => 15, 'follow_location' => 0, 'ignore_errors' => true,
        'header' => "platform: MP-WEIXIN\r\nversion: 2.0.51\r\nenv-version: develop\r\nAccess-Token: \r\nContent-Type: application/json;charset=utf-8\r\n"]]);
    $handle = @fopen($url, 'rb', false, $context);
    if ($handle === false) throw new RuntimeException('only-loopback GET failed');
    $raw = stream_get_contents($handle, $limit + 1);
    $meta = stream_get_meta_data($handle);
    fclose($handle);
    if (!is_string($raw) || strlen($raw) > $limit || !empty($meta['timed_out'])) {
        throw new RuntimeException('loopback GET timeout or response cap');
    }
    $line = (string)(($meta['wrapper_data'] ?? [])[0] ?? '');
    if (!preg_match('/^HTTP\/\S+\s+200\b/', $line)) {
        throw new RuntimeException('loopback GET HTTP not 200');
    }
    $json = json_decode($raw, true);
    if (!is_array($json)) throw new RuntimeException('loopback GET non-JSON');
    return $json;
}

function cdxRankReadRows(PDO $db, string $sql, array $params, int $max): array
{
    $started = microtime(true);
    $stmt = $db->prepare($sql);
    $stmt->execute($params);
    $rows = [];
    while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
        $rows[] = array_change_key_case($row, CASE_LOWER);
        if (count($rows) > $max) throw new RuntimeException('rank original-row cap exceeded');
    }
    $stmt->closeCursor();
    if (microtime(true) - $started > 5.0) {
        throw new RuntimeException('rank original-row SELECT exceeded five seconds');
    }
    return $rows;
}

function cdxRankDepartmentUserIds(PDO $store, bool $dm, string $uuid): array
{
    $staff = $dm ? 'YDY_STAFF' : 'ydy_staff';
    $link = $dm ? 'YDY_STAFF_DEPARTMENT' : 'ydy_staff_department';
    $tail = $dm ? 'FETCH FIRST 1001 ROWS ONLY' : 'LIMIT 1001';
    $sql = 'SELECT DISTINCT s.AI_USER_ID FROM ' . $staff . ' s JOIN ' . $link
        . ' sd ON sd.STAFF_UUID=s.UUID WHERE sd.DEPARTMENT_UUID=?'
        . ' AND s.IS_SHOW=1 AND s.AI_USER_ID>0 ORDER BY s.AI_USER_ID ' . $tail;
    $rows = cdxRankReadRows($store, $sql, [$uuid], 1001);
    if (count($rows) > 1000 || !$rows) {
        throw new RuntimeException('rank department user count outside approved bound');
    }
    $ids = [];
    foreach ($rows as $row) {
        $id = (int)($row['ai_user_id'] ?? 0);
        if ($id <= 0) throw new RuntimeException('rank department numeric AI ID invalid');
        $ids[$id] = true;
    }
    return array_keys($ids);
}

function cdxRankOriginalRows(PDO $ai, bool $dm, array $ids, int $type): array
{
    $score = $dm ? 'YOSHOP_USER_SCORE' : 'yoshop_user_score';
    $user = $dm ? 'YOSHOP_USER' : 'yoshop_user';
    $tail = $dm ? 'FETCH FIRST 1001 ROWS ONLY' : 'LIMIT 1001';
    $marks = implode(',', array_fill(0, count($ids), '?'));
    $sql = 'SELECT s.USER_ID,s.STORE_ID,s.TYPE,s.SCORE,u.NICK_NAME FROM '
        . $score . ' s JOIN ' . $user . ' u ON u.USER_ID=s.USER_ID AND u.IS_DELETE=0'
        . ' WHERE s.STORE_ID=10001 AND s.TYPE=? AND s.USER_ID IN (' . $marks . ')'
        . ' ORDER BY s.SCORE DESC,s.USER_ID ASC ' . $tail;
    $rows = cdxRankReadRows($ai, $sql, array_merge([$type], $ids), 1001);
    if (!$rows || count($rows) > 1000) {
        throw new RuntimeException('rank original score rows outside approved bound');
    }
    return $rows;
}

function cdxRankTuple(array $row, bool $response): array
{
    if ($response && (!array_key_exists('bestCount', $row)
        || !array_key_exists('nickname', $row)
        || (string)$row['bestCount'] !== (string)($row['score'] ?? '')
        || (string)$row['nickname'] !== (string)($row['nick_name'] ?? '')
        || gettype($row['bestCount']) !== gettype($row['score'] ?? null)
        || gettype($row['nickname']) !== gettype($row['nick_name'] ?? null))) {
        throw new RuntimeException('rank response alias value contract drift');
    }
    foreach (['user_id', 'store_id', 'type', 'score', 'nick_name'] as $key) {
        if (!array_key_exists($key, $row)) {
            throw new RuntimeException('rank source/response key missing');
        }
    }
    return [(string)$row['user_id'], (string)$row['store_id'],
        (string)$row['type'], (string)$row['score'], (string)$row['nick_name']];
}

function cdxRankMultiset(array $rows, bool $response): array
{
    $keys = [];
    foreach ($rows as $row) {
        $key = json_encode(cdxRankTuple($row, $response),
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($key === false) throw new RuntimeException('rank canonical row encoding failed');
        $keys[$key] = ($keys[$key] ?? 0) + 1;
    }
    ksort($keys, SORT_STRING);
    return $keys;
}

function cdxRankCompareRows(array $source, array $returned, int $type): array
{
    if (!$returned || count($returned) > 50 || count($source) < count($returned)) {
        throw new RuntimeException('rank nonempty pagination size contract failed');
    }
    $lastScore = null;
    $fieldTypes = null;
    $expectedKeys = ['bestCount', 'nick_name', 'nickname', 'score',
        'store_id', 'type', 'user_id'];
    foreach ($returned as $row) {
        if (!is_array($row) || (int)($row['store_id'] ?? 0) !== 10001
            || (int)($row['type'] ?? 0) !== $type) {
            throw new RuntimeException('rank row store/type contract failed');
        }
        $keys = array_keys($row);
        sort($keys, SORT_STRING);
        if ($keys !== $expectedKeys) {
            throw new RuntimeException('rank response row field set drift');
        }
        $score = (int)($row['score'] ?? -1);
        if ($lastScore !== null && $score > $lastScore) {
            throw new RuntimeException('rank score descending contract failed');
        }
        $lastScore = $score;
        $types = array_map('gettype', $row);
        ksort($types);
        foreach (['nick_name', 'nickname'] as $nameKey) {
            if (!in_array($types[$nameKey], ['string', 'NULL'], true)) {
                throw new RuntimeException('rank nickname JSON type invalid');
            }
            // Nullable nicknames are data-dependent; compare their allowed
            // contract, not which snapshot happened to contain a NULL.
            $types[$nameKey] = 'string|null';
        }
        if ($fieldTypes !== null && $fieldTypes !== $types) {
            throw new RuntimeException('rank response mixed JSON field types');
        }
        $fieldTypes = $types;
    }
    $allSource = cdxRankMultiset($source, false);
    $response = cdxRankMultiset($returned, true);
    $tieAtCutoff = false;
    if (count($source) <= 50) {
        if ($allSource !== $response) {
            throw new RuntimeException('rank response multiset differs from own full source');
        }
    } else {
        if (count($returned) !== 50) throw new RuntimeException('rank page size not 50');
        $cutoff = (int)$source[49]['score'];
        $tieAtCutoff = (int)$source[50]['score'] === $cutoff;
        $allowed = [];
        $above = [];
        foreach ($source as $row) {
            $score = (int)$row['score'];
            if ($score < $cutoff) continue;
            $key = json_encode(cdxRankTuple($row, false),
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            $allowed[$key] = ($allowed[$key] ?? 0) + 1;
            if ($score > $cutoff) $above[$key] = ($above[$key] ?? 0) + 1;
        }
        foreach ($response as $key => $count) {
            if ($count > ($allowed[$key] ?? 0)) {
                throw new RuntimeException('rank response outside own top-score source');
            }
        }
        foreach ($above as $key => $count) {
            if (($response[$key] ?? 0) < $count) {
                throw new RuntimeException('rank response omitted above-cutoff source row');
            }
        }
    }
    return ['source_rows' => count($source), 'response_rows' => count($returned),
        'source_multiset_sha256' => hash('sha256', json_encode($allSource)),
        'response_multiset_sha256' => hash('sha256', json_encode($response)),
        'field_types_sha256' => hash('sha256', json_encode($fieldTypes)),
        'tie_at_page_cutoff' => $tieAtCutoff, 'score_nonincreasing' => true];
}

try {
    if (!in_array($phase, ['--stop', '--archive'], true)) {
        cdxScoreAssetsAndSource($spec, $lane);
    }
    $root = $spec['root'];
    $port = $spec['port'];
    if ($phase === '--plan') {
        $stage = 'read-only-plan';
        if (file_exists($root) || is_link($root)
            || cdxScoreListenerPids($port, $lane) !== []) {
            throw new RuntimeException('planned root or loopback port already occupied');
        }
        echo json_encode(['status' => 'default-deny-plan-only', 'lane' => $lane,
            'new_port' => $port, 'existing_services_untouched' => true,
            'source_code_sha_locked' => true, 'run_root_not_created' => true,
            'business_http_or_dml' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
        exit(0);
    }
    if ($phase === '--start') {
        $stage = 'derive-natural-department-in-memory';
        if (file_exists($root) || is_link($root)
            || cdxScoreListenerPids($port, $lane) !== []) {
            throw new RuntimeException('new runtime root or port not empty');
        }
        $php = $lane === 'C' ? '/www/server/php/74/bin/php'
            : '/opt/homebrew/Cellar/php@7.4/7.4.33_13/bin/php';
        if (!function_exists('pcntl_fork') || !function_exists('pcntl_exec')
            || !function_exists('posix_setsid') || !is_executable($php)) {
            throw new RuntimeException('private PHP/pcntl/posix runtime unavailable');
        }
        if ($lane === 'C') {
            foreach (['/www/server/php/74/etc/php-cli.ini',
                '/home/dmdba/dmdbms/drivers/php_pdo/libphp74_dm.so',
                '/home/dmdba/dmdbms/drivers/php_pdo/php74_pdo_dm.so'] as $file) {
                if (!is_file($file) || !is_readable($file)) {
                    throw new RuntimeException('C private PHP/DM startup dependency unavailable');
                }
            }
        }
        umask(0077);
        $env = cdxScorePrivateEnvironment($spec, $lane);
        [$ai, $store] = cdxScoreConnections($env, $lane);
        require_once __DIR__ . '/score_rank_candidate_lib.php';
        $candidate = cdxDeriveRankDepartment($ai, $store, $lane === 'C');
        if ($candidate['sha256'] !== $spec['department_sha']
            || $candidate['counts'] !== $spec['counts']
            || $candidate['source_users_by_type'] !== $spec['source_users']
            || $candidate['links'] !== $spec['links']) {
            throw new RuntimeException('frozen natural department SHA/counts drift');
        }
        $uuid = $candidate['uuid'];
        unset($candidate['uuid']);
        $ai = null;
        $store = null;
        $stage = 'build-new-private-runtime-only';
        $recovery = ['root' => $root, 'port' => $port, 'pid' => 0,
            'old_pid' => $spec['old_pid'], 'manual_only' => true,
            'do_not_delete_or_retry' => true];
        $templateManifest = cdxScoreTemplateConfigManifest($spec);
        $templateManifestSha = hash('sha256', json_encode(
            $templateManifest, JSON_UNESCAPED_SLASHES));
        cdxScoreBuildRoot($spec);
        if (!hash_equals($templateManifestSha, hash('sha256', json_encode(
                cdxScoreTemplateConfigManifest($spec), JSON_UNESCAPED_SLASHES)))) {
            throw new RuntimeException('template changed during isolated root creation');
        }
        $codeManifestSha = cdxScoreSaveCodeManifest($spec,
            cdxScoreRootCodeManifest($spec, $templateManifest));
        $manifestRecord = ['code_manifest_sha256' => $codeManifestSha,
            'template_manifest_sha256' => $templateManifestSha];
        cdxScoreCheckCodeManifest($spec, $manifestRecord);
        cdxScoreAssertNoStubHits($spec);
        cdxScoreAppend($spec, ['event' => 'prepared', 'lane' => $lane,
            'port' => $port, 'department_sha256' => $candidate['sha256'],
            'type_user_counts' => $candidate['counts'],
            'router_sha256' => SCORE_ASSET_HASHES['score_rank_router.php'],
            'queue_disabled' => true, 'message_disabled' => true]
            + $manifestRecord);
        $env['PATH'] = '/usr/bin:/bin';
        $env['POC_TARGET_ROOT'] = $root;
        $env['POC_EXTERNAL_STUBS'] = '1';
        $env['PHP_ZGYH_DEPARTMENT_UUID'] = $uuid;
        $env['CDX_SCORE_DEPT_SHA'] = $candidate['sha256'];
        $env['PHP_CACHE_DRIVER'] = 'file';
        $env['PHP_LOG_CHANNEL'] = 'file';
        $args = [$php];
        if ($lane === 'C') {
            array_push($args, '-c', '/www/server/php/74/etc/php-cli.ini',
                '-d', 'extension=/home/dmdba/dmdbms/drivers/php_pdo/libphp74_dm.so',
                '-d', 'extension=/home/dmdba/dmdbms/drivers/php_pdo/php74_pdo_dm.so');
        }
        array_push($args, '-S', '127.0.0.1:' . $port,
            '-t', $root . '/public', $root . '/public/index.php');
        $shell = 'exec ' . implode(' ', array_map('escapeshellarg', $args))
            . ' >> ' . escapeshellarg($root . '/runtime/server.log') . ' 2>&1';
        $stage = 'start-new-child-only';
        $pid = pcntl_fork();
        if ($pid < 0) throw new RuntimeException('new private child fork failed');
        if ($pid === 0) {
            posix_setsid();
            pcntl_exec('/bin/sh', ['-c', $shell], $env);
            exit(111);
        }
        $recovery['pid'] = $pid;
        unset($uuid, $env);
        cdxScoreAppend($spec, ['event' => 'spawned', 'pid' => $pid,
            'port' => $port, 'department_sha256' => $candidate['sha256']]
            + $manifestRecord);
        $deadline = microtime(true) + 5.0;
        do {
            $owners = cdxScoreListenerPids($port, $lane);
            if ($owners === [$pid]) break;
            usleep(100000);
        } while (microtime(true) < $deadline);
        cdxScoreOwnProcess($spec, $lane, $pid);
        cdxScoreCheckCodeManifest($spec, $manifestRecord);
        cdxScoreAssertNoStubHits($spec);
        cdxScoreAppend($spec, ['event' => 'started', 'pid' => $pid,
            'port' => $port, 'department_sha256' => $candidate['sha256'],
            'type_user_counts' => $candidate['counts']]
            + $manifestRecord);
        echo json_encode(['status' => 'new-private-score-rank-started-no-http',
            'lane' => $lane, 'pid' => $pid, 'port' => $port,
            'department_sha256' => $candidate['sha256'],
            'old_pid_untouched' => $spec['old_pid']], JSON_UNESCAPED_SLASHES), PHP_EOL;
        exit(0);
    }
    $recovery = ['root' => $root, 'port' => $port, 'pid' => 0,
        'old_pid' => $spec['old_pid'], 'manual_only' => true,
        'do_not_delete_or_retry' => true];
    if (!is_dir($root) || is_link($root) || (fileperms($root) & 0777) !== 0700) {
        throw new RuntimeException('private runtime root ownership boundary drift');
    }
    [$started, $last] = cdxScoreLastState($spec);
    $pid = (int)($started['pid'] ?? 0);
    $recovery = ['root' => $root, 'port' => $port, 'pid' => $pid,
        'old_pid' => $spec['old_pid'], 'manual_only' => true,
        'do_not_delete_or_retry' => true];
    if ($phase === '--health' || $phase === '--test') {
        $stage = 'new-private-pid-socket-and-log';
        if ($phase === '--test') {
            $expectedTypeSha = (string)getenv('CDX_SCORE_EXPECT_A_TYPES_SHA');
            if ($lane !== 'A' && !preg_match('/^[a-f0-9]{64}$/D', $expectedTypeSha)) {
                throw new RuntimeException('A JSON type baseline required before B/C HTTP');
            }
            if ($lane === 'B'
                && (!preg_match('/^[a-f0-9]{64}$/D',
                        (string)getenv('CDX_SCORE_EXPECT_A_SOURCE_MULTI_SHA'))
                    || !preg_match('/^[a-f0-9]{64}$/D',
                        (string)getenv('CDX_SCORE_EXPECT_A_RESPONSE_MULTI_SHA')))) {
                throw new RuntimeException('A value multiset baselines required before B HTTP');
            }
        }
        if (($started['event'] ?? '') !== 'started') {
            throw new RuntimeException('new child not fully started');
        }
        cdxScoreOwnProcess($spec, $lane, $pid);
        cdxScoreCheckCodeManifest($spec, $started);
        cdxScoreAssertNoStubHits($spec);
        $log = $root . '/runtime/server.log';
        if (!is_file($log) || is_link($log) || (fileperms($log) & 0777) !== 0600
            || (fileperms($root) & 0777) !== 0700) {
            throw new RuntimeException('new private log/root boundary drift');
        }
        $stage = 'read-only-boundary-health';
        $boundary = cdxScoreGet('http://127.0.0.1:' . $port
            . '/__score_rank_boundary', 65536);
        if (($boundary['status'] ?? '') !== 'isolated-score-rank-boundary'
            || ($boundary['framework_env_matches_process'] ?? false) !== true
            || ($boundary['external_stubs'] ?? false) !== true
            || ($boundary['department_env_sha256'] ?? '') !== $started['department_sha256']) {
            throw new RuntimeException('actual ThinkPHP env or external stub boundary failed');
        }
        cdxScoreCheckCodeManifest($spec, $started);
        cdxScoreAssertNoStubHits($spec);
        if ($phase === '--health') {
            cdxScoreAppend($spec, ['event' => 'healthy', 'pid' => $pid]);
            echo json_encode(['status' => 'new-private-boundary-healthy',
                'lane' => $lane, 'pid' => $pid,
                'framework_env_matches_process' => true,
                'department_sha256' => $started['department_sha256'],
                'business_http_or_dml' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
            exit(0);
        }
        if (($last['event'] ?? '') !== 'healthy') {
            throw new RuntimeException('separate health phase required before three GETs');
        }
        $stage = 'same-department-original-table-snapshot';
        $env = cdxScorePrivateEnvironment($spec, $lane);
        [$ai, $store] = cdxScoreConnections($env, $lane);
        require_once __DIR__ . '/score_rank_candidate_lib.php';
        $candidate = cdxDeriveRankDepartment($ai, $store, $lane === 'C');
        if ($candidate['sha256'] !== $started['department_sha256']
            || $candidate['counts'] !== $spec['counts']) {
            throw new RuntimeException('rank configured department source drift');
        }
        $uuid = $candidate['uuid'];
        unset($candidate['uuid'], $env);
        $idsBefore = cdxRankDepartmentUserIds($store, $lane === 'C', $uuid);
        $sourceBefore = [];
        $sourceBeforeHashes = [];
        foreach ([1, 2, 3] as $type) {
            $sourceBefore[$type] = cdxRankOriginalRows($ai, $lane === 'C', $idsBefore, $type);
            $sourceBeforeHashes[$type] = hash('sha256', json_encode(
                cdxRankMultiset($sourceBefore[$type], false),
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
        $stage = 'three-whitelisted-get-and-value-type-contracts';
        $results = [];
        foreach (['weightRank' => 1, 'dietRank' => 2,
            'courseRank' => 3] as $action => $type) {
            cdxScoreCheckCodeManifest($spec, $started);
            cdxScoreAssertNoStubHits($spec);
            $body = cdxScoreGet('http://127.0.0.1:' . $port
                . '/api/user.score/' . $action . '?limit=50', 1048576);
            cdxScoreAssertNoStubHits($spec);
            $list = $body['data']['list']['data'] ?? null;
            if (($body['status'] ?? null) !== 200
                || !is_string($body['message'] ?? null)
                || !array_key_exists('trace_id', $body)
                || !is_array($body['data']['list'] ?? null)
                || !is_array($list) || !$list) {
                throw new RuntimeException('rank GET business status or nonempty list failed');
            }
            $compared = cdxRankCompareRows($sourceBefore[$type], $list, $type);
            $shell = $body['data']['list'];
            unset($shell['data']);
            $expectedTotal = count($sourceBefore[$type]);
            if ((int)($shell['total'] ?? -1) !== $expectedTotal
                || (int)($shell['per_page'] ?? -1) !== 50
                || (int)($shell['current_page'] ?? -1) !== 1
                || (int)($shell['last_page'] ?? -1)
                    !== (int)ceil($expectedTotal / 50)) {
                throw new RuntimeException('rank pagination values differ from own source');
            }
            $shellTypes = array_map('gettype', $shell);
            ksort($shellTypes);
            $topTypes = ['status' => gettype($body['status']),
                'message' => gettype($body['message']),
                'data' => gettype($body['data']),
                'trace_id' => gettype($body['trace_id'])];
            unset($body['trace_id']);
            $results[$action] = $compared + [
                'top_and_pagination_types_sha256' => hash('sha256',
                    json_encode([$topTypes, $shellTypes])),
                'body_without_trace_sha256' => hash('sha256',
                    json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))];
        }
        $stage = 'independent-original-table-after-readback';
        $idsAfter = cdxRankDepartmentUserIds($store, $lane === 'C', $uuid);
        if ($idsAfter !== $idsBefore) {
            throw new RuntimeException('rank Store department mapping changed');
        }
        foreach ([1, 2, 3] as $type) {
            $after = cdxRankOriginalRows($ai, $lane === 'C', $idsAfter, $type);
            $afterSha = hash('sha256', json_encode(cdxRankMultiset($after, false),
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
            if (!hash_equals($sourceBeforeHashes[$type], $afterSha)) {
                throw new RuntimeException('rank AI original rows changed after GET');
            }
        }
        cdxScoreCheckCodeManifest($spec, $started);
        cdxScoreAssertNoStubHits($spec);
        $sourceAggregate = hash('sha256', json_encode(array_map(
            static function (array $r): array {
                return [$r['source_rows'], $r['source_multiset_sha256']];
            }, $results), JSON_UNESCAPED_SLASHES));
        $responseAggregate = hash('sha256', json_encode(array_map(
            static function (array $r): array {
                return [$r['response_rows'], $r['response_multiset_sha256']];
            }, $results), JSON_UNESCAPED_SLASHES));
        $tieAtAnyCutoff = false;
        foreach ($results as $result) {
            $tieAtAnyCutoff = $tieAtAnyCutoff || $result['tie_at_page_cutoff'];
        }
        $typesAggregate = hash('sha256', json_encode(array_map(
            static function (array $r): array {
                return [$r['field_types_sha256'], $r['top_and_pagination_types_sha256']];
            }, $results), JSON_UNESCAPED_SLASHES));
        if ($lane !== 'A' && !hash_equals(
                (string)getenv('CDX_SCORE_EXPECT_A_TYPES_SHA'), $typesAggregate)) {
            throw new RuntimeException('A/B/C leaderboard JSON type contract differs');
        }
        if ($lane === 'B'
            && (!hash_equals((string)getenv('CDX_SCORE_EXPECT_A_SOURCE_MULTI_SHA'),
                    $sourceAggregate)
                || (!$tieAtAnyCutoff
                    && !hash_equals((string)getenv('CDX_SCORE_EXPECT_A_RESPONSE_MULTI_SHA'),
                        $responseAggregate)))) {
            throw new RuntimeException('A/B same-development-source/value multiset differs');
        }
        cdxScoreAppend($spec, ['event' => 'three_get_pass', 'pid' => $pid,
            'results' => $results, 'source_sha256' => $sourceAggregate,
            'response_multiset_sha256' => $responseAggregate,
            'tie_at_any_page_cutoff' => $tieAtAnyCutoff,
            'ab_exact_response_multiset_applicable' => !$tieAtAnyCutoff,
            'types_sha256' => $typesAggregate]);
        echo json_encode(['status' => 'three-rank-get-limited-pass',
            'lane' => $lane, 'pid' => $pid,
            'source_department_sha256' => $started['department_sha256'],
            'source_type_user_counts' => $started['type_user_counts'],
            'results' => $results,
            'source_sha256' => $sourceAggregate,
            'response_multiset_sha256' => $responseAggregate,
            'tie_at_any_page_cutoff' => $tieAtAnyCutoff,
            'ab_exact_response_multiset_applicable' => !$tieAtAnyCutoff,
            'types_sha256' => $typesAggregate,
            'names_or_ids_output' => false,
            'business_dml' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
        exit(0);
    }
    if ($phase === '--stop') {
        $stage = 'stop-only-own-new-pid';
        if (cdxScoreStoppableChild($spec, $lane, $pid)
            && !posix_kill($pid, SIGTERM)) {
            throw new RuntimeException('own new PID TERM failed');
        }
        $deadline = microtime(true) + 5.0;
        while (cdxScoreListenerPids($port, $lane) !== [] && microtime(true) < $deadline) {
            usleep(100000);
        }
        if (cdxScoreListenerPids($port, $lane) !== []) {
            throw new RuntimeException('own new service remains listening after TERM');
        }
        cdxScoreAppend($spec, ['event' => 'stopped', 'pid' => $pid]);
        echo json_encode(['status' => 'own-new-service-stopped',
            'lane' => $lane, 'pid' => $pid, 'old_pid_untouched' => $spec['old_pid']],
            JSON_UNESCAPED_SLASHES), PHP_EOL;
        exit(0);
    }
    if ($phase === '--archive') {
        $stage = 'recoverable-archive-only';
        if (($last['event'] ?? '') !== 'stopped'
            || cdxScoreListenerPids($port, $lane) !== []) {
            throw new RuntimeException('cannot archive running private root');
        }
        $archive = dirname($root) . '/archived-' . $lane . '-'
            . date('YmdHis');
        if (file_exists($archive) || is_link($archive)
            || !rename($root, $archive)) {
            throw new RuntimeException('recoverable private archive move failed');
        }
        echo json_encode(['status' => 'private-root-archived-not-deleted',
            'lane' => $lane, 'old_pid_untouched' => $spec['old_pid'],
            'archive_basename' => basename($archive)], JSON_UNESCAPED_SLASHES), PHP_EOL;
        exit(0);
    }
} catch (Throwable $error) {
    $handler = set_exception_handler(null);
    if (is_callable($handler)) $handler($error);
    exit(3);
}
