<?php

declare(strict_types=1);

// Three bounded synchronous XLS exports. Default is read-only preflight.
$lane = (string)getenv('CDX_EXPORT3_LANE');
$action = (string)($argv[1] ?? '');
$modes = [
    '--preflight-detail' => ['detail', false],
    '--preflight-user' => ['user', false],
    '--preflight-recharge' => ['recharge', false],
    '--execute-detail' => ['detail', true],
    '--execute-user' => ['user', true],
    '--execute-recharge' => ['recharge', true],
];
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 21023, 'mysql',
        '04e3f80cb0dddff6ec657f731185013ffffebcb4da48be8402b6d86547e14742',
        '8a89a807aef486303dfab7b660d7136d1bdc21886b9f63b2dc60751166f797f5',
        '95ce9bcee0f908ace8c86c1978ed9e41791e56a3ab53d53de17972d12e908341'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001, 'mysql',
        '08528bd3ac98c3dc8ac8c5f442313e86aacb6b6011b27861101afd00f7b7a998',
        '100b405138ffc62336202fd65ea6a1838fc2a84288fdec58d33d531436123462',
        '8953e4be37bb3a1f6adf40c9da9b19de5e3eb2f61d595812b080a66bec10b7c2'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175, 'dm',
        '08528bd3ac98c3dc8ac8c5f442313e86aacb6b6011b27861101afd00f7b7a998',
        '100b405138ffc62336202fd65ea6a1838fc2a84288fdec58d33d531436123462',
        '8953e4be37bb3a1f6adf40c9da9b19de5e3eb2f61d595812b080a66bec10b7c2'],
];
if (!isset($targets[$lane], $modes[$action])) {
    fwrite(STDERR, "Export test disabled\n");
    exit(78);
}
[$mode, $execute] = $modes[$action];
if ($execute && ((string)getenv('CDX_EXPORT3_EXECUTE') !== 'ROOT_APPROVED_EXPORT3_V1'
    || (string)getenv('CDX_EXPORT3_SHA') !== hash_file('sha256', __FILE__))) {
    fwrite(STDERR, "Export HTTP/DML disabled\n");
    exit(78);
}
[$root, $base, $pid, $dialect, $mealSha, $rechargeSha, $userSha] = $targets[$lane];
if ((string)getenv('POC_APP_ROOT') !== $root
    || (string)getenv('POC_BASE_URL') !== $base
    || (string)getenv('POC_DIALECT') !== $dialect
    || is_link($root)
    || hash_file('sha256', $root . '/application/p/controller/Report.php')
        !== '1673113e55dc7f247f697a77ece16ebdb21d1572d8a202c40abb8764cbc83f7e'
    || hash_file('sha256', $root . '/application/p/logic/MealOrder.php') !== $mealSha
    || hash_file('sha256', $root . '/application/p/logic/RechargeOrder.php') !== $rechargeSha
    || hash_file('sha256', $root . '/application/model/Excel.php')
        !== ($lane === 'A'
            ? 'fb3e2fb496b7b56a16969f2d01491d006aeb4cf2515fb48e3b62001468e74a1b'
            : '468f4d22f73e0bf3e6e5fac3551c690f0aafb60678b4ec38ca88ce60afad52d0')
    || hash_file('sha256', $root . '/application/model/User.php') !== $userSha) {
    throw new RuntimeException('Approved export source/runtime drift');
}
$runtimeReady = static function () use ($root, $base, $pid): bool {
    $cmdline = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $environment = (string)@file_get_contents('/proc/' . $pid . '/environ');
    $children = (string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children');
    $boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    $port = (int)parse_url($base, PHP_URL_PORT);
    $hex = strtoupper(str_pad(dechex($port), 4, '0', STR_PAD_LEFT));
    $inode = '';
    foreach (@file('/proc/net/tcp', FILE_IGNORE_NEW_LINES) ?: [] as $line) {
        $parts = preg_split('/\s+/', trim($line));
        if (($parts[1] ?? '') === '0100007F:' . $hex && ($parts[3] ?? '') === '0A') {
            $inode = (string)($parts[9] ?? '');
            break;
        }
    }
    $ownsSocket = false;
    if ($inode !== '') {
        foreach (@scandir('/proc/' . $pid . '/fd') ?: [] as $fd) {
            if ($fd !== '.' && $fd !== '..'
                && @readlink('/proc/' . $pid . '/fd/' . $fd) === 'socket:[' . $inode . ']') {
                $ownsSocket = true;
                break;
            }
        }
    }
    $multiWorker = preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=(\d+)(?:\x00|$)/',
        $environment, $match) && (int)$match[1] > 1;
    return strpos($cmdline, '127.0.0.1:' . $port) !== false
        && strpos($cmdline, $root . '/public') !== false
        && $environment !== '' && trim($children) === '' && !$multiWorker
        && $ownsSocket && is_array($boundary)
        && !empty($boundary['external_stubs'])
        && !empty($boundary['redis_isolated']) && empty($boundary['real_queue']);
};
if (!$runtimeReady()) throw new RuntimeException('Export isolated HTTP boundary absent');

$runtime = (static function (string $path): array {
    $config = require $path;
    if (!is_array($config)) throw new RuntimeException('Export runtime DB config invalid');
    return $config;
})($root . '/application/database.php');
if ((string)($runtime['prefix'] ?? '') !== 'ydy_') {
    throw new RuntimeException('Export Store prefix mismatch');
}
$local = null;
if ($dialect === 'mysql') {
    $local = (static function (): array {
        $config = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
        if (!is_array($config)) throw new RuntimeException('Export code local DB config invalid');
        return $config;
    })();
    if ((string)($runtime['type'] ?? '') !== 'mysql'
        || !in_array((string)($runtime['charset'] ?? ''), ['utf8', 'utf8mb4'], true)) {
        throw new RuntimeException('Export MySQL runtime type/charset mismatch');
    }
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('Export runtime differs from code local');
        }
    }
    if ((int)($runtime['hostport'] ?? 3306) !== (int)($local['hostport'] ?? 3306)) {
        throw new RuntimeException('Export MySQL runtime port mismatch');
    }
    $dsn = 'mysql:host=' . $runtime['hostname'] . ';port='
        . (int)($runtime['hostport'] ?? 3306) . ';dbname=' . $runtime['database']
        . ';charset=' . (string)$runtime['charset'];
} else {
    if ((string)($runtime['type'] ?? '') !== 'app\\cores\\database\\Dm'
        || (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('Export DM Store runtime target mismatch');
    }
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $key => $envKey) {
        if ((string)($runtime[$key] ?? '') !== (string)getenv($envKey)) {
            throw new RuntimeException('Export DM Store process context mismatch');
        }
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $machineId = trim((string)@file_get_contents('/etc/machine-id'));
    if ($machineId === '' || gethostname() === false
        || hash('sha256', gethostname() . '|' . $machineId)
            !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('Export DM host mismatch');
    }
    foreach ([5237, 5238] as $port) {
        $lines = [];
        $exitCode = 0;
        @exec('/usr/sbin/ss -ltnp 2>/dev/null', $lines, $exitCode);
        $localListener = false;
        if ($exitCode === 0) {
            foreach ($lines as $line) {
                if (preg_match('/\*:' . $port . '\s+.*users:\(\("dmserver",pid=(\d+),/',
                    $line, $match)) {
                    $exe = @readlink('/proc/' . $match[1] . '/exe');
                    $localListener = is_string($exe) && basename($exe) === 'dmserver';
                    break;
                }
            }
        }
        if (!$localListener) throw new RuntimeException('Export DM listener not local');
    }
}
$connect = static function (string $target = 'store') use ($runtime, $local, $dialect, $dsn): PDO {
    if ($target === 'store') {
        $selected = $runtime;
        $selectedDsn = $dsn;
        $approved = $dialect === 'dm'
            ? 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194'
            : '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
        $identityPrefix = 'ZHCT_APP|5237|';
    } elseif ($target === 'ai') {
        $selected = $runtime['ai_zhct'] ?? null;
        if (!is_array($selected) || (string)($selected['prefix'] ?? '') !== 'yoshop_') {
            throw new RuntimeException('Export AI secondary config missing');
        }
        if ($dialect === 'mysql') {
            $localAi = $local['ai_zhct'] ?? [];
            foreach (['hostname', 'database', 'username', 'password', 'prefix'] as $key) {
                if (($selected[$key] ?? null) !== ($localAi[$key] ?? null)) {
                    throw new RuntimeException('Export AI differs from code local');
                }
            }
            if ((int)($selected['hostport'] ?? 3306)
                !== (int)($localAi['hostport'] ?? $local['hostport'] ?? 3306)) {
                throw new RuntimeException('Export AI local port mismatch');
            }
            $selectedDsn = 'mysql:host=' . $selected['hostname'] . ';port='
                . (int)($selected['hostport'] ?? 3306) . ';dbname=' . $selected['database']
                . ';charset=' . (string)$runtime['charset'];
            $approved = '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b';
        } else {
            foreach (['hostname' => 'POC_AI_HOST', 'hostport' => 'POC_AI_PORT',
                'database' => 'POC_AI_SCHEMA', 'username' => 'POC_AI_USER',
                'password' => 'POC_AI_PASSWORD'] as $key => $envKey) {
                if ((string)($selected[$key] ?? '') !== (string)getenv($envKey)) {
                    throw new RuntimeException('Export AI process context mismatch');
                }
            }
            if ((string)$selected['hostname'] !== '127.0.0.1'
                || (int)$selected['hostport'] !== 5238
                || strtoupper((string)$selected['database']) !== 'AIZHCT_APP'
                || strtoupper((string)$selected['username']) !== 'AIZHCT_APP') {
                throw new RuntimeException('Export AI outside nonproduction DM target');
            }
            $selectedDsn = 'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP';
            $approved = '628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e';
        }
        $identityPrefix = 'AIZHCT_APP|5238|';
    } else {
        throw new RuntimeException('Unknown export DB target');
    }
    $pdo = new PDO($selectedDsn, (string)$selected['username'],
        (string)$selected['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
    $identity = $dialect === 'mysql'
        ? (string)$pdo->query(
            "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())"
        )->fetchColumn()
        : $identityPrefix . (string)$pdo->query(
            "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL"
        )->fetchColumn();
    if (!hash_equals($approved, hash('sha256', $identity))) {
        throw new RuntimeException('Export DB identity outside approved target');
    }
    return $pdo;
};

$readRows = static function (PDO $pdo, string $sql, array $args = [], int $cap = 101): array {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($args);
    $rows = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $raw) {
        $row = [];
        foreach (array_change_key_case($raw, CASE_LOWER) as $key => $value) {
            $row[$key] = is_resource($value) ? stream_get_contents($value) : $value;
        }
        $rows[] = $row;
        if (count($rows) >= $cap) break;
    }
    return $rows;
};
$store = $connect('store');
$ai = $mode === 'recharge' ? $connect('ai') : null;
$prefix = $dialect === 'dm' ? 'YDY_' : 'ydy_';
$aiPrefix = $dialect === 'dm' ? 'YOSHOP_' : 'yoshop_';
$window = $dialect === 'dm' ? '2025-02-17' : '2026-09-18';
$department = '';
$detailSource = [];
$rechargeSource = [];
$sourceSql = '';
$sourceArgs = [];
$boundedSql = '';
$rechargeAccount = '';
$rechargeDate = '';
$rechargeAiUserId = 0;
if ($mode !== 'recharge') {
    $deptSql = 'SELECT DISTINCT sd.DEPARTMENT_UUID FROM ' . $prefix . 'meal_order meal'
        . ' JOIN ' . $prefix . 'user u ON u.ID=meal.USER_ID'
        . ' JOIN (SELECT x.STAFF_UUID,x.DEPARTMENT_UUID FROM '
        . $prefix . 'staff_department x JOIN (SELECT STAFF_UUID,MIN(ID) MIN_ID FROM '
        . $prefix . 'staff_department GROUP BY STAFF_UUID) y ON y.MIN_ID=x.ID) sd'
        . ' ON sd.STAFF_UUID=u.STAFF_UUID'
        . ' JOIN ' . $prefix . 'department dep ON dep.UUID=sd.DEPARTMENT_UUID AND dep.IS_SHOW=1'
        . ' JOIN ' . $prefix . 'initial_menu menu ON menu.MEAL_ORDER_ID=meal.ID'
        . ' WHERE meal.ORDER_STATUS=30 AND meal.REFUND_STATUS IN (0,1,2)'
        . ' AND meal.IS_DELETE=0 AND u.IS_DELETE=0'
        . " AND menu.DISHES_UUID NOT IN ('custom_dish','package_fee')"
        . ' AND meal.RESTAURANT_ID=1 AND meal.MEAL_DATE=?'
        . ' AND meal.BIND_TIME BETWEEN ? AND ?'
        . ($dialect === 'dm' ? ' AND ROWNUM<=21' : ' LIMIT 21');
    $depts = $readRows($store, $deptSql,
        [$window, $window . ' 00:00:00', $window . ' 23:59:59'], 21);
    if (!$depts || count($depts) > 20) throw new RuntimeException('Export department set unsafe');
    if ($dialect === 'mysql') {
        if (count($depts) !== 1) throw new RuntimeException('Export MySQL department changed');
        $department = (string)$depts[0]['department_uuid'];
    } else {
        $matches = array_values(array_filter($depts, static function (array $row): bool {
            return strpos(hash('sha256', (string)$row['department_uuid']),
                'b42d4036c9d8') === 0;
        }));
        if (count($matches) !== 1) throw new RuntimeException('Export DM department changed');
        $department = (string)$matches[0]['department_uuid'];
    }
    if ($department === '') throw new RuntimeException('Export department blank');
    $sourceSql = 'SELECT meal.ID AS MEAL_ID,meal.ORDER_NO,meal.BIND_TIME,meal.MEAL_DATE,'
        . ' meal.MEAL_NUMBER,meal.TOTAL_PRICE,meal.PAY_PRICE,meal.REFUND_AMOUNT,meal.REMARK,'
        . ' u.NAME AS USER_NAME,u.MOBILE,sd.DEPARTMENT_UUID,dep.NAME AS DEPARTMENT_NAME,'
        . ' menu.ID AS MENU_ID,menu.DISHES_UUID,menu.NUMBER AS DISH_NUMBER,'
        . ' menu.TOTAL_PRICE AS DISH_TOTAL_PRICE,d.NAME AS DISH_NAME'
        . ' FROM ' . $prefix . 'meal_order meal'
        . ' JOIN ' . $prefix . 'user u ON u.ID=meal.USER_ID'
        . ' JOIN (SELECT x.STAFF_UUID,x.DEPARTMENT_UUID FROM '
        . $prefix . 'staff_department x JOIN (SELECT STAFF_UUID,MIN(ID) MIN_ID FROM '
        . $prefix . 'staff_department GROUP BY STAFF_UUID) y ON y.MIN_ID=x.ID) sd'
        . ' ON sd.STAFF_UUID=u.STAFF_UUID'
        . ' JOIN ' . $prefix . 'department dep ON dep.UUID=sd.DEPARTMENT_UUID AND dep.IS_SHOW=1'
        . ' JOIN ' . $prefix . 'initial_menu menu ON menu.MEAL_ORDER_ID=meal.ID'
        . ' LEFT JOIN ' . $prefix . 'dishes d ON d.UUID=menu.DISHES_UUID'
        . ' WHERE meal.ORDER_STATUS=30 AND meal.REFUND_STATUS IN (0,1,2)'
        . ' AND meal.IS_DELETE=0 AND u.IS_DELETE=0'
        . " AND menu.DISHES_UUID NOT IN ('custom_dish','package_fee')"
        . ' AND meal.RESTAURANT_ID=1 AND meal.MEAL_DATE=?'
        . ' AND meal.BIND_TIME BETWEEN ? AND ? AND sd.DEPARTMENT_UUID=?'
        . ($dialect === 'dm' ? ' AND ROWNUM<=101 ORDER BY menu.ID'
            : ' ORDER BY menu.ID LIMIT 101');
    $sourceArgs = [$window, $window . ' 00:00:00', $window . ' 23:59:59', $department];
    $detailSource = $readRows($store, $sourceSql, $sourceArgs, 101);
    if (count($detailSource) !== ($dialect === 'dm' ? 5 : 11)) {
        throw new RuntimeException('Export audited detail source/bound changed');
    }
    if (count(array_unique(array_column($detailSource, 'meal_id'))) !== 1) {
        throw new RuntimeException('Export detail source order count changed');
    }
} else {
    $accountSql = 'SELECT u.ACCOUNT,s.AI_USER_ID,s.NAME,s.MOBILE,s.UUID FROM '
        . $prefix . 'user u JOIN ' . $prefix . 'staff s ON s.UUID=u.STAFF_UUID'
        . " WHERE u.ACCOUNT LIKE 'CDXPAY%' AND s.NAME=u.ACCOUNT"
        . ($dialect === 'dm' ? ' AND ROWNUM<=50 ORDER BY u.ACCOUNT'
            : ' ORDER BY u.ACCOUNT LIMIT 50');
    $accounts = $readRows($store, $accountSql, [], 50);
    $rechargeSql = 'SELECT ORDER_ID,USER_ID,PAY_METHOD,PLATFORM,PAY_PRICE,GIFT_MONEY,'
        . ' ACTUAL_MONEY,RECHARGE_DATE,PAY_STATUS,IS_DELETE FROM '
        . $aiPrefix . 'recharge_order WHERE USER_ID=? AND IS_DELETE=0'
        . ' AND PAY_STATUS=20 ORDER BY ORDER_ID DESC'
        . ($dialect === 'dm' ? ' FETCH FIRST 2 ROWS ONLY' : ' LIMIT 2');
    $sourceStaff = null;
    foreach ($accounts as $account) {
        if ((int)$account['ai_user_id'] <= 0
            || !preg_match('/^CDXPAY[A-F0-9]{8}$/D', (string)$account['account'])) continue;
        $rows = $readRows($ai, $rechargeSql, [(int)$account['ai_user_id']], 2);
        if (count($rows) !== 1) continue;
        $sourceStaff = $account;
        $rechargeSource = $rows;
        break;
    }
    if ($sourceStaff === null) throw new RuntimeException('Export synthetic recharge source absent');
    $rechargeAccount = (string)$sourceStaff['account'];
    $rechargeAiUserId = (int)$sourceStaff['ai_user_id'];
    $rechargeDate = substr((string)$rechargeSource[0]['recharge_date'], 0, 10);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/D', $rechargeDate)) {
        throw new RuntimeException('Export recharge source date invalid');
    }
    // The actual WHERE uses nickname LIKE and this one payment method: bound all hits.
    $candidateStaff = $readRows($store,
        'SELECT AI_USER_ID,NAME FROM ' . $prefix . 'staff WHERE NAME LIKE ?'
            . ($dialect === 'dm' ? ' AND ROWNUM<=101' : ' LIMIT 101'),
        ['%' . $rechargeAccount . '%'], 101);
    if (count($candidateStaff) !== 1
        || (int)$candidateStaff[0]['ai_user_id'] !== $rechargeAiUserId) {
        throw new RuntimeException('Export nickname filter not unique');
    }
    $boundedSql = 'SELECT ORDER_ID,USER_ID,PAY_METHOD,PLATFORM,PAY_PRICE,GIFT_MONEY,'
        . ' ACTUAL_MONEY,RECHARGE_DATE,PAY_STATUS,IS_DELETE FROM '
        . $aiPrefix . 'recharge_order WHERE USER_ID=? AND IS_DELETE=0 AND PAY_STATUS=20'
        . ' AND RECHARGE_DATE BETWEEN ? AND ? AND PAY_METHOD=?'
        . ($dialect === 'dm' ? ' AND ROWNUM<=101' : ' LIMIT 101');
    $rechargeSource = $readRows($ai, $boundedSql,
        [$rechargeAiUserId, $rechargeDate, $rechargeDate,
            (string)$rechargeSource[0]['pay_method']], 101);
    if (count($rechargeSource) !== 1) {
        throw new RuntimeException('Export recharge result exceeds one audited row');
    }
}

$businessRows = $mode === 'recharge' ? 1 : count($detailSource);
if ($businessRows < 1 || $businessRows > 100) {
    throw new RuntimeException('Export preflight row bound exceeded');
}
$readQueue = static function (PDO $pdo) use ($prefix): array {
    $row = $pdo->query('SELECT COUNT(*) AS TOTAL,'
        . ' SUM(CASE WHEN STATUS=0 THEN 1 ELSE 0 END) AS PENDING FROM '
        . $prefix . 'export_log')->fetch(PDO::FETCH_ASSOC);
    $row = array_change_key_case(is_array($row) ? $row : [], CASE_LOWER);
    return ['total' => (int)($row['total'] ?? -1),
        'pending' => (int)($row['pending'] ?? -1)];
};
$preflightQueue = $readQueue($store);
if ($preflightQueue['total'] < 0 || $preflightQueue['pending'] !== 0) {
    throw new RuntimeException('Export queue preflight unavailable or pending');
}
$autoload = $root . '/vendor/autoload.php';
if (!is_file($autoload) || !is_readable($autoload)) {
    throw new RuntimeException('Export workbook parser autoload absent');
}
require_once $autoload;
if (!class_exists('PHPExcel_IOFactory')) {
    throw new RuntimeException('Export workbook parser class absent');
}
try {
    $readerPreflight = \PHPExcel_IOFactory::createReader('Excel5');
    if (!($readerPreflight instanceof \PHPExcel_Reader_IReader)) {
        throw new RuntimeException('Export Excel5 reader type unavailable');
    }
    unset($readerPreflight);
} catch (Throwable $e) {
    throw new RuntimeException('Export Excel5 reader unavailable');
}
$sourceBefore = hash('sha256', serialize($mode === 'recharge' ? $rechargeSource : $detailSource));
$markerCount = (int)$store->query('SELECT COUNT(*) FROM ' . $prefix . 'user'
    . " WHERE ACCOUNT LIKE 'CDXEX3%'")->fetchColumn();
$preflight = ['lane' => $lane, 'mode' => $mode, 'preflight' => true,
    'business_rows' => $businessRows, 'source_sha' => $sourceBefore,
    'existing_export_test_actors' => $markerCount,
    'export_log_total_before_actor' => $preflightQueue['total'],
    'export_log_pending_before_actor' => $preflightQueue['pending'],
    'store_identity_approved' => true, 'ai_identity_approved' => $ai !== null];
if (!$execute) {
    echo json_encode($preflight, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
if ($markerCount !== 0) throw new RuntimeException('Prior export actor requires review');
if ((string)getenv('CDX_EXPORT3_STORE_SHA') !== ($dialect === 'dm'
    ? 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194'
    : '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401')) {
    throw new RuntimeException('Export Store identity confirmation absent');
}
if ($mode === 'recharge'
    && (string)getenv('CDX_EXPORT3_AI_SHA') !== ($dialect === 'dm'
        ? '628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e'
        : '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b')) {
    throw new RuntimeException('Export AI identity confirmation absent');
}

// Business HTTP/download/actor implementation below; preflight only checks
// the isolated boundary endpoint and reads bounded source rows.
$money = static function ($value): string {
    return number_format((float)$value, 2, '.', '');
};
$text = static function ($value): string {
    if ($value === null) return '';
    return (string)$value;
};
$actor = 'CDXEX3' . strtoupper(bin2hex(random_bytes(8)));
$privateDir = sys_get_temp_dir() . '/zhct-report-export3-' . $actor;
if (file_exists($privateDir) || !mkdir($privateDir, 0700)) {
    throw new RuntimeException('Private export recovery directory unavailable');
}
chmod($privateDir, 0700);
$journalPath = $privateDir . '/recovery.json';
$journal = ['lane' => $lane, 'mode' => $mode, 'marker' => $actor,
    'operator_id' => 0, 'state' => 'prepared'];
$saveJournal = static function () use (&$journal, $privateDir, $journalPath): void {
    $next = $privateDir . '/recovery.next';
    if (file_put_contents($next, json_encode($journal, JSON_UNESCAPED_SLASHES)) === false) {
        throw new RuntimeException('Export recovery journal write failed');
    }
    chmod($next, 0600);
    if (!rename($next, $journalPath)) {
        throw new RuntimeException('Export recovery journal replace failed');
    }
};
$saveJournal();
$operatorId = 0;
$filePath = $privateDir . '/response.xls';
$sourceAfter = false;
$cleaned = false;
$queueStable = false;
$listSourceMatched = false;
$workbookMatched = false;
$downloadBytes = 0;
$sheetRows = 0;
$failure = '';
$token = '';
$queueBeforeTotal = null;
$queueBeforePending = null;
$queueAfterTotal = null;
$queueAfterPending = null;

$readSourceAgain = static function () use ($mode, $connect, $sourceSql, $sourceArgs,
    $boundedSql, $rechargeAiUserId, $rechargeDate, $rechargeSource, $readRows): array {
    if ($mode === 'recharge') {
        $aiNow = $connect('ai');
        return $readRows($aiNow, $boundedSql,
            [$rechargeAiUserId, $rechargeDate, $rechargeDate,
                (string)$rechargeSource[0]['pay_method']], 101);
    }
    $storeNow = $connect('store');
    return $readRows($storeNow, $sourceSql, $sourceArgs, 101);
};
$http = static function (string $route, array $params, string $method,
    ?string $targetFile = null) use ($base, $runtimeReady, &$token): array {
    if (!$runtimeReady()) throw new RuntimeException('Export runtime drift before HTTP');
    $url = $base . '/p/report/' . $route;
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n"
        . 'token: ' . $token . "\r\n";
    $options = ['method' => $method, 'timeout' => 20,
        'ignore_errors' => true, 'header' => $headers];
    if ($method === 'GET') {
        $url .= '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    } else {
        $options['content'] = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    }
    $ctx = stream_context_create(['http' => $options]);
    $input = @fopen($url, 'rb', false, $ctx);
    if (!$input) throw new RuntimeException('Export HTTP open failed');
    $responseHeaders = $http_response_header ?? [];
    $firstHeader = (string)($responseHeaders[0] ?? '');
    if (strpos($firstHeader, ' 200 ') === false) {
        fclose($input);
        throw new RuntimeException('Export HTTP status mismatch');
    }
    $limit = $targetFile === null ? 262144 : 1048576;
    $body = stream_get_contents($input, $limit + 1);
    fclose($input);
    if (!is_string($body) || strlen($body) > $limit) {
        throw new RuntimeException('Export HTTP response byte limit exceeded');
    }
    if ($targetFile !== null) {
        $contentType = implode('\n', $responseHeaders);
        if (stripos($contentType, 'application/vnd.ms-excel') === false
            || stripos($contentType, 'attachment;') === false
            || substr($body, 0, 8) !== "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1") {
            throw new RuntimeException('Export Excel5 response signature/header mismatch');
        }
        $file = @fopen($targetFile, 'xb');
        if (!$file) throw new RuntimeException('Private XLS output unavailable');
        chmod($targetFile, 0600);
        $written = fwrite($file, $body);
        fclose($file);
        if ($written !== strlen($body)) throw new RuntimeException('Private XLS truncated');
        return ['bytes' => strlen($body), 'sha256' => hash('sha256', $body)];
    }
    $json = json_decode($body, true);
    if (!is_array($json) || ($json['code'] ?? null) !== 0
        || ($json['message'] ?? null) !== '查询成功'
        || !is_array($json['data']['data'] ?? null)) {
        throw new RuntimeException('Export paired list contract mismatch');
    }
    return $json;
};
$readXls = static function (string $path, array $header, int $expectedRows) use ($root): array {
    require_once $root . '/vendor/autoload.php';
    if (!class_exists('PHPExcel_IOFactory')) {
        throw new RuntimeException('Excel5 parser not available');
    }
    $reader = \PHPExcel_IOFactory::createReader('Excel5');
    $reader->setReadDataOnly(true);
    $book = $reader->load($path);
    if ($book->getSheetCount() !== 1) {
        throw new RuntimeException('Export workbook sheet count mismatch');
    }
    $sheet = $book->getSheet(0);
    if ((int)$sheet->getHighestRow() !== $expectedRows + 1
        || \PHPExcel_Cell::columnIndexFromString($sheet->getHighestColumn())
            !== count($header)) {
        throw new RuntimeException('Export workbook dimensions mismatch');
    }
    $rows = [];
    for ($rowNum = 1; $rowNum <= $expectedRows + 1; $rowNum++) {
        $row = [];
        for ($column = 0; $column < count($header); $column++) {
            $cell = $sheet->getCellByColumnAndRow($column, $rowNum);
            $value = $cell->getValue();
            $row[] = $value === null ? '' : (string)$value;
        }
        $rows[] = $row;
    }
    $book->disconnectWorksheets();
    unset($book, $reader);
    if ($rows[0] !== $header) throw new RuntimeException('Export workbook header mismatch');
    return array_slice($rows, 1);
};

try {
    $store->query('SELECT ID,UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE'
        . ' FROM ' . $prefix . 'user WHERE 1=0');
    $insert = $store->prepare('INSERT INTO ' . $prefix . 'user'
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $insert->execute([$actor, $actor, 'DISABLED',
        strtoupper(bin2hex(random_bytes(8))), $actor, 0,
        date('Y-m-d H:i:s'), 'CODEX', 0]);
    $own = $store->prepare('SELECT ID FROM ' . $prefix . 'user'
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
    $own->execute([$actor, $actor, 'CODEX']);
    $operatorId = (int)$own->fetchColumn();
    if ($operatorId <= 0) throw new RuntimeException('Export actor insert readback failed');
    $journal['operator_id'] = $operatorId;
    $journal['state'] = 'actor_inserted';
    $saveJournal();
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t",
        [(string)$operatorId, $actor, $actor, '0', '', '', $actor]));
    if (!is_string($token) || $token === '') {
        throw new RuntimeException('Export actor token unavailable');
    }
    $queueBefore = $readQueue($store);
    $queueBeforeTotal = $queueBefore['total'];
    $queueBeforePending = $queueBefore['pending'];
    $journal['export_log_before_total'] = $queueBeforeTotal;
    $journal['export_log_before_pending'] = $queueBeforePending;
    $journal['state'] = 'queue_before_recorded';
    $saveJournal();
    if ($queueBeforeTotal < 0 || $queueBeforePending !== 0) {
        throw new RuntimeException('Export pending queue before business HTTP');
    }
    $source = $mode === 'recharge' ? $rechargeSource : $detailSource;
    $first = $source[0];

    if ($mode === 'recharge') {
        $form = ['start_date' => $rechargeDate, 'end_date' => $rechargeDate,
            'nickname' => $rechargeAccount,
            'pay_method' => (string)$first['pay_method']];
        $listRoute = 'rechargeStatisticList';
        $exportRoute = 'rechargeStatisticExport';
    } else {
        $form = ['bind_start_date' => $window, 'bind_end_date' => $window,
            'meal_start_date' => $window, 'meal_end_date' => $window,
            'restaurantIds' => '1'];
        if ($mode === 'detail') {
            $form['department_uuids'] = $department;
            $listRoute = 'dishSalesVolumeDetailList';
            $exportRoute = 'dishSalesVolumeDetailExport';
        } else {
            $form['department_uuid'] = $department;
            $listRoute = 'dishSalesVolumeDetailUserList';
            $exportRoute = 'dishSalesVolumeDetailUserExport';
        }
    }
    $listForm = $form;
    if ($mode !== 'recharge') {
        $listForm['page'] = 1;
        $listForm['list_rows'] = 100;
    }
    $list = $http($listRoute, $listForm, 'POST');
    $listRows = $list['data']['data'];
    if ($mode === 'recharge') {
        if ((int)($list['data']['total'] ?? -1) !== 1 || count($listRows) !== 1) {
            throw new RuntimeException('Export recharge paired list count mismatch');
        }
        $item = $listRows[0];
        if ((int)($item['user_id'] ?? 0) !== $rechargeAiUserId
            || (string)($item['user']['name'] ?? '') !== $rechargeAccount
            || (string)($item['recharge_date'] ?? '')
                !== $rechargeDate . '至' . $rechargeDate
            || $money($item['pay_amount'] ?? 0) !== $money($first['pay_price'])
            || $money($item['gift_amount'] ?? 0) !== $money($first['gift_money'])
            || $money($item['recharge_amount'] ?? 0)
                !== $money($first['actual_money'])) {
            throw new RuntimeException('Export recharge list/source mismatch');
        }
        $expectedRechargeType = (string)$first['platform'] === 'PC' ? '线下' : '线上';
        $methodNames = ['wechat' => '微信支付', 'alipay' => '支付宝支付',
            'balance' => '余额支付', 'wechat_offline' => '线下微信支付',
            'alipay_offline' => '线下支付宝支付', 'cash' => '现金支付',
            'psbc' => '邮储支付', 'zhangjiagang_bank' => '张家港农商银行'];
        $rawMethod = (string)$first['pay_method'];
        if ((string)($item['recharge_type'] ?? '') !== $expectedRechargeType
            || (string)($item['recharge_method'] ?? '')
                !== ($methodNames[$rawMethod] ?? $rawMethod)) {
            throw new RuntimeException('Export recharge list enum/source mismatch');
        }
        $listSourceMatched = true;
    } elseif ($mode === 'detail') {
        if ((int)($list['data']['total'] ?? -1) !== 1 || count($listRows) !== 1) {
            throw new RuntimeException('Export detail paired list count mismatch');
        }
        $group = $listRows[0];
        if ((string)($group['department_uuid'] ?? '') !== $department
            || (string)($group['department_name'] ?? '')
                !== (string)$first['department_name']
            || substr((string)($group['bind_date'] ?? ''), 0, 10) !== $window
            || substr((string)($group['meal_date'] ?? ''), 0, 10) !== $window
            || $money($group['total_number'] ?? 0) !== $money($first['meal_number'])
            || $money($group['total_price'] ?? 0) !== $money($first['total_price'])
            || $money($group['pay_amount'] ?? 0)
                !== $money((float)$first['pay_price'] - (float)$first['refund_amount'])) {
            throw new RuntimeException('Export detail group/list/source mismatch');
        }
        $expectedDish = [];
        foreach ($source as $row) {
            $key = (string)$row['dishes_uuid'];
            if (isset($expectedDish[$key])) {
                throw new RuntimeException('Export detail duplicate UUID outside one-row fixture');
            }
            $expectedDish[$key] = $row;
        }
        $seen = [];
        foreach (($group['dishes'] ?? []) as $dish) {
            $uuid = (string)($dish['dishes_uuid'] ?? '');
            $row = $expectedDish[$uuid] ?? null;
            if (!$row || isset($seen[$uuid])
                || (string)($dish['dishes_name'] ?? '') !== (string)$row['dish_name']
                || $money($dish['dish_number'] ?? 0) !== $money($row['dish_number'])
                || $money($dish['dish_total_price'] ?? 0)
                    !== $money($row['dish_total_price'])
                || $money($dish['unit_price'] ?? 0)
                    !== ((float)$row['dish_number'] == 0.0 ? '0.00'
                        : $money(round((float)$row['dish_total_price']
                            / (float)$row['dish_number'], 2)))) {
                throw new RuntimeException('Export detail dish/list/source mismatch');
            }
            $seen[$uuid] = true;
        }
        if (count($seen) !== count($source)) {
            throw new RuntimeException('Export detail dish list incomplete');
        }
        $listSourceMatched = true;
    } else {
        if ((int)($list['data']['total'] ?? -1) !== count($source)
            || count($listRows) !== count($source)) {
            throw new RuntimeException('Export user paired list count mismatch');
        }
        $remaining = [];
        foreach ($source as $row) {
            $uuid = (string)$row['dishes_uuid'];
            if (isset($remaining[$uuid])) {
                throw new RuntimeException('Export user UUID not unique in bounded fixture');
            }
            $remaining[$uuid] = $row;
        }
        foreach ($listRows as $item) {
            $uuid = (string)($item['dishes_uuid'] ?? '');
            $row = $remaining[$uuid] ?? null;
            if (!$row || (string)($item['order_no'] ?? '') !== (string)$row['order_no']
                || (string)($item['user_name'] ?? '') !== (string)$row['user_name']
                || (string)($item['mobile'] ?? '') !== (string)$row['mobile']
                || (string)($item['department_name'] ?? '')
                    !== (string)$row['department_name']
                || (string)($item['dishes_name'] ?? '') !== (string)$row['dish_name']
                || $money($item['total_price'] ?? 0) !== $money($row['total_price'])
                || $money($item['pay_amount'] ?? 0)
                    !== $money((float)$row['pay_price'] - (float)$row['refund_amount'])
                || $money($item['dish_number'] ?? 0) !== $money($row['dish_number'])
                || $money($item['dish_total_price'] ?? 0)
                    !== $money($row['dish_total_price'])
                || $money($item['unit_price'] ?? 0)
                    !== ((float)$row['dish_number'] == 0.0 ? '0.00'
                        : $money(round((float)$row['dish_total_price']
                            / (float)$row['dish_number'], 2)))) {
                throw new RuntimeException('Export user list/source mismatch');
            }
            unset($remaining[$uuid]);
        }
        if ($remaining) throw new RuntimeException('Export user list source incomplete');
        $listSourceMatched = true;
    }

    $download = $http($exportRoute, $form, 'GET', $filePath);
    $downloadBytes = (int)$download['bytes'];
    $journal['state'] = 'downloaded_private_xls';
    $saveJournal();
    if ($mode === 'detail') {
        $header = ['序号', '部门', '下单日期', '就餐日期', '总份数', '订单金额',
            '实付金额', '菜品名称', '菜品单价', '菜品份数', '菜品总价'];
        $xlsRows = $readXls($filePath, $header, count($source));
        $dishes = $listRows[0]['dishes'];
        if (count($xlsRows) !== count($dishes)) {
            throw new RuntimeException('Export detail XLS row count mismatch');
        }
        foreach ($dishes as $index => $dish) {
            $actual = $xlsRows[$index];
            $expected = [
                $index === 0 ? '1' : '',
                $index === 0 ? (string)$first['department_name'] : '',
                $index === 0 ? $window : '',
                $index === 0 ? $window : '',
                $index === 0 ? $money($first['meal_number']) : '',
                $index === 0 ? $money($first['total_price']) : '',
                $index === 0 ? $money((float)$first['pay_price']
                    - (float)$first['refund_amount']) : '',
                (string)$dish['dishes_name'],
                $money($dish['unit_price']),
                $money($dish['dish_number']),
                $money($dish['dish_total_price']),
            ];
            foreach ($expected as $column => $value) {
                $got = in_array($column, [4, 5, 6, 8, 9, 10], true) && $value !== ''
                    ? $money($actual[$column]) : $actual[$column];
                if ($got !== $value) {
                    throw new RuntimeException('Export detail XLS/list/source cell mismatch');
                }
            }
        }
        $sheetRows = count($xlsRows);
    } elseif ($mode === 'user') {
        $header = ['序号', '下单日期', '就餐日期', '姓名', '所属部门',
            '手机号', '订单号', '订单金额', '实付金额', '备注',
            '菜品名称', '菜品单价', '菜品份数', '菜品总价'];
        $xlsRows = $readXls($filePath, $header, count($source));
        $expected = [];
        foreach ($source as $row) {
            $number = (float)$row['dish_number'];
            $unit = $number == 0.0 ? '0.00'
                : $money(round((float)$row['dish_total_price'] / $number, 2));
            $payload = [
                $window, $window, (string)$row['user_name'],
                (string)$row['department_name'], (string)$row['mobile'],
                (string)$row['order_no'], $money($row['total_price']),
                $money((float)$row['pay_price'] - (float)$row['refund_amount']),
                $text($row['remark']), $text($row['dish_name']),
                $unit, $money($row['dish_number']),
                $money($row['dish_total_price']),
            ];
            $key = hash('sha256', serialize($payload));
            $expected[$key] = ($expected[$key] ?? 0) + 1;
        }
        foreach ($xlsRows as $index => $actual) {
            if ($actual[0] !== (string)($index + 1)) {
                throw new RuntimeException('Export user XLS index mismatch');
            }
            $payload = array_slice($actual, 1);
            foreach ([6, 7, 10, 11, 12] as $moneyIndex) {
                $payload[$moneyIndex] = $money($payload[$moneyIndex]);
            }
            $key = hash('sha256', serialize(array_values($payload)));
            if (($expected[$key] ?? 0) < 1) {
                throw new RuntimeException('Export user XLS/list/source row mismatch');
            }
            $expected[$key]--;
        }
        if (array_sum($expected) !== 0) {
            throw new RuntimeException('Export user XLS missing source row');
        }
        $sheetRows = count($xlsRows);
    } else {
        $header = ['序号', '充值日期', '部门名称', '姓名', '手机号',
            '充值类型', '充值方式', '支付金额', '赠送金额', '实际到账金额'];
        $xlsRows = $readXls($filePath, $header, 2);
        if (count($xlsRows) !== 2) {
            throw new RuntimeException('Export recharge XLS row count mismatch');
        }
        $item = $listRows[0];
        if ($text($item['mobile'] ?? '') !== $text($sourceStaff['mobile'] ?? '')
            || $text($item['user']['name'] ?? '') !== $rechargeAccount) {
            throw new RuntimeException('Export recharge Store staff/list mismatch');
        }
        $expectedBusiness = ['1', $rechargeDate . '至' . $rechargeDate,
            $text($item['department_name'] ?? ''), $rechargeAccount,
            $text($sourceStaff['mobile']), $text($item['recharge_type'] ?? ''),
            $text($item['recharge_method'] ?? ''),
            $money($first['pay_price']), $money($first['gift_money']),
            $money($first['actual_money'])];
        $expectedTotal = ['合计', '', '', '', '', '', '',
            $money($first['pay_price']), $money($first['gift_money']),
            $money($first['actual_money'])];
        foreach ([$expectedBusiness, $expectedTotal] as $rowIndex => $expected) {
            foreach ($expected as $column => $value) {
                $got = $column >= 7 ? $money($xlsRows[$rowIndex][$column])
                    : $xlsRows[$rowIndex][$column];
                if ($got !== $value) {
                    throw new RuntimeException('Export recharge XLS/list/source cell mismatch');
                }
            }
        }
        $sheetRows = count($xlsRows);
    }
    $workbookMatched = true;
    $token = '';
    $after = $readSourceAgain();
    $sourceAfter = hash('sha256', serialize($after)) === $sourceBefore;
    if (!$sourceAfter) throw new RuntimeException('Export business source changed');
    $storeAfter = $connect('store');
    $queueAfter = $readQueue($storeAfter);
    $queueAfterTotal = $queueAfter['total'];
    $queueAfterPending = $queueAfter['pending'];
    $queueStable = $queueAfterTotal === $queueBeforeTotal
        && $queueAfterPending === $queueBeforePending;
    if (!$queueStable) throw new RuntimeException('Export queue changed');
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    $token = '';
    if (is_file($filePath)) @unlink($filePath);
    $workerStopped = !is_dir('/proc/' . $pid);
    $boundarySafe = $workerStopped || $runtimeReady();
    if ($boundarySafe) {
        try {
            $cleanupDb = $connect('store');
            $check = $cleanupDb->prepare('SELECT ID FROM ' . $prefix . 'user'
                . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
            $check->execute([$actor, $actor, 'CODEX']);
            $owned = $check->fetchAll(PDO::FETCH_ASSOC);
            if (count($owned) > 1) throw new RuntimeException('Ambiguous export actor');
            if ($owned) {
                $id = (int)(array_change_key_case($owned[0])['id'] ?? 0);
                if ($id <= 0 || ($operatorId > 0 && $id !== $operatorId)) {
                    throw new RuntimeException('Export actor ownership changed');
                }
                $delete = $cleanupDb->prepare('DELETE FROM ' . $prefix . 'user'
                    . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
                $delete->execute([$id, $actor, $actor, 'CODEX']);
                if ($delete->rowCount() !== 1) {
                    throw new RuntimeException('Export actor exact cleanup failed');
                }
            }
            $check->execute([$actor, $actor, 'CODEX']);
            if ($check->fetch(PDO::FETCH_ASSOC)) {
                throw new RuntimeException('Export actor remains after cleanup');
            }
            $cleaned = true;
        } catch (Throwable $e) {
            $failure .= ($failure === '' ? '' : ';')
                . 'cleanup:' . get_class($e) . ':' . hash('sha256', $e->getMessage());
        }
    } else {
        $failure .= ($failure === '' ? '' : ';') . 'cleanup:runtime_boundary_drift';
    }
    if ($cleaned) {
        if (is_file($journalPath)) @unlink($journalPath);
        if (is_file($privateDir . '/recovery.next')) @unlink($privateDir . '/recovery.next');
        @rmdir($privateDir);
    } else {
        $journal['state'] = 'requires_manual_recovery';
        try { $saveJournal(); } catch (Throwable $e) { /* retain existing journal */ }
    }
}
$result = ['lane' => $lane, 'mode' => $mode,
    'source_business_rows' => $businessRows,
    'list_source_matched' => $listSourceMatched,
    'xlsx_signature_sheet_header_and_rows_matched' => $workbookMatched,
    'xlsx_bytes' => $downloadBytes,
    'xlsx_business_rows' => $sheetRows - ($mode === 'recharge' ? 1 : 0),
    'source_unchanged' => $sourceAfter,
    'export_queue_unchanged' => $queueStable,
    'export_log_before_total' => $queueBeforeTotal,
    'export_log_before_pending' => $queueBeforePending,
    'export_log_after_total' => $queueAfterTotal,
    'export_log_after_pending' => $queueAfterPending,
    'actor_exact_cleanup_zero' => $cleaned,
    'failure' => $failure];
echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($failure !== '' || !$listSourceMatched || !$workbookMatched
    || !$sourceAfter || !$queueStable || !$cleaned) exit(1);
