<?php

declare(strict_types=1);

// Four bounded Store PC read routes. Default is SELECT-only preflight.
$action = (string)($argv[1] ?? '');
$lane = (string)getenv('CDX_READ4_LANE');
if (!in_array($action, ['--preflight', '--execute'], true)
    || !in_array($lane, ['A', 'B', 'C'], true)
    || ($action === '--execute'
        && ((string)getenv('CDX_READ4_EXECUTE') !== 'ROOT_APPROVED_READ4_V1'
            || (string)getenv('CDX_READ4_SHA') !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "Read4 business HTTP and fixture DML disabled\n");
    exit(78);
}
$previousEmbed = getenv('CDX_READ4_EMBEDDED');
putenv('CDX_READ4_EMBEDDED=ONLY_FROM_REVIEWED_HARNESS');
try {
    $ctx = (static function (): array {
        $argv = ['source_preflight.php', '--embedded-preflight'];
        $context = require __DIR__ . '/source_preflight.php';
        if (!is_array($context)) throw new RuntimeException('Read4 source context absent');
        return $context;
    })();
} finally {
    if ($previousEmbed === false) putenv('CDX_READ4_EMBEDDED');
    else putenv('CDX_READ4_EMBEDDED=' . $previousEmbed);
}
[$db, $p, $dialect, $root, $base, $pid] = [
    $ctx['db'], $ctx['prefix'], $ctx['dialect'], $ctx['root'],
    $ctx['base'], $ctx['pid']];
$read = $ctx['read'];
$count = $ctx['count'];
$pre = $ctx['result'];
if (!$pre['birthday_menu_exact'] || !$pre['review_menu_exact']
    || $pre['stale_synthetic_users'] !== 0
    || count($ctx['active_dishes']) < 20
    || !is_array($ctx['questionnaire_candidate'])
    || count($ctx['questionnaire_group_rows']) !== 1
    || count($ctx['questionnaire_tag_rows']) !== 1) {
    throw new RuntimeException('Read4 natural business source/menu not bounded');
}
$candidate = $ctx['questionnaire_candidate'];
$questionnaireId = (int)$candidate['id'];
$restaurantId = (int)$candidate['restaurant_id'];
$dishUuid = (string)$candidate['dishes_uuid'];
$mealDate = substr((string)$candidate['meal_date'], 0, 10);
$mealTimes = (int)$candidate['meal_times'];
if ($questionnaireId <= 0 || $restaurantId <= 0 || $mealTimes < 1 || $mealTimes > 4
    || !preg_match('/^[A-Fa-f0-9-]{36}$/D', $dishUuid)
    || !preg_match('/^\d{4}-\d{2}-\d{2}$/D', $mealDate)) {
    throw new RuntimeException('Read4 questionnaire candidate invalid');
}
$restaurant = $read('SELECT ID,UUID,NAME FROM ' . $p . 'restaurants'
    . ' WHERE ID=? AND STATUS=1 AND IS_DELETE=2', [$restaurantId]);
if (count($restaurant) !== 1) throw new RuntimeException('Read4 restaurant scope absent');
$department = $read('SELECT UUID FROM ' . $p . 'department'
    . ' WHERE STATUS=1 AND IS_SHOW=1 AND LEAF_FLAG=1 ORDER BY ID'
    . ($dialect === 'dm' ? ' FETCH FIRST 1 ROW ONLY' : ' LIMIT 1'));
if (count($department) !== 1 || (string)$department[0]['uuid'] === '') {
    throw new RuntimeException('Read4 single leaf department absent');
}
$commentField = $dialect === 'dm' ? 'q."COMMENT"' : 'q.`comment`';
$fullQuestion = $read('SELECT q.ID,q.STORE_ID,q.RESTAURANT_ID,q.DISHES_UUID,'
    . ' q.DISHES_NAME_SNAPSHOT,q.MEAL_DATE,q.MEAL_TIMES,q.SCORE,'
    . $commentField . ' AS COMMENT_TEXT,q.WISH_DISH_KEY,q.WISH_DISH_NAME,'
    . ' q.CREATE_TIME FROM ' . $p . 'dish_questionnaire q'
    . ' WHERE q.ID=? AND q.STORE_ID=10001 AND q.IS_DELETE=0', [$questionnaireId]);
if (count($fullQuestion) !== 1
    || strtoupper((string)$fullQuestion[0]['dishes_uuid']) !== strtoupper($dishUuid)
    || (int)$fullQuestion[0]['restaurant_id'] !== $restaurantId) {
    throw new RuntimeException('Read4 exact questionnaire source drift');
}
$question = $fullQuestion[0];
$tagRows = $ctx['questionnaire_tag_rows'];
$sourceDishRows = array_slice($ctx['active_dishes'], 0, 20);
$orderedDishRows = $read('SELECT ID,UUID,NAME,CATEGORY,CATEGORY_ONE,CATEGORY_TWO,FILE_PATH'
    . ' FROM ' . $p . 'dishes WHERE STORE_ID=10001 AND STATUS=1'
    . ' AND IS_DEL=1 AND IS_DELETE=0 ORDER BY NAME,ID'
    . ($dialect === 'dm' ? ' FETCH FIRST 20 ROWS ONLY' : ' LIMIT 20'));
if (hash('sha256', serialize($sourceDishRows))
    !== hash('sha256', serialize($orderedDishRows))) {
    throw new RuntimeException('Read4 source top-20 ordering drift');
}
$dishTotal = $count('SELECT COUNT(*) FROM ' . $p . 'dishes'
    . ' WHERE STORE_ID=10001 AND STATUS=1 AND IS_DEL=1 AND IS_DELETE=0');
if ($dishTotal < 20) throw new RuntimeException('Read4 active dish total absent');
$birthdayMenuUuid = (string)$ctx['birthday_menu']['uuid'];
$reviewMenuUuid = (string)$ctx['review_menu']['uuid'];
if (strcasecmp($birthdayMenuUuid, $reviewMenuUuid) === 0) {
    throw new RuntimeException('Read4 two read menus unexpectedly identical');
}
$schema = ['user' => ['ID', 'UUID', 'ACCOUNT', 'PWD', 'SALT', 'NAME', 'TYPE',
        'CREATE_TIME', 'CREATE_BY'],
    'role' => ['ID', 'UUID', 'NAME', 'STATUS', 'CREATE_TIME', 'CREATE_BY'],
    'user_role' => ['ID', 'USER_UUID', 'ROLE_UUID', 'CREATE_TIME', 'CREATE_BY'],
    'role_menu' => ['ID', 'ROLE_UUID', 'MENU_UUID', 'CREATE_TIME', 'CREATE_BY'],
    'user_data' => ['ID', 'USER_UUID', 'DATA_UUID', 'TYPE']];
foreach ($schema as $table => $fields) {
    foreach ($fields as $field) {
        if ($dialect === 'mysql') {
            $n = $count('SELECT COUNT(*) FROM information_schema.COLUMNS'
                . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND COLUMN_NAME=?',
                [$p . $table, strtolower($field)]);
        } else {
            $n = $count('SELECT COUNT(*) FROM USER_TAB_COLUMNS'
                . ' WHERE TABLE_NAME=? AND COLUMN_NAME=?',
                [$p . strtoupper($table), $field]);
        }
        if ($n !== 1) throw new RuntimeException('Read4 permission fixture column absent');
    }
}
$fixtureIdDefinitionShas = [];
foreach (array_keys($schema) as $table) {
    if ($dialect === 'mysql') {
        $extra = $read('SELECT EXTRA FROM information_schema.COLUMNS'
            . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND COLUMN_NAME=?',
            [$p . $table, 'id']);
        if (count($extra) !== 1
            || stripos((string)$extra[0]['extra'], 'auto_increment') === false) {
            throw new RuntimeException('Read4 MySQL fixture ID not auto increment');
        }
        $fixtureIdDefinitionShas[$table] = hash('sha256', (string)$extra[0]['extra']);
    } else {
        $stmt = $db->prepare('CALL SP_TABLEDEF(?, ?)');
        $stmt->execute(['ZHCT_APP', $p . strtoupper($table)]);
        $definition = $stmt->fetchColumn();
        if (is_resource($definition)) $definition = stream_get_contents($definition);
        $definition = (string)$definition;
        if (!preg_match('/"ID"\s+[A-Z0-9]+\s+AUTO_INCREMENT\s+NOT\s+NULL/i',
            $definition)) {
            throw new RuntimeException('Read4 DM fixture ID not auto increment');
        }
        $fixtureIdDefinitionShas[$table] = hash('sha256', $definition);
    }
}
$expectedUnique = ['role' => ['name'],
    'user_role' => ['user_uuid', 'role_uuid'],
    'role_menu' => ['role_uuid', 'menu_uuid'],
    'user_data' => ['user_uuid', 'data_uuid', 'type']];
foreach ($expectedUnique as $table => $fields) {
    if ($dialect === 'mysql') {
        $rows = $read('SELECT INDEX_NAME,COLUMN_NAME,SEQ_IN_INDEX'
            . ' FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE()'
            . ' AND TABLE_NAME=? AND NON_UNIQUE=0 ORDER BY INDEX_NAME,SEQ_IN_INDEX',
            [$p . $table]);
        $groupKey = 'index_name';
    } else {
        $rows = $read('SELECT i.INDEX_NAME,c.COLUMN_NAME,c.COLUMN_POSITION'
            . ' FROM USER_INDEXES i JOIN USER_IND_COLUMNS c'
            . ' ON c.INDEX_NAME=i.INDEX_NAME AND c.TABLE_NAME=i.TABLE_NAME'
            . ' WHERE i.TABLE_NAME=? AND i.UNIQUENESS=\'UNIQUE\''
            . ' ORDER BY i.INDEX_NAME,c.COLUMN_POSITION',
            [$p . strtoupper($table)]);
        $groupKey = 'index_name';
    }
    $groups = [];
    foreach ($rows as $row) $groups[(string)$row[$groupKey]][] = strtolower((string)$row['column_name']);
    if (!in_array($fields, array_values($groups), true)) {
        throw new RuntimeException('Read4 permission fixture unique key absent');
    }
}
$fixtureTables = ['user', 'role', 'user_role', 'role_menu', 'user_data'];
if ($dialect === 'mysql') {
    $outFk = $count('SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN (?,?,?,?,?)'
        . ' AND REFERENCED_TABLE_NAME IS NOT NULL',
        array_map(static function (string $x): string { return 'ydy_' . $x; }, $fixtureTables));
    $inFk = $count('SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE'
        . ' WHERE REFERENCED_TABLE_SCHEMA=DATABASE()'
        . ' AND REFERENCED_TABLE_NAME IN (?,?,?,?,?)',
        array_map(static function (string $x): string { return 'ydy_' . $x; }, $fixtureTables));
    $triggers = $count('SELECT COUNT(*) FROM information_schema.TRIGGERS'
        . ' WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (?,?,?,?,?)',
        array_map(static function (string $x): string { return 'ydy_' . $x; }, $fixtureTables));
} else {
    $outFk = $count('SELECT COUNT(*) FROM USER_CONSTRAINTS'
        . " WHERE CONSTRAINT_TYPE='R' AND TABLE_NAME IN ('YDY_USER','YDY_ROLE',"
        . "'YDY_USER_ROLE','YDY_ROLE_MENU','YDY_USER_DATA')");
    $inFk = $count('SELECT COUNT(*) FROM ALL_CONSTRAINTS c JOIN ALL_CONSTRAINTS parent'
        . ' ON c.R_OWNER=parent.OWNER AND c.R_CONSTRAINT_NAME=parent.CONSTRAINT_NAME'
        . " WHERE c.CONSTRAINT_TYPE='R' AND parent.OWNER='ZHCT_APP'"
        . " AND parent.TABLE_NAME IN ('YDY_USER','YDY_ROLE','YDY_USER_ROLE',"
        . "'YDY_ROLE_MENU','YDY_USER_DATA')");
    $triggers = $count('SELECT COUNT(*) FROM USER_TRIGGERS'
        . " WHERE TABLE_NAME IN ('YDY_USER','YDY_ROLE','YDY_USER_ROLE',"
        . "'YDY_ROLE_MENU','YDY_USER_DATA')");
}
if ($outFk !== 0 || $inFk !== 0 || $triggers !== 0) {
    throw new RuntimeException('Read4 permission fixture FK/trigger boundary changed');
}
$markerCount = 0;
foreach (['user' => 'ACCOUNT', 'role' => 'NAME'] as $table => $field) {
    $markerCount += $count('SELECT COUNT(*) FROM ' . $p . $table
        . ' WHERE ' . $field . " LIKE 'CDXBDQ4%'");
}
if ($markerCount !== 0) throw new RuntimeException('Read4 old synthetic actor/role residue');
$sourceBefore = ['dishes' => hash('sha256', serialize($sourceDishRows)),
    'questionnaire' => hash('sha256', serialize($fullQuestion)),
    'tags' => hash('sha256', serialize($tagRows)),
    'restaurant' => hash('sha256', serialize($restaurant))];
$preflight = ['lane' => $lane, 'dialect' => $dialect,
    'natural_dishes_total' => $dishTotal,
    'dish_page_rows' => count($sourceDishRows),
    'questionnaire_group_rows' => count($ctx['questionnaire_group_rows']),
    'questionnaire_record_rows' => count($fullQuestion),
    'questionnaire_tag_rows' => count($tagRows),
    'review_restaurant_id' => $restaurantId,
    'review_dish_uuid_sha12' => substr(hash('sha256', $dishUuid), 0, 12),
    'menu_count' => 2, 'leaf_department_present' => true,
    'fixture_unique_keys_present' => true,
    'fixture_ids_auto_increment' => true,
    'fixture_id_definition_shas' => $fixtureIdDefinitionShas,
    'ordered_dish_page_sha' => hash('sha256', serialize($orderedDishRows)),
    'fixture_incoming_fk' => $inFk, 'fixture_outgoing_fk' => $outFk,
    'fixture_triggers' => $triggers,
    'old_marker_rows' => $markerCount,
    'source_shas' => $sourceBefore,
    'business_http_executed' => false, 'dml_executed' => false];
if ($action === '--preflight') {
    echo json_encode($preflight, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
if ((string)getenv('CDX_READ4_DB_SHA') !== ($dialect === 'dm'
    ? 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194'
    : '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401')) {
    throw new RuntimeException('Read4 explicit DB identity confirmation absent');
}

// Only the reviewed --execute mode can reach the permission fixture and HTTP below.
$marker = 'CDXBDQ4' . strtoupper(bin2hex(random_bytes(6)));
$roleUuid = $marker . 'R';
$expectedMenus = [strtoupper($birthdayMenuUuid), strtoupper($reviewMenuUuid)];
sort($expectedMenus);
$exactCount = $count('SELECT COUNT(*) FROM ' . $p . 'user'
    . ' WHERE UUID=? OR ACCOUNT=?', [$marker, $marker])
    + $count('SELECT COUNT(*) FROM ' . $p . 'role'
        . ' WHERE UUID=? OR NAME=?', [$roleUuid, $marker])
    + $count('SELECT COUNT(*) FROM ' . $p . 'user_role'
        . ' WHERE USER_UUID=? OR ROLE_UUID=?', [$marker, $roleUuid])
    + $count('SELECT COUNT(*) FROM ' . $p . 'role_menu'
        . ' WHERE ROLE_UUID=?', [$roleUuid])
    + $count('SELECT COUNT(*) FROM ' . $p . 'user_data'
        . ' WHERE USER_UUID=?', [$marker]);
if ($exactCount !== 0) throw new RuntimeException('Read4 marker collision');
$privateDir = sys_get_temp_dir() . '/zhct-bdq4-' . $marker;
if (file_exists($privateDir) || !mkdir($privateDir, 0700)) {
    throw new RuntimeException('Read4 private manifest directory unavailable');
}
chmod($privateDir, 0700);
$journalPath = $privateDir . '/recovery.json';
$journal = ['lane' => $lane, 'marker' => $marker, 'role_uuid' => $roleUuid,
    'menu_uuids' => [$birthdayMenuUuid, $reviewMenuUuid],
    'scope_uuids' => [(string)$department[0]['uuid'],
        (string)$restaurant[0]['uuid']],
    '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('Read4 journal write failed');
    }
    chmod($next, 0600);
    if (!rename($next, $journalPath)) throw new RuntimeException('Read4 journal rename failed');
};
$saveJournal();
$actorId = 0;
$roleId = 0;
$token = '';
$responseResults = [];
$beforeCounts = ['dishes' => $dishTotal,
    'questionnaire' => $count('SELECT COUNT(*) FROM ' . $p
        . 'dish_questionnaire WHERE ID=?', [$questionnaireId]),
    'tags' => $count('SELECT COUNT(*) FROM ' . $p
        . 'dish_questionnaire_tag WHERE QUESTIONNAIRE_ID=?', [$questionnaireId])];
$sourceUnchanged = false;
$cleaned = false;
$privateCleared = false;
$failure = '';
$httpSent = false;
$httpConfirmed = true;
$fixtureCommitted = false;
$allPositive = false;
$insert = static function (PDO $pdo, string $table, array $data) use ($p): void {
    $names = array_keys($data);
    $stmt = $pdo->prepare('INSERT INTO ' . $p . $table . ' ('
        . implode(',', $names) . ') VALUES ('
        . implode(',', array_fill(0, count($data), '?')) . ')');
    $stmt->execute(array_values($data));
    if ($stmt->rowCount() !== 1) throw new RuntimeException('Read4 fixture INSERT count mismatch');
};
$singleId = static function (PDO $pdo, string $table, string $column,
    string $value) use ($p): int {
    $stmt = $pdo->prepare('SELECT ID FROM ' . $p . $table . ' WHERE ' . $column . '=?');
    $stmt->execute([$value]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    if (count($rows) !== 1) throw new RuntimeException('Read4 fixture ID ambiguous');
    return (int)(array_change_key_case($rows[0], CASE_LOWER)['id'] ?? 0);
};
$http = static function (string $path, array $form, string $tokenValue)
    use ($base, $root, $pid, &$httpSent, &$httpConfirmed): array {
    $cmdline = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    if (strpos($cmdline, $root . '/public') === false
        || strpos($cmdline, '127.0.0.1:' . parse_url($base, PHP_URL_PORT)) === false) {
        throw new RuntimeException('Read4 HTTP runtime changed');
    }
    $context = stream_context_create(['http' => ['method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\n"
            . 'token: ' . $tokenValue . "\r\n",
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 20, 'ignore_errors' => true]]);
    $httpConfirmed = false;
    $stream = @fopen($base . $path, 'rb', false, $context);
    $httpSent = true;
    $body = is_resource($stream) ? stream_get_contents($stream, 262145) : false;
    if (is_resource($stream)) fclose($stream);
    if (!is_string($body) || strlen($body) > 262144) {
        throw new RuntimeException('Read4 HTTP transport/size unknown');
    }
    $httpConfirmed = true;
    $headers = $http_response_header ?? [];
    if (strpos((string)($headers[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('Read4 HTTP status mismatch');
    }
    $json = json_decode($body, true);
    if (!is_array($json) || !array_key_exists('code', $json)) {
        throw new RuntimeException('Read4 JSON envelope invalid');
    }
    return $json;
};
try {
    $db->beginTransaction();
    try {
        $stamp = date('Y-m-d H:i:s');
        $insert($db, 'user', ['UUID' => $marker, 'ACCOUNT' => $marker,
            'PWD' => 'DISABLED', 'SALT' => strtoupper(bin2hex(random_bytes(8))),
            'NAME' => $marker, 'TYPE' => 1, 'STATUS' => 1,
            'CREATE_TIME' => $stamp, 'CREATE_BY' => 'CODEX', 'IS_DELETE' => 0]);
        $actorId = $singleId($db, 'user', 'UUID', $marker);
        $journal['actor_id'] = $actorId;
        $saveJournal();
        $insert($db, 'role', ['UUID' => $roleUuid, 'NAME' => $marker,
            'STATUS' => 1, 'CREATE_TIME' => $stamp, 'CREATE_BY' => 'CODEX']);
        $roleId = $singleId($db, 'role', 'UUID', $roleUuid);
        $journal['role_id'] = $roleId;
        $saveJournal();
        $insert($db, 'user_role', ['USER_UUID' => $marker,
            'ROLE_UUID' => $roleUuid, 'CREATE_TIME' => $stamp,
            'CREATE_BY' => $marker]);
        $insert($db, 'user_data', ['USER_UUID' => $marker,
            'DATA_UUID' => (string)$department[0]['uuid'], 'TYPE' => 1]);
        $insert($db, 'user_data', ['USER_UUID' => $marker,
            'DATA_UUID' => (string)$restaurant[0]['uuid'], 'TYPE' => 3]);
        $db->commit();
        $fixtureCommitted = true;
    } catch (Throwable $error) {
        if ($db->inTransaction()) $db->rollBack();
        throw $error;
    }
    $journal['state'] = 'actor_role_scope_committed_without_menu';
    $saveJournal();
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t",
        [(string)$actorId, $marker, $marker, '1', '', '', $marker]));
    if (!is_string($token) || $token === '') {
        throw new RuntimeException('Read4 synthetic type1 token unavailable');
    }
    $negative = $http('/p/dishQuestionnaire/options',
        ['restaurant_id' => (string)$restaurantId], $token);
    if (($negative['code'] ?? null) !== 1) {
        throw new RuntimeException('Read4 ordinary no-menu negative gate failed');
    }
    $responseResults['ordinary_no_menu_rejected'] = true;
    $db->beginTransaction();
    try {
        foreach ([$birthdayMenuUuid, $reviewMenuUuid] as $menuUuid) {
            $insert($db, 'role_menu', ['ROLE_UUID' => $roleUuid,
                'MENU_UUID' => $menuUuid, 'CREATE_TIME' => date('Y-m-d H:i:s'),
                'CREATE_BY' => $marker]);
        }
        $db->commit();
    } catch (Throwable $error) {
        if ($db->inTransaction()) $db->rollBack();
        throw $error;
    }
    $journal['state'] = 'two_exact_read_menus_granted';
    $saveJournal();
    $menuLinks = $read('SELECT MENU_UUID FROM ' . $p . 'role_menu'
        . ' WHERE ROLE_UUID=? ORDER BY MENU_UUID', [$roleUuid]);
    $granted = array_map(static function (array $x): string {
        return strtoupper((string)$x['menu_uuid']);
    }, $menuLinks);
    if ($granted !== $expectedMenus) throw new RuntimeException('Read4 menu grants drift');
    $scopes = $read('SELECT DATA_UUID,TYPE FROM ' . $p . 'user_data'
        . ' WHERE USER_UUID=? ORDER BY TYPE,DATA_UUID', [$marker]);
    if (count($scopes) !== 2
        || (int)$scopes[0]['type'] !== 1
        || (string)$scopes[0]['data_uuid'] !== (string)$department[0]['uuid']
        || (int)$scopes[1]['type'] !== 3
        || (string)$scopes[1]['data_uuid'] !== (string)$restaurant[0]['uuid']) {
        throw new RuntimeException('Read4 exact department/restaurant scope drift');
    }

    $requests = [
        'birthdayDish/dishOptions' => ['page' => 1, 'limit' => 20],
        'dishQuestionnaire/options' => ['restaurant_id' => (string)$restaurantId],
        'dishQuestionnaire/list' => ['restaurant_id' => (string)$restaurantId,
            'dishes_uuid' => $dishUuid, 'date_start' => $mealDate,
            'date_end' => $mealDate, 'meal_times' => (string)$mealTimes,
            'page' => 1, 'page_size' => 20],
        'dishQuestionnaire/detail' => ['restaurant_id' => (string)$restaurantId,
            'dishes_uuid' => $dishUuid, 'date_start' => $mealDate,
            'date_end' => $mealDate, 'meal_times' => (string)$mealTimes,
            'page' => 1, 'page_size' => 20],
    ];
    foreach ($requests as $route => $form) {
        $json = $http('/p/' . $route, $form, $token);
        if (($json['code'] ?? null) !== 0 || !is_array($json['data'] ?? null)) {
            throw new RuntimeException('Read4 positive business response failed');
        }
        $data = $json['data'];
        if ($route === 'birthdayDish/dishOptions') {
            $rows = $data['rows'] ?? null;
            if (!is_array($rows) || count($rows) !== 20
                || (int)($data['total'] ?? -1) !== $dishTotal
                || (int)($data['page'] ?? 0) !== 1
                || (int)($data['page_size'] ?? 0) !== 20
                || ($data['has_more'] ?? null) !== ($dishTotal > 20)) {
                throw new RuntimeException('Read4 birthday pagination/source mismatch');
            }
            foreach ($sourceDishRows as $i => $source) {
                $row = $rows[$i] ?? null;
                if (!is_array($row)
                    || (string)($row['uuid'] ?? '') !== (string)$source['uuid']
                    || (string)($row['name'] ?? '') !== (string)$source['name']
                    || (string)($row['category'] ?? '') !== (string)$source['category']
                    || (string)($row['category_one'] ?? '')
                        !== (string)$source['category_one']
                    || (string)($row['category_two'] ?? '')
                        !== (string)$source['category_two']) {
                    throw new RuntimeException('Read4 birthday dish/source mismatch');
                }
                $path = str_replace('\\', '/', (string)($source['file_path'] ?? ''));
                $image = (string)($row['file_path'] ?? '');
                if ($path === '' ? $image !== ''
                    : ($image === '' || substr($image, -strlen($path)) !== $path)) {
                    throw new RuntimeException('Read4 birthday image path mismatch');
                }
            }
        } elseif ($route === 'dishQuestionnaire/options') {
            $restaurants = $data['restaurants'] ?? null;
            if (!is_array($restaurants) || count($restaurants) !== 1
                || (int)($restaurants[0]['value'] ?? -1) !== $restaurantId
                || (string)($restaurants[0]['label'] ?? '')
                    !== (string)$restaurant[0]['name']
                || count($data['meal_times'] ?? []) !== 4
                || count($data['tags'] ?? []) !== 8) {
                throw new RuntimeException('Read4 questionnaire options/source mismatch');
            }
            foreach ($data['meal_times'] as $i => $item) {
                if ((int)($item['value'] ?? -1) !== $i + 1) {
                    throw new RuntimeException('Read4 meal-times enum mismatch');
                }
            }
        } elseif ($route === 'dishQuestionnaire/list') {
            $rows = $data['rows'] ?? null;
            $row = is_array($rows) && count($rows) === 1 ? $rows[0] : null;
            $score = (int)$question['score'];
            $commentPresent = trim((string)($question['comment_text'] ?? '')) !== '';
            if (!is_array($row) || (int)($data['total'] ?? -1) !== 1
                || (int)($data['page'] ?? 0) !== 1
                || (int)($data['page_size'] ?? 0) !== 20
                || strtoupper((string)($row['dishes_uuid'] ?? ''))
                    !== strtoupper($dishUuid)
                || substr((string)($row['meal_date'] ?? ''), 0, 10) !== $mealDate
                || (int)($row['meal_times'] ?? 0) !== $mealTimes
                || (int)($row['evaluation_count'] ?? 0) !== 1
                || abs((float)($row['average_score'] ?? -1) - $score) > 0.00001
                || abs((float)($row['satisfaction_rate'] ?? -1)
                    - ($score >= 4 ? 100.0 : 0.0)) > 0.00001
                || (int)($row['suggestion_count'] ?? -1)
                    !== ($commentPresent ? 1 : 0)
                || (string)($row['dishes_name'] ?? '')
                    !== (string)$question['dishes_name_snapshot']
                || !is_array($row['tag_counts'] ?? null)
                || count($row['tag_counts']) !== 1
                || (int)($row['tag_counts'][0]['code'] ?? 0)
                    !== (int)$tagRows[0]['tag_code']
                || (string)($row['tag_counts'][0]['name'] ?? '')
                    !== (string)$tagRows[0]['tag_name_snapshot']
                || (int)($row['tag_counts'][0]['count'] ?? 0) !== 1) {
                throw new RuntimeException('Read4 questionnaire list/source mismatch');
            }
            $expectedWish = trim((string)($question['wish_dish_key'] ?? '')) !== '';
            if (!is_array($row['wish_counts'] ?? null)
                || count($row['wish_counts']) !== ($expectedWish ? 1 : 0)) {
                throw new RuntimeException('Read4 questionnaire wish/source mismatch');
            }
        } else {
            $rows = $data['rows'] ?? null;
            $row = is_array($rows) && count($rows) === 1 ? $rows[0] : null;
            if (!is_array($row) || (int)($data['total'] ?? -1) !== 1
                || (int)($data['page'] ?? 0) !== 1
                || (int)($data['page_size'] ?? 0) !== 20
                || (int)($row['id'] ?? 0) !== $questionnaireId
                || strtoupper((string)($row['dishes_uuid'] ?? ''))
                    !== strtoupper($dishUuid)
                || (int)($row['score'] ?? 0) !== (int)$question['score']
                || (string)($row['comment'] ?? '')
                    !== (string)$question['comment_text']
                || (string)($row['wish_dish_name'] ?? '')
                    !== (string)$question['wish_dish_name']
                || !is_array($row['tags'] ?? null)
                || count($row['tags']) !== 1
                || (int)($row['tags'][0]['code'] ?? 0)
                    !== (int)$tagRows[0]['tag_code']) {
                throw new RuntimeException('Read4 questionnaire detail/source mismatch');
            }
        }
        $responseResults[$route] = ['code' => 0,
            'data_shape_sha' => hash('sha256', json_encode(array_map('gettype',
                (array)$data), JSON_UNESCAPED_SLASHES)),
            'response_sha' => hash('sha256', json_encode($json,
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
            'positive_source_match' => true];
    }
    $allPositive = count($responseResults) === 5;
    $sourceAfter = [
        'dishes' => hash('sha256', serialize($read('SELECT ID,UUID,NAME,CATEGORY,'
            . 'CATEGORY_ONE,CATEGORY_TWO,FILE_PATH FROM ' . $p . 'dishes'
            . ' WHERE STORE_ID=10001 AND STATUS=1 AND IS_DEL=1 AND IS_DELETE=0'
            . ' ORDER BY NAME,ID'
            . ($dialect === 'dm' ? ' FETCH FIRST 20 ROWS ONLY' : ' LIMIT 20')))),
        'questionnaire' => hash('sha256', serialize($read('SELECT q.ID,q.STORE_ID,'
            . 'q.RESTAURANT_ID,q.DISHES_UUID,q.DISHES_NAME_SNAPSHOT,q.MEAL_DATE,'
            . 'q.MEAL_TIMES,q.SCORE,' . $commentField . ' AS COMMENT_TEXT,'
            . 'q.WISH_DISH_KEY,q.WISH_DISH_NAME,q.CREATE_TIME FROM '
            . $p . 'dish_questionnaire q WHERE q.ID=? AND q.STORE_ID=10001'
            . ' AND q.IS_DELETE=0', [$questionnaireId]))),
        'tags' => hash('sha256', serialize($read('SELECT QUESTIONNAIRE_ID,TAG_CODE,'
            . 'TAG_NAME_SNAPSHOT FROM ' . $p . 'dish_questionnaire_tag'
            . ' WHERE QUESTIONNAIRE_ID=? ORDER BY QUESTIONNAIRE_ID,TAG_CODE',
            [$questionnaireId]))),
        'restaurant' => hash('sha256', serialize($read('SELECT ID,UUID,NAME FROM '
            . $p . 'restaurants WHERE ID=? AND STATUS=1 AND IS_DELETE=2',
            [$restaurantId]))),
    ];
    $sourceUnchanged = $sourceAfter === $sourceBefore
        && $count('SELECT COUNT(*) FROM ' . $p . 'dishes'
            . ' WHERE STORE_ID=10001 AND STATUS=1 AND IS_DEL=1 AND IS_DELETE=0')
            === $dishTotal
        && $count('SELECT COUNT(*) FROM ' . $p . 'dish_questionnaire WHERE ID=?',
            [$questionnaireId]) === $beforeCounts['questionnaire']
        && $count('SELECT COUNT(*) FROM ' . $p
            . 'dish_questionnaire_tag WHERE QUESTIONNAIRE_ID=?',
            [$questionnaireId]) === $beforeCounts['tags'];
    if (!$sourceUnchanged) throw new RuntimeException('Read4 natural business source changed');
} catch (Throwable $error) {
    $failure = get_class($error) . ':' . hash('sha256', $error->getMessage());
} finally {
    $workerStopped = !is_dir('/proc/' . $pid);
    $currentCmdline = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $currentBoundary = $workerStopped ? null
        : json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    $runtimeSafe = $workerStopped
        || (strpos($currentCmdline, $root . '/public') !== false
            && strpos($currentCmdline,
                '127.0.0.1:' . parse_url($base, PHP_URL_PORT)) !== false
            && is_array($currentBoundary)
            && !empty($currentBoundary['external_stubs'])
            && !empty($currentBoundary['redis_isolated'])
            && empty($currentBoundary['real_queue']));
    $safeToClean = ($workerStopped || !$httpSent || $httpConfirmed)
        && $runtimeSafe;
    if ($safeToClean) {
        try {
            // Fresh approved connection; all candidate permissions must still
            // be exactly this actor/role and the two reviewed read menus.
            $cleanup = new PDO($dialect === 'dm'
                ? 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP'
                : 'mysql:host=' . $ctx['runtime']['hostname'] . ';port='
                    . (int)($ctx['runtime']['hostport'] ?? 3306) . ';dbname='
                    . $ctx['runtime']['database'] . ';charset='
                    . (string)($ctx['runtime']['charset'] ?? 'utf8'),
                (string)$ctx['runtime']['username'],
                (string)$ctx['runtime']['password'],
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
            $identity = $dialect === 'dm'
                ? 'ZHCT_APP|5237|' . (string)$cleanup->query(
                    "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
                    ->fetchColumn()
                : (string)$cleanup->query(
                    "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
                    ->fetchColumn();
            $approved = $dialect === 'dm'
                ? 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194'
                : '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
            if (!hash_equals($approved, hash('sha256', $identity))) {
                throw new RuntimeException('Read4 cleanup DB identity mismatch');
            }
            $fetch = static function (PDO $pdo, string $table,
                string $where, array $args) use ($p): array {
                $stmt = $pdo->prepare('SELECT * FROM ' . $p . $table . ' WHERE ' . $where);
                $stmt->execute($args);
                return array_map('array_change_key_case', $stmt->fetchAll(PDO::FETCH_ASSOC));
            };
            $users = $fetch($cleanup, 'user', 'UUID=? OR ACCOUNT=?', [$marker, $marker]);
            $roles = $fetch($cleanup, 'role', 'UUID=? OR NAME=?', [$roleUuid, $marker]);
            $userRoles = $fetch($cleanup, 'user_role', 'USER_UUID=? OR ROLE_UUID=?',
                [$marker, $roleUuid]);
            $roleMenus = $fetch($cleanup, 'role_menu', 'ROLE_UUID=?', [$roleUuid]);
            $userDatas = $fetch($cleanup, 'user_data', 'USER_UUID=?', [$marker]);
            if (count($users) > 1 || count($roles) > 1
                || count($userRoles) > 1 || count($roleMenus) > 2
                || count($userDatas) > 2) {
                throw new RuntimeException('Read4 extra marker association needs review');
            }
            foreach ($users as $row) if ((string)$row['uuid'] !== $marker
                || (string)$row['account'] !== $marker
                || (string)$row['name'] !== $marker
                || (int)$row['type'] !== 1
                || (string)$row['create_by'] !== 'CODEX'
                || ($actorId > 0 && (int)$row['id'] !== $actorId)) {
                throw new RuntimeException('Read4 user ownership drift');
            }
            foreach ($roles as $row) if ((string)$row['uuid'] !== $roleUuid
                || (string)$row['name'] !== $marker
                || (string)$row['create_by'] !== 'CODEX'
                || ($roleId > 0 && (int)$row['id'] !== $roleId)) {
                throw new RuntimeException('Read4 role ownership drift');
            }
            foreach ($userRoles as $row) if ((string)$row['user_uuid'] !== $marker
                || (string)$row['role_uuid'] !== $roleUuid
                || (string)$row['create_by'] !== $marker) {
                throw new RuntimeException('Read4 user-role ownership drift');
            }
            foreach ($roleMenus as $row) if ((string)$row['role_uuid'] !== $roleUuid
                || !in_array(strtoupper((string)$row['menu_uuid']), $expectedMenus, true)
                || (string)$row['create_by'] !== $marker) {
                throw new RuntimeException('Read4 role-menu ownership drift');
            }
            foreach ($userDatas as $row) if ((string)$row['user_uuid'] !== $marker
                || !in_array([(int)$row['type'], (string)$row['data_uuid']],
                    [[1, (string)$department[0]['uuid']],
                        [3, (string)$restaurant[0]['uuid']]], true)) {
                throw new RuntimeException('Read4 user-data ownership drift');
            }
            $cleanup->beginTransaction();
            try {
                foreach ([['role_menu', $roleMenus,
                            'ID=? AND ROLE_UUID=? AND MENU_UUID=? AND CREATE_BY=?',
                            static function (array $r) use ($roleUuid, $marker): array {
                                return [$r['id'], $roleUuid, $r['menu_uuid'], $marker];
                            }],
                        ['user_role', $userRoles,
                            'ID=? AND USER_UUID=? AND ROLE_UUID=? AND CREATE_BY=?',
                            static function (array $r) use ($marker, $roleUuid): array {
                                return [$r['id'], $marker, $roleUuid, $marker];
                            }],
                        ['user_data', $userDatas,
                            'ID=? AND USER_UUID=? AND DATA_UUID=? AND TYPE=?',
                            static function (array $r) use ($marker): array {
                                return [$r['id'], $marker, $r['data_uuid'], $r['type']];
                            }],
                        ['role', $roles, 'ID=? AND UUID=? AND NAME=? AND CREATE_BY=?',
                            static function (array $r) use ($roleUuid, $marker): array {
                                return [$r['id'], $roleUuid, $marker, 'CODEX'];
                            }],
                        ['user', $users,
                            'ID=? AND UUID=? AND ACCOUNT=? AND TYPE=1 AND CREATE_BY=?',
                            static function (array $r) use ($marker): array {
                                return [$r['id'], $marker, $marker, 'CODEX'];
                            }]] as $deletion) {
                    [$table, $rows, $where, $argsFor] = $deletion;
                    $stmt = $cleanup->prepare('DELETE FROM ' . $p . $table
                        . ' WHERE ' . $where);
                    foreach ($rows as $row) {
                        $stmt->execute($argsFor($row));
                        if ($stmt->rowCount() !== 1) {
                            throw new RuntimeException('Read4 exact cleanup affected rows mismatch');
                        }
                    }
                }
                foreach (['user' => ['UUID=? OR ACCOUNT=?', [$marker, $marker]],
                    'role' => ['UUID=? OR NAME=?', [$roleUuid, $marker]],
                    'user_role' => ['USER_UUID=? OR ROLE_UUID=?', [$marker, $roleUuid]],
                    'role_menu' => ['ROLE_UUID=?', [$roleUuid]],
                    'user_data' => ['USER_UUID=?', [$marker]]] as $table => $probe) {
                    [$where, $args] = $probe;
                    $stmt = $cleanup->prepare('SELECT COUNT(*) FROM ' . $p . $table
                        . ' WHERE ' . $where);
                    $stmt->execute($args);
                    if ((int)$stmt->fetchColumn() !== 0) {
                        throw new RuntimeException('Read4 marker remains before commit');
                    }
                }
                $cleanup->commit();
            } catch (Throwable $error) {
                if ($cleanup->inTransaction()) $cleanup->rollBack();
                throw $error;
            }
            $verify = new PDO($dialect === 'dm'
                ? 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP'
                : 'mysql:host=' . $ctx['runtime']['hostname'] . ';port='
                    . (int)($ctx['runtime']['hostport'] ?? 3306) . ';dbname='
                    . $ctx['runtime']['database'] . ';charset='
                    . (string)($ctx['runtime']['charset'] ?? 'utf8'),
                (string)$ctx['runtime']['username'],
                (string)$ctx['runtime']['password'],
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
            $verifyIdentity = $dialect === 'dm'
                ? 'ZHCT_APP|5237|' . (string)$verify->query(
                    "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
                    ->fetchColumn()
                : (string)$verify->query(
                    "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
                    ->fetchColumn();
            if (!hash_equals($approved, hash('sha256', $verifyIdentity))) {
                throw new RuntimeException('Read4 independent zero DB identity mismatch');
            }
            foreach (['user' => ['UUID=? OR ACCOUNT=?', [$marker, $marker]],
                'role' => ['UUID=? OR NAME=?', [$roleUuid, $marker]],
                'user_role' => ['USER_UUID=? OR ROLE_UUID=?', [$marker, $roleUuid]],
                'role_menu' => ['ROLE_UUID=?', [$roleUuid]],
                'user_data' => ['USER_UUID=?', [$marker]]] as $table => $probe) {
                [$where, $args] = $probe;
                $stmt = $verify->prepare('SELECT COUNT(*) FROM ' . $p . $table
                    . ' WHERE ' . $where);
                $stmt->execute($args);
                if ((int)$stmt->fetchColumn() !== 0) {
                    throw new RuntimeException('Read4 independent zero failed');
                }
            }
            $cleaned = true;
        } catch (Throwable $error) {
            $failure .= ($failure === '' ? '' : ';') . 'cleanup:'
                . get_class($error) . ':' . hash('sha256', $error->getMessage());
        }
    } else {
        $failure .= ($failure === '' ? '' : ';')
            . ($httpSent && !$httpConfirmed && !$workerStopped
                ? 'cleanup:uncertain_http_outcome' : 'cleanup:runtime_drift');
    }
    if ($cleaned) {
        if (is_file($journalPath)) @unlink($journalPath);
        if (is_file($privateDir . '/recovery.next')) @unlink($privateDir . '/recovery.next');
        $privateCleared = @rmdir($privateDir);
    } else {
        $journal['state'] = 'requires_manual_recovery';
        try { $saveJournal(); } catch (Throwable $error) { /* preserve old journal */ }
    }
}
$result = ['lane' => $lane, 'marker' => $marker,
    'fixture_committed' => $fixtureCommitted,
    'ordinary_permission_negative' => $responseResults['ordinary_no_menu_rejected'] ?? false,
    'positive_routes' => array_keys(array_filter($responseResults,
        static function ($r): bool { return is_array($r) && !empty($r['positive_source_match']); })),
    'all_four_positive' => $allPositive,
    'source_unchanged' => $sourceUnchanged,
    'actor_role_scope_cleanup_zero' => $cleaned,
    'private_journal_removed' => $privateCleared,
    'private_recovery_pointer' => $cleaned ? null : $privateDir,
    'failure' => $failure];
echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($failure !== '' || !$allPositive || !$sourceUnchanged || !$cleaned
    || !$privateCleared) exit(1);
