<?php

declare(strict_types=1);

// Draft only. No DML/HTTP write unless root separately sets the exact approval flag.
$GLOBALS['poc_preflight_stage'] = 'runtime_target';
set_exception_handler(static function (Throwable $error): void {
    fwrite(STDERR, 'USER_PERMISSION_TEST_FAILED ' . get_class($error)
        . ' code=' . (string)$error->getCode()
        . ' stage=' . ($GLOBALS['poc_preflight_stage'] ?? 'unknown') . PHP_EOL);
    exit(2);
});
$dialect = (string)getenv('POC_DIALECT');
$variant = (string)getenv('POC_VARIANT');
$root = (string)getenv('POC_APP_ROOT');
$base = rtrim((string)getenv('POC_BASE_URL'), '/');
$runtimes = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080'],
];
if (!in_array($dialect, ['dm', 'mysql'], true)
    || !in_array($variant, ['A', 'B', 'C'], true)
    || ($dialect === 'dm') !== ($variant === 'C')
    || $root !== $runtimes[$variant][0] || $base !== $runtimes[$variant][1]
    || !is_file($root . '/application/base/Security.php')
    || !is_file($root . '/application/p/logic/User.php')
    || !preg_match('~^http://127\.0\.0\.1:[0-9]+$~', $base)) {
    throw new RuntimeException('Explicit isolated Store permission runtime required');
}
$GLOBALS['poc_preflight_stage'] = 'source_sha';
$oldSha = '601cb635cf0ba8800327fbe13210779d4150963207a1133dd2b286380f4536e1';
$newSha = '55875f71387dfae147e978c7cba6f56af599aa5a00776c3153b59ac57cb36bbc';
$expected = $variant === 'A' ? $oldSha : $newSha;
if (!hash_equals($expected, hash_file('sha256', $root . '/application/p/logic/User.php'))) {
    throw new RuntimeException('User logic runtime SHA is not the approved variant');
}
$GLOBALS['poc_preflight_stage'] = 'http_boundary';
$boundaryRaw = @file_get_contents($base . '/__poc_boundary', false,
    stream_context_create(['http' => ['timeout' => 5]]));
$boundary = is_string($boundaryRaw) ? json_decode($boundaryRaw, true) : null;
if (!is_array($boundary) || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated']) || !empty($boundary['real_queue'])) {
    throw new RuntimeException('Store external/Redis isolation boundary absent');
}
$GLOBALS['poc_preflight_stage'] = 'db_config';

if ($dialect === 'dm') {
    $pid = (string)getenv('POC_RUNTIME_PID');
    if (!ctype_digit($pid) || (int)$pid <= 1) {
        throw new RuntimeException('Approved Store HTTP PID required');
    }
    $processEnv = [];
    foreach (explode("\0", (string)@file_get_contents('/proc/' . $pid . '/environ')) as $entry) {
        $parts = explode('=', $entry, 2);
        if (count($parts) === 2) $processEnv[$parts[0]] = $parts[1];
    }
    $processArgs = explode("\0", (string)@file_get_contents('/proc/' . $pid . '/cmdline'));
    if (($processEnv['POC_APP_ROOT'] ?? '') !== $root
        || ($processEnv['POC_DIALECT'] ?? '') !== 'dm'
        || !in_array('-S', $processArgs, true)
        || !in_array('127.0.0.1:18080', $processArgs, true)
        || !in_array($root . '/public', $processArgs, true)
        || !in_array($root . '/public/index.php', $processArgs, true)) {
        throw new RuntimeException('Store HTTP process identity diverged');
    }
    foreach (['POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER', 'POC_PASSWORD'] as $key) {
        if (empty($processEnv[$key])) throw new RuntimeException('Store DM process DB context missing');
        putenv($key . '=' . $processEnv[$key]);
    }
    if ((string)getenv('POC_HOST') !== '127.0.0.1'
        || (string)getenv('POC_PORT') !== '5237' || (string)getenv('POC_SCHEMA') !== 'ZHCT_APP'
        || (string)getenv('POC_USER') !== 'ZHCT_APP') {
        throw new RuntimeException('Non-production Store DM account/schema boundary failed');
    }
    $dsn = 'dm:host=' . getenv('POC_HOST') . ';port=' . getenv('POC_PORT')
        . ';schema=' . getenv('POC_SCHEMA');
    $username = (string)getenv('POC_USER');
    $password = (string)getenv('POC_PASSWORD');
} else {
    $GLOBALS['poc_preflight_stage'] = 'mysql_paths';
    $local = '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    if (!is_file($local) || !is_file($root . '/application/database.php')) {
        throw new RuntimeException('Store local development DB config missing');
    }
    $loadConfig = static function (string $path): array { return require $path; };
    $source = $loadConfig($local);
    $running = $loadConfig($root . '/application/database.php');
    $GLOBALS['poc_preflight_stage'] = 'mysql_values';
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        $GLOBALS['poc_preflight_stage'] = 'mysql_value_' . $key;
        if (($source[$key] ?? null) !== ($running[$key] ?? null)) {
            throw new RuntimeException('Runtime differs from Store local development DB');
        }
    }
    $GLOBALS['poc_preflight_stage'] = 'mysql_port';
    if ((int)($source['hostport'] ?? 3306) !== (int)($running['hostport'] ?? 3306)) {
        throw new RuntimeException('Runtime differs from Store local development port');
    }
    $GLOBALS['poc_preflight_stage'] = 'mysql_pdo';
    $dsn = 'mysql:host=' . $running['hostname']
        . ';port=' . (int)($running['hostport'] ?? 3306)
        . ';dbname=' . $running['database'] . ';charset=utf8mb4';
    $username = (string)$running['username'];
    $password = (string)$running['password'];
    unset($source, $running);
}
$db = new PDO($dsn, $username, $password,
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER,
        PDO::ATTR_TIMEOUT => 5]);
unset($dsn, $username, $password);
$GLOBALS['poc_preflight_stage'] = 'db_schema';
if ($dialect === 'dm') {
    $current = (string)$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
        ->fetchColumn();
    if ($current !== 'ZHCT_APP') throw new RuntimeException('Effective Store DM schema diverged');
}
$table = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YDY_' . strtoupper($name) : 'ydy_' . $name;
};
$userTable = $table('user');
$dataTable = $table('user_data');
$departmentTable = $table('department');
$restaurantTable = $table('restaurants');
$rows = static function (string $sql, array $args = []) use ($db): array {
    $statement = $db->prepare($sql);
    $statement->execute($args);
    return $statement->fetchAll(PDO::FETCH_ASSOC);
};
$one = static function (string $sql, array $args = []) use ($rows): array {
    return $rows($sql, $args)[0] ?? [];
};
$count = static function (string $sql, array $args = []) use ($one): int {
    return (int)($one($sql, $args)['n'] ?? -1);
};
$assert = static function (bool $ok, string $message): void {
    if (!$ok) throw new RuntimeException($message);
};
$required = [
    'user' => ['ID', 'UUID', 'ACCOUNT', 'PWD', 'SALT', 'NAME', 'TYPE', 'CREATE_TIME', 'CREATE_BY', 'IS_DELETE'],
    'user_data' => ['ID', 'USER_UUID', 'DATA_UUID', 'STAFF_UUID_STR', 'TYPE'],
    'department' => ['ID', 'UUID', 'PUUID', 'CODE', 'NAME', 'CHARGE_BY', 'LEADER',
        'STATUS', 'IS_SHOW', 'LEAF_FLAG', 'CREATE_TIME', 'CREATE_BY'],
    'restaurants' => ['ID', 'UUID', 'NAME', 'BOOK_RULES', 'ORDER_RULES', 'STATUS', 'IS_DELETE'],
    'meal_order' => ['ID', 'RESTAURANT_ID'],
    'staff_department' => ['ID', 'DEPARTMENT_UUID'],
];
foreach ($required as $name => $columns) {
    $GLOBALS['poc_preflight_stage'] = 'columns_' . $name;
    $actual = $dialect === 'dm'
        ? $rows('SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME=?', [$table($name)])
        : $rows('SELECT COLUMN_NAME FROM information_schema.COLUMNS'
            . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?', [$table($name)]);
    $actualNames = array_map('strtoupper', array_column($actual, 'column_name'));
    $assert(count(array_diff($columns, $actualNames)) === 0,
        'Required Store permission columns missing: ' . $name);
}
$GLOBALS['poc_preflight_stage'] = 'ready_read_only';
if ((string)getenv('POC_APPROVED_USER_PERMISSION_WRITE') !== '1') {
    echo json_encode(['suite' => 'user-data-permission-' . $variant,
        'mode' => 'read-only-draft-preflight', 'source_sha_match' => true,
        'boundary_isolated' => true, 'required_columns_present' => true,
        'post_delete_rollback' => 'blocked-safety'], JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

require_once $root . '/application/base/Security.php';
$suffix = strtoupper(bin2hex(random_bytes(4)));
$marker = 'CDXPARPERM' . $suffix;
$operator = $marker . 'O';
$target = $marker . 'T';
$departmentName1 = $marker . 'D1';
$departmentName2 = $marker . 'D2';
$stallName = $marker . 'S';
$guid = static function (): string {
    $hex = strtoupper(bin2hex(random_bytes(16)));
    return substr($hex, 0, 8) . '-' . substr($hex, 8, 4) . '-'
        . substr($hex, 12, 4) . '-' . substr($hex, 16, 4) . '-'
        . substr($hex, 20, 12);
};
$departmentUuid1 = $guid();
$departmentUuid2 = $guid();
$stallUuid = $guid();
$type2Uuid = $guid();
$unknownUserUuid = $guid();
$operatorId = 0;
$targetId = 0;
$departmentIds = [];
$stallId = 0;
$results = [];
$cleanup = [];
$failure = '';
$http = static function (array $payload, string $token) use ($base): array {
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: " . $token . "\r\n",
        'content' => http_build_query($payload, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 25, 'ignore_errors' => true,
    ]]);
    $raw = @file_get_contents($base . '/p/user/saveDataPermission', false, $context);
    $headerOut = $http_response_header ?? [];
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headerOut[0] ?? ''), $match);
    $json = is_string($raw) ? json_decode($raw, true) : null;
    return ['http' => isset($match[1]) ? (int)$match[1] : 0,
        'code' => is_array($json) ? (int)($json['code'] ?? -1) : -1,
        'message' => is_array($json) ? (string)($json['message'] ?? '') : '',
        'data_empty' => is_array($json) && ($json['data'] ?? null) === []];
};
$permissionRows = static function () use ($rows, $dataTable, $target): array {
    return $rows('SELECT ID,USER_UUID,DATA_UUID,TYPE,STAFF_UUID_STR FROM ' . $dataTable
        . ' WHERE USER_UUID=? ORDER BY TYPE,DATA_UUID,ID', [$target]);
};
$permissionSet = static function (array $items): array {
    $set = [];
    foreach ($items as $item) $set[] = (int)$item['type'] . ':' . trim((string)$item['data_uuid']);
    sort($set, SORT_STRING);
    return $set;
};
$expectedSet = static function (array $items) use ($permissionSet): array {
    $rows = [];
    foreach ($items as $type => $uuids) {
        foreach ($uuids as $uuid) $rows[] = ['type' => $type, 'data_uuid' => $uuid];
    }
    return $permissionSet($rows);
};
$checkSnapshot = static function (array $expect, int $sentinelId) use ($assert, $permissionRows,
    $permissionSet, $expectedSet, $type2Uuid): void {
    $items = $permissionRows();
    $assert($permissionSet($items) === $expectedSet($expect), 'Target permission set mismatch');
    $sentinel = array_values(array_filter($items, static function ($item) use ($type2Uuid) {
        return (int)$item['type'] === 2 && trim((string)$item['data_uuid']) === $type2Uuid;
    }));
    $assert(count($sentinel) === 1 && (int)$sentinel[0]['id'] === $sentinelId
        && (string)$sentinel[0]['staff_uuid_str'] === '',
        'Target type2 sentinel changed');
};
try {
    $assert($count('SELECT COUNT(*) N FROM ' . $userTable
        . ' WHERE ACCOUNT IN (?,?)', [$operator, $target]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $departmentTable
            . ' WHERE NAME IN (?,?)', [$departmentName1, $departmentName2]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $restaurantTable
            . ' WHERE NAME=?', [$stallName]) === 0,
        'Synthetic permission markers already present');

    foreach ([[$operator, 0], [$target, 1]] as $user) {
        $db->prepare('INSERT INTO ' . $userTable
            . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
            . ' VALUES (?,?,?,?,?,?,?,?,?)')
            ->execute([$user[0], $user[0], 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
                $user[0], $user[1], date('Y-m-d H:i:s'), 'CODEX', 0]);
    }
    $operatorId = (int)($one('SELECT ID FROM ' . $userTable
        . ' WHERE UUID=? AND ACCOUNT=?', [$operator, $operator])['id'] ?? 0);
    $targetId = (int)($one('SELECT ID FROM ' . $userTable
        . ' WHERE UUID=? AND ACCOUNT=?', [$target, $target])['id'] ?? 0);
    $assert($operatorId > 0 && $targetId > 0, 'Synthetic accounts not inserted');

    foreach ([[$departmentUuid1, $departmentName1, '8' . random_int(10000000, 99999999)],
        [$departmentUuid2, $departmentName2, '8' . random_int(10000000, 99999999)]] as $item) {
        $assert($count('SELECT COUNT(*) N FROM ' . $departmentTable
            . ' WHERE PUUID=? AND CODE=?', ['0', $item[2]]) === 0,
            'Synthetic department code occupied');
        $db->prepare('INSERT INTO ' . $departmentTable
            . ' (UUID,PUUID,CODE,NAME,CHARGE_BY,LEADER,LEAF_FLAG,STATUS,IS_SHOW,CREATE_TIME,CREATE_BY)'
            . ' VALUES (?,?,?,?,?,?,?,?,?,?,?)')
            ->execute([$item[0], '0', $item[2], $item[1], '', '', 0, 1, 1,
                date('Y-m-d H:i:s'), $marker]);
        $departmentIds[] = (int)($one('SELECT ID FROM ' . $departmentTable
            . ' WHERE UUID=? AND NAME=?', [$item[0], $item[1]])['id'] ?? 0);
    }
    $db->prepare('INSERT INTO ' . $restaurantTable
        . ' (UUID,NAME,BOOK_RULES,ORDER_RULES,STATUS,IS_DELETE,STALL_TYPE,CREATE_TIME,CREATE_BY)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)')
        ->execute([$stallUuid, $stallName, '{}', '{}', 1, 2, 2,
            date('Y-m-d H:i:s'), $marker]);
    $stallId = (int)($one('SELECT ID FROM ' . $restaurantTable
        . ' WHERE UUID=? AND NAME=?', [$stallUuid, $stallName])['id'] ?? 0);
    $assert($stallId > 0 && count($departmentIds) === 2
        && min($departmentIds) > 0, 'Synthetic department/stall insert incomplete');

    $db->prepare('INSERT INTO ' . $dataTable
        . ' (USER_UUID,DATA_UUID,STAFF_UUID_STR,TYPE) VALUES (?,?,?,?)')
        ->execute([$target, $type2Uuid, '', 2]);
    $sentinelId = (int)($one('SELECT ID FROM ' . $dataTable
        . ' WHERE USER_UUID=? AND DATA_UUID=? AND TYPE=2', [$target, $type2Uuid])['id'] ?? 0);
    $assert($sentinelId > 0, 'Synthetic type2 sentinel missing');
    $checkSnapshot([2 => [$type2Uuid]], $sentinelId);
    $results['fixture_synthetic_two_users_two_departments_one_stall_type2'] = true;

    $token = \app\base\Security::encrypt(implode("\t",
        ['0', $operator, $operator, '0', '', '', $operator]));
    $first = $http(['uuid' => $target, 'department_uuids' => $departmentUuid1,
        'stall_uuids' => $stallUuid], $token);
    $assert($first['http'] === 200 && $first['code'] === 0
        && $first['message'] === '保存成功' && $first['data_empty'],
        'First permission HTTP contract failed');
    $checkSnapshot([1 => [$departmentUuid1], 2 => [$type2Uuid], 3 => [$stallUuid]], $sentinelId);
    $results['first_save_set_and_type2'] = true;

    $repeat = $http(['uuid' => $target, 'department_uuids' => [$departmentUuid1],
        'stall_uuids' => [$stallUuid]], $token);
    $assert($repeat['http'] === 200 && $repeat['code'] === 0
        && $repeat['message'] === '保存成功', 'Repeat permission HTTP contract failed');
    $checkSnapshot([1 => [$departmentUuid1], 2 => [$type2Uuid], 3 => [$stallUuid]], $sentinelId);
    $results['repeat_same_set_and_type2'] = true;

    $changed = $http(['uuid' => $target, 'department_uuids' => $departmentUuid2,
        'stall_uuids' => ''], $token);
    $assert($changed['http'] === 200 && $changed['code'] === 0
        && $changed['message'] === '保存成功', 'Changed permission HTTP contract failed');
    $checkSnapshot([1 => [$departmentUuid2], 2 => [$type2Uuid]], $sentinelId);
    $results['change_department_clear_stall_type2'] = true;

    $empty = $http(['uuid' => $target, 'department_uuids' => '', 'stall_uuids' => ''], $token);
    $assert($empty['http'] === 200 && $empty['code'] === 0
        && $empty['message'] === '保存成功', 'Empty permission HTTP contract failed');
    $checkSnapshot([2 => [$type2Uuid]], $sentinelId);
    $results['empty_set_keeps_only_type2'] = true;

    $missing = $http(['uuid' => $unknownUserUuid,
        'department_uuids' => $departmentUuid1, 'stall_uuids' => $stallUuid], $token);
    $assert($missing['http'] === 200 && $missing['code'] === 1,
        'Missing synthetic target was not rejected');
    $checkSnapshot([2 => [$type2Uuid]], $sentinelId);
    $results['missing_synthetic_user_predelete_rejected'] = true;
    $results['post_delete_failure_rollback'] = 'blocked-safety';
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    try {
        $owned = $permissionRows();
        foreach ($owned as $item) {
            if (!in_array(trim((string)$item['data_uuid']),
                [$departmentUuid1, $departmentUuid2, $stallUuid, $type2Uuid], true)
                || !in_array((int)$item['type'], [1, 2, 3], true)) {
                throw new RuntimeException('Unexpected target permission; refusing cleanup');
            }
        }
        foreach ($owned as $item) {
            $db->prepare('DELETE FROM ' . $dataTable
                . ' WHERE ID=? AND USER_UUID=? AND DATA_UUID=? AND TYPE=?')
                ->execute([(int)$item['id'], $target,
                    trim((string)$item['data_uuid']), (int)$item['type']]);
        }
        $stallOwned = $rows('SELECT ID,UUID,NAME,CREATE_BY FROM ' . $restaurantTable
            . ' WHERE UUID=? AND NAME=? AND CREATE_BY=?', [$stallUuid, $stallName, $marker]);
        if (count($stallOwned) > 1) throw new RuntimeException('Ambiguous synthetic stall cleanup');
        if (count($stallOwned) === 1) {
            $id = (int)$stallOwned[0]['id'];
            if ($stallId > 0 && $id !== $stallId) throw new RuntimeException('Stall ID changed');
            if ($count('SELECT COUNT(*) N FROM ' . $table('meal_order')
                . ' WHERE RESTAURANT_ID=?', [$id]) !== 0) {
                throw new RuntimeException('Synthetic stall acquired orders; refusing cleanup');
            }
            $db->prepare('DELETE FROM ' . $restaurantTable
                . ' WHERE ID=? AND UUID=? AND NAME=? AND CREATE_BY=?')
                ->execute([$id, $stallUuid, $stallName, $marker]);
        }
        foreach ([[$departmentIds[0] ?? 0, $departmentUuid1, $departmentName1],
            [$departmentIds[1] ?? 0, $departmentUuid2, $departmentName2]] as $item) {
            $found = $rows('SELECT ID,UUID,NAME,CREATE_BY FROM ' . $departmentTable
                . ' WHERE UUID=? AND NAME=? AND CREATE_BY=?', [$item[1], $item[2], $marker]);
            if (count($found) > 1) throw new RuntimeException('Ambiguous synthetic department cleanup');
            if (count($found) === 1) {
                $id = (int)$found[0]['id'];
                if ($item[0] > 0 && $id !== $item[0]) {
                    throw new RuntimeException('Department ID changed');
                }
                if ($count('SELECT COUNT(*) N FROM ' . $table('staff_department')
                    . ' WHERE DEPARTMENT_UUID=?', [$item[1]]) !== 0) {
                    throw new RuntimeException('Synthetic department acquired staff; refusing cleanup');
                }
                $db->prepare('DELETE FROM ' . $departmentTable
                    . ' WHERE ID=? AND UUID=? AND NAME=? AND CREATE_BY=?')
                    ->execute([$id, $item[1], $item[2], $marker]);
            }
        }
        foreach ([[$targetId, $target], [$operatorId, $operator]] as $item) {
            $found = $rows('SELECT ID,UUID,ACCOUNT FROM ' . $userTable
                . ' WHERE UUID=? AND ACCOUNT=?', [$item[1], $item[1]]);
            if (count($found) > 1) throw new RuntimeException('Ambiguous synthetic user cleanup');
            if (count($found) === 1) {
                $id = (int)$found[0]['id'];
                if ($item[0] > 0 && $id !== $item[0]) throw new RuntimeException('User ID changed');
                $db->prepare('DELETE FROM ' . $userTable
                    . ' WHERE ID=? AND UUID=? AND ACCOUNT=?')
                    ->execute([$id, $item[1], $item[1]]);
            }
        }
        $cleanup = [
            'target_permission_zero' => $count('SELECT COUNT(*) N FROM ' . $dataTable
                . ' WHERE USER_UUID=?', [$target]) === 0,
            'users_zero' => $count('SELECT COUNT(*) N FROM ' . $userTable
                . ' WHERE ACCOUNT IN (?,?)', [$operator, $target]) === 0,
            'departments_zero' => $count('SELECT COUNT(*) N FROM ' . $departmentTable
                . ' WHERE NAME IN (?,?)', [$departmentName1, $departmentName2]) === 0,
            'stall_zero' => $count('SELECT COUNT(*) N FROM ' . $restaurantTable
                . ' WHERE NAME=?', [$stallName]) === 0,
        ];
    } catch (Throwable $e) {
        $cleanup['error_class'] = get_class($e);
        $cleanup['error_sha256'] = hash('sha256', $e->getMessage());
    }
}
$clean = ($cleanup['target_permission_zero'] ?? false)
    && ($cleanup['users_zero'] ?? false)
    && ($cleanup['departments_zero'] ?? false)
    && ($cleanup['stall_zero'] ?? false);
echo json_encode(['suite' => 'user-data-permission-' . $variant,
    'marker' => $marker, 'results' => $results,
    'failure' => $failure, 'cleanup' => $cleanup],
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
if ($failure !== '' || !$clean) exit(1);
