<?php
declare(strict_types=1);

// A/B/C synthetic-only electronic-menu dual-display UPDATE. Dormant by default.
$lane = (string)getenv('CDX_EM_DUAL_LANE');
$mode = $argv[1] ?? '';
$swapMode = (string)getenv('CDX_EM_DUAL_SWAP');
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 18097, 21023, 'mysql',
        '525caaeb63bc83a4f401de077b0784de49b98572b3b7732577b143138d9e7e28'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 18081, 10001, 'mysql',
        '179869c96cfdaf5e9de1003ace06949dc654bc4876efe89ec53455a7721e47cd'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 18080, 112175, 'dm',
        '179869c96cfdaf5e9de1003ace06949dc654bc4876efe89ec53455a7721e47cd'],
];
if (!isset($targets[$lane]) || count($argv) !== 2
    || !in_array($mode, ['--preflight', '--execute'], true)
    || !in_array($swapMode, ['', 'ROOT_APPROVED_DUAL_SWAP_V1'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_EM_DUAL_RUN') !== 'ROOT_APPROVED_DUAL_OWNED_UPDATE_V1'
            || !hash_equals((string)hash_file('sha256', __FILE__),
                (string)getenv('CDX_EM_DUAL_SHA'))))) {
    echo json_encode(['status' => 'default-deny', 'db_connected' => false,
        'business_http_sent' => false, 'dml_sent' => false]), PHP_EOL;
    exit(78);
}
[$root, $port, $pid, $dialect, $serviceSha] = $targets[$lane];
$base = 'http://127.0.0.1:' . $port;
$stage = 'runtime_identity';
$httpSent = false;
set_exception_handler(static function (Throwable $error) use (&$stage, &$httpSent): void {
    echo json_encode(['status' => 'redacted_stop', 'stage' => $stage,
        'error_class' => get_class($error), 'code' => (string)$error->getCode(),
        'business_http_sent' => $httpSent,
        'recovery' => 'retain_private_journal_and_inspect']), PHP_EOL;
    exit(2);
});
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted warning', 0, $severity);
});
if (PHP_VERSION_ID < 70300 || PHP_VERSION_ID >= 70400
    || is_link($root) || !is_file($root . '/application/database.php')
    || hash_file('sha256', $root . '/application/common/service/ElectronicMenu.php') !== $serviceSha
    || hash_file('sha256', $root . '/application/common/service/ElectronicMenuConfig.php')
        !== '9219c5dfee8f22758cc0e7bc3a1eb6e9cbcd18397dad66e1264efe7b1bbd637c'
    || hash_file('sha256', $root . '/application/p/controller/ElectronicMenu.php')
        !== 'a93de90caced9884bb40374b9f3da7cbb04997930023e6cb229a7ca7ffefc2d7'
    || hash_file('sha256', $root . '/application/api/controller/ElectronicMenu.php')
        !== '2c1df1125c5ced2abd4f97a460f4290950d8ca5fbb978d63800e76ba5d60e056') {
    throw new RuntimeException('Approved runtime/source changed');
}
$cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
$environment = (string)@file_get_contents('/proc/' . $pid . '/environ');
$children = trim((string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children'));
if (strpos($cmd, '127.0.0.1:' . $port) === false
    || strpos($cmd, $root . '/public') === false
    || $environment === '' || $children !== ''
    || strpos($environment, 'PHP_CLI_SERVER_WORKERS=') !== false) {
    throw new RuntimeException('Approved single-worker listener changed');
}
$ownsListener = static function (int $targetPid, int $targetPort): bool {
    $hex = strtoupper(str_pad(dechex($targetPort), 4, '0', STR_PAD_LEFT));
    $lines = @file('/proc/net/tcp', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if (!is_array($lines)) return false;
    $inodes = [];
    foreach (array_slice($lines, 1) as $line) {
        $parts = preg_split('/\s+/', trim($line));
        if (is_array($parts) && ($parts[1] ?? '') === '0100007F:' . $hex
            && ($parts[3] ?? '') === '0A') $inodes[] = (string)($parts[9] ?? '');
    }
    if (count($inodes) !== 1 || $inodes[0] === '') return false;
    $fds = glob('/proc/' . $targetPid . '/fd/*');
    if (!is_array($fds)) return false;
    foreach ($fds as $fd) {
        if (@readlink($fd) === 'socket:[' . $inodes[0] . ']') return true;
    }
    return false;
};
if (!$ownsListener($pid, $port)) {
    throw new RuntimeException('Fixed PID does not own loopback listener');
}
$boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary', false,
    stream_context_create(['http' => ['timeout' => 5]])), true);
if (!is_array($boundary) || ($boundary['external_stubs'] ?? null) !== true
    || ($boundary['redis_isolated'] ?? null) !== true
    || ($boundary['real_queue'] ?? true) !== false) {
    throw new RuntimeException('External/queue isolation changed');
}
$config = require $root . '/application/database.php';
if (!is_array($config)) throw new RuntimeException('Runtime DB config invalid');
if ($dialect === 'mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    foreach (['hostname', 'hostport', 'database', 'username', 'password', 'type'] as $key) {
        if (($config[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('A/B runtime differs from code local');
        }
    }
    $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . (int)$config['hostport']
        . ';dbname=' . $config['database'] . ';charset=' . ($config['charset'] ?? 'utf8');
    $dbUser = (string)$config['username'];
    $dbPassword = (string)$config['password'];
} else {
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $field => $envKey) {
        if ((string)($config[$field] ?? '') !== (string)getenv($envKey)) {
            throw new RuntimeException('C runtime differs from nonprod DM');
        }
    }
    if ((string)getenv('POC_HOST') !== '127.0.0.1'
        || (string)getenv('POC_PORT') !== '5237'
        || strtoupper((string)getenv('POC_SCHEMA')) !== 'ZHCT_APP'
        || strtoupper((string)getenv('POC_USER')) !== 'ZHCT_APP') {
        throw new RuntimeException('C nonproduction DM identity mismatch');
    }
    $machine = trim((string)@file_get_contents('/etc/machine-id'));
    if ($machine === '' || gethostname() === false
        || hash('sha256', gethostname() . '|' . $machine)
            !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('C host identity mismatch');
    }
    $listeners = [];
    @exec('/usr/sbin/ss -ltnp 2>/dev/null', $listeners);
    $dmserver = false;
    foreach ($listeners as $line) {
        if (preg_match('/\*:5237\s+.*users:\(\("dmserver",pid=(\d+),/', $line, $match)) {
            $exe = @readlink('/proc/' . $match[1] . '/exe');
            $dmserver = is_string($exe) && basename($exe) === 'dmserver';
        }
    }
    if (!$dmserver) throw new RuntimeException('C 5237 is not local dmserver');
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $dbUser = (string)getenv('POC_USER');
    $dbPassword = (string)getenv('POC_PASSWORD');
}
$connect = static function () use ($dsn, $dbUser, $dbPassword): PDO {
    return new PDO($dsn, $dbUser, $dbPassword,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
};
$db = $connect();
$stage = 'db_identity_and_candidate';
if ($dialect === 'mysql') {
    $identity = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
        ->fetchColumn();
    if (hash('sha256', $identity)
        !== '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401') {
        throw new RuntimeException('A/B developer MySQL identity mismatch');
    }
} else {
    $schema = strtoupper((string)$db->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn());
    if ($schema !== 'ZHCT_APP'
        || hash('sha256', 'ZHCT_APP|5237|' . $schema)
            !== 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194') {
        throw new RuntimeException('C Store DM schema mismatch');
    }
}
$t = static function (string $name) use ($dialect): string {
    if (!in_array($name, ['user', 'electronic_menu', 'electronic_menu_bind_code',
        'electronic_menu_device', 'electronic_menu_device_log',
        'restaurants', 'recipes', 'dishes'], true)) {
        throw new RuntimeException('Table not allowlisted');
    }
    return $dialect === 'dm' ? strtoupper('ydy_' . $name) : 'ydy_' . $name;
};
$candidate = $db->query('SELECT m.LEFT_RESTAURANT_ID,m.RIGHT_RESTAURANT_ID'
    . ' FROM ' . $t('electronic_menu') . ' m JOIN ' . $t('restaurants')
    . ' l ON l.ID=m.LEFT_RESTAURANT_ID JOIN ' . $t('restaurants')
    . ' r ON r.ID=m.RIGHT_RESTAURANT_ID'
    . ' WHERE m.STORE_ID=10001 AND m.STATUS=1 AND m.IS_DELETE=0'
    . ' AND m.DISPLAY_MODE=2 AND m.RIGHT_RESTAURANT_ID>0'
    . ' AND l.STATUS=1 AND l.IS_DELETE=2 AND r.STATUS=1 AND r.IS_DELETE=2'
    . ' ORDER BY m.ID')->fetch(PDO::FETCH_ASSOC);
$left = (int)($candidate['left_restaurant_id'] ?? 0);
$right = (int)($candidate['right_restaurant_id'] ?? 0);
if ($left <= 0 || $right <= 0 || $left === $right) {
    throw new RuntimeException('Two active same-store restaurants unavailable');
}
if ($swapMode === 'ROOT_APPROVED_DUAL_SWAP_V1') {
    [$left, $right] = [$right, $left];
}
$oldOperators = (int)$db->query('SELECT COUNT(*) FROM ' . $t('user')
    . " WHERE ACCOUNT LIKE 'CDXEMDUAL%'")->fetchColumn();
if ($oldOperators !== 0) throw new RuntimeException('Old dual-test actor needs review');
if ($mode === '--preflight') {
    echo json_encode(['status' => 'select_only_pass', 'lane' => $lane,
        'db' => $dialect === 'dm' ? 'nonproduction_store_dm' : 'code_local_dev_mysql',
        'two_active_restaurants' => true, 'old_dual_actor_count' => 0,
        'business_http_sent' => false, 'dml_sent' => false]), PHP_EOL;
    exit(0);
}

// From here: one synthetic type0 actor and one synthetic menu, never a real device.
$stage = 'exclusive_fixture_preparation';
$lock = fopen(sys_get_temp_dir() . '/cdx-eboard-dual-'
    . ($dialect === 'dm' ? 'c' : 'dev') . '.lock', 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
    throw new RuntimeException('A/B or C exclusive writer lock unavailable');
}
$marker = 'CDXEMDUAL' . strtoupper(bin2hex(random_bytes(6)));
$uuid = strtoupper(bin2hex(random_bytes(16)));
$publicToken = bin2hex(random_bytes(32));
$before = [];
foreach (['electronic_menu', 'electronic_menu_bind_code',
    'electronic_menu_device', 'electronic_menu_device_log'] as $name) {
    $before[$name] = hash('sha256', serialize($db->query('SELECT * FROM '
        . $t($name) . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC)));
}
$journalDir = sys_get_temp_dir() . '/cdx-eboard-dual-' . strtolower($marker);
if (file_exists($journalDir) || !mkdir($journalDir, 0700)) {
    throw new RuntimeException('Private journal collision');
}
chmod($journalDir, 0700);
$journalPath = $journalDir . '/recovery.json';
$journal = ['lane' => $lane, 'marker' => $marker, 'uuid' => $uuid,
    'public_token' => $publicToken, 'actor_id' => 0,
    'menu_id' => 0, 'source_before' => $before, 'phase' => 'prepared'];
$saveJournal = static function (array $change) use (&$journal, $journalPath): void {
    $journal = array_merge($journal, $change);
    $text = json_encode($journal, JSON_UNESCAPED_SLASHES);
    if (!is_string($text) || file_put_contents($journalPath, $text, LOCK_EX) === false
        || !chmod($journalPath, 0600)) {
        throw new RuntimeException('Private journal write failed');
    }
};
$saveJournal([]);
$stage = 'owned_fixture_insert';
$now = date('Y-m-d H:i:s');
$db->prepare('INSERT INTO ' . $t('user')
    . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
    . ' VALUES (?,?,?,?,?,0,?, ?,0)')
    ->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, $now, $marker]);
$actorRows = $db->prepare('SELECT ID FROM ' . $t('user')
    . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
$actorRows->execute([$marker, $marker, $marker]);
$actorIds = $actorRows->fetchAll(PDO::FETCH_COLUMN);
if (count($actorIds) !== 1 || (int)$actorIds[0] <= 0) {
    throw new RuntimeException('Owned actor ambiguous');
}
$actorId = (int)$actorIds[0];
$saveJournal(['phase' => 'actor_inserted', 'actor_id' => $actorId]);
$db->prepare('INSERT INTO ' . $t('electronic_menu')
    . ' (UUID,PUBLIC_TOKEN,STORE_ID,STATUS,IS_DELETE,DISPLAY_MODE,'
    . ' LEFT_RESTAURANT_ID,RIGHT_RESTAURANT_ID,CAROUSEL_INTERVAL,CREATE_BY,'
    . ' CREATE_TIME,UPDATE_TIME) VALUES (?,?,10001,1,0,1,?,0,8,?,?,?)')
    ->execute([$uuid, $publicToken, $left, $marker, $now, $now]);
$menuRows = $db->prepare('SELECT ID FROM ' . $t('electronic_menu')
    . ' WHERE UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=?');
$menuRows->execute([$uuid, $publicToken, $marker]);
$menuIds = $menuRows->fetchAll(PDO::FETCH_COLUMN);
if (count($menuIds) !== 1 || (int)$menuIds[0] <= 0) {
    throw new RuntimeException('Owned menu ambiguous');
}
$menuId = (int)$menuIds[0];
$saveJournal(['phase' => 'menu_inserted', 'menu_id' => $menuId]);
require_once $root . '/application/base/Security.php';
$token = \app\base\Security::encrypt(implode("\t",
    [(string)$actorId, $marker, $marker, '0', '', '', $marker]));
if (!is_string($token) || $token === '') throw new RuntimeException('Owned token unavailable');
$httpSent = true;
$send = static function (string $method, string $path, array $form = [],
    string $tokenHeader = '') use ($base): array {
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($tokenHeader !== '') $headers .= 'token: ' . $tokenHeader . "\r\n";
    $context = stream_context_create(['http' => [
        'method' => $method, 'header' => $headers,
        'content' => $method === 'POST' ? http_build_query($form, '', '&', PHP_QUERY_RFC3986) : '',
        'timeout' => 12, 'ignore_errors' => true, 'follow_location' => 0,
    ]]);
    $stream = @fopen($base . $path, 'rb', false, $context);
    $raw = $stream === false ? false : stream_get_contents($stream, 1048577);
    $head = $http_response_header ?? [];
    if (is_resource($stream)) fclose($stream);
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($head[0] ?? ''), $match);
    if (!is_string($raw) || strlen($raw) > 1048576) {
        throw new RuntimeException('HTTP response unavailable or too large');
    }
    $body = json_decode($raw, true);
    return ['http' => (int)($match[1] ?? 0), 'body' => is_array($body) ? $body : null];
};
$results = [];
$failed = '';
$barrierSafe = false;
try {
    $stage = 'dual_update_http';
    $saveJournal(['phase' => 'http_started']);
    $dual = ['id' => $menuId, 'display_mode' => 2,
        'left_restaurant_id' => $left, 'right_restaurant_id' => $right,
        'carousel_interval' => 9];
    $update = $send('POST', '/p/electronicMenu/save', $dual, $token);
    $data = $update['body']['data'] ?? null;
    if ($update['http'] !== 200 || ($update['body']['code'] ?? null) !== 0
        || !is_array($data) || (int)($data['id'] ?? 0) !== $menuId
        || (string)($data['uuid'] ?? '') !== $uuid
        || (string)($data['public_token'] ?? '') !== $publicToken) {
        throw new RuntimeException('Dual save response/identity mismatch');
    }
    $sourceDb = $connect();
    $rowStmt = $sourceDb->prepare('SELECT * FROM ' . $t('electronic_menu')
        . ' WHERE ID=? AND UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=?');
    $rowStmt->execute([$menuId, $uuid, $publicToken, $marker]);
    $updated = $rowStmt->fetch(PDO::FETCH_ASSOC);
    if (!$updated || (int)$updated['display_mode'] !== 2
        || (int)$updated['left_restaurant_id'] !== $left
        || (int)$updated['right_restaurant_id'] !== $right
        || (int)$updated['carousel_interval'] !== 9
        || (int)$updated['store_id'] !== 10001) {
        throw new RuntimeException('Dual update original row mismatch');
    }
    $results['dual_update_owned_row'] = true;
    $stage = 'dual_play_http';
    $play = $send('GET', '/api/electronicMenu/play/' . $publicToken);
    $playData = $play['body']['data'] ?? null;
    if ($play['http'] !== 200 || ($play['body']['code'] ?? null) !== 0
        || !is_array($playData)
        || (int)($playData['config']['display_mode'] ?? 0) !== 2
        || (int)($playData['config']['carousel_interval'] ?? 0) !== 9
        || !is_array($playData['stalls'] ?? null)
        || count($playData['stalls']) !== 2) {
        throw new RuntimeException('Dual play contract mismatch');
    }
    $dishCounts = [];
    foreach ([$left, $right] as $index => $restaurantId) {
        $stall = $playData['stalls'][$index];
        if (!is_array($stall) || (int)($stall['restaurant_id'] ?? 0) !== $restaurantId
            || ($stall['position'] ?? '') !== ($index === 0 ? 'left' : 'right')
            || !is_array($stall['dishes'] ?? null)) {
            throw new RuntimeException('Dual stall order/shape mismatch');
        }
        $source = $sourceDb->prepare('SELECT d.UUID,d.PRICE,d.CARBOHYDRATE,'
            . ' d.PROTEIN,d.FAT FROM ' . $t('recipes') . ' r JOIN ' . $t('dishes')
            . ' d ON d.UUID=r.DISHES_UUID WHERE r.RESTAURANT_ID=?'
            . ' AND r.MENU_DATE=? AND r.MEAL_TIMES=? AND d.STATUS=1 AND d.IS_DEL=1'
            . ' ORDER BY r.ID');
        $source->execute([$restaurantId,
            (string)($playData['context']['business_date'] ?? ''),
            (int)($playData['context']['meal_times'] ?? 0)]);
        $sourceRows = $source->fetchAll(PDO::FETCH_ASSOC);
        if (count($sourceRows) !== count($stall['dishes'])) {
            throw new RuntimeException('Dual dishes count differs from own source');
        }
        foreach ($stall['dishes'] as $n => $dish) {
            if (!is_array($dish) || !is_string($dish['uuid'] ?? null)
                || strtoupper($dish['uuid']) !== strtoupper((string)$sourceRows[$n]['uuid'])) {
                throw new RuntimeException('Dual dish identity differs from own source');
            }
            foreach (['price', 'carbohydrate', 'protein', 'fat'] as $field) {
                if (!is_string($dish[$field] ?? null)
                    || !preg_match('/^-?\d+\.\d{2}$/D', $dish[$field])
                    || $dish[$field] !== number_format(
                        (float)$sourceRows[$n][$field], 2, '.', '')) {
                    throw new RuntimeException('Dual numeric JSON/source mismatch');
                }
            }
        }
        $dishCounts[] = count($sourceRows);
    }
    if (array_sum($dishCounts) === 0) {
        throw new RuntimeException('Both dual stalls empty; numeric JSON branch unproved');
    }
    if ($swapMode === 'ROOT_APPROVED_DUAL_SWAP_V1'
        && ($dishCounts[0] !== 0 || $dishCounts[1] <= 0)) {
        throw new RuntimeException('Swapped right-positive branch unproved');
    }
    $results['dual_play_source_counts'] = $dishCounts;
    $results['right_stall_empty_boundary'] = $dishCounts[1] === 0;
    $results['swapped_right_positive'] = $swapMode === 'ROOT_APPROVED_DUAL_SWAP_V1';
    $stage = 'invalid_dual_update_zero_write';
    $beforeInvalid = hash('sha256', serialize($updated));
    $bad = $send('POST', '/p/electronicMenu/save',
        array_merge($dual, ['right_restaurant_id' => $left]), $token);
    $rowStmt->execute([$menuId, $uuid, $publicToken, $marker]);
    $afterInvalid = $rowStmt->fetch(PDO::FETCH_ASSOC);
    if ($bad['http'] !== 200 || ($bad['body']['code'] ?? null) !== 1
        || !is_array($afterInvalid)
        || hash('sha256', serialize($afterInvalid)) !== $beforeInvalid) {
        throw new RuntimeException('Invalid dual update changed owned row');
    }
    $results['same_restaurant_rejected_zero_write'] = true;
} catch (Throwable $error) {
    $failed = get_class($error) . ':' . hash('sha256', $error->getMessage());
}
$stage = 'single_worker_completion_barrier';
$afterCmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
$afterChildren = trim((string)@file_get_contents('/proc/' . $pid
    . '/task/' . $pid . '/children'));
$socket = @stream_socket_client('tcp://127.0.0.1:' . $port, $errno, $errstr, 5);
$socketConnected = is_resource($socket);
if (is_resource($socket)) fclose($socket);
$afterBoundary = json_decode((string)@file_get_contents($base . '/__poc_boundary',
    false, stream_context_create(['http' => ['timeout' => 8]])), true);
$barrierSafe = $socketConnected;
$barrierSafe = $barrierSafe && strpos($afterCmd, $root . '/public') !== false
    && strpos($afterCmd, '127.0.0.1:' . $port) !== false
    && $afterChildren === '' && $ownsListener($pid, $port)
    && is_array($afterBoundary)
    && ($afterBoundary['external_stubs'] ?? null) === true
    && ($afterBoundary['redis_isolated'] ?? null) === true
    && ($afterBoundary['real_queue'] ?? true) === false;
if (!$barrierSafe) {
    echo json_encode(['status' => 'uncertain_http_retaining_journal',
        'lane' => $lane, 'failure' => $failed,
        'journal' => $journalPath]), PHP_EOL;
    exit(3);
}
$saveJournal(['phase' => 'http_finished_barrier']);
$stage = 'exact_transaction_cleanup';
$cleanupDb = $connect();
$relationZero = true;
foreach (['electronic_menu_bind_code', 'electronic_menu_device',
    'electronic_menu_device_log'] as $name) {
    $checkRelation = $cleanupDb->prepare('SELECT COUNT(*) FROM ' . $t($name)
        . ' WHERE MENU_ID=?');
    $checkRelation->execute([$menuId]);
    $relationZero = $relationZero && (int)$checkRelation->fetchColumn() === 0;
}
if (!$relationZero) throw new RuntimeException('Owned menu gained device relation');
$cleanupDb->beginTransaction();
$deleteMenu = $cleanupDb->prepare('DELETE FROM ' . $t('electronic_menu')
    . ' WHERE ID=? AND UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=? AND STORE_ID=10001');
$deleteMenu->execute([$menuId, $uuid, $publicToken, $marker]);
if ($deleteMenu->rowCount() !== 1) throw new RuntimeException('Owned menu cleanup not one');
$deleteActor = $cleanupDb->prepare('DELETE FROM ' . $t('user')
    . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
$deleteActor->execute([$actorId, $marker, $marker, $marker]);
if ($deleteActor->rowCount() !== 1) throw new RuntimeException('Owned actor cleanup not one');
if (!$cleanupDb->commit()) throw new RuntimeException('Cleanup commit uncertain');
$saveJournal(['phase' => 'cleanup_committed']);
$second = $connect();
$check = $second->prepare('SELECT COUNT(*) FROM ' . $t('electronic_menu')
    . ' WHERE ID=? OR UUID=? OR PUBLIC_TOKEN=? OR CREATE_BY=?');
$check->execute([$menuId, $uuid, $publicToken, $marker]);
$menuZero = (int)$check->fetchColumn() === 0;
$check = $second->prepare('SELECT COUNT(*) FROM ' . $t('user')
    . ' WHERE ID=? OR UUID=? OR ACCOUNT=?');
$check->execute([$actorId, $marker, $marker]);
$actorZero = (int)$check->fetchColumn() === 0;
$same = $menuZero && $actorZero;
foreach ($before as $name => $fingerprint) {
    $same = $same && hash('sha256', serialize($second->query('SELECT * FROM '
        . $t($name) . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC))) === $fingerprint;
}
if (!$same) throw new RuntimeException('Post-cleanup independent source fingerprint differs');
$saveJournal(['phase' => 'local_cleanup_checked_awaiting_independent_audit']);
echo json_encode(['status' => $failed === ''
        ? 'scoped_http_pass_pending_independent_audit'
        : 'scoped_http_fail_pending_independent_audit',
    'lane' => $lane, 'results' => $results,
    'failure' => $failed, 'owned_rows_zero' => true,
    'four_table_fingerprints_restored' => true,
    'journal' => $journalPath,
    'real_device_or_external_called' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($failed !== '') exit(1);
