<?php
declare(strict_types=1);

// Offline draft. No I/O without exact per-file and library SHA approval.
const CAT8_FIXTURE_APPROVAL = 'CDX_CAT8_FIXTURE_APPROVED_20261002_V1';
$stage = 'default_deny';
$journalPath = null;
set_exception_handler(static function (Throwable $error) use (&$stage, &$journalPath): void {
    echo json_encode(['status' => 'redacted_error', 'stage' => $stage,
        'class' => get_class($error), 'journal_path' => $journalPath,
        'cleanup_independently_required' => $journalPath !== null],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(2);
});
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted_warning', 0, $severity);
});
if (!hash_equals(CAT8_FIXTURE_APPROVAL,
    (string)getenv('CDX_CAT8_FIXTURE_APPROVAL'))) {
    echo json_encode(['status' => 'default_deny', 'database_connected' => false,
        'cache_accessed' => false, 'http_sent' => false,
        'dml_sent' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(78);
}
$stage = 'exact_source_review';
if (!hash_equals((string)hash_file('sha256', __FILE__),
        (string)getenv('CDX_CAT8_FIXTURE_SHA256'))
    || !hash_equals((string)hash_file('sha256', __DIR__
        . '/catalog8_fixture_lib.draft.php'),
        (string)getenv('CDX_CAT8_LIB_SHA256'))) {
    throw new RuntimeException('Exact reviewed fixture and library SHA required');
}
require __DIR__ . '/catalog8_fixture_lib.draft.php';
$mode = (string)getenv('CDX_CAT8_FIXTURE_MODE');
if (!in_array($mode, ['create', 'rotate', 'cleanup'], true)) {
    throw new RuntimeException('Explicit mode required');
}
$target = (string)getenv('CDX_CAT8_TARGET');
$stage = 'isolated_runtime_preflight';
$ctx = cat8_context($target);
/** @var PDO $pdo */
$pdo = $ctx['pdo'];
$stage = 'exact_schema_preflight';
$columns = [
    'yoshop_store_user' => 'STORE_USER_ID,USER_NAME,PASSWORD,REAL_NAME,IS_SUPER,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME',
    'yoshop_store_role' => 'ROLE_ID,ROLE_NAME,PARENT_ID,STORE_ID,CREATE_TIME,UPDATE_TIME',
    'yoshop_store_menu' => 'MENU_ID,NAME,PATH,TYPE,IS_PAGE,PARENT_ID,CREATE_TIME,UPDATE_TIME',
    'yoshop_store_user_role' => 'ID,STORE_USER_ID,ROLE_ID,STORE_ID,CREATE_TIME',
    'yoshop_store_role_menu' => 'ID,ROLE_ID,MENU_ID,STORE_ID,CREATE_TIME',
    'yoshop_store_menu_api' => 'ID,MENU_ID,API_ID,CREATE_TIME',
    'yoshop_store_api' => 'API_ID,NAME,URL,PARENT_ID,CREATE_TIME,UPDATE_TIME',
    'yoshop_store_user_relation' => 'ID,STORE_USER_ID,RELATION_STORE_USER_ID,STORE_ID',
    'yoshop_staff_store_user' => 'ID,STORE_USER_ID,STORE_ID',
];
foreach ($columns as $table => $fieldList) {
    $pdo->query('SELECT ' . $fieldList . ' FROM ' . $table . ' WHERE 1=0');
}
$storeId = 10001;
$now = time();

if ($mode === 'create') {
    $stage = 'api_permission_mapping_readonly';
    $apiIds = [];
    $missingLibrary = false;
    foreach (CAT8_URLS as $url) {
        $rows = cat8_rows($pdo,
            'SELECT API_ID,NAME,URL FROM yoshop_store_api WHERE URL=?', [$url]);
        if (count($rows) > 1 || (count($rows) === 0
                && $url !== '/dishes.dishes/dishLibrary')) {
            throw new RuntimeException('Existing API mapping missing or ambiguous');
        }
        if (count($rows) === 1
            && strpos((string)$rows[0]['name'], 'CDXCAT8U') === 0) {
            throw new RuntimeException('Prior synthetic API mapping needs recovery');
        }
        if (count($rows) === 0) {
            $missingLibrary = true;
        } else {
            $apiIds[$url] = (int)$rows[0]['api_id'];
        }
    }
    $marker = 'CDXCAT8U' . strtoupper(bin2hex(random_bytes(8)));
    $token = 'CDXCAT8T' . strtoupper(bin2hex(random_bytes(16)));
    $menuPath = '/__codex/catalog8/' . strtolower($marker);
    if (cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_user'
            . " WHERE USER_NAME LIKE 'CDXCAT8U%' AND STORE_ID=10001") !== 0
        || cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_user'
            . ' WHERE USER_NAME=? AND STORE_ID=10001', [$marker]) !== 0
        || cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_role'
            . ' WHERE ROLE_NAME=? AND STORE_ID=10001', [$marker]) !== 0
        || cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_menu'
            . ' WHERE PATH=?', [$menuPath]) !== 0
        || cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_api'
            . ' WHERE NAME=?', [$marker]) !== 0) {
        throw new RuntimeException('Synthetic marker collision');
    }
    $stage = 'create_private_journal_before_dml';
    $directory = '/tmp/cdx-cat8-' . bin2hex(random_bytes(8));
    if (!mkdir($directory, 0700)) throw new RuntimeException('Private journal directory failed');
    $journalPath = $directory . '/manifest.json';
    $manifest = [
        '_path' => $journalPath, 'target' => $target,
        'schema' => $ctx['schema'], 'store_id' => $storeId,
        'fixture_mode' => 'synthetic_role_catalog_read',
        'status' => 'creating', 'phase' => 'before_dml',
        'user_name' => $marker, 'menu_path' => $menuPath,
        'token' => $token, 'store_user_id' => 0,
        'role_id' => 0, 'menu_id' => 0, 'created_api_id' => 0,
        'api_ids' => [], 'ttl_seconds' => 300,
        'ttl_proof' => 'pending', 'created_at' => $now,
        'expires_at' => 0, 'hard_stop_at' => $now + 3600,
    ];
    $seed = tempnam($directory, 'seed-');
    if (!is_string($seed) || !chmod($seed, 0600)
        || !rename($seed, $journalPath)) {
        throw new RuntimeException('Private journal seed failed');
    }
    cat8_save_manifest($manifest);
    $stage = 'single_transaction_synthetic_role';
    $pdo->beginTransaction();
    try {
        $passwordHash = password_hash(bin2hex(random_bytes(24)), PASSWORD_BCRYPT);
        if (!is_string($passwordHash)) throw new RuntimeException('Temporary password hash failed');
        $insert = static function (string $sql, array $bind) use ($pdo): void {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($bind);
            if ($stmt->rowCount() !== 1) throw new RuntimeException('Synthetic insert count mismatch');
        };
        $insert('INSERT INTO yoshop_store_user'
            . ' (USER_NAME,PASSWORD,REAL_NAME,IS_SUPER,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME)'
            . ' VALUES (?,?,?,0,0,10001,?,?)',
            [$marker, $passwordHash, $marker, $now, $now]);
        $user = cat8_one($pdo, 'SELECT STORE_USER_ID FROM yoshop_store_user'
            . ' WHERE USER_NAME=? AND REAL_NAME=? AND STORE_ID=10001 AND IS_SUPER=0'
            . ' AND IS_DELETE=0', [$marker, $marker]);
        $userId = (int)$user['store_user_id'];
        $insert('INSERT INTO yoshop_store_role'
            . ' (ROLE_NAME,PARENT_ID,SORT,USER_NUM,NODE,CREATE_BY,STORE_ID,CREATE_TIME,UPDATE_TIME)'
            . ' VALUES (?,0,100,1,?,?,10001,?,?)',
            [$marker, 'codex catalog read fixture', $marker, $now, $now]);
        $role = cat8_one($pdo, 'SELECT ROLE_ID FROM yoshop_store_role'
            . ' WHERE ROLE_NAME=? AND STORE_ID=10001', [$marker]);
        $roleId = (int)$role['role_id'];
        $insert('INSERT INTO yoshop_store_menu'
            . ' (TYPE,NAME,PATH,IS_PAGE,MODULE_KEY,ACTION_MARK,PARENT_ID,SORT,CREATE_TIME,UPDATE_TIME)'
            . " VALUES (10,?,?,0,'CDXCAT8','',0,100,?,?)",
            [$marker, $menuPath, $now, $now]);
        $menu = cat8_one($pdo, 'SELECT MENU_ID FROM yoshop_store_menu'
            . ' WHERE NAME=? AND PATH=?', [$marker, $menuPath]);
        $menuId = (int)$menu['menu_id'];
        if ($missingLibrary) {
            $insert('INSERT INTO yoshop_store_api'
                . ' (NAME,URL,PARENT_ID,SORT,CREATE_TIME,UPDATE_TIME)'
                . ' VALUES (?,?,0,100,?,?)',
                [$marker, '/dishes.dishes/dishLibrary', $now, $now]);
            $api = cat8_one($pdo, 'SELECT API_ID FROM yoshop_store_api'
                . ' WHERE NAME=? AND URL=?',
                [$marker, '/dishes.dishes/dishLibrary']);
            $apiIds['/dishes.dishes/dishLibrary'] = (int)$api['api_id'];
            $createdApiId = (int)$api['api_id'];
        } else {
            $createdApiId = 0;
        }
        if (count($apiIds) !== 8 || count(array_unique($apiIds)) !== 8) {
            throw new RuntimeException('Eight unique API mappings required');
        }
        $insert('INSERT INTO yoshop_store_user_role'
            . ' (STORE_USER_ID,ROLE_ID,STORE_ID,CREATE_TIME) VALUES (?,?,10001,?)',
            [$userId, $roleId, $now]);
        $insert('INSERT INTO yoshop_store_role_menu'
            . ' (ROLE_ID,MENU_ID,STORE_ID,CREATE_TIME) VALUES (?,?,10001,?)',
            [$roleId, $menuId, $now]);
        foreach (CAT8_URLS as $url) {
            $insert('INSERT INTO yoshop_store_menu_api'
                . ' (MENU_ID,API_ID,CREATE_TIME) VALUES (?,?,?)',
                [$menuId, $apiIds[$url], $now]);
        }
        $granted = cat8_rows($pdo,
            'SELECT DISTINCT a.URL FROM yoshop_store_user_role ur'
            . ' INNER JOIN yoshop_store_role_menu rm ON rm.ROLE_ID=ur.ROLE_ID'
            . ' INNER JOIN yoshop_store_menu_api ma ON ma.MENU_ID=rm.MENU_ID'
            . ' INNER JOIN yoshop_store_api a ON a.API_ID=ma.API_ID'
            . ' WHERE ur.STORE_USER_ID=? AND ur.ROLE_ID=? AND ur.STORE_ID=10001'
            . ' AND rm.MENU_ID=? AND rm.STORE_ID=10001 ORDER BY a.URL',
            [$userId, $roleId, $menuId]);
        $urls = array_map(static function (array $row): string {
            return (string)$row['url'];
        }, $granted);
        $expectedUrls = CAT8_URLS;
        sort($expectedUrls);
        if ($urls !== $expectedUrls) throw new RuntimeException('Role grants not exact eight URLs');
        $pdo->commit();
    } catch (Throwable $error) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        throw $error;
    }
    $manifest['phase'] = 'db_committed';
    $manifest['store_user_id'] = $userId;
    $manifest['role_id'] = $roleId;
    $manifest['menu_id'] = $menuId;
    $manifest['created_api_id'] = $createdApiId;
    $manifest['api_ids'] = $apiIds;
    cat8_save_manifest($manifest);
    $stage = 'short_file_cache_session';
    $session = ['user' => ['store_user_id' => $userId,
        'store_id' => 10001, 'user_name' => $marker,
        'real_name' => $marker, 'is_super' => 0, 'is_delete' => 0],
        'store_id' => 10001, 'is_login' => true];
    if (!\think\facade\Cache::set($token, $session, 300)
        || \think\facade\Cache::get($token) !== $session) {
        throw new RuntimeException('Synthetic short session not proven');
    }
    $ttl = cat8_cache_ttl($token);
    $manifest['status'] = 'ready';
    $manifest['phase'] = 'session_ready';
    $manifest['ttl_proof'] = 'runtime_cache_ttl_verified';
    $manifest['expires_at'] = $ttl['expires_at'];
    $manifest['cache_file_sha256'] = $ttl['cache_file_sha256'];
    cat8_save_manifest($manifest);
    echo json_encode(['status' => 'fixture_ready', 'target' => $target,
        'journal_path' => $journalPath, 'role_granted_paths' => 8,
        'synthetic_account_rows' => 1, 'ttl_seconds' => 300,
        'expires_at' => $ttl['expires_at'], 'token_output' => false],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$stage = 'private_existing_journal';
$manifest = cat8_manifest((string)getenv('CDX_CAT8_FIXTURE_MANIFEST'));
$journalPath = $manifest['_path'];
if ($manifest['target'] !== $target || $manifest['schema'] !== $ctx['schema']
    || ($manifest['fixture_mode'] ?? null) !== 'synthetic_role_catalog_read'
    || (int)($manifest['hard_stop_at'] ?? 0) <= 0) {
    throw new RuntimeException('Existing fixture target or mode mismatch');
}
$marker = $manifest['user_name'];
$menuPath = (string)($manifest['menu_path'] ?? '');
if ($menuPath !== '/__codex/catalog8/' . strtolower($marker)) {
    throw new RuntimeException('Synthetic menu path mismatch');
}
$userRows = cat8_rows($pdo, 'SELECT STORE_USER_ID,USER_NAME,REAL_NAME,STORE_ID,'
    . ' IS_SUPER,IS_DELETE FROM yoshop_store_user'
    . ' WHERE USER_NAME=? AND STORE_ID=10001', [$marker]);
$roleRows = cat8_rows($pdo, 'SELECT ROLE_ID,ROLE_NAME,STORE_ID FROM yoshop_store_role'
    . ' WHERE ROLE_NAME=? AND STORE_ID=10001', [$marker]);
$menuRows = cat8_rows($pdo, 'SELECT MENU_ID,NAME,PATH FROM yoshop_store_menu'
    . ' WHERE PATH=?', [$menuPath]);
if (count($userRows) > 1 || count($roleRows) > 1 || count($menuRows) > 1) {
    throw new RuntimeException('Ambiguous fixture ownership');
}

if ($mode === 'rotate') {
    $stage = 'rotate_short_session';
    if (($manifest['status'] ?? null) !== 'ready'
        || time() >= (int)$manifest['hard_stop_at']
        || count($userRows) !== 1 || count($roleRows) !== 1
        || count($menuRows) !== 1
        || (int)$userRows[0]['store_user_id'] !== (int)$manifest['store_user_id']
        || (int)$roleRows[0]['role_id'] !== (int)$manifest['role_id']
        || (int)$menuRows[0]['menu_id'] !== (int)$manifest['menu_id']) {
        throw new RuntimeException('Existing fixture cannot be rotated');
    }
    $granted = cat8_rows($pdo,
        'SELECT DISTINCT a.URL FROM yoshop_store_user_role ur'
        . ' INNER JOIN yoshop_store_role_menu rm ON rm.ROLE_ID=ur.ROLE_ID'
        . ' INNER JOIN yoshop_store_menu_api ma ON ma.MENU_ID=rm.MENU_ID'
        . ' INNER JOIN yoshop_store_api a ON a.API_ID=ma.API_ID'
        . ' WHERE ur.STORE_USER_ID=? AND ur.ROLE_ID=? AND ur.STORE_ID=10001'
        . ' AND rm.MENU_ID=? AND rm.STORE_ID=10001 ORDER BY a.URL',
        [(int)$manifest['store_user_id'], (int)$manifest['role_id'],
            (int)$manifest['menu_id']]);
    $urls = array_map(static function (array $row): string {
        return (string)$row['url'];
    }, $granted);
    $expectedUrls = CAT8_URLS;
    sort($expectedUrls);
    if ($urls !== $expectedUrls || (int)$userRows[0]['is_super'] !== 0
        || (int)$userRows[0]['is_delete'] !== 0) {
        throw new RuntimeException('Rotated fixture permission or account drift');
    }
    $oldSession = \think\facade\Cache::get($manifest['token']);
    if ($oldSession !== null && (!is_array($oldSession)
        || (int)($oldSession['user']['store_user_id'] ?? 0)
            !== (int)$manifest['store_user_id'])) {
        throw new RuntimeException('Old cache key ownership mismatch');
    }
    if (!\think\facade\Cache::delete($manifest['token'])
        && \think\facade\Cache::get($manifest['token']) !== null) {
        throw new RuntimeException('Old session not revoked');
    }
    $newToken = 'CDXCAT8T' . strtoupper(bin2hex(random_bytes(16)));
    $manifest['token'] = $newToken;
    $manifest['status'] = 'rotating';
    $manifest['phase'] = 'new_token_journaled_before_cache_set';
    $manifest['ttl_proof'] = 'pending';
    $manifest['expires_at'] = 0;
    cat8_save_manifest($manifest);
    $session = ['user' => ['store_user_id' => (int)$manifest['store_user_id'],
        'store_id' => 10001, 'user_name' => $marker,
        'real_name' => $marker, 'is_super' => 0, 'is_delete' => 0],
        'store_id' => 10001, 'is_login' => true];
    if (!\think\facade\Cache::set($newToken, $session, 300)
        || \think\facade\Cache::get($newToken) !== $session) {
        throw new RuntimeException('New short session not proven');
    }
    $ttl = cat8_cache_ttl($newToken);
    $manifest['status'] = 'ready';
    $manifest['phase'] = 'session_rotated';
    $manifest['ttl_proof'] = 'runtime_cache_ttl_verified';
    $manifest['expires_at'] = $ttl['expires_at'];
    $manifest['cache_file_sha256'] = $ttl['cache_file_sha256'];
    cat8_save_manifest($manifest);
    echo json_encode(['status' => 'fixture_session_rotated',
        'target' => $target, 'journal_path' => $journalPath,
        'expires_at' => $ttl['expires_at'], 'token_output' => false],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$stage = 'cleanup_cache_first';
$session = \think\facade\Cache::get($manifest['token']);
if ($session !== null) {
    $expectedCacheUserId = (int)($manifest['store_user_id'] ?? 0);
    if ($expectedCacheUserId <= 0 && count($userRows) === 1) {
        $expectedCacheUserId = (int)$userRows[0]['store_user_id'];
    }
    if (!is_array($session)
        || (int)($session['user']['store_user_id'] ?? 0)
            !== $expectedCacheUserId || $expectedCacheUserId <= 0) {
        throw new RuntimeException('Cache key belongs to unexpected identity');
    }
    if (!\think\facade\Cache::delete($manifest['token'])
        || \think\facade\Cache::get($manifest['token']) !== null) {
        throw new RuntimeException('Synthetic session deletion not proven');
    }
}
$manifest['status'] = 'cleaning';
$manifest['phase'] = 'cache_absent_before_db_cleanup';
cat8_save_manifest($manifest);
$stage = 'cleanup_exact_database_relations';
if (count($userRows) === 0 && count($roleRows) === 0 && count($menuRows) === 0) {
    $knownUserId = (int)($manifest['store_user_id'] ?? 0);
    $knownRoleId = (int)($manifest['role_id'] ?? 0);
    $knownMenuId = (int)($manifest['menu_id'] ?? 0);
    $orphanCounts = [
        cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_api'
            . ' WHERE NAME=? AND URL=?',
            [$marker, '/dishes.dishes/dishLibrary']),
        $knownUserId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_user_role WHERE STORE_USER_ID=?',
            [$knownUserId]) : 0,
        $knownRoleId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_role_menu WHERE ROLE_ID=?',
            [$knownRoleId]) : 0,
        $knownMenuId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_menu_api WHERE MENU_ID=?',
            [$knownMenuId]) : 0,
        $knownRoleId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_role WHERE PARENT_ID=?',
            [$knownRoleId]) : 0,
        $knownMenuId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_menu WHERE PARENT_ID=?',
            [$knownMenuId]) : 0,
    ];
    if (array_sum($orphanCounts) !== 0) {
        throw new RuntimeException('Orphaned fixture references need manual recovery');
    }
    $manifest['status'] = 'cleaned';
    $manifest['phase'] = 'independent_zero_required';
    cat8_save_manifest($manifest);
    echo json_encode(['status' => 'no_fixture_rows', 'target' => $target,
        'journal_path' => $journalPath,
        'independent_readback_required' => true], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
if (count($userRows) !== 1 || count($roleRows) !== 1 || count($menuRows) !== 1) {
    throw new RuntimeException('Partial fixture needs manual recovery');
}
$userId = (int)$userRows[0]['store_user_id'];
$roleId = (int)$roleRows[0]['role_id'];
$menuId = (int)$menuRows[0]['menu_id'];
if (($manifest['store_user_id'] ?? 0) > 0
    && (int)$manifest['store_user_id'] !== $userId) {
    throw new RuntimeException('Synthetic user ID drift');
}
if (($manifest['role_id'] ?? 0) > 0 && (int)$manifest['role_id'] !== $roleId) {
    throw new RuntimeException('Synthetic role ID drift');
}
if (($manifest['menu_id'] ?? 0) > 0 && (int)$manifest['menu_id'] !== $menuId) {
    throw new RuntimeException('Synthetic menu ID drift');
}
if ((string)$userRows[0]['real_name'] !== $marker
    || (int)$userRows[0]['is_super'] !== 0
    || (int)$userRows[0]['is_delete'] !== 0
    || (int)$userRows[0]['store_id'] !== 10001
    || (string)$roleRows[0]['role_name'] !== $marker
    || (string)$menuRows[0]['name'] !== $marker) {
    throw new RuntimeException('Synthetic base row ownership mismatch');
}
$scopes = [
    'user_role' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_user_role'
        . ' WHERE STORE_USER_ID=? AND ROLE_ID=? AND STORE_ID=10001', [$userId, $roleId]),
    'all_user_roles' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_user_role'
        . ' WHERE STORE_USER_ID=? OR ROLE_ID=?', [$userId, $roleId]),
    'role_menu' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_role_menu'
        . ' WHERE ROLE_ID=? AND MENU_ID=? AND STORE_ID=10001', [$roleId, $menuId]),
    'all_role_menus' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_role_menu'
        . ' WHERE ROLE_ID=? OR MENU_ID=?', [$roleId, $menuId]),
    'menu_api' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_menu_api'
        . ' WHERE MENU_ID=?', [$menuId]),
    'user_relation' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_user_relation'
        . ' WHERE STORE_USER_ID=? OR RELATION_STORE_USER_ID=?', [$userId, $userId]),
    'staff_relation' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_staff_store_user'
        . ' WHERE STORE_USER_ID=?', [$userId]),
    'child_role' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_role'
        . ' WHERE PARENT_ID=?', [$roleId]),
    'child_menu' => cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_menu'
        . ' WHERE PARENT_ID=?', [$menuId]),
];
if ($scopes !== ['user_role' => 1, 'all_user_roles' => 1,
        'role_menu' => 1, 'all_role_menus' => 1,
        'menu_api' => 8, 'user_relation' => 0, 'staff_relation' => 0,
        'child_role' => 0, 'child_menu' => 0]) {
    throw new RuntimeException('Unexpected external references; no database cleanup');
}
$createdApi = cat8_rows($pdo, 'SELECT API_ID,NAME,URL FROM yoshop_store_api'
    . ' WHERE NAME=? AND URL=?', [$marker, '/dishes.dishes/dishLibrary']);
if (count($createdApi) > 1) throw new RuntimeException('Synthetic API ambiguity');
$createdApiId = count($createdApi) === 1 ? (int)$createdApi[0]['api_id'] : 0;
if ((int)($manifest['created_api_id'] ?? 0) > 0
    && $createdApiId !== (int)$manifest['created_api_id']) {
    throw new RuntimeException('Synthetic API ID drift');
}
if ($createdApiId > 0 && cat8_count($pdo,
        'SELECT COUNT(*) FROM yoshop_store_menu_api WHERE API_ID=?',
        [$createdApiId]) !== 1) {
    throw new RuntimeException('Synthetic API referenced beyond fixture');
}
if ($createdApiId > 0 && cat8_count($pdo,
        'SELECT COUNT(*) FROM yoshop_store_api WHERE PARENT_ID=?',
        [$createdApiId]) !== 0) {
    throw new RuntimeException('Synthetic API has child references');
}
$pdo->beginTransaction();
try {
    $delete = static function (string $sql, array $bind, int $expected) use ($pdo): void {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($bind);
        if ($stmt->rowCount() !== $expected) {
            throw new RuntimeException('Exact cleanup affected-row mismatch');
        }
    };
    $delete('DELETE FROM yoshop_store_menu_api WHERE MENU_ID=?', [$menuId], 8);
    $delete('DELETE FROM yoshop_store_role_menu'
        . ' WHERE ROLE_ID=? AND MENU_ID=? AND STORE_ID=10001',
        [$roleId, $menuId], 1);
    $delete('DELETE FROM yoshop_store_user_role'
        . ' WHERE STORE_USER_ID=? AND ROLE_ID=? AND STORE_ID=10001',
        [$userId, $roleId], 1);
    $delete('DELETE FROM yoshop_store_menu WHERE MENU_ID=? AND NAME=? AND PATH=?',
        [$menuId, $marker, $menuPath], 1);
    $delete('DELETE FROM yoshop_store_role'
        . ' WHERE ROLE_ID=? AND ROLE_NAME=? AND STORE_ID=10001',
        [$roleId, $marker], 1);
    $delete('DELETE FROM yoshop_store_user'
        . ' WHERE STORE_USER_ID=? AND USER_NAME=? AND REAL_NAME=?'
        . ' AND STORE_ID=10001 AND IS_SUPER=0 AND IS_DELETE=0',
        [$userId, $marker, $marker], 1);
    if ($createdApiId > 0) {
        $delete('DELETE FROM yoshop_store_api WHERE API_ID=? AND NAME=? AND URL=?',
            [$createdApiId, $marker, '/dishes.dishes/dishLibrary'], 1);
    }
    $pdo->commit();
} catch (Throwable $error) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    throw $error;
}
$manifest['status'] = 'cleaned';
$manifest['phase'] = 'independent_zero_required';
cat8_save_manifest($manifest);
echo json_encode(['status' => 'fixture_cleanup_committed',
    'target' => $target, 'journal_path' => $journalPath,
    'independent_readback_required' => true], JSON_UNESCAPED_SLASHES), PHP_EOL;
