<?php

declare(strict_types=1);

// Bounded Store BirthdayDish/lists acceptance. Preflight is SELECT-only.
set_exception_handler(static function (Throwable $e): void {
    fwrite(STDERR, 'Birthday lists preflight error ' . get_class($e) . ':'
        . hash('sha256', $e->getMessage()) . PHP_EOL);
    exit(1);
});
$mode = (string)($argv[1] ?? '');
$lane = (string)getenv('CDX_READ4_LANE');
if (!in_array($mode, ['--preflight', '--execute'], true)
    || !in_array($lane, ['A', 'B', 'C'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_LISTS_EXECUTE') !== 'ROOT_APPROVED_BIRTHDAY_LISTS_V1'
            || (string)getenv('CDX_LISTS_SHA') !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "Birthday lists business HTTP and fixture DML disabled\n");
    exit(78);
}
// Reuse the frozen read4 runtime/source gate; it performs no DML/route HTTP.
if (hash_file('sha256', __DIR__ . '/source_preflight.php')
    !== '4928bc5cff700c3e46c1a03017d8a228b8d64428133ee03559604126a3e2869a') {
    throw new RuntimeException('Birthday lists dependency source preflight drift');
}
$previousEmbed = getenv('CDX_READ4_EMBEDDED');
putenv('CDX_READ4_EMBEDDED=ONLY_FROM_REVIEWED_HARNESS');
try {
    $ctx = (static function (): array {
        $argv = ['source_preflight.php', '--embedded-preflight'];
        $value = require __DIR__ . '/source_preflight.php';
        if (!is_array($value)) throw new RuntimeException('Birthday lists source context absent');
        return $value;
    })();
} 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'];
$approvedIdentity = $dialect === 'dm'
    ? 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194'
    : '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
$departmentId = $dialect === 'dm' ? 18 : 9;
$monthDay = $dialect === 'dm' ? '01-02' : '01-01';
$date = '2026-' . $monthDay;
$restaurantId = 1;
$birthdayMenu = $ctx['birthday_menu'];
$menuTokens = array_map('trim', explode('|', strtolower((string)($birthdayMenu['filter'] ?? ''))));
if (count($menuTokens) !== 2
    || !in_array('p/birthdaydish/lists', $menuTokens, true)
    || !in_array('p/birthdaydish/dishoptions', $menuTokens, true)) {
    throw new RuntimeException('Birthday lists exact existing menu drift');
}
$department = $read('SELECT UUID FROM ' . $p . 'department'
    . ' WHERE ID=? AND STATUS=1 AND IS_SHOW=1 AND LEAF_FLAG=1', [$departmentId]);
$restaurant = $read('SELECT UUID FROM ' . $p . 'restaurants'
    . ' WHERE ID=? AND STATUS=1 AND IS_DELETE=2', [$restaurantId]);
if (count($department) !== 1 || count($restaurant) !== 1
    || (string)$department[0]['uuid'] === '' || (string)$restaurant[0]['uuid'] === '') {
    throw new RuntimeException('Birthday lists exact scope unavailable');
}
$departmentUuid = (string)$department[0]['uuid'];
$menuUuid = (string)$birthdayMenu['uuid'];
$configSource = file_get_contents($root . '/application/config.php');
if (!is_string($configSource)
    || preg_match_all("/'status_is_show'\\s*=>\\s*0\\b/", $configSource) !== 1) {
    throw new RuntimeException('Birthday lists status filter config drift');
}
$fixtureTables = ['user', 'role', 'user_role', 'role_menu', 'user_data'];
foreach ($fixtureTables as $table) {
    if ($dialect === 'dm') {
        $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);
        if (!preg_match('/"ID"\s+[A-Z0-9]+\s+AUTO_INCREMENT\s+NOT\s+NULL/i',
            (string)$definition)) {
            throw new RuntimeException('Birthday lists DM fixture identity drift');
        }
    } else {
        $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('Birthday lists MySQL fixture identity drift');
        }
    }
}
$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) {
    $rows = $dialect === 'dm'
        ? $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)])
        : $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]);
    $groups = [];
    foreach ($rows as $r) $groups[(string)$r['index_name']][] = strtolower((string)$r['column_name']);
    if (!in_array($fields, array_values($groups), true)) {
        throw new RuntimeException('Birthday lists fixture unique key drift');
    }
}
if ($dialect === 'dm') {
    $fk = $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')")
        + $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')");
} else {
    $names = array_map(static function (string $x): string { return 'ydy_' . $x; }, $fixtureTables);
    $fk = $count('SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN (?,?,?,?,?)'
        . ' AND REFERENCED_TABLE_NAME IS NOT NULL', $names)
        + $count('SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE'
            . ' WHERE REFERENCED_TABLE_SCHEMA=DATABASE()'
            . ' AND REFERENCED_TABLE_NAME IN (?,?,?,?,?)', $names);
    $triggers = $count('SELECT COUNT(*) FROM information_schema.TRIGGERS'
        . ' WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (?,?,?,?,?)', $names);
}
if ($fk !== 0 || $triggers !== 0) throw new RuntimeException('Birthday lists fixture side effects drift');
$stale = $count('SELECT COUNT(*) FROM ' . $p . 'user'
    . " WHERE ACCOUNT LIKE 'CDXBDQ4%'")
    + $count('SELECT COUNT(*) FROM ' . $p . 'role'
        . " WHERE NAME LIKE 'CDXBDQ4%'");
if ($stale !== 0) throw new RuntimeException('Birthday lists prior fixture residue');

$source = static function (string $fill = '') use ($read, $count, $p, $dialect,
    $departmentUuid, $restaurantId, $monthDay): array {
    $joins = ' FROM ' . $p . 'staff s LEFT JOIN (SELECT STAFF_UUID,'
        . ' MAX(ID) RELATION_ID,COUNT(*) RELATION_COUNT FROM '
        . $p . 'birthday_dish WHERE STORE_ID=10001 AND IS_DELETE=0'
        . ' GROUP BY STAFF_UUID) bm ON bm.STAFF_UUID=s.UUID'
        . ' LEFT JOIN ' . $p . 'birthday_dish bd ON bd.ID=bm.RELATION_ID'
        . ' LEFT JOIN ' . $p . 'dishes d ON d.UUID=bd.DISHES_UUID AND d.STORE_ID=10001';
    $where = ' WHERE s.IS_SHOW=1 AND s.JOB_STATUS=1 AND s.RESTAURANT_ID=?'
        . ' AND EXISTS(SELECT 1 FROM ' . $p . 'staff_department sd'
        . ' WHERE sd.STAFF_UUID=s.UUID AND sd.DEPARTMENT_UUID=?)'
        . ' AND DATE_FORMAT(s.BIRTHDAY,?)=?';
    if ($fill === 'filled') $where .= ' AND bm.RELATION_ID IS NOT NULL';
    elseif ($fill === 'unfilled') $where .= ' AND bm.RELATION_ID IS NULL';
    $args = [$restaurantId, $departmentUuid, '%m-%d', $monthDay];
    $total = $count('SELECT COUNT(DISTINCT s.ID)' . $joins . $where, $args);
    $order = ' ORDER BY FIELD(DATE_FORMAT(s.BIRTHDAY,?),?) ASC,'
        . ' CASE WHEN s.PERSONNEL_UMBER=? THEN 1 ELSE 0 END ASC,'
        . ' s.PERSONNEL_UMBER ASC,s.ID ASC';
    $rows = $read('SELECT s.ID STAFF_ID,s.UUID STAFF_UUID,s.PERSONNEL_UMBER,'
        . ' s.NAME STAFF_NAME,s.BIRTHDAY,s.HEAD_IMG,s.RESTAURANT_ID,'
        . ' bd.ID RELATION_ID,bd.UUID RELATION_UUID,bd.DISHES_UUID,'
        . ' bd.VERSION,bd.UPDATE_TIME,d.NAME DISH_NAME,d.FILE_PATH DISH_IMAGE,'
        . ' bm.RELATION_COUNT' . $joins . $where . $order
        . ($dialect === 'dm' ? ' FETCH FIRST 20 ROWS ONLY' : ' LIMIT 20'),
        array_merge($args, ['%m-%d', $monthDay, '']));
    if ($total > 20 || count($rows) !== $total) {
        throw new RuntimeException('Birthday lists source exceeds exact bounded window');
    }
    return ['total' => $total, 'rows' => $rows];
};
$before = ['all' => $source(), 'filled' => $source('filled'),
    'unfilled' => $source('unfilled')];
$expectedTotals = $dialect === 'dm' ? [4, 0, 4] : [13, 7, 6];
if (array_map(static function (array $x): int { return $x['total']; }, array_values($before))
    !== $expectedTotals) {
    throw new RuntimeException('Birthday lists natural source count drift');
}
$tableCounts = [];
foreach (['staff', 'staff_department', 'birthday_dish', 'dishes'] as $table) {
    $tableCounts[$table] = $count('SELECT COUNT(*) FROM ' . $p . $table);
}
$pre = ['lane' => $lane, 'dialect' => $dialect, 'restaurant_id' => $restaurantId,
    'department_id' => $departmentId, 'month_day' => $monthDay,
    'all_rows' => $before['all']['total'], 'filled_rows' => $before['filled']['total'],
    'unfilled_rows' => $before['unfilled']['total'],
    'source_sha' => hash('sha256', serialize($before)),
    'four_source_table_counts' => $tableCounts,
    'exact_menu_and_scope' => true, 'fixture_id_auto_unique_fk0_trigger0' => true,
    'old_fixture_marker_rows' => $stale,
    'business_http_executed' => false, 'dml_executed' => false];
if ($mode === '--preflight') {
    echo json_encode($pre, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
if ((string)getenv('CDX_LISTS_DB_SHA') !== $approvedIdentity) {
    throw new RuntimeException('Birthday lists explicit database confirmation absent');
}

// No write/HTTP statement is reachable before all checks above.
$marker = 'CDXBDQ4' . strtoupper(bin2hex(random_bytes(6)));
$roleUuid = $marker . 'R';
$unique = $count('SELECT COUNT(*) FROM ' . $p . 'user WHERE UUID=? OR ACCOUNT=?',
    [$marker, $marker]) + $count('SELECT COUNT(*) FROM ' . $p
        . 'role WHERE UUID=? OR NAME=?', [$roleUuid, $marker]);
if ($unique !== 0) throw new RuntimeException('Birthday lists random marker collision');
$privateDir = sys_get_temp_dir() . '/zhct-bdq4-' . $marker;
if (file_exists($privateDir) || !mkdir($privateDir, 0700)) {
    throw new RuntimeException('Birthday lists journal directory unavailable');
}
chmod($privateDir, 0700);
$journalPath = $privateDir . '/recovery.json';
$journal = ['lane' => $lane, 'marker' => $marker, 'role_uuid' => $roleUuid,
    'restaurant_id' => $restaurantId, 'department_id' => $departmentId,
    'menu_uuid' => $menuUuid, '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('Birthday lists journal write failure');
    }
    chmod($next, 0600);
    if (!rename($next, $journalPath)) throw new RuntimeException('Birthday lists journal rename failure');
};
$saveJournal();
$actorId = 0;
$roleId = 0;
$httpSent = false;
$httpConfirmed = true;
$positive = [];
$negative = false;
$sourceUnchanged = false;
$cleaned = false;
$privateCleared = false;
$failure = '';
$insert = static function (PDO $pdo, string $table, array $values) use ($p): void {
    $keys = array_keys($values);
    $stmt = $pdo->prepare('INSERT INTO ' . $p . $table . ' (' . implode(',', $keys)
        . ') VALUES (' . implode(',', array_fill(0, count($keys), '?')) . ')');
    $stmt->execute(array_values($values));
    if ($stmt->rowCount() !== 1) throw new RuntimeException('Birthday lists fixture insert count');
};
$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('Birthday lists fixture ID ambiguous');
    return (int)(array_change_key_case($rows[0], CASE_LOWER)['id'] ?? 0);
};
$http = static function (array $form, string $token) 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('Birthday lists HTTP runtime drift');
    }
    $context = stream_context_create(['http' => ['method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\n"
            . 'token: ' . $token . "\r\n",
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 20, 'ignore_errors' => true]]);
    $httpConfirmed = false;
    $stream = @fopen($base . '/p/birthdayDish/lists', '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('Birthday lists HTTP transport/size unknown');
    }
    $httpConfirmed = true;
    $headers = $http_response_header ?? [];
    if (strpos((string)($headers[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('Birthday lists HTTP status mismatch');
    }
    $json = json_decode($body, true);
    if (!is_array($json) || !array_key_exists('code', $json)) {
        throw new RuntimeException('Birthday lists response envelope invalid');
    }
    return $json;
};
$imageMatches = static function ($actual, $source): bool {
    $suffix = str_replace('\\', '/', (string)($source ?? ''));
    $value = (string)($actual ?? '');
    return $suffix === '' ? $value === ''
        : ($value !== '' && substr($value, -strlen($suffix)) === $suffix);
};
$dbIdentity = static function (PDO $pdo) use ($dialect): string {
    return $dialect === 'dm'
        ? 'ZHCT_APP|5237|' . (string)$pdo->query(
            "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()
        : (string)$pdo->query(
            "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
            ->fetchColumn();
};
$newConnection = static function () use ($dialect, $ctx): PDO {
    $r = $ctx['runtime'];
    $dsn = $dialect === 'dm' ? 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP'
        : 'mysql:host=' . $r['hostname'] . ';port=' . (int)($r['hostport'] ?? 3306)
            . ';dbname=' . $r['database'] . ';charset=' . ($r['charset'] ?? 'utf8');
    return new PDO($dsn, (string)$r['username'], (string)$r['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
};

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' => $departmentUuid, 'TYPE' => 1]);
        $insert($db, 'user_data', ['USER_UUID' => $marker,
            'DATA_UUID' => (string)$restaurant[0]['uuid'], 'TYPE' => 3]);
        $db->commit();
    } catch (Throwable $e) {
        if ($db->inTransaction()) $db->rollBack();
        throw $e;
    }
    $journal['state'] = 'actor_scope_committed_no_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('Birthday lists synthetic token unavailable');
    }
    $form = ['restaurant_id' => (string)$restaurantId,
        'date_start' => $date, 'date_end' => $date,
        'page' => '1', 'page_size' => '20'];
    $withoutMenu = $http($form, $token);
    if (($withoutMenu['code'] ?? null) !== 1) {
        throw new RuntimeException('Birthday lists no-menu negative mismatch');
    }
    $negative = true;
    $db->beginTransaction();
    try {
        $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 $e) {
        if ($db->inTransaction()) $db->rollBack();
        throw $e;
    }
    $journal['state'] = 'one_exact_birthday_menu_granted';
    $saveJournal();
    $grants = $read('SELECT MENU_UUID FROM ' . $p . 'role_menu WHERE ROLE_UUID=?', [$roleUuid]);
    if (count($grants) !== 1 || strcasecmp((string)$grants[0]['menu_uuid'], $menuUuid) !== 0) {
        throw new RuntimeException('Birthday lists menu grant drift');
    }
    foreach (['all', 'filled', 'unfilled'] as $filter) {
        $request = $form;
        if ($filter !== 'all') $request['fill_status'] = $filter;
        $json = $http($request, $token);
        if (($json['code'] ?? null) !== 0 || !is_array($json['data'] ?? null)) {
            throw new RuntimeException('Birthday lists business response failed');
        }
        $data = $json['data'];
        $expected = $before[$filter];
        $rows = $data['rows'] ?? null;
        if (!is_array($rows) || count($rows) !== count($expected['rows'])
            || !is_int($data['total'] ?? null) || $data['total'] !== $expected['total']
            || !is_int($data['page'] ?? null) || $data['page'] !== 1
            || !is_int($data['page_size'] ?? null) || $data['page_size'] !== 20
            || (string)($data['date_start'] ?? '') !== $date
            || (string)($data['date_end'] ?? '') !== $date) {
            throw new RuntimeException('Birthday lists pagination/type/source mismatch');
        }
        foreach ($expected['rows'] as $i => $sourceRow) {
            $row = $rows[$i] ?? null;
            $relationId = (int)($sourceRow['relation_id'] ?? 0);
            $expectedDishName = $relationId > 0
                ? ((string)($sourceRow['dish_name'] ?: '菜品记录已不存在')) : '';
            if (!is_array($row)
                || !is_int($row['staff_id'] ?? null)
                || $row['staff_id'] !== (int)$sourceRow['staff_id']
                || (string)($row['staff_uuid'] ?? '') !== (string)$sourceRow['staff_uuid']
                || (string)($row['personnel_umber'] ?? '') !== (string)$sourceRow['personnel_umber']
                || (string)($row['staff_name'] ?? '') !== (string)$sourceRow['staff_name']
                || (string)($row['birthday'] ?? '') !== (string)$sourceRow['birthday']
                || !is_int($row['restaurant_id'] ?? null)
                || $row['restaurant_id'] !== $restaurantId
                || !is_int($row['relation_id'] ?? null)
                || $row['relation_id'] !== $relationId
                || !is_int($row['version'] ?? null)
                || $row['version'] !== (int)($sourceRow['version'] ?? 0)
                || !is_int($row['relation_count'] ?? null)
                || $row['relation_count'] !== (int)($sourceRow['relation_count'] ?? 0)
                || (string)($row['relation_uuid'] ?? '') !== (string)($sourceRow['relation_uuid'] ?? '')
                || (string)($row['dishes_uuid'] ?? '') !== (string)($sourceRow['dishes_uuid'] ?? '')
                || (string)($row['update_time'] ?? '') !== (string)($sourceRow['update_time'] ?? '')
                || (string)($row['fill_status'] ?? '') !== ($relationId > 0 ? '已填写' : '待填写')
                || (string)($row['dish_name'] ?? '') !== $expectedDishName
                || !$imageMatches($row['head_img'] ?? null, $sourceRow['head_img'] ?? null)
                || !$imageMatches($row['dish_image'] ?? null, $sourceRow['dish_image'] ?? null)) {
                throw new RuntimeException('Birthday lists exact staff/relation/dish mismatch');
            }
        }
        $positive[$filter] = ['rows' => count($rows),
            'response_sha' => hash('sha256', json_encode($json,
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))];
    }
    $invalid = $http(['restaurant_id' => (string)$restaurantId,
        'date_start' => '2026-13-40', 'date_end' => $date,
        'page' => '1', 'page_size' => '20'], $token);
    if (($invalid['code'] ?? null) !== 1) {
        throw new RuntimeException('Birthday lists invalid-date rejection mismatch');
    }
    $after = ['all' => $source(), 'filled' => $source('filled'),
        'unfilled' => $source('unfilled')];
    $sourceUnchanged = $before === $after;
    foreach ($tableCounts as $table => $beforeCount) {
        if ($count('SELECT COUNT(*) FROM ' . $p . $table) !== $beforeCount) {
            $sourceUnchanged = false;
        }
    }
    if (!$sourceUnchanged) throw new RuntimeException('Birthday lists natural source changed');
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    $workerStopped = !is_dir('/proc/' . $pid);
    $cmdline = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $boundary = $workerStopped ? null
        : json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    $runtimeSafe = $workerStopped
        || (strpos($cmdline, $root . '/public') !== false
            && strpos($cmdline, '127.0.0.1:' . parse_url($base, PHP_URL_PORT)) !== false
            && is_array($boundary) && !empty($boundary['external_stubs'])
            && !empty($boundary['redis_isolated']) && empty($boundary['real_queue']));
    if (($workerStopped || !$httpSent || $httpConfirmed) && $runtimeSafe) {
        try {
            $cleanup = $newConnection();
            if (!hash_equals($approvedIdentity, hash('sha256', $dbIdentity($cleanup)))) {
                throw new RuntimeException('Birthday lists cleanup DB identity drift');
            }
            $fetch = static function (string $table, string $where, array $args)
                use ($cleanup, $p): array {
                $stmt = $cleanup->prepare('SELECT * FROM ' . $p . $table . ' WHERE ' . $where);
                $stmt->execute($args);
                return array_map('array_change_key_case', $stmt->fetchAll(PDO::FETCH_ASSOC));
            };
            $users = $fetch('user', 'UUID=? OR ACCOUNT=?', [$marker, $marker]);
            $roles = $fetch('role', 'UUID=? OR NAME=?', [$roleUuid, $marker]);
            $userRoles = $fetch('user_role', 'USER_UUID=? OR ROLE_UUID=?', [$marker, $roleUuid]);
            $roleMenus = $fetch('role_menu', 'ROLE_UUID=?', [$roleUuid]);
            $userData = $fetch('user_data', 'USER_UUID=?', [$marker]);
            if (count($users) > 1 || count($roles) > 1 || count($userRoles) > 1
                || count($roleMenus) > 1 || count($userData) > 2) {
                throw new RuntimeException('Birthday lists extra fixture association');
            }
            foreach ($users as $r) if ((string)$r['uuid'] !== $marker
                || (string)$r['account'] !== $marker || (string)$r['name'] !== $marker
                || (int)$r['type'] !== 1 || (string)$r['create_by'] !== 'CODEX'
                || ($actorId > 0 && (int)$r['id'] !== $actorId)) {
                throw new RuntimeException('Birthday lists user ownership drift');
            }
            foreach ($roles as $r) if ((string)$r['uuid'] !== $roleUuid
                || (string)$r['name'] !== $marker || (string)$r['create_by'] !== 'CODEX'
                || ($roleId > 0 && (int)$r['id'] !== $roleId)) {
                throw new RuntimeException('Birthday lists role ownership drift');
            }
            foreach ($userRoles as $r) if ((string)$r['user_uuid'] !== $marker
                || (string)$r['role_uuid'] !== $roleUuid
                || (string)$r['create_by'] !== $marker) {
                throw new RuntimeException('Birthday lists user-role ownership drift');
            }
            foreach ($roleMenus as $r) if ((string)$r['role_uuid'] !== $roleUuid
                || strcasecmp((string)$r['menu_uuid'], $menuUuid) !== 0
                || (string)$r['create_by'] !== $marker) {
                throw new RuntimeException('Birthday lists role-menu ownership drift');
            }
            foreach ($userData as $r) if ((string)$r['user_uuid'] !== $marker
                || !in_array([(int)$r['type'], (string)$r['data_uuid']],
                    [[1, $departmentUuid], [3, (string)$restaurant[0]['uuid']]], true)) {
                throw new RuntimeException('Birthday lists user-data ownership drift');
            }
            $cleanup->beginTransaction();
            try {
                $targets = [
                    ['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', $userData, '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'];
                        }],
                ];
                foreach ($targets as $target) {
                    [$table, $rows, $where, $argsFor] = $target;
                    $stmt = $cleanup->prepare('DELETE FROM ' . $p . $table . ' WHERE ' . $where);
                    foreach ($rows as $row) {
                        $stmt->execute($argsFor($row));
                        if ($stmt->rowCount() !== 1) {
                            throw new RuntimeException('Birthday lists exact cleanup count mismatch');
                        }
                    }
                }
                $cleanup->commit();
            } catch (Throwable $e) {
                if ($cleanup->inTransaction()) $cleanup->rollBack();
                throw $e;
            }
            $verify = $newConnection();
            if (!hash_equals($approvedIdentity, hash('sha256', $dbIdentity($verify)))) {
                throw new RuntimeException('Birthday lists independent DB identity drift');
            }
            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('Birthday lists independent cleanup zero failed');
                }
            }
            $cleaned = true;
        } catch (Throwable $e) {
            $failure .= ($failure === '' ? '' : ';') . 'cleanup:'
                . get_class($e) . ':' . hash('sha256', $e->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 $e) { /* preserve previous journal */ }
    }
}
$result = ['lane' => $lane, 'marker' => $marker,
    'negative_no_menu' => $negative,
    'positive' => $positive,
    'invalid_date_rejected' => isset($invalid) && ($invalid['code'] ?? null) === 1,
    'source_unchanged' => $sourceUnchanged,
    'fixture_independent_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 !== '' || !$negative || count($positive) !== 3
    || !$result['invalid_date_rejected'] || !$sourceUnchanged
    || !$cleaned || !$privateCleared) exit(1);
