<?php

declare(strict_types=1);

// Keep update-step responses free of notices, warnings, redirects, and layout
// output. The browser expects JSON for asynchronous update requests.
$__dentalUpdateAjaxRequest = ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST'
    && in_array((string)($_POST['action'] ?? ''), ['apply_update', 'apply_update_step'], true)
    && ((string)($_POST['ajax'] ?? '') === '1'
        || str_contains(strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? '')), 'application/json'));
if ($__dentalUpdateAjaxRequest) {
    ob_start();
}

require DENTAL_ROOT . '/app/bootstrap.php';
if (!current_user()) {
    header('Location: login.php');
    exit;
}
require_roles('super_admin');
require DENTAL_ROOT . '/app/layout.php';
require_once DENTAL_ROOT . '/app/Services/SystemBackupService.php';
require_once DENTAL_ROOT . '/app/tooth_model.php';
require_once DENTAL_ROOT . '/app/revenue_integrity.php';
require_once DENTAL_ROOT . '/app/update_readiness.php';
require_once DENTAL_ROOT . '/app/update_checkpoint.php';
require_once DENTAL_ROOT . '/app/security_hardening.php';
require_once DENTAL_ROOT . '/app/appointment_schedule_guard.php';
require_once DENTAL_ROOT . '/app/clinical_record_completion.php';
require_once DENTAL_ROOT . '/app/inventory_integrity.php';
require_once DENTAL_ROOT . '/app/release_hardening.php';
require_once DENTAL_ROOT . '/app/role_satisfaction.php';
require_once DENTAL_ROOT . '/app/clinical_safety.php';

$pdo = db();
$tab = (string) ($_GET['tab'] ?? 'overview');
$allowedTabs = ['overview','updates','permissions','email','backups','environment','security'];
if (!in_array($tab, $allowedTabs, true)) $tab = 'overview';

function setup_table_exists(PDO $pdo, string $tableName): bool
{
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=? AND table_name=?');
        $stmt->execute([(string) app_config('database.name'), $tableName]);
        return (int) $stmt->fetchColumn() > 0;
    } catch (Throwable) {
        return false;
    }
}

function setup_column_exists(PDO $pdo, string $tableName, string $column): bool
{
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=? AND table_name=? AND column_name=?');
        $stmt->execute([(string) app_config('database.name'), $tableName, $column]);
        return (int) $stmt->fetchColumn() > 0;
    } catch (Throwable) {
        return false;
    }
}

function setup_add_column(PDO $pdo, string $tableName, string $column, string $definition): void
{
    if (!setup_column_exists($pdo, $tableName, $column)) {
        $pdo->exec('ALTER TABLE `' . $tableName . '` ADD `' . $column . '` ' . $definition);
    }
}

if (!function_exists('ensure_settings_table')) {
    /**
     * Emergency settings-table guard. v0.7.5/v0.7.5.1 readiness checks used
     * ensure_settings_table(), but older installs did not define it globally.
     * Keep System Setup readable even before the full update chain is reapplied.
     */
    function ensure_settings_table(PDO $pdo): void
    {
        $table = table('settings');
        $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $table . '` (
            `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            `group_name` VARCHAR(80) NOT NULL,
            `setting_key` VARCHAR(120) NOT NULL,
            `setting_value` LONGTEXT NULL,
            `is_secret` TINYINT(1) NOT NULL DEFAULT 0,
            `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            `updated_at` DATETIME NULL,
            PRIMARY KEY (`id`),
            UNIQUE KEY `uq_setting` (`group_name`, `setting_key`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
    }
}

function setup_column_nullable(PDO $pdo, string $tableName, string $column): bool
{
    $stmt = $pdo->prepare('SELECT IS_NULLABLE FROM information_schema.columns WHERE table_schema=? AND table_name=? AND column_name=? LIMIT 1');
    $stmt->execute([(string) app_config('database.name'), $tableName, $column]);
    return strtoupper((string) $stmt->fetchColumn()) === 'YES';
}

function setup_index_exists(PDO $pdo, string $tableName, string $indexName): bool
{
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=? AND table_name=? AND index_name=?');
        $stmt->execute([(string) app_config('database.name'), $tableName, $indexName]);
        return (int) $stmt->fetchColumn() > 0;
    } catch (Throwable) {
        return false;
    }
}

function setup_constraint_exists(PDO $pdo, string $tableName, string $constraintName): bool
{
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_schema=? AND table_name=? AND constraint_name=?');
        $stmt->execute([(string) app_config('database.name'), $tableName, $constraintName]);
        return (int) $stmt->fetchColumn() > 0;
    } catch (Throwable) {
        return false;
    }
}

function setup_record_history(PDO $pdo, string $version, string $action): void
{
    $tableName = table('install_history');
    if (!setup_table_exists($pdo, $tableName)) {
        $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $tableName . '` (
'
            . '  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
'
            . '  `version` VARCHAR(40) NOT NULL,
'
            . '  `database_action` VARCHAR(190) NOT NULL,
'
            . '  `installed_by_email` VARCHAR(190) NOT NULL,
'
            . '  `installed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
'
            . '  PRIMARY KEY (`id`),
'
            . '  KEY `idx_install_history_version` (`version`,`installed_at`)
'
            . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
    }

    // Never shrink a live history column. Earlier releases modified this field
    // to VARCHAR(100) on every update, which can fail when historical action
    // labels are longer or the host uses strict SQL mode.
    $lengthStmt = $pdo->prepare('SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.columns WHERE table_schema=? AND table_name=? AND column_name="database_action" LIMIT 1');
    $lengthStmt->execute([(string) app_config('database.name'), $tableName]);
    $currentLength = (int)($lengthStmt->fetchColumn() ?: 0);
    if ($currentLength > 0 && $currentLength < 190) {
        $pdo->exec('ALTER TABLE `' . $tableName . '` MODIFY `database_action` VARCHAR(190) NOT NULL');
    }

    $version = mb_substr(trim($version), 0, 40);
    $action = mb_substr(trim($action), 0, 190);
    $check = $pdo->prepare('SELECT COUNT(*) FROM `' . $tableName . '` WHERE version=? AND database_action=?');
    $check->execute([$version, $action]);
    if (!(int) $check->fetchColumn()) {
        $pdo->prepare('INSERT INTO `' . $tableName . '` (version,database_action,installed_by_email,installed_at) VALUES (?,?,?,NOW())')
            ->execute([$version, $action, mb_substr((string) (current_user()['email'] ?? ''), 0, 190)]);
    }
}

function setup_execute_sql_file(PDO $pdo, string $path): void
{
    $schema = file_get_contents($path);
    if ($schema === false) throw new RuntimeException('Unable to read migration ' . basename($path));
    $schema = str_replace('{{prefix}}', (string) app_config('database.prefix', 'dent_'), $schema);
    $statements = array_filter(array_map('trim', preg_split('/;\s*(?:\r?\n|$)/', $schema) ?: []));
    foreach ($statements as $statement) {
        if ($statement !== '') $pdo->exec($statement);
    }
}

function setup_apply_v022(PDO $pdo): void
{
    // Create the application tables that were not part of the very first foundation installer.
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/application_schema.sql');
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.2.2_public_appointment_intake.sql');
    setup_record_history($pdo, '0.2.2', 'public_appointment_intake_upgrade');
}

function setup_apply_v030(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.3.0_availability_branding_license.sql');

    $requestTable = table('appointment_requests');
    setup_add_column($pdo, $requestTable, 'service_id', 'BIGINT UNSIGNED NULL AFTER `service_name`');
    setup_add_column($pdo, $requestTable, 'preferred_dentist_user_id', 'BIGINT UNSIGNED NULL AFTER `preferred_dentist`');
    if (!setup_index_exists($pdo, $requestTable, 'idx_request_service')) {
        $pdo->exec('ALTER TABLE `' . $requestTable . '` ADD KEY `idx_request_service` (`service_id`)');
    }
    if (!setup_index_exists($pdo, $requestTable, 'idx_request_dentist_user')) {
        $pdo->exec('ALTER TABLE `' . $requestTable . '` ADD KEY `idx_request_dentist_user` (`preferred_dentist_user_id`)');
    }

    $branchIds = $pdo->query('SELECT id FROM `' . table('branches') . '` WHERE is_active=1')->fetchAll(PDO::FETCH_COLUMN);
    $hours = $pdo->prepare('INSERT INTO `' . table('branch_operating_hours') . '` (branch_id,day_of_week,is_open,open_time,close_time,slot_interval_minutes,created_at) VALUES (?,?,?,?,?,?,NOW()) ON DUPLICATE KEY UPDATE branch_id=VALUES(branch_id)');
    foreach ($branchIds as $branchId) {
        for ($day = 0; $day <= 6; $day++) {
            $isOpen = $day >= 1 && $day <= 6;
            $hours->execute([(int) $branchId, $day, $isOpen ? 1 : 0, $isOpen ? '08:00:00' : null, $day === 6 ? '12:00:00' : ($isOpen ? '17:00:00' : null), 30]);
        }
    }

    if ((int) $pdo->query('SELECT COUNT(*) FROM `' . table('service_catalog') . '`')->fetchColumn() === 0) {
        $defaults = [
            ['Dental Examination',30],['Oral Prophylaxis',45],['Tooth Restoration',60],['Tooth Extraction',60],
            ['Root Canal Consultation',60],['Orthodontic Consultation',45],['Denture Consultation',45],['Emergency Dental Visit',60],
        ];
        $service = $pdo->prepare('INSERT INTO `' . table('service_catalog') . '` (branch_id,name,duration_minutes,is_public,is_active,created_at) VALUES (NULL,?,?,1,1,NOW())');
        foreach ($defaults as [$name,$duration]) $service->execute([$name,$duration]);
    }
    setup_record_history($pdo, '0.3.0', 'availability_branding_license_upgrade');
}

function setup_apply_v031(PDO $pdo): void
{
    $queue = table('queue_entries');
    $invoice = table('invoices');
    setup_add_column($pdo, $queue, 'source_type', "ENUM('appointment','walk_in') NOT NULL DEFAULT 'walk_in' AFTER `patient_id`");
    setup_add_column($pdo, $queue, 'service_name', 'VARCHAR(150) NULL AFTER `priority`');
    setup_add_column($pdo, $queue, 'dentist_user_id', 'BIGINT UNSIGNED NULL AFTER `service_name`');
    setup_add_column($pdo, $queue, 'dentist_name', 'VARCHAR(150) NULL AFTER `dentist_user_id`');
    setup_add_column($pdo, $queue, 'chair_name', 'VARCHAR(100) NULL AFTER `dentist_name`');
    setup_add_column($pdo, $queue, 'reason', 'TEXT NULL AFTER `chair_name`');
    setup_add_column($pdo, $queue, 'notes', 'TEXT NULL AFTER `reason`');
    setup_add_column($pdo, $queue, 'checked_in_by', 'BIGINT UNSIGNED NULL AFTER `status`');
    setup_add_column($pdo, $queue, 'seated_at', 'DATETIME NULL AFTER `called_at`');
    setup_add_column($pdo, $queue, 'treatment_started_at', 'DATETIME NULL AFTER `seated_at`');
    setup_add_column($pdo, $queue, 'billing_at', 'DATETIME NULL AFTER `treatment_started_at`');
    setup_add_column($pdo, $queue, 'cashier_called_at', 'DATETIME NULL AFTER `billing_at`');
    setup_add_column($pdo, $queue, 'paid_at', 'DATETIME NULL AFTER `cashier_called_at`');
    setup_add_column($pdo, $queue, 'cancelled_at', 'DATETIME NULL AFTER `completed_at`');
    setup_add_column($pdo, $queue, 'updated_at', 'DATETIME NULL AFTER `cancelled_at`');
    foreach ([
        'idx_queue_source'=>'(`source_type`,`checked_in_at`)',
        'idx_queue_patient_day'=>'(`patient_id`,`checked_in_at`)',
        'idx_queue_dentist'=>'(`dentist_user_id`,`checked_in_at`)',
    ] as $name=>$columns) {
        if (!setup_index_exists($pdo,$queue,$name)) $pdo->exec('ALTER TABLE `' . $queue . '` ADD KEY `' . $name . '` ' . $columns);
    }
    setup_add_column($pdo, $invoice, 'queue_entry_id', 'BIGINT UNSIGNED NULL AFTER `encounter_id`');
    if (!setup_index_exists($pdo,$invoice,'idx_invoice_queue')) $pdo->exec('ALTER TABLE `' . $invoice . '` ADD KEY `idx_invoice_queue` (`queue_entry_id`)');
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.3.1_queue_cashier.sql');
    $pdo->exec(
        'UPDATE `' . $queue . '` q LEFT JOIN `' . table('appointments') . '` a ON a.id=q.appointment_id SET '
        . 'q.source_type=IF(q.appointment_id IS NULL,"walk_in","appointment"),'
        . 'q.service_name=COALESCE(q.service_name,a.service_name),'
        . 'q.dentist_user_id=COALESCE(q.dentist_user_id,a.dentist_user_id),'
        . 'q.dentist_name=COALESCE(q.dentist_name,a.dentist_name),'
        . 'q.chair_name=COALESCE(q.chair_name,a.chair_name)'
    );
    setup_record_history($pdo, '0.3.1', 'queue_cashier_patient_name_upgrade');
}

function setup_normalize_roles(PDO $pdo): void
{
    $stmt = $pdo->query('SELECT id,code FROM `' . table('roles') . '` WHERE code IN ("assistant","dental_assistant")');
    $found = [];
    foreach ($stmt as $row) $found[(string) $row['code']] = (int) $row['id'];
    if (isset($found['assistant']) && !isset($found['dental_assistant'])) {
        $pdo->prepare('UPDATE `' . table('roles') . '` SET code="dental_assistant",name="Dental Assistant" WHERE id=?')->execute([$found['assistant']]);
    } elseif (isset($found['assistant'], $found['dental_assistant'])) {
        $pdo->prepare('UPDATE `' . table('users') . '` SET role_id=? WHERE role_id=?')->execute([$found['dental_assistant'],$found['assistant']]);
        $pdo->prepare('DELETE FROM `' . table('roles') . '` WHERE id=?')->execute([$found['assistant']]);
    } elseif (!isset($found['dental_assistant'])) {
        $pdo->prepare('INSERT INTO `' . table('roles') . '` (code,name,description,created_at) VALUES ("dental_assistant","Dental Assistant","Screening, chairside support, and draft clinical entries",NOW())')->execute();
    }
}

function setup_seed_permissions(PDO $pdo): void
{
    $permissions = [
        ['patients.view','View patient records','Patient Care'],['patients.manage','Create and edit patients','Patient Care'],
        ['patients.identity.view','View patient identity','Patient Care'],['patients.contact.view','View patient contact details','Patient Care'],
        ['patients.safety_alert.view','View patient dental-safety alert flag','Patient Care'],['patients.medical.intake','Record patient-reported dental-safety history','Patient Care'],
        ['patients.medical.view','View health information for dental care','Clinical'],['patients.medical.verify','Verify dental-safety history and alerts','Clinical'],
        ['patients.financial.view','View patient financial records','Finance'],['patients.portal.manage','Manage patient portal access','Patient Care'],
        ['appointments.manage','Manage appointments and online requests','Patient Care'],['queue.manage','Manage appointment and walk-in queues','Patient Care'],
        ['clinical.view','View clinical records','Clinical'],['clinical.manage','Create clinical records','Clinical'],
        ['clinical.finalize','Finalize clinical notes and prescriptions','Clinical'],['billing.manage','Manage invoices and payments','Finance'],
        ['insurance.manage','Manage insurance and HMO claims','Finance'],['inventory.manage','Manage inventory and stock','Operations'],['inventory.catalog','Add retail products and supply catalogue items','Operations'],
        ['sterilization.manage','Manage sterilization records','Operations'],['communications.manage','Manage patient communications','Operations'],
        ['reports.view','View operational and financial reports','Reports'],['reports.frontdesk','View front-desk operational reports','Reports'],
        ['reports.assistant','View dental assistant worklog reports','Reports'],['reports.clinical','View dentist clinical completion reports','Reports'],
        ['reports.inventory','View inventory and sterilization reports','Reports'],['appointment.service_disposition','Defer or move appointment services','Patient Care'],
        ['cashier.statement.print','Print cashier invoices, receipts, and statements','Finance'],['cashier.session.manage','Open and close own cashier session','Finance'],
        ['records.print','Print and export authorized records','Reports'],
        ['staff.manage','Manage staff and dentists','Administration'],
        ['configuration.manage','Manage clinic configuration','Administration'],['system.manage','Manage system updates, backups, and permissions','Administration'],
        ['uploads.manage','Manage clinic files and uploads','Administration'],
    ];
    $insert = $pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
    foreach ($permissions as [$code,$name,$group]) $insert->execute([$code,$name,$group,$name]);

    $defaults = [
        'clinic_admin' => ['patients.view','patients.manage','patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.view','patients.financial.view','patients.portal.manage','appointments.manage','queue.manage','clinical.view','billing.manage','insurance.manage','inventory.manage','sterilization.manage','communications.manage','reports.view','reports.frontdesk','reports.assistant','reports.clinical','reports.inventory','appointment.service_disposition','cashier.statement.print','records.print','staff.manage','configuration.manage','uploads.manage'],
        'receptionist' => ['patients.view','patients.manage','patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.portal.manage','appointments.manage','queue.manage','communications.manage','reports.view','reports.frontdesk','appointment.service_disposition'],
        'staff' => ['patients.view','patients.manage','patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.financial.view','patients.portal.manage','appointments.manage','queue.manage','communications.manage','reports.view','reports.frontdesk','records.print','appointment.service_disposition','billing.manage','cashier.statement.print','cashier.session.manage'],
        'dentist' => ['patients.view','patients.identity.view','patients.safety_alert.view','patients.medical.view','patients.medical.verify','appointments.manage','queue.manage','clinical.view','clinical.manage','clinical.finalize','reports.view','reports.clinical','appointment.service_disposition'],
        'dental_assistant' => ['patients.view','patients.identity.view','patients.safety_alert.view','patients.medical.intake','patients.medical.view','queue.manage','clinical.view','clinical.manage','sterilization.manage','reports.view','reports.assistant','appointment.service_disposition'],
        'cashier' => ['patients.identity.view','patients.financial.view','billing.manage','cashier.statement.print','cashier.session.manage'],
        'inventory_officer' => ['inventory.manage','sterilization.manage','reports.view','reports.inventory'],
    ];
    $roleStmt = $pdo->query('SELECT id,code FROM `' . table('roles') . '`');
    $permStmt = $pdo->query('SELECT id,code FROM `' . table('permissions') . '`');
    $roleIds = [];
    foreach ($roleStmt as $row) $roleIds[(string)$row['code']] = (int)$row['id'];
    $permIds = [];
    foreach ($permStmt as $row) $permIds[(string)$row['code']] = (int)$row['id'];
    $link = $pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) VALUES (?,?,1) ON DUPLICATE KEY UPDATE allowed=VALUES(allowed)');
    foreach ($defaults as $role => $codes) {
        if (!isset($roleIds[$role])) continue;
        foreach ($codes as $code) if (isset($permIds[$code])) $link->execute([$roleIds[$role],$permIds[$code]]);
    }
    $hardDenied = [
        'clinic_admin'=>['clinical.manage','clinical.finalize','patients.medical.intake','patients.medical.verify'],
        'receptionist'=>['patients.medical.view','patients.medical.verify','patients.financial.view','billing.manage','inventory.manage','inventory.catalog','clinical.manage','clinical.finalize','staff.manage','uploads.manage'],
        'staff'=>['patients.medical.view','patients.medical.verify','inventory.manage','inventory.catalog','clinical.manage','clinical.finalize','insurance.manage','staff.manage','uploads.manage'],
        'dentist'=>['patients.contact.view','patients.medical.intake','patients.financial.view','patients.portal.manage','billing.manage','inventory.manage','inventory.catalog','staff.manage'],
        'dental_assistant'=>['patients.contact.view','patients.medical.verify','patients.financial.view','patients.portal.manage','billing.manage','inventory.manage','inventory.catalog','clinical.finalize','staff.manage','uploads.manage'],
        'cashier'=>['patients.view','patients.manage','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.medical.view','patients.medical.verify','patients.portal.manage','patients.manage','appointments.manage','queue.manage','clinical.view','clinical.manage','clinical.finalize','inventory.manage','inventory.catalog','sterilization.manage','communications.manage','reports.view','records.print','staff.manage','uploads.manage'],
        'inventory_officer'=>['patients.view','patients.manage','patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.medical.view','patients.medical.verify','patients.financial.view','patients.portal.manage','patients.manage','appointments.manage','queue.manage','clinical.view','clinical.manage','clinical.finalize','billing.manage','staff.manage','uploads.manage'],
    ];
    $revoke=$pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) VALUES (?,?,0) ON DUPLICATE KEY UPDATE allowed=0');
    foreach($hardDenied as $role=>$codes){
        if(!isset($roleIds[$role]))continue;
        foreach($codes as $code)if(isset($permIds[$code]))$revoke->execute([$roleIds[$role],$permIds[$code]]);
    }
}

function setup_apply_v040(PDO $pdo): void
{
    // MySQL/MariaDB DDL performs implicit commits, so this migration is deliberately
    // idempotent instead of being wrapped in a transaction. If hosting interrupts it,
    // the System Setup page can safely run it again and continue from the last step.
    $pdo->exec('ALTER TABLE `' . table('install_history') . '` MODIFY `database_action` VARCHAR(100) NOT NULL');
    setup_add_column($pdo, table('patients'), 'preferred_name', 'VARCHAR(100) NULL AFTER `last_name`');
    setup_add_column($pdo, table('patients'), 'civil_status', 'VARCHAR(40) NULL AFTER `sex`');
    setup_add_column($pdo, table('patients'), 'nationality', 'VARCHAR(80) NULL AFTER `civil_status`');
    setup_add_column($pdo, table('patients'), 'occupation', 'VARCHAR(120) NULL AFTER `nationality`');
    setup_add_column($pdo, table('patients'), 'employer', 'VARCHAR(190) NULL AFTER `occupation`');
    setup_add_column($pdo, table('patients'), 'preferred_language', 'VARCHAR(60) NULL AFTER `employer`');
    setup_add_column($pdo, table('patients'), 'communication_preference', 'VARCHAR(40) NULL AFTER `preferred_language`');
    setup_add_column($pdo, table('patients'), 'profile_photo', 'VARCHAR(255) NULL AFTER `communication_preference`');
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.4.0_documented_modules.sql');
    setup_normalize_roles($pdo);
    setup_seed_permissions($pdo);
    foreach ([DENTAL_ROOT . '/storage/private/documents', DENTAL_ROOT . '/storage/private/system_uploads', DENTAL_ROOT . '/storage/backups'] as $directory) {
        if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
            throw new RuntimeException('Unable to create ' . $directory);
        }
    }
    setting_set('system', 'application_version', '0.4.0');
    setup_record_history($pdo, '0.4.0', 'documented_modules_and_superuser_setup');
    audit('system_update_applied','system',null,['version'=>'0.4.0']);
}

function setup_apply_v041(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.4.1_roles_dashboards_uploads.sql');
    setup_seed_permissions($pdo);
    foreach ([private_system_upload_directory(), backup_directory()] as $directory) {
        if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
            throw new RuntimeException('Unable to create ' . $directory);
        }
    }
    setting_set('system', 'application_version', '0.4.1');
    setup_record_history($pdo, '0.4.1', 'role_dashboards_inventory_backup_uploads');
    audit('system_update_applied','system',null,['version'=>'0.4.1']);
}

function setup_apply_v042(PDO $pdo): void
{
    $routes = require DENTAL_ROOT . '/config/routes.php';
    $removed = [];
    $failed = [];

    foreach (array_keys($routes) as $route) {
        $legacy = DENTAL_ROOT . '/' . $route . '.php';
        $target = $routes[$route] ?? null;
        if (!is_file((string) $target) || !is_file($legacy)) continue;
        if (@unlink($legacy)) $removed[] = basename($legacy);
        else $failed[] = basename($legacy);
    }

    foreach (['APPLY_UPDATE.txt','RELEASE_NOTES_v0.4.0.md','RELEASE_NOTES_v0.4.1.md'] as $legacyDoc) {
        $legacy = DENTAL_ROOT . '/' . $legacyDoc;
        if (is_file($legacy)) {
            if (@unlink($legacy)) $removed[] = $legacyDoc;
            else $failed[] = $legacyDoc;
        }
    }

    setting_set('system', 'application_version', '0.4.2');
    setting_set('system', 'grouped_structure_applied', date(DATE_ATOM));
    setup_record_history($pdo, '0.4.2', 'grouped_application_structure');
    audit('system_update_applied','system',null,[
        'version'=>'0.4.2',
        'root_files_removed'=>$removed,
        'root_files_not_removed'=>$failed,
    ]);

    if ($failed) {
        flash('warning', 'The grouped structure is active, but these old root files could not be removed because of file permissions: ' . implode(', ', $failed) . '. They are bypassed by the central router and may be deleted manually.');
    }
}

function setup_apply_v043(PDO $pdo): void
{
    setting_set('system', 'application_version', '0.4.3');
    setup_record_history($pdo, '0.4.3', 'task_role_switcher_and_install_state_repair');
    audit('system_update_applied', 'system', null, [
        'version' => '0.4.3',
        'features' => ['task_role_switcher', 'installation_state_repair'],
    ]);
}

function setup_apply_v050(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.0_doctor_workflow_smtp.sql');

    $encounters = table('clinical_encounters');
    setup_add_column($pdo, $encounters, 'queue_entry_id', 'BIGINT UNSIGNED NULL AFTER `appointment_id`');
    setup_add_column($pdo, $encounters, 'autosaved_at', 'DATETIME NULL AFTER `updated_at`');
    setup_add_column($pdo, $encounters, 'completion_check_json', 'JSON NULL AFTER `autosaved_at`');
    if (!setup_index_exists($pdo, $encounters, 'idx_encounter_queue')) {
        $pdo->exec('ALTER TABLE `' . $encounters . '` ADD KEY `idx_encounter_queue` (`queue_entry_id`)');
    }

    $outbox = table('email_outbox');
    setup_add_column($pdo, $outbox, 'last_attempt_at', 'DATETIME NULL AFTER `available_at`');

    // Create a starter profile and routine privileges for every existing dentist.
    $pdo->exec(
        'INSERT INTO `' . table('dentist_profiles') . '` (user_id,display_name,primary_branch_id,created_at) '
        . 'SELECT u.id,u.name,u.branch_id,NOW() FROM `' . table('users') . '` u '
        . 'JOIN `' . table('roles') . '` r ON r.id=u.role_id AND r.code="dentist" '
        . 'LEFT JOIN `' . table('dentist_profiles') . '` dp ON dp.user_id=u.id WHERE dp.id IS NULL'
    );
    $pdo->exec(
        'INSERT INTO `' . table('dentist_clinical_privileges') . '` (dentist_user_id,privilege_id,status,effective_from,approved_by,created_at) '
        . 'SELECT u.id,p.id,"granted",CURDATE(),' . (int) (current_user()['id'] ?? 0) . ',NOW() '
        . 'FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id AND r.code="dentist" '
        . 'JOIN `' . table('clinical_privileges') . '` p ON p.code IN ("consultation","diagnosis","restoration","oral_prophylaxis","simple_extraction","prescription","clinical_note_finalization") '
        . 'ON DUPLICATE KEY UPDATE status=VALUES(status)'
    );

    setting_set('system', 'application_version', '0.5.0');
    setup_record_history($pdo, '0.5.0', 'doctor_profile_clinical_day_smtp_repair');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.0',
        'features' => ['doctor_profiles','clinical_privileges','my_clinical_day','unified_encounter','smtp_diagnostics'],
    ]);
}

function setup_apply_v051(PDO $pdo): void
{
    setting_set('system', 'application_version', '0.5.1');
    setup_record_history($pdo, '0.5.1', 'task_workspace_role_scope_hardening');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.1',
        'features' => ['task_page_guards','task_action_guards','role_scoped_worklists','role_scoped_reports','task_workflow_guidance'],
    ]);
}

function setup_apply_v052(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.2_data_import_reset.sql');
    $users = table('users');
    $patients = table('patients');
    $requests = table('appointment_requests');
    setup_add_column($pdo, $users, 'employee_no', 'VARCHAR(30) NULL AFTER `role_id`');
    setup_add_column($pdo, $users, 'must_change_password', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `last_login_at`');
    setup_add_column($pdo, $users, 'is_demo', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `must_change_password`');
    setup_add_column($pdo, $patients, 'is_demo', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `status`');
    setup_add_column($pdo, $requests, 'is_demo', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `submitted_ip`');
    if (!setup_index_exists($pdo, $users, 'uq_users_employee_no')) {
        $pdo->exec('ALTER TABLE `' . $users . '` ADD UNIQUE KEY `uq_users_employee_no` (`employee_no`)');
    }
    if (!setup_index_exists($pdo, $patients, 'idx_patients_demo')) {
        $pdo->exec('ALTER TABLE `' . $patients . '` ADD KEY `idx_patients_demo` (`is_demo`,`created_at`)');
    }
    if (!setup_index_exists($pdo, $requests, 'idx_requests_demo')) {
        $pdo->exec('ALTER TABLE `' . $requests . '` ADD KEY `idx_requests_demo` (`is_demo`,`created_at`)');
    }
    setting_set('system', 'application_version', '0.5.2');
    setup_record_history($pdo, '0.5.2', 'data_import_demo_reset_tools');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.2',
        'features' => ['xlsx_csv_zip_import','import_preview_validation','demo_record_registry','automatic_pre_reset_backup','demo_activity_factory_reset'],
    ]);
}

function setup_apply_v053(PDO $pdo): void
{
    $backupTable = table('backup_logs');
    setup_add_column($pdo, $backupTable, 'backup_type', "VARCHAR(30) NOT NULL DEFAULT 'database' AFTER `status`");
    setup_add_column($pdo, $backupTable, 'checksum_sha256', 'CHAR(64) NULL AFTER `backup_type`');
    setup_add_column($pdo, $backupTable, 'file_count', 'INT UNSIGNED NOT NULL DEFAULT 0 AFTER `checksum_sha256`');
    setup_add_column($pdo, $backupTable, 'is_encrypted', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `file_count`');
    setup_add_column($pdo, $backupTable, 'verified_at', 'DATETIME NULL AFTER `is_encrypted`');
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.3_full_backup_printing.sql');
    setting_set('system', 'application_version', '0.5.3');
    setup_record_history($pdo, '0.5.3', 'full_system_backup_and_records_printing');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.3',
        'features' => ['complete_backup_package','backup_manifest_checksums','backup_verification','records_print_center','print_audit_log'],
    ]);
}

function setup_apply_v054(PDO $pdo): void
{
    $appointments = table('appointments');
    $queue = table('queue_entries');
    $encounters = table('clinical_encounters');
    $followUps = table('follow_ups');

    setup_add_column($pdo, $appointments, 'appointment_origin', "VARCHAR(40) NOT NULL DEFAULT 'manual' AFTER `status`");
    setup_add_column($pdo, $appointments, 'source_request_id', 'BIGINT UNSIGNED NULL AFTER `appointment_origin`');
    setup_add_column($pdo, $appointments, 'follow_up_id', 'BIGINT UNSIGNED NULL AFTER `source_request_id`');
    setup_add_column($pdo, $appointments, 'reason_for_visit', 'TEXT NULL AFTER `follow_up_id`');
    setup_add_column($pdo, $appointments, 'confirmation_status', "VARCHAR(40) NOT NULL DEFAULT 'confirmed' AFTER `reason_for_visit`");
    if (!setup_index_exists($pdo, $appointments, 'idx_appt_origin')) {
        $pdo->exec('ALTER TABLE `' . $appointments . '` ADD KEY `idx_appt_origin` (`appointment_origin`,`start_at`)');
    }
    if (!setup_index_exists($pdo, $appointments, 'idx_appt_followup')) {
        $pdo->exec('ALTER TABLE `' . $appointments . '` ADD KEY `idx_appt_followup` (`follow_up_id`)');
    }

    setup_add_column($pdo, $queue, 'intake_completed_at', 'DATETIME NULL AFTER `checked_in_at`');

    setup_add_column($pdo, $encounters, 'visit_type', "VARCHAR(40) NOT NULL DEFAULT 'scheduled' AFTER `status`");
    setup_add_column($pdo, $encounters, 'history_present_condition', 'TEXT NULL AFTER `chief_complaint`');
    setup_add_column($pdo, $encounters, 'follow_up_purpose', 'VARCHAR(190) NULL AFTER `prescription`');
    setup_add_column($pdo, $encounters, 'follow_up_urgency', 'VARCHAR(30) NULL AFTER `follow_up_purpose`');
    setup_add_column($pdo, $encounters, 'disposition', 'VARCHAR(60) NULL AFTER `follow_up_urgency`');

    setup_add_column($pdo, $followUps, 'branch_id', 'BIGINT UNSIGNED NULL AFTER `patient_id`');
    setup_add_column($pdo, $followUps, 'service_id', 'BIGINT UNSIGNED NULL AFTER `branch_id`');
    setup_add_column($pdo, $followUps, 'preferred_time', 'TIME NULL AFTER `recommended_date`');
    setup_add_column($pdo, $followUps, 'duration_minutes', 'SMALLINT UNSIGNED NOT NULL DEFAULT 30 AFTER `preferred_time`');
    setup_add_column($pdo, $followUps, 'scheduled_at', 'DATETIME NULL AFTER `appointment_id`');
    setup_add_column($pdo, $followUps, 'scheduled_by', 'BIGINT UNSIGNED NULL AFTER `scheduled_at`');
    setup_add_column($pdo, $followUps, 'completion_notes', 'TEXT NULL AFTER `scheduled_by`');
    if (!setup_index_exists($pdo, $followUps, 'idx_followup_branch')) {
        $pdo->exec('ALTER TABLE `' . $followUps . '` ADD KEY `idx_followup_branch` (`branch_id`,`recommended_date`)');
    }

    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.4_complete_clinical_workflow.sql');

    $pdo->exec('UPDATE `' . $appointments . '` SET appointment_origin=COALESCE(NULLIF(appointment_origin,""),"manual"), confirmation_status=COALESCE(NULLIF(confirmation_status,""),status,"confirmed")');
    $pdo->exec('UPDATE `' . $encounters . '` e LEFT JOIN `' . $queue . '` q ON q.id=e.queue_entry_id SET e.visit_type=CASE WHEN q.source_type="walk_in" THEN "walk_in" WHEN e.appointment_id IS NOT NULL THEN "scheduled" ELSE COALESCE(NULLIF(e.visit_type,""),"scheduled") END');

    setting_set('system', 'application_version', '0.5.4');
    setup_record_history($pdo, '0.5.4', 'complete_clinical_sequence_followup_scheduler');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.4',
        'features' => ['patient_intake','visit_workflow','structured_assessment','structured_diagnosis','appointment_lifecycle','walk_in_sequence','followup_scheduler','cashier_completion'],
    ]);
}

function setup_apply_v055(PDO $pdo): void
{
    setting_set('system', 'application_version', '0.5.5');
    setup_record_history($pdo, '0.5.5', 'tabbed_patient_workspace_compact_navigation');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.5',
        'features' => ['compact_primary_navigation','contextual_module_tabs','tabbed_patient_profile','role_default_patient_tabs','visit_specific_clinical_tabs'],
    ]);
}

function setup_apply_v056(PDO $pdo): void
{
    $appointments=table('appointments');
    $requests=table('appointment_requests');
    setup_add_column($pdo,$appointments,'schedule_bypass','TINYINT(1) NOT NULL DEFAULT 0 AFTER `confirmation_status`');
    setup_add_column($pdo,$appointments,'schedule_bypass_reason','VARCHAR(500) NULL AFTER `schedule_bypass`');
    setup_add_column($pdo,$appointments,'schedule_bypass_by','BIGINT UNSIGNED NULL AFTER `schedule_bypass_reason`');
    setup_add_column($pdo,$appointments,'schedule_bypass_at','DATETIME NULL AFTER `schedule_bypass_by`');
    setup_add_column($pdo,$requests,'reschedule_required','TINYINT(1) NOT NULL DEFAULT 0 AFTER `staff_notes`');
    setup_add_column($pdo,$requests,'reschedule_note','TEXT NULL AFTER `reschedule_required`');
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.6_preferred_schedule_bypass.sql');
    setting_set('system','application_version','0.5.6');
    setup_record_history($pdo,'0.5.6','preferred_schedule_filter_bypass_print_route');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.6',
        'features'=>['preferred_schedule_doctor_filter','reschedule_note','audited_schedule_bypass','print_record_route_repair'],
    ]);
}


function setup_apply_v0561(PDO $pdo): void
{
    setting_set('system','application_version','0.5.6.1');
    setup_record_history($pdo,'0.5.6.1','automatic_schedule_filter_reschedule_contact_patient_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.6.1',
        'features'=>['automatic_schedule_filter','conditional_reschedule_note','appointment_contact_attempts','patient_page_compatibility_repair'],
    ]);
}

function setup_apply_v0562(PDO $pdo): void
{
    $patients = table('patients');
    if (setup_table_exists($pdo, $patients)) {
        setup_add_column($pdo, $patients, 'status', "VARCHAR(40) NOT NULL DEFAULT 'active' AFTER `medical_alerts`");
        setup_add_column($pdo, $patients, 'created_at', 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `created_by`');
        setup_add_column($pdo, $patients, 'updated_at', 'DATETIME NULL AFTER `created_at`');
        setup_add_column($pdo, $patients, 'deleted_at', 'DATETIME NULL AFTER `updated_at`');
    }
    setting_set('system','application_version','0.5.6.2');
    setup_record_history($pdo,'0.5.6.2','schema_aware_patient_scope_and_list_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.6.2',
        'features'=>['schema_aware_patient_scope','safe_patient_list_fallback','patient_core_column_repair','restricted_role_fail_closed'],
    ]);
}

function setup_apply_v057(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7');
    setup_record_history($pdo,'0.5.7','navigation_workflow_visual_refinement');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7',
        'features'=>['icon_navigation','collapsible_sidebar','limited_context_tabs','administration_center','global_search','quick_create','mobile_bottom_navigation','visual_polish'],
    ]);
}

function setup_apply_v0571(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.1');
    setup_record_history($pdo,'0.5.7.1','patient_workspace_guardian_and_route_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.1',
        'features'=>['patient_workspace_prescription_query_repair','public_request_guardian_schema_repair','root_grouped_route_rewrite','installer_scope_documentation_refresh'],
    ]);
}

function setup_apply_v0572(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.2');
    setup_record_history($pdo,'0.5.7.2','automatic_filters_weekly_schedule_and_print_reason_workflow');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.2',
        'features'=>['automatic_patient_filters','searchable_patient_selectors','weekly_appointment_calendar','weekly_doctor_availability_tiles','required_print_reason_page','print_reason_on_document'],
    ]);
}

function setup_apply_v0573(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.3');
    setup_record_history($pdo,'0.5.7.3','dentist_schedule_builder_and_task_view_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.3',
        'features'=>['dentist_task_view_schedule_access','unified_schedule_builder','drag_time_selection','copy_day_schedule','draft_and_publish_schedule','calendar_exceptions','appointment_overlay'],
    ]);
}


function setup_apply_v0574(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.4');
    setup_record_history($pdo,'0.5.7.4','patient_facing_queue_screen');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.4',
        'features'=>['patient_queue_screen','privacy_safe_ticket_display','tokenized_kiosk_link','live_queue_refresh','fullscreen_mode','optional_call_sound','cashier_waiting_panel'],
    ]);
}

function setup_apply_v0575(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.5');
    setup_record_history($pdo,'0.5.7.5','expected_patient_day_list');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.5',
        'features'=>['expected_patient_day_list','arrival_state_summary','automatic_expected_patient_search','expected_patient_filters','quick_appointment_checkin','patient_and_queue_shortcuts'],
    ]);
}

function setup_apply_v0576(PDO $pdo): void
{
    $patients = table('patients');
    setup_add_column($pdo,$patients,'primary_dentist_user_id','BIGINT UNSIGNED NULL AFTER `branch_id`');
    setup_add_column($pdo,$patients,'primary_dentist_assigned_at','DATETIME NULL AFTER `primary_dentist_user_id`');
    setup_add_column($pdo,$patients,'primary_dentist_change_reason','VARCHAR(500) NULL AFTER `primary_dentist_assigned_at`');
    setup_add_column($pdo,$patients,'primary_dentist_changed_by','BIGINT UNSIGNED NULL AFTER `primary_dentist_change_reason`');
    if (!setup_index_exists($pdo,$patients,'idx_patient_primary_dentist')) {
        $pdo->exec('ALTER TABLE `' . $patients . '` ADD KEY `idx_patient_primary_dentist` (`primary_dentist_user_id`)');
    }
    $pdo->exec('UPDATE `' . $patients . '` p SET p.primary_dentist_user_id=(SELECT a.dentist_user_id FROM `' . table('appointments') . '` a WHERE a.patient_id=p.id AND a.dentist_user_id IS NOT NULL AND a.status NOT IN ("cancelled","no_show") ORDER BY CASE WHEN a.status="completed" THEN 0 ELSE 1 END,a.start_at DESC,a.id DESC LIMIT 1),p.primary_dentist_assigned_at=NOW(),p.primary_dentist_change_reason="Backfilled from appointment history" WHERE p.primary_dentist_user_id IS NULL AND EXISTS(SELECT 1 FROM `' . table('appointments') . '` ax WHERE ax.patient_id=p.id AND ax.dentist_user_id IS NOT NULL AND ax.status NOT IN ("cancelled","no_show"))');
    setting_set('system','application_version','0.5.7.6');
    setup_record_history($pdo,'0.5.7.6','daily_task_dashboard_calendar_and_care_continuity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.6',
        'features'=>['access_safe_task_links','daily_priority_dashboard','doctor_week_month_calendar_export','appointment_calendar_invites','primary_dentist_continuity','followup_doctor_lock'],
    ]);
}

function setup_apply_v0577(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.7');
    setup_record_history($pdo,'0.5.7.7','patient_queue_screen_render_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.7',
        'features'=>['patient_queue_screen_layout_dependency','waiting_room_screen_route_repair','queue_display_render_regression_check'],
    ]);
}

function setup_apply_v0578(PDO $pdo): void
{
    setting_set('system','application_version','0.5.7.8');
    setup_record_history($pdo,'0.5.7.8','scalable_lists_and_compact_queue_flow');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.8',
        'features'=>['automatic_large_table_paging','list_search_and_page_size','sticky_table_headers','top_and_bottom_navigation','expected_patient_paging','independent_queue_column_scrolling','compact_queue_monitor_actions'],
    ]);
}


function setup_apply_v0579(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.7.9_doctor_chair_and_future_booking.sql');
    $appointments = table('appointments');
    if (!setup_index_exists($pdo,$appointments,'idx_appt_dentist_user')) {
        $pdo->exec('ALTER TABLE `' . $appointments . '` ADD KEY `idx_appt_dentist_user` (`dentist_user_id`,`start_at`,`end_at`)');
    }

    // Backfill only unambiguous existing chair names. Duplicate chair names are
    // deliberately left unassigned so an administrator can resolve the physical resource.
    $rows = $pdo->query(
        'SELECT u.id dentist_user_id,COALESCE(dp.primary_branch_id,u.branch_id) branch_id,TRIM(COALESCE(dp.default_chair_name,"")) chair_name '
        . 'FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id AND r.code="dentist" '
        . 'LEFT JOIN `' . table('dentist_profiles') . '` dp ON dp.user_id=u.id WHERE u.status="active"'
    )->fetchAll();
    $insert = $pdo->prepare(
        'INSERT IGNORE INTO `' . table('doctor_chair_assignments') . '` (dentist_user_id,branch_id,chair_name,is_active,created_by,created_at) VALUES (?,?,?,1,?,NOW())'
    );
    foreach ($rows as $row) {
        $doctorId = (int) ($row['dentist_user_id'] ?? 0);
        $branchId = (int) ($row['branch_id'] ?? 0);
        $chair = normalize_chair_name((string) ($row['chair_name'] ?? ''));
        if ($doctorId > 0 && $branchId > 0 && $chair !== '') {
            $insert->execute([$doctorId,$branchId,$chair,(int) (current_user()['id'] ?? 0) ?: null]);
        }
    }
    $pdo->exec(
        'UPDATE `' . $appointments . '` a JOIN `' . table('doctor_chair_assignments') . '` dca '
        . 'ON dca.dentist_user_id=a.dentist_user_id AND dca.branch_id=a.branch_id AND dca.is_active=1 '
        . 'SET a.chair_name=dca.chair_name,a.updated_at=NOW() '
        . 'WHERE a.start_at>=CURDATE() AND a.status NOT IN ("cancelled","no_show","completed")'
    );
    setting_set('system','application_version','0.5.7.9');
    setup_record_history($pdo,'0.5.7.9','doctor_bound_chairs_available_dates_and_future_appointments');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.7.9',
        'features'=>['branch_specific_doctor_chair_binding','hard_resource_conflict_prevention','patient_available_date_tiles','chair_aware_slot_generation','doctor_future_appointment_view','future_calendar_visibility'],
    ]);
}

function setup_apply_v0580(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.8.0_doctor_integrity_admin_coverage_inhouse.sql');
    $appointments=table('appointments');$queue=table('queue_entries');$encounters=table('clinical_encounters');
    if (!setup_index_exists($pdo,$appointments,'idx_appt_service_provider')) {
        $pdo->exec('ALTER TABLE `' . $appointments . '` ADD KEY `idx_appt_service_provider` (`service_id`,`provider_type`,`start_at`)');
    }
    if (!setup_index_exists($pdo,$queue,'idx_queue_service_provider')) {
        $pdo->exec('ALTER TABLE `' . $queue . '` ADD KEY `idx_queue_service_provider` (`service_id`,`provider_type`,`checked_in_at`)');
    }
    if (!setup_index_exists($pdo,$encounters,'uq_clinical_queue_entry')) {
        $pdo->exec('ALTER TABLE `' . $encounters . '` ADD UNIQUE KEY `uq_clinical_queue_entry` (`queue_entry_id`)');
    }
    setting_set('system','application_version','0.5.8.0');
    setup_record_history($pdo,'0.5.8.0','doctor_integrity_admin_criticals_and_inhouse_services');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.0',
        'features'=>['strict_queue_state_machine','single_active_dentist_treatment','transactional_clinical_saves','signed_clinical_snapshots','signed_addenda','record_scope_enforcement','admin_read_only_clinical_oversight','multi_item_prescriptions','schedule_change_impact_validation','admin_critical_operations','doctor_coverage_gaps','inhouse_service_capacity','automatic_patient_reminders','staff_privilege_boundary'],
    ]);
}

function setup_apply_v0581(PDO $pdo): void
{
    setting_set('system','application_version','0.5.8.1');
    setup_record_history($pdo,'0.5.8.1','responsive_weekly_appointment_calendar');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.1',
        'features'=>['responsive_week_calendar','day_carousel_navigation','compact_appointment_tiles','inline_tile_details','role_safe_tile_actions','visible_conflict_labels'],
    ]);
}

function setup_apply_v0582(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.8.2_scalable_reception_waitlist.sql');
    $indexes = [
        [table('patients'),'idx_patient_directory','(`deleted_at`,`status`,`last_name`,`first_name`,`id`)'],
        [table('appointments'),'idx_appointment_branch_start_status','(`branch_id`,`start_at`,`status`)'],
        [table('appointments'),'idx_appointment_dentist_start_status','(`dentist_user_id`,`start_at`,`status`)'],
        [table('follow_ups'),'idx_followup_booking_work','(`status`,`appointment_id`,`recommended_date`)'],
    ];
    foreach($indexes as [$tableName,$indexName,$definition]){
        if(setup_table_exists($pdo,$tableName)&&!setup_index_exists($pdo,$tableName,$indexName)){
            $pdo->exec('ALTER TABLE `' . $tableName . '` ADD KEY `' . $indexName . '` ' . $definition);
        }
    }
    setting_set('system','application_version','0.5.8.2');
    setup_record_history($pdo,'0.5.8.2','scalable_reception_workflow_and_waitlist');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.2',
        'features'=>['server_side_patient_paging','server_side_appointment_paging','server_side_request_paging','server_side_followup_paging','server_side_communication_paging','remote_patient_search','reception_priority_dashboard','appointment_waitlist','cancelled_slot_matching','patient_care_query_indexes'],
    ]);
}

function setup_v0582_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo, table('appointment_waitlist'))
        && setup_index_exists($pdo, table('patients'), 'idx_patient_directory')
        && setup_index_exists($pdo, table('appointments'), 'idx_appointment_branch_start_status')
        && setup_index_exists($pdo, table('appointments'), 'idx_appointment_dentist_start_status')
        && setup_index_exists($pdo, table('follow_ups'), 'idx_followup_booking_work');
}


function setup_apply_v0583(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.8.3_email_templates_staff_invites_chairs.sql');

    $assignments = table('doctor_chair_assignments');
    $chairs = table('clinic_chairs');
    setup_add_column($pdo, $assignments, 'chair_id', 'BIGINT UNSIGNED NULL AFTER `branch_id`');
    if (!setup_index_exists($pdo, $assignments, 'idx_doctor_chair_resource')) {
        $pdo->exec('ALTER TABLE `' . $assignments . '` ADD KEY `idx_doctor_chair_resource` (`chair_id`)');
    }

    // Convert existing free-text dentist-chair bindings into formal resources.
    $rows = $pdo->query(
        'SELECT MIN(id) source_id,branch_id,chair_name FROM `' . $assignments . '` '
        . 'WHERE chair_name IS NOT NULL AND TRIM(chair_name)<>"" GROUP BY branch_id,chair_name'
    )->fetchAll();
    $insert = $pdo->prepare(
        'INSERT INTO `' . $chairs . '` (branch_id,chair_code,chair_name,usage_mode,status,created_by,created_at) '
        . 'VALUES (?,?,?,"dentist_bound","available",?,NOW()) '
        . 'ON DUPLICATE KEY UPDATE chair_name=VALUES(chair_name),updated_at=NOW()'
    );
    $lookup = $pdo->prepare('SELECT id FROM `' . $chairs . '` WHERE branch_id=? AND chair_name=? LIMIT 1');
    $bind = $pdo->prepare('UPDATE `' . $assignments . '` SET chair_id=?,updated_at=NOW() WHERE branch_id=? AND chair_name=?');
    foreach ($rows as $row) {
        $branchId = (int) $row['branch_id'];
        $chairName = normalize_chair_name((string) $row['chair_name']);
        if ($branchId < 1 || $chairName === '') continue;
        $sourceId = max(1, (int) $row['source_id']);
        $code = 'LEGACY-' . $sourceId;
        $insert->execute([$branchId,$code,$chairName,(int) (current_user()['id'] ?? 0) ?: null]);
        $lookup->execute([$branchId,$chairName]);
        $chairId = (int) ($lookup->fetchColumn() ?: 0);
        if ($chairId > 0) $bind->execute([$chairId,$branchId,$chairName]);
    }

    $constraint = (string) app_config('database.prefix', 'dent_') . 'fk_doctor_chair_resource';
    if (!setup_constraint_exists($pdo, $assignments, $constraint)) {
        try {
            $pdo->exec('ALTER TABLE `' . $assignments . '` ADD CONSTRAINT `' . $constraint . '` FOREIGN KEY (`chair_id`) REFERENCES `' . $chairs . '` (`id`) ON DELETE SET NULL');
        } catch (Throwable $constraintError) {
            // The resource model remains usable even on hosts that restrict
            // adding foreign keys to an existing table. Readiness is based on
            // the table, column, and index so the update can still complete.
            error_log('Chair resource foreign key was not added: ' . $constraintError->getMessage());
        }
    }

    setting_set('system','application_version','0.5.8.3');
    setup_record_history($pdo,'0.5.8.3','email_templates_staff_invites_and_chair_resources');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.3',
        'features'=>['formal_chair_resource_management','secure_staff_account_invites','staff_email_delivery_tracking','email_template_manager','single_message_retry','template_based_appointment_and_reminder_email'],
    ]);
}

function setup_v0583_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo, table('email_templates'))
        && setup_table_exists($pdo, table('staff_account_invites'))
        && setup_table_exists($pdo, table('clinic_chairs'))
        && setup_column_exists($pdo, table('doctor_chair_assignments'), 'chair_id')
        && setup_index_exists($pdo, table('doctor_chair_assignments'), 'idx_doctor_chair_resource');
}

function setup_apply_v0584(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.8.4_inhouse_staff_shared_chairs.sql');

    $appointments = table('appointments');
    $queue = table('queue_entries');
    setup_add_column($pdo, $appointments, 'chair_id', 'BIGINT UNSIGNED NULL AFTER `end_at`');
    setup_add_column($pdo, $queue, 'chair_id', 'BIGINT UNSIGNED NULL AFTER `inhouse_provider_user_id`');
    if (setup_column_exists($pdo,$appointments,'dentist_name') && !setup_column_nullable($pdo,$appointments,'dentist_name')) {
        $pdo->exec('ALTER TABLE `' . $appointments . '` MODIFY `dentist_name` VARCHAR(150) NULL');
    }

    foreach ([
        [$appointments,'idx_appt_chair_resource','(`chair_id`,`start_at`,`end_at`)'],
        [$appointments,'idx_appt_inhouse_provider','(`inhouse_provider_user_id`,`start_at`,`end_at`)'],
        [$queue,'idx_queue_inhouse_provider','(`inhouse_provider_user_id`,`checked_in_at`,`status`)'],
        [$queue,'idx_queue_chair_resource','(`chair_id`,`checked_in_at`,`status`)'],
    ] as [$tableName,$indexName,$definition]) {
        if (setup_table_exists($pdo,$tableName) && !setup_index_exists($pdo,$tableName,$indexName)) {
            $pdo->exec('ALTER TABLE `' . $tableName . '` ADD KEY `' . $indexName . '` ' . $definition);
        }
    }

    if (setup_table_exists($pdo, table('clinic_chairs'))) {
        $pdo->exec(
            'UPDATE `' . $appointments . '` a JOIN `' . table('clinic_chairs') . '` c '
            . 'ON c.branch_id=a.branch_id AND LOWER(TRIM(c.chair_name))=LOWER(TRIM(a.chair_name)) '
            . 'SET a.chair_id=c.id WHERE a.chair_id IS NULL AND a.chair_name IS NOT NULL AND TRIM(a.chair_name)<>""'
        );
        $pdo->exec(
            'UPDATE `' . $queue . '` q JOIN `' . table('clinic_chairs') . '` c '
            . 'ON c.branch_id=q.branch_id AND LOWER(TRIM(c.chair_name))=LOWER(TRIM(q.chair_name)) '
            . 'SET q.chair_id=c.id WHERE q.chair_id IS NULL AND q.chair_name IS NOT NULL AND TRIM(q.chair_name)<>""'
        );
    }

    setting_set('system','application_version','0.5.8.4');
    setup_record_history($pdo,'0.5.8.4','inhouse_staff_and_shared_chair_resources');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.4',
        'features'=>['branch_service_staff_assignment','automatic_inhouse_provider_assignment','shared_inhouse_chair_assignment','provider_and_chair_conflict_enforcement','queue_resource_continuity'],
    ]);
}

function setup_v0584_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo, table('inhouse_service_staff'))
        && setup_column_exists($pdo, table('appointments'), 'chair_id')
        && setup_column_nullable($pdo, table('appointments'), 'dentist_name')
        && setup_column_exists($pdo, table('queue_entries'), 'chair_id')
        && setup_index_exists($pdo, table('appointments'), 'idx_appt_chair_resource')
        && setup_index_exists($pdo, table('appointments'), 'idx_appt_inhouse_provider')
        && setup_index_exists($pdo, table('queue_entries'), 'idx_queue_inhouse_provider')
        && setup_index_exists($pdo, table('queue_entries'), 'idx_queue_chair_resource');
}

function setup_apply_v0585(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.8.5_email_delivery_completion.sql');
    $appointments=table('appointments');
    $requests=table('appointment_requests');
    $portalInvites=table('patient_portal_invites');
    $payments=table('payments');
    $communications=table('communications');

    setup_add_column($pdo,$appointments,'last_email_outbox_id','BIGINT UNSIGNED NULL AFTER `notes`');
    setup_add_column($pdo,$appointments,'last_email_type','VARCHAR(60) NULL AFTER `last_email_outbox_id`');
    setup_add_column($pdo,$appointments,'last_email_at','DATETIME NULL AFTER `last_email_type`');
    setup_add_column($pdo,$requests,'receipt_email_outbox_id','BIGINT UNSIGNED NULL AFTER `appointment_id`');
    setup_add_column($pdo,$portalInvites,'email_outbox_id','BIGINT UNSIGNED NULL AFTER `used_at`');
    setup_add_column($pdo,$portalInvites,'last_sent_at','DATETIME NULL AFTER `email_outbox_id`');
    setup_add_column($pdo,$portalInvites,'updated_at','DATETIME NULL AFTER `created_at`');
    setup_add_column($pdo,$payments,'receipt_email_outbox_id','BIGINT UNSIGNED NULL AFTER `reference_no`');
    setup_add_column($pdo,$communications,'email_outbox_id','BIGINT UNSIGNED NULL AFTER `appointment_id`');

    foreach([
        [$appointments,'idx_appointment_last_email','(`last_email_outbox_id`)'],
        [$requests,'idx_request_receipt_email','(`receipt_email_outbox_id`)'],
        [$portalInvites,'idx_portal_invite_email','(`email_outbox_id`)'],
        [$payments,'idx_payment_receipt_email','(`receipt_email_outbox_id`)'],
        [$communications,'idx_comm_email_outbox','(`email_outbox_id`)'],
    ] as [$tableName,$indexName,$definition])if(setup_table_exists($pdo,$tableName)&&!setup_index_exists($pdo,$tableName,$indexName))$pdo->exec('ALTER TABLE `' . $tableName . '` ADD KEY `' . $indexName . '` ' . $definition);

    if(email_template_schema_ready()){
        $pdo->exec('UPDATE `' . table('email_templates') . '` SET html_template=' . $pdo->quote('<h2>Your dental follow-up is due</h2><p>Hello {{recipient_name}},</p><p>Your clinic care team recommended a follow-up for <strong>{{due_date}}</strong>.</p><p><strong>Purpose:</strong> {{purpose}}<br><strong>{{provider_label}}:</strong> {{provider_name}}</p><p>{{instructions}}</p><p><a href="{{booking_url}}">Request your follow-up appointment</a></p>') . ',text_template=' . $pdo->quote('Your dental follow-up is due on {{due_date}}. {{provider_label}}: {{provider_name}}. Request an appointment: {{booking_url}}') . ',allowed_variables_json=' . $pdo->quote('["clinic_name","recipient_name","due_date","purpose","provider_label","provider_name","instructions","booking_url","support_email"]') . ' WHERE template_key="followup_reminder" AND updated_at IS NULL');
        $pdo->exec('UPDATE `' . table('email_templates') . '` SET html_template=' . $pdo->quote('<h2>Dental recall reminder</h2><p>Hello {{recipient_name}},</p><p>Your <strong>{{recall_type}}</strong> recall is due on <strong>{{due_date}}</strong>.</p><p><a href="{{booking_url}}">Request an appointment</a></p>') . ' WHERE template_key="recall_reminder" AND updated_at IS NULL');
    }
    setting_set('email_queue','automatic_enabled','1');
    setting_set('email_queue','automatic_interval_minutes','2');
    setting_set('email_queue','automatic_batch_size','10');
    setting_set('email_queue','stale_processing_minutes','15');
    setting_set('system','application_version','0.5.8.5');
    setup_record_history($pdo,'0.5.8.5','email_delivery_and_reminder_completion');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.5',
        'features'=>['automatic_email_queue_processing','concurrency_safe_queue_claims','stale_delivery_recovery','entity_delivery_tracking','targeted_resend_controls','email_health_test','provider_aware_reminders','payment_receipt_email'],
    ]);
}

function setup_v0585_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('email_queue_runs'))
        && setup_column_exists($pdo,table('email_queue_runs'),'trigger_type')
        && setup_column_exists($pdo,table('appointments'),'last_email_outbox_id')
        && setup_column_exists($pdo,table('appointments'),'last_email_type')
        && setup_column_exists($pdo,table('appointments'),'last_email_at')
        && setup_column_exists($pdo,table('appointment_requests'),'receipt_email_outbox_id')
        && setup_column_exists($pdo,table('patient_portal_invites'),'email_outbox_id')
        && setup_column_exists($pdo,table('patient_portal_invites'),'last_sent_at')
        && setup_column_exists($pdo,table('payments'),'receipt_email_outbox_id')
        && setup_column_exists($pdo,table('communications'),'email_outbox_id')
        && setup_index_exists($pdo,table('appointments'),'idx_appointment_last_email')
        && setup_index_exists($pdo,table('patient_portal_invites'),'idx_portal_invite_email')
        && setup_index_exists($pdo,table('communications'),'idx_comm_email_outbox');
}

function setup_apply_v0586(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.8.6_patient_portal_self_service.sql');
    $odontogram=table('odontogram_entries');
    setup_add_column($pdo,$odontogram,'finding_type','VARCHAR(40) NOT NULL DEFAULT "condition" AFTER `condition_code`');
    setup_add_column($pdo,$odontogram,'severity','VARCHAR(30) NULL AFTER `finding_type`');
    setup_add_column($pdo,$odontogram,'diagnosis_id','BIGINT UNSIGNED NULL AFTER `status`');
    setup_add_column($pdo,$odontogram,'treatment_plan_item_id','BIGINT UNSIGNED NULL AFTER `diagnosis_id`');
    setup_add_column($pdo,$odontogram,'supersedes_entry_id','BIGINT UNSIGNED NULL');
    setup_add_column($pdo,$odontogram,'resolved_by','BIGINT UNSIGNED NULL AFTER `recorded_at`');
    setup_add_column($pdo,$odontogram,'resolved_at','DATETIME NULL AFTER `resolved_by`');
    foreach([
        ['idx_odontogram_active','(`patient_id`,`status`,`dentition`,`tooth_number`)'],
        ['idx_odontogram_diagnosis','(`diagnosis_id`)'],
        ['idx_odontogram_plan_item','(`treatment_plan_item_id`)'],
    ] as [$index,$definition]) if(!setup_index_exists($pdo,$odontogram,$index)) $pdo->exec('ALTER TABLE `' . $odontogram . '` ADD KEY `' . $index . '` ' . $definition);

    $items=table('treatment_plan_items');
    setup_add_column($pdo,$items,'diagnosis_id','BIGINT UNSIGNED NULL AFTER `diagnosis`');
    setup_add_column($pdo,$items,'odontogram_entry_id','BIGINT UNSIGNED NULL AFTER `diagnosis_id`');
    setup_add_column($pdo,$items,'tooth_number','VARCHAR(12) NULL AFTER `tooth_region`');
    setup_add_column($pdo,$items,'surfaces','VARCHAR(80) NULL AFTER `tooth_number`');
    setup_add_column($pdo,$items,'procedure_code','VARCHAR(80) NULL AFTER `surfaces`');
    setup_add_column($pdo,$items,'clinical_rationale','TEXT NULL AFTER `status`');
    setup_add_column($pdo,$items,'prognosis','VARCHAR(190) NULL AFTER `clinical_rationale`');
    setup_add_column($pdo,$items,'consent_status','VARCHAR(30) NOT NULL DEFAULT "not_requested" AFTER `prognosis`');
    foreach([
        ['idx_plan_item_finding','(`odontogram_entry_id`)'],
        ['idx_plan_item_diagnosis','(`diagnosis_id`)'],
    ] as [$index,$definition]) if(!setup_index_exists($pdo,$items,$index)) $pdo->exec('ALTER TABLE `' . $items . '` ADD KEY `' . $index . '` ' . $definition);

    $constraints=[
        [$odontogram,(string) app_config('database.prefix','dent_') . 'fk_odon_diagnosis','FOREIGN KEY (`diagnosis_id`) REFERENCES `' . table('diagnoses') . '` (`id`) ON DELETE SET NULL'],
        [$odontogram,(string) app_config('database.prefix','dent_') . 'fk_odon_plan_item','FOREIGN KEY (`treatment_plan_item_id`) REFERENCES `' . $items . '` (`id`) ON DELETE SET NULL'],
        [$items,(string) app_config('database.prefix','dent_') . 'fk_plan_item_diagnosis','FOREIGN KEY (`diagnosis_id`) REFERENCES `' . table('diagnoses') . '` (`id`) ON DELETE SET NULL'],
        [$items,(string) app_config('database.prefix','dent_') . 'fk_plan_item_finding','FOREIGN KEY (`odontogram_entry_id`) REFERENCES `' . $odontogram . '` (`id`) ON DELETE SET NULL'],
    ];
    foreach($constraints as [$constraintTable,$constraintName,$definition]){
        if(setup_constraint_exists($pdo,$constraintTable,$constraintName))continue;
        try{$pdo->exec('ALTER TABLE `' . $constraintTable . '` ADD CONSTRAINT `' . $constraintName . '` ' . $definition);}
        catch(Throwable $constraintError){error_log('Version 0.5.8.6 clinical linkage foreign key was not added: ' . $constraintError->getMessage());}
    }

    setting_set('system','application_version','0.5.8.6');
    setup_record_history($pdo,'0.5.8.6','patient_self_service_bootstrap_and_dental_charting');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.6',
        'features'=>['patient_appointment_confirmation','patient_cancellation','patient_reschedule_requests','portal_followup_booking','medical_history_update_review','patient_request_workspace','licensed_production_bootstrap','isolated_read_only_demo_bootstrap','structured_tooth_findings','surface_charting','dental_chart_reviews','finding_linked_treatment_plans'],
    ]);
}

function setup_apply_v0587(PDO $pdo): void
{
    $directory = DENTAL_ROOT . '/storage/generated-installers';
    if (!is_dir($directory) && !mkdir($directory, 0770, true) && !is_dir($directory)) {
        throw new RuntimeException('Unable to create the protected installer-package repository.');
    }
    $protection = $directory . '/.htaccess';
    if (!is_file($protection)) {
        file_put_contents($protection, "Require all denied\n", LOCK_EX);
        @chmod($protection, 0640);
    }
    setting_set('system','application_version','0.5.8.7');
    setup_record_history($pdo,'0.5.8.7','self_service_installer_builder');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.7',
        'features'=>['in_app_installer_builder','licensed_full_package','locked_demo_package','distribution_mode_lock','package_manifest','package_checksums','sensitive_file_exclusion','secure_installer_repository'],
    ]);
}

function setup_v0587_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/app/Services/InstallerPackageService.php')
        && is_file(DENTAL_ROOT . '/modules/administration/installer_builder.php')
        && is_file(DENTAL_ROOT . '/handlers/download_installer.php')
        && is_file(DENTAL_ROOT . '/.htaccess');
}

function setup_apply_v0588(PDO $pdo): void
{
    setting_set('system','application_version','0.5.8.8');
    setup_record_history($pdo,'0.5.8.8','doctor_treatment_dashboard_and_anatomical_odontogram');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.8',
        'features'=>['doctor_treatment_dashboard','chair_centered_patient_flow','seated_patient_handoff','embedded_odontogram','anatomical_tooth_model','surface_finding_visualization','encounter_linked_charting'],
    ]);
}

function setup_v0588_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/modules/clinical/doctor_treatment.php')
        && is_file(DENTAL_ROOT . '/app/tooth_model.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/odontogram.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/clinical_workspace.php');
}

function setup_apply_v0589(PDO $pdo): void
{
    setting_set('system','application_version','0.5.8.9');
    setup_record_history($pdo,'0.5.8.9','exclusive_chair_and_treatment_transition');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.8.9',
        'features'=>['exclusive_chair_occupancy','start_treatment_transition','doctor_patient_snapshot'],
    ]);
}

function setup_v0589_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/app/queue_flow.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/doctor_treatment.php')
        && is_file(DENTAL_ROOT . '/modules/patient-care/visit_workflow.php');
}

function setup_apply_v0590(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.0');
    setup_record_history($pdo,'0.5.9.0','interactive_dentist_charting_and_profile_images');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.0',
        'features'=>['tooth_click_finding_dialog','assigned_dentist_chart_lock','read_only_chart_notice','doctor_profile_images_in_clinical_workspaces'],
    ]);
}

function setup_v0590_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/public/assets/js/dental-chart.js')
        && is_file(DENTAL_ROOT . '/handlers/doctor_asset.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/doctor_profile.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/odontogram.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/clinical_workspace.php')
        && is_file(DENTAL_ROOT . '/modules/clinical/doctor_treatment.php');
}

function setup_apply_v0591(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.1');
    setup_record_history($pdo,'0.5.9.1','fully_visible_wrapping_context_navigation');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.1',
        'features'=>['fully_visible_context_navigation','automatic_navigation_wrapping','mobile_two_column_navigation'],
    ]);
}

function setup_v0591_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/app/layout.php')
        && is_file(DENTAL_ROOT . '/public/assets/css/app.css');
}

function setup_apply_v0592(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.2');
    setup_record_history($pdo,'0.5.9.2','doctor_completion_imaging_keyboard_and_complete_records');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.2',
        'features'=>[
            'verified_fdi_tooth_labels',
            'chairside_xray_capture_and_upload',
            'keyboard_first_clinical_entry',
            'treatment_plan_execution_handoff',
            'procedure_and_cashier_handoff',
            'complete_medical_and_dental_record_printing',
            'latest_prescription_visibility',
        ],
    ]);
}

function setup_v0592_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/app/clinical_media.php')
        && is_file(DENTAL_ROOT . '/app/tooth_model.php')
        && is_file(DENTAL_ROOT . '/public/assets/js/dental-chart.js')
        && is_file(DENTAL_ROOT . '/modules/clinical/clinical_workspace.php')
        && is_file(DENTAL_ROOT . '/handlers/print_record.php');
}


function setup_apply_v0593(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.3_queue_navigation_cashier_pos.sql');

    $services=table('service_catalog');
    $invoices=table('invoices');
    $payments=table('payments');
    $items=table('invoice_items');

    setup_add_column($pdo,$services,'standard_fee','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `duration_minutes`');
    setup_add_column($pdo,$invoices,'subtotal','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `description`');
    setup_add_column($pdo,$invoices,'discount_type','VARCHAR(20) NOT NULL DEFAULT "none" AFTER `subtotal`');
    setup_add_column($pdo,$invoices,'discount_value','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_type`');
    setup_add_column($pdo,$invoices,'discount_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_value`');
    setup_add_column($pdo,$invoices,'discount_reason','VARCHAR(255) NULL AFTER `discount_amount`');
    setup_add_column($pdo,$invoices,'tax_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_reason`');
    setup_add_column($pdo,$invoices,'pos_finalized_at','DATETIME NULL AFTER `updated_at`');
    setup_add_column($pdo,$invoices,'pos_finalized_by','BIGINT UNSIGNED NULL AFTER `pos_finalized_at`');
    setup_add_column($pdo,$payments,'tendered_amount','DECIMAL(12,2) NULL AFTER `amount`');
    setup_add_column($pdo,$payments,'change_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `tendered_amount`');
    setup_add_column($pdo,$payments,'notes','VARCHAR(500) NULL AFTER `reference_no`');

    if(setup_table_exists($pdo,$items)){
        foreach([
            ['service_id','BIGINT UNSIGNED NULL AFTER `invoice_id`'],
            ['treatment_plan_item_id','BIGINT UNSIGNED NULL AFTER `service_id`'],
            ['procedure_id','BIGINT UNSIGNED NULL'],
            ['item_code','VARCHAR(80) NULL AFTER `procedure_id`'],
            ['description','VARCHAR(255) NOT NULL AFTER `item_code`'],
            ['quantity','DECIMAL(10,2) NOT NULL DEFAULT 1.00 AFTER `description`'],
            ['unit_price','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `quantity`'],
            ['gross_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `unit_price`'],
            ['discount_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `gross_amount`'],
            ['net_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_amount`'],
            ['notes','VARCHAR(500) NULL AFTER `net_amount`'],
            ['created_by','BIGINT UNSIGNED NULL AFTER `notes`'],
            ['created_at','DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `created_by`'],
        ] as [$column,$definition]) setup_add_column($pdo,$items,$column,$definition);
        if(!setup_index_exists($pdo,$items,'idx_invoice_item_invoice'))$pdo->exec('ALTER TABLE `' . $items . '` ADD KEY `idx_invoice_item_invoice` (`invoice_id`,`id`)');
    }

    $pdo->exec('UPDATE `' . $invoices . '` SET subtotal=amount WHERE subtotal=0 AND amount>0');
    setting_set('system','application_version','0.5.9.3');
    setup_record_history($pdo,'0.5.9.3','strict_queue_navigation_and_cashier_pos');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.3',
        'features'=>['persistent_back_navigation','breadcrumb_trail','strict_queue_state_machine','reason_gated_queue_exceptions','cashier_pos_register','invoice_line_items','service_standard_fees','discounts','amount_tendered_and_change','itemized_invoice_and_receipt'],
    ]);
}

function setup_v0593_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('invoice_items'))
        && setup_column_exists($pdo,table('service_catalog'),'standard_fee')
        && setup_column_exists($pdo,table('invoices'),'subtotal')
        && setup_column_exists($pdo,table('invoices'),'discount_amount')
        && setup_column_exists($pdo,table('payments'),'tendered_amount')
        && setup_column_exists($pdo,table('payments'),'change_amount')
        && is_file(DENTAL_ROOT . '/app/queue_flow.php')
        && is_file(DENTAL_ROOT . '/app/layout.php')
        && is_file(DENTAL_ROOT . '/modules/finance/billing.php');
}


function setup_apply_v0594(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.4');
    setup_record_history($pdo,'0.5.9.4','cashier_pos_runtime_and_transition_integrity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.4',
        'features'=>[
            'cashier_pos_runtime_repair',
            'cashier_helper_dependency_loaded',
            'transactional_cashier_call',
            'cashier_called_payment_gate',
            'queue_invoice_visit_consistency',
        ],
    ]);
}

function setup_v0594_ready(PDO $pdo): bool
{
    return is_file(DENTAL_ROOT . '/modules/finance/billing.php')
        && is_file(DENTAL_ROOT . '/app/module_helpers.php');
}

function setup_apply_v0595(PDO $pdo): void
{
    $medical=table('medical_histories');
    $guardians=table('patient_guardians');

    setup_add_column($pdo,$medical,'source_type','ENUM("patient_reported","staff_recorded","clinician_verified","legacy_record") NOT NULL DEFAULT "legacy_record" AFTER `confirmed_at`');
    setup_add_column($pdo,$medical,'verification_status','ENUM("pending_review","verified","rejected") NOT NULL DEFAULT "pending_review" AFTER `source_type`');
    setup_add_column($pdo,$medical,'verified_by','BIGINT UNSIGNED NULL AFTER `verification_status`');
    setup_add_column($pdo,$medical,'verified_at','DATETIME NULL AFTER `verified_by`');
    setup_add_column($pdo,$medical,'verification_note','TEXT NULL AFTER `verified_at`');
    if(!setup_index_exists($pdo,$medical,'idx_medical_verification'))$pdo->exec('ALTER TABLE `' . $medical . '` ADD KEY `idx_medical_verification` (`patient_id`,`verification_status`,`created_at`)');

    setup_add_column($pdo,$guardians,'verification_status','ENUM("pending","verified","rejected","revoked") NOT NULL DEFAULT "pending" AFTER `authorized_to_receive_records`');
    setup_add_column($pdo,$guardians,'verification_method','VARCHAR(120) NULL AFTER `verification_status`');
    setup_add_column($pdo,$guardians,'verification_note','TEXT NULL AFTER `verification_method`');
    setup_add_column($pdo,$guardians,'verified_by','BIGINT UNSIGNED NULL AFTER `verification_note`');
    setup_add_column($pdo,$guardians,'verified_at','DATETIME NULL AFTER `verified_by`');
    setup_add_column($pdo,$guardians,'revoked_at','DATETIME NULL AFTER `verified_at`');
    if(!setup_index_exists($pdo,$guardians,'idx_guardian_verification'))$pdo->exec('ALTER TABLE `' . $guardians . '` ADD KEY `idx_guardian_verification` (`patient_id`,`verification_status`)');
    $pdo->exec('ALTER TABLE `' . $guardians . '` MODIFY `authorized_to_consent` TINYINT(1) NOT NULL DEFAULT 0, MODIFY `authorized_to_receive_records` TINYINT(1) NOT NULL DEFAULT 0');

    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.5_transaction_integrity.sql');
    $pdo->exec('ALTER TABLE `' . $medical . '` MODIFY `source_type` ENUM("patient_reported","staff_recorded","clinician_verified","legacy_record") NOT NULL DEFAULT "staff_recorded"');
    setting_set('system','application_version','0.5.9.5');
    setup_record_history($pdo,'0.5.9.5','privacy_frontdesk_and_authority_integrity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.5',
        'features'=>[
            'clinical_document_scope_enforcement',
            'medical_history_dentist_verification',
            'guardian_authority_verification',
            'atomic_online_request_processing',
            'appointment_state_machine',
            'appointment_queue_synchronization',
            'typed_portal_request_completion',
        ],
    ]);
}

function setup_v0595_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('medical_histories'),'verification_status')
        && setup_column_exists($pdo,table('medical_histories'),'verified_by')
        && setup_column_exists($pdo,table('patient_guardians'),'verification_status')
        && setup_column_exists($pdo,table('patient_guardians'),'verified_by')
        && is_file(DENTAL_ROOT . '/app/transaction_integrity.php')
        && is_file(DENTAL_ROOT . '/handlers/download_document.php');
}

function setup_apply_v0596(PDO $pdo): void
{
    $profiles=table('dentist_profiles');
    $consents=table('consent_forms');
    $encounters=table('clinical_encounters');
    $procedures=table('procedures');
    $prescriptions=table('prescriptions');
    $prescriptionItems=table('prescription_items');

    setup_add_column($pdo,$profiles,'credential_status','ENUM("pending","verified","rejected","suspended") NOT NULL DEFAULT "pending" AFTER `profile_status`');
    setup_add_column($pdo,$profiles,'credential_verified_by','BIGINT UNSIGNED NULL AFTER `credential_status`');
    setup_add_column($pdo,$profiles,'credential_verified_at','DATETIME NULL AFTER `credential_verified_by`');
    setup_add_column($pdo,$profiles,'credential_note','TEXT NULL AFTER `credential_verified_at`');
    if(!setup_index_exists($pdo,$profiles,'idx_dentist_credential'))$pdo->exec('ALTER TABLE `' . $profiles . '` ADD KEY `idx_dentist_credential` (`credential_status`,`license_expiry`)');

    $pdo->exec('ALTER TABLE `' . $consents . '` MODIFY `status` ENUM("draft","requested","signed","declined","withdrawn","expired") NOT NULL DEFAULT "draft"');
    setup_add_column($pdo,$consents,'treatment_plan_item_id','BIGINT UNSIGNED NULL AFTER `treatment_plan_id`');
    setup_add_column($pdo,$consents,'procedure_id','BIGINT UNSIGNED NULL');
    setup_add_column($pdo,$consents,'signer_type','ENUM("patient","guardian","authorized_representative") NOT NULL DEFAULT "patient" AFTER `signer_relationship`');
    setup_add_column($pdo,$consents,'guardian_id','BIGINT UNSIGNED NULL AFTER `signer_type`');
    setup_add_column($pdo,$consents,'identity_verification_method','VARCHAR(150) NULL AFTER `guardian_id`');
    setup_add_column($pdo,$consents,'signature_method','ENUM("in_person_typed","in_person_electronic","uploaded_signed_form") NULL AFTER `identity_verification_method`');
    setup_add_column($pdo,$consents,'signature_attestation_snapshot','LONGTEXT NULL AFTER `signature_method`');
    setup_add_column($pdo,$consents,'risks_snapshot','LONGTEXT NULL AFTER `content_snapshot`');
    setup_add_column($pdo,$consents,'alternatives_snapshot','LONGTEXT NULL AFTER `risks_snapshot`');
    setup_add_column($pdo,$consents,'refusal_consequences_snapshot','LONGTEXT NULL AFTER `alternatives_snapshot`');
    setup_add_column($pdo,$consents,'questions_answered','TINYINT(1) NOT NULL DEFAULT 0 AFTER `refusal_consequences_snapshot`');
    setup_add_column($pdo,$consents,'expires_at','DATE NULL AFTER `signed_at`');
    setup_add_column($pdo,$consents,'declined_at','DATETIME NULL AFTER `withdrawn_at`');
    setup_add_column($pdo,$consents,'decline_reason','TEXT NULL AFTER `declined_at`');
    setup_add_column($pdo,$consents,'form_hash','CHAR(64) NULL AFTER `decline_reason`');
    setup_add_column($pdo,$consents,'signature_hash','CHAR(64) NULL AFTER `form_hash`');
    setup_add_column($pdo,$consents,'created_by','BIGINT UNSIGNED NULL AFTER `dentist_user_id`');
    setup_add_column($pdo,$consents,'signed_by_user_id','BIGINT UNSIGNED NULL AFTER `created_by`');
    if(!setup_index_exists($pdo,$consents,'idx_consent_plan_item'))$pdo->exec('ALTER TABLE `' . $consents . '` ADD KEY `idx_consent_plan_item` (`treatment_plan_item_id`,`status`,`signed_at`)');
    if(!setup_index_exists($pdo,$consents,'idx_consent_encounter'))$pdo->exec('ALTER TABLE `' . $consents . '` ADD KEY `idx_consent_encounter` (`encounter_id`,`status`)');

    setup_add_column($pdo,$encounters,'consent_form_id','BIGINT UNSIGNED NULL AFTER `queue_entry_id`');
    setup_add_column($pdo,$encounters,'consent_exception_reason','TEXT NULL AFTER `consent_form_id`');
    setup_add_column($pdo,$encounters,'finalized_snapshot_json','LONGTEXT NULL AFTER `completion_check_json`');
    if(!setup_index_exists($pdo,$encounters,'idx_encounter_consent'))$pdo->exec('ALTER TABLE `' . $encounters . '` ADD KEY `idx_encounter_consent` (`consent_form_id`)');
    setup_add_column($pdo,$procedures,'consent_form_id','BIGINT UNSIGNED NULL');

    setup_add_column($pdo,$prescriptions,'clinical_indication','TEXT NULL AFTER `status`');
    setup_add_column($pdo,$prescriptions,'review_snapshot_json','LONGTEXT NULL AFTER `clinical_indication`');
    setup_add_column($pdo,$prescriptions,'signed_by_name','VARCHAR(190) NULL AFTER `review_snapshot_json`');
    setup_add_column($pdo,$prescriptions,'signed_license_number','VARCHAR(100) NULL AFTER `signed_by_name`');
    setup_add_column($pdo,$prescriptions,'signed_signature_path','VARCHAR(255) NULL AFTER `signed_license_number`');
    setup_add_column($pdo,$prescriptions,'prescription_hash','CHAR(64) NULL AFTER `signed_signature_path`');
    setup_add_column($pdo,$prescriptions,'cancelled_at','DATETIME NULL AFTER `prescription_hash`');
    setup_add_column($pdo,$prescriptions,'cancelled_by','BIGINT UNSIGNED NULL AFTER `cancelled_at`');
    setup_add_column($pdo,$prescriptions,'cancellation_reason','TEXT NULL AFTER `cancelled_by`');
    setup_add_column($pdo,$prescriptions,'replaces_prescription_id','BIGINT UNSIGNED NULL AFTER `cancellation_reason`');
    setup_add_column($pdo,$prescriptionItems,'indication','VARCHAR(255) NULL AFTER `generic_name`');

    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.6_clinical_integrity.sql');
    setting_set('system','application_version','0.5.9.6');
    setup_record_history($pdo,'0.5.9.6','procedure_consent_credentials_prescription_and_billing_search_integrity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.6',
        'features'=>[
            'billing_patient_search_json_contract',
            'procedure_specific_consent',
            'encounter_only_treatment_completion',
            'signed_clinical_record_snapshot',
            'prescription_signature_and_cancellation',
            'verified_dentist_credentials_before_privileges',
        ],
    ]);
}

function setup_v0596_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('dentist_profiles'),'credential_status')
        && setup_column_exists($pdo,table('consent_forms'),'treatment_plan_item_id')
        && setup_column_exists($pdo,table('consent_forms'),'signature_hash')
        && setup_column_exists($pdo,table('consent_forms'),'signature_attestation_snapshot')
        && setup_column_exists($pdo,table('clinical_encounters'),'consent_form_id')
        && setup_column_exists($pdo,table('clinical_encounters'),'finalized_snapshot_json')
        && setup_column_exists($pdo,table('prescriptions'),'prescription_hash')
        && setup_column_exists($pdo,table('prescription_items'),'indication');
}


function setup_apply_v0597(PDO $pdo): void
{
    $invoices=table('invoices');
    $items=table('invoice_items');
    $payments=table('payments');
    $claims=table('insurance_claims');

    setup_add_column($pdo,$invoices,'discount_authorization_id','BIGINT UNSIGNED NULL AFTER `discount_reason`');
    setup_add_column($pdo,$invoices,'zero_charge_authorization_id','BIGINT UNSIGNED NULL AFTER `discount_authorization_id`');
    setup_add_column($pdo,$invoices,'charge_source_hash','CHAR(64) NULL AFTER `zero_charge_authorization_id`');
    setup_add_column($pdo,$invoices,'charge_locked_at','DATETIME NULL AFTER `charge_source_hash`');
    setup_add_column($pdo,$invoices,'charge_locked_by','BIGINT UNSIGNED NULL AFTER `charge_locked_at`');
    setup_add_column($pdo,$invoices,'legacy_charge_review_reason','VARCHAR(500) NULL AFTER `charge_locked_by`');
    setup_add_column($pdo,$invoices,'legacy_charge_review_evidence','VARCHAR(255) NULL AFTER `legacy_charge_review_reason`');
    setup_add_column($pdo,$invoices,'legacy_charge_reviewed_by','BIGINT UNSIGNED NULL AFTER `legacy_charge_review_evidence`');
    setup_add_column($pdo,$invoices,'legacy_charge_reviewed_at','DATETIME NULL AFTER `legacy_charge_reviewed_by`');
    if(!setup_index_exists($pdo,$invoices,'idx_invoice_discount_authorization'))$pdo->exec('ALTER TABLE `' . $invoices . '` ADD KEY `idx_invoice_discount_authorization` (`discount_authorization_id`)');

    setup_add_column($pdo,$items,'source_type','ENUM("clinical_procedure","service_catalog","manual_charge","legacy_manual") NOT NULL DEFAULT "legacy_manual" AFTER `procedure_id`');
    setup_add_column($pdo,$items,'source_authorized_by','BIGINT UNSIGNED NULL AFTER `notes`');
    setup_add_column($pdo,$items,'source_authorized_at','DATETIME NULL AFTER `source_authorized_by`');
    setup_add_column($pdo,$items,'source_hash','CHAR(64) NULL AFTER `source_authorized_at`');
    if(!setup_index_exists($pdo,$items,'idx_invoice_item_source'))$pdo->exec('ALTER TABLE `' . $items . '` ADD KEY `idx_invoice_item_source` (`source_type`,`procedure_id`,`service_id`)');

    setup_add_column($pdo,$payments,'cashier_session_id','BIGINT UNSIGNED NULL AFTER `invoice_id`');
    setup_add_column($pdo,$payments,'source_type','ENUM("patient","insurance") NOT NULL DEFAULT "patient" AFTER `cashier_session_id`');
    setup_add_column($pdo,$payments,'status','ENUM("posted","partially_adjusted","adjusted") NOT NULL DEFAULT "posted" AFTER `source_type`');
    setup_add_column($pdo,$payments,'adjustment_total','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `amount`');
    if(!setup_index_exists($pdo,$payments,'idx_payment_cashier_session'))$pdo->exec('ALTER TABLE `' . $payments . '` ADD KEY `idx_payment_cashier_session` (`cashier_session_id`,`created_at`)');

    setup_add_column($pdo,$claims,'authorization_no','VARCHAR(120) NULL AFTER `claim_no`');
    setup_add_column($pdo,$claims,'submission_reference','VARCHAR(120) NULL AFTER `authorization_no`');
    setup_add_column($pdo,$claims,'created_by','BIGINT UNSIGNED NULL AFTER `notes`');
    setup_add_column($pdo,$claims,'updated_by','BIGINT UNSIGNED NULL AFTER `created_by`');
    setup_add_column($pdo,$claims,'updated_at','DATETIME NULL AFTER `created_at`');
    setup_add_column($pdo,$claims,'remittance_reference','VARCHAR(120) NULL AFTER `paid_at`');
    setup_add_column($pdo,$claims,'payment_id','BIGINT UNSIGNED NULL AFTER `remittance_reference`');
    setup_add_column($pdo,$claims,'settled_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `payment_id`');
    setup_add_column($pdo,$claims,'settled_by','BIGINT UNSIGNED NULL AFTER `settled_amount`');
    setup_add_column($pdo,$claims,'settled_at','DATETIME NULL AFTER `settled_by`');
    if(!setup_index_exists($pdo,$claims,'idx_claim_payment'))$pdo->exec('ALTER TABLE `' . $claims . '` ADD KEY `idx_claim_payment` (`payment_id`)');

    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.7_revenue_cycle_integrity.sql');
    setting_set('system','application_version','0.5.9.7');
    setup_record_history($pdo,'0.5.9.7','governed_revenue_cycle_and_cashier_reconciliation');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.7',
        'features'=>[
            'server_authoritative_charge_sources',
            'two_person_discount_authorization',
            'approved_zero_charge_completion',
            'refund_and_reversal_workflow',
            'insurance_payment_allocation',
            'insurance_claim_state_history',
            'cashier_session_reconciliation',
        ],
    ]);
}

function setup_v0597_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('cashier_sessions'))
        && setup_table_exists($pdo,table('discount_authorizations'))
        && setup_table_exists($pdo,table('zero_charge_authorizations'))
        && setup_table_exists($pdo,table('payment_adjustments'))
        && setup_table_exists($pdo,table('insurance_claim_history'))
        && setup_column_exists($pdo,table('invoice_items'),'source_type')
        && setup_column_exists($pdo,table('invoices'),'discount_authorization_id')
        && setup_column_exists($pdo,table('invoices'),'legacy_charge_reviewed_at')
        && setup_column_exists($pdo,table('payments'),'cashier_session_id')
        && setup_column_exists($pdo,table('insurance_claims'),'payment_id')
        && setup_column_exists($pdo,table('insurance_claims'),'authorization_no')
        && setup_column_exists($pdo,table('insurance_claims'),'created_by')
        && setup_column_exists($pdo,table('insurance_claims'),'updated_at')
        && is_file(DENTAL_ROOT . '/app/revenue_integrity.php');
}



function setup_apply_v0598(PDO $pdo): void
{
    $items=table('inventory_items');
    $lots=table('inventory_lots');
    $transactions=table('inventory_transactions');
    $batches=table('sterilization_batches');

    setup_add_column($pdo,$items,'default_supplier_id','BIGINT UNSIGNED NULL AFTER `supplier_name`');
    setup_add_column($pdo,$items,'track_lots','TINYINT(1) NOT NULL DEFAULT 1 AFTER `default_supplier_id`');
    setup_add_column($pdo,$items,'is_clinical_supply','TINYINT(1) NOT NULL DEFAULT 1 AFTER `track_lots`');

    setup_add_column($pdo,$lots,'branch_id','BIGINT UNSIGNED NULL AFTER `item_id`');
    setup_add_column($pdo,$lots,'received_document_id','BIGINT UNSIGNED NULL AFTER `branch_id`');
    setup_add_column($pdo,$lots,'unit_cost','DECIMAL(12,2) NULL AFTER `quantity_remaining`');
    setup_add_column($pdo,$lots,'quarantined_at','DATETIME NULL AFTER `status`');
    setup_add_column($pdo,$lots,'quarantine_reason','VARCHAR(500) NULL AFTER `quarantined_at`');
    setup_add_column($pdo,$lots,'updated_at','DATETIME NULL AFTER `created_at`');
    $pdo->exec('ALTER TABLE `' . $lots . '` MODIFY `status` ENUM("active","depleted","expired","disposed","quarantined") NOT NULL DEFAULT "active"');
    $fallbackBranch=(int)$pdo->query('SELECT id FROM `' . table('branches') . '` WHERE is_active=1 ORDER BY id LIMIT 1')->fetchColumn();
    if($fallbackBranch>0){
        $pdo->exec('UPDATE `' . $lots . '` l JOIN `' . $items . '` i ON i.id=l.item_id SET l.branch_id=COALESCE(i.branch_id,' . $fallbackBranch . ') WHERE l.branch_id IS NULL');
        $pdo->exec('ALTER TABLE `' . $lots . '` MODIFY `branch_id` BIGINT UNSIGNED NOT NULL');
    }
    if(!setup_index_exists($pdo,$lots,'idx_lot_branch_item'))$pdo->exec('ALTER TABLE `' . $lots . '` ADD KEY `idx_lot_branch_item` (`branch_id`,`item_id`,`status`)');
    if(!setup_index_exists($pdo,$lots,'idx_lot_lookup'))$pdo->exec('ALTER TABLE `' . $lots . '` ADD KEY `idx_lot_lookup` (`item_id`,`branch_id`,`lot_number`)');

    setup_add_column($pdo,$transactions,'document_id','BIGINT UNSIGNED NULL AFTER `branch_id`');
    setup_add_column($pdo,$transactions,'document_line_id','BIGINT UNSIGNED NULL AFTER `document_id`');
    setup_add_column($pdo,$transactions,'movement_no','VARCHAR(40) NULL AFTER `document_line_id`');
    setup_add_column($pdo,$transactions,'counterpart_transaction_id','BIGINT UNSIGNED NULL AFTER `movement_no`');
    setup_add_column($pdo,$transactions,'balance_before','DECIMAL(12,2) NULL AFTER `unit_cost`');
    setup_add_column($pdo,$transactions,'balance_after','DECIMAL(12,2) NULL AFTER `balance_before`');
    setup_add_column($pdo,$transactions,'status','ENUM("posted","voided") NOT NULL DEFAULT "posted" AFTER `balance_after`');
    setup_add_column($pdo,$transactions,'approved_by','BIGINT UNSIGNED NULL AFTER `user_id`');
    setup_add_column($pdo,$transactions,'posted_at','DATETIME NULL AFTER `approved_by`');
    if(!setup_index_exists($pdo,$transactions,'idx_inventory_tx_document'))$pdo->exec('ALTER TABLE `' . $transactions . '` ADD KEY `idx_inventory_tx_document` (`document_id`,`document_line_id`)');
    if(!setup_index_exists($pdo,$transactions,'uq_inventory_movement_no'))$pdo->exec('ALTER TABLE `' . $transactions . '` ADD UNIQUE KEY `uq_inventory_movement_no` (`movement_no`)');

    setup_add_column($pdo,$batches,'equipment_id','BIGINT UNSIGNED NULL AFTER `branch_id`');
    setup_add_column($pdo,$batches,'cycle_status','ENUM("loaded","running","completed","quarantined","failed","released","cancelled") NOT NULL DEFAULT "loaded" AFTER `operator_user_id`');
    setup_add_column($pdo,$batches,'temperature_c','DECIMAL(6,2) NULL AFTER `cycle_status`');
    setup_add_column($pdo,$batches,'pressure_kpa','DECIMAL(8,2) NULL AFTER `temperature_c`');
    setup_add_column($pdo,$batches,'exposure_minutes','INT UNSIGNED NULL AFTER `pressure_kpa`');
    setup_add_column($pdo,$batches,'chemical_indicator_result','ENUM("pending","pass","fail") NOT NULL DEFAULT "pending" AFTER `exposure_minutes`');
    setup_add_column($pdo,$batches,'biological_indicator_required','TINYINT(1) NOT NULL DEFAULT 0 AFTER `chemical_indicator_result`');
    setup_add_column($pdo,$batches,'biological_indicator_result','ENUM("not_required","pending","pass","fail") NOT NULL DEFAULT "not_required" AFTER `biological_result`');
    setup_add_column($pdo,$batches,'completed_by','BIGINT UNSIGNED NULL AFTER `biological_indicator_result`');
    setup_add_column($pdo,$batches,'released_by','BIGINT UNSIGNED NULL AFTER `completed_by`');
    setup_add_column($pdo,$batches,'released_at','DATETIME NULL AFTER `released_by`');
    setup_add_column($pdo,$batches,'release_note','TEXT NULL AFTER `released_at`');
    setup_add_column($pdo,$batches,'failure_reason','TEXT NULL AFTER `release_note`');
    setup_add_column($pdo,$batches,'batch_hash','CHAR(64) NULL AFTER `failure_reason`');
    if(!setup_index_exists($pdo,$batches,'idx_sterile_cycle_status'))$pdo->exec('ALTER TABLE `' . $batches . '` ADD KEY `idx_sterile_cycle_status` (`branch_id`,`cycle_status`,`cycle_started_at`)');
    if(!setup_index_exists($pdo,$batches,'idx_sterile_cycle_load'))$pdo->exec('ALTER TABLE `' . $batches . '` ADD KEY `idx_sterile_cycle_load` (`branch_id`,`cycle_no`,`load_no`)');

    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8_inventory_sterilization_integrity.sql');
    setting_set('system','application_version','0.5.9.8');
    setup_record_history($pdo,'0.5.9.8','inventory_procurement_and_sterilization_integrity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8',
        'features'=>[
            'branch_and_lot_stock_reconciliation','governed_purchase_orders_and_receiving','paired_branch_transfers',
            'approved_physical_counts_and_disposals','sterilizer_cycle_state_machine','instrument_set_patient_traceability',
        ],
    ]);
}

function setup_v0598_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('inventory_branch_stocks'))
        && setup_table_exists($pdo,table('inventory_documents'))
        && setup_table_exists($pdo,table('inventory_document_lines'))
        && setup_table_exists($pdo,table('inventory_document_history'))
        && setup_column_exists($pdo,table('inventory_document_lines'),'source_line_id')
        && setup_table_exists($pdo,table('sterilization_equipment'))
        && setup_table_exists($pdo,table('instrument_sets'))
        && setup_table_exists($pdo,table('instrument_usage'))
        && setup_column_exists($pdo,table('inventory_lots'),'branch_id')
        && setup_column_exists($pdo,table('inventory_transactions'),'movement_no')
        && setup_column_exists($pdo,table('sterilization_batches'),'cycle_status')
        && is_file(DENTAL_ROOT . '/app/inventory_integrity.php');
}

function setup_apply_v05981(PDO $pdo): void
{
    $procedures=table('procedures');
    setup_add_column($pdo,$procedures,'branch_id','BIGINT UNSIGNED NULL AFTER `patient_id`');
    $pdo->exec('UPDATE `' . $procedures . '` pr LEFT JOIN `' . table('clinical_encounters') . '` ce ON ce.id=pr.encounter_id LEFT JOIN `' . table('patients') . '` p ON p.id=pr.patient_id SET pr.branch_id=COALESCE(pr.branch_id,ce.branch_id,p.branch_id) WHERE pr.branch_id IS NULL');
    if(!setup_index_exists($pdo,$procedures,'idx_proc_branch'))$pdo->exec('ALTER TABLE `' . $procedures . '` ADD KEY `idx_proc_branch` (`branch_id`,`created_at`)');
    $constraint=(string)app_config('database.prefix','dent_') . 'fk_proc_branch';
    if(!setup_constraint_exists($pdo,$procedures,$constraint)){
        try{$pdo->exec('ALTER TABLE `' . $procedures . '` ADD CONSTRAINT `' . $constraint . '` FOREIGN KEY (`branch_id`) REFERENCES `' . table('branches') . '` (`id`) ON DELETE SET NULL');}
        catch(Throwable $constraintError){error_log('Version 0.5.9.8.1 procedure branch foreign key was not added: ' . $constraintError->getMessage());}
    }
    setting_set('system','application_version','0.5.9.8.1');
    setup_record_history($pdo,'0.5.9.8.1','inventory_procedure_branch_and_navigation_usability_hotfix');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.1',
        'features'=>['inventory_procedure_branch_repair','compact_doctor_assignments','separate_website_navigation'],
    ]);
}

function setup_v05981_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('procedures'),'branch_id')
        && setup_index_exists($pdo,table('procedures'),'idx_proc_branch');
}

function setup_apply_v05982(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.2_schedule_scope_stock_treatment.sql');
    setting_set('system','application_version','0.5.9.8.2');
    setup_record_history($pdo,'0.5.9.8.2','schedule_future_patient_stock_catalog_and_treatment_handoff');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.2',
        'features'=>[
            'compact_schedule_toolbar','future_assigned_patient_access','ten_second_transient_notices',
            'persistent_common_stock_catalog','superuser_sidebar_group','governed_treatment_plan_handoff',
            'existing_patient_walkin_autofill','internal_inhouse_checkup_catalog','governed_inhouse_checkup_results',
            'unused_staff_email_correction','protected_unused_staff_profile_deletion',
        ],
    ]);
}

function setup_v05982_ready(PDO $pdo): bool
{
    // Readiness is structural rather than dependent on mutable catalog names.
    // Administrators may rename, disable, or add stock and in-house checkup
    // catalog entries without making the application appear out of date.
    return setup_table_exists($pdo,table('treatment_plan_reassignments'))
        && setup_column_exists($pdo,table('treatment_plan_reassignments'),'request_type')
        && setup_column_exists($pdo,table('treatment_plan_reassignments'),'to_dentist_user_id')
        && setup_table_exists($pdo,table('inhouse_checkup_results'))
        && setup_column_exists($pdo,table('inhouse_checkup_results'),'queue_entry_id')
        && setup_column_exists($pdo,table('inhouse_checkup_results'),'referral_dentist_user_id');
}

function setup_apply_v05983(PDO $pdo): void
{
    setup_add_column($pdo,table('service_catalog'),'required_privilege_code','VARCHAR(100) NULL AFTER `provider_role_code`');
    setup_add_column($pdo,table('appointment_requests'),'provider_preference_mode',"ENUM('specific','clinic_decides') NOT NULL DEFAULT 'specific' AFTER `preferred_dentist_user_id`");
    if(!setup_index_exists($pdo,table('appointment_requests'),'idx_request_provider_preference')){
        $pdo->exec('ALTER TABLE `' . table('appointment_requests') . '` ADD KEY `idx_request_provider_preference` (`provider_preference_mode`,`status`,`preferred_date`)');
    }
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.3_provider_task_dashboard_integrity.sql');
    setting_set('system','application_version','0.5.9.8.3');
    setup_record_history($pdo,'0.5.9.8.3','provider_choice_clinical_task_routing_dashboard_and_privilege_clarity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.3',
        'features'=>[
            'new_patient_provider_choice','clinic_decides_provider_mode','provider_privilege_readiness',
            'future_assigned_patient_scope','specific_clinical_task_assignment','clinical_task_acceptance',
            'dashboard_priority_integrity','critical_provider_exceptions','credential_correction_history',
            'clinical_privilege_grouped_interface',
        ],
    ]);
}

function setup_v05983_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('service_catalog'),'required_privilege_code')
        && setup_column_exists($pdo,table('appointment_requests'),'provider_preference_mode')
        && setup_index_exists($pdo,table('appointment_requests'),'idx_request_provider_preference')
        && setup_table_exists($pdo,table('clinical_task_assignments'))
        && setup_column_exists($pdo,table('clinical_task_assignments'),'assigned_dentist_user_id')
        && setup_table_exists($pdo,table('dentist_credential_history'));
}

function setup_apply_v05984(PDO $pdo): void
{
    $plans=table('treatment_plans');
    setup_add_column($pdo,$plans,'branch_id','BIGINT UNSIGNED NULL AFTER `patient_id`');
    $pdo->exec('UPDATE `' . $plans . '` tp LEFT JOIN `' . table('clinical_encounters') . '` ce ON ce.id=tp.encounter_id LEFT JOIN `' . table('patients') . '` p ON p.id=tp.patient_id SET tp.branch_id=COALESCE(tp.branch_id,ce.branch_id,p.branch_id) WHERE tp.branch_id IS NULL');
    if(!setup_index_exists($pdo,$plans,'idx_plan_branch'))$pdo->exec('ALTER TABLE `' . $plans . '` ADD KEY `idx_plan_branch` (`branch_id`,`status`,`created_at`)');

    $audit=table('audit_logs');
    setup_add_column($pdo,$audit,'branch_id','BIGINT UNSIGNED NULL AFTER `user_id`');
    if(!setup_index_exists($pdo,$audit,'idx_audit_branch'))$pdo->exec('ALTER TABLE `' . $audit . '` ADD KEY `idx_audit_branch` (`branch_id`,`created_at`)');

    $invites=table('patient_portal_invites');
    setup_add_column($pdo,$invites,'guardian_id','BIGINT UNSIGNED NULL AFTER `patient_id`');
    if(!setup_index_exists($pdo,$invites,'idx_portal_invite_guardian'))$pdo->exec('ALTER TABLE `' . $invites . '` ADD KEY `idx_portal_invite_guardian` (`guardian_id`,`expires_at`)');

    $accounts=table('patient_portal_accounts');
    $pdo->exec('ALTER TABLE `' . $accounts . '` MODIFY `patient_id` BIGINT UNSIGNED NULL');
    // Add a non-unique supporting index before dropping the legacy unique index so the patient FK remains indexed.
    if(!setup_index_exists($pdo,$accounts,'idx_portal_primary_patient'))$pdo->exec('ALTER TABLE `' . $accounts . '` ADD KEY `idx_portal_primary_patient` (`patient_id`)');
    if(setup_index_exists($pdo,$accounts,'uq_portal_patient'))$pdo->exec('ALTER TABLE `' . $accounts . '` DROP INDEX `uq_portal_patient`');

    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.4_role_scope_guardian_portal.sql');
    $pdo->exec('INSERT INTO `' . table('patient_portal_account_patients') . '` (account_id,patient_id,is_self,can_view_records,can_manage_appointments,can_submit_health,status,authorized_at,created_at) SELECT a.id,a.patient_id,1,1,1,1,"active",COALESCE(a.created_at,NOW()),COALESCE(a.created_at,NOW()) FROM `' . $accounts . '` a WHERE a.patient_id IS NOT NULL ON DUPLICATE KEY UPDATE is_self=VALUES(is_self),can_view_records=1,can_manage_appointments=1,can_submit_health=1,status="active",revoked_at=NULL,updated_at=NOW()');

    setting_set('system','application_version','0.5.9.8.4');
    setup_record_history($pdo,'0.5.9.8.4','role_scope_printing_guardian_portal_and_treatment_branch_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.4',
        'branch_id'=>signed_user_branch_id() ?: null,
        'features'=>[
            'treatment_plan_branch_repair','branch_scoped_printing_and_reports','cashier_minimum_necessary_view',
            'branch_aware_audit_log','guardian_dependent_portal_access','sanitized_hotfix_packaging',
        ],
    ]);
}

function setup_v05984_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('treatment_plans'),'branch_id')
        && setup_index_exists($pdo,table('treatment_plans'),'idx_plan_branch')
        && setup_column_exists($pdo,table('audit_logs'),'branch_id')
        && setup_index_exists($pdo,table('audit_logs'),'idx_audit_branch')
        && setup_column_exists($pdo,table('patient_portal_invites'),'guardian_id')
        && setup_table_exists($pdo,table('patient_portal_account_patients'))
        && setup_index_exists($pdo,table('patient_portal_accounts'),'idx_portal_primary_patient');
}


function setup_apply_v05985(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.5_medicine_followup_diagnostic.sql');
    $items=table('prescription_items');
    setup_add_column($pdo,$items,'medicine_catalog_id','BIGINT UNSIGNED NULL AFTER `prescription_id`');
    if(!setup_index_exists($pdo,$items,'idx_rx_item_catalog'))$pdo->exec('ALTER TABLE `' . $items . '` ADD KEY `idx_rx_item_catalog` (`medicine_catalog_id`)');
    $catalogConstraint=(string)app_config('database.prefix','dent_').'fk_rx_item_catalog';
    if(!setup_constraint_exists($pdo,$items,$catalogConstraint))$pdo->exec('ALTER TABLE `' . $items . '` ADD CONSTRAINT `' . $catalogConstraint . '` FOREIGN KEY (`medicine_catalog_id`) REFERENCES `' . table('medicine_catalog') . '` (`id`) ON DELETE SET NULL');

    setting_set('system','application_version','0.5.9.8.5');
    setup_record_history($pdo,'0.5.9.8.5','medicine_catalog_unlimited_prescriptions_staff_followup_and_diagnostic_review_tasks');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.5',
        'features'=>[
            'database_backed_medicine_catalog','unlimited_prescription_items','inline_new_medicine_creation',
            'receptionist_cashier_followup_booking','dentist_followup_recommendation_only','diagnostic_review_todo_tasks',
        ],
    ]);
}

function setup_v05985_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('medicine_catalog'))
        && setup_column_exists($pdo,table('prescription_items'),'medicine_catalog_id')
        && setup_index_exists($pdo,table('prescription_items'),'idx_rx_item_catalog')
        && setup_constraint_exists($pdo,table('prescription_items'),(string)app_config('database.prefix','dent_').'fk_rx_item_catalog')
        && setup_table_exists($pdo,table('diagnostic_review_tasks'));
}


function setup_apply_v05986(PDO $pdo): void
{
    $consents = table('consent_forms');
    $encounters = table('clinical_encounters');
    $items = table('treatment_plan_items');
    $procedures = table('procedures');

    // Repair partial v0.5.9.6 upgrades. Some installations had enough
    // columns for the consent page to open but not enough for the final
    // INSERT, causing a failure only after Save was pressed.
    $pdo->exec('ALTER TABLE `' . $consents . '` MODIFY `status` ENUM("draft","requested","signed","declined","withdrawn","expired") NOT NULL DEFAULT "draft"');
    setup_add_column($pdo,$consents,'branch_id','BIGINT UNSIGNED NULL AFTER `patient_id`');
    setup_add_column($pdo,$consents,'service_id','BIGINT UNSIGNED NULL AFTER `branch_id`');
    setup_add_column($pdo,$consents,'treatment_plan_item_id','BIGINT UNSIGNED NULL AFTER `treatment_plan_id`');
    setup_add_column($pdo,$consents,'procedure_id','BIGINT UNSIGNED NULL');
    setup_add_column($pdo,$consents,'risks_snapshot','LONGTEXT NULL AFTER `content_snapshot`');
    setup_add_column($pdo,$consents,'alternatives_snapshot','LONGTEXT NULL AFTER `risks_snapshot`');
    setup_add_column($pdo,$consents,'refusal_consequences_snapshot','LONGTEXT NULL AFTER `alternatives_snapshot`');
    setup_add_column($pdo,$consents,'questions_answered','TINYINT(1) NOT NULL DEFAULT 0 AFTER `refusal_consequences_snapshot`');
    setup_add_column($pdo,$consents,'signer_type','ENUM("patient","guardian","authorized_representative") NOT NULL DEFAULT "patient" AFTER `signer_relationship`');
    setup_add_column($pdo,$consents,'guardian_id','BIGINT UNSIGNED NULL AFTER `signer_type`');
    setup_add_column($pdo,$consents,'identity_verification_method','VARCHAR(150) NULL AFTER `guardian_id`');
    setup_add_column($pdo,$consents,'signature_method','ENUM("in_person_typed","in_person_electronic","uploaded_signed_form") NULL AFTER `identity_verification_method`');
    setup_add_column($pdo,$consents,'signature_attestation_snapshot','LONGTEXT NULL AFTER `signature_method`');
    setup_add_column($pdo,$consents,'expires_at','DATE NULL AFTER `signed_at`');
    setup_add_column($pdo,$consents,'declined_at','DATETIME NULL AFTER `withdrawn_at`');
    setup_add_column($pdo,$consents,'decline_reason','TEXT NULL AFTER `declined_at`');
    setup_add_column($pdo,$consents,'form_hash','CHAR(64) NULL AFTER `decline_reason`');
    setup_add_column($pdo,$consents,'signature_hash','CHAR(64) NULL AFTER `form_hash`');
    setup_add_column($pdo,$consents,'created_by','BIGINT UNSIGNED NULL AFTER `dentist_user_id`');
    setup_add_column($pdo,$consents,'signed_by_user_id','BIGINT UNSIGNED NULL AFTER `created_by`');
    if(!setup_index_exists($pdo,$consents,'idx_consent_plan_item'))$pdo->exec('ALTER TABLE `' . $consents . '` ADD KEY `idx_consent_plan_item` (`treatment_plan_item_id`,`status`,`signed_at`)');
    if(!setup_index_exists($pdo,$consents,'idx_consent_encounter'))$pdo->exec('ALTER TABLE `' . $consents . '` ADD KEY `idx_consent_encounter` (`encounter_id`,`status`)');
    if(!setup_index_exists($pdo,$consents,'idx_consent_branch'))$pdo->exec('ALTER TABLE `' . $consents . '` ADD KEY `idx_consent_branch` (`branch_id`,`created_at`)');

    setup_add_column($pdo,$encounters,'consent_form_id','BIGINT UNSIGNED NULL AFTER `queue_entry_id`');
    setup_add_column($pdo,$encounters,'consent_exception_reason','TEXT NULL AFTER `consent_form_id`');
    if(!setup_index_exists($pdo,$encounters,'idx_encounter_consent'))$pdo->exec('ALTER TABLE `' . $encounters . '` ADD KEY `idx_encounter_consent` (`consent_form_id`)');
    setup_add_column($pdo,$items,'consent_status','VARCHAR(30) NOT NULL DEFAULT "not_requested" AFTER `status`');
    setup_add_column($pdo,$procedures,'consent_form_id','BIGINT UNSIGNED NULL');

    $pdo->exec('UPDATE `' . $consents . '` c LEFT JOIN `' . $encounters . '` e ON e.id=c.encounter_id LEFT JOIN `' . table('patients') . '` p ON p.id=c.patient_id SET c.branch_id=COALESCE(c.branch_id,e.branch_id,p.branch_id) WHERE c.branch_id IS NULL');

    setting_set('system','application_version','0.5.9.8.6');
    setup_record_history($pdo,'0.5.9.8.6','consent_save_schema_and_local_session_storage_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.6',
        'features'=>['complete_consent_schema_repair','consent_preflight_validation','protected_local_php_session_storage'],
    ]);
}

function setup_v05986_ready(PDO $pdo): bool
{
    foreach (clinical_consent_required_schema() as $tableName => $columns) {
        foreach ($columns as $column) {
            if (!setup_column_exists($pdo,$tableName,$column)) return false;
        }
    }
    return setup_column_exists($pdo,table('consent_forms'),'branch_id')
        && setup_index_exists($pdo,table('consent_forms'),'idx_consent_plan_item')
        && setup_index_exists($pdo,table('consent_forms'),'idx_consent_encounter')
        && setup_index_exists($pdo,table('consent_forms'),'idx_consent_branch');
}


function setup_apply_v05987(PDO $pdo): void
{
    $encounters=table('clinical_encounters');
    $assessments=table('clinical_assessments');
    $invoiceItems=table('invoice_items');
    $diagnoses=table('diagnoses');

    setup_add_column($pdo,$encounters,'clinical_disposition','VARCHAR(60) NULL AFTER `treatment_plan`');
    setup_add_column($pdo,$encounters,'disposition_reason','TEXT NULL AFTER `clinical_disposition`');
    setup_add_column($pdo,$encounters,'patient_advice','TEXT NULL AFTER `disposition_reason`');

    setup_add_column($pdo,$assessments,'extraoral_status','ENUM("normal","abnormal","not_examined","not_applicable") NOT NULL DEFAULT "not_examined" AFTER `patient_id`');
    setup_add_column($pdo,$assessments,'intraoral_status','ENUM("normal","abnormal","not_examined","not_applicable") NOT NULL DEFAULT "not_examined" AFTER `extraoral_findings`');
    setup_add_column($pdo,$assessments,'soft_tissue_status','ENUM("normal","abnormal","not_examined","not_applicable") NOT NULL DEFAULT "not_examined" AFTER `intraoral_findings`');
    setup_add_column($pdo,$assessments,'tmj_status','ENUM("normal","abnormal","not_examined","not_applicable") NOT NULL DEFAULT "not_examined" AFTER `soft_tissue_findings`');
    setup_add_column($pdo,$assessments,'occlusion_status','ENUM("normal","abnormal","not_examined","not_applicable") NOT NULL DEFAULT "not_examined" AFTER `tmj_findings`');
    setup_add_column($pdo,$assessments,'radiographic_status','ENUM("normal","abnormal","not_examined","not_applicable") NOT NULL DEFAULT "not_examined" AFTER `oral_hygiene_status`');

    setup_add_column($pdo,$invoiceItems,'clinical_usage_item_id','BIGINT UNSIGNED NULL AFTER `procedure_id`');
    $pdo->exec('ALTER TABLE `' . $invoiceItems . '` MODIFY `source_type` ENUM("clinical_procedure","service_catalog","manual_charge","legacy_manual","clinical_supply","dispensed_medicine","equipment_service","diagnostic_service") NOT NULL DEFAULT "legacy_manual"');
    if(!setup_index_exists($pdo,$invoiceItems,'idx_invoice_item_usage'))$pdo->exec('ALTER TABLE `' . $invoiceItems . '` ADD KEY `idx_invoice_item_usage` (`clinical_usage_item_id`)');
    if(schema_table_exists($diagnoses))$pdo->exec('ALTER TABLE `' . $diagnoses . '` MODIFY `diagnosis_status` ENUM("provisional","confirmed","resolved","chronic","under_observation","referred") NOT NULL DEFAULT "provisional"');

    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.7_clinical_workspace_redesign.sql');

    setting_set('system','application_version','0.5.9.8.7');
    setup_record_history($pdo,'0.5.9.8.7','guided_clinical_encounter_live_patient_filter_and_billing_handoff');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.7',
        'features'=>[
            'compact_guided_clinical_encounter','conditional_intake_and_examination','unlimited_diagnosis_lookup',
            'no_treatment_clinical_disposition','clinic_medicine_and_material_handoff','equipment_and_diagnostic_usage',
            'source_traceable_cashier_billing','visible_consent_and_finalization_checklist','live_patient_filtering',
        ],
    ]);
}

function setup_v05987_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('diagnosis_catalog'))
        && setup_table_exists($pdo,table('clinical_usage_items'))
        && setup_column_exists($pdo,table('clinical_encounters'),'clinical_disposition')
        && setup_column_exists($pdo,table('clinical_encounters'),'disposition_reason')
        && setup_column_exists($pdo,table('clinical_encounters'),'patient_advice')
        && setup_column_exists($pdo,table('clinical_assessments'),'extraoral_status')
        && setup_column_exists($pdo,table('clinical_assessments'),'radiographic_status')
        && setup_column_exists($pdo,table('invoice_items'),'clinical_usage_item_id')
        && setup_index_exists($pdo,table('invoice_items'),'idx_invoice_item_usage');
}

function setup_apply_v05988(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.8');
    setup_record_history($pdo,'0.5.9.8.8','consent_signer_guidance_application_chrome_and_required_field_keyboard_flow');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.8',
        'features'=>[
            'consent_signer_autofill','identity_verification_selector','guardian_signer_validation_repair',
            'first_missing_field_focus','clinical_next_required_shortcut','clinical_sidebar_and_topbar_preserved',
        ],
    ]);
}

function setup_v05988_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.8','>=');
}

function setup_apply_v05989(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.9');
    setup_record_history($pdo,'0.5.9.8.9','clinical_finalization_consistency_intake_hpi_sync_and_followup_decision');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.9',
        'features'=>[
            'intake_hpi_encounter_sync','structured_examination_summary_generation','truthful_finalization_checklist',
            'explicit_followup_decision','first_missing_requirement_redirect','finalize_preflight_consistency',
        ],
    ]);
}

function setup_v05989_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.9','>=');
}


function setup_apply_v059810(PDO $pdo): void
{
    $procedures=table('procedures');
    setup_add_column($pdo,$procedures,'service_id','BIGINT UNSIGNED NULL AFTER `branch_id`');
    if(!setup_index_exists($pdo,$procedures,'idx_proc_service')){
        $pdo->exec('ALTER TABLE `' . $procedures . '` ADD KEY `idx_proc_service` (`service_id`)');
    }

    // Preserve existing treatment fees, while binding historical procedures to
    // an exact active catalogue service when a unique branch-aware name match is available.
    try{
        $rows=$pdo->query('SELECT id,branch_id,procedure_name FROM `' . $procedures . '` WHERE service_id IS NULL AND TRIM(procedure_name)<>"" ORDER BY id')->fetchAll();
        $match=$pdo->prepare(
            'SELECT id FROM `' . table('service_catalog') . '` '
            . 'WHERE is_active=1 AND LOWER(TRIM(name))=LOWER(TRIM(?)) AND (branch_id=? OR branch_id IS NULL) '
            . 'ORDER BY CASE WHEN branch_id=? THEN 0 ELSE 1 END,id LIMIT 1'
        );
        $update=$pdo->prepare('UPDATE `' . $procedures . '` SET service_id=? WHERE id=? AND service_id IS NULL');
        foreach($rows as $row){
            $branchId=(int)($row['branch_id']??0);
            $match->execute([(string)$row['procedure_name'],$branchId,$branchId]);
            $serviceId=(int)($match->fetchColumn()?:0);
            if($serviceId>0)$update->execute([$serviceId,(int)$row['id']]);
        }
    }catch(Throwable $backfillError){
        error_log('Version 0.5.9.8.10 procedure service backfill skipped: ' . $backfillError->getMessage());
    }

    $constraint=(string)app_config('database.prefix','dent_') . 'fk_proc_service';
    if(!setup_constraint_exists($pdo,$procedures,$constraint)){
        try{
            $pdo->exec('ALTER TABLE `' . $procedures . '` ADD CONSTRAINT `' . $constraint . '` FOREIGN KEY (`service_id`) REFERENCES `' . table('service_catalog') . '` (`id`) ON DELETE SET NULL');
        }catch(Throwable $constraintError){
            error_log('Version 0.5.9.8.10 procedure service foreign key was not added: ' . $constraintError->getMessage());
        }
    }

    // Backfill tooth-specific encounter diagnoses into the longitudinal dental
    // chart. Never invent a surface: older diagnoses without one are shown as
    // whole-tooth findings and can be refined by the Dentist later.
    try{
        if(setup_table_exists($pdo,table('diagnoses'))&&setup_table_exists($pdo,table('odontogram_entries'))){
            $diagnosisRows=$pdo->query(
                'SELECT d.*,e.branch_id FROM `' . table('diagnoses') . '` d '
                . 'JOIN `' . table('clinical_encounters') . '` e ON e.id=d.encounter_id '
                . 'LEFT JOIN `' . table('odontogram_entries') . '` o ON o.diagnosis_id=d.id '
                . 'WHERE o.id IS NULL AND TRIM(COALESCE(d.tooth_region,""))<>"" ORDER BY d.id'
            )->fetchAll();
            $insertColumns=['patient_id','encounter_id','dentition','tooth_number','surface','condition_code','finding_type','severity','chart_layer','status','diagnosis_id','notes','recorded_by','recorded_at'];
            $hasBranch=setup_column_exists($pdo,table('odontogram_entries'),'branch_id');
            if($hasBranch)array_splice($insertColumns,1,0,['branch_id']);
            $quoted=array_map(static fn(string $column):string=>'`'.$column.'`',$insertColumns);
            $placeholders=array_fill(0,count($insertColumns)-1,'?');
            $insertChart=$pdo->prepare('INSERT INTO `' . table('odontogram_entries') . '` ('.implode(',',$quoted).') VALUES ('.implode(',',$placeholders).',NOW())');
            foreach($diagnosisRows as $diagnosis){
                $tooth=dental_extract_fdi_tooth((string)$diagnosis['tooth_region']);
                if(!$tooth)continue;
                $surfaces=dental_surface_codes_from_text((string)$diagnosis['tooth_region'],$tooth);
                $surfaceText=$surfaces?implode(',',$surfaces):'Whole';
                $resolved=(string)$diagnosis['diagnosis_status']==='resolved';
                $values=[
                    (int)$diagnosis['patient_id'],(int)$diagnosis['encounter_id'],dental_tooth_dentition($tooth)??'permanent',$tooth,$surfaceText,
                    trim((string)($diagnosis['diagnosis_code']??''))?:'diagnosis','diagnosis',$diagnosis['severity']?:null,$resolved?'historical':'existing',$resolved?'resolved':'active',
                    (int)$diagnosis['id'],$diagnosis['notes']?:null,(int)($diagnosis['diagnosed_by']??0)?:null,
                ];
                if($hasBranch)array_splice($values,1,0,[(int)($diagnosis['branch_id']??0)?:null]);
                $insertChart->execute($values);
            }
        }
    }catch(Throwable $chartBackfillError){
        error_log('Version 0.5.9.8.10 diagnosis dental-chart backfill skipped: ' . $chartBackfillError->getMessage());
    }

    setting_set('system','application_version','0.5.9.8.10');
    setup_record_history($pdo,'0.5.9.8.10','billing_service_compatibility_tooth_specific_caries_surfaces_and_diagnosis_chart_sync');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.10',
        'features'=>[
            'billing_before_migration_compatibility','procedure_service_column_repair','branch_aware_service_backfill',
            'future_procedure_catalog_binding','authorized_fee_fallback_preserved',
            'anterior_caries_surface_labels','posterior_caries_surface_labels','surface_server_validation',
            'encounter_diagnosis_to_dental_chart_sync','mixed_dentition_chart_visibility','existing_diagnosis_chart_backfill',
        ],
    ]);
}

function setup_v059810_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('procedures'),'service_id')
        && setup_index_exists($pdo,table('procedures'),'idx_proc_service')
        && version_compare(installed_application_version(),'0.5.9.8.10','>=');
}

function setup_apply_v059811(PDO $pdo): void
{
    $payments=table('payments');
    setup_add_column($pdo,$payments,'payer_name','VARCHAR(190) NULL AFTER `reference_no`');
    setup_add_column($pdo,$payments,'payer_relationship','VARCHAR(40) NULL AFTER `payer_name`');

    setting_set('system','application_version','0.5.9.8.11');
    setup_record_history($pdo,'0.5.9.8.11','procedure_first_cashier_register_patient_payee_search_and_payment_identity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.11',
        'features'=>[
            'working_cashier_patient_search','patient_and_payee_payment_identity','procedure_first_pos_register',
            'completed_procedure_amount_queue_preview','open_invoice_and_unbilled_visit_selector',
            'cashier_layout_class_alignment','direct_account_payment','receipt_payee_audit',
        ],
    ]);
}

function setup_v059811_ready(PDO $pdo): bool
{
    return setup_column_exists($pdo,table('payments'),'payer_name')
        && setup_column_exists($pdo,table('payments'),'payer_relationship')
        && version_compare(installed_application_version(),'0.5.9.8.11','>=');
}

function setup_apply_v059812(PDO $pdo): void
{
    // Repair the exact structural fields used by the procedure-first Cashier
    // Register. Definitions intentionally omit AFTER clauses so this repair
    // also works on older or partially migrated table layouts.
    $repairs = [
        table('queue_entries') => [
            'billing_at' => 'DATETIME NULL',
        ],
        table('clinical_encounters') => [
            'branch_id' => 'BIGINT UNSIGNED NULL',
            'queue_entry_id' => 'BIGINT UNSIGNED NULL',
            'finalized_at' => 'DATETIME NULL',
        ],
        table('procedures') => [
            'branch_id' => 'BIGINT UNSIGNED NULL',
            'service_id' => 'BIGINT UNSIGNED NULL',
            'encounter_id' => 'BIGINT UNSIGNED NULL',
            'treatment_plan_item_id' => 'BIGINT UNSIGNED NULL',
        ],
        table('treatment_plan_items') => [
            'estimated_fee' => 'DECIMAL(12,2) NOT NULL DEFAULT 0.00',
        ],
        table('invoices') => [
            'branch_id' => 'BIGINT UNSIGNED NULL',
            'encounter_id' => 'BIGINT UNSIGNED NULL',
            'queue_entry_id' => 'BIGINT UNSIGNED NULL',
            'paid_amount' => 'DECIMAL(12,2) NOT NULL DEFAULT 0.00',
        ],
        table('invoice_items') => [
            'procedure_id' => 'BIGINT UNSIGNED NULL',
            'clinical_usage_item_id' => 'BIGINT UNSIGNED NULL',
        ],
        table('payments') => [
            'payer_name' => 'VARCHAR(190) NULL',
            'payer_relationship' => 'VARCHAR(40) NULL',
        ],
    ];
    foreach ($repairs as $tableName => $columns) {
        if (!setup_table_exists($pdo,$tableName)) continue;
        foreach ($columns as $column => $definition) setup_add_column($pdo,$tableName,$column,$definition);
    }

    if (setup_table_exists($pdo,table('invoice_items'))) {
        if (setup_column_exists($pdo,table('invoice_items'),'procedure_id') && !setup_index_exists($pdo,table('invoice_items'),'idx_invoice_item_procedure')) {
            try { $pdo->exec('ALTER TABLE `' . table('invoice_items') . '` ADD KEY `idx_invoice_item_procedure` (`procedure_id`)'); } catch (Throwable $ignored) {}
        }
        if (setup_column_exists($pdo,table('invoice_items'),'clinical_usage_item_id') && !setup_index_exists($pdo,table('invoice_items'),'idx_invoice_item_usage')) {
            try { $pdo->exec('ALTER TABLE `' . table('invoice_items') . '` ADD KEY `idx_invoice_item_usage` (`clinical_usage_item_id`)'); } catch (Throwable $ignored) {}
        }
    }

    // Recover branch ownership for historical rows when the column existed only
    // after an interrupted migration. Null remains allowed when no source branch
    // can be established safely.
    try {
        if (setup_table_exists($pdo,table('clinical_encounters')) && setup_table_exists($pdo,table('patients'))) {
            $pdo->exec('UPDATE `' . table('clinical_encounters') . '` ce JOIN `' . table('patients') . '` p ON p.id=ce.patient_id SET ce.branch_id=COALESCE(ce.branch_id,p.branch_id) WHERE ce.branch_id IS NULL');
        }
        if (setup_table_exists($pdo,table('procedures')) && setup_table_exists($pdo,table('patients'))) {
            $pdo->exec('UPDATE `' . table('procedures') . '` pr JOIN `' . table('patients') . '` p ON p.id=pr.patient_id SET pr.branch_id=COALESCE(pr.branch_id,p.branch_id) WHERE pr.branch_id IS NULL');
        }
        if (setup_table_exists($pdo,table('invoices')) && setup_table_exists($pdo,table('patients'))) {
            $pdo->exec('UPDATE `' . table('invoices') . '` i JOIN `' . table('patients') . '` p ON p.id=i.patient_id SET i.branch_id=COALESCE(i.branch_id,p.branch_id) WHERE i.branch_id IS NULL');
        }
        if (setup_table_exists($pdo,table('queue_entries'))) {
            $pdo->exec('UPDATE `' . table('queue_entries') . '` SET billing_at=COALESCE(billing_at,updated_at,checked_in_at) WHERE status IN ("waiting_billing","cashier_called") AND billing_at IS NULL');
        }
    } catch (Throwable $backfillError) {
        error_log('Version 0.5.9.8.12 cashier compatibility backfill skipped: ' . $backfillError->getMessage());
    }

    setting_set('system','application_version','0.5.9.8.12');
    setup_record_history($pdo,'0.5.9.8.12','cashier_register_page_load_compatibility_and_partial_schema_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.12',
        'features'=>[
            'cashier_page_load_failure_repair','partial_billing_schema_detection','legacy_column_safe_charge_preview',
            'cashier_queue_fallback','clinical_charge_preview_fallback','cashier_required_column_repair',
            'exact_update_required_message','procedure_amount_register_preserved',
        ],
    ]);
}

function setup_v059812_ready(PDO $pdo): bool
{
    $required = [
        table('queue_entries') => ['billing_at'],
        table('clinical_encounters') => ['branch_id','queue_entry_id','finalized_at'],
        table('procedures') => ['branch_id','service_id','encounter_id','treatment_plan_item_id'],
        table('treatment_plan_items') => ['estimated_fee'],
        table('invoices') => ['branch_id','encounter_id','queue_entry_id','paid_amount'],
        table('invoice_items') => ['procedure_id','clinical_usage_item_id'],
        table('payments') => ['payer_name','payer_relationship'],
    ];
    foreach ($required as $tableName => $columns) {
        if (!setup_table_exists($pdo,$tableName)) return false;
        foreach ($columns as $column) if (!setup_column_exists($pdo,$tableName,$column)) return false;
    }
    return version_compare(installed_application_version(),'0.5.9.8.12','>=');
}

function setup_apply_v059813(PDO $pdo): void
{
    // Code-only compatibility release. The billing schema repair remains owned
    // by v0.5.9.8.12; this update records that the corrected URL helper and
    // pre-render patient-search endpoint are installed.
    setting_set('system','application_version','0.5.9.8.13');
    setup_record_history($pdo,'0.5.9.8.13','cashier_register_patient_search_url_helper_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.13',
        'features'=>[
            'cashier_patient_search_render_failure_repair','canonical_app_absolute_url_usage',
            'legacy_app_url_compatibility_alias','pre_render_endpoint_resolution',
            'cashier_register_partial_output_error_prevention',
        ],
    ]);
}

function setup_v059813_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.13','>=');
}


function setup_apply_v059814(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.14_cashier_windows_multi_appointment.sql');

    $cashierSession = table('cashier_sessions');
    setup_add_column($pdo,$cashierSession,'window_id','BIGINT UNSIGNED NULL AFTER `cashier_user_id`');
    setup_add_column($pdo,$cashierSession,'window_name','VARCHAR(100) NULL AFTER `window_id`');
    setup_add_column($pdo,$cashierSession,'locked_patient_id','BIGINT UNSIGNED NULL AFTER `window_name`');
    setup_add_column($pdo,$cashierSession,'locked_queue_entry_id','BIGINT UNSIGNED NULL AFTER `locked_patient_id`');
    setup_add_column($pdo,$cashierSession,'transaction_locked_at','DATETIME NULL AFTER `locked_queue_entry_id`');
    setup_add_column($pdo,$cashierSession,'transaction_lock_note','VARCHAR(500) NULL AFTER `transaction_locked_at`');
    if (!setup_index_exists($pdo,$cashierSession,'idx_cashier_session_window')) $pdo->exec('ALTER TABLE `' . $cashierSession . '` ADD KEY `idx_cashier_session_window` (`branch_id`,`window_id`,`status`,`opened_at`)');
    if (!setup_index_exists($pdo,$cashierSession,'idx_cashier_session_lock')) $pdo->exec('ALTER TABLE `' . $cashierSession . '` ADD KEY `idx_cashier_session_lock` (`locked_patient_id`,`locked_queue_entry_id`,`status`)');

    setup_add_column($pdo,table('payments'),'cashier_window_id','BIGINT UNSIGNED NULL AFTER `cashier_session_id`');
    if (!setup_index_exists($pdo,table('payments'),'idx_payment_window')) $pdo->exec('ALTER TABLE `' . table('payments') . '` ADD KEY `idx_payment_window` (`cashier_window_id`,`created_at`)');

    $appointments = table('appointments');
    setup_add_column($pdo,$appointments,'series_id','BIGINT UNSIGNED NULL AFTER `updated_at`');
    setup_add_column($pdo,$appointments,'series_sequence','SMALLINT UNSIGNED NULL AFTER `series_id`');
    setup_add_column($pdo,$appointments,'planned_visit_label','VARCHAR(190) NULL AFTER `series_sequence`');
    if (!setup_index_exists($pdo,$appointments,'idx_appt_series')) $pdo->exec('ALTER TABLE `' . $appointments . '` ADD KEY `idx_appt_series` (`series_id`,`series_sequence`)');
    $appointmentServices = table('appointment_services');
    if (setup_table_exists($pdo,$appointmentServices)) {
        if (!setup_index_exists($pdo,$appointmentServices,'idx_appointment_service_dentist')) $pdo->exec('ALTER TABLE `' . $appointmentServices . '` ADD KEY `idx_appointment_service_dentist` (`dentist_user_id`,`start_at`,`end_at`)');
        if (!setup_index_exists($pdo,$appointmentServices,'idx_appointment_service_inhouse')) $pdo->exec('ALTER TABLE `' . $appointmentServices . '` ADD KEY `idx_appointment_service_inhouse` (`inhouse_provider_user_id`,`start_at`,`end_at`)');
        if (!setup_index_exists($pdo,$appointmentServices,'idx_appointment_service_chair')) $pdo->exec('ALTER TABLE `' . $appointmentServices . '` ADD KEY `idx_appointment_service_chair` (`chair_id`,`start_at`,`end_at`)');
    }

    $pdo->exec('INSERT IGNORE INTO `' . table('cashier_windows') . '` (branch_id,window_code,window_name,is_active,created_at) SELECT id,"W1","Cashier Window 1",1,NOW() FROM `' . table('branches') . '`');
    $pdo->exec('UPDATE `' . $cashierSession . '` cs SET cs.window_id=(SELECT cw.id FROM `' . table('cashier_windows') . '` cw WHERE cw.branch_id=cs.branch_id AND cw.is_active=1 ORDER BY cw.id LIMIT 1),cs.window_name=COALESCE(cs.window_name,"Cashier Window 1") WHERE cs.window_id IS NULL');

    if (setup_table_exists($pdo,table('appointment_services'))) {
        $stmt=$pdo->query('SELECT a.* FROM `' . $appointments . '` a LEFT JOIN `' . table('appointment_services') . '` aps ON aps.appointment_id=a.id WHERE aps.id IS NULL ORDER BY a.id');
        $insert=$pdo->prepare('INSERT INTO `' . table('appointment_services') . '` (appointment_id,service_id,sequence_no,service_name,provider_type,required_privilege_code,duration_minutes,preparation_minutes,cleanup_minutes,dentist_user_id,dentist_name,inhouse_provider_user_id,inhouse_provider_name,chair_id,chair_name,start_at,end_at,status,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,"planned",NOW())');
        foreach($stmt->fetchAll() as $row){
            $service=(int)($row['service_id']??0)>0?service_definition((int)$row['service_id'],(int)($row['branch_id']??0)):null;
            $duration=max(5,(int)round((strtotime((string)$row['end_at'])-strtotime((string)$row['start_at']))/60));
            $insert->execute([(int)$row['id'],$row['service_id']?:null,1,(string)$row['service_name'],(string)($row['provider_type']??'dentist'),$service?service_required_privilege_code($service):null,$service?(int)$service['duration_minutes']:$duration,$service?(int)$service['preparation_minutes']:0,$service?(int)$service['cleanup_minutes']:0,$row['dentist_user_id']?:null,$row['dentist_name']?:null,$row['inhouse_provider_user_id']?:null,$row['inhouse_provider_name']?:null,$row['chair_id']?:null,$row['chair_name']?:null,(string)$row['start_at'],(string)$row['end_at']]);
        }
    }

    setting_set('system','application_version','0.5.9.8.14');
    setup_record_history($pdo,'0.5.9.8.14','cashier_windows_transaction_lock_multi_service_future_series');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.14',
        'features'=>[
            'cashier_window_sessions','cashier_transaction_lock','cashier_window_payment_attribution',
            'audited_zero_balance_checkout','mandatory_digital_payment_reference','multi_service_appointments',
            'combined_visit_duration_and_privilege_validation','future_appointment_series','custom_multi_visit_treatment_plans','patient_upcoming_visit_visibility',
        ],
    ]);
}

function setup_v059814_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.14','>=')
        && setup_table_exists($pdo,table('cashier_windows'))
        && setup_table_exists($pdo,table('cashier_checkouts'))
        && setup_table_exists($pdo,table('appointment_services'))
        && setup_table_exists($pdo,table('appointment_series'))
        && setup_column_exists($pdo,table('cashier_sessions'),'window_id')
        && setup_column_exists($pdo,table('cashier_sessions'),'locked_patient_id')
        && setup_column_exists($pdo,table('payments'),'cashier_window_id')
        && setup_column_exists($pdo,table('appointments'),'series_id');
}


function setup_apply_v059815(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.15_clinical_billing_handoff.sql');

    $planItems=table('treatment_plan_items');
    $procedures=table('procedures');
    if (!setup_index_exists($pdo,$planItems,'idx_plan_item_service')) {
        $pdo->exec('ALTER TABLE `' . $planItems . '` ADD KEY `idx_plan_item_service` (`service_id`)');
    }
    if (!setup_index_exists($pdo,$procedures,'idx_procedure_billing_status')) {
        $pdo->exec('ALTER TABLE `' . $procedures . '` ADD KEY `idx_procedure_billing_status` (`encounter_id`,`billing_status`,`status`)');
    }

    setting_set('system','application_version','0.5.9.8.15');
    setup_record_history($pdo,'0.5.9.8.15','canonical_clinical_procedure_billing_handoff');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.15',
        'features'=>[
            'service_catalog_fee_policy','treatment_plan_fee_authorization','canonical_procedure_sources',
            'immutable_completed_procedure_fee_snapshot','multiple_procedure_invoice_generation',
            'automatic_clinical_invoice_handoff','finalization_block_for_missing_fee',
            'authorized_no_charge_separation','legacy_procedure_billing_source_correction',
        ],
    ]);
}

function setup_v059815_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.15','>=')
        && setup_column_exists($pdo,table('service_catalog'),'fee_status')
        && setup_column_exists($pdo,table('service_catalog'),'no_charge_reason')
        && setup_column_exists($pdo,table('treatment_plan_items'),'service_id')
        && setup_column_exists($pdo,table('treatment_plan_items'),'fee_status')
        && setup_column_exists($pdo,table('procedures'),'service_name_snapshot')
        && setup_column_exists($pdo,table('procedures'),'approved_fee_snapshot')
        && setup_column_exists($pdo,table('procedures'),'fee_status')
        && setup_column_exists($pdo,table('procedures'),'billing_status')
        && setup_index_exists($pdo,table('treatment_plan_items'),'idx_plan_item_service')
        && setup_index_exists($pdo,table('procedures'),'idx_procedure_billing_status');
}


function setup_apply_v059816(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.16_workflow_governance.sql');

    $invoiceItems=table('invoice_items');
    $inhouseResults=table('inhouse_checkup_results');
    $encounters=table('clinical_encounters');
    if (!setup_index_exists($pdo,$invoiceItems,'idx_invoice_item_inhouse_result')) {
        $pdo->exec('ALTER TABLE `' . $invoiceItems . '` ADD KEY `idx_invoice_item_inhouse_result` (`inhouse_checkup_result_id`)');
    }
    if (!setup_index_exists($pdo,$inhouseResults,'idx_inhouse_result_billing')) {
        $pdo->exec('ALTER TABLE `' . $inhouseResults . '` ADD KEY `idx_inhouse_result_billing` (`billing_status`,`invoice_id`,`completed_at`)');
    }
    if (!setup_index_exists($pdo,$encounters,'idx_encounter_paused')) {
        $pdo->exec('ALTER TABLE `' . $encounters . '` ADD KEY `idx_encounter_paused` (`dentist_user_id`,`paused_at`,`status`)');
    }

    setting_set('system','application_version','0.5.9.8.16');
    setup_record_history($pdo,'0.5.9.8.16','workflow_governance_clinical_transfer_inhouse_billing');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.16',
        'features'=>[
            'cashier_billing_correction_route','dentist_pause_resume','dentist_consultation_and_transfer',
            'emergency_takeover_with_own_credentials','identity_safe_clinical_addenda',
            'explicit_inhouse_fee_policy','automatic_inhouse_invoice_lines',
            'segment_level_conflict_reporting','multi_provider_future_visit_plans',
        ],
    ]);
}

function setup_v059816_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.16','>=')
        && setup_table_exists($pdo,table('billing_correction_requests'))
        && setup_table_exists($pdo,table('financial_correction_addenda'))
        && setup_table_exists($pdo,table('clinical_encounter_transfers'))
        && setup_column_exists($pdo,table('clinical_encounters'),'paused_at')
        && setup_column_exists($pdo,table('clinical_encounters'),'responsibility_transfer_id')
        && setup_column_exists($pdo,table('inhouse_checkup_results'),'fee_status')
        && setup_column_exists($pdo,table('inhouse_checkup_results'),'approved_fee_snapshot')
        && setup_column_exists($pdo,table('inhouse_checkup_results'),'invoice_id')
        && setup_column_exists($pdo,table('invoice_items'),'inhouse_checkup_result_id')
        && setup_index_exists($pdo,table('invoice_items'),'idx_invoice_item_inhouse_result')
        && setup_index_exists($pdo,table('inhouse_checkup_results'),'idx_inhouse_result_billing')
        && setup_index_exists($pdo,table('clinical_encounters'),'idx_encounter_paused');
}

function setup_apply_v059817(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.17');
    setup_record_history($pdo,'0.5.9.8.17','dentist_four_phase_cashier_exception_workspace');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.17',
        'features'=>[
            'dentist_review_assess_treat_close_workspace',
            'phase_aware_clinical_navigation',
            'cashier_patient_call_and_transaction_lock',
            'separate_cashier_exception_workspace',
            'supervisor_exception_navigation',
        ],
    ]);
}

function setup_v059817_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.17','>=');
}


function setup_apply_v059818(PDO $pdo): void
{
    // Code-only scalability release. No schema migration is required; this
    // records that the compact, paginated management workspaces are installed.
    setting_set('system','application_version','0.5.9.8.18');
    setup_record_history($pdo,'0.5.9.8.18','scalable_management_workspaces');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.18',
        'features'=>[
            'compact_paginated_management_tables',
            'single_selected_record_management_panel',
            'bulk_inhouse_team_assignment',
            'server_side_management_filters',
            'on_demand_management_lookups',
            'removal_of_repeated_row_forms_and_dialogs',
        ],
    ]);
}

function setup_v059818_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.18','>=');
}

function setup_apply_v0598181(PDO $pdo): void
{
    // Corrects the update registry omitted from the v0.5.9.8.18 package.
    // Without this marker the package VERSION reported 0.5.9.8.18 while the
    // update handler could only advance the database marker through 0.5.9.8.17.
    setting_set('system','application_version','0.5.9.8.18.1');
    setup_record_history($pdo,'0.5.9.8.18.1','v059818_update_registry_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.18.1',
        'features'=>[
            'v059818_update_registry',
            'package_and_installed_version_alignment',
            'code_only_release_history_tracking',
        ],
    ]);
}

function setup_v0598181_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.18.1','>=');
}


function setup_apply_v059819(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.19_itemized_payment_receipts.sql');
    setting_set('system','application_version','0.5.9.8.19');
    setup_record_history($pdo,'0.5.9.8.19','itemized_payment_receipts_compact_week_schedule');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.19',
        'features'=>[
            'payment_specific_itemized_receipts',
            'partial_payment_item_allocation_snapshots',
            'legacy_payment_receipt_allocation_compatibility',
            'compact_weekly_appointment_schedule',
            'live_queue_status_icons',
        ],
    ]);
}

function setup_v059819_ready(PDO $pdo): bool
{
    $table = table('payment_item_allocations');
    return version_compare(installed_application_version(),'0.5.9.8.19','>=')
        && setup_table_exists($pdo,$table)
        && setup_column_exists($pdo,$table,'payment_id')
        && setup_column_exists($pdo,$table,'invoice_item_id')
        && setup_column_exists($pdo,$table,'amount_applied')
        && setup_column_exists($pdo,$table,'remaining_after_snapshot')
        && setup_index_exists($pdo,$table,'uq_payment_item_allocation_sequence')
        && setup_index_exists($pdo,$table,'idx_payment_item_allocation_invoice');
}


function setup_apply_v059820(PDO $pdo): void
{
    $table = table('payment_item_allocations');
    if (!setup_table_exists($pdo,$table)) {
        setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.19_itemized_payment_receipts.sql');
    }

    // Repair partially-created v0.5.9.8.19 receipt tables without deleting any
    // payments. Optional foreign keys are deliberately not required here; the
    // receipt snapshot is an audit aid and must never make patient checkout
    // unavailable on an upgraded installation.
    if (!setup_column_exists($pdo,$table,'id')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
    } elseif (!setup_index_exists($pdo,$table,'PRIMARY')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD PRIMARY KEY (`id`)');
    }

    $columns = [
        ['payment_id','BIGINT UNSIGNED NULL AFTER `id`'],
        ['invoice_id','BIGINT UNSIGNED NULL AFTER `payment_id`'],
        ['invoice_item_id','BIGINT UNSIGNED NULL AFTER `invoice_id`'],
        ['sequence_no','INT UNSIGNED NOT NULL DEFAULT 1 AFTER `invoice_item_id`'],
        ['item_code_snapshot','VARCHAR(80) NULL AFTER `sequence_no`'],
        ['description_snapshot',"VARCHAR(255) NOT NULL DEFAULT 'Invoice payment' AFTER `item_code_snapshot`"],
        ['quantity_snapshot','DECIMAL(10,2) NOT NULL DEFAULT 1.00 AFTER `description_snapshot`'],
        ['unit_price_snapshot','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `quantity_snapshot`'],
        ['line_net_snapshot','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `unit_price_snapshot`'],
        ['payable_snapshot','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `line_net_snapshot`'],
        ['previous_paid_snapshot','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `payable_snapshot`'],
        ['amount_applied','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `previous_paid_snapshot`'],
        ['remaining_after_snapshot','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `amount_applied`'],
        ['created_at','DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `remaining_after_snapshot`'],
    ];
    foreach ($columns as [$column,$definition]) setup_add_column($pdo,$table,$column,$definition);

    if (!setup_index_exists($pdo,$table,'idx_payment_item_allocation_payment')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD KEY `idx_payment_item_allocation_payment` (`payment_id`,`id`)');
    }
    if (!setup_index_exists($pdo,$table,'idx_payment_item_allocation_invoice')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD KEY `idx_payment_item_allocation_invoice` (`invoice_id`,`invoice_item_id`)');
    }
    if (!setup_index_exists($pdo,$table,'uq_payment_item_allocation_sequence')) {
        // A partially-run import may have repeated rows. Keep the oldest audit
        // snapshot before enforcing one sequence number per payment.
        try {
            $pdo->exec('DELETE newer FROM `' . $table . '` newer JOIN `' . $table . '` older ON older.payment_id=newer.payment_id AND older.sequence_no=newer.sequence_no AND older.id<newer.id');
        } catch (Throwable) {
            // The unique index statement below will provide the actionable error.
        }
        $pdo->exec('ALTER TABLE `' . $table . '` ADD UNIQUE KEY `uq_payment_item_allocation_sequence` (`payment_id`,`sequence_no`)');
    }

    $receiptSnapshotsBackfilled=0;
    try {
        $missingPayments=$pdo->query(
            'SELECT p.id,p.invoice_id FROM `' . table('payments') . '` p '
            . 'LEFT JOIN `' . $table . '` a ON a.payment_id=p.id WHERE a.id IS NULL ORDER BY p.id LIMIT 1000'
        )->fetchAll();
        foreach($missingPayments as $missingPayment){
            if(revenue_store_payment_item_allocations($pdo,(int)$missingPayment['id'],(int)$missingPayment['invoice_id']))$receiptSnapshotsBackfilled++;
        }
    } catch(Throwable $backfillError){
        error_log('Payment receipt snapshot backfill warning: '.$backfillError->getMessage());
    }

    setting_set('system','application_version','0.5.9.8.20');
    setup_record_history($pdo,'0.5.9.8.20','cashier_billing_recovery_receipt_history');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.20',
        'features'=>[
            'non_blocking_payment_receipt_allocation',
            'receipt_snapshot_schema_repair',
            'legacy_invoice_item_compatibility',
            'per_payment_receipt_history',
            'safe_print_receipt_fallback',
            'cashier_register_failure_containment',
        ],
        'receipt_snapshots_backfilled'=>$receiptSnapshotsBackfilled,
    ]);
}

function setup_v059820_ready(PDO $pdo): bool
{
    $table = table('payment_item_allocations');
    $required = [
        'payment_id','invoice_id','invoice_item_id','sequence_no','item_code_snapshot',
        'description_snapshot','quantity_snapshot','unit_price_snapshot','line_net_snapshot',
        'payable_snapshot','previous_paid_snapshot','amount_applied','remaining_after_snapshot','created_at',
    ];
    if (!version_compare(installed_application_version(),'0.5.9.8.20','>=') || !setup_table_exists($pdo,$table)) return false;
    foreach ($required as $column) if (!setup_column_exists($pdo,$table,$column)) return false;
    return setup_index_exists($pdo,$table,'uq_payment_item_allocation_sequence')
        && setup_index_exists($pdo,$table,'idx_payment_item_allocation_invoice')
        && setup_index_exists($pdo,$table,'idx_payment_item_allocation_payment');
}


function setup_apply_v0598201(PDO $pdo): void
{
    // Deployment correction: v0.5.9.8.20 was accidentally packaged inside
    // an extra top-level folder, so extracting it into the application root
    // could leave the live billing.php unchanged. This code-only marker lets
    // administrators verify that the corrected root-flat package is active.
    setting_set('system','application_version','0.5.9.8.20.1');
    setup_record_history($pdo,'0.5.9.8.20.1','billing_root_package_and_stale_helper_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.20.1',
        'features'=>[
            'root_flat_hotfix_packaging',
            'stale_app_url_compatibility_alias',
            'corrected_billing_register_deployment',
            'non_blocking_itemized_receipt_rendering',
        ],
    ]);
}

function setup_v0598201_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.20.1','>=');
}

function setup_apply_v059821(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.21');
    setup_record_history($pdo,'0.5.9.8.21','visit_centric_patient_journey');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.21',
        'features'=>[
            'one_row_per_appointment_or_walk_in_visit',
            'visit_link_integrity_detection',
            'role_aware_next_action_routing',
            'future_visit_and_active_queue_separation',
            'visit_specific_timeline_and_financial_summary',
            'multi_branch_visit_command_center',
        ],
    ]);
}

function setup_v059821_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.21','>=');
}

function setup_apply_v059822(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.22_visit_state_reconciliation.sql');

    $linkTables = [
        'appointments'=>'idx_appointment_visit',
        'queue_entries'=>'idx_queue_visit',
        'clinical_encounters'=>'idx_encounter_visit',
        'invoices'=>'idx_invoice_visit',
        'follow_ups'=>'idx_followup_visit',
        'visit_workflow_events'=>'idx_workflow_visit',
    ];
    foreach ($linkTables as $tableName=>$indexName) {
        $table = table($tableName);
        setup_add_column($pdo,$table,'visit_id','BIGINT UNSIGNED NULL AFTER `id`');
        if (!setup_index_exists($pdo,$table,$indexName)) {
            $columns = $tableName === 'visit_workflow_events' ? '(`visit_id`,`created_at`)'
                : ($tableName === 'invoices' ? '(`visit_id`,`status`)'
                : ($tableName === 'follow_ups' ? '(`visit_id`,`status`)' : '(`visit_id`)'));
            $pdo->exec('ALTER TABLE `' . $table . '` ADD KEY `' . $indexName . '` ' . $columns);
        }
    }
    if(!setup_index_exists($pdo,table('visits'),'uq_visit_encounter')){
        $pdo->exec('ALTER TABLE `' . table('visits') . '` ADD UNIQUE KEY `uq_visit_encounter` (`encounter_id`)');
    }

    $linked = visit_backfill_links($pdo,0,10000);
    $scan = visit_scan_integrity($pdo,0);
    setting_set('system','application_version','0.5.9.8.22');
    setup_record_history($pdo,'0.5.9.8.22','canonical_visit_state_and_operations_reconciliation');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.22',
        'features'=>[
            'canonical_visit_id_across_appointment_queue_clinical_billing_followup',
            'controlled_visit_state_history',
            'operations_exception_center',
            'safe_rebuild_invoice_and_lock_release_actions',
            'role_owned_operational_exceptions',
            'visit_specific_timeline_linkage',
        ],
        'records_linked'=>$linked,
        'issues_detected'=>(int)($scan['detected']??0),
    ]);
}

function setup_v059822_ready(PDO $pdo): bool
{
    if (!version_compare(installed_application_version(),'0.5.9.8.22','>=')) return false;
    foreach (['visits','visit_state_history','visit_integrity_issues'] as $tableName) {
        if (!setup_table_exists($pdo,table($tableName))) return false;
    }
    foreach (['appointments','queue_entries','clinical_encounters','invoices','follow_ups','visit_workflow_events'] as $tableName) {
        if (!setup_column_exists($pdo,table($tableName),'visit_id')) return false;
    }
    return true;
}

function setup_apply_v059823(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.23');
    setup_record_history($pdo,'0.5.9.8.23','role_flow_and_active_treatment_navigation_completion');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.23',
        'features'=>[
            'persistent_active_patient_treatment_context',
            'one_click_return_to_current_encounter',
            'direct_patient_data_access_from_chairside_workspace',
            'dentist_today_navigation_starts_at_treatment_dashboard',
            'active_treatment_survives_calendar_day_rollover',
            'role_flow_navigation_audit',
            'clinical_task_route_alignment',
            'dental_assistant_appointment_navigation',
            'cashier_and_inventory_print_scope_minimization',
        ],
    ]);
}

function setup_v059823_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.23','>=');
}


function setup_apply_v059824(PDO $pdo): void
{
    $vitalsTable = table('vital_signs');
    $columns = [
        'oxygen_saturation' => 'DECIMAL(5,2) NULL AFTER `respiratory_rate`',
        'height_cm' => 'DECIMAL(7,2) NULL AFTER `weight_kg`',
        'bmi' => 'DECIMAL(6,2) NULL AFTER `height_cm`',
        'bp_arm' => "ENUM('left','right','other') NULL AFTER `blood_pressure_diastolic`",
        'bp_position' => "ENUM('seated','standing','supine','other') NULL AFTER `bp_arm`",
        'bp_cuff_size' => 'VARCHAR(30) NULL AFTER `bp_position`',
        'bp_device_type' => "ENUM('automatic','manual','other') NULL AFTER `bp_cuff_size`",
        'bp_measurement_confirmed' => 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `bp_device_type`',
        'bp_repeat_systolic' => 'SMALLINT UNSIGNED NULL AFTER `bp_measurement_confirmed`',
        'bp_repeat_diastolic' => 'SMALLINT UNSIGNED NULL AFTER `bp_repeat_systolic`',
        'bp_repeat_after_minutes' => 'SMALLINT UNSIGNED NULL AFTER `bp_repeat_diastolic`',
        'bp_symptoms_reviewed' => 'TINYINT(1) NOT NULL DEFAULT 0 AFTER `bp_repeat_after_minutes`',
        'bp_symptoms' => 'VARCHAR(500) NULL AFTER `bp_symptoms_reviewed`',
        'measurement_notes' => 'TEXT NULL AFTER `medical_changes`',
    ];
    foreach ($columns as $column => $definition) setup_add_column($pdo, $vitalsTable, $column, $definition);
    setting_set('system','application_version','0.5.9.8.24');
    setup_record_history($pdo,'0.5.9.8.24','structured_chairside_vital_sign_capture');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.24',
        'features'=>[
            'separated_intake_and_vital_sign_capture',
            'guided_blood_pressure_measurement_setup',
            'repeat_blood_pressure_reading',
            'blood_pressure_symptom_review',
            'oxygen_saturation_height_and_bmi',
            'measurement_context_and_notes',
            'adult_screening_cues_and_pediatric_review_notice',
            'chairside_and_inhouse_vital_sign_ui_alignment',
        ],
    ]);
}

function setup_v059824_ready(PDO $pdo): bool
{
    if (!version_compare(installed_application_version(),'0.5.9.8.24','>=')) return false;
    foreach (['oxygen_saturation','height_cm','bmi','bp_arm','bp_position','bp_cuff_size','bp_device_type','bp_measurement_confirmed','bp_repeat_systolic','bp_repeat_diastolic','bp_repeat_after_minutes','bp_symptoms_reviewed','bp_symptoms','measurement_notes'] as $column) {
        if (!setup_column_exists($pdo,table('vital_signs'),$column)) return false;
    }
    return true;
}

function setup_apply_v059825(PDO $pdo): void
{
    $encounters = table('clinical_encounters');
    $columns = [
        'completion_destination' => 'VARCHAR(40) NULL',
        'no_payment_reason' => 'VARCHAR(500) NULL AFTER `completion_destination`',
        'discharge_summary_json' => 'LONGTEXT NULL AFTER `no_payment_reason`',
        'billing_handoff_amount' => 'DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discharge_summary_json`',
        'billing_handoff_at' => 'DATETIME NULL AFTER `billing_handoff_amount`',
        'discharged_at' => 'DATETIME NULL AFTER `billing_handoff_at`',
        'chair_released_at' => 'DATETIME NULL AFTER `discharged_at`',
    ];
    foreach ($columns as $column => $definition) setup_add_column($pdo, $encounters, $column, $definition);
    setting_set('system','application_version','0.5.9.8.25');
    setup_record_history($pdo,'0.5.9.8.25','guided_treatment_completion_discharge_and_billing_handoff');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.25',
        'features'=>[
            'twelve_point_live_completion_checklist',
            'explicit_cashier_no_payment_and_pause_outcomes',
            'authorized_no_payment_discharge_validation',
            'automatic_chair_release_after_clinical_completion',
            'signed_structured_discharge_summary',
            'verified_billing_handoff_amount',
            'printable_discharge_and_encounter_summary',
        ],
    ]);
}

function setup_v059825_ready(PDO $pdo): bool
{
    if (!version_compare(installed_application_version(),'0.5.9.8.25','>=')) return false;
    foreach (['completion_destination','no_payment_reason','discharge_summary_json','billing_handoff_amount','billing_handoff_at','discharged_at','chair_released_at'] as $column) {
        if (!setup_column_exists($pdo,table('clinical_encounters'),$column)) return false;
    }
    return true;
}

function setup_apply_v059826(PDO $pdo): void
{
    $encounters = table('clinical_encounters');
    $procedures = table('procedures');
    foreach ([
        'assistant_user_id' => 'BIGINT UNSIGNED NULL AFTER `dentist_name`',
        'assistant_name' => 'VARCHAR(150) NULL AFTER `assistant_user_id`',
        'assistant_assigned_by' => 'BIGINT UNSIGNED NULL AFTER `assistant_name`',
        'assistant_assigned_at' => 'DATETIME NULL AFTER `assistant_assigned_by`',
    ] as $column => $definition) setup_add_column($pdo,$encounters,$column,$definition);
    foreach ([
        'entered_by_user_id' => 'BIGINT UNSIGNED NULL AFTER `assistant_user_id`',
        'entered_by_role' => 'VARCHAR(30) NULL AFTER `entered_by_user_id`',
        'verified_by_user_id' => 'BIGINT UNSIGNED NULL AFTER `entered_by_role`',
        'verified_at' => 'DATETIME NULL AFTER `verified_by_user_id`',
    ] as $column => $definition) setup_add_column($pdo,$procedures,$column,$definition);
    if (!setup_index_exists($pdo,$encounters,'idx_encounter_assistant')) {
        $pdo->exec('ALTER TABLE `' . $encounters . '` ADD INDEX `idx_encounter_assistant` (`assistant_user_id`,`status`,`created_at`)');
    }
    $pdo->exec('UPDATE `' . $procedures . '` SET entered_by_user_id=COALESCE(entered_by_user_id,dentist_user_id),entered_by_role=COALESCE(NULLIF(entered_by_role,""),"dentist")');
    setting_set('system','application_version','0.5.9.8.26');
    setup_record_history($pdo,'0.5.9.8.26','encounter_dental_assistant_assignment_and_delegated_treatment_entries');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.26',
        'features'=>[
            'encounter_specific_dental_assistant_assignment',
            'assistant_assigned_dentist_patient_and_chair_workspace',
            'delegated_treatment_entry_documentation',
            'responsible_dentist_preserved_on_every_procedure',
            'dentist_verification_of_assistant_entries',
            'care_team_and_entry_attribution_in_record_summaries',
        ],
    ]);
}

function setup_v059826_ready(PDO $pdo): bool
{
    if (!version_compare(installed_application_version(),'0.5.9.8.26','>=')) return false;
    foreach (['assistant_user_id','assistant_name','assistant_assigned_by','assistant_assigned_at'] as $column) {
        if (!setup_column_exists($pdo,table('clinical_encounters'),$column)) return false;
    }
    foreach (['entered_by_user_id','entered_by_role','verified_by_user_id','verified_at'] as $column) {
        if (!setup_column_exists($pdo,table('procedures'),$column)) return false;
    }
    return setup_index_exists($pdo,table('clinical_encounters'),'idx_encounter_assistant');
}


function setup_apply_v059827(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.27');
    setup_record_history($pdo,'0.5.9.8.27','unified_patient_record_timeline_and_live_queue_synchronization');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.27',
        'features'=>[
            'role_aware_unified_patient_timeline',
            'vital_sign_trend_comparison',
            'latest_prescription_and_care_summary',
            'date_and_encounter_scoped_complete_record_printing',
            'automatic_queue_revision_polling',
            'in_place_queue_region_updates',
            'doctor_assistant_cashier_inhouse_and_waiting_room_live_sync',
        ],
    ]);
}

function setup_v059827_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.27','>=');
}

function setup_apply_v059828(PDO $pdo): void
{
    $items=table('inventory_items');
    $documents=table('inventory_documents');
    setup_add_column($pdo,$items,'barcode','VARCHAR(120) NULL AFTER `sku`');
    foreach ([
        'usage_destination'=>'VARCHAR(40) NULL AFTER `procedure_id`',
        'chair_id'=>'BIGINT UNSIGNED NULL AFTER `usage_destination`',
        'evidence_file_path'=>'VARCHAR(255) NULL AFTER `chair_id`',
        'evidence_original_name'=>'VARCHAR(255) NULL AFTER `evidence_file_path`',
    ] as $column=>$definition) setup_add_column($pdo,$documents,$column,$definition);
    if(!setup_index_exists($pdo,$items,'idx_inventory_barcode')){
        $pdo->exec('ALTER TABLE `' . $items . '` ADD INDEX `idx_inventory_barcode` (`barcode`)');
    }
    if(!setup_index_exists($pdo,$documents,'idx_inventory_document_encounter')){
        $pdo->exec('ALTER TABLE `' . $documents . '` ADD INDEX `idx_inventory_document_encounter` (`encounter_id`,`status`)');
    }
    if(!setup_index_exists($pdo,$documents,'idx_inventory_document_destination')){
        $pdo->exec('ALTER TABLE `' . $documents . '` ADD INDEX `idx_inventory_document_destination` (`usage_destination`,`chair_id`,`status`)');
    }
    setting_set('system','application_version','0.5.9.8.28');
    setup_record_history($pdo,'0.5.9.8.28','inventory_operations_workspace_and_adaptive_stock_transactions');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.28',
        'features'=>[
            'corrected_inventory_alert_counters',
            'operational_stock_and_movement_workspace',
            'adaptive_inventory_transaction_workflows',
            'dynamic_item_rows_and_stock_impact_preview',
            'fefo_lot_guidance',
            'active_encounter_dentist_assistant_and_chair_linkage',
            'original_issue_linked_returns',
            'barcode_ready_catalog_search',
            'protected_inventory_evidence_uploads',
        ],
    ]);
}

function setup_v059828_ready(PDO $pdo): bool
{
    if(!version_compare(installed_application_version(),'0.5.9.8.28','>=')) return false;
    foreach(['usage_destination','chair_id','evidence_file_path','evidence_original_name'] as $column){
        if(!setup_column_exists($pdo,table('inventory_documents'),$column)) return false;
    }
    return setup_column_exists($pdo,table('inventory_items'),'barcode')
        && setup_index_exists($pdo,table('inventory_items'),'idx_inventory_barcode')
        && setup_index_exists($pdo,table('inventory_documents'),'idx_inventory_document_encounter')
        && setup_index_exists($pdo,table('inventory_documents'),'idx_inventory_document_destination');
}

function setup_apply_v0598282(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.28.2');
    setup_record_history($pdo,'0.5.9.8.28.2','compact_tab_specific_inventory_filter_toolbar');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.28.2',
        'features'=>[
            'compact_desktop_inventory_filter_toolbar',
            'tab_specific_inventory_filters',
            'automatic_search_and_select_filtering',
            'inventory_table_visible_without_excess_filter_height',
            'filter_state_and_active_tab_preservation',
        ],
    ]);
}

function setup_v0598282_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.28.2','>=');
}


function setup_apply_v059829(PDO $pdo): void
{
    $issues=table('visit_integrity_issues');
    if(!setup_table_exists($pdo,$issues)) setup_apply_v059822($pdo);
    try{$pdo->exec('ALTER TABLE `' . $issues . '` MODIFY COLUMN `severity` ENUM("warning","urgent","critical","approval") NOT NULL DEFAULT "warning"');}catch(Throwable $e){error_log('Reconciliation severity extension warning: '.$e->getMessage());}
    foreach([
        'issue_group'=>"VARCHAR(40) NOT NULL DEFAULT 'workflow' AFTER `issue_code`",
        'source_module'=>"VARCHAR(40) NOT NULL DEFAULT 'operations' AFTER `issue_group`",
        'assigned_to_user_id'=>'BIGINT UNSIGNED NULL AFTER `responsible_role`',
        'assigned_by_user_id'=>'BIGINT UNSIGNED NULL AFTER `assigned_to_user_id`',
        'assigned_at'=>'DATETIME NULL AFTER `assigned_by_user_id`',
        'due_at'=>'DATETIME NULL AFTER `assigned_at`',
        'metadata_json'=>'JSON NULL AFTER `detail`',
    ] as $column=>$definition) setup_add_column($pdo,$issues,$column,$definition);
    if(!setup_index_exists($pdo,$issues,'idx_visit_integrity_group'))$pdo->exec('ALTER TABLE `' . $issues . '` ADD INDEX `idx_visit_integrity_group` (`issue_group`,`status`,`severity`,`last_detected_at`)');
    if(!setup_index_exists($pdo,$issues,'idx_visit_integrity_assignment'))$pdo->exec('ALTER TABLE `' . $issues . '` ADD INDEX `idx_visit_integrity_assignment` (`assigned_to_user_id`,`status`,`due_at`)');
    setting_set('system','application_version','0.5.9.8.29');
    setup_record_history($pdo,'0.5.9.8.29','workflow_exception_and_reconciliation_center');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.29',
        'features'=>[
            'cross_module_queue_chair_clinical_billing_inventory_reconciliation',
            'critical_urgent_warning_and_approval_groups',
            'audited_exception_assignment_and_due_dates',
            'safe_status_chair_cashier_and_stock_corrections',
            'automatic_reconciliation_refresh',
            'branch_and_role_scoped_exception_visibility',
        ],
    ]);
}

function setup_v059829_ready(PDO $pdo): bool
{
    if(!version_compare(installed_application_version(),'0.5.9.8.29','>='))return false;
    $issues=table('visit_integrity_issues');
    foreach(['issue_group','source_module','assigned_to_user_id','assigned_by_user_id','assigned_at','due_at','metadata_json'] as $column)if(!setup_column_exists($pdo,$issues,$column))return false;
    return setup_index_exists($pdo,$issues,'idx_visit_integrity_group')&&setup_index_exists($pdo,$issues,'idx_visit_integrity_assignment');
}

function setup_apply_v059830(PDO $pdo): void
{
    setup_execute_sql_file($pdo,DENTAL_ROOT . '/database/migrations/0.5.9.8.30_dashboard_guidance_help_support.sql');
    setting_set('system','application_version','0.5.9.8.30');
    setup_record_history($pdo,'0.5.9.8.30','compact_dashboard_guidance_and_help_support_center');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.30',
        'features'=>[
            'compact_dashboard_role_guidance_dialogs',
            'global_contextual_page_help',
            'role_aware_help_and_support_center',
            'attributable_staff_support_requests',
            'administrator_assignment_status_and_resolution_tracking',
            'clinic_support_contact_and_safe_reporting_guidance',
        ],
    ]);
}

function setup_v059830_ready(PDO $pdo): bool
{
    if(!version_compare(installed_application_version(),'0.5.9.8.30','>='))return false;
    $requests=table('support_requests');
    if(!setup_table_exists($pdo,$requests))return false;
    foreach(['branch_id','user_id','category','priority','subject','message','page_route','page_url','error_reference','context_json','status','assigned_to_user_id','resolution_note','resolved_at','created_at','updated_at'] as $column)if(!setup_column_exists($pdo,$requests,$column))return false;
    return setup_index_exists($pdo,$requests,'idx_support_request_user')
        && setup_index_exists($pdo,$requests,'idx_support_request_branch')
        && setup_index_exists($pdo,$requests,'idx_support_request_assignment');
}


function setup_apply_v059831(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.31');
    setup_record_history($pdo,'0.5.9.8.31','usability_accessibility_keyboard_mobile_and_month_schedule');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.31',
        'features'=>[
            'appointment_month_view_with_daily_counts',
            'month_day_to_week_navigation',
            'selected_week_date_highlight',
            'keyboard_save_and_logical_field_advancement',
            'protected_unsaved_dialog_forms',
            'responsive_bounded_dialogs_and_long_tables',
            'visible_focus_and_reduced_motion_accessibility',
        ],
    ]);
}

function setup_v059831_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.31','>=');
}

function setup_apply_v059832(PDO $pdo): void
{
    $users=table('users');
    foreach([
        'first_name'=>'VARCHAR(80) NULL AFTER `name`',
        'middle_name'=>'VARCHAR(80) NULL AFTER `first_name`',
        'last_name'=>'VARCHAR(80) NULL AFTER `middle_name`',
    ] as $column=>$definition) setup_add_column($pdo,$users,$column,$definition);
    $pdo->exec(
        'UPDATE `' . $users . '` SET '
        . '`first_name`=CASE WHEN COALESCE(NULLIF(TRIM(`first_name`),""),"")<>"" THEN `first_name` WHEN LOCATE(" ",TRIM(`name`))=0 THEN TRIM(`name`) ELSE SUBSTRING_INDEX(TRIM(`name`)," ",1) END,'
        . '`last_name`=CASE WHEN COALESCE(NULLIF(TRIM(`last_name`),""),"")<>"" THEN `last_name` WHEN LOCATE(" ",TRIM(`name`))=0 THEN "" ELSE SUBSTRING_INDEX(TRIM(`name`)," ",-1) END,'
        . '`middle_name`=CASE WHEN COALESCE(NULLIF(TRIM(`middle_name`),""),"")<>"" THEN `middle_name` WHEN (LENGTH(TRIM(`name`))-LENGTH(REPLACE(TRIM(`name`)," ","")))<2 THEN NULL ELSE NULLIF(TRIM(SUBSTRING(TRIM(`name`),LENGTH(SUBSTRING_INDEX(TRIM(`name`)," ",1))+2,LENGTH(TRIM(`name`))-LENGTH(SUBSTRING_INDEX(TRIM(`name`)," ",1))-LENGTH(SUBSTRING_INDEX(TRIM(`name`)," ",-1))-2)),"") END '
        . 'WHERE COALESCE(NULLIF(TRIM(`first_name`),""),"")="" OR COALESCE(NULLIF(TRIM(`last_name`),""),"")=""'
    );

    $services=table('service_catalog');
    setup_add_column($pdo,$services,'booking_scope',"VARCHAR(40) NOT NULL DEFAULT 'public' AFTER `is_public`");
    $pdo->exec('UPDATE `' . $services . '` SET booking_scope=CASE WHEN is_public=1 THEN "public" ELSE "reception" END WHERE booking_scope IS NULL OR TRIM(booking_scope)=""');
    $pdo->exec('UPDATE `' . $services . '` SET is_public=0,booking_scope="clinical_intake",updated_at=NOW() WHERE LOWER(TRIM(name)) IN ("blood pressure check","blood pressure","vitals check","vital signs check","pre-procedure assessment")');

    $appointments=table('appointments');
    setup_add_column($pdo,$appointments,'booking_source',"VARCHAR(40) NOT NULL DEFAULT 'staff' AFTER `appointment_origin`");
    setup_add_column($pdo,$appointments,'confirmation_source','VARCHAR(60) NULL AFTER `confirmation_status`');
    setup_add_column($pdo,$appointments,'confirmation_note','VARCHAR(500) NULL AFTER `confirmation_source`');
    setup_add_column($pdo,$appointments,'patient_agreed_at','DATETIME NULL AFTER `confirmation_note`');

    $appointmentServices=table('appointment_services');
    if(!setup_table_exists($pdo,$appointmentServices)) setup_apply_v0598201($pdo);
    try{$pdo->exec('ALTER TABLE `' . $appointmentServices . '` MODIFY COLUMN `status` ENUM("planned","completed","deferred","moved","not_performed","cancelled") NOT NULL DEFAULT "planned"');}catch(Throwable $exception){error_log('Appointment-service status extension warning: '.$exception->getMessage());}
    foreach([
        'disposition_reason'=>'VARCHAR(500) NULL AFTER `status`',
        'destination_appointment_id'=>'BIGINT UNSIGNED NULL AFTER `disposition_reason`',
        'source_appointment_service_id'=>'BIGINT UNSIGNED NULL AFTER `destination_appointment_id`',
        'disposition_by'=>'BIGINT UNSIGNED NULL AFTER `source_appointment_service_id`',
        'disposition_at'=>'DATETIME NULL AFTER `disposition_by`',
    ] as $column=>$definition) setup_add_column($pdo,$appointmentServices,$column,$definition);

    $dispositions=table('appointment_service_dispositions');
    if(!setup_table_exists($pdo,$dispositions)){
        $pdo->exec(
            'CREATE TABLE `' . $dispositions . '` ('
            . '`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,'
            . '`appointment_service_id` BIGINT UNSIGNED NOT NULL,'
            . '`source_appointment_id` BIGINT UNSIGNED NOT NULL,'
            . '`destination_appointment_id` BIGINT UNSIGNED NULL,'
            . '`action` ENUM("defer_next","move_existing","create_followup","not_performed") NOT NULL,'
            . '`reason` VARCHAR(500) NOT NULL,'
            . '`performed_by` BIGINT UNSIGNED NULL,'
            . '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,'
            . 'PRIMARY KEY (`id`),'
            . 'KEY `idx_service_disposition_source` (`source_appointment_id`,`created_at`),'
            . 'KEY `idx_service_disposition_destination` (`destination_appointment_id`,`created_at`),'
            . 'KEY `idx_service_disposition_segment` (`appointment_service_id`,`created_at`)'
            . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
        );
    }

    setting_set('system','application_version','0.5.9.8.32');
    setup_record_history($pdo,'0.5.9.8.32','appointment_booking_profile_and_service_disposition_integrity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.32',
        'features'=>[
            'structured_first_middle_and_last_names_for_staff_and_dentists',
            'public_reception_dentist_order_clinical_intake_and_inhouse_service_scopes',
            'server_enforced_public_service_booking',
            'branch_scoped_patient_matching_and_privacy_safe_continuity',
            'patient_agreement_capture_for_confirmed_or_changed_schedules',
            'add_and_drop_appointment_services_before_check_in',
            'audited_defer_move_followup_and_not_performed_actions_after_check_in',
        ],
    ]);
}

function setup_v059832_ready(PDO $pdo): bool
{
    if(!version_compare(installed_application_version(),'0.5.9.8.32','>='))return false;
    foreach(['first_name','middle_name','last_name'] as $column)if(!setup_column_exists($pdo,table('users'),$column))return false;
    if(!setup_column_exists($pdo,table('service_catalog'),'booking_scope'))return false;
    foreach(['booking_source','confirmation_source','confirmation_note','patient_agreed_at'] as $column)if(!setup_column_exists($pdo,table('appointments'),$column))return false;
    foreach(['disposition_reason','destination_appointment_id','source_appointment_service_id','disposition_by','disposition_at'] as $column)if(!setup_column_exists($pdo,table('appointment_services'),$column))return false;
    return setup_table_exists($pdo,table('appointment_service_dispositions'));
}

function setup_apply_v059833(PDO $pdo): void
{
    // Version 0.5.9.8.33 is a code-only focused hotfix. No schema changes are required.
    // Recording the installed application version keeps the update center aligned with the VERSION file.
    setting_set('system','application_version','0.5.9.8.33');
    setup_record_history($pdo,'0.5.9.8.33','returning_booking_api_bootstrap_warning_cleanup_and_package_hygiene');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.33',
        'features'=>[
            'medicine_and_reconciliation_api_bootstrap_repair',
            'returning_patient_clinic_decides_provider_option',
            'returning_booking_context_api_alignment',
            'visit_workflow_warning_cleanup',
            'visit_exception_warning_cleanup',
            'installer_package_runtime_data_exclusion',
        ],
    ]);
}

function setup_v059833_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.33','>=');
}

function setup_apply_v059834(PDO $pdo): void
{
    dental_update_safety_apply_v059834($pdo);
    setting_set('system','application_version','0.5.9.8.34');
    setting_set('system','last_update_readiness_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.34','update_safety_database_readiness_and_transaction_foundation');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.34',
        'features'=>[
            'database_readiness_checker',
            'safe_schema_auto_repair_for_transaction_foundation',
            'update_center_consistency_alignment',
            'cashier_transaction_lock_foundation',
            'invoice_state_history_foundation',
            'transaction_audit_event_foundation',
            'payment_method_reference_rules',
        ],
    ]);
}

function setup_v059834_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.34','>=')
        && dental_update_table_exists($pdo, table('cashier_transaction_locks'))
        && dental_update_table_exists($pdo, table('invoice_state_history'))
        && dental_update_table_exists($pdo, table('transaction_audit_events'))
        && dental_update_table_exists($pdo, table('payment_method_rules'))
        && dental_update_column_exists($pdo, table('invoices'), 'transaction_type')
        && dental_update_column_exists($pdo, table('invoices'), 'flow_state')
        && dental_update_column_exists($pdo, table('invoices'), 'balance_due')
        && dental_update_column_exists($pdo, table('payments'), 'reference_required')
        && dental_update_column_exists($pdo, table('cashier_sessions'), 'active_invoice_id');
}

function setup_v059835_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.35','>=');
}

function setup_apply_v059835(PDO $pdo): void
{
    // Version 0.5.9.8.35 is a code-focused cashier role-scope/POS hardening update.
    // No destructive schema change is required; recording this version aligns System Setup with the package files.
    setting_set('system','application_version','0.5.9.8.35');
    setup_record_history($pdo,'0.5.9.8.35','cashier_only_payment_workspace_and_payables_hardening');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.35',
        'features'=>[
            'cashier_role_limited_to_billing_and_receipts',
            'cashier_navigation_removed_from_patient_clinical_followup_report_and_insurance_modules',
            'cashier_dashboard_payment_only_actions',
            'all_payables_search_inside_cashier_register',
            'gcash_maya_and_mixed_payment_method_labels',
            'cashier_followup_dependency_removed_from_payment_posting',
            'supervisor_only_discount_waiver_refund_requests',
        ],
    ]);
}


function setup_v059836_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.36','>=') && dental_update_safety_v059836_ready($pdo);
}

function setup_apply_v059836(PDO $pdo): void
{
    dental_update_safety_apply_v059836($pdo);
    setting_set('system','application_version','0.5.9.8.36');
    setup_record_history($pdo,'0.5.9.8.36','treatment_to_billing_final_flow');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.36',
        'features'=>[
            'performed_services_only_cashier_handoff',
            'deferred_moved_not_performed_cancelled_procedure_statuses',
            'non_billable_clinical_disposition_reason_and_patient_agreement_record',
            'clinical_billing_handoff_event_log',
            'cashier_invoice_excludes_not_performed_services',
        ],
    ]);
}

function setup_v059837_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.37','>=');
}

function setup_apply_v059837(PDO $pdo): void
{
    // Version 0.5.9.8.37 is a focused UI and finance-report polish update.
    // No destructive schema change is required; recording this version aligns System Setup with the package files.
    setting_set('system','application_version','0.5.9.8.37');
    setup_record_history($pdo,'0.5.9.8.37','schedule_action_overflow_and_collection_report_polish');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.37',
        'features'=>[
            'clinic_schedule_table_action_dropdown',
            'appointment_actions_no_longer_overflow_right_edge',
            'collection_report_method_totals',
            'collection_report_cashier_totals',
            'update_center_version_alignment',
        ],
    ]);
}

function setup_v059838_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.38','>=');
}

function setup_apply_v059838(PDO $pdo): void
{
    // Version 0.5.9.8.38 is a focused patient account statement and payables-print polish update.
    // No schema change is required; it records the installed code version for Update Center consistency.
    setting_set('system','application_version','0.5.9.8.38');
    setup_record_history($pdo,'0.5.9.8.38','patient_account_statement_and_payables_print_polish');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.38',
        'features'=>[
            'patient_account_statement_summary',
            'itemized_statement_invoice_breakdown',
            'statement_payments_returns_and_balances',
            'cashier_all_payables_statement_link',
            'update_center_version_alignment',
        ],
    ]);
}


function setup_v059839_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.39','>=');
}

function setup_apply_v059839(PDO $pdo): void
{
    // Version 0.5.9.8.39 is a focused print-reason policy correction.
    // Routine financial, appointment, inventory, sterilization, and collection prints no longer require a reason.
    setting_set('system','application_version','0.5.9.8.39');
    setup_record_history($pdo,'0.5.9.8.39','print_reason_policy_correction');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.39',
        'features'=>[
            'print_reason_required_only_for_confidential_clinical_exports',
            'routine_operational_prints_open_without_reason_prompt',
            'print_audit_log_preserved_with_optional_reason',
            'update_center_version_alignment',
        ],
    ]);
}

function setup_v059840_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.40','>=');
}

function setup_apply_v059840(PDO $pdo): void
{
    // Version 0.5.9.8.40 is a focused patient-portal statement transparency update.
    // No schema change is needed; this records the code-level update so System Setup stays aligned.
    setting_set('system','application_version','0.5.9.8.40');
    setup_record_history($pdo,'0.5.9.8.40','patient_portal_statement_transparency');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.40',
        'features'=>[
            'patient_portal_statement_summary_cards',
            'portal_invoice_payment_and_return_history',
            'portal_print_save_account_statement',
            'update_center_message_alignment',
        ],
    ]);
}


function setup_v059841_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.41','>=');
}

function setup_apply_v059841(PDO $pdo): void
{
    // Version 0.5.9.8.41 is a major cashier resilience update.
    // It formalizes balance-only payment so old balances can be paid without
    // creating a checkup, queue ticket, appointment, or clinical encounter.
    if (setup_table_exists($pdo, table('cashier_checkouts')) && setup_column_exists($pdo, table('cashier_checkouts'), 'checkout_type')) {
        try {
            $pdo->exec('ALTER TABLE `' . table('cashier_checkouts') . '` MODIFY `checkout_type` ENUM("paid","zero_balance","insurance","settled","balance_payment","partial_balance") NOT NULL DEFAULT "settled"');
        } catch (Throwable $exception) {
            error_log('v0.5.9.8.41 cashier checkout type compatibility: ' . $exception->getMessage());
        }
    }
    if (setup_table_exists($pdo, table('cashier_sessions'))) {
        if (!setup_column_exists($pdo, table('cashier_sessions'), 'active_invoice_id')) {
            try { $pdo->exec('ALTER TABLE `' . table('cashier_sessions') . '` ADD `active_invoice_id` BIGINT UNSIGNED NULL'); } catch (Throwable $exception) { error_log($exception->getMessage()); }
        }
        if (!setup_column_exists($pdo, table('cashier_sessions'), 'transaction_mode')) {
            try { $pdo->exec('ALTER TABLE `' . table('cashier_sessions') . '` ADD `transaction_mode` VARCHAR(40) NOT NULL DEFAULT "patient_visit"'); } catch (Throwable $exception) { error_log($exception->getMessage()); }
        }
        if (!setup_column_exists($pdo, table('cashier_sessions'), 'lock_started_at')) {
            try { $pdo->exec('ALTER TABLE `' . table('cashier_sessions') . '` ADD `lock_started_at` DATETIME NULL'); } catch (Throwable $exception) { error_log($exception->getMessage()); }
        }
    }
    setting_set('system','application_version','0.5.9.8.41');
    setup_record_history($pdo,'0.5.9.8.41','major_what_if_resilience_balance_only_payment');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.41',
        'features'=>[
            'balance_only_payment_without_checkup',
            'pay_balance_action_from_all_payables',
            'no_queue_or_clinical_encounter_created_for_old_balance_payment',
            'existing_invoice_charges_locked_during_balance_payment',
            'partial_balance_payment_releases_cashier_transaction',
            'cashier_checkout_activity_distinguishes_balance_payment',
            'major_what_if_cashier_guardrails',
        ],
    ]);
}




function setup_v059842_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.42','>=')
        && dental_update_apply_chain_complete('0.5.9.8.42')
        && !dental_update_schema_drift_detected($pdo);
}

function setup_apply_v059842(PDO $pdo): void
{
    // Version 0.5.9.8.42 is a compatibility/catch-up guard for copied systems
    // that skipped one or more focused hotfixes. It repairs only safe missing
    // structures and records the final aligned version.
    $repaired = dental_update_repair_skipped_foundations($pdo);
    setting_set('system','application_version','0.5.9.8.42');
    setting_set('system','last_skipped_update_recovery_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.42','skipped_update_recovery_and_compatibility_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.42',
        'features'=>[
            'missed_update_detection',
            'safe_catch_up_schema_repair',
            'apply_chain_function_availability_check',
            'code_database_version_alignment_guard',
            'schema_drift_warning_before_runtime_errors',
        ],
        'repaired_foundations'=>$repaired,
        'recovery_summary'=>dental_update_recovery_summary($pdo,'0.5.9.8.42'),
    ]);
}

function setup_v059843_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.43','>=')
        && function_exists('dental_security_schema_ready')
        && dental_security_schema_ready($pdo);
}

function setup_apply_v059843(PDO $pdo): void
{
    // Version 0.5.9.8.43 hardens sign-in and session handling while staying
    // safe for copied systems that may be catching up from an older version.
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.43_security_hardening.sql');
    setting_set('system','application_version','0.5.9.8.43');
    setting_set('system','last_security_hardening_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.43','security_hardening_login_throttle_sessions_confirmations');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.43',
        'features'=>[
            'login_throttling',
            'security_login_attempt_log',
            'staff_session_device_tracking',
            'session_revocation_guard',
            'security_event_log',
            'privileged_action_confirmation_foundation',
            'mfa_privileged_roles_setting_foundation',
        ],
        'security_readiness'=>function_exists('dental_security_readiness_summary') ? dental_security_readiness_summary($pdo) : [],
    ]);
}


function setup_v059844_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.44','>=')
        && function_exists('appointment_schedule_guard_schema_ready')
        && appointment_schedule_guard_schema_ready($pdo);
}

function setup_apply_v059844(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.44_appointment_schedule_final_guard.sql');
    setup_add_column($pdo, table('appointments'), 'assignment_status', "ENUM('not_required','needs_assignment','assigned','blocked') NOT NULL DEFAULT 'assigned' AFTER `confirmation_status`");
    setup_add_column($pdo, table('appointments'), 'assignment_note', 'VARCHAR(500) NULL AFTER `assignment_status`');
    setup_add_column($pdo, table('appointments'), 'assignment_requested_at', 'DATETIME NULL AFTER `assignment_note`');
    setup_add_column($pdo, table('appointments'), 'assignment_resolved_at', 'DATETIME NULL AFTER `assignment_requested_at`');
    setup_add_column($pdo, table('appointments'), 'schedule_guard_status', "ENUM('unchecked','passed','conflict_blocked','bypass_approved') NOT NULL DEFAULT 'unchecked' AFTER `assignment_resolved_at`");
    setup_add_column($pdo, table('appointments'), 'schedule_guard_checked_at', 'DATETIME NULL AFTER `schedule_guard_status`');
    setup_add_column($pdo, table('appointments'), 'schedule_guard_message', 'VARCHAR(500) NULL AFTER `schedule_guard_checked_at`');
    setup_add_column($pdo, table('appointments'), 'no_show_marked_at', 'DATETIME NULL AFTER `schedule_guard_message`');
    setup_add_column($pdo, table('appointment_requests'), 'assignment_queue_status', "ENUM('not_required','pending','resolved') NOT NULL DEFAULT 'not_required' AFTER `provider_preference_mode`");
    setup_add_column($pdo, table('appointment_requests'), 'assignment_queue_note', 'VARCHAR(500) NULL AFTER `assignment_queue_status`');
    if(!setup_index_exists($pdo,table('appointments'),'idx_appointment_assignment_guard')){
        $pdo->exec('ALTER TABLE `' . table('appointments') . '` ADD KEY `idx_appointment_assignment_guard` (`branch_id`,`assignment_status`,`start_at`)');
    }
    if(!setup_index_exists($pdo,table('appointments'),'idx_appointment_schedule_guard')){
        $pdo->exec('ALTER TABLE `' . table('appointments') . '` ADD KEY `idx_appointment_schedule_guard` (`branch_id`,`schedule_guard_status`,`start_at`)');
    }
    if(!setup_index_exists($pdo,table('appointment_requests'),'idx_request_assignment_queue')){
        $pdo->exec('ALTER TABLE `' . table('appointment_requests') . '` ADD KEY `idx_request_assignment_queue` (`assignment_queue_status`,`status`,`preferred_date`)');
    }
    $pdo->exec('UPDATE `' . table('appointments') . '` SET assignment_status=CASE WHEN provider_type="dentist" AND (dentist_user_id IS NULL OR dentist_user_id=0) THEN "needs_assignment" WHEN provider_type="dentist" THEN "assigned" ELSE "not_required" END WHERE assignment_status="assigned" OR assignment_status IS NULL');
    $pdo->exec('UPDATE `' . table('appointment_requests') . '` SET assignment_queue_status=CASE WHEN provider_preference_mode="clinic_decides" AND status NOT IN ("scheduled","declined","cancelled") THEN "pending" ELSE "not_required" END WHERE assignment_queue_status="not_required" OR assignment_queue_status IS NULL');
    setting_set('system','application_version','0.5.9.8.44');
    setting_set('system','last_appointment_schedule_guard_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.44','appointment_schedule_final_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.44',
        'features'=>[
            'provider_chair_conflict_guard',
            'clinic_decides_assignment_queue',
            'unavailable_provider_fallback_guard',
            'strict_cancel_no_show_rules',
            'appointment_safety_event_log',
        ],
    ]);

}

function setup_v059845_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.45','>=')
        && function_exists('clinical_record_completion_schema_ready')
        && clinical_record_completion_schema_ready($pdo);
}

function setup_apply_v059845(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.45_clinical_record_completion.sql');

    setup_add_column($pdo, table('clinical_encounters'), 'record_completion_status', "ENUM('unchecked','needs_review','complete','locked') NOT NULL DEFAULT 'unchecked'");
    setup_add_column($pdo, table('clinical_encounters'), 'record_completion_checked_at', 'DATETIME NULL');
    setup_add_column($pdo, table('clinical_encounters'), 'record_completion_missing_json', 'LONGTEXT NULL');
    if (!setup_index_exists($pdo, table('clinical_encounters'), 'idx_encounter_record_completion')) {
        $pdo->exec('ALTER TABLE `' . table('clinical_encounters') . '` ADD KEY `idx_encounter_record_completion` (`record_completion_status`,`finalized_at`)');
    }

    setup_add_column($pdo, table('documents'), 'clinical_significance', "ENUM('supporting','diagnostic','pre_op','post_op','consent_evidence','external_record') NOT NULL DEFAULT 'supporting'");
    setup_add_column($pdo, table('documents'), 'captured_at', 'DATETIME NULL');
    setup_add_column($pdo, table('documents'), 'review_status', "ENUM('uploaded','reviewed','needs_retake','archived') NOT NULL DEFAULT 'uploaded'");
    setup_add_column($pdo, table('documents'), 'review_note', 'TEXT NULL');
    if (!setup_index_exists($pdo, table('documents'), 'idx_doc_review_status')) {
        $pdo->exec('ALTER TABLE `' . table('documents') . '` ADD KEY `idx_doc_review_status` (`patient_id`,`review_status`,`created_at`)');
    }

    if (setup_table_exists($pdo, table('procedures'))) {
        setup_add_column($pdo, table('procedures'), 'note_template_id', 'BIGINT UNSIGNED NULL');
        setup_add_column($pdo, table('procedures'), 'procedure_note_quality', "ENUM('unchecked','template_used','free_text_complete','needs_detail') NOT NULL DEFAULT 'unchecked'");
    }

    if (function_exists('procedure_note_template_seed_rows') && setup_table_exists($pdo, table('procedure_note_templates'))) {
        $stmt = $pdo->prepare('INSERT IGNORE INTO `' . table('procedure_note_templates') . '` (template_name,procedure_keyword,template_body,is_active,created_at) VALUES (?,?,?,1,NOW())');
        foreach (procedure_note_template_seed_rows() as $row) {
            $stmt->execute([$row[0], $row[0], $row[1]]);
        }
    }

    setting_set('system','application_version','0.5.9.8.45');
    setting_set('system','last_clinical_record_completion_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.45','clinical_record_completion_printing_signatures_templates');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.45',
        'features'=>[
            'clinical_record_completion_review',
            'complete_printable_record_completeness_status',
            'procedure_note_templates',
            'treatment_plan_signature_foundation',
            'clinical_document_review_metadata',
        ],
    ]);
}



function setup_v059846_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.46','>=')
        && function_exists('inventory_sterilization_completion_schema_ready')
        && inventory_sterilization_completion_schema_ready();
}

function setup_apply_v059846(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.46_inventory_sterilization_supply_completion.sql');

    setup_add_column($pdo, table('inventory_items'), 'is_controlled_supply', 'TINYINT(1) NOT NULL DEFAULT 1');
    setup_add_column($pdo, table('inventory_items'), 'requires_lot_for_issue', 'TINYINT(1) NOT NULL DEFAULT 1');
    setup_add_column($pdo, table('inventory_items'), 'requires_expiry_for_receipt', 'TINYINT(1) NOT NULL DEFAULT 0');
    setup_add_column($pdo, table('inventory_items'), 'default_usage_policy', "VARCHAR(40) NOT NULL DEFAULT 'manual_issue'");
    setup_add_column($pdo, table('inventory_items'), 'sterile_item_policy', "VARCHAR(40) NOT NULL DEFAULT 'not_applicable'");
    if (setup_table_exists($pdo, table('inventory_items'))) {
        if (!setup_index_exists($pdo, table('inventory_items'), 'idx_inventory_item_controlled_supply')) {
            $pdo->exec('ALTER TABLE `' . table('inventory_items') . '` ADD KEY `idx_inventory_item_controlled_supply` (`is_controlled_supply`,`is_clinical_supply`,`status`)');
        }
        $pdo->exec('UPDATE `' . table('inventory_items') . '` SET is_controlled_supply=1 WHERE is_controlled_supply IS NULL');
    }

    setup_add_column($pdo, table('inventory_lots'), 'quarantine_reason', 'VARCHAR(500) NULL');
    setup_add_column($pdo, table('inventory_lots'), 'quarantined_at', 'DATETIME NULL');
    setup_add_column($pdo, table('inventory_lots'), 'quarantined_by', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, table('inventory_lots'), 'last_safety_checked_at', 'DATETIME NULL');
    if (setup_table_exists($pdo, table('inventory_lots')) && !setup_index_exists($pdo, table('inventory_lots'), 'idx_inventory_lot_safety')) {
        $pdo->exec('ALTER TABLE `' . table('inventory_lots') . '` ADD KEY `idx_inventory_lot_safety` (`branch_id`,`status`,`expiry_date`,`quantity_remaining`)');
    }

    setup_add_column($pdo, table('inventory_documents'), 'clinical_consumption_review_status', "VARCHAR(40) NOT NULL DEFAULT 'not_required'");
    setup_add_column($pdo, table('inventory_documents'), 'clinical_consumption_reviewed_at', 'DATETIME NULL');
    setup_add_column($pdo, table('inventory_documents'), 'clinical_consumption_reviewed_by', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, table('inventory_documents'), 'procedure_supply_template_id', 'BIGINT UNSIGNED NULL');
    if (setup_table_exists($pdo, table('inventory_documents'))) {
        if (!setup_index_exists($pdo, table('inventory_documents'), 'idx_inventory_clinical_consumption_review')) {
            $pdo->exec('ALTER TABLE `' . table('inventory_documents') . '` ADD KEY `idx_inventory_clinical_consumption_review` (`branch_id`,`clinical_consumption_review_status`,`document_type`,`usage_destination`)');
        }
        $pdo->exec('UPDATE `' . table('inventory_documents') . '` SET clinical_consumption_review_status=CASE WHEN document_type="issue" AND usage_destination="active_treatment" THEN "pending" ELSE "not_required" END WHERE clinical_consumption_review_status="not_required" OR clinical_consumption_review_status IS NULL');
    }

    setup_add_column($pdo, table('sterilization_batches'), 'release_review_status', "VARCHAR(40) NOT NULL DEFAULT 'pending'");
    setup_add_column($pdo, table('sterilization_batches'), 'release_review_id', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, table('sterilization_batches'), 'release_block_reason', 'VARCHAR(500) NULL');
    setup_add_column($pdo, table('sterilization_batches'), 'biological_result_due_at', 'DATETIME NULL');
    if (setup_table_exists($pdo, table('sterilization_batches')) && !setup_index_exists($pdo, table('sterilization_batches'), 'idx_sterilization_release_review_status')) {
        $pdo->exec('ALTER TABLE `' . table('sterilization_batches') . '` ADD KEY `idx_sterilization_release_review_status` (`branch_id`,`release_review_status`,`cycle_status`,`created_at`)');
    }

    setup_add_column($pdo, table('instrument_sets'), 'last_release_review_id', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, table('instrument_sets'), 'quarantine_reason', 'VARCHAR(500) NULL');
    setup_add_column($pdo, table('instrument_sets'), 'last_patient_use_id', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, table('instrument_sets'), 'next_inspection_due_at', 'DATETIME NULL');
    if (setup_table_exists($pdo, table('instrument_sets')) && !setup_index_exists($pdo, table('instrument_sets'), 'idx_instrument_set_next_inspection')) {
        $pdo->exec('ALTER TABLE `' . table('instrument_sets') . '` ADD KEY `idx_instrument_set_next_inspection` (`branch_id`,`status`,`next_inspection_due_at`)');
    }

    setup_add_column($pdo, table('instrument_usage'), 'chair_id', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, table('instrument_usage'), 'usage_status', "VARCHAR(40) NOT NULL DEFAULT 'assigned'");
    setup_add_column($pdo, table('instrument_usage'), 'return_condition', 'VARCHAR(120) NULL');
    setup_add_column($pdo, table('instrument_usage'), 'decontamination_received_at', 'DATETIME NULL');
    setup_add_column($pdo, table('instrument_usage'), 'decontamination_received_by', 'BIGINT UNSIGNED NULL');
    if (setup_table_exists($pdo, table('instrument_usage')) && !setup_index_exists($pdo, table('instrument_usage'), 'idx_instrument_usage_status')) {
        $pdo->exec('ALTER TABLE `' . table('instrument_usage') . '` ADD KEY `idx_instrument_usage_status` (`branch_id`,`usage_status`,`assigned_at`)');
    }

    setup_add_column($pdo, table('sterilization_equipment'), 'maintenance_lock_reason', 'VARCHAR(500) NULL');
    setup_add_column($pdo, table('sterilization_equipment'), 'service_status_checked_at', 'DATETIME NULL');
    setup_add_column($pdo, table('sterilization_equipment'), 'service_status', "VARCHAR(40) NOT NULL DEFAULT 'unknown'");
    if (setup_table_exists($pdo, table('sterilization_equipment')) && !setup_index_exists($pdo, table('sterilization_equipment'), 'idx_sterilization_equipment_service_due')) {
        $pdo->exec('ALTER TABLE `' . table('sterilization_equipment') . '` ADD KEY `idx_sterilization_equipment_service_due` (`branch_id`,`status`,`next_service_date`,`service_status`)');
    }

    if (setup_table_exists($pdo, table('procedure_supply_templates'))) {
        $stmt = $pdo->prepare('INSERT IGNORE INTO `' . table('procedure_supply_templates') . '` (branch_id,template_name,procedure_keyword,is_active,created_at) VALUES (NULL,?,?,1,NOW())');
        foreach ([
            ['Consultation / examination basic supplies','consultation'],
            ['Oral prophylaxis supplies','prophylaxis'],
            ['Restoration supplies','restoration'],
            ['Extraction supplies','extraction'],
            ['X-ray / imaging barriers','x-ray'],
            ['Endodontic supplies','root canal'],
        ] as $row) {
            $stmt->execute([$row[0], $row[1]]);
        }
    }

    setting_set('system','application_version','0.5.9.8.46');
    setting_set('system','last_inventory_sterilization_completion_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.46','inventory_sterilization_supplies_completion');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.46',
        'features'=>[
            'procedure_supply_template_foundation',
            'clinical_supply_usage_audit',
            'inventory_lot_quarantine_metadata',
            'clinical_consumption_review_status',
            'sterilization_release_review_foundation',
            'instrument_usage_return_traceability',
            'equipment_service_guard_metadata',
        ],
    ]);
}


function setup_v059847_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.47','>=')
        && setup_table_exists($pdo, table('chair_session_assignments'))
        && setup_table_exists($pdo, table('chair_status_events'))
        && setup_table_exists($pdo, table('chair_visit_movements'))
        && setup_column_exists($pdo, table('clinic_chairs'), 'resource_kind')
        && setup_column_exists($pdo, table('clinic_chairs'), 'default_provider_user_id')
        && setup_column_exists($pdo, table('clinic_chairs'), 'last_released_at')
        && setup_column_exists($pdo, table('doctor_chair_assignments'), 'assignment_type');
}

function setup_apply_v059847(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.47_chair_resource_operatory_flow.sql');

    if (setup_table_exists($pdo, table('clinic_chairs'))) {
        try {
            $pdo->exec('ALTER TABLE `' . table('clinic_chairs') . '` MODIFY `usage_mode` VARCHAR(40) NOT NULL DEFAULT "operatory"');
        } catch (Throwable $exception) {
            error_log('Chair resource usage_mode compatibility skipped: ' . $exception->getMessage());
        }
        setup_add_column($pdo, table('clinic_chairs'), 'resource_kind', "VARCHAR(40) NOT NULL DEFAULT 'operatory'");
        setup_add_column($pdo, table('clinic_chairs'), 'default_provider_user_id', 'BIGINT UNSIGNED NULL');
        setup_add_column($pdo, table('clinic_chairs'), 'service_scope_json', 'LONGTEXT NULL');
        setup_add_column($pdo, table('clinic_chairs'), 'current_status_note', 'VARCHAR(500) NULL');
        setup_add_column($pdo, table('clinic_chairs'), 'last_released_at', 'DATETIME NULL');
        if (!setup_index_exists($pdo, table('clinic_chairs'), 'idx_chair_resource_kind_status')) {
            $pdo->exec('ALTER TABLE `' . table('clinic_chairs') . '` ADD KEY `idx_chair_resource_kind_status` (`branch_id`,`resource_kind`,`status`)');
        }
        if (!setup_index_exists($pdo, table('clinic_chairs'), 'idx_chair_default_provider')) {
            $pdo->exec('ALTER TABLE `' . table('clinic_chairs') . '` ADD KEY `idx_chair_default_provider` (`branch_id`,`default_provider_user_id`,`status`)');
        }
        try {
            $pdo->exec('UPDATE `' . table('clinic_chairs') . '` SET resource_kind=CASE WHEN usage_mode="shared_inhouse" THEN "shared_inhouse" WHEN usage_mode="dentist_bound" THEN "operatory" ELSE usage_mode END WHERE resource_kind="operatory" OR resource_kind IS NULL');
        } catch (Throwable) {}
    }

    if (setup_table_exists($pdo, table('doctor_chair_assignments'))) {
        setup_add_column($pdo, table('doctor_chair_assignments'), 'assignment_type', "VARCHAR(40) NOT NULL DEFAULT 'default_provider'");
        setup_add_column($pdo, table('doctor_chair_assignments'), 'starts_at', 'DATETIME NULL');
        setup_add_column($pdo, table('doctor_chair_assignments'), 'ends_at', 'DATETIME NULL');
        setup_add_column($pdo, table('doctor_chair_assignments'), 'notes', 'VARCHAR(500) NULL');
    }

    setting_set('system','application_version','0.5.9.8.47');
    setting_set('system','last_chair_resource_flow_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.47','chair_resource_operatory_flow');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.47',
        'features'=>[
            'chairs_as_clinic_resources',
            'optional_default_provider_per_chair',
            'temporary_provider_chair_session_assignments',
            'visit_level_chair_move_and_release_history',
            'chair_occupancy_event_log',
        ],
    ]);
}


function setup_v059848_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.48','>=')
        && is_file(DENTAL_ROOT . '/app/owner_reports.php');
}

function setup_apply_v059848(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.48');
    setting_set('system','last_owner_reports_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.48','reports_owner_dashboard_completion');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.48',
        'features'=>[
            'owner_management_dashboard',
            'monthly_collection_trend',
            'provider_production_report',
            'patient_balance_aging',
            'cashier_closeout_exceptions',
            'appointment_conversion_report',
            'no_show_cancellation_watchlist',
            'clinical_supply_usage_cost_report',
        ],
    ]);
}

function setup_v059849_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.49','>=')
        && is_file(DENTAL_ROOT . '/app/release_hardening.php')
        && is_file(DENTAL_ROOT . '/app/Services/InstallerPackageService.php')
        && dental_release_hardening_ready($pdo);
}

function setup_apply_v059849(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.49_release_hardening_clean_package_builder.sql');
    if (function_exists('dental_release_hardening_record')) {
        dental_release_hardening_record($pdo, dental_release_package_policy_ready() ? 'passed' : 'needs_review');
    }
    setting_set('system','application_version','0.5.9.8.49');
    setting_set('system','last_release_hardening_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.49','release_hardening_clean_package_builder');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.49',
        'features'=>[
            'clean_package_builder_policy_check',
            'runtime_file_exclusion_audit',
            'release_hardening_audit_log',
            'backup_readiness_reminder',
            'installer_package_secret_exclusion_guard',
        ],
    ]);
}


function setup_v059850_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.50','>=')
        && is_file(DENTAL_ROOT . '/app/role_satisfaction.php')
        && setup_table_exists($pdo, table('role_satisfaction_audits'));
}

function setup_apply_v059850(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.50_role_satisfaction_permission_reports.sql');
    if (function_exists('dental_role_satisfaction_repair')) {
        dental_role_satisfaction_repair($pdo);
    }
    setting_set('system','application_version','0.5.9.8.50');
    setting_set('system','last_role_satisfaction_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.50','role_satisfaction_permission_task_reports');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.50',
        'features'=>[
            'appointment_service_action_task_route_repair',
            'cashier_payment_only_permission_cleanup',
            'frontdesk_daily_report',
            'dental_assistant_worklog_report',
            'dentist_clinical_completion_report',
            'admin_daily_operations_closeout',
            'role_permission_matrix_summary',
        ],
    ]);
}

function setup_v059851_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.51','>=')
        && function_exists('task_context_auto_align_for_route');
}

function setup_apply_v059851(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.51');
    setting_set('system','last_task_context_guard_repaired_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.51','task_view_auto_alignment_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.51',
        'features'=>[
            'stale_task_view_session_repair',
            'admin_preview_route_auto_alignment',
            'doctor_dashboard_receptionist_context_guard_repair',
            'dead_end_task_view_error_prevention',
        ],
    ]);
}

function setup_v059852_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.52','>=');
}

function setup_apply_v059852(PDO $pdo): void
{
    setting_set('system','application_version','0.5.9.8.52');
    setting_set('system','last_dashboard_clip_repaired_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.52','dashboard_table_clipping_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.52',
        'features'=>[
            'doctor_dashboard_right_panel_clip_repair',
            'grid_card_min_width_repair',
            'dashboard_table_horizontal_scroll_guard',
            'card_header_wrapping_repair',
        ],
    ]);
}


function setup_v059853_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.53','>=')
        && function_exists('dentalcare_clinic_defaults_ready')
        && dentalcare_clinic_defaults_ready($pdo);
}

function setup_apply_v059853(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.53_services_facilities_flow_allergy.sql');
    $seededServices = function_exists('dentalcare_seed_default_services') ? dentalcare_seed_default_services($pdo) : 0;
    $seededFacilities = function_exists('dentalcare_seed_default_facilities') ? dentalcare_seed_default_facilities($pdo) : 0;

    if (setup_table_exists($pdo, table('medical_histories'))) {
        $pdo->exec('UPDATE `' . table('medical_histories') . '` SET allergies=NULL WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(allergies,"")),".",""),"/","")," ","")) IN ("na","none","noallergy","noallergies","noknownallergy","noknownallergies","noknowndrugallergy","noknowndrugallergies","nkda","notapplicable","nil")');
    }
    if (setup_table_exists($pdo, table('patient_intakes'))) {
        $pdo->exec('UPDATE `' . table('patient_intakes') . '` SET allergy_changes=NULL WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(allergy_changes,"")),".",""),"/","")," ","")) IN ("na","none","noallergy","noallergies","noknownallergy","noknownallergies","noknowndrugallergy","noknowndrugallergies","nkda","notapplicable","nil")');
    }
    if (setup_table_exists($pdo, table('appointment_requests'))) {
        $pdo->exec('UPDATE `' . table('appointment_requests') . '` SET allergies=NULL WHERE LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE(allergies,"")),".",""),"/","")," ","")) IN ("na","none","noallergy","noallergies","noknownallergy","noknownallergies","noknowndrugallergy","noknowndrugallergies","nkda","notapplicable","nil")');
    }

    setting_set('system','application_version','0.5.9.8.53');
    setting_set('system','last_clinic_default_seed_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.53','services_facilities_treatment_flow_allergy_normalization');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.53',
        'features'=>[
            'complete_default_service_catalog',
            'inhouse_service_catalog_completion',
            'default_facility_resource_seeding',
            'factory_reset_preserves_services_and_facilities',
            'na_allergy_normalization',
            'doctor_treatment_carry_forward_suggestions',
            'previous_record_continuity_visibility',
        ],
        'seeded_services'=>$seededServices,
        'seeded_facilities'=>$seededFacilities,
    ]);
}


function setup_v059854_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.54','>=')
        && function_exists('dentalcare_clinic_defaults_ready')
        && dentalcare_clinic_defaults_ready($pdo);
}

function setup_apply_v059854(PDO $pdo): void
{
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.54_facility_seed_duplicate_guard.sql');
    $seededFacilities = function_exists('dentalcare_repair_default_facility_seed_conflicts') ? dentalcare_repair_default_facility_seed_conflicts($pdo) : 0;
    $seededServices = function_exists('dentalcare_seed_default_services') ? dentalcare_seed_default_services($pdo) : 0;

    setting_set('system','application_version','0.5.9.8.54');
    setting_set('system','last_facility_duplicate_seed_guard_checked_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.54','facility_seed_duplicate_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.54',
        'features'=>[
            'default_facility_seed_duplicate_guard',
            'legacy_chair_name_matching',
            'idempotent_chair_resource_restore',
            'service_catalog_reseed_safety_check',
        ],
        'seeded_services'=>$seededServices,
        'seeded_facilities'=>$seededFacilities,
    ]);
}

function setup_v059855_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.55','>=')
        && (string) setting_get('system','landing_installer_link_ready','0') === '1'
        && (string) setting_get('system','latest_update_pending_repair','0') === '1';
}

function setup_apply_v059855(PDO $pdo): void
{
    // Finalization is intentionally conservative: it records the new release,
    // re-runs the safe default catalog/facility guard, and prevents latest-version
    // pending loops caused by brittle optional service counts. It does not delete
    // clinic records, services, facilities, appointments, clinical records, or payments.
    $seededServices = function_exists('dentalcare_seed_default_services') ? dentalcare_seed_default_services($pdo) : 0;
    $seededFacilities = function_exists('dentalcare_repair_default_facility_seed_conflicts') ? dentalcare_repair_default_facility_seed_conflicts($pdo) : (function_exists('dentalcare_seed_default_facilities') ? dentalcare_seed_default_facilities($pdo) : 0);

    setting_set('system','application_version','0.5.9.8.55');
    setting_set('system','landing_installer_link_ready','1');
    setting_set('system','latest_update_pending_repair','1');
    setting_set('system','final_release_candidate_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.55','finalization_installer_link_update_pending_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.55',
        'features'=>[
            'landing_page_installer_setup_link',
            'latest_version_pending_loop_repair',
            'final_release_candidate_marker',
            'resilient_service_facility_readiness_check',
            'safe_default_catalog_and_facility_recheck',
        ],
        'seeded_services'=>$seededServices,
        'seeded_facilities'=>$seededFacilities,
    ]);
}


function setup_v059856_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.56','>=')
        && (string) setting_get('system','installer_builder_version_regex_repair','0') === '1'
        && (string) setting_get('system','final_update_status_consolidated','0') === '1';
}

function setup_apply_v059856(PDO $pdo): void
{
    // Code-focused finalization repair. It accepts the current five-part hotfix
    // version format in Installer Builder and uses the consolidated update-readiness
    // guard so advisory release-hardening/backup warnings do not masquerade as a
    // database update pending state.
    if (function_exists('dental_release_hardening_record')) {
        dental_release_hardening_record($pdo, function_exists('dental_release_package_policy_ready') && dental_release_package_policy_ready() ? 'passed' : 'needs_review');
    }

    setting_set('system','application_version','0.5.9.8.56');
    setting_set('system','installer_builder_version_regex_repair','1');
    setting_set('system','final_update_status_consolidated','1');
    setting_set('system','latest_update_pending_repair','1');
    setting_set('system','final_release_candidate_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.56','installer_builder_version_and_final_status_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.56',
        'features'=>[
            'installer_builder_accepts_five_segment_versions',
            'release_package_policy_false_warning_repair',
            'consolidated_final_update_pending_status',
            'advisory_backup_warnings_separated_from_database_updates',
        ],
    ]);
}

function setup_v059857_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.57','>=')
        && (string) setting_get('system','landing_release_download_buttons_ready','0') === '1'
        && (string) setting_get('system','final_update_status_consolidated','0') === '1';
}

function setup_apply_v059857(PDO $pdo): void
{
    // Landing-page release action repair. This is a code/UI update only; it keeps
    // installer downloads protected by the existing Superuser-only Installer Builder.
    setting_set('system','application_version','0.5.9.8.57');
    setting_set('system','landing_release_download_buttons_ready','1');
    setting_set('system','final_update_status_consolidated','1');
    setting_set('system','latest_update_pending_repair','1');
    setting_set('system','final_release_candidate_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.57','landing_full_demo_installer_download_buttons');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.57',
        'features'=>[
            'landing_page_full_installer_download_button',
            'landing_page_demo_installer_download_button',
            'superuser_protected_installer_builder_download_links',
            'final_status_recognition_for_0_5_9_8_57',
        ],
    ]);
}


function setup_v059858_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.58','>=')
        && (string) setting_get('system','landing_release_download_separated','0') === '1'
        && (string) setting_get('system','distributed_installer_builder_blocked','0') === '1'
        && (string) setting_get('system','distributed_license_issuer_blocked','0') === '1'
        && (string) setting_get('system','public_verified_release_downloads_ready','0') === '1';
}

function setup_apply_v059858(PDO $pdo): void
{
    // Landing-page and distribution-security repair. Public buttons now serve only
    // already-generated, verified packages. Distributed copies cannot generate
    // installers or issue their own licenses.
    setting_set('system','application_version','0.5.9.8.58');
    setting_set('system','landing_release_download_separated','1');
    setting_set('system','distributed_installer_builder_blocked','1');
    setting_set('system','distributed_license_issuer_blocked','1');
    setting_set('system','public_verified_release_downloads_ready','1');
    setting_set('system','final_update_status_consolidated','1');
    setting_set('system','latest_update_pending_repair','1');
    setting_set('system','final_release_candidate_at',date('Y-m-d H:i:s'));
    setup_record_history($pdo,'0.5.9.8.58','landing_release_download_and_distribution_security_cleanup');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.58',
        'features'=>[
            'landing_page_repetition_cleanup',
            'compact_public_services_preview',
            'about_developer_section',
            'installer_downloads_separated_from_staff_portal',
            'public_downloads_of_verified_generated_packages_only',
            'distributed_copies_cannot_generate_installers',
            'distributed_copies_cannot_issue_licenses',
        ],
    ]);
}



function setup_v059859_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.59','>=')
        && setup_column_exists($pdo, table('queue_entries'), 'inhouse_provider_name')
        && (string) setting_get('system','reset_queue_inhouse_provider_compatibility','0') === '1'
        && (string) setting_get('system','body_scroll_layout_repair','0') === '1'
        && (string) setting_get('system','uniform_form_width_repair','0') === '1';
}

function setup_apply_v059859(PDO $pdo): void
{
    $queueTable = table('queue_entries');
    if (setup_table_exists($pdo, $queueTable)) {
        setup_add_column($pdo, $queueTable, 'inhouse_provider_name', 'VARCHAR(150) NULL AFTER `inhouse_provider_user_id`');
        $userTable = table('users');
        if (setup_table_exists($pdo, $userTable)) {
            $pdo->exec('UPDATE `' . $queueTable . '` q LEFT JOIN `' . $userTable . '` u ON u.id=q.inhouse_provider_user_id SET q.inhouse_provider_name=COALESCE(NULLIF(q.inhouse_provider_name,""),u.name) WHERE q.inhouse_provider_user_id IS NOT NULL AND (q.inhouse_provider_name IS NULL OR q.inhouse_provider_name="")');
        }
    }
    setting_set('system','application_version','0.5.9.8.59');
    setting_set('system','reset_queue_inhouse_provider_compatibility','1');
    setting_set('system','body_scroll_layout_repair','1');
    setting_set('system','uniform_form_width_repair','1');
    setting_set('system','final_update_status_consolidated','1');
    setting_set('system','latest_update_pending_repair','1');
    setup_record_history($pdo,'0.5.9.8.59','reset_queue_provider_layout_form_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.59',
        'features'=>[
            'queue_inhouse_provider_name_reset_compatibility',
            'fresh_reset_schema_alignment_for_queue_entries',
            'body_content_scroll_repair',
            'uniform_textbox_width_policy',
        ],
    ]);
}

function setup_v059860_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.60','>=')
        && (string) setting_get('system','chair_service_edit_usability_repair','0') === '1'
        && setup_column_exists($pdo, table('service_catalog'), 'duration_minutes')
        && setup_column_exists($pdo, table('service_catalog'), 'description')
        && setup_column_exists($pdo, table('clinic_chairs'), 'resource_kind')
        && setup_table_exists($pdo, table('chair_session_assignments'));
}

function setup_apply_v059860(PDO $pdo): void
{
    if (function_exists('setup_apply_v059847')) {
        setup_apply_v059847($pdo);
    }
    setting_set('system','application_version','0.5.9.8.60');
    setting_set('system','chair_service_edit_usability_repair','1');
    setting_set('system','management_dialog_scroll_repair','1');
    setting_set('system','chair_status_board_layout_repair','1');
    setting_set('system','service_core_details_editing','1');
    setting_set('system','final_update_status_consolidated','1');
    setup_record_history($pdo,'0.5.9.8.60','chair_service_edit_usability_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.60',
        'features'=>[
            'chair_resource_manager_restored',
            'chair_status_board_cards',
            'scrollable_chair_and_service_dialogs',
            'service_name_duration_description_editing',
            'missing_dentist_profile_guided_setup',
            'existing_records_preserved',
        ],
    ]);
}

function setup_v059861_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.61','>=')
        && setup_column_exists($pdo, table('queue_entries'), 'inhouse_provider_name')
        && (string) setting_get('system','reset_runtime_hardening_repair','0') === '1'
        && (string) setting_get('system','appointment_assignment_preview_initialized','0') === '1';
}

function setup_apply_v059861(PDO $pdo): void
{
    if (function_exists('setup_apply_v059859')) {
        setup_apply_v059859($pdo);
    }
    $queueTable = table('queue_entries');
    if (setup_table_exists($pdo, $queueTable)) {
        setup_add_column($pdo, $queueTable, 'inhouse_provider_name', 'VARCHAR(150) NULL AFTER `inhouse_provider_user_id`');
        if (setup_column_exists($pdo, $queueTable, 'inhouse_provider_user_id') && setup_table_exists($pdo, table('users'))) {
            $pdo->exec('UPDATE `' . $queueTable . '` q LEFT JOIN `' . table('users') . '` u ON u.id=q.inhouse_provider_user_id SET q.inhouse_provider_name=COALESCE(NULLIF(q.inhouse_provider_name,""),u.name) WHERE q.inhouse_provider_user_id IS NOT NULL AND (q.inhouse_provider_name IS NULL OR q.inhouse_provider_name="")');
        }
    }
    setting_set('system','application_version','0.5.9.8.61');
    setting_set('system','reset_runtime_hardening_repair','1');
    setting_set('system','chair_board_schema_fallback','1');
    setting_set('system','appointment_assignment_preview_initialized','1');
    setting_set('system','clean_hotfix_package_policy_confirmed','1');
    setting_set('system','final_update_status_consolidated','1');
    setup_record_history($pdo,'0.5.9.8.61','reset_runtime_schema_fallback_and_assignment_preview_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.61',
        'features'=>[
            'chair_board_no_longer_crashes_before_queue_provider_column_exists',
            'appointments_page_assignment_preview_initialized_before_use',
            'reset_state_schema_fallbacks_preserve_access_to_system_setup',
            'focused_hotfix_package_excludes_runtime_secrets_and_logs',
        ],
    ]);
}

function setup_v059862_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.62','>=')
        && (string) setting_get('system','ui_roleflow_release_qa_hardening','0') === '1'
        && (string) setting_get('system','management_dialog_focus_repair','0') === '1'
        && (string) setting_get('system','release_deny_list_confirmed','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/system_smoke_test.php');
}

function setup_apply_v059862(PDO $pdo): void
{
    if (function_exists('setup_apply_v059861')) {
        setup_apply_v059861($pdo);
    }
    setup_execute_sql_file($pdo, DENTAL_ROOT . '/database/migrations/0.5.9.8.62_ui_roleflow_release_qa.sql');
    setting_set('system','application_version','0.5.9.8.62');
    setting_set('system','ui_roleflow_release_qa_hardening','1');
    setting_set('system','management_dialog_focus_repair','1');
    setting_set('system','release_deny_list_confirmed','1');
    setting_set('system','superuser_smoke_test_available','1');
    setting_set('system','final_update_status_consolidated','1');
    setup_record_history($pdo,'0.5.9.8.62','ui_roleflow_release_qa_hardening');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.62',
        'features'=>[
            'doctor_profile_and_treatment_empty_states_confirmed',
            'chair_and_service_management_dialog_focus_and_scroll_repaired',
            'body_scroll_and_uniform_form_width_policy_reinforced',
            'superuser_qa_smoke_test_page_added',
            'client_release_deny_list_confirmed',
        ],
    ]);
}


function setup_v059863_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.63','>=')
        && (string) setting_get('system','release_package_runtime_guard_confirmed','0') === '1'
        && (string) setting_get('system','qa_smoke_layout_guard_confirmed','0') === '1'
        && function_exists('dental_release_package_policy_ready')
        && dental_release_package_policy_ready()
        && is_file(DENTAL_ROOT . '/app/Services/InstallerPackageService.php');
}

function setup_apply_v059863(PDO $pdo): void
{
    if (function_exists('setup_apply_v059862')) {
        setup_apply_v059862($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.63_client_release_safety_and_qa_layout.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.63');
    setting_set('system','release_deny_list_confirmed','1');
    setting_set('system','release_package_runtime_guard_confirmed','1');
    setting_set('system','qa_smoke_layout_guard_confirmed','1');
    setting_set('system','doctor_empty_state_go_live_confirmed','1');
    setting_set('system','client_release_safety_ready','1');
    setting_set('system','final_update_status_consolidated','1');
    if (function_exists('dental_release_hardening_record')) {
        dental_release_hardening_record($pdo, function_exists('dental_release_package_policy_ready') && dental_release_package_policy_ready() ? 'passed' : 'needs_review');
    }
    setup_record_history($pdo,'0.5.9.8.63','client_release_safety_qa_layout_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.63',
        'features'=>[
            'generated_installer_archive_runtime_forbidden_path_guard',
            'release_package_policy_uses_installer_service_safety_rules',
            'qa_smoke_test_layout_overlap_repaired',
            'qa_smoke_test_release_failures_now_show_specific_items',
            'manual_role_flow_checklist_added_to_qa_page',
            'doctor_empty_state_readiness_confirmed',
        ],
    ]);
}


function setup_v059864_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.64','>=')
        && (string) setting_get('system','first_time_setup_wizard_ready','0') === '1'
        && (string) setting_get('system','go_live_checklist_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/app/setup_readiness.php')
        && is_file(DENTAL_ROOT . '/modules/administration/setup_wizard.php')
        && is_file(DENTAL_ROOT . '/modules/administration/go_live_checklist.php');
}

function setup_apply_v059864(PDO $pdo): void
{
    if (function_exists('setup_apply_v059863')) {
        setup_apply_v059863($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.64_first_time_setup_wizard.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.64');
    setting_set('system','first_time_setup_wizard_ready','1');
    setting_set('system','go_live_checklist_ready','1');
    setting_set('system','setup_completion_indicator_ready','1');
    setting_set('system','demo_data_seeder_preparation_ready','1');
    setting_set('system','doctor_empty_state_go_live_confirmed','1');
    setting_set('system','final_update_status_consolidated','1');
    setup_record_history($pdo,'0.5.9.8.64','first_time_clinic_setup_wizard_go_live_checklist');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.64',
        'features'=>[
            'first_time_clinic_setup_wizard_added',
            'setup_completion_percentage_and_missing_items_added',
            'go_live_checklist_added',
            'guided_no_dentist_profile_readiness_path_confirmed',
            'demo_data_seeder_foundation_marked_for_next_update',
        ],
    ]);
}


function setup_v059865_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.65','>=')
        && (string) setting_get('system','demo_accounts_seeder_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/app/Services/DemoAccountService.php')
        && is_file(DENTAL_ROOT . '/modules/administration/demo_accounts.php');
}

function setup_apply_v059865(PDO $pdo): void
{
    if (function_exists('setup_apply_v059864')) {
        setup_apply_v059864($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.65_demo_accounts_seeder.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.65');
    setting_set('system','demo_accounts_seeder_ready','1');
    setting_set('system','demo_accounts_superuser_page_ready','1');
    setting_set('system','demo_accounts_orientation_mode_foundation','1');
    setup_record_history($pdo,'0.5.9.8.65','superuser_demo_accounts_seeder');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.65',
        'features'=>[
            'superuser_demo_accounts_page_added',
            'role_based_fictional_demo_logins_seedable',
            'demo_dentist_profile_and_schedule_seedable',
            'demo_cashier_window_seedable',
            'demo_accounts_deactivate_reactivate_controls_added',
        ],
    ]);
}


function setup_v0598651_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.65.1','>=')
        && (string) setting_get('system','demo_patients_seeder_ready','0') === '1'
        && (string) setting_get('system','service_requires_dentist_runtime_guard_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/app/Services/DemoAccountService.php')
        && is_file(DENTAL_ROOT . '/modules/administration/demo_accounts.php');
}

function setup_apply_v0598651(PDO $pdo): void
{
    if (function_exists('setup_apply_v059865')) {
        setup_apply_v059865($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.65.1_demo_patients_and_service_guard.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.65.1');
    setting_set('system','demo_patients_seeder_ready','1');
    setting_set('system','demo_patient_workflow_entries_ready','1');
    setting_set('system','service_requires_dentist_runtime_guard_ready','1');
    setup_record_history($pdo,'0.5.9.8.65.1','demo_patients_and_service_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.65.1',
        'features'=>[
            'superuser_demo_patients_seeder_added',
            'demo_patient_appointments_requests_billing_samples_added',
            'followup_reminder_service_requires_dentist_guard_added',
            'demo_patient_counts_and_existing_patient_table_added',
        ],
    ]);
}



function setup_v059866_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.66','>=')
        && (string) setting_get('system','full_demo_clinic_dataset_feature_ready','0') === '1'
        && (string) setting_get('system','orientation_mode_foundation_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/app/Services/DemoAccountService.php')
        && is_file(DENTAL_ROOT . '/modules/administration/demo_accounts.php');
}

function setup_apply_v059866(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598651')) {
        setup_apply_v0598651($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.66_full_demo_clinic_orientation_mode.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.66');
    setting_set('system','full_demo_clinic_dataset_feature_ready','1');
    setting_set('system','orientation_mode_foundation_ready','1');
    setting_set('system','demo_inventory_sterilization_seed_ready','1');
    setting_set('system','demo_clinic_profile_seed_ready','1');
    setup_record_history($pdo,'0.5.9.8.66','full_demo_clinic_dataset_and_orientation_mode');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.66',
        'features'=>[
            'full_demo_clinic_dataset_seed_button_added',
            'fictional_clinic_profile_hours_services_chairs_inventory_sterilization_samples_added',
            'demo_prescription_followup_recall_communication_samples_added',
            'orientation_mode_toggle_added_to_pause_real_email_delivery_during_training',
            'go_live_and_qa_checks_extended_for_full_demo_dataset',
        ],
    ]);
}

function setup_v0598662_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.66.2','>=')
        && (string) setting_get('system','runtime_workflow_patient_numbering_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/patient_numbering.php');
}

function setup_apply_v0598662(PDO $pdo): void
{
    if (function_exists('setup_apply_v059866')) {
        setup_apply_v059866($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.66.2_runtime_workflow_patient_numbering.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    if ((string) setting_get('patient_numbering','mode','') === '') setting_set('patient_numbering','mode','auto');
    if ((string) setting_get('patient_numbering','prefix','') === '') setting_set('patient_numbering','prefix','P');
    if ((string) setting_get('patient_numbering','year_format','') === '') setting_set('patient_numbering','year_format','ymd');
    if ((string) setting_get('patient_numbering','separator','') === '') setting_set('patient_numbering','separator','');
    if ((int) setting_get('patient_numbering','padding','0') < 1) setting_set('patient_numbering','padding','4');
    if ((int) setting_get('patient_numbering','next_number','0') < 1) setting_set('patient_numbering','next_number','1');
    setting_set('system','application_version','0.5.9.8.66.2');
    setting_set('system','runtime_workflow_patient_numbering_ready','1');
    setting_set('system','patient_numbering_configured','1');
    setup_record_history($pdo,'0.5.9.8.66.2','runtime_workflow_patient_numbering_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.66.2',
        'features'=>[
            'demo_seeder_branch_guard_extended',
            'patient_numbering_control_added',
            'clinic_profile_readiness_uses_public_profile_fallback',
            'mixed_tooth_chart_overflow_repaired',
            'supervised_admin_start_treatment_added',
            'mariadb_like_escape_syntax_repaired',
        ],
    ]);
}

function setup_v0598663_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.66.3','>=')
        && (string) setting_get('system','update_pending_reconciliation_ready','0') === '1'
        && (string) setting_get('system','runtime_workflow_patient_numbering_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/system_setup.php')
        && is_file(DENTAL_ROOT . '/app/update_readiness.php');
}

function setup_apply_v0598663(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598662')) {
        setup_apply_v0598662($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.66.3_update_pending_reconciliation.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.66.3');
    setting_set('system','update_pending_reconciliation_ready','1');
    setting_set('system','latest_update_apply_chain_ready','1');
    setting_set('system','updates_database_status_reconciled','1');
    setting_set('system','runtime_workflow_patient_numbering_ready','1');
    setup_record_history($pdo,'0.5.9.8.66.3','update_pending_reconciliation');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.66.3',
        'features'=>[
            'apply_all_pending_updates_now_calls_latest_patch_function',
            'update_pending_status_reconciles_after_hotfix_application',
            'patient_numbering_runtime_marker_confirmed',
            'system_setup_status_text_aligned_with_current_release',
        ],
    ]);
}


function setup_v0598664_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.66.4','>=')
        && (string) setting_get('system','patient_numbering_page_guard_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/patient_numbering.php')
        && is_file(DENTAL_ROOT . '/app/module_helpers.php');
}

function setup_apply_v0598664(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598663')) {
        setup_apply_v0598663($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.66.4_patient_numbering_page_guard.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    if ((string) setting_get('patient_numbering','mode','') === '') setting_set('patient_numbering','mode','auto');
    if ((string) setting_get('patient_numbering','prefix','') === '') setting_set('patient_numbering','prefix','P');
    if ((string) setting_get('patient_numbering','year_format','') === '') setting_set('patient_numbering','year_format','ymd');
    if ((int) setting_get('patient_numbering','padding','0') < 1) setting_set('patient_numbering','padding','4');
    if ((int) setting_get('patient_numbering','next_number','0') < 1) setting_set('patient_numbering','next_number','1');
    setting_set('system','application_version','0.5.9.8.66.4');
    setting_set('system','patient_numbering_page_guard_ready','1');
    setting_set('system','runtime_workflow_patient_numbering_ready','1');
    setting_set('system','updates_database_status_reconciled','1');
    setup_record_history($pdo,'0.5.9.8.66.4','patient_numbering_page_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.66.4',
        'features'=>[
            'patient_numbering_page_loads_module_helper_before_status_formatting',
            'patient_numbering_settings_defaults_confirmed',
            'generic_request_failure_on_patient_numbering_page_repaired',
        ],
    ]);
}

function setup_v059867_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.67','>=')
        && (string) setting_get('system','cashier_pos_payment_integrity_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/finance/billing.php')
        && is_file(DENTAL_ROOT . '/app/revenue_integrity.php');
}

function setup_apply_v059867(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598664')) {
        setup_apply_v0598664($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.67_cashier_pos_payment_integrity.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    if (setup_table_exists($pdo, table('payment_method_rules'))) {
        $methods = [
            ['cash','Cash',0,1],['card','Card',1,0],['gcash','GCash',1,0],['maya','Maya',1,0],
            ['bank_transfer','Bank Transfer',1,0],['e-wallet','Other e-wallet',1,0],['cheque','Cheque',1,0],['mixed','Mixed Payment',1,1],['other','Other',1,0],
        ];
        $stmt = $pdo->prepare('INSERT INTO `' . table('payment_method_rules') . '` (method_code,method_name,requires_reference,allows_change,is_active,created_at) VALUES (?,?,?,?,1,NOW()) ON DUPLICATE KEY UPDATE method_name=VALUES(method_name),requires_reference=VALUES(requires_reference),allows_change=VALUES(allows_change),is_active=1,updated_at=NOW()');
        foreach ($methods as $method) {
            $stmt->execute($method);
        }
    }
    if (setup_table_exists($pdo, table('payments'))) {
        setup_add_column($pdo, table('payments'), 'reference_required', 'TINYINT(1) NOT NULL DEFAULT 0');
        if (!setup_index_exists($pdo, table('payments'), 'idx_payment_method_reference')) {
            try { $pdo->exec('ALTER TABLE `' . table('payments') . '` ADD KEY `idx_payment_method_reference` (`method`,`reference_no`)'); } catch (Throwable $exception) { error_log($exception->getMessage()); }
        }
    }
    setting_set('system','application_version','0.5.9.8.67');
    setting_set('system','cashier_pos_payment_integrity_ready','1');
    setting_set('system','cashier_pos_reference_guard_ready','1');
    setting_set('system','cashier_transaction_lock_ui_ready','1');
    setup_record_history($pdo,'0.5.9.8.67','cashier_pos_payment_integrity');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.67',
        'features'=>[
            'cashier_transaction_focus_lock_reinforced',
            'payment_method_reference_rules_confirmed',
            'non_cash_duplicate_reference_guard_confirmed',
            'orientation_mode_demo_receipt_label_added',
            'cashier_payment_transaction_audit_event_added',
        ],
    ]);
}


function setup_v059868_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.68','>=')
        && (string) setting_get('system','sms_readiness_ready','0') === '1'
        && setup_table_exists($pdo, table('sms_outbox'))
        && setup_table_exists($pdo, table('sms_templates'))
        && is_file(DENTAL_ROOT . '/modules/administration/sms_settings.php')
        && is_file(DENTAL_ROOT . '/app/sms_service.php');
}

function setup_apply_v059868(PDO $pdo): void
{
    if (function_exists('setup_apply_v059867')) {
        setup_apply_v059867($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.68_sms_readiness.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    if ((string) setting_get('sms','provider','') === '') setting_set('sms','provider','log');
    if ((string) setting_get('sms','sender_name','') === '') setting_set('sms','sender_name','DentalCare');
    if ((string) setting_get('sms','default_country_code','') === '') setting_set('sms','default_country_code','+63');
    if ((int) setting_get('sms','queue_batch_size','0') < 1) setting_set('sms','queue_batch_size','10');
    setting_set('system','application_version','0.5.9.8.68');
    setting_set('system','sms_readiness_ready','1');
    setting_set('system','sms_templates_ready','1');
    setup_record_history($pdo,'0.5.9.8.68','sms_readiness_provider_templates_outbox');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.68',
        'features'=>[
            'sms_provider_settings_page_added',
            'sms_templates_and_outbox_added',
            'sms_test_send_and_queue_processing_added',
            'orientation_mode_pauses_live_sms_delivery',
            'go_live_and_qa_readiness_include_sms',
        ],
    ]);
}


function setup_v059869_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.69','>=')
        && (string) setting_get('system','sms_android_gateway_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/sms_settings.php')
        && is_file(DENTAL_ROOT . '/app/sms_service.php');
}

function setup_apply_v059869(PDO $pdo): void
{
    if (function_exists('setup_apply_v059868')) {
        setup_apply_v059868($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.69_android_sms_gateway.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    if ((string) setting_get('sms','android_gateway_header_name','') === '') setting_set('sms','android_gateway_header_name','x-api-key');
    setting_set('system','application_version','0.5.9.8.69');
    setting_set('system','sms_android_gateway_ready','1');
    setting_set('system','sms_readiness_ready','1');
    setup_record_history($pdo,'0.5.9.8.69','android_textbee_sms_gateway_readiness');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.69',
        'features'=>[
            'android_phone_sim_gateway_provider_added',
            'textbee_compatible_json_payload_added',
            'android_gateway_endpoint_api_key_device_fields_added',
            'sms_health_checks_include_android_gateway',
        ],
    ]);
}


function setup_v059870_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.70','>=')
        && (string) setting_get('system','setup_wizard_compact_grouped_view_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/setup_wizard.php')
        && is_file(DENTAL_ROOT . '/app/setup_readiness.php');
}

function setup_apply_v059870(PDO $pdo): void
{
    if (function_exists('setup_apply_v059869')) {
        setup_apply_v059869($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.70_setup_wizard_compact_grouped_view.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.70');
    setting_set('system','setup_wizard_compact_grouped_view_ready','1');
    setup_record_history($pdo,'0.5.9.8.70','setup_wizard_compact_grouped_view');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.70',
        'features'=>[
            'setup_wizard_long_checklist_replaced_with_grouped_phases',
            'setup_priority_missing_cards_added',
            'setup_step_facts_collapsed_to_details',
            'setup_sequence_numbers_normalized',
        ],
    ]);
}


function setup_v059871_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.71','>=')
        && (string) setting_get('system','long_list_compact_audit_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/public/assets/css/app.css')
        && is_file(DENTAL_ROOT . '/modules/administration/setup_wizard.php')
        && is_file(DENTAL_ROOT . '/modules/administration/system_smoke_test.php');
}

function setup_apply_v059871(PDO $pdo): void
{
    if (function_exists('setup_apply_v059870')) {
        setup_apply_v059870($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.71_long_list_compact_pages.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.71');
    setting_set('system','long_list_compact_audit_ready','1');
    setting_set('system','page_long_line_guard_ready','1');
    setup_record_history($pdo,'0.5.9.8.71','long_list_compact_page_audit');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.71',
        'features'=>[
            'global_long_line_wrapping_guard_added',
            'setup_wizard_ready_items_collapsed',
            'qa_smoke_test_details_collapsed',
            'go_live_checklist_top_missing_group_added',
            'system_setup_long_role_matrix_scroll_guard_added',
        ],
    ]);
}


function setup_v0598711_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.71.1','>=')
        && setup_table_exists($pdo, table('settings'))
        && setup_column_exists($pdo, table('settings'), 'group_name')
        && setup_column_exists($pdo, table('settings'), 'setting_key')
        && (string) setting_get('system','settings_table_repair_ready','0') === '1'
        && (string) setting_get('system','long_list_compact_audit_ready','0') === '1';
}

function setup_apply_v0598711(PDO $pdo): void
{
    if (function_exists('setup_apply_v059871')) {
        setup_apply_v059871($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.71.1_settings_table_repair.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.71.1');
    setting_set('system','settings_table_repair_ready','1');
    setting_set('system','long_list_compact_audit_ready','1');
    setting_set('system','page_long_line_guard_ready','1');
    setup_record_history($pdo,'0.5.9.8.71.1','settings_table_guard_after_long_list_update');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.71.1',
        'features'=>[
            'settings_table_created_if_missing',
            'long_list_migration_uses_correct_settings_schema',
            'setting_get_and_setting_set_are_schema_guarded',
        ],
    ]);
}


function setup_v0598712_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.71.2','>=')
        && setup_table_exists($pdo, table('settings'))
        && setup_column_exists($pdo, table('settings'), 'group_name')
        && (string) setting_get('system','settings_table_repair_ready','0') === '1'
        && (string) setting_get('system','long_list_compact_audit_ready','0') === '1'
        && (string) setting_get('system','update_chain_reconciliation_0712_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/app/update_readiness.php')
        && is_file(DENTAL_ROOT . '/modules/administration/system_setup.php');
}

function setup_apply_v0598712(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598711')) {
        setup_apply_v0598711($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.71.2_update_pending_chain_reconciliation.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.71.2');
    setting_set('system','settings_table_repair_ready','1');
    setting_set('system','long_list_compact_audit_ready','1');
    setting_set('system','page_long_line_guard_ready','1');
    setting_set('system','update_chain_reconciliation_0712_ready','1');
    setting_set('system','latest_update_apply_chain_ready','1');
    setting_set('system','updates_database_status_reconciled','1');
    setup_record_history($pdo,'0.5.9.8.71.2','update_pending_chain_reconciliation_after_settings_repair');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.71.2',
        'features'=>[
            'apply_all_pending_updates_calls_0_5_9_8_71_1_and_71_2',
            'system_setup_pending_checks_include_71_71_1_71_2',
            'database_version_reconciled_to_package_release',
        ],
    ]);
}


function setup_v059872_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.72','>=')
        && (string) setting_get('system','backup_restore_center_ready','0') === '1'
        && (string) setting_get('system','permission_matrix_ready','0') === '1'
        && (string) setting_get('system','tooth_favicon_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/backup_restore.php')
        && is_file(DENTAL_ROOT . '/modules/administration/permission_matrix.php')
        && is_file(DENTAL_ROOT . '/public/favicon.svg');
}

function setup_apply_v059872(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598712')) {
        setup_apply_v0598712($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.72_backup_restore_permission_matrix_favicon.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    if (setup_table_exists($pdo, table('backup_logs'))) {
        setup_add_column($pdo, table('backup_logs'), 'backup_type', 'VARCHAR(30) NOT NULL DEFAULT \"database\"');
        setup_add_column($pdo, table('backup_logs'), 'checksum_sha256', 'CHAR(64) NULL');
        setup_add_column($pdo, table('backup_logs'), 'file_count', 'INT UNSIGNED NOT NULL DEFAULT 0');
        setup_add_column($pdo, table('backup_logs'), 'is_encrypted', 'TINYINT(1) NOT NULL DEFAULT 0');
        setup_add_column($pdo, table('backup_logs'), 'verified_at', 'DATETIME NULL');
    }
    if ((string) setting_get('branding','favicon','') === '') {
        setting_set('branding','favicon','public/favicon.svg');
    }
    setting_set('system','application_version','0.5.9.8.72');
    setting_set('system','backup_restore_center_ready','1');
    setting_set('system','permission_matrix_ready','1');
    setting_set('system','tooth_favicon_ready','1');
    setting_set('system','go_live_backup_permission_gate_ready','1');
    setting_set('system','latest_update_apply_chain_ready','1');
    setting_set('system','updates_database_status_reconciled','1');
    setup_record_history($pdo,'0.5.9.8.72','backup_restore_permission_matrix_tooth_favicon');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.72',
        'features'=>[
            'backup_restore_center_added',
            'permission_matrix_role_access_audit_added',
            'go_live_checklist_includes_backup_and_permission_review',
            'qa_smoke_test_includes_backup_restore_permission_matrix_and_favicon',
            'tooth_only_favicon_added_and_wired_to_layout',
        ],
    ]);
}


function setup_v059873_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.73','>=')
        && (string) setting_get('system','treatment_flow_finalization_ready','0') === '1'
        && (string) setting_get('system','doctor_workspace_flow_rail_ready','0') === '1'
        && (string) setting_get('system','clinical_empty_state_guard_ready','0') === '1'
        && is_file(DENTAL_ROOT . '/app/treatment_flow.php');
}

function setup_apply_v059873(PDO $pdo): void
{
    if (function_exists('setup_apply_v059872')) {
        setup_apply_v059872($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.73_doctor_workspace_treatment_flow.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setting_set('system','application_version','0.5.9.8.73');
    setting_set('system','treatment_flow_finalization_ready','1');
    setting_set('system','doctor_workspace_flow_rail_ready','1');
    setting_set('system','clinical_empty_state_guard_ready','1');
    setting_set('system','go_live_treatment_flow_gate_ready','1');
    setting_set('system','latest_update_apply_chain_ready','1');
    setting_set('system','updates_database_status_reconciled','1');
    setup_record_history($pdo,'0.5.9.8.73','doctor_workspace_treatment_flow_finalization');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.73',
        'features'=>[
            'canonical_treatment_status_flow_helper_added',
            'doctor_dashboard_flow_rail_added',
            'clinical_workspace_flow_checks_added',
            'clinical_workspace_empty_state_guard_added',
            'go_live_and_qa_checks_include_treatment_flow',
        ],
    ]);
}

function setup_v059874_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.74','>=')
        && schema_table_exists(table('user_role_assignments'))
        && schema_column_exists(table('users'),'contact_number')
        && schema_column_exists(table('users'),'job_title')
        && schema_column_exists(table('users'),'emergency_contact_phone')
        && schema_column_exists(table('users'),'profile_completed_at')
        && (string) setting_get('system','staff_profile_dual_roles_ready','0') === '1';
}

function setup_apply_v059874(PDO $pdo): void
{
    if (function_exists('setup_apply_v059873')) {
        setup_apply_v059873($pdo);
    }
    $migration = DENTAL_ROOT . '/database/migrations/0.5.9.8.74_staff_profile_dual_roles.sql';
    if (is_file($migration)) {
        setup_execute_sql_file($pdo, $migration);
    }
    setup_add_column($pdo, table('users'), 'contact_number', 'VARCHAR(50) NULL AFTER `email`');
    setup_add_column($pdo, table('users'), 'job_title', 'VARCHAR(120) NULL AFTER `contact_number`');
    setup_add_column($pdo, table('users'), 'address', 'TEXT NULL AFTER `job_title`');
    setup_add_column($pdo, table('users'), 'emergency_contact_name', 'VARCHAR(150) NULL AFTER `address`');
    setup_add_column($pdo, table('users'), 'emergency_contact_phone', 'VARCHAR(50) NULL AFTER `emergency_contact_name`');
    setup_add_column($pdo, table('users'), 'profile_completed_at', 'DATETIME NULL AFTER `emergency_contact_phone`');
    if (schema_table_exists(table('user_role_assignments'))) {
        $pdo->exec('INSERT INTO `' . table('user_role_assignments') . '` (user_id,role_id,assignment_type,status,created_at) SELECT u.id,u.role_id,"primary","active",NOW() FROM `' . table('users') . '` u ON DUPLICATE KEY UPDATE assignment_type="primary",status="active",updated_at=NOW()');
    }
    setting_set('system','application_version','0.5.9.8.74');
    setting_set('system','staff_profile_dual_roles_ready','1');
    setting_set('system','staff_self_profile_ready','1');
    setting_set('system','role_assignments_table_ready','1');
    setup_record_history($pdo,'0.5.9.8.74','staff_profile_dual_roles');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.74',
        'features'=>[
            'my_profile_self_service_added',
            'protected_role_assignment_manager_added',
            'dual_role_assignment_table_added',
            'profile_completion_contact_fields_added',
            'role_switch_supports_assigned_dual_roles',
        ],
    ]);
}


function setup_v059875_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.75','>=')
        && (string) setting_get('system','task_switcher_visibility_ready','0') === '1';
}

function setup_apply_v059875(PDO $pdo): void
{
    if (function_exists('setup_apply_v059874')) {
        setup_apply_v059874($pdo);
    }
    setting_set('system','application_version','0.5.9.8.75');
    setting_set('system','task_switcher_visibility_ready','1');
    setup_record_history($pdo,'0.5.9.8.75','task_switcher_visibility_guard');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.75',
        'features'=>[
            'single_role_staff_task_switcher_hidden',
            'secondary_role_switching_requires_explicit_assignment',
            'admin_superuser_task_preview_preserved',
        ],
    ]);
}

function setup_v059876_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.76','>=')
        && (string) setting_get('system','doctor_profile_credential_boundary_ready','0') === '1';
}

function setup_apply_v059876(PDO $pdo): void
{
    if (function_exists('setup_apply_v059875')) {
        setup_apply_v059875($pdo);
    }
    if (schema_table_exists(table('dentist_documents'))) {
        setup_add_column($pdo, table('dentist_documents'), 'verified_at', 'DATETIME NULL AFTER `verified_by`');
    }
    if (schema_table_exists(table('dentist_profiles')) && schema_table_exists(table('dentist_documents')) && schema_column_exists(table('dentist_profiles'),'credential_status')) {
        $pdo->exec('UPDATE `' . table('dentist_profiles') . '` dp SET dp.credential_status="verified", dp.credential_verified_by=COALESCE(dp.credential_verified_by,(SELECT dd.verified_by FROM `' . table('dentist_documents') . '` dd WHERE dd.dentist_user_id=dp.user_id AND dd.document_type="license" AND dd.verification_status="verified" AND (dd.expires_at IS NULL OR dd.expires_at>=CURDATE()) ORDER BY dd.id DESC LIMIT 1)), dp.credential_verified_at=COALESCE(dp.credential_verified_at,NOW()), dp.credential_note=COALESCE(NULLIF(dp.credential_note,""),"Professional license document verified during update reconciliation."), dp.profile_status=CASE WHEN dp.profile_status="suspended" THEN dp.profile_status ELSE "active" END WHERE dp.credential_status<>"verified" AND COALESCE(dp.license_number,"")<>"" AND dp.license_expiry>=CURDATE() AND EXISTS (SELECT 1 FROM `' . table('dentist_documents') . '` dd2 WHERE dd2.dentist_user_id=dp.user_id AND dd2.document_type="license" AND dd2.verification_status="verified" AND (dd2.expires_at IS NULL OR dd2.expires_at>=CURDATE()))');
    }
    setting_set('system','application_version','0.5.9.8.76');
    setting_set('system','doctor_profile_credential_boundary_ready','1');
    setup_record_history($pdo,'0.5.9.8.76','doctor_profile_self_completion_admin_verification_reconciliation');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.76',
        'features'=>[
            'doctor_self_profile_and_document_upload_boundary',
            'admin_only_license_verification_reconciles_credential_status',
            'self_verification_block_for_dual_role_doctors',
            'update_pending_chain_extended_to_0_5_9_8_76',
        ],
    ]);
}


function setup_v059878_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.78','>=')
        && (string) setting_get('system','role_satisfaction_completion_review_query_repaired','0') === '1'
        && (string) setting_get('system','public_release_downloads_disabled_by_default','0') === '1'
        && (string) setting_get('system','release_package_export_sanitization_ready','0') === '1';
}

function setup_apply_v059878(PDO $pdo): void
{
    if (function_exists('setup_apply_v059876')) {
        setup_apply_v059876($pdo);
    }
    setting_set('system','application_version','0.5.9.8.78');
    setting_set('system','role_satisfaction_completion_review_query_repaired','1');
    setting_set('system','public_release_downloads_disabled_by_default','1');
    setting_set('system','release_package_export_sanitization_ready','1');
    setting_set('system','final_update_status_consolidated','1');
    setup_record_history($pdo,'0.5.9.8.78','release_safety_and_completion_review_hotfix');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.78',
        'features'=>[
            'role_satisfaction_completion_review_column_fixed',
            'public_installer_download_buttons_disabled_by_default',
            'direct_release_download_requires_superuser',
            'package_builder_excludes_runtime_update_manifests_and_private_files',
        ],
    ]);
}

function setup_v059879_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(),'0.5.9.8.79','>=')
        && (string) setting_get('system','public_verified_buyer_installer_downloads_ready','0') === '1'
        && (string) setting_get('system','release_package_export_sanitization_ready','0') === '1';
}

function setup_apply_v059879(PDO $pdo): void
{
    if (function_exists('setup_apply_v059878')) {
        setup_apply_v059878($pdo);
    }
    setting_set('system','application_version','0.5.9.8.79');
    setting_set('system','public_verified_buyer_installer_downloads_ready','1');
    setting_set('system','public_demo_installer_download_ready','1');
    setting_set('system','public_anonymous_full_installer_blocked','1');
    setting_set('system','release_package_export_sanitization_ready','1');
    setup_record_history($pdo,'0.5.9.8.79','verified_buyer_installer_download_access');
    audit('system_update_applied','system',null,[
        'version'=>'0.5.9.8.79',
        'features'=>[
            'public_demo_installer_download_restored',
            'full_installer_download_requires_signed_buyer_license_or_superuser',
            'anonymous_raw_full_installer_download_blocked',
            'package_builder_sanitization_preserved',
        ],
    ]);
}


function setup_v059880_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.80', '>=')
        && (string) setting_get('system', 'public_website_pages_navigation_manager_ready', '0') === '1'
        && (string) setting_get('system', 'public_landing_page_builder_ready', '0') === '1';
}

function setup_apply_v059880(PDO $pdo): void
{
    if (function_exists('setup_apply_v059879')) {
        setup_apply_v059879($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.80');
    setting_set('system', 'public_website_pages_navigation_manager_ready', '1');
    setting_set('system', 'public_landing_page_builder_ready', '1');
    setting_set('system', 'public_verified_buyer_installer_downloads_ready', '1');
    setup_record_history($pdo, '0.5.9.8.80', 'public_website_pages_navigation_and_landing_builder');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.80',
        'features' => [
            'public_webpage_manager',
            'public_navigation_manager',
            'landing_page_copy_builder',
            'buyer_installer_access_preserved',
        ],
    ]);
}

function setup_v059881_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.81', '>=')
        && (string) setting_get('system', 'public_page_image_design_builder_ready', '0') === '1';
}

function setup_apply_v059881(PDO $pdo): void
{
    if (function_exists('setup_apply_v059880')) {
        setup_apply_v059880($pdo);
    }
    $uploadDir = DENTAL_ROOT . '/public/uploads/website-pages';
    if (!is_dir($uploadDir)) {
        @mkdir($uploadDir, 0775, true);
    }
    $htaccess = $uploadDir . '/.htaccess';
    if (!is_file($htaccess)) {
        @file_put_contents($htaccess, "Options -Indexes\n\n<FilesMatch \"\\.(?:php[0-9]?|phtml|phar|cgi|pl|py|sh|shtml)$\">\n  <IfModule mod_authz_core.c>\n    Require all denied\n  </IfModule>\n  <IfModule !mod_authz_core.c>\n    Order allow,deny\n    Deny from all\n  </IfModule>\n</FilesMatch>\n");
    }
    setting_set('system', 'application_version', '0.5.9.8.81');
    setting_set('system', 'public_page_image_design_builder_ready', '1');
    setting_set('system', 'public_website_pages_navigation_manager_ready', '1');
    setup_record_history($pdo, '0.5.9.8.81', 'public_page_image_design_builder');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.81',
        'features' => [
            'public_page_image_uploads',
            'public_page_design_layouts',
            'public_page_cta_buttons',
            'public_page_feature_cards',
        ],
    ]);
}

function setup_v059882_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.82', '>=')
        && (string) setting_get('system', 'public_website_media_library_ready', '0') === '1'
        && (string) setting_get('system', 'public_landing_section_builder_ready', '0') === '1'
        && (string) setting_get('system', 'public_page_preview_ready', '0') === '1';
}

function setup_apply_v059882(PDO $pdo): void
{
    if (function_exists('setup_apply_v059881')) {
        setup_apply_v059881($pdo);
    }
    foreach (['website-pages', 'website-media'] as $folder) {
        $uploadDir = DENTAL_ROOT . '/public/uploads/' . $folder;
        if (!is_dir($uploadDir)) {
            @mkdir($uploadDir, 0775, true);
        }
        $htaccess = $uploadDir . '/.htaccess';
        if (!is_file($htaccess)) {
            $rules = <<<'HTACCESS'
Options -Indexes

<FilesMatch "\.(?:php[0-9]?|phtml|phar|cgi|pl|py|sh|shtml)$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
  </IfModule>
</FilesMatch>
HTACCESS;
            @file_put_contents($htaccess, $rules . "
");
        }
    }
    setting_set('system', 'application_version', '0.5.9.8.82');
    setting_set('system', 'public_website_media_library_ready', '1');
    setting_set('system', 'public_landing_section_builder_ready', '1');
    setting_set('system', 'public_page_preview_ready', '1');
    setting_set('system', 'public_website_header_footer_builder_ready', '1');
    setting_set('system', 'public_safe_slug_guard_ready', '1');
    setting_set('system', 'public_page_image_design_builder_ready', '1');
    setup_record_history($pdo, '0.5.9.8.82', 'website_builder_media_preview_section_controls');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.82',
        'features' => [
            'website_media_library',
            'draft_public_page_preview_for_managers',
            'landing_section_show_hide_order_and_images',
            'public_header_footer_social_controls',
            'reserved_slug_guard',
        ],
    ]);
}


function setup_v059883_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.83', '>=')
        && (
            (string) setting_get('system', 'authenticated_app_fullscreen_ready', '0') === '1'
            || (string) setting_get('system', 'fullscreen_shell_stability_rollback_ready', '0') === '1'
            || (string) setting_get('system', 'in_app_fullscreen_removed', '0') === '1'
        );
}

function setup_apply_v059883(PDO $pdo): void
{
    if (function_exists('setup_apply_v059882')) {
        setup_apply_v059882($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.83');
    setting_set('system', 'authenticated_app_fullscreen_ready', '1');
    setting_set('system', 'authenticated_app_fullscreen_policy', 'locked_after_staff_login');
    setup_record_history($pdo, '0.5.9.8.83', 'authenticated_app_fullscreen_workspace');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.83',
        'features' => [
            'fullscreen_request_from_staff_login_submit',
            'authenticated_workspace_fullscreen_restore_prompt',
            'app_wide_fullscreen_topbar_control',
            'public_pages_left_normal_non_kiosk',
        ],
    ]);
}

function setup_v059884_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.84', '>=')
        && (
            (string) setting_get('system', 'authenticated_fullscreen_prompt_sanity_ready', '0') === '1'
            || (string) setting_get('system', 'fullscreen_shell_stability_rollback_ready', '0') === '1'
            || (string) setting_get('system', 'in_app_fullscreen_removed', '0') === '1'
        );
}

function setup_apply_v059884(PDO $pdo): void
{
    if (function_exists('setup_apply_v059883')) {
        setup_apply_v059883($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.84');
    setting_set('system', 'authenticated_app_fullscreen_ready', '1');
    setting_set('system', 'authenticated_app_fullscreen_policy', 'locked_after_staff_login');
    setting_set('system', 'authenticated_fullscreen_prompt_sanity_ready', '1');
    setup_record_history($pdo, '0.5.9.8.84', 'authenticated_fullscreen_prompt_sanity');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.84',
        'features' => [
            'fullscreen_requested_from_staff_login_only',
            'no_repeated_fullscreen_prompt_on_focus_or_normal_clicks',
            'restore_overlay_only_after_exit_or_blocked_login_fullscreen',
            'logged_in_workspace_remains_full_width_and_full_height',
        ],
    ]);
}


function setup_v059885_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.85', '>=')
        && is_file(DENTAL_ROOT . '/config/route_slugs.php')
        && (string) setting_get('system', 'clean_url_slug_routing_ready', '0') === '1';
}

function setup_apply_v059885(PDO $pdo): void
{
    if (function_exists('setup_apply_v059884')) {
        setup_apply_v059884($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.85');
    setting_set('system', 'clean_url_slug_routing_ready', '1');
    setting_set('system', 'clean_url_slug_routing_mode', 'enabled_with_php_fallback');
    setup_record_history($pdo, '0.5.9.8.85', 'clean_url_slug_routing');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.85',
        'features' => [
            'clean_public_slugs_for_routed_pages',
            'legacy_php_links_remain_compatible',
            'query_strings_are_preserved',
            'reserved_public_page_slugs_remain_protected',
        ],
    ]);
}


function setup_v059886_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.86', '>=')
        && (string) setting_get('system', 'clean_url_redirect_loop_fix_ready', '0') === '1';
}

function setup_apply_v059886(PDO $pdo): void
{
    if (function_exists('setup_apply_v059885')) {
        setup_apply_v059885($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.86');
    setting_set('system', 'clean_url_slug_routing_ready', '1');
    setting_set('system', 'clean_url_redirect_loop_fix_ready', '1');
    setup_record_history($pdo, '0.5.9.8.86', 'clean_url_redirect_loop_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.86',
        'features' => [
            'clean_slugs_no_longer_redirect_to_themselves',
            'legacy_php_get_urls_still_redirect_to_clean_slugs',
            'post_submissions_are_not_redirected',
        ],
    ]);
}


function setup_v059887_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.87', '>=')
        && is_file(DENTAL_ROOT . '/app_shell.php')
        && (string) setting_get('system', 'persistent_fullscreen_shell_ready', '0') === '1';
}

function setup_apply_v059887(PDO $pdo): void
{
    if (function_exists('setup_apply_v059886')) {
        setup_apply_v059886($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.87');
    setting_set('system', 'persistent_fullscreen_shell_ready', '1');
    setting_set('system', 'authenticated_app_fullscreen_policy', 'persistent_workspace_shell');
    setup_record_history($pdo, '0.5.9.8.87', 'persistent_fullscreen_workspace_shell');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.87',
        'features' => [
            'staff_login_opens_persistent_workspace_shell',
            'login_form_can_trigger_parent_fullscreen_before_iframe_submission',
            'internal_pages_load_inside_shell_to_prevent_fullscreen_drop_on_navigation',
            'page_level_fullscreen_prompts_disabled_inside_shell',
        ],
    ]);
}


function setup_v059888_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.88', '>=')
        && (string) setting_get('system', 'fullscreen_shell_stability_rollback_ready', '0') === '1';
}

function setup_apply_v059888(PDO $pdo): void
{
    if (function_exists('setup_apply_v059887')) {
        setup_apply_v059887($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.88');
    setting_set('system', 'authenticated_app_fullscreen_policy', 'css_workspace_only');
    setting_set('system', 'fullscreen_shell_stability_rollback_ready', '1');
    setup_record_history($pdo, '0.5.9.8.88', 'fullscreen_shell_stability_rollback');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.88',
        'features' => [
            'unstable_workspace_shell_redirect_disabled',
            'normal_staff_login_restored',
            'browser_fullscreen_prompt_disabled',
            'clean_url_slug_routing_kept',
        ],
    ]);
}

function setup_v059889_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.89', '>=')
        && (string) setting_get('system', 'in_app_fullscreen_removed', '0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/kiosk_mode.php');
}

function setup_apply_v059889(PDO $pdo): void
{
    if (function_exists('setup_apply_v059888')) {
        setup_apply_v059888($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.89');
    setting_set('system', 'authenticated_app_fullscreen_policy', 'off');
    setting_set('system', 'authenticated_app_fullscreen_ready', '0');
    setting_set('system', 'authenticated_fullscreen_prompt_sanity_ready', '0');
    setting_set('system', 'in_app_fullscreen_removed', '1');
    setting_set('system', 'kiosk_mode_guidance_ready', '1');
    setup_record_history($pdo, '0.5.9.8.89', 'fullscreen_removed_kiosk_guidance');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.89',
        'features' => [
            'removed_in_app_browser_fullscreen_forcing',
            'kept_full_height_workspace_layout',
            'added_kiosk_mode_setup_guidance',
            'normal_php_navigation_remains_stable',
        ],
    ]);
}

function setup_v059890_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.90', '>=')
        && (string) setting_get('system', 'fullscreen_superseded_update_readiness_fixed', '0') === '1';
}

function setup_apply_v059890(PDO $pdo): void
{
    if (function_exists('setup_apply_v059889')) {
        setup_apply_v059889($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.90');
    setting_set('system', 'authenticated_app_fullscreen_policy', 'off');
    setting_set('system', 'authenticated_app_fullscreen_ready', '0');
    setting_set('system', 'authenticated_fullscreen_prompt_sanity_ready', '0');
    setting_set('system', 'in_app_fullscreen_removed', '1');
    setting_set('system', 'kiosk_mode_guidance_ready', '1');
    setting_set('system', 'fullscreen_superseded_update_readiness_fixed', '1');
    setup_record_history($pdo, '0.5.9.8.90', 'fullscreen_superseded_update_readiness_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.90',
        'features' => [
            'prevents_superseded_fullscreen_updates_from_showing_pending',
            'keeps_in_app_fullscreen_removed',
            'keeps_kiosk_guidance_available',
            'keeps_normal_navigation_stable',
        ],
    ]);
}

function setup_v059891_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.91', '>=')
        && (string) setting_get('system', 'system_setup_paged_sections_ready', '0') === '1';
}

function setup_apply_v059891(PDO $pdo): void
{
    if (function_exists('setup_apply_v059890')) {
        setup_apply_v059890($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.91');
    setting_set('system', 'system_setup_paged_sections_ready', '1');
    setup_record_history($pdo, '0.5.9.8.91', 'system_setup_paginated_sections');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.91',
        'features' => [
            'system_setup_long_sections_paginated',
            '10_20_50_page_size_controls',
            'permission_matrix_pagination',
            'backup_repository_pagination',
        ],
    ]);
}

function setup_v059892_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.92', '>=')
        && is_file(DENTAL_ROOT . '/handlers/download_kiosk_launcher.php')
        && (string) setting_get('system', 'kiosk_launcher_generator_ready', '0') === '1';
}

function setup_apply_v059892(PDO $pdo): void
{
    if (function_exists('setup_apply_v059891')) {
        setup_apply_v059891($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.92');
    setting_set('system', 'kiosk_mode_guidance_ready', '1');
    setting_set('system', 'kiosk_launcher_generator_ready', '1');
    setup_record_history($pdo, '0.5.9.8.92', 'kiosk_launcher_generator');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.92',
        'features' => [
            'downloadable_edge_kiosk_launcher',
            'downloadable_chrome_kiosk_launcher',
            'startup_page_selector_for_kiosk_mode',
            'no_in_app_forced_fullscreen',
        ],
    ]);
}

function setup_v059893_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.93', '>=')
        && is_file(DENTAL_ROOT . '/modules/administration/kiosk_mode.php')
        && (string) setting_get('system', 'kiosk_access_recovery_ready', '0') === '1';
}

function setup_apply_v059893(PDO $pdo): void
{
    if (function_exists('setup_apply_v059892')) {
        setup_apply_v059892($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.93');
    setting_set('system', 'kiosk_access_recovery_ready', '1');
    setting_set('system', 'permission_matrix_paginated_ready', '1');
    setup_record_history($pdo, '0.5.9.8.93', 'kiosk_access_recovery_permission_pagination');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.93',
        'features' => [
            'fixed_kiosk_mode_page_runtime_error',
            'added_short_kiosk_route',
            'added_kiosk_links_to_admin_and_sidebar',
            'permission_matrix_explicit_pagination',
        ],
    ]);
}

function setup_v059894_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.94', '>=')
        && is_file(DENTAL_ROOT . '/kiosk.php')
        && is_file(DENTAL_ROOT . '/download_kiosk_launcher.php')
        && (string) setting_get('system', 'kiosk_physical_fallback_ready', '0') === '1';
}

function setup_apply_v059894(PDO $pdo): void
{
    if (function_exists('setup_apply_v059893')) {
        setup_apply_v059893($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.94');
    setting_set('system', 'kiosk_access_recovery_ready', '1');
    setting_set('system', 'kiosk_physical_fallback_ready', '1');
    setting_set('system', 'kiosk_shortcut_creator_ready', '1');
    setup_record_history($pdo, '0.5.9.8.94', 'kiosk_physical_fallback_shortcut_creator');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.94',
        'features' => [
            'physical_kiosk_php_fallback',
            'physical_kiosk_mode_php_fallback',
            'physical_download_kiosk_launcher_fallback',
            'desktop_shortcut_creator_for_kiosk_mode',
        ],
    ]);
}

function setup_v059895_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.95', '>=')
        && (string) setting_get('system', 'update_chain_059894_constant_fix_ready', '0') === '1';
}

function setup_apply_v059895(PDO $pdo): void
{
    if (function_exists('setup_apply_v059894')) {
        setup_apply_v059894($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.95');
    setting_set('system', 'update_chain_059894_constant_fix_ready', '1');
    setup_record_history($pdo, '0.5.9.8.95', 'update_chain_059894_constant_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.95',
        'features' => [
            'fixed_setup_apply_v059894_function_exists_call',
            'restored_apply_all_update_chain_after_kiosk_hotfix',
        ],
    ]);
}

function setup_v059896_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.96', '>=')
        && is_file(DENTAL_ROOT . '/handlers/download_kiosk_launcher.php')
        && (string) setting_get('system', 'kiosk_launcher_physical_url_ready', '0') === '1';
}

function setup_apply_v059896(PDO $pdo): void
{
    if (function_exists('setup_apply_v059895')) {
        setup_apply_v059895($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.96');
    setting_set('system', 'kiosk_launcher_physical_url_ready', '1');
    setting_set('system', 'kiosk_shortcut_creator_ready', '1');
    setup_record_history($pdo, '0.5.9.8.96', 'kiosk_launcher_physical_url_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.96',
        'features' => [
            'kiosk_launcher_uses_login_php',
            'kiosk_launcher_uses_route_php_targets',
            'chrome_edge_dedicated_kiosk_profile',
            'old_clean_url_kiosk_shortcut_404_fixed',
        ],
    ]);
}


function setup_v059897_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.97', '>=')
        && (string) setting_get('system', 'future_chair_session_availability_ready', '0') === '1';
}

function setup_apply_v059897(PDO $pdo): void
{
    if (function_exists('setup_apply_v059896')) {
        setup_apply_v059896($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.97');
    setting_set('system', 'future_chair_session_availability_ready', '1');
    setup_record_history($pdo, '0.5.9.8.97', 'future_chair_session_availability_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.97',
        'features' => [
            'future_dentist_chair_sessions_resolve_per_slot',
            'public_booking_no_longer_requires_current_chair_binding',
            'multi_service_bundle_availability_uses_actual_segment_chairs',
        ],
    ]);
}

function setup_v059898_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.98', '>=')
        && (string) setting_get('system', 'smtp_tls_diagnostics_ready', '0') === '1';
}

function setup_apply_v059898(PDO $pdo): void
{
    if (function_exists('setup_apply_v059897')) {
        setup_apply_v059897($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.98');
    setting_set('system', 'smtp_tls_diagnostics_ready', '1');
    setup_record_history($pdo, '0.5.9.8.98', 'smtp_tls_starttls_diagnostics_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.98',
        'features' => [
            'smtp_starttls_failure_has_actionable_port_certificate_guidance',
            'smtp_tls_crypto_method_uses_available_php_tls_versions',
            'smtp_certificate_host_override_supported',
        ],
    ]);
}


function setup_v059899_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.99', '>=')
        && (string) setting_get('system', 'smtp_gmail_redirect_guard_ready', '0') === '1';
}

function setup_apply_v059899(PDO $pdo): void
{
    if (function_exists('setup_apply_v059898')) {
        setup_apply_v059898($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.99');
    setting_set('system', 'smtp_gmail_redirect_guard_ready', '1');
    setup_record_history($pdo, '0.5.9.8.99', 'smtp_gmail_redirect_guard_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.99',
        'features' => [
            'smtp_gmail_cpanel_certificate_mismatch_detected_as_redirect_or_block',
            'smtp_public_provider_certificate_override_blocked',
            'smtp_diagnostics_show_remote_endpoint',
            'smtp_settings_warn_when_gmail_returns_cpanel_certificate',
        ],
    ]);
}

function setup_v0598100_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.100', '>=')
        && (string) setting_get('system', 'gmail_api_mail_driver_ready', '0') === '1';
}

function setup_apply_v0598100(PDO $pdo): void
{
    if (function_exists('setup_apply_v059899')) {
        setup_apply_v059899($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.100');
    setting_set('system', 'gmail_api_mail_driver_ready', '1');
    setup_record_history($pdo, '0.5.9.8.100', 'gmail_api_mail_driver');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.100',
        'features' => [
            'gmail_api_mail_driver_uses_https_443_not_smtp_ports',
            'gmail_oauth_refresh_token_delivery',
            'email_settings_provider_selector_gmail_api',
            'gmail_api_diagnostics_and_full_delivery_test',
        ],
    ]);
}


function setup_v0598101_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.101', '>=')
        && (string) setting_get('system', 'navigation_group_context_ready', '0') === '1';
}

function setup_apply_v0598101(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598100')) {
        setup_apply_v0598100($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.101');
    setting_set('system', 'navigation_group_context_ready', '1');
    setup_record_history($pdo, '0.5.9.8.101', 'navigation_group_context_guard');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.101',
        'features' => [
            'sidebar_group_context_preserved_on_duplicate_routes',
            'context_navigation_links_keep_selected_group',
            'mobile_navigation_keeps_same_group_area',
            'navigation_area_parameter_ignored_by_route_access_checks',
        ],
    ]);
}


function setup_v0598102_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.102', '>=')
        && (string) setting_get('system', 'online_request_quick_approval_ready', '0') === '1';
}

function setup_apply_v0598102(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598101')) {
        setup_apply_v0598101($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.102');
    setting_set('system', 'online_request_quick_approval_ready', '1');
    setup_record_history($pdo, '0.5.9.8.102', 'online_request_quick_approval');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.102',
        'features' => [
            'one_click_approve_online_requested_schedule',
            'online_request_confirmation_source',
            'new_patient_approval_without_existing_patient_identity_checkbox',
            'manual_contact_and_status_actions_collapsed_below_booking',
            'future_chair_resolution_rechecked_during_request_conversion',
        ],
    ]);
}


function setup_v0598103_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.103', '>=')
        && (string) setting_get('system', 'odontogram_tooth_scroll_anchor_ready', '0') === '1';
}

function setup_apply_v0598103(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598102')) {
        setup_apply_v0598102($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.103');
    setting_set('system', 'odontogram_tooth_scroll_anchor_ready', '1');
    setup_record_history($pdo, '0.5.9.8.103', 'odontogram_tooth_scroll_anchor_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.103',
        'features' => [
            'odontogram_tooth_click_keeps_view_on_selected_tooth',
            'selected_tooth_hash_anchor_after_reload',
            'read_only_chart_selection_no_longer_scrolls_to_top_notice',
            'finding_dialog_focus_no_longer_forces_page_jump',
            'saved_or_resolved_tooth_returns_to_selected_tooth_anchor',
        ],
    ]);
}










function setup_v0598104_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.104', '>=')
        && (string) setting_get('system', 'walkin_no_appointment_checkin_ready', '0') === '1';
}

function setup_apply_v0598104(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598103')) {
        setup_apply_v0598103($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.104');
    setting_set('system', 'walkin_no_appointment_checkin_ready', '1');
    setup_record_history($pdo, '0.5.9.8.104', 'walkin_no_appointment_checkin_and_role_flow_audit');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.104',
        'features' => [
            'patient_profile_walkin_checkin_without_appointment',
            'queue_walkin_existing_patient_prefill',
            'appointment_checkin_label_reserved_for_confirmed_today_appointments',
            'walkin_quick_registration_saves_medical_alerts',
            'role_flow_audit_started_with_no_shortcut_scope',
        ],
    ]);
}



function setup_v0598105_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.105', '>=')
        && (string) setting_get('system', 'mail_provider_neutral_guidance_ready', '0') === '1'
        && (string) setting_get('system', 'odontogram_click_stability_ready', '0') === '1'
        && (string) setting_get('system', 'walkin_dialog_top_start_ready', '0') === '1';
}

function setup_apply_v0598105(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598104')) {
        setup_apply_v0598104($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.105');
    setting_set('system', 'mail_provider_neutral_guidance_ready', '1');
    setting_set('system', 'odontogram_click_stability_ready', '1');
    setting_set('system', 'walkin_dialog_top_start_ready', '1');
    setup_record_history($pdo, '0.5.9.8.105', 'mail_provider_neutral_guidance_odontogram_walkin_scroll');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.105',
        'features' => [
            'gmail_uses_gmail_api_without_smtp_certificate_override',
            'smtp_diagnostics_report_remote_certificate_without_hardcoding_hosting_brand',
            'odontogram_tooth_click_prevents_default_anchor_jump_before_dialog_open',
            'read_only_odontogram_inspection_keeps_selected_tooth_anchor',
            'walkin_dialog_opens_at_patient_search_top_instead_of_scrolling_to_service',
        ],
    ]);
}

function setup_v0598106_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.106', '>=')
        && (string) setting_get('system', 'clinical_start_assistant_prompt_ready', '0') === '1';
}

function setup_apply_v0598106(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598105')) {
        setup_apply_v0598105($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.106');
    setting_set('system', 'clinical_start_assistant_prompt_ready', '1');
    setup_record_history($pdo, '0.5.9.8.106', 'clinical_start_assistant_confirmation');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.106',
        'features' => [
            'dentist_start_treatment_prompts_for_dental_assistant_or_none',
            'assistant_assignment_saved_at_clinical_start',
            'no_assistant_choice_is_recorded_as_no_assignment_for_visit',
            'clinical_day_and_treatment_dashboard_use_same_start_flow',
        ],
    ]);
}

function setup_v0598107_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.107', '>=')
        && (string) setting_get('system', 'clinical_scroll_preservation_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_manual_next_required_ready', '0') === '1';
}

function setup_apply_v0598107(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598106')) {
        setup_apply_v0598106($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.107');
    setting_set('system', 'clinical_scroll_preservation_ready', '1');
    setting_set('system', 'clinical_manual_next_required_ready', '1');
    setup_record_history($pdo, '0.5.9.8.107', 'clinical_flow_scroll_position_stability');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.107',
        'features' => [
            'forms_preserve_operator_scroll_position_after_save_or_refresh',
            'cashier_and_clinical_workflows_no_longer_jump_to_page_top_after_same_page_posts',
            'clinical_encounter_no_longer_auto_scrolls_to_next_required_item_on_initial_load',
            'dentist_gets_clear_next_required_workflow_guide_and_manual_next_button',
        ],
    ]);
}



function setup_v0598108_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.108', '>=')
        && (string) setting_get('system', 'patient_numbering_canonical_yyyy_ready', '0') === '1'
        && (string) setting_get('system', 'patient_creation_paths_use_numbering_engine', '0') === '1'
        && (string) setting_get('patient_numbering', 'prefix', 'P') === 'P'
        && (string) setting_get('patient_numbering', 'year_format', 'Y') === 'Y'
        && (string) setting_get('patient_numbering', 'separator', '') === ''
        && (string) setting_get('patient_numbering', 'padding', '4') === '4';
}

function setup_apply_v0598108(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598107')) {
        setup_apply_v0598107($pdo);
    }

    $mode = (string) setting_get('patient_numbering', 'mode', 'auto');
    if (!in_array($mode, ['auto','auto_allow_override','manual'], true)) $mode = 'auto';
    if ($mode !== 'manual') {
        setting_set('patient_numbering', 'mode', 'auto');
    }
    setting_set('patient_numbering', 'prefix', 'P');
    setting_set('patient_numbering', 'year_format', 'Y');
    setting_set('patient_numbering', 'separator', '');
    setting_set('patient_numbering', 'padding', '4');
    setting_set('system', 'patient_numbering_configured', '1');

    $settings = function_exists('dental_patient_numbering_settings') ? dental_patient_numbering_settings() : ['prefix'=>'P','year_format'=>'Y','separator'=>'','padding'=>4,'next_number'=>1];
    $nextFromExisting = function_exists('dental_patient_numbering_next_counter') ? dental_patient_numbering_next_counter($pdo, $settings) : 1;
    $currentNext = max(1, (int) setting_get('patient_numbering', 'next_number', '1'));
    setting_set('patient_numbering', 'next_number', (string) max($currentNext, $nextFromExisting));

    $renumbered = 0;
    $patientsTable = table('patients');
    if (setup_table_exists($pdo, $patientsTable)) {
        $rows = [];
        try {
            $where = 'patient_no REGEXP ?';
            $args = ['^P[0-9]{10}$'];
            if (setup_column_exists($pdo, $patientsTable, 'deleted_at')) $where .= ' AND deleted_at IS NULL';
            $stmt = $pdo->prepare('SELECT id,branch_id,patient_no FROM `' . $patientsTable . '` WHERE ' . $where . ' ORDER BY id ASC');
            $stmt->execute($args);
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable) {
            $rows = [];
        }
        foreach ($rows as $row) {
            $oldNo = (string)($row['patient_no'] ?? '');
            if (function_exists('dental_patient_number_looks_legacy_random') && !dental_patient_number_looks_legacy_random($oldNo)) continue;
            $newNo = dental_next_patient_number($pdo, (int)($row['branch_id'] ?? 0), dental_patient_numbering_settings());
            $sql = setup_column_exists($pdo, $patientsTable, 'updated_at')
                ? 'UPDATE `' . $patientsTable . '` SET patient_no=?, updated_at=NOW() WHERE id=? AND patient_no=?'
                : 'UPDATE `' . $patientsTable . '` SET patient_no=? WHERE id=? AND patient_no=?';
            $update = $pdo->prepare($sql);
            $update->execute([$newNo, (int)$row['id'], $oldNo]);
            if ($update->rowCount() > 0) {
                $renumbered++;
                audit('patient_number_legacy_repaired', 'patient', (int)$row['id'], ['old_patient_no'=>$oldNo, 'new_patient_no'=>$newNo]);
            }
        }
    }

    $settings = dental_patient_numbering_settings();
    $finalNext = function_exists('dental_patient_numbering_next_counter') ? dental_patient_numbering_next_counter($pdo, $settings) : max(1, (int) setting_get('patient_numbering', 'next_number', '1'));
    setting_set('patient_numbering', 'next_number', (string) max((int) setting_get('patient_numbering', 'next_number', '1'), $finalNext));
    setting_set('system', 'application_version', '0.5.9.8.108');
    setting_set('system', 'patient_numbering_canonical_yyyy_ready', '1');
    setting_set('system', 'patient_creation_paths_use_numbering_engine', '1');
    setting_set('system', 'patient_numbering_legacy_renumbered_count', (string)$renumbered);
    setup_record_history($pdo, '0.5.9.8.108', 'patient_numbering_canonical_yyyy_sequence');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.108',
        'features' => [
            'new_patient_default_id_format_p_yyyy_sequence',
            'walkin_patient_creation_uses_patient_numbering_engine',
            'online_request_patient_creation_uses_patient_numbering_engine',
            'legacy_random_p_yymmdd_numbers_repaired_when_safe',
        ],
        'legacy_patient_numbers_repaired' => $renumbered,
    ]);
}


function setup_v0598109_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.109', '>=')
        && (string) setting_get('system', 'patient_numbering_reset_sequence_ready', '0') === '1';
}

function setup_apply_v0598109(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598108')) {
        setup_apply_v0598108($pdo);
    }

    // Preserve the configured pattern but make reset behavior explicit:
    // a clinic-activity or factory reset clears patients and restarts next_number to 1.
    // Demo-only reset must not reset it because non-demo patients may remain.
    setting_set('system', 'application_version', '0.5.9.8.109');
    setting_set('system', 'patient_numbering_reset_sequence_ready', '1');
    setup_record_history($pdo, '0.5.9.8.109', 'patient_numbering_reset_sequence_policy');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.109',
        'features' => [
            'clinic_activity_reset_restarts_patient_number_next_number_to_1',
            'factory_reset_restarts_patient_number_next_number_to_1',
            'demo_only_reset_preserves_patient_number_sequence_to_avoid_reuse',
            'next_visible_patient_after_full_reset_is_p_yyyy_0001',
        ],
    ]);
}


function setup_v0598110_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.5.9.8.110', '>=')
        && (string) setting_get('system', 'stabilization_pass_110_ready', '0') === '1'
        && (string) setting_get('system', 'dialog_initial_focus_scroll_guard_ready', '0') === '1'
        && (string) setting_get('system', 'odontogram_readonly_no_jump_ready', '0') === '1';
}

function setup_apply_v0598110(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598109')) {
        setup_apply_v0598109($pdo);
    }
    setting_set('system', 'application_version', '0.5.9.8.110');
    setting_set('system', 'stabilization_pass_110_ready', '1');
    setting_set('system', 'dialog_initial_focus_scroll_guard_ready', '1');
    setting_set('system', 'odontogram_readonly_no_jump_ready', '1');
    setting_set('system', 'pilot_stabilization_freeze_ready', '1');
    setup_record_history($pdo, '0.5.9.8.110', 'role_flow_stabilization_no_new_features');
    audit('system_update_applied', 'system', null, [
        'version' => '0.5.9.8.110',
        'features' => [
            'dialog_initial_focus_obeys_workflow_start',
            'walkin_and_cashier_dialogs_preserve_page_position',
            'odontogram_readonly_tooth_selection_no_page_reload',
            'keyboard_tooth_navigation_scrolls_only_when_needed',
            'stabilization_freeze_marker_for_role_based_pilot_qa',
        ],
    ]);
}


function setup_v0600_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.0', '>=')
        && (string) setting_get('system', 'major_role_flow_stabilization_ready', '0') === '1'
        && (string) setting_get('system', 'major_reception_intake_launcher_ready', '0') === '1'
        && (string) setting_get('system', 'major_clinical_sequence_card_ready', '0') === '1'
        && (string) setting_get('system', 'major_cashier_pos_lane_ready', '0') === '1'
        && (string) setting_get('system', 'major_email_provider_neutral_final_ready', '0') === '1'
        && (string) setting_get('system', 'major_360_qa_checklist_ready', '0') === '1';
}

function setup_apply_v0600(PDO $pdo): void
{
    if (function_exists('setup_apply_v0598110')) {
        setup_apply_v0598110($pdo);
    }
    setting_set('system', 'application_version', '0.6.0');
    setting_set('system', 'major_role_flow_stabilization_ready', '1');
    setting_set('system', 'major_reception_intake_launcher_ready', '1');
    setting_set('system', 'major_clinical_sequence_card_ready', '1');
    setting_set('system', 'major_cashier_pos_lane_ready', '1');
    setting_set('system', 'major_email_provider_neutral_final_ready', '1');
    setting_set('system', 'major_360_qa_checklist_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.0', 'major_360_workflow_stabilization_upgrade');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.0',
        'features' => [
            'reception_three_path_launchpad_for_booking_online_and_walkin',
            'dentist_guided_chairside_sequence_with_assistant_or_none_guard',
            'dental_assistant_support_lane_for_vitals_xray_materials_and_turnover',
            'cashier_pos_lane_with_visible_transaction_lock_guard',
            'email_provider_neutral_gmail_api_domain_smtp_and_log_only_language',
            'role_by_role_360_go_live_qa_checklist',
        ],
    ]);
}


function setup_v0601_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.1', '>=')
        && (string) setting_get('system', 'role_flow_qa_command_center_ready', '0') === '1'
        && (string) setting_get('system', 'role_flow_route_audit_ready', '0') === '1'
        && (string) setting_get('system', 'sell_ready_gate_requires_blocker_pass_ready', '0') === '1';
}

function setup_apply_v0601(PDO $pdo): void
{
    if (function_exists('setup_apply_v0600')) {
        setup_apply_v0600($pdo);
    }
    setting_set('system', 'application_version', '0.6.1');
    setting_set('system', 'role_flow_qa_command_center_ready', '1');
    setting_set('system', 'role_flow_route_audit_ready', '1');
    setting_set('system', 'sell_ready_gate_requires_blocker_pass_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.1', 'role_flow_qa_enforcement_and_route_audit');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.1',
        'features' => [
            'role_flow_qa_command_center_with_pass_failed_blocked_statuses',
            'release_blocker_gate_for_reception_dentist_assistant_cashier_email_navigation_numbering_and_odontogram',
            'expected_route_existence_audit_for_role_workflows',
            'sell_ready_marker_requires_all_blocker_tests_passed',
            'apply_all_chain_includes_missing_0_5_9_8_98_call',
        ],
    ]);
}



function setup_v0602_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.2', '>=')
        && (string) setting_get('system', 'workflow_enforcement_v062_ready', '0') === '1'
        && (string) setting_get('system', 'reception_intake_mode_lock_v062_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_next_step_rail_v062_ready', '0') === '1'
        && (string) setting_get('system', 'cashier_pos_enforcement_v062_ready', '0') === '1';
}

function setup_apply_v0602(PDO $pdo): void
{
    if (function_exists('setup_apply_v0601')) {
        setup_apply_v0601($pdo);
    }
    setting_set('system', 'application_version', '0.6.2');
    setting_set('system', 'workflow_enforcement_v062_ready', '1');
    setting_set('system', 'reception_intake_mode_lock_v062_ready', '1');
    setting_set('system', 'clinical_next_step_rail_v062_ready', '1');
    setting_set('system', 'assistant_scope_guard_v062_ready', '1');
    setting_set('system', 'cashier_pos_enforcement_v062_ready', '1');
    setting_set('system', 'zero_balance_reason_gate_v062_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.2', 'role_workflow_enforcement_upgrade');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.2',
        'features' => [
            'reception_intake_mode_lock_for_appointment_walkin_and_online_request_paths',
            'queue_direct_clinical_start_redirects_to_dentist_start_gate_with_assistant_decision',
            'clinical_next_step_enforcement_rail_with_missing_item_targets',
            'assistant_support_boundary_visible_in_clinical_workspace',
            'cashier_pos_enforcement_panel_with_itemized_breakdown_and_zero_balance_rules',
            'go_live_gate_tracks_v062_enforced_workflow_controls',
        ],
    ]);
}

function setup_v06021_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.2.1', '>=')
        && (string) setting_get('system', 'dialog_padding_v0621_ready', '0') === '1'
        && (string) setting_get('system', 'legacy_invoice_total_lock_v0621_ready', '0') === '1'
        && (string) setting_get('system', 'inventory_batch_upload_v0621_ready', '0') === '1';
}

function setup_apply_v06021(PDO $pdo): void
{
    if (function_exists('setup_apply_v0602')) {
        setup_apply_v0602($pdo);
    }
    setting_set('system', 'application_version', '0.6.2.1');
    setting_set('system', 'dialog_padding_v0621_ready', '1');
    setting_set('system', 'legacy_invoice_total_lock_v0621_ready', '1');
    setting_set('system', 'inventory_batch_upload_v0621_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.2.1', 'invoice_modal_and_inventory_batch_upload_hotfix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.2.1',
        'features' => [
            'wide_dialog_padding_and_safe_viewport_margins',
            'legacy_invoice_placeholder_matches_invoice_total_and_warns_against_sample_breakdowns',
            'inventory_batch_csv_or_paste_upload_for_catalog_and_opening_stock_migration',
        ],
    ]);
}

function setup_v06022_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.2.2', '>=')
        && (string) setting_get('system', 'carryover_queue_resolution_v0622_ready', '0') === '1';
}

function setup_apply_v06022(PDO $pdo): void
{
    if (function_exists('setup_apply_v06021')) {
        setup_apply_v06021($pdo);
    }
    setting_set('system', 'application_version', '0.6.2.2');
    setting_set('system', 'carryover_queue_resolution_v0622_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.2.2', 'carryover_queue_resolution_hotfix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.2.2',
        'features' => [
            'previous_day_active_queue_rows_visible_in_live_queue',
            'stale_queue_dashboard_links_target_the_real_queue_card',
            'chair_occupancy_checks_include_unclosed_previous_day_patients',
            'cashier_queue_includes_previous_day_billing_handoffs',
        ],
    ]);
}


function setup_v0603_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.3', '>=')
        && (string) setting_get('system', 'navigation_scroll_governance_v063_ready', '0') === '1'
        && (string) setting_get('system', 'internal_link_preserve_scroll_v063_ready', '0') === '1'
        && (string) setting_get('system', 'role_flow_scroll_qa_v063_ready', '0') === '1';
}

function setup_apply_v0603(PDO $pdo): void
{
    if (function_exists('setup_apply_v06022')) {
        setup_apply_v06022($pdo);
    }
    setting_set('system', 'application_version', '0.6.3');
    setting_set('system', 'navigation_scroll_governance_v063_ready', '1');
    setting_set('system', 'internal_link_preserve_scroll_v063_ready', '1');
    setting_set('system', 'dialog_anchor_target_scroll_v063_ready', '1');
    setting_set('system', 'role_flow_scroll_qa_v063_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.3', 'global_navigation_scroll_governance_and_role_qa_gate');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.3',
        'features' => [
            'sidebar_navigation_opens_new_pages_normally',
            'same_page_internal_links_preserve_workspace_scroll',
            'hash_targets_move_only_to_the_requested_in_page_section',
            'pagination_filter_manage_view_close_links_preserve_context',
            'dialog_openers_keep_background_page_position',
            'clinical_next_step_rail_uses_governed_scroll_targeting',
            'role_flow_qa_adds_release_blocking_navigation_scroll_behavior_test',
        ],
    ]);
}


function setup_v06031_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.3.1', '>=')
        && (string) setting_get('system', 'queue_cancel_schedule_sync_v0631_ready', '0') === '1'
        && (string) setting_get('system', 'offline_transaction_guard_v0631_ready', '0') === '1';
}

function setup_apply_v06031(PDO $pdo): void
{
    if (function_exists('setup_apply_v0603')) {
        setup_apply_v0603($pdo);
    }
    setting_set('system', 'application_version', '0.6.3.1');
    setting_set('system', 'queue_cancel_schedule_sync_v0631_ready', '1');
    setting_set('system', 'weekly_schedule_terminal_status_autohide_v0631_ready', '1');
    setting_set('system', 'offline_transaction_guard_v0631_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.3.1', 'queue_cancellation_schedule_sync_and_offline_transaction_guard');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.3.1',
        'features' => [
            'waiting_queue_cancel_updates_linked_appointment_and_service_segments',
            'weekly_monthly_operational_schedule_hides_cancelled_left_no_show_by_default',
            'resource_conflict_checks_ignore_left_before_treatment_appointments',
            'offline_transaction_guard_blocks_cached_page_posts_when_browser_is_offline',
            'offline_transactions_must_use_manual_log_and_online_reconciliation_not_unsafe_bypass',
        ],
    ]);
}


function setup_v0604_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.4', '>=')
        && (string) setting_get('system', 'appointment_terminal_calendar_visibility_v064_ready', '0') === '1'
        && (string) setting_get('system', 'odontogram_dentition_scroll_scope_v064_ready', '0') === '1'
        && (string) setting_get('system', 'click_target_audit_v064_ready', '0') === '1';
}

function setup_apply_v0604(PDO $pdo): void
{
    if (function_exists('setup_apply_v06031')) {
        setup_apply_v06031($pdo);
    }
    setting_set('system', 'application_version', '0.6.4');
    setting_set('system', 'appointment_terminal_calendar_visibility_v064_ready', '1');
    setting_set('system', 'odontogram_dentition_scroll_scope_v064_ready', '1');
    setting_set('system', 'click_target_audit_v064_ready', '1');
    setting_set('system', 'internal_workspace_link_policy_v064_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.4', 'calendar_history_visibility_click_target_audit_and_scroll_governance_hardening');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.4',
        'features' => [
            'weekly_and_monthly_schedule_can_show_cancelled_no_show_left_as_non_blocking_history',
            'odontogram_dentition_tabs_target_the_chart_workspace_not_page_top',
            'global_internal_workspace_links_use_context_targets_instead_of_blind_scroll_restore',
            'click_target_audit_document_added_for_role_flow_QA',
            'next_major_stabilization_gate_added_for_calendar_history_and_click_behavior',
        ],
    ]);
}

function setup_v0605_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.5', '>=')
        && (string) setting_get('system', 'demo_reset_linked_transaction_integrity_v065_ready', '0') === '1'
        && (string) setting_get('system', 'email_mail_path_redesign_v065_ready', '0') === '1'
        && (string) setting_get('system', 'request_level_cache_v065_ready', '0') === '1';
}

function setup_apply_v0605(PDO $pdo): void
{
    if (function_exists('setup_apply_v0604')) {
        setup_apply_v0604($pdo);
    }
    setting_set('system', 'application_version', '0.6.5');
    setting_set('system', 'demo_reset_linked_transaction_integrity_v065_ready', '1');
    setting_set('system', 'email_mail_path_redesign_v065_ready', '1');
    setting_set('system', 'request_level_cache_v065_ready', '1');
    setting_set('system', 'multi_user_performance_cache_v065_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.5', 'demo_reset_integrity_email_path_redesign_and_runtime_cache_hardening');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.5',
        'features' => [
            'demo_reset_removes_demo_patients_by_marker_and_DEMO_PAT_pattern',
            'demo_reset_deletes_linked_appointments_queue_encounters_invoices_payments_and_email_links',
            'email_settings_split_into_gmail_api_domain_smtp_php_mail_log_queue_and_diagnostics_paths',
            'settings_reads_are_cached_per_request_to_reduce_repeated_queries_on_heavy_pages',
            'multi_user_performance_governance_gate_added_for_staging_load_checks',
        ],
    ]);
}

function setup_v0606_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.6', '>=')
        && (string) setting_get('system', 'landing_page_editor_v066_ready', '0') === '1'
        && is_file(DENTAL_ROOT . '/modules/administration/settings.php');
}

function setup_apply_v0606(PDO $pdo): void
{
    if (function_exists('setup_apply_v0605')) {
        setup_apply_v0605($pdo);
    }
    setting_set('system', 'application_version', '0.6.6');
    setting_set('system', 'landing_page_editor_v066_ready', '1');
    setting_set('system', 'landing_page_full_copy_control_v066_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.6', 'landing_page_editor_and_full_public_copy_control');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.6',
        'features' => [
            'non_user_landing_page_editor_redesigned_by_section',
            'appointment_cards_clinic_info_download_developer_and_portal_copy_are_editable',
            'landing_page_visibility_order_and_media_controls_are_grouped',
            'website_content_sidebar_label_changed_to_landing_page_editor',
            'public_page_content_updates_preserve_immediate_refresh_behavior',
        ],
    ]);
}


function setup_v0607_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.7', '>=')
        && (string) setting_get('system', 'role_permission_navigation_hardening_v067_ready', '0') === '1'
        && function_exists('role_navigation_audit_snapshot')
        && is_file(DENTAL_ROOT . '/modules/administration/permission_matrix.php');
}

function setup_apply_v0607(PDO $pdo): void
{
    if (function_exists('setup_apply_v0606')) {
        setup_apply_v0606($pdo);
    }
    setting_set('system', 'application_version', '0.6.7');
    setting_set('system', 'role_permission_navigation_hardening_v067_ready', '1');
    setting_set('system', 'role_route_access_guard_v067_ready', '1');
    setting_set('system', 'role_navigation_audit_v067_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.7', 'role_permission_navigation_and_access_hardening');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.7',
        'features' => [
            'central_role_capability_lanes_for_each_staff_role',
            'route_access_guard_blocks_direct_url_escape_from_active_task_view',
            'permission_matrix_includes_route_navigation_audit_and_high_risk_role_leakage_checks',
            'go_live_role_qa_requires_visible_link_direct_url_and_button_boundary_testing',
            'visible_navigation_remains_role_filtered_and direct_routes_are_guarded_server_side',
        ],
    ]);
}


function setup_v0608_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.8', '>=')
        && (string) setting_get('system', 'update_loop_self_healing_v068_ready', '0') === '1'
        && (string) setting_get('system', 'inventory_treatment_reconciliation_v068_ready', '0') === '1'
        && function_exists('inventory_treatment_readiness_summary');
}

function setup_apply_v0608(PDO $pdo): void
{
    if (function_exists('setup_apply_v0607')) {
        setup_apply_v0607($pdo);
    }
    setting_set('system', 'application_version', '0.6.8');
    setting_set('system', 'update_loop_self_healing_v068_ready', '1');
    setting_set('system', 'update_chain_terminal_apply_v068_ready', '1');
    setting_set('system', 'inventory_treatment_reconciliation_v068_ready', '1');
    setting_set('system', 'clinical_supply_guard_v068_ready', '1');
    setting_set('system', 'pilot_only_until_role_qa_passed', '1');
    setup_record_history($pdo, '0.6.8', 'update_loop_self_healing_and_inventory_to_treatment_reconciliation');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.8',
        'features' => [
            'apply_all_pending_updates_now_calls_terminal_0_6_7_and_0_6_8_markers',
            'update_center_explains_exact_pending_reason_if_update_still_shows_after_apply',
            'update_recovery_summary_requires_missing_history_and_terminal_apply_check_to_pass',
            'clinical_workspace_shows_inventory_to_treatment_traceability_guard',
            'inventory_usage_and_treatment_reconciliation_QA_is_release_blocking',
        ],
    ]);
}

function setup_v0609_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.9', '>=')
        && (string) setting_get('system', 'unified_mail_engine_v069_ready', '0') === '1'
        && (string) setting_get('system', 'production_deployment_mode_v069_ready', '0') === '1'
        && function_exists('dental_deployment_mode');
}

function setup_apply_v0609(PDO $pdo): void
{
    if (function_exists('setup_apply_v0608')) {
        setup_apply_v0608($pdo);
    }
    setting_set('system', 'application_version', '0.6.9');
    setting_set('system', 'unified_mail_engine_v069_ready', '1');
    setting_set('system', 'email_test_uses_outbox_transport_v069_ready', '1');
    setting_set('system', 'production_deployment_mode_v069_ready', '1');
    setting_set('system', 'deployment_mode', 'production');
    setting_set('system', 'show_internal_qa_tools', '0');
    setup_record_history($pdo, '0.6.9', 'unified_mail_engine_and_production_deployment_mode');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.9',
        'features' => [
            'send_test_mail_uses_the_same_outbox_transport_engine_as_password_reset_invites_appointments_receipts_and_reminders',
            'connection_diagnostic_is_separate_and_no_longer_blocks_the_full_delivery_test_when_actual_transport_can_send',
            'email_settings_displays_unified_mail_path_coverage_for_client_troubleshooting',
            'enterprise_production_mode_hides_internal_qa_testing_and_demo_orientation_tools_by_default',
            'superuser_can_temporarily_enable_internal_qa_tools_from_administration_when_troubleshooting',
        ],
    ]);
}


function setup_v06091_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.6.9.1', '>=')
        && (string) setting_get('system', 'smtp_diagnostics_test_type_v0691_ready', '0') === '1'
        && (string) setting_get('system', 'queue_ticket_printing_v0691_ready', '0') === '1';
}

function setup_apply_v06091(PDO $pdo): void
{
    if (function_exists('setup_apply_v0609')) {
        setup_apply_v0609($pdo);
    }
    if (setup_table_exists($pdo, table('smtp_diagnostics')) && setup_column_exists($pdo, table('smtp_diagnostics'), 'test_type')) {
        try {
            $pdo->exec('ALTER TABLE `' . table('smtp_diagnostics') . '` MODIFY `test_type` VARCHAR(40) NOT NULL DEFAULT "delivery"');
        } catch (Throwable $exception) {
            error_log('SMTP diagnostics test_type widening warning: ' . $exception->getMessage());
        }
    }
    setting_set('system', 'application_version', '0.6.9.1');
    setting_set('system', 'smtp_diagnostics_test_type_v0691_ready', '1');
    setting_set('system', 'queue_ticket_printing_v0691_ready', '1');
    setup_record_history($pdo, '0.6.9.1', 'smtp_diagnostics_and_queue_ticket_visibility_fix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.6.9.1',
        'features' => [
            'test_mail_no_longer_fails_when_older_smtp_diagnostics_test_type_enum_rejects_unified_delivery',
            'smtp_diagnostics_test_type_column_widened_to_varchar_for_future_mail_health_labels',
            'queue_cards_and_expected_appointment_rows_offer_printable_patient_queue_tickets',
            'queue_ticket_explains_a_series_for_appointments_and_w_series_for_walk_ins',
        ],
    ]);
}

function setup_v0700_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.7.0', '>=')
        && (string) setting_get('system', 'clinical_chairside_focus_v0700_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_live_validation_v0700_ready', '0') === '1';
}

function setup_apply_v0700(PDO $pdo): void
{
    if (function_exists('setup_apply_v06091')) {
        setup_apply_v06091($pdo);
    }
    setting_set('system', 'application_version', '0.7.0');
    setting_set('system', 'clinical_chairside_focus_v0700_ready', '1');
    setting_set('system', 'clinical_live_validation_v0700_ready', '1');
    setting_set('system', 'clinical_send_keys_focus_v0700_ready', '1');
    setup_record_history($pdo, '0.7.0', 'doctor_chairside_focus_and_live_validation');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.0',
        'features' => [
            'doctor_treatment_workspace_focuses_the_exact_next_missing_field_with_alt_n',
            'intake_and_vital_signs_checklist_uses_live_typed_values_instead_of_stale_saved_state',
            'field_warnings_appear_while_typing_or_changing_fields_before_save',
            'keyboard_entry_moves_to_next_missing_required_field_inside_the_treatment_workspace',
            'global_next_clinical_required_handler_delegates_to_the_precise_chairside_focus_engine',
        ],
    ]);
}


function setup_v0710_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.7.1', '>=')
        && (string) setting_get('system', 'clinical_service_path_matrix_v0710_ready', '0') === '1'
        && (string) setting_get('system', 'odontogram_baseline_review_v0710_ready', '0') === '1'
        && (string) setting_get('system', 'chairside_section_acknowledgements_v0710_ready', '0') === '1';
}

function setup_apply_v0710(PDO $pdo): void
{
    if (function_exists('setup_apply_v0700')) {
        setup_apply_v0700($pdo);
    }
    setting_set('system', 'application_version', '0.7.1');
    setting_set('system', 'clinical_service_path_matrix_v0710_ready', '1');
    setting_set('system', 'odontogram_baseline_review_v0710_ready', '1');
    setting_set('system', 'chairside_section_acknowledgements_v0710_ready', '1');
    setting_set('system', 'healthy_dentition_shortcut_v0710_ready', '1');
    setup_record_history($pdo, '0.7.1', 'dentist_treatment_path_and_odontogram_baseline_review');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.1',
        'features' => [
            'service_specific_treatment_path_card_lists_what_each_visit_requires',
            'odontogram_previous_baseline_carries_forward_and_can_be_reviewed_no_changes',
            'healthy_dentition_shortcut_satisfies_chart_and_diagnosis_without_tooth_by_tooth_clicking',
            'vital_sign_review_acknowledgement_is_available_inside_intake_section_not_only_closeout',
            'chart_review_result_is_required_so_dentist_knows_why_chart_gate_is_blocking',
        ],
    ]);
}


function setup_v0720_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.7.2', '>=')
        && (string) setting_get('system', 'dentist_treatment_cockpit_v0720_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_patient_context_v0720_ready', '0') === '1'
        && (string) setting_get('system', 'update_apply_chain_v0720_ready', '0') === '1';
}

function setup_apply_v0720(PDO $pdo): void
{
    if (function_exists('setup_apply_v0710')) {
        setup_apply_v0710($pdo);
    } elseif (function_exists('setup_apply_v0700')) {
        setup_apply_v0700($pdo);
    }
    setting_set('system', 'application_version', '0.7.2');
    setting_set('system', 'dentist_treatment_cockpit_v0720_ready', '1');
    setting_set('system', 'clinical_patient_context_v0720_ready', '1');
    setting_set('system', 'update_apply_chain_v0720_ready', '1');
    setting_set('system', 'treatment_path_latest_ready', '1');
    setup_record_history($pdo, '0.7.2', 'dentist_treatment_cockpit_patient_context_and_update_chain');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.2',
        'features' => [
            'dentist_workspace_collapses_secondary_flow_guides_and_shows_one_current_required_treatment_task',
            'prescription_decision_is_moved_into_the_prescription_step_so_alt_n_focuses_the_visible_choice',
            'clinical_records_keep_the_selected_patient_context_across_encounters_treatment_plans_odontogram_prescriptions_and_consents',
            'apply_all_pending_updates_now_calls_v0700_v0710_and_v0720_to_stop_pending_after_update',
        ],
    ]);
}


function setup_v0730_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.7.3', '>=')
        && (string) setting_get('system', 'dentist_single_task_cockpit_v0730_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_duplicate_guides_removed_v0730_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_exact_focus_v0730_ready', '0') === '1';
}

function setup_apply_v0730(PDO $pdo): void
{
    if (function_exists('setup_apply_v0720')) {
        setup_apply_v0720($pdo);
    } elseif (function_exists('setup_apply_v0710')) {
        setup_apply_v0710($pdo);
    }
    setting_set('system', 'application_version', '0.7.3');
    setting_set('system', 'dentist_single_task_cockpit_v0730_ready', '1');
    setting_set('system', 'clinical_duplicate_guides_removed_v0730_ready', '1');
    setting_set('system', 'clinical_exact_focus_v0730_ready', '1');
    setting_set('system', 'clinical_patient_context_v0730_ready', '1');
    setting_set('system', 'treatment_path_latest_ready', '1');
    setup_record_history($pdo, '0.7.3', 'dentist_single_task_cockpit_and_exact_focus');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.3',
        'features' => [
            'clinical_workspace_no_longer_renders_duplicate_checklist_engines_above_the_dentist_form',
            'alt_n_uses_only_the_patient_release_checklist_as_the_source_of_truth_for_missing_items',
            'intake_and_vital_signs_acknowledgement_focus_stays_inside_the_intake_section',
            'completion_buttons_are_disabled_until_the_exact_required_treatment_path_is_complete',
            'clinical_records_preserve_selected_patient_context_when_opening_related treatment links',
        ],
    ]);
}


function setup_v0740_ready(PDO $pdo): bool
{
    return version_compare(installed_application_version(), '0.7.4', '>=')
        && (string) setting_get('system', 'clinical_contextual_help_popouts_v0740_ready', '0') === '1'
        && (string) setting_get('system', 'clinical_inline_guide_reduction_v0740_ready', '0') === '1'
        && (string) setting_get('system', 'dentist_compact_cockpit_v0740_ready', '0') === '1';
}

function setup_apply_v0740(PDO $pdo): void
{
    if (function_exists('setup_apply_v0730')) {
        setup_apply_v0730($pdo);
    } elseif (function_exists('setup_apply_v0720')) {
        setup_apply_v0720($pdo);
    }
    setting_set('system', 'application_version', '0.7.4');
    setting_set('system', 'clinical_contextual_help_popouts_v0740_ready', '1');
    setting_set('system', 'clinical_inline_guide_reduction_v0740_ready', '1');
    setting_set('system', 'dentist_compact_cockpit_v0740_ready', '1');
    setting_set('system', 'treatment_path_latest_ready', '1');
    setup_record_history($pdo, '0.7.4', 'compact_guides_and_contextual_help_popouts');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.4',
        'features' => [
            'doctor_treatment_workspace_reduces_inline_guide_text_and_keeps_the_form_primary',
            'guide_text_is_moved_to_hover_or_click_context_help_popouts',
            'dentist_cockpit_shows_only_the_current_required_task_and_exact_focus_button',
            'full_treatment_checklist_is_available_as_a_popout_instead_of_a_visible_panel',
        ],
    ]);
}


function setup_v0750_ready(PDO $pdo): bool
{
    ensure_settings_table($pdo);
    return version_compare(installed_application_version(), '0.7.5', '>=')
        && (string) setting_get('system', 'task_first_layout_v075_ready', '0') === '1'
        && (string) setting_get('system', 'cashier_active_task_first_v075_ready', '0') === '1'
        && (string) setting_get('system', 'odontogram_active_chart_first_v075_ready', '0') === '1';
}

function setup_apply_v0750(PDO $pdo): void
{
    if (function_exists('setup_apply_v0740')) {
        setup_apply_v0740($pdo);
    }
    setting_set('system', 'application_version', '0.7.5');
    setting_set('system', 'task_first_layout_v075_ready', '1');
    setting_set('system', 'cashier_active_task_first_v075_ready', '1');
    setting_set('system', 'odontogram_active_chart_first_v075_ready', '1');
    setting_set('system', 'last_update_completed_at', date('c'));
    setup_record_history($pdo, '0.7.5', 'task_first_layout_priority_for_cashier_and_odontogram');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.5',
        'features' => [
            'cashier_register_prioritizes_patient_queue_and_payment_workspace_before_reports',
            'odontogram_prioritizes_current_tooth_chart_before_summary_stats_and_history',
            'role_workflow_lanes_render_as_collapsed_help_instead_of_large_guide_cards',
        ],
    ]);
}



function setup_v0751_ready(PDO $pdo): bool
{
    ensure_settings_table($pdo);
    return version_compare(installed_application_version(), '0.7.5.1', '>=')
        && (string) setting_get('system', 'request_failure_guard_v0751_ready', '0') === '1'
        && (string) setting_get('system', 'cashier_report_degrade_safe_v0751_ready', '0') === '1'
        && (string) setting_get('system', 'odontogram_dependency_guard_v0751_ready', '0') === '1';
}

function setup_apply_v0751(PDO $pdo): void
{
    if (function_exists('setup_apply_v0750')) {
        setup_apply_v0750($pdo);
    }
    setting_set('system', 'application_version', '0.7.5.1');
    setting_set('system', 'request_failure_guard_v0751_ready', '1');
    setting_set('system', 'cashier_report_degrade_safe_v0751_ready', '1');
    setting_set('system', 'odontogram_dependency_guard_v0751_ready', '1');
    setting_set('system', 'last_update_completed_at', date('c'));
    setup_record_history($pdo, '0.7.5.1', 'request_failure_guard_and_task_first_dependency_self_healing');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.5.1',
        'features' => [
            'cashier_report_errors_no_longer_block_the_cashier_register',
            'odontogram_loads_with_dependency_fallbacks_if_partial_package_files_are_missing',
            'update_chain_marks_request_failure_guard_complete',
        ],
    ]);
}

function setup_v0752_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.7.5.2', '>=')
            && (string) setting_get('system', 'system_setup_readiness_guard_v0752_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0752(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0751')) {
        setup_apply_v0751($pdo);
    }
    setting_set('system', 'application_version', '0.7.5.2');
    setting_set('system', 'system_setup_readiness_guard_v0752_ready', '1');
    setting_set('system', 'last_update_completed_at', date('c'));
    setup_record_history($pdo, '0.7.5.2', 'system_setup_readiness_guard_for_075x');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.5.2',
        'features' => [
            'system_setup_no_longer_crashes_when_075_readiness_checks_run_before_settings_helper_exists',
            'readiness_helpers_return_pending_instead_of_throwing_on_partial_schema',
        ],
    ]);
}


function setup_v0760_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.7.6', '>=')
            && (string) setting_get('system', 'role_first_navigation_v0760_ready', '0') === '1'
            && (string) setting_get('system', 'patient_journey_context_v0760_ready', '0') === '1'
            && (string) setting_get('system', 'assistant_dedicated_lane_v0760_ready', '0') === '1'
            && (string) setting_get('system', 'dashboard_primary_action_v0760_ready', '0') === '1'
            && (string) setting_get('system', 'patient_services_role_v0760_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0760(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0752')) {
        setup_apply_v0752($pdo);
    }
    setting_set('system', 'application_version', '0.7.6');
    setting_set('system', 'role_first_navigation_v0760_ready', '1');
    setting_set('system', 'patient_journey_context_v0760_ready', '1');
    setting_set('system', 'assistant_dedicated_lane_v0760_ready', '1');
    setting_set('system', 'dashboard_primary_action_v0760_ready', '1');
    if (setup_table_exists($pdo, table('roles'))) {
        $roleTable = table('roles');
        $description = 'Front-desk support for patient verification, arrivals, queue coordination, and documented communication without clinical, finance, inventory, or system authority';
        if (setup_column_exists($pdo, $roleTable, 'description')) {
            $stmt = $pdo->prepare('UPDATE `' . $roleTable . '` SET name=?,description=? WHERE code="staff"');
            $stmt->execute(['Patient Services Support', $description]);
        } else {
            $stmt = $pdo->prepare('UPDATE `' . $roleTable . '` SET name=? WHERE code="staff"');
            $stmt->execute(['Patient Services Support']);
        }
    }
    setting_set('system', 'patient_services_role_v0760_ready', '1');
    setting_set('system', 'last_update_completed_at', date('c'));
    setup_record_history($pdo, '0.7.6', 'role_first_flow_stabilization');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.6',
        'features' => [
            'canonical_workspace_per_role',
            'persistent_patient_visit_journey_and_handoff_context',
            'dedicated_dental_assistant_chairside_lane',
            'single_primary_dashboard_action',
            'progressively_disclosed_appointment_exceptions',
            'explicit_two_step_cashier_checkout',
            'general_staff_role_renamed_to_patient_services_support',
        ],
    ]);
}

function setup_v0761_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.7.6.1', '>=')
            && (string) setting_get('system', 'task_guard_api_separation_v0761_ready', '0') === '1'
            && (string) setting_get('system', 'navigation_permission_cache_v0761_ready', '0') === '1'
            && (string) setting_get('system', 'duplicate_flash_guard_v0761_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0761(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0760')) {
        setup_apply_v0760($pdo);
    }
    setting_set('system', 'application_version', '0.7.6.1');
    setting_set('system', 'task_guard_api_separation_v0761_ready', '1');
    setting_set('system', 'navigation_permission_cache_v0761_ready', '1');
    setting_set('system', 'duplicate_flash_guard_v0761_ready', '1');
    setting_set('system', 'last_update_completed_at', date('c'));
    unset($_SESSION['dental_update_pending_cache']);
    setup_record_history($pdo, '0.7.6.1', 'dentist_task_guard_and_performance_hotfix');
    audit('system_update_applied', 'system', null, [
        'version' => '0.7.6.1',
        'features' => [
            'api_requests_bypass_page_task_view_guard_and_keep_endpoint_authorization',
            'valid_role_routes_use_canonical_route_authorization_fallback',
            'duplicate_flash_messages_are_deduplicated_and_capped',
            'role_assignment_permission_and_update_readiness_checks_are_cached',
        ],
    ]);
}

function setup_v0762_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        if (!version_compare(installed_application_version(), '0.7.6.2', '>=')) return false;
        if (!setup_column_exists($pdo,table('inventory_items'),'is_retail_product') || !setup_column_exists($pdo,table('inventory_items'),'selling_price')) return false;
        $permission=$pdo->prepare('SELECT COUNT(*) FROM `' . table('permissions') . '` WHERE code=?');$permission->execute(['inventory.catalog']);
        $grant=$pdo->prepare('SELECT COUNT(*) FROM `' . table('role_permissions') . '` rp JOIN `' . table('roles') . '` r ON r.id=rp.role_id JOIN `' . table('permissions') . '` p ON p.id=rp.permission_id WHERE r.code=? AND p.code=? AND rp.allowed=1');$grant->execute(['dental_assistant','inventory.catalog']);
        return (int)$permission->fetchColumn()>0 && (int)$grant->fetchColumn()>0
            && (string)setting_get('system','cashier_task_first_completion_v0762_ready','0')==='1'
            && (string)setting_get('system','assistant_product_catalog_v0762_ready','0')==='1'
            && (string)setting_get('system','retail_product_pos_v0762_ready','0')==='1';
    } catch (Throwable) { return false; }
}

function setup_apply_v0762(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0761')) setup_apply_v0761($pdo);
    setup_add_column($pdo,table('inventory_items'),'is_retail_product','TINYINT(1) NOT NULL DEFAULT 0 AFTER `is_clinical_supply`');
    setup_add_column($pdo,table('inventory_items'),'selling_price','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `purchase_cost`');
    if (setup_table_exists($pdo,table('invoice_items'))) {
        $pdo->exec("ALTER TABLE `" . table('invoice_items') . "` MODIFY `source_type` ENUM('clinical_procedure','service_catalog','retail_product','manual_charge','legacy_manual','clinical_supply','dispensed_medicine','equipment_service','diagnostic_service') NOT NULL DEFAULT 'legacy_manual'");
    }
    if (setup_table_exists($pdo,table('permissions'))) {
        $permission=$pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
        $permission->execute(['inventory.catalog','Add retail products and supply catalogue items','Operations','Create product and supply catalogue records without receiving, issuing, transferring, adjusting, approving, or disposing stock']);
        $grant=$pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) SELECT r.id,p.id,1 FROM `' . table('roles') . '` r JOIN `' . table('permissions') . '` p WHERE r.code=? AND p.code=? ON DUPLICATE KEY UPDATE allowed=1');
        $grant->execute(['dental_assistant','inventory.catalog']);
    }
    setting_set('system','application_version','0.7.6.2');
    setting_set('system','cashier_task_first_completion_v0762_ready','1');
    setting_set('system','assistant_product_catalog_v0762_ready','1');
    setting_set('system','retail_product_pos_v0762_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache']);
    setup_record_history($pdo,'0.7.6.2','cashier_task_first_and_retail_product_flow');
    audit('system_update_applied','system',null,['version'=>'0.7.6.2','features'=>[
        'cashier_finalize_action_kept_beside_payment_controls',
        'dental_assistant_limited_product_and_supply_catalogue_access',
        'protected_retail_selling_price_and_cashier_product_selection',
        'fefo_stock_deduction_only_after_full_successful_payment',
    ]]);
}


function setup_v0763_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.7.6.3', '>=')
            && setup_index_exists($pdo,table('email_outbox'),'idx_email_outbox_status_created')
            && setup_index_exists($pdo,table('email_outbox'),'idx_email_outbox_processing')
            && (string)setting_get('system','superuser_system_control_v0763_ready','0')==='1'
            && (string)setting_get('system','email_history_scaling_v0763_ready','0')==='1';
    } catch (Throwable) { return false; }
}

function setup_apply_v0763(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0762')) setup_apply_v0762($pdo);
    if (setup_table_exists($pdo,table('email_outbox'))) {
        if (!setup_index_exists($pdo,table('email_outbox'),'idx_email_outbox_status_created')) {
            $pdo->exec('ALTER TABLE `' . table('email_outbox') . '` ADD INDEX `idx_email_outbox_status_created` (`status`,`created_at`)');
        }
        if (!setup_index_exists($pdo,table('email_outbox'),'idx_email_outbox_processing')) {
            $pdo->exec('ALTER TABLE `' . table('email_outbox') . '` ADD INDEX `idx_email_outbox_processing` (`status`,`last_attempt_at`)');
        }
    }
    $defaults=['outbox_days'=>'365','email_log_days'=>'365','run_days'=>'90','diagnostic_days'=>'90','transcript_days'=>'14'];
    foreach($defaults as $key=>$value)if((string)setting_get('email_history',$key,'')==='')setting_set('email_history',$key,$value);
    if (setup_table_exists($pdo,table('email_queue_runs'))) {
        $pdo->exec('DELETE FROM `' . table('email_queue_runs') . '` WHERE trigger_type="automatic_request" AND selected_count=0 AND sent_count=0 AND retry_count=0 AND failed_count=0 AND skipped_count=0 AND recovered_count=0 LIMIT 10000');
    }
    setting_set('system','application_version','0.7.6.3');
    setting_set('system','superuser_system_control_v0763_ready','1');
    setting_set('system','email_history_scaling_v0763_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache'],$_SESSION['email_queue_health_cache_v0763']);
    setup_record_history($pdo,'0.7.6.3','superuser_navigation_and_scalable_email_history');
    audit('system_update_applied','system',null,['version'=>'0.7.6.3','features'=>[
        'system_control_navigation_restricted_to_signed_in_superuser',
        'assistant_products_supplies_route_visible_in_assistant_workspace',
        'empty_automatic_email_cycles_not_stored',
        'email_history_loaded_one_paginated_view_at_a_time',
        'bounded_email_history_retention_and_indexed_health_counts',
    ]]);
}


function setup_v0764_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.7.6.4', '>=')
            && (string)setting_get('system','route_ownership_audit_v0764_ready','0') === '1'
            && (string)setting_get('system','cashier_context_release_v0764_ready','0') === '1'
            && (string)setting_get('system','deployment_owner_boundary_v0764_ready','0') === '1';
    } catch (Throwable) { return false; }
}

function setup_apply_v0764(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0763')) setup_apply_v0763($pdo);
    setting_set('system','application_version','0.7.6.4');
    setting_set('system','route_ownership_audit_v0764_ready','1');
    setting_set('system','cashier_context_release_v0764_ready','1');
    setting_set('system','deployment_owner_boundary_v0764_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache'], $_SESSION['dental_patient_context']);
    setup_record_history($pdo,'0.7.6.4','role_route_ownership_and_patient_context_repair');
    audit('system_update_applied','system',null,['version'=>'0.7.6.4','features'=>[
        'all_administration_links_filtered_by_the_same_route_guard_as_the_sidebar',
        'deployment_environment_moved_to_superuser_system_control',
        'cashier_context_cleared_after_completed_or_released_transactions',
        'global_search_results_open_the_exact_patient_appointment_queue_or_invoice',
        'duplicate_and_out_of_scope_navigation_entries_removed',
    ]]);
}

function setup_v0765_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.7.6.5', '>=')
            && setup_column_exists($pdo,table('payments'),'request_token')
            && setup_index_exists($pdo,table('payments'),'uq_payments_request_token')
            && setup_column_exists($pdo,table('audit_logs'),'record_hash')
            && setup_index_exists($pdo,table('audit_logs'),'idx_audit_record_hash')
            && (string)setting_get('system','payment_idempotency_v0765_ready','0')==='1'
            && (string)setting_get('system','role_separation_v0765_ready','0')==='1'
            && (string)setting_get('system','task_preview_read_only_v0765_ready','0')==='1'
            && (string)setting_get('system','critical_audit_v0765_ready','0')==='1'
            && (string)setting_get('system','patient_portal_scope_v0765_ready','0')==='1'
            && (string)setting_get('system','route_flow_reaudit_v0765_ready','0')==='1';
    } catch (Throwable) { return false; }
}

function setup_apply_v0765(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (function_exists('setup_apply_v0764')) setup_apply_v0764($pdo);
    if (setup_table_exists($pdo,table('payments'))) {
        setup_add_column($pdo,table('payments'),'request_token','CHAR(64) NULL AFTER `reference_no`');
        if (!setup_index_exists($pdo,table('payments'),'uq_payments_request_token')) {
            $pdo->exec('ALTER TABLE `' . table('payments') . '` ADD UNIQUE INDEX `uq_payments_request_token` (`request_token`)');
        }
    }
    if (setup_table_exists($pdo,table('audit_logs'))) {
        setup_add_column($pdo,table('audit_logs'),'record_hash','CHAR(64) NULL AFTER `new_values_json`');
        if (!setup_index_exists($pdo,table('audit_logs'),'idx_audit_record_hash')) {
            $pdo->exec('ALTER TABLE `' . table('audit_logs') . '` ADD INDEX `idx_audit_record_hash` (`record_hash`)');
        }
    }
    $disabledRoleAssignments=0;
    if (setup_table_exists($pdo,table('user_role_assignments')) && setup_table_exists($pdo,table('roles'))) {
        $assignmentRows=$pdo->query('SELECT ura.id,ura.user_id,ura.assignment_type,r.code FROM `' . table('user_role_assignments') . '` ura JOIN `' . table('roles') . '` r ON r.id=ura.role_id WHERE ura.status="active" ORDER BY ura.user_id,FIELD(ura.assignment_type,"primary","secondary"),ura.id')->fetchAll();
        $byUser=[];foreach($assignmentRows as $row)$byUser[(int)$row['user_id']][]=$row;
        $conflictPairs=[
            ['super_admin','clinic_admin'],['super_admin','cashier'],['super_admin','dentist'],['super_admin','dental_assistant'],['super_admin','receptionist'],['super_admin','staff'],['super_admin','inventory_officer'],
            ['clinic_admin','cashier'],['clinic_admin','dentist'],['clinic_admin','dental_assistant'],['clinic_admin','receptionist'],['clinic_admin','staff'],['clinic_admin','inventory_officer'],
            ['cashier','dentist'],['cashier','dental_assistant'],['cashier','receptionist'],['cashier','staff'],['cashier','inventory_officer'],
            ['dentist','dental_assistant'],['dentist','inventory_officer'],['dental_assistant','inventory_officer'],
        ];
        $rolesConflict=static function(string $left,string $right)use($conflictPairs):bool{
            foreach($conflictPairs as [$a,$b])if(($left===$a&&$right===$b)||($left===$b&&$right===$a))return true;
            return false;
        };
        $disableIds=[];
        foreach($byUser as $rows){
            usort($rows,static fn(array $a,array $b):int=>((string)$a['assignment_type']==='primary'?0:1)<=>((string)$b['assignment_type']==='primary'?0:1));
            $kept=[];
            foreach($rows as $row){
                $code=(string)$row['code'];
                if((string)$row['assignment_type']==='primary'){$kept[]=$code;continue;}
                $conflicting=false;
                foreach($kept as $keptCode)if($rolesConflict($keptCode,$code)){$conflicting=true;break;}
                if($conflicting)$disableIds[]=(int)$row['id'];else$kept[]=$code;
            }
        }
        $disableIds=array_values(array_unique(array_filter($disableIds)));
        if($disableIds){
            $marks=implode(',',array_fill(0,count($disableIds),'?'));
            $stmt=$pdo->prepare('UPDATE `' . table('user_role_assignments') . '` SET status="inactive",updated_at=NOW() WHERE id IN ('.$marks.') AND assignment_type="secondary"');
            $stmt->execute($disableIds);$disabledRoleAssignments=$stmt->rowCount();
        }
    }
    setting_set('system','application_version','0.7.6.5');
    setting_set('system','payment_idempotency_v0765_ready','1');
    setting_set('system','role_separation_v0765_ready','1');
    setting_set('system','task_preview_read_only_v0765_ready','1');
    setting_set('system','critical_audit_v0765_ready','1');
    setting_set('system','patient_portal_scope_v0765_ready','1');
    setting_set('system','route_flow_reaudit_v0765_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache'],$_SESSION['dental_patient_context']);
    setup_record_history($pdo,'0.7.6.5','security_role_flow_and_transaction_integrity');
    audit_required('system_update_applied','system',null,['version'=>'0.7.6.5','features'=>[
        'explicit_patient_portal_scope_denial_and_audit',
        'persistent_cashier_payment_idempotency_token',
        'signed_in_cashier_required_for_cashier_transactions',
        'incompatible_dual_role_assignments_blocked',
        'existing_incompatible_secondary_roles_disabled_'.$disabledRoleAssignments,
        'administrator_and_superuser_task_previews_read_only',
        'critical_clinical_and_financial_audits_required_before_commit',
        'factory_reset_preserves_append_only_audit_history',
        'stray_system_control_and_cashier_patient_links_removed',
    ]]);
}

function setup_v0765_production_baseline_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return setup_column_exists($pdo,table('payments'),'request_token')
            && setup_index_exists($pdo,table('payments'),'uq_payments_request_token')
            && setup_column_exists($pdo,table('audit_logs'),'record_hash')
            && setup_index_exists($pdo,table('audit_logs'),'idx_audit_record_hash')
            && (string)setting_get('system','payment_idempotency_v0765_ready','0')==='1'
            && (string)setting_get('system','role_separation_v0765_ready','0')==='1'
            && (string)setting_get('system','task_preview_read_only_v0765_ready','0')==='1'
            && (string)setting_get('system','critical_audit_v0765_ready','0')==='1'
            && (string)setting_get('system','patient_portal_scope_v0765_ready','0')==='1'
            && (string)setting_get('system','route_flow_reaudit_v0765_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_v0780_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.7.8.0','>=')
            && (string)setting_get('system','production_acceptance_v0780_ready','0')==='1'
            && (string)setting_get('system','chair_release_integrity_v0780_ready','0')==='1'
            && (string)setting_get('system','task_view_dead_end_guard_v0780_ready','0')==='1'
            && (string)setting_get('system','pending_only_update_v0780_ready','0')==='1'
            && (string)setting_get('system','runtime_diagnostics_v0780_ready','0')==='1'
            && (string)setting_get('system','clean_release_packaging_v0780_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0780(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0765_production_baseline_ready($pdo)) {
        if (!version_compare(installed_application_version(),'0.7.6.5','>=')) {
            throw new RuntimeException('The Version 0.7.6.5 production baseline is incomplete. Apply the missing baseline update before Version 0.7.8.0.');
        }
        // Repair version-record mismatches without replaying the historical chain.
        if (setup_table_exists($pdo,table('payments'))) {
            setup_add_column($pdo,table('payments'),'request_token','CHAR(64) NULL AFTER `reference_no`');
            if (!setup_index_exists($pdo,table('payments'),'uq_payments_request_token')) {
                $pdo->exec('ALTER TABLE `' . table('payments') . '` ADD UNIQUE INDEX `uq_payments_request_token` (`request_token`)');
            }
        }
        if (setup_table_exists($pdo,table('audit_logs'))) {
            setup_add_column($pdo,table('audit_logs'),'record_hash','CHAR(64) NULL AFTER `new_values_json`');
            if (!setup_index_exists($pdo,table('audit_logs'),'idx_audit_record_hash')) {
                $pdo->exec('ALTER TABLE `' . table('audit_logs') . '` ADD INDEX `idx_audit_record_hash` (`record_hash`)');
            }
        }
        foreach (['payment_idempotency_v0765_ready','role_separation_v0765_ready','task_preview_read_only_v0765_ready','critical_audit_v0765_ready','patient_portal_scope_v0765_ready','route_flow_reaudit_v0765_ready'] as $baselineFlag) {
            setting_set('system',$baselineFlag,'1');
        }
    }
    setting_set('system','application_version','0.7.8.0');
    setting_set('system','production_acceptance_v0780_ready','1');
    setting_set('system','chair_release_integrity_v0780_ready','1');
    setting_set('system','task_view_dead_end_guard_v0780_ready','1');
    setting_set('system','pending_only_update_v0780_ready','1');
    setting_set('system','runtime_diagnostics_v0780_ready','1');
    setting_set('system','clean_release_packaging_v0780_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache'],$_SESSION['dental_patient_context']);
    setup_record_history($pdo,'0.7.8.0','production_acceptance_and_workflow_stabilization');
    audit_required('system_update_applied','system',null,['version'=>'0.7.8.0','features'=>[
        'pending_only_update_execution_for_current_production_baseline',
        'verified_queue_and_appointment_chair_release_before_encounter_commit',
        'stale_finalized_chair_correction_preserves_checkout_stage',
        'task_view_preview_hides_mutating_controls_and_avoids_dead_end_pages',
        'application_owned_runtime_fatal_and_exception_log',
        'strict_clean_installer_allowlist_and_root_archive_layout',
    ]]);
}

function setup_v0790_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.7.9.0','>=')
            && (string)setting_get('system','production_monitoring_v0790_ready','0')==='1'
            && (string)setting_get('system','request_telemetry_v0790_ready','0')==='1'
            && (string)setting_get('system','role_acceptance_v0790_ready','0')==='1'
            && (string)setting_get('system','concurrency_verification_v0790_ready','0')==='1'
            && (string)setting_get('system','recovery_monitoring_v0790_ready','0')==='1'
            && (string)setting_get('system','error_standardization_v0790_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0790(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0780_ready($pdo)) {
        setup_apply_v0780($pdo);
    }
    setting_set('system','application_version','0.7.9.0');
    setting_set('system','production_monitoring_v0790_ready','1');
    setting_set('system','request_telemetry_v0790_ready','1');
    setting_set('system','role_acceptance_v0790_ready','1');
    setting_set('system','concurrency_verification_v0790_ready','1');
    setting_set('system','recovery_monitoring_v0790_ready','1');
    setting_set('system','error_standardization_v0790_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache'],$_SESSION['dental_patient_context']);
    setup_record_history($pdo,'0.7.9.0','production_validation_monitoring_and_performance');
    audit_required('system_update_applied','system',null,['version'=>'0.7.9.0','features'=>[
        'superuser_production_health_and_acceptance_monitor',
        'non_destructive_role_prerequisite_and_manual_signoff_gates',
        'slow_and_failed_request_telemetry_without_request_payloads',
        'backup_smtp_disk_session_and_runtime_log_readiness_checks',
        'stale_chair_cashier_queue_and_inventory_integrity_detection',
        'payment_idempotency_audit_and_transaction_engine_verification',
        'standardized_request_reference_for_exceptions_and_fatal_errors',
    ]]);
}

function setup_v0791_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.7.9.1','>=')
            && setup_column_exists($pdo,table('dentist_profiles'),'photo_original_path')
            && setup_column_exists($pdo,table('dentist_profiles'),'photo_square_path')
            && setup_column_exists($pdo,table('dentist_profiles'),'photo_portrait_path')
            && setup_column_exists($pdo,table('dentist_profiles'),'photo_thumbnail_path')
            && (string)setting_get('system','professional_doctor_photo_v0791_ready','0')==='1'
            && (string)setting_get('system','public_task_preview_isolation_v0791_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0791(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0790_ready($pdo)) setup_apply_v0790($pdo);
    setup_add_column($pdo,table('dentist_profiles'),'photo_original_path','VARCHAR(255) NULL AFTER `photo_path`');
    setup_add_column($pdo,table('dentist_profiles'),'photo_square_path','VARCHAR(255) NULL AFTER `photo_original_path`');
    setup_add_column($pdo,table('dentist_profiles'),'photo_portrait_path','VARCHAR(255) NULL AFTER `photo_square_path`');
    setup_add_column($pdo,table('dentist_profiles'),'photo_thumbnail_path','VARCHAR(255) NULL AFTER `photo_portrait_path`');
    setting_set('system','application_version','0.7.9.1');
    setting_set('system','professional_doctor_photo_v0791_ready','1');
    setting_set('system','public_task_preview_isolation_v0791_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache']);
    setup_record_history($pdo,'0.7.9.1','professional_doctor_photo_and_public_task_view_isolation');
    audit_required('system_update_applied','system',null,['version'=>'0.7.9.1','features'=>[
        'professional_square_portrait_thumbnail_and_protected_original_doctor_photos',
        'browser_crop_editor_with_server_side_validation_and_fallback',
        'public_page_uses_half_body_portrait_internal_workspaces_use_square_photo',
        'public_layout_never_renders_task_view_banner_or_read_only_script',
    ]]);
}

function setup_v0792_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.7.9.2','>=')
            && setup_column_exists($pdo,table('clinical_encounters'),'assistant_decision')
            && setup_column_exists($pdo,table('clinical_encounters'),'assistant_decided_by')
            && setup_column_exists($pdo,table('clinical_encounters'),'assistant_decided_at')
            && (string)setting_get('system','clinical_context_lock_v0792_ready','0')==='1'
            && (string)setting_get('system','assistant_decision_gate_v0792_ready','0')==='1'
            && (string)setting_get('system','clinical_dead_end_guard_v0792_ready','0')==='1'
            && (string)setting_get('system','odontogram_interaction_guard_v0792_ready','0')==='1'
            && (string)setting_get('system','walkin_focus_handoff_v0792_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0792(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0791_ready($pdo)) setup_apply_v0791($pdo);
    setup_add_column($pdo,table('clinical_encounters'),'assistant_decision',"ENUM('assigned','none') NULL AFTER `assistant_assigned_at`");
    setup_add_column($pdo,table('clinical_encounters'),'assistant_decided_by','BIGINT UNSIGNED NULL AFTER `assistant_decision`');
    setup_add_column($pdo,table('clinical_encounters'),'assistant_decided_at','DATETIME NULL AFTER `assistant_decided_by`');
    if (setup_column_exists($pdo,table('clinical_encounters'),'assistant_decision')) {
        $pdo->exec('UPDATE `' . table('clinical_encounters') . '` SET assistant_decision=IF(COALESCE(assistant_user_id,0)>0,"assigned","none"),assistant_decided_by=COALESCE(assistant_decided_by,assistant_assigned_by,dentist_user_id),assistant_decided_at=COALESCE(assistant_decided_at,assistant_assigned_at,created_at) WHERE assistant_decision IS NULL');
    }
    setting_set('system','application_version','0.7.9.2');
    setting_set('system','clinical_context_lock_v0792_ready','1');
    setting_set('system','assistant_decision_gate_v0792_ready','1');
    setting_set('system','clinical_dead_end_guard_v0792_ready','1');
    setting_set('system','odontogram_interaction_guard_v0792_ready','1');
    setting_set('system','walkin_focus_handoff_v0792_ready','1');
    setting_set('system','last_update_completed_at',date('c'));
    unset($_SESSION['dental_update_pending_cache'],$_SESSION['dental_active_clinical_context']);
    setup_record_history($pdo,'0.7.9.2','clinical_workflow_completion_and_dead_end_cleanup');
    audit_required('system_update_applied','system',null,['version'=>'0.7.9.2','features'=>[
        'explicit_assistant_or_none_decision_before_treatment_workspace_opens',
        'active_patient_and_encounter_context_with_explicit_exit',
        'walkin_and_checkin_return_to_the_created_queue_ticket',
        'critical_clinical_permission_failures_return_to_useful_workspaces',
        'odontogram_duplicate_click_and_unsaved_finding_guards',
        'finalization_clears_active_context_after_verified_chair_and_billing_handoff',
    ]]);
}


function setup_v0800_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.8.0.0','>=')
            && (string)setting_get('system','release_candidate_framework_v0800_ready','0')==='1'
            && (string)setting_get('system','release_candidate_installer_gate_v0800_ready','0')==='1'
            && (string)setting_get('system','release_candidate_cleanup_v0800_ready','0')==='1'
            && (string)setting_get('system','release_candidate_evidence_v0800_ready','0')==='1'
            && (string)setting_get('system','release_candidate_signoff_v0800_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0800(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0792_ready($pdo)) setup_apply_v0792($pdo);
    $now = date('c');
    setting_set('system','application_version','0.8.0.0');
    setting_set('system','release_candidate_framework_v0800_ready','1');
    setting_set('system','release_candidate_installer_gate_v0800_ready','1');
    setting_set('system','release_candidate_cleanup_v0800_ready','1');
    setting_set('system','release_candidate_evidence_v0800_ready','1');
    setting_set('system','release_candidate_signoff_v0800_ready','1');
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.8.0.0');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','release_candidate_version','0.8.0.0');
    setting_set('system','last_update_completed_at',$now);
    unset($_SESSION['dental_update_pending_cache']);
    setup_record_history($pdo,'0.8.0.0','production_release_candidate_and_final_go_live_gates');
    audit_required('system_update_applied','system',null,['version'=>'0.8.0.0','features'=>[
        'evidence_based_release_candidate_approval',
        'current_release_role_acceptance_expiry',
        'post_update_verified_backup_and_restore_gate',
        'emergency_diagnostic_entry_point_cleanup',
        'production_installer_locked_until_release_candidate_approval',
        'release_candidate_fingerprint_and_automatic_invalidation',
    ]]);
}

function setup_v0801_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.8.0.1','>=')
            && (string)setting_get('system','release_candidate_evidence_binding_v0801_ready','0')==='1'
            && (string)setting_get('system','verified_backup_selection_v0801_ready','0')==='1'
            && (string)setting_get('system','role_prerequisite_gate_v0801_ready','0')==='1'
            && (string)setting_get('system','monitor_review_fingerprint_v0801_ready','0')==='1'
            && (string)setting_get('system','installer_approval_binding_v0801_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0801(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0800_ready($pdo)) setup_apply_v0800($pdo);
    $now = date('c');
    setting_set('system','application_version','0.8.0.1');
    setting_set('system','release_candidate_evidence_binding_v0801_ready','1');
    setting_set('system','verified_backup_selection_v0801_ready','1');
    setting_set('system','role_prerequisite_gate_v0801_ready','1');
    setting_set('system','monitor_review_fingerprint_v0801_ready','1');
    setting_set('system','installer_approval_binding_v0801_ready','1');
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.8.0.1');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','release_candidate_version','0.8.0.1');
    setting_set('production_acceptance','last_monitor_reviewed_at','');
    setting_set('production_acceptance','last_monitor_reviewed_by','');
    setting_set('production_acceptance','last_monitor_review_fingerprint','');
    setting_set('production_acceptance','last_monitor_review_version','');
    setting_set('system','last_update_completed_at',$now);
    unset($_SESSION['dental_update_pending_cache']);
    setup_record_history($pdo,'0.8.0.1','release_candidate_evidence_and_backup_gate_reliability');
    audit_required('system_update_applied','system',null,['version'=>'0.8.0.1','features'=>[
        'release_candidate_approval_bound_to_stable_evidence_fingerprint',
        'production_installer_bound_to_exact_current_approval_id',
        'verified_backup_selected_without_being_hidden_by_newer_failed_attempt',
        'role_signoff_requires_current_operational_prerequisites',
        'production_monitor_review_expires_when_check_status_changes',
    ]]);
}

function setup_v0802_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.8.0.2','>=')
            && (string)setting_get('system','recovery_evidence_integrity_v0802_ready','0')==='1'
            && (string)setting_get('system','explicit_backup_verification_v0802_ready','0')==='1'
            && (string)setting_get('system','version_bound_manual_flow_v0802_ready','0')==='1'
            && (string)setting_get('system','version_bound_restore_evidence_v0802_ready','0')==='1'
            && (string)setting_get('system','future_timestamp_guard_v0802_ready','0')==='1'
            && (string)setting_get('system','role_evidence_fingerprint_v0802_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0802(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0801_ready($pdo)) setup_apply_v0801($pdo);
    $now = date('c');
    setting_set('system','application_version','0.8.0.2');
    setting_set('system','recovery_evidence_integrity_v0802_ready','1');
    setting_set('system','explicit_backup_verification_v0802_ready','1');
    setting_set('system','version_bound_manual_flow_v0802_ready','1');
    setting_set('system','version_bound_restore_evidence_v0802_ready','1');
    setting_set('system','future_timestamp_guard_v0802_ready','1');
    setting_set('system','role_evidence_fingerprint_v0802_ready','1');
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.8.0.2');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','release_candidate_version','0.8.0.2');
    setting_set('production_acceptance','last_monitor_reviewed_at','');
    setting_set('production_acceptance','last_monitor_reviewed_by','');
    setting_set('production_acceptance','last_monitor_review_fingerprint','');
    setting_set('production_acceptance','last_monitor_review_version','');
    setting_set('production_acceptance','last_monitor_review_evidence_id','');
    setting_set('production_acceptance','manual_flow_evidence','');
    setting_set('production_acceptance','restore_test_evidence','');
    setting_set('system','manual_role_flow_reviewed_at','');
    setting_set('system','manual_role_flow_reviewed_by','');
    setting_set('system','restore_tested_at','');
    setting_set('system','restore_tested_by','');
    setting_set('system','last_update_completed_at',$now);
    unset($_SESSION['dental_update_pending_cache']);
    setup_record_history($pdo,'0.8.0.2','recovery_evidence_and_approval_clock_integrity');
    audit_required('system_update_applied','system',null,['version'=>'0.8.0.2','features'=>[
        'explicit_backup_verification_required',
        'backup_file_checksum_size_and_timestamp_evidence_guard',
        'version_bound_manual_clinic_flow_evidence',
        'restore_drill_linked_to_verified_complete_backup',
        'future_dated_evidence_rejected',
        'role_signoff_bound_to_current_prerequisite_fingerprint',
    ]]);
}


function setup_v0803_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(),'0.8.0.3','>=')
            && (string)setting_get('system','patient_visit_ledger_v0803_ready','0')==='1'
            && (string)setting_get('system','general_staff_combined_role_v0803_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0803(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0802_ready($pdo)) setup_apply_v0802($pdo);

    setup_seed_permissions($pdo);
    if (function_exists('dental_role_satisfaction_repair')) {
        dental_role_satisfaction_repair($pdo);
    }

    if (setup_table_exists($pdo, table('roles'))) {
        $stmt = $pdo->prepare('UPDATE `' . table('roles') . '` SET name=?,description=? WHERE code=?');
        $stmt->execute([
            'General Staff',
            'Combined Receptionist and Cashier role for registration, appointments, queue, communications, cashier sessions, payments, receipts, and checkout without clinical, inventory, approval, or system authority',
            'staff',
        ]);
    }

    // General Staff now handles money and therefore requires a dedicated identity.
    // Preserve a primary General Staff assignment and disable its other secondary roles;
    // if General Staff is secondary to another primary role, disable the General Staff secondary assignment.
    if (setup_table_exists($pdo, table('user_role_assignments')) && setup_table_exists($pdo, table('roles'))) {
        $staffRoleId = (int)($pdo->query('SELECT id FROM `' . table('roles') . '` WHERE code="staff" LIMIT 1')->fetchColumn() ?: 0);
        if ($staffRoleId > 0) {
            $rows = $pdo->prepare('SELECT ura.id,ura.user_id,ura.role_id,ura.assignment_type FROM `' . table('user_role_assignments') . '` ura WHERE ura.status="active" AND (ura.role_id=? OR ura.user_id IN (SELECT x.user_id FROM `' . table('user_role_assignments') . '` x WHERE x.role_id=? AND x.status="active")) ORDER BY ura.user_id,FIELD(ura.assignment_type,"primary","secondary"),ura.id');
            $rows->execute([$staffRoleId,$staffRoleId]);
            $byUser=[]; foreach($rows->fetchAll() as $row) $byUser[(int)$row['user_id']][]=$row;
            $disable=[];
            foreach($byUser as $assignments){
                $staffAssignment=null;$primary=null;
                foreach($assignments as $assignment){
                    if((int)$assignment['role_id']===$staffRoleId)$staffAssignment=$assignment;
                    if((string)$assignment['assignment_type']==='primary')$primary=$assignment;
                }
                if(!$staffAssignment||count($assignments)<2)continue;
                if((string)$staffAssignment['assignment_type']==='primary'){
                    foreach($assignments as $assignment)if((int)$assignment['id']!==(int)$staffAssignment['id']&&(string)$assignment['assignment_type']==='secondary')$disable[]=(int)$assignment['id'];
                }else{
                    $disable[]=(int)$staffAssignment['id'];
                }
            }
            $disable=array_values(array_unique(array_filter($disable)));
            if($disable){
                $marks=implode(',',array_fill(0,count($disable),'?'));
                $pdo->prepare('UPDATE `' . table('user_role_assignments') . '` SET status="inactive",updated_at=NOW() WHERE id IN ('.$marks.')')->execute($disable);
            }
        }
    }

    $now = date('c');
    setting_set('system','application_version','0.8.0.3');
    setting_set('system','patient_visit_ledger_v0803_ready','1');
    setting_set('system','general_staff_combined_role_v0803_ready','1');
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.8.0.3');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','release_candidate_version','0.8.0.3');
    setting_set('production_acceptance','last_monitor_reviewed_at','');
    setting_set('production_acceptance','last_monitor_reviewed_by','');
    setting_set('production_acceptance','last_monitor_review_fingerprint','');
    setting_set('production_acceptance','last_monitor_review_version','');
    setting_set('production_acceptance','last_monitor_review_evidence_id','');
    setting_set('production_acceptance','manual_flow_evidence','');
    setting_set('production_acceptance','restore_test_evidence','');
    setting_set('system','manual_role_flow_reviewed_at','');
    setting_set('system','manual_role_flow_reviewed_by','');
    setting_set('system','restore_tested_at','');
    setting_set('system','restore_tested_by','');
    setting_set('system','last_update_completed_at',$now);
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo,'0.8.0.3','patient_visit_ledger_and_general_staff_combined_role');
    audit_required('system_update_applied','system',null,['version'=>'0.8.0.3','features'=>[
        'patient_lifetime_appointment_and_visit_ledger',
        'patient_itemized_invoice_payment_allocation_and_return_history',
        'general_staff_combines_receptionist_and_cashier_duties',
        'general_staff_clinical_inventory_approval_and_system_boundaries_preserved',
        'production_acceptance_expanded_to_general_staff',
    ]]);
}

function setup_v0804_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        $encounters = table('clinical_encounters');
        return version_compare(installed_application_version(),'0.8.0.4','>=')
            && setup_column_exists($pdo,$encounters,'care_path')
            && setup_column_exists($pdo,$encounters,'vitals_skip_reason')
            && setup_column_exists($pdo,$encounters,'care_path_decided_by')
            && setup_column_exists($pdo,$encounters,'care_path_decided_at')
            && (string)setting_get('system','adaptive_encounter_workflow_v0804_ready','0')==='1'
            && (string)setting_get('system','production_gate_completion_center_v0804_ready','0')==='1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0804(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0803_ready($pdo)) setup_apply_v0803($pdo);

    $encounters = table('clinical_encounters');
    if (setup_table_exists($pdo,$encounters)) {
        setup_add_column($pdo,$encounters,'care_path',"VARCHAR(40) NOT NULL DEFAULT 'comprehensive' AFTER `visit_id`");
        setup_add_column($pdo,$encounters,'vitals_skip_reason','VARCHAR(500) NULL AFTER `care_path`');
        setup_add_column($pdo,$encounters,'care_path_decided_by','BIGINT UNSIGNED NULL AFTER `vitals_skip_reason`');
        setup_add_column($pdo,$encounters,'care_path_decided_at','DATETIME NULL AFTER `care_path_decided_by`');
        if (!setup_index_exists($pdo,$encounters,'idx_encounter_care_path')) {
            $pdo->exec('ALTER TABLE `' . $encounters . '` ADD INDEX `idx_encounter_care_path` (`care_path`,`status`,`created_at`)');
        }
        $pdo->exec("UPDATE `{$encounters}` SET care_path='comprehensive' WHERE care_path IS NULL OR care_path='' OR care_path NOT IN ('focused_dental','comprehensive','emergency','follow_up')");
    }

    $now = date('c');
    setting_set('system','application_version','0.8.0.4');
    setting_set('system','adaptive_encounter_workflow_v0804_ready','1');
    setting_set('system','production_gate_completion_center_v0804_ready','1');
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.8.0.4');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','release_candidate_version','0.8.0.4');
    setting_set('production_acceptance','last_monitor_reviewed_at','');
    setting_set('production_acceptance','last_monitor_reviewed_by','');
    setting_set('production_acceptance','last_monitor_review_fingerprint','');
    setting_set('production_acceptance','last_monitor_review_version','');
    setting_set('production_acceptance','last_monitor_review_evidence_id','');
    setting_set('production_acceptance','manual_flow_evidence','');
    setting_set('production_acceptance','restore_test_evidence','');
    setting_set('system','manual_role_flow_reviewed_at','');
    setting_set('system','manual_role_flow_reviewed_by','');
    setting_set('system','restore_tested_at','');
    setting_set('system','restore_tested_by','');
    setting_set('system','last_update_completed_at',$now);
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo,'0.8.0.4','adaptive_dental_encounter_and_production_gate_completion');
    audit_required('system_update_applied','system',null,['version'=>'0.8.0.4','features'=>[
        'adaptive_focused_comprehensive_emergency_and_follow_up_encounter_paths',
        'conditional_hpi_general_assessment_and_vital_sign_requirements',
        'audited_optional_vitals_reason_for_low_risk_dental_care',
        'teeth_first_clinical_navigation',
        'ordered_production_gate_completion_center',
        'reviewed_runtime_and_performance_monitoring_checkpoints',
    ]]);
}

function setup_v0805_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        $backup = table('backup_logs');
        return version_compare(installed_application_version(), '0.8.0.5', '>=')
            && setup_table_exists($pdo, table('system_update_runs'))
            && setup_column_exists($pdo, $backup, 'verification_evidence_json')
            && setup_column_exists($pdo, $backup, 'verification_file_count')
            && setup_column_exists($pdo, $backup, 'verification_format_version')
            && (string)setting_get('system', 'automated_production_validation_v0805_ready', '0') === '1'
            && (string)setting_get('system', 'resumable_updates_v0805_ready', '0') === '1'
            && (string)setting_get('system', 'simplified_production_release_v0805_ready', '0') === '1'
            && (string)setting_get('system', 'backup_verification_status_fix_v0805_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0805(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0804_ready($pdo)) setup_apply_v0804($pdo);

    dental_update_checkpoint_ensure_schema($pdo);

    $backup = table('backup_logs');
    if (setup_table_exists($pdo, $backup)) {
        setup_add_column($pdo, $backup, 'verification_evidence_json', 'LONGTEXT NULL AFTER `verified_at`');
        setup_add_column($pdo, $backup, 'verification_file_count', 'INT UNSIGNED NOT NULL DEFAULT 0 AFTER `verification_evidence_json`');
        setup_add_column($pdo, $backup, 'verification_format_version', 'INT UNSIGNED NULL AFTER `verification_file_count`');
        // Avoid future strict-mode truncation when backup lifecycle labels evolve.
        $pdo->exec('ALTER TABLE `' . $backup . '` MODIFY `status` VARCHAR(30) NOT NULL DEFAULT "created"');
    }

    setup_seed_permissions($pdo);
    if (function_exists('dental_role_satisfaction_repair')) dental_role_satisfaction_repair($pdo);

    $now = date('c');
    setting_set('system', 'application_version', '0.8.0.5');
    setting_set('system', 'automated_production_validation_v0805_ready', '1');
    setting_set('system', 'resumable_updates_v0805_ready', '1');
    setting_set('system', 'simplified_production_release_v0805_ready', '1');
    setting_set('system', 'backup_verification_status_fix_v0805_ready', '1');
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.8.0.5');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'release_candidate_version', '0.8.0.5');
    setting_set('production_acceptance', 'release_candidate_manual_hold', '0');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    setting_set('production_acceptance', 'manual_flow_evidence', '');
    setting_set('production_acceptance', 'restore_test_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo, '0.8.0.5', 'automated_production_validation_and_resumable_updates');
    audit_required('system_update_applied', 'system', null, ['version'=>'0.8.0.5','features'=>[
        'automated_technical_production_validation',
        'three_live_environment_confirmations_only',
        'automatic_production_package_unlock_when_all_gates_pass',
        'one_release_step_per_request_resumable_update_runner',
        'database_update_lock_and_persistent_checkpoint',
        'deep_backup_verification_without_invalid_status_enum_write',
        'patient_ledger_finalized_only_privacy_regression_guard',
        'general_staff_reception_cashier_separation_validation',
    ]]);
}


function setup_v0810_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.8.1.0', '>=')
            && is_file(DENTAL_ROOT . '/app/storage_health.php')
            && function_exists('verify_csrf')
            && function_exists('csrf_validate')
            && (string)setting_get('system', 'quota_aware_storage_health_v0810_ready', '0') === '1'
            && (string)setting_get('system', 'update_transport_reliability_v0810_ready', '0') === '1'
            && (string)setting_get('system', 'csrf_compatibility_v0810_ready', '0') === '1'
            && (string)setting_get('system', 'production_release_v0810_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0810(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0805_ready($pdo)) setup_apply_v0805($pdo);

    $now = date('c');
    setting_set('system', 'application_version', '0.8.1.0');
    setting_set('system', 'quota_aware_storage_health_v0810_ready', '1');
    setting_set('system', 'update_transport_reliability_v0810_ready', '1');
    setting_set('system', 'csrf_compatibility_v0810_ready', '1');
    setting_set('system', 'production_release_v0810_ready', '1');
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.8.1.0');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('production_acceptance', 'release_candidate_version', '0.8.1.0');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo, '0.8.1.0', 'production_release_storage_health_and_update_reliability');
    audit_required('system_update_applied', 'system', null, ['version'=>'0.8.1.0','features'=>[
        'quota_aware_cpanel_storage_health',
        'shared_filesystem_not_mislabeled_as_account_quota',
        'protected_storage_write_probe',
        'bounded_managed_storage_usage_breakdown',
        'current_cpanel_quota_snapshot_support',
        'resumable_update_transport_fallback',
        'csrf_function_compatibility_recovery',
        'formal_production_release',
    ]]);
}


function setup_v0811_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        return version_compare(installed_application_version(), '0.8.1.1', '>=')
            && is_file(DENTAL_ROOT . '/app/storage_health.php')
            && (string)setting_get('system', 'shared_filesystem_percentage_nonblocking_v0811_ready', '0') === '1'
            && (string)setting_get('system', 'unknown_quota_nonblocking_v0811_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0811(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (!setup_v0810_ready($pdo)) setup_apply_v0810($pdo);

    $now = date('c');
    setting_set('system', 'application_version', '0.8.1.1');
    setting_set('system', 'shared_filesystem_percentage_nonblocking_v0811_ready', '1');
    setting_set('system', 'unknown_quota_nonblocking_v0811_ready', '1');
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.8.1.1');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('production_acceptance', 'release_candidate_version', '0.8.1.1');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo, '0.8.1.1', 'shared_filesystem_storage_gate_correction');
    audit_required('system_update_applied', 'system', null, ['version'=>'0.8.1.1','features'=>[
        'shared_filesystem_percentage_is_informational_only',
        'write_safe_unknown_quota_passes_production_gate',
        'absolute_emergency_free_space_floor_retained',
        'false_storage_blocker_removed',
    ]]);
}

function setup_v0812_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        $assignments = table('user_role_assignments');
        $users = table('users');
        return version_compare(installed_application_version(), '0.8.1.2', '>=')
            && setup_table_exists($pdo, table('system_migration_ledger'))
            && setup_column_exists($pdo, $assignments, 'expires_at')
            && setup_column_exists($pdo, $assignments, 'last_switched_at')
            && setup_column_exists($pdo, $users, 'role_context_version')
            && setup_column_exists($pdo, $users, 'last_role_switch_at')
            && setup_column_exists($pdo, $users, 'last_role_switch_role_id')
            && (string)setting_get('system', 'controlled_active_roles_v0812_ready', '0') === '1'
            && (string)setting_get('system', 'deterministic_update_plan_v0812_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0812(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (version_compare(installed_application_version(), '0.8.1.1', '<')) {
        throw new RuntimeException('DentalCare v0.8.1.2 requires the installed database marker to be at least v0.8.1.1. Apply the supported prior package first; historical release steps will not be replayed automatically.');
    }

    $users = table('users');
    $assignments = table('user_role_assignments');
    if (setup_table_exists($pdo, $users)) {
        setup_add_column($pdo, $users, 'role_context_version', 'INT UNSIGNED NOT NULL DEFAULT 1');
        setup_add_column($pdo, $users, 'last_role_switch_at', 'DATETIME NULL');
        setup_add_column($pdo, $users, 'last_role_switch_role_id', 'BIGINT UNSIGNED NULL');
    }
    if (setup_table_exists($pdo, $assignments)) {
        setup_add_column($pdo, $assignments, 'expires_at', 'DATETIME NULL');
        setup_add_column($pdo, $assignments, 'last_switched_at', 'DATETIME NULL');
        if (!setup_index_exists($pdo, $assignments, 'idx_user_role_assignment_expiry')) {
            $pdo->exec('ALTER TABLE `' . $assignments . '` ADD INDEX `idx_user_role_assignment_expiry` (`user_id`,`status`,`expires_at`)');
        }
        $pdo->exec('INSERT INTO `' . $assignments . '` (user_id,role_id,assignment_type,status,note,created_at,updated_at) '
            . 'SELECT u.id,u.role_id,"primary","active","Primary role",NOW(),NOW() FROM `' . $users . '` u '
            . 'ON DUPLICATE KEY UPDATE assignment_type="primary",status="active",expires_at=NULL,updated_at=NOW()');

        // Normalize legacy assignments without deleting accounts or historical
        // attribution. Privileged authority cannot be borrowed as a secondary
        // role, dedicated financial roles remain exclusive, and incompatible
        // clinical/custody combinations are disabled deterministically.
        $rows = $pdo->query('SELECT ura.id,ura.user_id,ura.assignment_type,r.code,u.role_id primary_role_id,pr.code primary_code '
            . 'FROM `' . $assignments . '` ura '
            . 'JOIN `' . table('roles') . '` r ON r.id=ura.role_id '
            . 'JOIN `' . $users . '` u ON u.id=ura.user_id '
            . 'JOIN `' . table('roles') . '` pr ON pr.id=u.role_id '
            . 'WHERE ura.status="active" ORDER BY ura.user_id,FIELD(ura.assignment_type,"primary","secondary"),ura.id')->fetchAll();
        $grouped = [];
        foreach ($rows as $row) $grouped[(int)$row['user_id']][] = $row;
        $disableIds = [];
        $affectedUsers = [];
        $exclusive = ['super_admin','cashier','staff'];
        $conflicts = [['dentist','dental_assistant'],['dentist','inventory_officer'],['dental_assistant','inventory_officer']];
        foreach ($grouped as $userId => $roleRows) {
            $primaryCode = trim((string)($roleRows[0]['primary_code'] ?? ''));
            $kept = $primaryCode !== '' ? [$primaryCode] : [];
            foreach ($roleRows as $row) {
                if ((string)$row['assignment_type'] === 'primary') continue;
                $code = trim((string)$row['code']);
                $blocked = $code === ''
                    || $code === 'clinic_admin'
                    || in_array($code, $exclusive, true)
                    || (bool)array_intersect($kept, $exclusive);
                if (!$blocked) {
                    foreach ($conflicts as [$left,$right]) {
                        if ((in_array($left,$kept,true) && $code === $right) || (in_array($right,$kept,true) && $code === $left)) {
                            $blocked = true;
                            break;
                        }
                    }
                }
                if ($blocked) {
                    $disableIds[] = (int)$row['id'];
                    $affectedUsers[] = (int)$userId;
                } else {
                    $kept[] = $code;
                }
            }
        }
        $disableIds = array_values(array_unique(array_filter($disableIds)));
        $affectedUsers = array_values(array_unique(array_filter($affectedUsers)));
        if ($disableIds) {
            $marks = implode(',', array_fill(0, count($disableIds), '?'));
            $pdo->prepare('UPDATE `' . $assignments . '` SET status="inactive",updated_at=NOW() WHERE id IN (' . $marks . ') AND assignment_type="secondary"')->execute($disableIds);
        }
        if ($affectedUsers && setup_column_exists($pdo, $users, 'role_context_version')) {
            $marks = implode(',', array_fill(0, count($affectedUsers), '?'));
            $pdo->prepare('UPDATE `' . $users . '` SET role_context_version=role_context_version+1,updated_at=NOW() WHERE id IN (' . $marks . ')')->execute($affectedUsers);
        }
        setting_set('system', 'controlled_active_roles_normalized_count_v0812', (string)count($disableIds));
    }

    if (setup_table_exists($pdo, table('permissions')) && setup_table_exists($pdo, table('role_permissions'))) {
        $stmt = $pdo->prepare('UPDATE `' . table('role_permissions') . '` rp '
            . 'JOIN `' . table('permissions') . '` p ON p.id=rp.permission_id '
            . 'JOIN `' . table('roles') . '` r ON r.id=rp.role_id '
            . 'SET rp.allowed=0 WHERE r.code="dental_assistant" AND p.code IN ("queue.intake","queue.frontdesk","billing.manage","cashier.session.manage","inventory.catalog","inventory.manage","settings.manage","staff.manage","smtp.manage")');
        $stmt->execute();
    }

    $ledger = table('system_migration_ledger');
    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $ledger . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `version` VARCHAR(40) NOT NULL,
        `migration_key` VARCHAR(120) NOT NULL,
        `checksum_sha256` CHAR(64) NULL,
        `status` VARCHAR(30) NOT NULL DEFAULT "completed",
        `applied_by` BIGINT UNSIGNED NULL,
        `applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `error_reference` VARCHAR(40) NULL,
        `error_message` TEXT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_system_migration_key` (`migration_key`),
        KEY `idx_system_migration_version` (`version`,`status`,`applied_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $migrationPath = DENTAL_ROOT . '/database/migrations/0.8.1.2_controlled_active_roles.sql';
    $checksum = is_file($migrationPath) ? hash_file('sha256', $migrationPath) : null;
    $stmt = $pdo->prepare('INSERT INTO `' . $ledger . '` (version,migration_key,checksum_sha256,status,applied_by,applied_at) VALUES (?,?,?,?,?,NOW()) ON DUPLICATE KEY UPDATE version=VALUES(version),checksum_sha256=VALUES(checksum_sha256),status="completed",applied_by=VALUES(applied_by),applied_at=NOW(),error_reference=NULL,error_message=NULL');
    $stmt->execute(['0.8.1.2','0.8.1.2_controlled_active_roles',$checksum,'completed',(int)(current_user()['id'] ?? 0) ?: null]);

    $now = date('c');
    setting_set('system', 'application_version', '0.8.1.2');
    setting_set('system', 'controlled_active_roles_v0812_ready', '1');
    setting_set('system', 'deterministic_update_plan_v0812_ready', '1');
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.8.1.2');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('production_acceptance', 'release_candidate_version', '0.8.1.2');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache'], $_SESSION['task_role'], $_SESSION['role_context_version']);

    setup_record_history($pdo, '0.8.1.2', 'controlled_active_roles_and_deterministic_update_plan');
    audit_required('system_update_applied', 'system', null, ['version'=>'0.8.1.2','features'=>[
        'database_controlled_active_role_assignments',
        'role_expiration_assignment_reason_and_last_used_tracking',
        'post_only_csrf_protected_role_switching',
        'sensitive_work_role_switch_blockers',
        'superuser_workspace_filters_without_authority_loss',
        'ordinary_multi_role_permission_isolation',
        'redundant_workspace_banner_removed',
        'historical_release_steps_not_replayed_by_readiness_drift',
        'permanent_migration_ledger',
    ]]);
}

function setup_v0900_ready(PDO $pdo): bool
{
    try {
        ensure_settings_table($pdo);
        $ledger = table('system_migration_ledger');
        return version_compare(installed_application_version(), '0.9.0.0', '>=')
            && setup_table_exists($pdo, $ledger)
            && setup_column_exists($pdo, $ledger, 'package_version')
            && setup_column_exists($pdo, $ledger, 'attempt_count')
            && setup_column_exists($pdo, $ledger, 'started_at')
            && setup_column_exists($pdo, $ledger, 'completed_at')
            && setup_column_exists($pdo, $ledger, 'updated_at')
            && setup_column_exists($pdo, $ledger, 'execution_ms')
            && setup_table_exists($pdo, table('system_hardening_runs'))
            && is_file(DENTAL_ROOT . '/app/hardening_foundation.php')
            && (string)setting_get('system', 'hardening_foundation_v0900_ready', '0') === '1'
            && (string)setting_get('system', 'deterministic_migration_ledger_v0900_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0900(PDO $pdo): void
{
    ensure_settings_table($pdo);
    if (version_compare(installed_application_version(), '0.8.1.2', '<')) {
        throw new RuntimeException('DentalCare v0.9.0.0 requires the supported v0.8.1.2 baseline. Historical releases are not replayed automatically.');
    }

    // Runtime migration success must depend only on runtime/database assets.
    // Composer, PHPUnit, PHPStan, Semgrep, and CI files are development tools;
    // cPanel may hide or omit dotfiles and they must never block a production
    // database migration.
    dental_update_checkpoint_ensure_schema($pdo);
    dental_migration_ledger_ensure_schema($pdo);

    $hardeningRuns = table('system_hardening_runs');
    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $hardeningRuns . '` (
'
        . '  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
'
        . '  `release_version` VARCHAR(40) NOT NULL,
'
        . '  `status` VARCHAR(20) NOT NULL,
'
        . '  `blocked_count` INT UNSIGNED NOT NULL DEFAULT 0,
'
        . '  `review_count` INT UNSIGNED NOT NULL DEFAULT 0,
'
        . '  `fingerprint_sha256` CHAR(64) NOT NULL,
'
        . '  `result_json` LONGTEXT NOT NULL,
'
        . '  `run_by` BIGINT UNSIGNED NULL,
'
        . '  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
'
        . '  PRIMARY KEY (`id`),
'
        . '  KEY `idx_hardening_release_status` (`release_version`,`status`,`created_at`)
'
        . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $now = date('c');
    setting_set('system', 'application_version', '0.9.0.0');
    setting_set('system', 'hardening_foundation_v0900_ready', '1');
    setting_set('system', 'deterministic_migration_ledger_v0900_ready', '1');
    setting_set('system', 'automated_test_foundation_v0900_ready', '1');
    setting_set('system', 'sast_ci_foundation_v0900_ready', '1');
    setting_set('system', 'canonical_release_baseline_v0900_ready', '1');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.9.0.0');
    setting_set('production_acceptance', 'release_candidate_version', '0.9.0.0');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo, '0.9.0.0', 'hardening_foundation_and_deterministic_migrations');
    try {
        audit_required('system_update_applied', 'system', null, ['version'=>'0.9.0.0','features'=>[
            'canonical_hardening_baseline',
            'immutable_migration_keys_and_checksums',
            'completed_migrations_never_replayed_by_readiness_drift',
            'interrupted_migration_recovery_by_postcondition',
            'phpunit_test_foundation',
            'phpstan_static_analysis_foundation',
            'semgrep_security_rules',
            'continuous_integration_workflow',
            'deterministic_release_builder',
            'in_application_hardening_center',
        ]]);
        setting_set('system', 'hardening_foundation_v0900_audit_warning', '');
    } catch (Throwable $auditException) {
        // The permanent migration ledger already records actor, checksum,
        // status, and timing. Keep the migration recoverable and surface the
        // audit problem as a Hardening Center review item instead of leaving
        // the whole installation in a false failed state after schema success.
        setting_set('system', 'hardening_foundation_v0900_audit_warning', mb_substr($auditException->getMessage(), 0, 1000));
        if (function_exists('dental_runtime_write_log')) {
            dental_runtime_write_log('v0.9.0.0 update audit warning: ' . $auditException->getMessage());
        } else {
            error_log('DentalCare v0.9.0.0 update audit warning: ' . $auditException->getMessage());
        }
    }
}

function setup_v0901_ready(PDO $pdo): bool
{
    try {
        // Runtime migration readiness must depend on database/runtime state,
        // not optional documentation files that may be omitted by cPanel.
        return version_compare(installed_application_version(), '0.9.0.1', '>=')
            && setup_v0900_ready($pdo)
            && (string)setting_get('system', 'hardening_tracker_v0901_ready', '0') === '1'
            && (string)setting_get('system', 'migration_recovery_v0901_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0901(PDO $pdo): void
{
    if (!setup_v0900_ready($pdo)) {
        // Retry/recover the failed foundation idempotently before advancing.
        setup_apply_v0900($pdo);
    }
    if (!setup_v0900_ready($pdo)) {
        throw new RuntimeException('The v0.9.0.0 hardening foundation still does not satisfy its runtime postconditions.');
    }

    $now = date('c');
    setting_set('system', 'application_version', '0.9.0.1');
    setting_set('system', 'hardening_tracker_v0901_ready', '1');
    setting_set('system', 'migration_recovery_v0901_ready', '1');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.9.0.1');
    setting_set('production_acceptance', 'release_candidate_version', '0.9.0.1');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo, '0.9.0.1', 'migration_recovery_and_hardening_tracker');
    audit('system_update_applied', 'system', null, ['version'=>'0.9.0.1','features'=>[
        'production_runtime_postconditions_only',
        'development_toolchain_nonblocking_for_database_migration',
        'safe_install_history_expansion',
        'failed_v0900_migration_recovery',
        'living_markdown_hardening_tracker',
        'next_hardening_phase_plan',
    ]]);
}


function setup_v0902_ready(PDO $pdo): bool
{
    try {
        $timeout = (int)setting_get('security', 'patient_session_timeout_minutes', '0');
        return version_compare(installed_application_version(), '0.9.0.2', '>=')
            && setup_v0901_ready($pdo)
            && (string)setting_get('system', 'release_sanitization_v0902_ready', '0') === '1'
            && (string)setting_get('system', 'sensitive_permissions_fail_closed_v0902_ready', '0') === '1'
            && (string)setting_get('system', 'patient_portal_session_hardening_v0902_ready', '0') === '1'
            && $timeout >= 5
            && $timeout <= 120;
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0902(PDO $pdo): void
{
    if (!setup_v0901_ready($pdo)) setup_apply_v0901($pdo);
    if (!setup_v0901_ready($pdo)) {
        throw new RuntimeException('The v0.9.0.1 recovery baseline is incomplete.');
    }

    $timeout = (int)setting_get('security', 'patient_session_timeout_minutes', '20');
    if ($timeout < 5 || $timeout > 120) $timeout = 20;

    $now = date('c');
    setting_set('security', 'patient_session_timeout_minutes', (string)$timeout);
    setting_set('system', 'application_version', '0.9.0.2');
    setting_set('system', 'release_sanitization_v0902_ready', '1');
    setting_set('system', 'sensitive_permissions_fail_closed_v0902_ready', '1');
    setting_set('system', 'patient_portal_session_hardening_v0902_ready', '1');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.9.0.2');
    setting_set('production_acceptance', 'release_candidate_version', '0.9.0.2');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);

    setup_record_history($pdo, '0.9.0.2', 'emergency_release_sanitization_and_access_control');
    audit('system_update_applied', 'system', null, ['version'=>'0.9.0.2','features'=>[
        'completed_archive_secret_scan',
        'forbidden_runtime_and_private_path_rejection',
        'sensitive_permissions_fail_closed',
        'patient_portal_login_throttling',
        'patient_portal_session_rotation',
        'patient_portal_idle_timeout',
        'same_request_setting_cache_consistency',
        'failed_migration_status_reconciliation',
    ]]);
}

function setup_v0903_ready(PDO $pdo): bool
{
    try {
        $methods = table('user_mfa_methods');
        $recovery = table('user_mfa_recovery_codes');
        return version_compare(installed_application_version(), '0.9.0.3', '>=')
            && setup_v0902_ready($pdo)
            && setup_table_exists($pdo, $methods)
            && setup_column_exists($pdo, $methods, 'user_id')
            && setup_column_exists($pdo, $methods, 'method_type')
            && setup_column_exists($pdo, $methods, 'secret_encrypted')
            && setup_column_exists($pdo, $methods, 'enabled_at')
            && setup_column_exists($pdo, $methods, 'revoked_at')
            && setup_table_exists($pdo, $recovery)
            && setup_column_exists($pdo, $recovery, 'user_id')
            && setup_column_exists($pdo, $recovery, 'code_hash')
            && setup_column_exists($pdo, $recovery, 'used_at')
            && (string)setting_get('system', 'privileged_mfa_v0903_ready', '0') === '1'
            && (string)setting_get('system', 'staff_session_security_v0903_ready', '0') === '1'
            && (string)setting_get('system', 'service_staffing_bulk_scope_v0903_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0903(PDO $pdo): void
{
    if (!setup_v0902_ready($pdo)) setup_apply_v0902($pdo);
    if (!setup_v0902_ready($pdo)) {
        throw new RuntimeException('The v0.9.0.2 emergency hardening baseline is incomplete.');
    }

    $methods = table('user_mfa_methods');
    $recovery = table('user_mfa_recovery_codes');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $methods . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `user_id` BIGINT UNSIGNED NOT NULL,
        `method_type` VARCHAR(30) NOT NULL DEFAULT "totp",
        `secret_encrypted` TEXT NOT NULL,
        `label` VARCHAR(120) NULL,
        `enabled_at` DATETIME NULL,
        `last_used_at` DATETIME NULL,
        `revoked_at` DATETIME NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NULL,
        PRIMARY KEY (`id`),
        KEY `idx_mfa_user_active` (`user_id`,`method_type`,`revoked_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $recovery . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `user_id` BIGINT UNSIGNED NOT NULL,
        `code_hash` CHAR(64) NOT NULL,
        `used_at` DATETIME NULL,
        `used_ip` VARCHAR(64) NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_mfa_recovery_hash` (`user_id`,`code_hash`),
        KEY `idx_mfa_recovery_unused` (`user_id`,`used_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    // Repair partially-created tables without destructive recreation.
    setup_add_column($pdo, $methods, 'user_id', 'BIGINT UNSIGNED NULL AFTER `id`');
    setup_add_column($pdo, $methods, 'method_type', 'VARCHAR(30) NOT NULL DEFAULT "totp" AFTER `user_id`');
    setup_add_column($pdo, $methods, 'secret_encrypted', 'TEXT NULL AFTER `method_type`');
    setup_add_column($pdo, $methods, 'label', 'VARCHAR(120) NULL AFTER `secret_encrypted`');
    setup_add_column($pdo, $methods, 'enabled_at', 'DATETIME NULL AFTER `label`');
    setup_add_column($pdo, $methods, 'last_used_at', 'DATETIME NULL AFTER `enabled_at`');
    setup_add_column($pdo, $methods, 'revoked_at', 'DATETIME NULL AFTER `last_used_at`');
    setup_add_column($pdo, $methods, 'created_at', 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `revoked_at`');
    setup_add_column($pdo, $methods, 'updated_at', 'DATETIME NULL AFTER `created_at`');
    if (!setup_index_exists($pdo, $methods, 'idx_mfa_user_active')) {
        try { $pdo->exec('ALTER TABLE `' . $methods . '` ADD KEY `idx_mfa_user_active` (`user_id`,`method_type`,`revoked_at`)'); } catch (Throwable $exception) { error_log('MFA method index repair skipped: ' . $exception->getMessage()); }
    }

    setup_add_column($pdo, $recovery, 'user_id', 'BIGINT UNSIGNED NULL AFTER `id`');
    setup_add_column($pdo, $recovery, 'code_hash', 'CHAR(64) NULL AFTER `user_id`');
    setup_add_column($pdo, $recovery, 'used_at', 'DATETIME NULL AFTER `code_hash`');
    setup_add_column($pdo, $recovery, 'used_ip', 'VARCHAR(64) NULL AFTER `used_at`');
    setup_add_column($pdo, $recovery, 'created_at', 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `used_ip`');
    if (!setup_index_exists($pdo, $recovery, 'uq_mfa_recovery_hash')) {
        try { $pdo->exec('ALTER TABLE `' . $recovery . '` ADD UNIQUE KEY `uq_mfa_recovery_hash` (`user_id`,`code_hash`)'); } catch (Throwable $exception) { error_log('MFA recovery unique index repair skipped: ' . $exception->getMessage()); }
    }
    if (!setup_index_exists($pdo, $recovery, 'idx_mfa_recovery_unused')) {
        try { $pdo->exec('ALTER TABLE `' . $recovery . '` ADD KEY `idx_mfa_recovery_unused` (`user_id`,`used_at`)'); } catch (Throwable $exception) { error_log('MFA recovery index repair skipped: ' . $exception->getMessage()); }
    }

    $now = date('c');
    $existingMfaPolicy = setting_get('security', 'require_mfa_privileged', null);
    if (!extension_loaded('openssl')) {
        setting_set('security', 'require_mfa_privileged', '0');
    } elseif ($existingMfaPolicy === null || $existingMfaPolicy === '') {
        setting_set('security', 'require_mfa_privileged', '1');
    }
    setting_set('security', 'staff_absolute_session_hours', '12');
    setting_set('security', 'staff_session_rotation_minutes', '15');
    setting_set('system', 'application_version', '0.9.0.3');
    setting_set('system', 'privileged_mfa_v0903_ready', '1');
    setting_set('system', 'staff_session_security_v0903_ready', '1');
    setting_set('system', 'service_staffing_bulk_scope_v0903_ready', '1');
    setting_set('system', 'mfa_encryption_available_v0903', extension_loaded('openssl') ? '1' : '0');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.9.0.3');
    setting_set('production_acceptance', 'release_candidate_version', '0.9.0.3');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo, '0.9.0.3', 'privileged_mfa_session_security_and_bulk_service_staffing');
    try {
        audit('system_update_applied', 'system', null, ['version'=>'0.9.0.3','features'=>[
            'totp_authenticator_enrollment',
            'single_use_recovery_codes',
            'second_step_staff_login',
            'privileged_role_mfa_enrollment_gate',
            'staff_absolute_session_timeout',
            'periodic_session_identifier_rotation',
            'session_revocation_after_password_or_mfa_change',
            'service_staffing_apply_to_all_matching',
            'service_staffing_bulk_scope_across_pagination',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.0.3 migration audit write skipped after successful state application: ' . $exception->getMessage());
    }
}

function setup_v0904_ready(PDO $pdo): bool
{
    try {
        $logs = table('access_decision_logs');
        if (!version_compare(installed_application_version(), '0.9.0.4', '>=')) return false;
        if (!setup_v0903_ready($pdo)) return false;
        if (!setup_table_exists($pdo, $logs)
            || !setup_column_exists($pdo, $logs, 'decision')
            || !setup_column_exists($pdo, $logs, 'permission_code')
            || !setup_column_exists($pdo, $logs, 'route_key')
            || !setup_column_exists($pdo, $logs, 'created_at')) return false;

        $codes = [
            'patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake',
            'patients.medical.view','patients.medical.verify','patients.financial.view','patients.portal.manage',
        ];
        $placeholders = implode(',', array_fill(0, count($codes), '?'));
        $permissionCheck = $pdo->prepare('SELECT COUNT(DISTINCT code) FROM `' . table('permissions') . '` WHERE code IN (' . $placeholders . ')');
        $permissionCheck->execute($codes);
        if ((int)$permissionCheck->fetchColumn() !== count($codes)) return false;

        return (string)setting_get('system', 'granular_patient_permissions_v0904_ready', '0') === '1'
            && (string)setting_get('system', 'global_search_minimum_necessary_v0904_ready', '0') === '1'
            && (string)setting_get('system', 'independent_approval_guard_v0904_ready', '0') === '1'
            && (string)setting_get('system', 'denied_access_logging_v0904_ready', '0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0904(PDO $pdo): void
{
    if (!setup_v0903_ready($pdo)) setup_apply_v0903($pdo);
    if (!setup_v0903_ready($pdo)) {
        throw new RuntimeException('The v0.9.0.3 MFA, session-security, and staffing baseline is incomplete.');
    }

    $logs = table('access_decision_logs');
    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $logs . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `user_id` BIGINT UNSIGNED NULL,
        `branch_id` BIGINT UNSIGNED NULL,
        `actual_role` VARCHAR(40) NOT NULL DEFAULT "",
        `active_role` VARCHAR(40) NOT NULL DEFAULT "",
        `decision` VARCHAR(30) NOT NULL,
        `permission_code` VARCHAR(120) NULL,
        `route_key` VARCHAR(120) NULL,
        `entity_type` VARCHAR(80) NULL,
        `entity_id` VARCHAR(80) NULL,
        `reason` VARCHAR(500) NULL,
        `ip_address` VARCHAR(64) NULL,
        `user_agent` VARCHAR(500) NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        KEY `idx_access_user_created` (`user_id`,`created_at`),
        KEY `idx_access_decision_created` (`decision`,`created_at`),
        KEY `idx_access_permission_created` (`permission_code`,`created_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    setup_add_column($pdo, $logs, 'user_id', 'BIGINT UNSIGNED NULL AFTER `id`');
    setup_add_column($pdo, $logs, 'branch_id', 'BIGINT UNSIGNED NULL AFTER `user_id`');
    setup_add_column($pdo, $logs, 'actual_role', 'VARCHAR(40) NOT NULL DEFAULT "" AFTER `branch_id`');
    setup_add_column($pdo, $logs, 'active_role', 'VARCHAR(40) NOT NULL DEFAULT "" AFTER `actual_role`');
    setup_add_column($pdo, $logs, 'decision', 'VARCHAR(30) NOT NULL DEFAULT "denied" AFTER `active_role`');
    setup_add_column($pdo, $logs, 'permission_code', 'VARCHAR(120) NULL AFTER `decision`');
    setup_add_column($pdo, $logs, 'route_key', 'VARCHAR(120) NULL AFTER `permission_code`');
    setup_add_column($pdo, $logs, 'entity_type', 'VARCHAR(80) NULL AFTER `route_key`');
    setup_add_column($pdo, $logs, 'entity_id', 'VARCHAR(80) NULL AFTER `entity_type`');
    setup_add_column($pdo, $logs, 'reason', 'VARCHAR(500) NULL AFTER `entity_id`');
    setup_add_column($pdo, $logs, 'ip_address', 'VARCHAR(64) NULL AFTER `reason`');
    setup_add_column($pdo, $logs, 'user_agent', 'VARCHAR(500) NULL AFTER `ip_address`');
    setup_add_column($pdo, $logs, 'created_at', 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `user_agent`');
    foreach ([
        'idx_access_user_created' => '(`user_id`,`created_at`)',
        'idx_access_decision_created' => '(`decision`,`created_at`)',
        'idx_access_permission_created' => '(`permission_code`,`created_at`)',
    ] as $index => $columns) {
        if (!setup_index_exists($pdo, $logs, $index)) {
            try { $pdo->exec('ALTER TABLE `' . $logs . '` ADD KEY `' . $index . '` ' . $columns); }
            catch (Throwable $exception) { error_log('v0.9.0.4 access-decision index repair skipped: ' . $exception->getMessage()); }
        }
    }

    $permissions = [
        ['patients.identity.view','View patient identity','Patient Care','View patient number, name, date of birth, sex, and photograph.'],
        ['patients.contact.view','View patient contact details','Patient Care','View telephone, email, address, and emergency-contact details.'],
        ['patients.safety_alert.view','View patient dental-safety alert flag','Patient Care','See that a dental treatment safety alert exists without exposing detailed health information.'],
        ['patients.medical.intake','Record patient-reported dental-safety history','Patient Care','Record an unverified patient-reported health update that may affect dental care for dentist review.'],
        ['patients.medical.view','View health information for dental care','Clinical','View allergies, medicines, conditions, alerts, and history versions relevant to dental care.'],
        ['patients.medical.verify','Verify dental-safety history and alerts','Clinical','Verify health information relevant to dental care and activate structured dental safety alerts.'],
        ['patients.financial.view','View patient financial records','Finance','View invoices, payments, balances, adjustments, and insurance details.'],
        ['patients.portal.manage','Manage patient portal access','Patient Care','Invite, link, review, or revoke patient and guardian portal access.'],
    ];
    $permissionInsert = $pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
    foreach ($permissions as $permission) $permissionInsert->execute($permission);

    $grants = [
        'clinic_admin' => ['patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.view','patients.financial.view','patients.portal.manage'],
        'receptionist' => ['patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.portal.manage'],
        'staff' => ['patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.financial.view','patients.portal.manage'],
        'dentist' => ['patients.identity.view','patients.safety_alert.view','patients.medical.view','patients.medical.verify'],
        'dental_assistant' => ['patients.identity.view','patients.safety_alert.view','patients.medical.intake','patients.medical.view'],
        'cashier' => ['patients.identity.view','patients.financial.view'],
    ];
    $denies = [
        'clinic_admin' => ['patients.medical.intake','patients.medical.verify'],
        'receptionist' => ['patients.medical.view','patients.medical.verify','patients.financial.view'],
        'staff' => ['patients.medical.view','patients.medical.verify'],
        'dentist' => ['patients.contact.view','patients.medical.intake','patients.financial.view','patients.portal.manage'],
        'dental_assistant' => ['patients.contact.view','patients.medical.verify','patients.financial.view','patients.portal.manage'],
        'cashier' => ['patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.medical.view','patients.medical.verify','patients.portal.manage'],
        'inventory_officer' => ['patients.identity.view','patients.contact.view','patients.safety_alert.view','patients.medical.intake','patients.medical.view','patients.medical.verify','patients.financial.view','patients.portal.manage'],
    ];
    $roleIds = [];
    foreach ($pdo->query('SELECT id,code FROM `' . table('roles') . '`') as $row) $roleIds[(string)$row['code']] = (int)$row['id'];
    $permissionIds = [];
    foreach ($pdo->query('SELECT id,code FROM `' . table('permissions') . '`') as $row) $permissionIds[(string)$row['code']] = (int)$row['id'];
    $assign = $pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) VALUES (?,?,?) ON DUPLICATE KEY UPDATE allowed=VALUES(allowed)');
    foreach ($grants as $role => $codes) {
        if (!isset($roleIds[$role])) continue;
        foreach ($codes as $code) if (isset($permissionIds[$code])) $assign->execute([$roleIds[$role], $permissionIds[$code], 1]);
    }
    foreach ($denies as $role => $codes) {
        if (!isset($roleIds[$role])) continue;
        foreach ($codes as $code) if (isset($permissionIds[$code])) $assign->execute([$roleIds[$role], $permissionIds[$code], 0]);
    }

    $now = date('c');
    setting_set('system', 'application_version', '0.9.0.4');
    setting_set('system', 'granular_patient_permissions_v0904_ready', '1');
    setting_set('system', 'global_search_minimum_necessary_v0904_ready', '1');
    setting_set('system', 'independent_approval_guard_v0904_ready', '1');
    setting_set('system', 'denied_access_logging_v0904_ready', '1');
    setting_set('system', 'last_update_completed_at', $now);
    setting_set('system', 'release_candidate_required_since', $now);
    setting_set('system', 'release_candidate_version', '0.9.0.4');
    setting_set('production_acceptance', 'release_candidate_version', '0.9.0.4');
    setting_set('production_acceptance', 'release_candidate_approval', '');
    setting_set('production_acceptance', 'release_candidate_status', 'pending');
    setting_set('production_acceptance', 'automated_validation_evidence', '');
    setting_set('production_acceptance', 'permission_matrix_reviewed_at', '');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo, '0.9.0.4', 'authorization_minimum_necessary_access_and_independent_approvals');
    try {
        audit('system_update_applied', 'system', null, ['version'=>'0.9.0.4','features'=>[
            'granular_patient_data_permissions',
            'minimum_necessary_global_search',
            'role_specific_patient_workspace_projection',
            'fail_closed_patient_list_scope',
            'central_independent_approval_guard',
            'denied_access_decision_log',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.0.4 migration audit write skipped after successful state application: ' . $exception->getMessage());
    }
}


function setup_v0905_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.0.5', '>=')) return false;
        if (!setup_v0904_ready($pdo)) return false;
        $sessions = table('administrative_break_glass_sessions');
        $events = table('administrative_break_glass_events');
        $notifications = table('administrative_break_glass_notifications');
        if (!setup_table_exists($pdo,$sessions) || !setup_table_exists($pdo,$events) || !setup_table_exists($pdo,$notifications)) return false;
        foreach (['user_id','branch_id','status','reason_code','reason_text','scope_permissions_json','entity_type','entity_id','token_hash','user_agent_hash','mfa_verified_at','started_at','expires_at','review_due_at','reviewer_user_id','review_outcome','reviewed_at','created_at','updated_at'] as $column) {
            if (!setup_column_exists($pdo,$sessions,$column)) return false;
        }
        foreach (['session_id','user_id','event_type','permission_code','route_key','entity_type','entity_id','details_json','created_at'] as $column) {
            if (!setup_column_exists($pdo,$events,$column)) return false;
        }
        foreach (['session_id','reviewer_user_id','channel','status','acknowledged_at','created_at'] as $column) {
            if (!setup_column_exists($pdo,$notifications,$column)) return false;
        }
        $stmt = $pdo->prepare('SELECT COUNT(DISTINCT code) FROM `' . table('permissions') . '` WHERE code IN (?,?)');
        $stmt->execute(['security.break_glass.manage','security.break_glass.review']);
        if ((int)$stmt->fetchColumn() !== 2) return false;
        return (string)setting_get('system','controlled_break_glass_v0905_ready','0') === '1'
            && (string)setting_get('system','superuser_task_isolation_v0905_ready','0') === '1'
            && (string)setting_get('system','hard_schedule_conflicts_unbypassable_v0905_ready','0') === '1'
            && (string)setting_get('system','break_glass_review_required_v0905_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0905(PDO $pdo): void
{
    if (!setup_v0904_ready($pdo)) setup_apply_v0904($pdo);
    if (!setup_v0904_ready($pdo)) throw new RuntimeException('The v0.9.0.4 authorization and minimum-necessary access baseline is incomplete.');

    $sessions = table('administrative_break_glass_sessions');
    $events = table('administrative_break_glass_events');
    $notifications = table('administrative_break_glass_notifications');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $sessions . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `user_id` BIGINT UNSIGNED NOT NULL,
        `branch_id` BIGINT UNSIGNED NULL,
        `status` VARCHAR(30) NOT NULL DEFAULT "active",
        `reason_code` VARCHAR(80) NOT NULL,
        `reason_text` TEXT NOT NULL,
        `scope_permissions_json` TEXT NOT NULL,
        `entity_type` VARCHAR(80) NULL,
        `entity_id` VARCHAR(80) NULL,
        `token_hash` CHAR(64) NOT NULL,
        `user_agent_hash` CHAR(64) NULL,
        `mfa_verified_at` DATETIME NOT NULL,
        `started_at` DATETIME NOT NULL,
        `expires_at` DATETIME NOT NULL,
        `ended_at` DATETIME NULL,
        `ended_reason` VARCHAR(190) NULL,
        `review_due_at` DATETIME NOT NULL,
        `reviewer_user_id` BIGINT UNSIGNED NULL,
        `review_outcome` VARCHAR(30) NULL,
        `review_notes` TEXT NULL,
        `reviewed_at` DATETIME NULL,
        `created_ip` VARCHAR(64) NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_break_glass_token` (`token_hash`),
        KEY `idx_break_glass_user_status` (`user_id`,`status`,`expires_at`),
        KEY `idx_break_glass_review` (`status`,`review_due_at`),
        KEY `idx_break_glass_branch` (`branch_id`,`started_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    foreach ([
        'user_id'=>'BIGINT UNSIGNED NOT NULL AFTER `id`','branch_id'=>'BIGINT UNSIGNED NULL AFTER `user_id`','status'=>'VARCHAR(30) NOT NULL DEFAULT "active" AFTER `branch_id`',
        'reason_code'=>'VARCHAR(80) NOT NULL DEFAULT "system_recovery" AFTER `status`','reason_text'=>'TEXT NULL AFTER `reason_code`','scope_permissions_json'=>'TEXT NULL AFTER `reason_text`',
        'entity_type'=>'VARCHAR(80) NULL AFTER `scope_permissions_json`','entity_id'=>'VARCHAR(80) NULL AFTER `entity_type`','token_hash'=>'CHAR(64) NULL AFTER `entity_id`',
        'user_agent_hash'=>'CHAR(64) NULL AFTER `token_hash`','mfa_verified_at'=>'DATETIME NULL AFTER `user_agent_hash`','started_at'=>'DATETIME NULL AFTER `mfa_verified_at`',
        'expires_at'=>'DATETIME NULL AFTER `started_at`','ended_at'=>'DATETIME NULL AFTER `expires_at`','ended_reason'=>'VARCHAR(190) NULL AFTER `ended_at`',
        'review_due_at'=>'DATETIME NULL AFTER `ended_reason`','reviewer_user_id'=>'BIGINT UNSIGNED NULL AFTER `review_due_at`','review_outcome'=>'VARCHAR(30) NULL AFTER `reviewer_user_id`',
        'review_notes'=>'TEXT NULL AFTER `review_outcome`','reviewed_at'=>'DATETIME NULL AFTER `review_notes`','created_ip'=>'VARCHAR(64) NULL AFTER `reviewed_at`',
        'created_at'=>'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `created_ip`','updated_at'=>'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `created_at`',
    ] as $column=>$definition) setup_add_column($pdo,$sessions,$column,$definition);
    foreach ([
        'uq_break_glass_token'=>'UNIQUE (`token_hash`)','idx_break_glass_user_status'=>'(`user_id`,`status`,`expires_at`)',
        'idx_break_glass_review'=>'(`status`,`review_due_at`)','idx_break_glass_branch'=>'(`branch_id`,`started_at`)',
    ] as $index=>$definition) if (!setup_index_exists($pdo,$sessions,$index)) {
        try { $pdo->exec('ALTER TABLE `' . $sessions . '` ADD ' . (str_starts_with($definition,'UNIQUE') ? 'UNIQUE KEY' : 'KEY') . ' `' . $index . '` ' . preg_replace('/^UNIQUE\s+/', '', $definition)); }
        catch (Throwable $exception) { error_log('v0.9.0.5 session index repair skipped: ' . $exception->getMessage()); }
    }

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $events . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `session_id` BIGINT UNSIGNED NOT NULL,
        `user_id` BIGINT UNSIGNED NULL,
        `event_type` VARCHAR(80) NOT NULL,
        `permission_code` VARCHAR(120) NULL,
        `route_key` VARCHAR(120) NULL,
        `entity_type` VARCHAR(80) NULL,
        `entity_id` VARCHAR(80) NULL,
        `details_json` LONGTEXT NULL,
        `ip_address` VARCHAR(64) NULL,
        `user_agent` VARCHAR(500) NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        KEY `idx_break_glass_event_session` (`session_id`,`created_at`),
        KEY `idx_break_glass_event_permission` (`permission_code`,`created_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
    foreach ([
        'session_id'=>'BIGINT UNSIGNED NOT NULL AFTER `id`','user_id'=>'BIGINT UNSIGNED NULL AFTER `session_id`','event_type'=>'VARCHAR(80) NOT NULL DEFAULT "permission_used" AFTER `user_id`',
        'permission_code'=>'VARCHAR(120) NULL AFTER `event_type`','route_key'=>'VARCHAR(120) NULL AFTER `permission_code`','entity_type'=>'VARCHAR(80) NULL AFTER `route_key`',
        'entity_id'=>'VARCHAR(80) NULL AFTER `entity_type`','details_json'=>'LONGTEXT NULL AFTER `entity_id`','ip_address'=>'VARCHAR(64) NULL AFTER `details_json`',
        'user_agent'=>'VARCHAR(500) NULL AFTER `ip_address`','created_at'=>'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `user_agent`',
    ] as $column=>$definition) setup_add_column($pdo,$events,$column,$definition);
    foreach (['idx_break_glass_event_session'=>'(`session_id`,`created_at`)','idx_break_glass_event_permission'=>'(`permission_code`,`created_at`)'] as $index=>$definition) if (!setup_index_exists($pdo,$events,$index)) {
        try { $pdo->exec('ALTER TABLE `' . $events . '` ADD KEY `' . $index . '` ' . $definition); } catch (Throwable $exception) { error_log('v0.9.0.5 event index repair skipped: ' . $exception->getMessage()); }
    }

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $notifications . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `session_id` BIGINT UNSIGNED NOT NULL,
        `reviewer_user_id` BIGINT UNSIGNED NOT NULL,
        `channel` VARCHAR(40) NOT NULL DEFAULT "in_app",
        `status` VARCHAR(30) NOT NULL DEFAULT "pending",
        `queued_email_id` BIGINT UNSIGNED NULL,
        `last_error` VARCHAR(500) NULL,
        `acknowledged_at` DATETIME NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_break_glass_reviewer` (`session_id`,`reviewer_user_id`),
        KEY `idx_break_glass_notification_review` (`reviewer_user_id`,`status`,`created_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
    foreach ([
        'session_id'=>'BIGINT UNSIGNED NOT NULL AFTER `id`','reviewer_user_id'=>'BIGINT UNSIGNED NOT NULL AFTER `session_id`','channel'=>'VARCHAR(40) NOT NULL DEFAULT "in_app" AFTER `reviewer_user_id`',
        'status'=>'VARCHAR(30) NOT NULL DEFAULT "pending" AFTER `channel`','queued_email_id'=>'BIGINT UNSIGNED NULL AFTER `status`','last_error'=>'VARCHAR(500) NULL AFTER `queued_email_id`',
        'acknowledged_at'=>'DATETIME NULL AFTER `last_error`','created_at'=>'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `acknowledged_at`','updated_at'=>'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `created_at`',
    ] as $column=>$definition) setup_add_column($pdo,$notifications,$column,$definition);
    foreach (['uq_break_glass_reviewer'=>'UNIQUE (`session_id`,`reviewer_user_id`)','idx_break_glass_notification_review'=>'(`reviewer_user_id`,`status`,`created_at`)'] as $index=>$definition) if (!setup_index_exists($pdo,$notifications,$index)) {
        try { $pdo->exec('ALTER TABLE `' . $notifications . '` ADD ' . (str_starts_with($definition,'UNIQUE') ? 'UNIQUE KEY' : 'KEY') . ' `' . $index . '` ' . preg_replace('/^UNIQUE\s+/', '', $definition)); } catch (Throwable $exception) { error_log('v0.9.0.5 notification index repair skipped: ' . $exception->getMessage()); }
    }

    $permissionInsert = $pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
    foreach ([
        ['security.break_glass.manage','Use controlled emergency administrative access','Security','Start, end, and view a time-limited MFA re-authenticated emergency-access session.'],
        ['security.break_glass.review','Review controlled emergency access','Security','Independently review or revoke an assigned emergency-access session.'],
    ] as $permission) $permissionInsert->execute($permission);

    $roleIds=[]; foreach ($pdo->query('SELECT id,code FROM `' . table('roles') . '`') as $row) $roleIds[(string)$row['code']] = (int)$row['id'];
    $permissionIds=[]; foreach ($pdo->query('SELECT id,code FROM `' . table('permissions') . '` WHERE code IN ("security.break_glass.manage","security.break_glass.review")') as $row) $permissionIds[(string)$row['code']] = (int)$row['id'];
    $assign=$pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) VALUES (?,?,1) ON DUPLICATE KEY UPDATE allowed=1');
    foreach (['super_admin','clinic_admin'] as $role) foreach (['security.break_glass.manage','security.break_glass.review'] as $code) if (isset($roleIds[$role],$permissionIds[$code])) $assign->execute([$roleIds[$role],$permissionIds[$code]]);

    $now=date('c');
    setting_set('system','application_version','0.9.0.5');
    setting_set('system','controlled_break_glass_v0905_ready','1');
    setting_set('system','superuser_task_isolation_v0905_ready','1');
    setting_set('system','hard_schedule_conflicts_unbypassable_v0905_ready','1');
    setting_set('system','break_glass_review_required_v0905_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.0.5');
    setting_set('production_acceptance','release_candidate_version','0.9.0.5');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.0.5','controlled_break_glass_superuser_isolation');
    try {
        audit('system_update_applied','system',null,['version'=>'0.9.0.5','features'=>[
            'superuser_task_view_server_isolation','mfa_reauthenticated_break_glass','branch_permission_entity_scope',
            'automatic_expiry','independent_reviewer_notification','retrospective_review','protected_break_glass_event_trail',
            'hard_schedule_resource_conflicts_unbypassable',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.0.5 migration audit write skipped after successful state application: ' . $exception->getMessage());
    }
}


function setup_v0906_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.0.6', '>=')) return false;
        if (!setup_v0905_ready($pdo)) return false;
        $audit = table('audit_logs');
        foreach (['chain_key','chain_sequence','previous_hash','integrity_version'] as $column) {
            if (!setup_column_exists($pdo,$audit,$column)) return false;
        }
        foreach ([
            table('audit_chain_heads'),table('audit_integrity_snapshots'),
            table('privacy_processing_activities'),table('privacy_rights_requests'),
            table('privacy_incidents'),table('privacy_retention_rules'),
        ] as $requiredTable) if (!setup_table_exists($pdo,$requiredTable)) return false;
        $codes=['audit.integrity.verify','audit.export','privacy.governance.manage','privacy.requests.manage','privacy.incidents.manage'];
        $in=implode(',',array_fill(0,count($codes),'?'));
        $stmt=$pdo->prepare('SELECT COUNT(DISTINCT code) FROM `' . table('permissions') . '` WHERE code IN (' . $in . ')');
        $stmt->execute($codes);
        if ((int)$stmt->fetchColumn() !== count($codes)) return false;
        return (string)setting_get('system','tamper_evident_audit_chain_v0906_ready','0') === '1'
            && (string)setting_get('system','privacy_governance_center_v0906_ready','0') === '1'
            && (string)setting_get('system','privacy_incident_rights_register_v0906_ready','0') === '1'
            && (string)setting_get('system','audit_integrity_snapshot_v0906_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0906(PDO $pdo): void
{
    if (!setup_v0905_ready($pdo)) setup_apply_v0905($pdo);
    if (!setup_v0905_ready($pdo)) throw new RuntimeException('The v0.9.0.5 controlled-access baseline is incomplete.');

    $audit = table('audit_logs');
    setup_add_column($pdo,$audit,'chain_key','VARCHAR(100) NULL AFTER `branch_id`');
    setup_add_column($pdo,$audit,'chain_sequence','BIGINT UNSIGNED NULL AFTER `chain_key`');
    setup_add_column($pdo,$audit,'previous_hash','CHAR(64) NULL AFTER `chain_sequence`');
    setup_add_column($pdo,$audit,'integrity_version','SMALLINT UNSIGNED NOT NULL DEFAULT 1 AFTER `previous_hash`');
    foreach ([
        'uq_audit_chain_sequence'=>'UNIQUE (`chain_key`,`chain_sequence`)',
        'idx_audit_chain_id'=>'(`chain_key`,`id`)',
    ] as $index=>$definition) if (!setup_index_exists($pdo,$audit,$index)) {
        try {
            $prefix=str_starts_with($definition,'UNIQUE')?'UNIQUE KEY':'KEY';
            $body=preg_replace('/^UNIQUE\s+/', '', $definition);
            $pdo->exec('ALTER TABLE `' . $audit . '` ADD ' . $prefix . ' `' . $index . '` ' . $body);
        } catch (Throwable $exception) {
            error_log('v0.9.0.6 audit index repair skipped: ' . $exception->getMessage());
        }
    }

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('audit_chain_heads') . '` (
        `chain_key` VARCHAR(100) NOT NULL,
        `last_sequence` BIGINT UNSIGNED NOT NULL DEFAULT 0,
        `last_audit_id` BIGINT UNSIGNED NULL,
        `last_hash` CHAR(64) NOT NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`chain_key`),
        KEY `idx_audit_chain_head_id` (`last_audit_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('audit_integrity_snapshots') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `status` VARCHAR(30) NOT NULL,
        `checked_rows` INT UNSIGNED NOT NULL DEFAULT 0,
        `valid_rows` INT UNSIGNED NOT NULL DEFAULT 0,
        `invalid_rows` INT UNSIGNED NOT NULL DEFAULT 0,
        `unhashed_rows` INT UNSIGNED NOT NULL DEFAULT 0,
        `broken_links` INT UNSIGNED NOT NULL DEFAULT 0,
        `head_mismatches` INT UNSIGNED NOT NULL DEFAULT 0,
        `latest_audit_id` BIGINT UNSIGNED NULL,
        `tip_hash` CHAR(64) NULL,
        `summary_json` LONGTEXT NULL,
        `verified_by` BIGINT UNSIGNED NULL,
        `verified_at` DATETIME NOT NULL,
        `anchor_reference` VARCHAR(255) NULL,
        `anchored_at` DATETIME NULL,
        PRIMARY KEY (`id`),
        KEY `idx_audit_snapshot_status` (`status`,`verified_at`),
        KEY `idx_audit_snapshot_tip` (`tip_hash`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('privacy_processing_activities') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `branch_id` BIGINT UNSIGNED NULL,
        `title` VARCHAR(190) NOT NULL,
        `purpose` TEXT NOT NULL,
        `data_categories` TEXT NULL,
        `data_subjects` TEXT NULL,
        `legal_basis` VARCHAR(500) NOT NULL,
        `recipients` TEXT NULL,
        `international_transfer` TEXT NULL,
        `safeguards` TEXT NULL,
        `status` VARCHAR(30) NOT NULL DEFAULT "active",
        `owner_user_id` BIGINT UNSIGNED NULL,
        `review_due_at` DATE NULL,
        `created_by` BIGINT UNSIGNED NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        KEY `idx_privacy_processing_branch` (`branch_id`,`status`),
        KEY `idx_privacy_processing_review` (`review_due_at`,`status`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('privacy_retention_rules') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `branch_id` BIGINT UNSIGNED NULL,
        `record_category` VARCHAR(190) NOT NULL,
        `retention_months` INT UNSIGNED NOT NULL,
        `trigger_event` VARCHAR(255) NOT NULL,
        `legal_basis` VARCHAR(500) NULL,
        `disposition_action` VARCHAR(255) NOT NULL,
        `legal_hold_allowed` TINYINT(1) NOT NULL DEFAULT 1,
        `is_active` TINYINT(1) NOT NULL DEFAULT 1,
        `review_due_at` DATE NULL,
        `created_by` BIGINT UNSIGNED NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        KEY `idx_privacy_retention_branch` (`branch_id`,`is_active`),
        KEY `idx_privacy_retention_review` (`review_due_at`,`is_active`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('privacy_rights_requests') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `branch_id` BIGINT UNSIGNED NULL,
        `request_reference` VARCHAR(80) NOT NULL,
        `patient_id` BIGINT UNSIGNED NULL,
        `requester_name` VARCHAR(190) NOT NULL,
        `requester_contact` VARCHAR(255) NULL,
        `request_type` VARCHAR(60) NOT NULL,
        `received_at` DATE NOT NULL,
        `due_at` DATE NOT NULL,
        `status` VARCHAR(40) NOT NULL DEFAULT "received",
        `identity_verification_status` VARCHAR(40) NOT NULL DEFAULT "pending",
        `identity_verified_at` DATETIME NULL,
        `assigned_user_id` BIGINT UNSIGNED NULL,
        `notes` TEXT NULL,
        `outcome_summary` TEXT NULL,
        `completed_at` DATETIME NULL,
        `created_by` BIGINT UNSIGNED NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_privacy_request_reference` (`request_reference`),
        KEY `idx_privacy_request_branch_status` (`branch_id`,`status`,`due_at`),
        KEY `idx_privacy_request_patient` (`patient_id`,`received_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('privacy_incidents') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `branch_id` BIGINT UNSIGNED NULL,
        `incident_reference` VARCHAR(80) NOT NULL,
        `title` VARCHAR(255) NOT NULL,
        `severity` VARCHAR(30) NOT NULL,
        `status` VARCHAR(40) NOT NULL DEFAULT "open",
        `occurred_at` DATETIME NULL,
        `discovered_at` DATETIME NOT NULL,
        `affected_subjects_estimate` INT UNSIGNED NOT NULL DEFAULT 0,
        `data_categories` TEXT NULL,
        `containment_actions` TEXT NULL,
        `risk_assessment` TEXT NULL,
        `notification_required` VARCHAR(20) NOT NULL DEFAULT "unknown",
        `authority_notification_due_at` DATETIME NULL,
        `patient_notification_due_at` DATETIME NULL,
        `assigned_user_id` BIGINT UNSIGNED NULL,
        `closed_at` DATETIME NULL,
        `created_by` BIGINT UNSIGNED NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_privacy_incident_reference` (`incident_reference`),
        KEY `idx_privacy_incident_branch_status` (`branch_id`,`status`,`severity`),
        KEY `idx_privacy_incident_deadline` (`notification_required`,`authority_notification_due_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    $permissionInsert=$pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
    foreach ([
        ['audit.integrity.verify','Verify protected audit-chain integrity','Security','Run and store a tamper-evidence verification snapshot.'],
        ['audit.export','Export audit integrity manifest','Security','Export a minimum-necessary integrity manifest without clinical payloads.'],
        ['privacy.governance.manage','Manage privacy processing and retention','Privacy','Maintain processing activities, legal bases, recipients, transfers, safeguards, and retention rules.'],
        ['privacy.requests.manage','Manage privacy-rights requests','Privacy','Track identity verification, due dates, decisions, and outcomes for individual-rights requests.'],
        ['privacy.incidents.manage','Manage privacy incidents','Privacy','Record containment, risk assessment, notification decisions, deadlines, and closure.'],
    ] as $permission) $permissionInsert->execute($permission);

    $roleIds=[]; foreach ($pdo->query('SELECT id,code FROM `' . table('roles') . '`') as $row) $roleIds[(string)$row['code']] = (int)$row['id'];
    $permissionIds=[]; foreach ($pdo->query('SELECT id,code FROM `' . table('permissions') . '`') as $row) $permissionIds[(string)$row['code']] = (int)$row['id'];
    $assign=$pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) VALUES (?,?,1) ON DUPLICATE KEY UPDATE allowed=1');
    foreach (['super_admin','clinic_admin'] as $role) foreach (['audit.integrity.verify','audit.export','privacy.governance.manage','privacy.requests.manage','privacy.incidents.manage'] as $code) {
        if (isset($roleIds[$role],$permissionIds[$code])) $assign->execute([$roleIds[$role],$permissionIds[$code]]);
    }

    $now=date('c');
    setting_set('system','application_version','0.9.0.6');
    setting_set('system','tamper_evident_audit_chain_v0906_ready','1');
    setting_set('system','privacy_governance_center_v0906_ready','1');
    setting_set('system','privacy_incident_rights_register_v0906_ready','1');
    setting_set('system','audit_integrity_snapshot_v0906_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.0.6');
    setting_set('production_acceptance','release_candidate_version','0.9.0.6');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.0.6','tamper_evident_audit_privacy_governance');
    audit_required('system_update_applied','system',null,['version'=>'0.9.0.6','features'=>[
        'branch_scoped_hash_chained_audit_records','serialized_audit_chain_heads','integrity_verification_snapshots',
        'minimum_necessary_audit_integrity_export','processing_activity_register','retention_schedule',
        'privacy_rights_request_register','privacy_incident_and_notification_deadline_register',
    ]]);
}


function setup_v0907_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.0.7', '>=')) return false;
        if (!setup_v0906_ready($pdo)) return false;
        foreach ([
            table('patient_allergies'),table('patient_medications'),table('patient_conditions'),
            table('medicine_safety_profiles'),table('prescription_safety_assessments'),
            table('prescription_safety_findings'),table('clinical_safety_events'),
        ] as $requiredTable) if (!setup_table_exists($pdo,$requiredTable)) return false;
        foreach (['safety_assessment_id','safety_status','safety_override_reason'] as $column) {
            if (!setup_column_exists($pdo,table('prescriptions'),$column)) return false;
        }
        foreach ([
            [table('patient_allergies'),'normalized_substance'],
            [table('patient_medications'),'normalized_medication'],
            [table('patient_conditions'),'normalized_condition'],
            [table('medicine_safety_profiles'),'evidence_status'],
            [table('prescription_safety_assessments'),'context_hash'],
            [table('prescription_safety_findings'),'resolution_status'],
            [table('clinical_safety_events'),'action_taken'],
        ] as [$tableName,$column]) if (!setup_column_exists($pdo,$tableName,$column)) return false;
        if (setup_table_exists($pdo,table('medicine_catalog'))) {
            $activeCount=(int)$pdo->query('SELECT COUNT(*) FROM `' . table('medicine_catalog') . '` WHERE is_active=1')->fetchColumn();
            if ($activeCount>0) {
                $profileCount=(int)$pdo->query('SELECT COUNT(*) FROM `' . table('medicine_safety_profiles') . '` WHERE is_active=1')->fetchColumn();
                if ($profileCount<1) return false;
            }
        }
        return (string)setting_get('system','structured_clinical_safety_v0907_ready','0') === '1'
            && (string)setting_get('system','medication_safety_review_v0907_ready','0') === '1'
            && (string)setting_get('system','prescription_hard_stop_v0907_ready','0') === '1'
            && (string)setting_get('system','clinical_safety_events_v0907_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0907(PDO $pdo): void
{
    if (!setup_v0906_ready($pdo)) setup_apply_v0906($pdo);
    if (!setup_v0906_ready($pdo)) throw new RuntimeException('The v0.9.0.6 audit and privacy baseline is incomplete.');

    $create = static function (PDO $pdo, string $logicalTable, string $definition): void {
        $sql=str_replace('{{table}}',table($logicalTable),$definition);
        $pdo->exec($sql);
    };

    $create($pdo,'patient_allergies',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `patient_id` BIGINT UNSIGNED NOT NULL,
 `medical_history_id` BIGINT UNSIGNED NULL,
 `substance_name` VARCHAR(190) NOT NULL,
 `normalized_substance` VARCHAR(190) NOT NULL,
 `reaction` VARCHAR(255) NULL,
 `severity` VARCHAR(30) NOT NULL DEFAULT 'unknown',
 `status` VARCHAR(30) NOT NULL DEFAULT 'active',
 `is_current` TINYINT(1) NOT NULL DEFAULT 1,
 `verification_status` VARCHAR(30) NOT NULL DEFAULT 'verified',
 `source_type` VARCHAR(40) NOT NULL DEFAULT 'clinician_verified',
 `recorded_by` BIGINT UNSIGNED NULL,
 `verified_by` BIGINT UNSIGNED NULL,
 `verified_at` DATETIME NULL,
 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_patient_allergy_current` (`patient_id`,`is_current`,`status`),
 KEY `idx_patient_allergy_normalized` (`normalized_substance`,`status`),
 KEY `idx_patient_allergy_history` (`medical_history_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    $create($pdo,'patient_medications',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `patient_id` BIGINT UNSIGNED NOT NULL,
 `medical_history_id` BIGINT UNSIGNED NULL,
 `medication_name` VARCHAR(190) NOT NULL,
 `normalized_medication` VARCHAR(190) NOT NULL,
 `generic_name` VARCHAR(190) NULL,
 `dose_text` VARCHAR(190) NULL,
 `frequency` VARCHAR(100) NULL,
 `route` VARCHAR(80) NULL,
 `reason` VARCHAR(255) NULL,
 `status` VARCHAR(30) NOT NULL DEFAULT 'active',
 `is_current` TINYINT(1) NOT NULL DEFAULT 1,
 `verification_status` VARCHAR(30) NOT NULL DEFAULT 'verified',
 `source_type` VARCHAR(40) NOT NULL DEFAULT 'clinician_verified',
 `recorded_by` BIGINT UNSIGNED NULL,
 `verified_by` BIGINT UNSIGNED NULL,
 `verified_at` DATETIME NULL,
 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_patient_med_current` (`patient_id`,`is_current`,`status`),
 KEY `idx_patient_med_normalized` (`normalized_medication`,`status`),
 KEY `idx_patient_med_history` (`medical_history_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    $create($pdo,'patient_conditions',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `patient_id` BIGINT UNSIGNED NOT NULL,
 `medical_history_id` BIGINT UNSIGNED NULL,
 `condition_name` VARCHAR(190) NOT NULL,
 `normalized_condition` VARCHAR(190) NOT NULL,
 `severity` VARCHAR(30) NOT NULL DEFAULT 'unknown',
 `status` VARCHAR(30) NOT NULL DEFAULT 'active',
 `is_current` TINYINT(1) NOT NULL DEFAULT 1,
 `verification_status` VARCHAR(30) NOT NULL DEFAULT 'verified',
 `source_type` VARCHAR(40) NOT NULL DEFAULT 'clinician_verified',
 `recorded_by` BIGINT UNSIGNED NULL,
 `verified_by` BIGINT UNSIGNED NULL,
 `verified_at` DATETIME NULL,
 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_patient_condition_current` (`patient_id`,`is_current`,`status`),
 KEY `idx_patient_condition_normalized` (`normalized_condition`,`status`),
 KEY `idx_patient_condition_history` (`medical_history_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    $create($pdo,'medicine_safety_profiles',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `medicine_catalog_id` BIGINT UNSIGNED NOT NULL,
 `normalized_ingredient` VARCHAR(190) NOT NULL,
 `medication_class` VARCHAR(100) NOT NULL DEFAULT 'unknown',
 `allergy_groups_json` LONGTEXT NULL,
 `interaction_tags_json` LONGTEXT NULL,
 `contraindication_tags_json` LONGTEXT NULL,
 `pregnancy_note` TEXT NULL,
 `renal_note` TEXT NULL,
 `hepatic_note` TEXT NULL,
 `minimum_age_years` DECIMAL(5,2) NULL,
 `evidence_status` VARCHAR(40) NOT NULL DEFAULT 'clinic_curated',
 `source_label` VARCHAR(255) NULL,
 `is_active` TINYINT(1) NOT NULL DEFAULT 1,
 `reviewed_by` BIGINT UNSIGNED NULL,
 `reviewed_at` DATETIME NULL,
 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `uq_medicine_safety_catalog` (`medicine_catalog_id`),
 KEY `idx_medicine_safety_ingredient` (`normalized_ingredient`,`is_active`),
 KEY `idx_medicine_safety_class` (`medication_class`,`is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    $create($pdo,'prescription_safety_assessments',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `prescription_id` BIGINT UNSIGNED NOT NULL,
 `patient_id` BIGINT UNSIGNED NOT NULL,
 `encounter_id` BIGINT UNSIGNED NULL,
 `dentist_user_id` BIGINT UNSIGNED NOT NULL,
 `status` VARCHAR(30) NOT NULL,
 `hard_stop_count` INT UNSIGNED NOT NULL DEFAULT 0,
 `serious_warning_count` INT UNSIGNED NOT NULL DEFAULT 0,
 `advisory_count` INT UNSIGNED NOT NULL DEFAULT 0,
 `data_gap_count` INT UNSIGNED NOT NULL DEFAULT 0,
 `context_hash` CHAR(64) NOT NULL,
 `summary_json` LONGTEXT NULL,
 `override_reason` TEXT NULL,
 `acknowledged_by` BIGINT UNSIGNED NULL,
 `acknowledged_at` DATETIME NULL,
 `assessed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `uq_rx_safety_prescription` (`prescription_id`),
 KEY `idx_rx_safety_patient` (`patient_id`,`assessed_at`),
 KEY `idx_rx_safety_status` (`status`,`assessed_at`),
 KEY `idx_rx_safety_context` (`context_hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    $create($pdo,'prescription_safety_findings',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `assessment_id` BIGINT UNSIGNED NOT NULL,
 `severity` VARCHAR(30) NOT NULL,
 `code` VARCHAR(100) NOT NULL,
 `medicine_index` INT UNSIGNED NOT NULL DEFAULT 0,
 `medicine_name` VARCHAR(190) NULL,
 `title` VARCHAR(255) NOT NULL,
 `detail` TEXT NOT NULL,
 `override_allowed` TINYINT(1) NOT NULL DEFAULT 0,
 `resolution_status` VARCHAR(40) NOT NULL DEFAULT 'not_required',
 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_rx_safety_finding_assessment` (`assessment_id`,`severity`),
 KEY `idx_rx_safety_finding_code` (`code`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
    $create($pdo,'clinical_safety_events',<<<'SQL'
CREATE TABLE IF NOT EXISTS `{{table}}` (
 `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `patient_id` BIGINT UNSIGNED NOT NULL,
 `encounter_id` BIGINT UNSIGNED NULL,
 `prescription_id` BIGINT UNSIGNED NULL,
 `event_type` VARCHAR(80) NOT NULL,
 `severity` VARCHAR(30) NOT NULL,
 `code` VARCHAR(100) NOT NULL,
 `details_json` LONGTEXT NULL,
 `action_taken` VARCHAR(100) NOT NULL,
 `actor_user_id` BIGINT UNSIGNED NULL,
 `reviewed_by` BIGINT UNSIGNED NULL,
 `reviewed_at` DATETIME NULL,
 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_clinical_safety_patient` (`patient_id`,`created_at`),
 KEY `idx_clinical_safety_event` (`event_type`,`severity`,`created_at`),
 KEY `idx_clinical_safety_review` (`reviewed_at`,`severity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);

    setup_add_column($pdo,table('prescriptions'),'safety_assessment_id','BIGINT UNSIGNED NULL AFTER `replaces_prescription_id`');
    setup_add_column($pdo,table('prescriptions'),'safety_status','VARCHAR(30) NOT NULL DEFAULT "pending" AFTER `safety_assessment_id`');
    setup_add_column($pdo,table('prescriptions'),'safety_override_reason','TEXT NULL AFTER `safety_status`');
    foreach ([
        'idx_rx_safety_status'=>'(`safety_status`,`issued_at`)',
        'idx_rx_safety_assessment'=>'(`safety_assessment_id`)',
    ] as $index=>$definition) if (!setup_index_exists($pdo,table('prescriptions'),$index)) {
        try {$pdo->exec('ALTER TABLE `' . table('prescriptions') . '` ADD KEY `' . $index . '` ' . $definition);}
        catch (Throwable $exception) {error_log('v0.9.0.7 prescription safety index repair skipped: '.$exception->getMessage());}
    }

    if (setup_table_exists($pdo,table('medicine_catalog'))) {
        $profileUpsert=$pdo->prepare(
            'INSERT INTO `' . table('medicine_safety_profiles') . '` '
            . '(medicine_catalog_id,normalized_ingredient,medication_class,allergy_groups_json,interaction_tags_json,contraindication_tags_json,pregnancy_note,renal_note,hepatic_note,minimum_age_years,evidence_status,source_label,is_active,reviewed_by,reviewed_at,created_at,updated_at) '
            . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,1,?,NOW(),NOW(),NOW()) '
            . 'ON DUPLICATE KEY UPDATE '
            . 'normalized_ingredient=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(normalized_ingredient),normalized_ingredient),'
            . 'medication_class=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(medication_class),medication_class),'
            . 'allergy_groups_json=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(allergy_groups_json),allergy_groups_json),'
            . 'interaction_tags_json=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(interaction_tags_json),interaction_tags_json),'
            . 'contraindication_tags_json=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(contraindication_tags_json),contraindication_tags_json),'
            . 'pregnancy_note=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(pregnancy_note),pregnancy_note),'
            . 'renal_note=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(renal_note),renal_note),'
            . 'hepatic_note=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(hepatic_note),hepatic_note),'
            . 'minimum_age_years=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(minimum_age_years),minimum_age_years),'
            . 'evidence_status=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(evidence_status),evidence_status),'
            . 'source_label=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(source_label),source_label),'
            . 'is_active=1,reviewed_by=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",VALUES(reviewed_by),reviewed_by),'
            . 'reviewed_at=IF(source_label LIKE "DentalCare clinic-curated%" OR source_label LIKE "Unverified catalog entry%",NOW(),reviewed_at),updated_at=NOW()'
        );
        $catalogRows=$pdo->query('SELECT id,display_name medication_name,generic_name FROM `' . table('medicine_catalog') . '` WHERE is_active=1 ORDER BY id')->fetchAll();
        $reviewer=(int)(current_user()['id']??0) ?: null;
        foreach ($catalogRows as $catalogRow) {
            $profile=clinical_safety_inferred_profile($catalogRow);
            $source=(string)($profile['evidence_status']??'unverified')==='clinic_curated'
                ? 'DentalCare clinic-curated deterministic profile v0.9.0.7'
                : 'Unverified catalog entry; configure an authoritative medication-information source before relying on interaction coverage.';
            $profileReviewer=(string)($profile['evidence_status']??'unverified')==='clinic_curated'?$reviewer:null;
            $profileUpsert->execute([
                (int)$catalogRow['id'],(string)$profile['ingredient'],(string)$profile['medication_class'],
                json_encode($profile['allergy_groups'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
                json_encode($profile['interaction_tags'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
                json_encode($profile['contraindication_tags'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
                (string)$profile['pregnancy_note'] ?: null,(string)$profile['renal_note'] ?: null,(string)$profile['hepatic_note'] ?: null,
                $profile['minimum_age_years'],(string)$profile['evidence_status'],$source,$profileReviewer,
            ]);
        }
    }

    $now=date('c');
    setting_set('system','application_version','0.9.0.7');
    setting_set('system','structured_clinical_safety_v0907_ready','1');
    setting_set('system','medication_safety_review_v0907_ready','1');
    setting_set('system','prescription_hard_stop_v0907_ready','1');
    setting_set('system','clinical_safety_events_v0907_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.0.7');
    setting_set('production_acceptance','release_candidate_version','0.9.0.7');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.0.7','clinical_safety_medication_review');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.0.7','features'=>[
            'structured_verified_allergy_medication_condition_records','clinic_curated_medication_safety_profiles',
            'prescription_hard_stops','serious_warning_acknowledgement_and_justification','medication_safety_data_gap_labels',
            'prescription_safety_assessment_evidence','clinical_safety_block_events',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.0.7 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}


function setup_v0908_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.0.8', '>=')) return false;
        if (!setup_v0907_ready($pdo)) return false;
        return (string)setting_get('system','patient_context_isolation_v0908_ready','0') === '1'
            && (string)setting_get('system','completed_visit_context_cleanup_v0908_ready','0') === '1'
            && (string)setting_get('system','dental_scope_labels_v0908_ready','0') === '1'
            && (string)setting_get('system','clinical_scope','') === 'dental_facility_only';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0908(PDO $pdo): void
{
    if (!setup_v0907_ready($pdo)) setup_apply_v0907($pdo);
    if (!setup_v0907_ready($pdo)) throw new RuntimeException('The v0.9.0.7 dental prescription safety baseline is incomplete.');

    // Keep the established permission codes for backward compatibility while
    // making their displayed scope explicitly dental-facility focused.
    if (setup_table_exists($pdo, table('permissions'))) {
        $permissions = [
            ['patients.safety_alert.view','View patient dental-safety alert flag','Patient Care','See that a dental treatment safety alert exists without exposing detailed health information.'],
            ['patients.medical.intake','Record patient-reported dental-safety history','Patient Care','Record an unverified patient-reported health update that may affect dental care for dentist review.'],
            ['patients.medical.view','View health information for dental care','Clinical','View allergies, medicines, conditions, alerts, and history versions relevant to dental care.'],
            ['patients.medical.verify','Verify dental-safety history and alerts','Clinical','Verify health information relevant to dental care and activate structured dental safety alerts.'],
        ];
        $permissionInsert = $pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
        foreach ($permissions as $permission) $permissionInsert->execute($permission);
    }

    $now = date('c');
    setting_set('system','application_version','0.9.0.8');
    setting_set('system','patient_context_isolation_v0908_ready','1');
    setting_set('system','completed_visit_context_cleanup_v0908_ready','1');
    setting_set('system','dental_scope_labels_v0908_ready','1');
    setting_set('system','clinical_scope','dental_facility_only');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.0.8');
    setting_set('production_acceptance','release_candidate_version','0.9.0.8');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');

    // Clear only the current request's legacy pinned patient. Future requests
    // persist patient context solely for a real queue, encounter, or Cashier task.
    unset($_SESSION['dental_patient_context'], $_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.0.8','patient_context_isolation_dental_scope');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.0.8','features'=>[
            'viewed_patient_separated_from_active_visit_context',
            'completed_visit_context_auto_cleanup',
            'dental_facility_only_clinical_scope',
            'dental_safety_user_interface_labels',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.0.8 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}

function setup_v0909_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.0.9', '>=')) return false;
        if (!setup_v0908_ready($pdo)) return false;
        return setup_column_exists($pdo, table('users'), 'employee_no')
            && setup_column_exists($pdo, table('users'), 'job_title')
            && (string)setting_get('system','profile_center_v0909_ready','0') === '1'
            && (string)setting_get('system','staff_identity_single_source_v0909_ready','0') === '1'
            && (string)setting_get('system','staff_work_assignment_boundary_v0909_ready','0') === '1'
            && (string)setting_get('system','dentist_professional_record_boundary_v0909_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0909(PDO $pdo): void
{
    if (!setup_v0908_ready($pdo)) setup_apply_v0908($pdo);
    if (!setup_v0908_ready($pdo)) throw new RuntimeException('The v0.9.0.8 patient-context and dental-scope baseline is incomplete.');

    setup_add_column($pdo, table('users'), 'employee_no', 'VARCHAR(30) NULL AFTER `role_id`');
    setup_add_column($pdo, table('users'), 'job_title', 'VARCHAR(120) NULL AFTER `contact_number`');

    // Preserve historical professional records while synchronizing the current
    // Dentist display identity from the authoritative staff account record.
    if (setup_table_exists($pdo, table('dentist_profiles'))) {
        $pdo->exec(
            'UPDATE `' . table('dentist_profiles') . '` dp '
            . 'JOIN `' . table('users') . '` u ON u.id=dp.user_id '
            . 'SET dp.display_name=u.name,dp.updated_at=NOW() '
            . 'WHERE COALESCE(dp.display_name,"")<>COALESCE(u.name,"")'
        );
    }

    $now = date('c');
    setting_set('system','application_version','0.9.0.9');
    setting_set('system','profile_center_v0909_ready','1');
    setting_set('system','staff_identity_single_source_v0909_ready','1');
    setting_set('system','staff_work_assignment_boundary_v0909_ready','1');
    setting_set('system','dentist_professional_record_boundary_v0909_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.0.9');
    setting_set('production_acceptance','release_candidate_version','0.9.0.9');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.0.9','unified_profile_center_staff_work_assignment');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.0.9','features'=>[
            'single_staff_identity_profile_center',
            'administrator_controlled_work_assignment',
            'dentist_professional_record_separated_from_personal_identity',
            'activated_staff_identity_edit_boundary',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.0.9 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}


function setup_v0910_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.1.0', '>=')) return false;
        if (!setup_v0909_ready($pdo)) return false;
        return setup_table_exists($pdo, table('dental_safety_assessments'))
            && setup_column_exists($pdo, table('clinical_encounters'), 'dental_safety_assessment_id')
            && setup_column_exists($pdo, table('vital_signs'), 'queue_entry_id')
            && setup_column_exists($pdo, table('vital_signs'), 'dental_safety_assessment_id')
            && (string)setting_get('system','first_seating_dental_safety_v0910_ready','0') === '1'
            && (string)setting_get('system','task_context_isolation_v0910_ready','0') === '1'
            && (string)setting_get('system','multi_role_task_switch_v0910_ready','0') === '1'
            && (string)setting_get('system','treatment_requires_first_seating_v0910_ready','0') === '1'
            && (string)setting_get('system','general_health_services_disabled_v0910_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0910(PDO $pdo): void
{
    if (!setup_v0909_ready($pdo)) setup_apply_v0909($pdo);
    if (!setup_v0909_ready($pdo)) throw new RuntimeException('The v0.9.0.9 unified-profile baseline is incomplete.');

    setup_add_column($pdo, table('clinical_encounters'), 'dental_safety_assessment_id', 'BIGINT UNSIGNED NULL AFTER `queue_entry_id`');
    setup_add_column($pdo, table('vital_signs'), 'queue_entry_id', 'BIGINT UNSIGNED NULL AFTER `encounter_id`');
    setup_add_column($pdo, table('vital_signs'), 'dental_safety_assessment_id', 'BIGINT UNSIGNED NULL AFTER `queue_entry_id`');

    $assessmentTable = table('dental_safety_assessments');
    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $assessmentTable . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `branch_id` BIGINT UNSIGNED NULL,
        `patient_id` BIGINT UNSIGNED NOT NULL,
        `appointment_id` BIGINT UNSIGNED NULL,
        `queue_entry_id` BIGINT UNSIGNED NOT NULL,
        `intake_id` BIGINT UNSIGNED NULL,
        `assessment_type` ENUM("first_visit","returning_review","urgent_review") NOT NULL DEFAULT "returning_review",
        `identity_confirmed` TINYINT(1) NOT NULL DEFAULT 0,
        `allergies_reviewed` TINYINT(1) NOT NULL DEFAULT 0,
        `medicines_reviewed` TINYINT(1) NOT NULL DEFAULT 0,
        `conditions_reviewed` TINYINT(1) NOT NULL DEFAULT 0,
        `anesthesia_history_reviewed` TINYINT(1) NOT NULL DEFAULT 0,
        `bleeding_risk_reviewed` TINYINT(1) NOT NULL DEFAULT 0,
        `pregnancy_relevance_reviewed` TINYINT(1) NOT NULL DEFAULT 0,
        `change_status` ENUM("none","reported","unknown") NOT NULL DEFAULT "unknown",
        `changes_summary` TEXT NULL,
        `allergies_value` TEXT NULL,
        `medicines_value` TEXT NULL,
        `conditions_value` TEXT NULL,
        `dental_history_value` TEXT NULL,
        `bp_indication` VARCHAR(60) NOT NULL DEFAULT "not_required",
        `blood_pressure_systolic` SMALLINT UNSIGNED NULL,
        `blood_pressure_diastolic` SMALLINT UNSIGNED NULL,
        `pulse_rate` SMALLINT UNSIGNED NULL,
        `measurement_notes` TEXT NULL,
        `status` ENUM("draft","ready","needs_dentist_review","hold","verified") NOT NULL DEFAULT "draft",
        `outcome_note` TEXT NULL,
        `recorded_by` BIGINT UNSIGNED NULL,
        `recorded_at` DATETIME NULL,
        `verified_by` BIGINT UNSIGNED NULL,
        `verified_at` DATETIME NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_dental_safety_queue` (`queue_entry_id`),
        KEY `idx_dental_safety_patient` (`patient_id`,`recorded_at`),
        KEY `idx_dental_safety_status` (`status`,`recorded_at`),
        KEY `idx_dental_safety_branch` (`branch_id`,`status`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    if (!setup_index_exists($pdo, table('vital_signs'), 'idx_vitals_queue')) {
        $pdo->exec('ALTER TABLE `' . table('vital_signs') . '` ADD KEY `idx_vitals_queue` (`queue_entry_id`)');
    }
    if (!setup_index_exists($pdo, table('vital_signs'), 'idx_vitals_safety_assessment')) {
        $pdo->exec('ALTER TABLE `' . table('vital_signs') . '` ADD KEY `idx_vitals_safety_assessment` (`dental_safety_assessment_id`)');
    }
    if (!setup_index_exists($pdo, table('clinical_encounters'), 'idx_encounter_safety_assessment')) {
        $pdo->exec('ALTER TABLE `' . table('clinical_encounters') . '` ADD KEY `idx_encounter_safety_assessment` (`dental_safety_assessment_id`)');
    }

    if (setup_table_exists($pdo, table('permissions'))) {
        $permissions = [
            ['dental_safety.assess','Complete first-seating dental safety assessment','Clinical','Confirm dental-treatment safety information after seating and before treatment starts.'],
            ['dental_safety.verify','Verify first-seating dental safety assessment','Clinical','Dentist review and verification of reported changes, alerts, or treatment holds before treatment.'],
        ];
        $permissionInsert = $pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
        foreach ($permissions as $permission) $permissionInsert->execute($permission);
        if (setup_table_exists($pdo, table('role_permissions')) && setup_table_exists($pdo, table('roles'))) {
            $pdo->exec('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) SELECT r.id,p.id,1 FROM `' . table('roles') . '` r JOIN `' . table('permissions') . '` p WHERE r.code IN ("dentist","dental_assistant") AND p.code="dental_safety.assess" ON DUPLICATE KEY UPDATE allowed=1');
            $pdo->exec('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) SELECT r.id,p.id,1 FROM `' . table('roles') . '` r JOIN `' . table('permissions') . '` p WHERE r.code="dentist" AND p.code="dental_safety.verify" ON DUPLICATE KEY UPDATE allowed=1');
        }
    }

    // These were previously installed as ordinary services. They are now part
    // of a pre-treatment workflow and must not appear as routine billable or
    // bookable general-health services. Existing records remain intact.
    if (setup_table_exists($pdo, table('service_catalog'))) {
        $names = ['Vital Signs Screening','Blood Pressure Check','Weight and BMI Check','Blood Glucose Screening','Pre-treatment Health Screening','Medical Clearance Document Intake','Treatment Consent Intake','Consent Intake','Post-treatment Monitoring'];
        $marks = implode(',', array_fill(0, count($names), '?'));
        $stmt = $pdo->prepare('UPDATE `' . table('service_catalog') . '` SET is_active=0,is_public=0,updated_at=NOW() WHERE name IN (' . $marks . ')');
        $stmt->execute($names);
    }

    $now = date('c');
    setting_set('system','application_version','0.9.1.0');
    setting_set('system','first_seating_dental_safety_v0910_ready','1');
    setting_set('system','task_context_isolation_v0910_ready','1');
    setting_set('system','multi_role_task_switch_v0910_ready','1');
    setting_set('system','treatment_requires_first_seating_v0910_ready','1');
    setting_set('system','general_health_services_disabled_v0910_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.1.0');
    setting_set('production_acceptance','release_candidate_version','0.9.1.0');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.1.0','first_seating_task_context_role_switch');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.1.0','features'=>[
            'first_seating_dental_safety_assessment','treatment_start_readiness_gate','assigned_multi_role_task_switch',
            'role_switch_context_isolation','role_specific_landing_pages','general_health_service_defaults_disabled',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.1.0 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}

function setup_v0911_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.1.1', '>=')) return false;
        if (!setup_v0910_ready($pdo)) return false;
        return setup_column_exists($pdo, table('patient_allergies'), 'canonical_key')
            && setup_column_exists($pdo, table('patient_medications'), 'canonical_key')
            && setup_column_exists($pdo, table('patient_conditions'), 'canonical_key')
            && setup_index_exists($pdo, table('patient_allergies'), 'uq_patient_allergy_current_key')
            && setup_index_exists($pdo, table('patient_medications'), 'uq_patient_med_current_key')
            && setup_index_exists($pdo, table('patient_conditions'), 'uq_patient_condition_current_key')
            && setup_index_exists($pdo, table('print_logs'), 'idx_print_user_time')
            && setup_index_exists($pdo, table('print_logs'), 'idx_print_branch_time')
            && (string)setting_get('system','allergy_canonicalization_v0911_ready','0') === '1'
            && (string)setting_get('system','dental_safety_timeline_v0911_ready','0') === '1'
            && (string)setting_get('system','print_activity_v0911_ready','0') === '1'
            && (string)setting_get('system','cross_session_update_status_v0911_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0911(PDO $pdo): void
{
    if (!setup_v0910_ready($pdo)) setup_apply_v0910($pdo);
    if (!setup_v0910_ready($pdo)) throw new RuntimeException('The v0.9.1.0 first-seating and Task View baseline is incomplete.');

    $canonicalTables = [
        [table('patient_allergies'), 'normalized_substance', 'uq_patient_allergy_current_key'],
        [table('patient_medications'), 'normalized_medication', 'uq_patient_med_current_key'],
        [table('patient_conditions'), 'normalized_condition', 'uq_patient_condition_current_key'],
    ];
    foreach ($canonicalTables as [$tableName, $normalizedColumn, $indexName]) {
        setup_add_column($pdo, $tableName, 'canonical_key', 'CHAR(64) NULL AFTER `' . $normalizedColumn . '`');

        // Clear any incomplete key population, retain the newest current row for
        // each normalized item, and mark older duplicates as historical rather
        // than deleting them. This preserves the audit/change timeline.
        $pdo->exec('UPDATE `' . $tableName . '` SET canonical_key=NULL');
        $pdo->exec(
            'UPDATE `' . $tableName . '` older '
            . 'JOIN `' . $tableName . '` newer ON newer.patient_id=older.patient_id '
            . 'AND newer.`' . $normalizedColumn . '`=older.`' . $normalizedColumn . '` '
            . 'AND newer.is_current=1 AND newer.status="active" AND newer.id>older.id '
            . 'SET older.is_current=0,older.status="superseded",older.canonical_key=NULL,older.updated_at=NOW() '
            . 'WHERE older.is_current=1 AND older.status="active"'
        );
        $pdo->exec(
            'UPDATE `' . $tableName . '` SET canonical_key=CASE '
            . 'WHEN is_current=1 AND status="active" AND TRIM(`' . $normalizedColumn . '`)<>"" '
            . 'THEN SHA2(CONCAT(patient_id,"|",`' . $normalizedColumn . '`),256) ELSE NULL END'
        );
        if (!setup_index_exists($pdo, $tableName, $indexName)) {
            $pdo->exec('ALTER TABLE `' . $tableName . '` ADD UNIQUE KEY `' . $indexName . '` (`canonical_key`)');
        }
    }

    if (setup_table_exists($pdo, table('medical_alerts'))) {
        $pdo->exec(
            'UPDATE `' . table('medical_alerts') . '` older '
            . 'JOIN `' . table('medical_alerts') . '` newer ON newer.patient_id=older.patient_id '
            . 'AND LOWER(TRIM(newer.alert_type))=LOWER(TRIM(older.alert_type)) '
            . 'AND LOWER(TRIM(newer.description))=LOWER(TRIM(older.description)) '
            . 'AND newer.is_active=1 AND newer.id>older.id '
            . 'SET older.is_active=0,older.resolved_at=COALESCE(older.resolved_at,NOW()) '
            . 'WHERE older.is_active=1'
        );
    }

    if (!setup_index_exists($pdo, table('print_logs'), 'idx_print_user_time')) {
        $pdo->exec('ALTER TABLE `' . table('print_logs') . '` ADD KEY `idx_print_user_time` (`printed_by`,`printed_at`)');
    }
    if (!setup_index_exists($pdo, table('print_logs'), 'idx_print_branch_time')) {
        $pdo->exec('ALTER TABLE `' . table('print_logs') . '` ADD KEY `idx_print_branch_time` (`branch_id`,`printed_at`)');
    }

    $now = date('c');
    setting_set('system','application_version','0.9.1.1');
    setting_set('system','allergy_canonicalization_v0911_ready','1');
    setting_set('system','dental_safety_timeline_v0911_ready','1');
    setting_set('system','print_activity_v0911_ready','1');
    setting_set('system','cross_session_update_status_v0911_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.1.1');
    setting_set('production_acceptance','release_candidate_version','0.9.1.1');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.1.1','allergy_canonicalization_timeline_print_activity');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.1.1','features'=>[
            'one_current_allergy_per_substance','historical_allergy_rows_preserved','unchanged_safety_confirmations_collapsed',
            'print_activity_visibility','truthful_opened_for_print_status','cross_session_update_status_refresh',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.1.1 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}

function setup_v0912_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.1.2', '>=')) return false;
        if (!setup_v0911_ready($pdo)) return false;
        return setup_table_exists($pdo, table('patient_record_releases'))
            && setup_column_exists($pdo, table('patient_portal_accounts'), 'preferred_channel')
            && setup_column_exists($pdo, table('patient_portal_accounts'), 'appointment_notifications')
            && setup_column_exists($pdo, table('patient_portal_accounts'), 'record_notifications')
            && setup_index_exists($pdo, table('patient_record_releases'), 'uq_patient_record_release_request')
            && setup_index_exists($pdo, table('patient_record_releases'), 'idx_patient_record_release_patient')
            && (string)setting_get('system','patient_portal_sidebar_v0912_ready','0') === '1'
            && (string)setting_get('system','controlled_record_release_v0912_ready','0') === '1'
            && (string)setting_get('system','patient_safe_record_copy_v0912_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0912(PDO $pdo): void
{
    if (!setup_v0911_ready($pdo)) setup_apply_v0911($pdo);
    if (!setup_v0911_ready($pdo)) throw new RuntimeException('The v0.9.1.1 allergy, timeline, and print-activity baseline is incomplete.');

    setup_add_column($pdo, table('patient_portal_accounts'), 'preferred_channel', 'VARCHAR(30) NOT NULL DEFAULT "email" AFTER `profile_completed_at`');
    setup_add_column($pdo, table('patient_portal_accounts'), 'appointment_notifications', 'TINYINT(1) NOT NULL DEFAULT 1 AFTER `preferred_channel`');
    setup_add_column($pdo, table('patient_portal_accounts'), 'record_notifications', 'TINYINT(1) NOT NULL DEFAULT 1 AFTER `appointment_notifications`');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('patient_record_releases') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `request_id` BIGINT UNSIGNED NOT NULL,
        `patient_id` BIGINT UNSIGNED NOT NULL,
        `branch_id` BIGINT UNSIGNED NULL,
        `release_scope` VARCHAR(30) NOT NULL DEFAULT "summary",
        `date_from` DATE NULL,
        `date_to` DATE NULL,
        `status` VARCHAR(30) NOT NULL DEFAULT "pending_identity",
        `identity_verified_by` BIGINT UNSIGNED NULL,
        `identity_verified_at` DATETIME NULL,
        `clinical_approved_by` BIGINT UNSIGNED NULL,
        `clinical_approved_at` DATETIME NULL,
        `released_by` BIGINT UNSIGNED NULL,
        `released_at` DATETIME NULL,
        `expires_at` DATETIME NULL,
        `revoked_by` BIGINT UNSIGNED NULL,
        `revoked_at` DATETIME NULL,
        `revocation_reason` VARCHAR(500) NULL,
        `view_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `last_viewed_at` DATETIME NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_patient_record_release_request` (`request_id`),
        KEY `idx_patient_record_release_patient` (`patient_id`,`status`,`created_at`),
        KEY `idx_patient_record_release_branch` (`branch_id`,`status`,`created_at`),
        KEY `idx_patient_record_release_expiry` (`status`,`expires_at`),
        CONSTRAINT `' . table('fk_record_release_request') . '` FOREIGN KEY (`request_id`) REFERENCES `' . table('patient_portal_requests') . '` (`id`) ON DELETE CASCADE,
        CONSTRAINT `' . table('fk_record_release_patient') . '` FOREIGN KEY (`patient_id`) REFERENCES `' . table('patients') . '` (`id`) ON DELETE CASCADE,
        CONSTRAINT `' . table('fk_record_release_branch') . '` FOREIGN KEY (`branch_id`) REFERENCES `' . table('branches') . '` (`id`) ON DELETE SET NULL,
        CONSTRAINT `' . table('fk_record_release_identity_user') . '` FOREIGN KEY (`identity_verified_by`) REFERENCES `' . table('users') . '` (`id`) ON DELETE SET NULL,
        CONSTRAINT `' . table('fk_record_release_clinical_user') . '` FOREIGN KEY (`clinical_approved_by`) REFERENCES `' . table('users') . '` (`id`) ON DELETE SET NULL,
        CONSTRAINT `' . table('fk_record_release_released_user') . '` FOREIGN KEY (`released_by`) REFERENCES `' . table('users') . '` (`id`) ON DELETE SET NULL,
        CONSTRAINT `' . table('fk_record_release_revoked_user') . '` FOREIGN KEY (`revoked_by`) REFERENCES `' . table('users') . '` (`id`) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    if (setup_table_exists($pdo, table('permissions'))) {
        $permissions = [
            ['records.release.receive','Receive patient record-copy requests','Records','Verify identity and route a patient dental-record request for approval.'],
            ['records.release.clinical_approve','Approve clinical record release','Records','Dentist review of finalized clinical content before patient release.'],
            ['records.release.manage','Release patient dental records','Records','Release an approved patient-safe dental-record copy through the secure portal.'],
            ['records.release.financial','Release patient financial records','Records','Release invoices, payments, receipts, and statements without clinical content.'],
        ];
        $insert=$pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
        foreach($permissions as $permission)$insert->execute($permission);
        if (setup_table_exists($pdo, table('role_permissions')) && setup_table_exists($pdo, table('roles'))) {
            $grants = [
                ['records.release.receive',['super_admin','clinic_admin','receptionist','staff']],
                ['records.release.clinical_approve',['dentist']],
                ['records.release.manage',['super_admin','clinic_admin']],
                ['records.release.financial',['super_admin','clinic_admin','cashier','staff']],
            ];
            $grant=$pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) SELECT r.id,p.id,1 FROM `' . table('roles') . '` r JOIN `' . table('permissions') . '` p ON p.code=? WHERE r.code=? ON DUPLICATE KEY UPDATE allowed=1');
            foreach($grants as [$permissionCode,$roles])foreach($roles as $roleCode)$grant->execute([$permissionCode,$roleCode]);
        }
    }

    $now=date('c');
    setting_set('system','application_version','0.9.1.2');
    setting_set('system','patient_portal_sidebar_v0912_ready','1');
    setting_set('system','controlled_record_release_v0912_ready','1');
    setting_set('system','patient_safe_record_copy_v0912_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.1.2');
    setting_set('production_acceptance','release_candidate_version','0.9.1.2');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();
    setup_record_history($pdo,'0.9.1.2','patient_portal_controlled_record_release');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.1.2','features'=>[
            'patient_portal_sidebar','patient_profile_preferences','record_copy_request','identity_verification',
            'dentist_clinical_release_approval','secure_portal_release','patient_safe_record_output','release_expiry_and_revocation',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.1.2 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}

function setup_v0913_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.1.3', '>=')) return false;
        if (!setup_v0912_ready($pdo)) return false;
        return (string)setting_get('system','navigation_cycle_guard_v0913_ready','0') === '1'
            && (string)setting_get('system','route_role_integrity_v0913_ready','0') === '1'
            && (string)setting_get('system','workspace_recovery_v0913_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0913(PDO $pdo): void
{
    if (!setup_v0912_ready($pdo)) setup_apply_v0912($pdo);
    if (!setup_v0912_ready($pdo)) throw new RuntimeException('The v0.9.1.2 patient portal and controlled record-release baseline is incomplete.');

    $requiredFiles = [
        DENTAL_ROOT . '/app/navigation_guard.php',
        DENTAL_ROOT . '/app/navigation_integrity.php',
        DENTAL_ROOT . '/config/route_access.php',
        DENTAL_ROOT . '/modules/overview/navigation_recovery.php',
        DENTAL_ROOT . '/modules/administration/navigation_diagnostics.php',
    ];
    $missing = [];
    foreach ($requiredFiles as $file) if (!is_file($file)) $missing[] = str_replace(DENTAL_ROOT . '/', '', $file);
    if ($missing) throw new RuntimeException('The v0.9.1.3 application files are incomplete: ' . implode(', ', $missing) . '. Re-upload the update package, then retry.');

    $now = date('c');
    setting_set('system','application_version','0.9.1.3');
    setting_set('system','navigation_cycle_guard_v0913_ready','1');
    setting_set('system','route_role_integrity_v0913_ready','1');
    setting_set('system','workspace_recovery_v0913_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.1.3');
    setting_set('production_acceptance','release_candidate_version','0.9.1.3');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache'], $_SESSION['dental_redirect_edges']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.1.3','navigation_role_integrity_workflow_recovery');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.1.3','features'=>[
            'redirect_cycle_circuit_breaker','canonical_route_access_map','route_role_visibility_inventory',
            'safe_workspace_recovery','navigation_integrity_csv_export','session_context_isolation_review',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.1.3 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}


function setup_v0914_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.1.4', '>=')) return false;
        if (!setup_v0913_ready($pdo)) return false;
        return setup_table_exists($pdo, table('operation_requests'))
            && setup_index_exists($pdo, table('operation_requests'), 'uq_operation_dedupe_key')
            && setup_index_exists($pdo, table('operation_requests'), 'idx_operation_status_expiry')
            && setup_index_exists($pdo, table('operation_requests'), 'idx_operation_route_action')
            && (string)setting_get('system','transaction_integrity_v0914_ready','0') === '1'
            && (string)setting_get('system','duplicate_submission_guard_v0914_ready','0') === '1'
            && (string)setting_get('system','workflow_concurrency_monitor_v0914_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0914(PDO $pdo): void
{
    if (!setup_v0913_ready($pdo)) setup_apply_v0913($pdo);
    if (!setup_v0913_ready($pdo)) throw new RuntimeException('The v0.9.1.3 navigation and role-integrity baseline is incomplete.');

    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . table('operation_requests') . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `dedupe_key` CHAR(64) NOT NULL,
        `user_id` BIGINT UNSIGNED NULL,
        `branch_id` BIGINT UNSIGNED NULL,
        `active_role` VARCHAR(40) NOT NULL DEFAULT "",
        `route_name` VARCHAR(100) NOT NULL,
        `action_name` VARCHAR(80) NOT NULL,
        `entity_type` VARCHAR(80) NULL,
        `entity_id` BIGINT UNSIGNED NULL,
        `payload_hash` CHAR(64) NOT NULL,
        `status` ENUM("processing","completed","failed") NOT NULL DEFAULT "processing",
        `attempt_count` INT UNSIGNED NOT NULL DEFAULT 1,
        `duplicate_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `started_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `completed_at` DATETIME NULL,
        `expires_at` DATETIME NOT NULL,
        `last_error_code` VARCHAR(64) NULL,
        `last_error_message` VARCHAR(500) NULL,
        `response_reference` VARCHAR(255) NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` DATETIME NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_operation_dedupe_key` (`dedupe_key`),
        KEY `idx_operation_status_expiry` (`status`,`expires_at`),
        KEY `idx_operation_user_time` (`user_id`,`created_at`),
        KEY `idx_operation_branch_time` (`branch_id`,`created_at`),
        KEY `idx_operation_route_action` (`route_name`,`action_name`,`created_at`),
        CONSTRAINT `' . table('fk_operation_user') . '` FOREIGN KEY (`user_id`) REFERENCES `' . table('users') . '` (`id`) ON DELETE SET NULL,
        CONSTRAINT `' . table('fk_operation_branch') . '` FOREIGN KEY (`branch_id`) REFERENCES `' . table('branches') . '` (`id`) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    if (setup_table_exists($pdo, table('permissions'))) {
        $permission = ['system.transaction_integrity.view','View transaction-integrity monitoring','System','Review duplicate-submission protection, concurrent processing, failed attempts, and workflow request integrity metadata.'];
        $insert=$pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
        $insert->execute($permission);
        if (setup_table_exists($pdo, table('role_permissions')) && setup_table_exists($pdo, table('roles'))) {
            $grant=$pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) SELECT r.id,p.id,1 FROM `' . table('roles') . '` r JOIN `' . table('permissions') . '` p ON p.code=? WHERE r.code=? ON DUPLICATE KEY UPDATE allowed=1');
            foreach (['super_admin','clinic_admin'] as $roleCode) $grant->execute([$permission[0],$roleCode]);
        }
    }

    $now=date('c');
    setting_set('system','application_version','0.9.1.4');
    setting_set('system','transaction_integrity_v0914_ready','1');
    setting_set('system','duplicate_submission_guard_v0914_ready','1');
    setting_set('system','workflow_concurrency_monitor_v0914_ready','1');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.1.4');
    setting_set('production_acceptance','release_candidate_version','0.9.1.4');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.1.4','transaction_integrity_duplicate_submission');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.1.4','features'=>[
            'duplicate_submission_guard','concurrent_operation_ledger','appointment_queue_prescription_release_protection',
            'retryable_failed_operations','transaction_integrity_monitoring','minimum_metadata_only',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.1.4 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}

function setup_v0920_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.2.0', '>=')) return false;
        if (!setup_v0914_ready($pdo)) return false;
        return is_file(DENTAL_ROOT . '/config/release_manifest.json')
            && (string)setting_get('system','unified_update_state_v0920_ready','0') === '1'
            && (string)setting_get('system','machine_release_manifest_v0920_ready','0') === '1'
            && (string)setting_get('system','stable_update_button_v0920_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0920(PDO $pdo): void
{
    if (!setup_v0914_ready($pdo)) setup_apply_v0914($pdo);
    if (!setup_v0914_ready($pdo)) {
        throw new RuntimeException('The v0.9.1.4 transaction-integrity baseline is incomplete.');
    }

    $requiredFiles = [
        DENTAL_ROOT . '/config/release_manifest.json',
        DENTAL_ROOT . '/app/hardening_foundation.php',
        DENTAL_ROOT . '/app/update_readiness.php',
        DENTAL_ROOT . '/app/update_checkpoint.php',
    ];
    $missing = [];
    foreach ($requiredFiles as $file) {
        if (!is_file($file)) $missing[] = str_replace(DENTAL_ROOT . '/', '', $file);
    }
    if ($missing) {
        throw new RuntimeException('The v0.9.2.0 application files are incomplete: ' . implode(', ', $missing) . '. Re-upload the update package, then retry.');
    }

    $manifest = dental_hardening_release_manifest();
    if ((string)($manifest['release'] ?? '') !== '0.9.2.0'
        || (string)($manifest['migration_key'] ?? '') !== '0.9.2.0_update_state_release_manifest_client_docs') {
        throw new RuntimeException('The v0.9.2.0 machine release manifest is missing or does not match this package.');
    }

    $now = date('c');
    setting_set('system','application_version','0.9.2.0');
    setting_set('system','unified_update_state_v0920_ready','1');
    setting_set('system','machine_release_manifest_v0920_ready','1');
    setting_set('system','stable_update_button_v0920_ready','1');
    setting_set('system','client_documentation_v0920_available',is_dir(DENTAL_ROOT . '/docs/client') ? '1' : '0');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.2.0');
    setting_set('production_acceptance','release_candidate_version','0.9.2.0');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.2.0','update_state_release_manifest_client_docs');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.2.0','features'=>[
            'unified_update_state','machine_release_manifest','stable_update_button',
            'stale_checkpoint_normalization','latest_v0914_repairs_carried_forward','current_client_documentation',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.2.0 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}


function setup_v0930_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.3.0', '>=')) return false;
        if (!setup_v0920_ready($pdo)) return false;
        $table = table('operational_health_snapshots');
        return setup_table_exists($pdo, $table)
            && setup_column_exists($pdo, $table, 'fingerprint_sha256')
            && setup_column_exists($pdo, $table, 'snapshot_json')
            && setup_index_exists($pdo, $table, 'idx_operational_health_created')
            && setup_index_exists($pdo, $table, 'idx_operational_health_status')
            && (string)setting_get('system','operational_health_v0930_ready','0') === '1'
            && (string)setting_get('system','system_recovery_sidebar_v0930_ready','0') === '1'
            && (string)setting_get('system','nonblocking_health_gate_v0930_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0930(PDO $pdo): void
{
    if (!setup_v0920_ready($pdo)) setup_apply_v0920($pdo);
    if (!setup_v0920_ready($pdo)) {
        throw new RuntimeException('The v0.9.2.0 update-state and release-hygiene baseline is incomplete.');
    }

    $requiredFiles = [
        DENTAL_ROOT . '/config/release_manifest.json',
        DENTAL_ROOT . '/app/operational_health.php',
        DENTAL_ROOT . '/modules/administration/operational_health.php',
        DENTAL_ROOT . '/config/routes.php',
        DENTAL_ROOT . '/config/route_access.php',
        DENTAL_ROOT . '/config/route_slugs.php',
        DENTAL_ROOT . '/app/layout.php',
    ];
    $missing = [];
    foreach ($requiredFiles as $file) {
        if (!is_file($file)) $missing[] = str_replace(DENTAL_ROOT . '/', '', $file);
    }
    if ($missing) {
        throw new RuntimeException('The v0.9.3.0 application files are incomplete: ' . implode(', ', $missing) . '. Re-upload the complete update overlay, then retry.');
    }

    $manifest = dental_hardening_release_manifest();
    if ((string)($manifest['release'] ?? '') !== '0.9.3.0'
        || (string)($manifest['migration_key'] ?? '') !== '0.9.3.0_operational_health_recovery_sidebar') {
        throw new RuntimeException('The v0.9.3.0 machine release manifest is missing or does not match this package.');
    }

    $table = table('operational_health_snapshots');
    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $table . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `release_version` VARCHAR(40) NOT NULL,
        `installed_version` VARCHAR(40) NOT NULL,
        `overall_status` VARCHAR(20) NOT NULL DEFAULT \'review\',
        `pass_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `review_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `blocked_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `fingerprint_sha256` CHAR(64) NOT NULL,
        `snapshot_json` LONGTEXT NOT NULL,
        `created_by_user_id` BIGINT UNSIGNED NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        KEY `idx_operational_health_created` (`created_at`),
        KEY `idx_operational_health_status` (`overall_status`,`created_at`),
        KEY `idx_operational_health_user` (`created_by_user_id`,`created_at`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    // Repair partial tables from an interrupted attempt without relying on
    // column ordering or foreign keys that vary across shared-hosting installs.
    setup_add_column($pdo, $table, 'release_version', "VARCHAR(40) NOT NULL DEFAULT '0.0.0'");
    setup_add_column($pdo, $table, 'installed_version', "VARCHAR(40) NOT NULL DEFAULT '0.0.0'");
    setup_add_column($pdo, $table, 'overall_status', "VARCHAR(20) NOT NULL DEFAULT 'review'");
    setup_add_column($pdo, $table, 'pass_count', 'INT UNSIGNED NOT NULL DEFAULT 0');
    setup_add_column($pdo, $table, 'review_count', 'INT UNSIGNED NOT NULL DEFAULT 0');
    setup_add_column($pdo, $table, 'blocked_count', 'INT UNSIGNED NOT NULL DEFAULT 0');
    setup_add_column($pdo, $table, 'fingerprint_sha256', "CHAR(64) NOT NULL DEFAULT ''");
    setup_add_column($pdo, $table, 'snapshot_json', 'LONGTEXT NULL');
    setup_add_column($pdo, $table, 'created_by_user_id', 'BIGINT UNSIGNED NULL');
    setup_add_column($pdo, $table, 'created_at', 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
    if (!setup_index_exists($pdo,$table,'idx_operational_health_created')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD KEY `idx_operational_health_created` (`created_at`)');
    }
    if (!setup_index_exists($pdo,$table,'idx_operational_health_status')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD KEY `idx_operational_health_status` (`overall_status`,`created_at`)');
    }
    if (!setup_index_exists($pdo,$table,'idx_operational_health_user')) {
        $pdo->exec('ALTER TABLE `' . $table . '` ADD KEY `idx_operational_health_user` (`created_by_user_id`,`created_at`)');
    }

    $now = date('c');
    setting_set('system','application_version','0.9.3.0');
    setting_set('system','operational_health_v0930_ready','1');
    setting_set('system','system_recovery_sidebar_v0930_ready','1');
    setting_set('system','nonblocking_health_gate_v0930_ready','1');
    setting_set('system','client_documentation_v0930_available',is_dir(DENTAL_ROOT . '/docs/client') ? '1' : '0');
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.3.0');
    setting_set('production_acceptance','release_candidate_version','0.9.3.0');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.3.0','operational_health_recovery_sidebar');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.3.0','features'=>[
            'operational_health_center','database_and_write_probes','backup_age_and_verification_review',
            'nonblocking_optional_health_warnings','safe_cleanup_inventory','system_recovery_sidebar',
            'current_client_documentation',
        ]]);
    } catch (Throwable $exception) {
        error_log('v0.9.3.0 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}


function setup_v0940_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '0.9.4.0', '>=')) return false;
        if (!setup_v0930_ready($pdo)) return false;
        if (!is_file(DENTAL_ROOT . '/app/query_integrity.php')) return false;
        if (!function_exists('dental_query_integrity_snapshot')) {
            require_once DENTAL_ROOT . '/app/query_integrity.php';
        }
        $snapshot = dental_query_integrity_snapshot($pdo);
        return (string)($snapshot['overall'] ?? 'blocked') === 'pass'
            && (string)setting_get('system','query_integrity_v0940_ready','0') === '1'
            && (string)setting_get('system','joined_query_alias_guard_v0940_ready','0') === '1'
            && (string)setting_get('system','release_query_regression_gate_v0940_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v0940(PDO $pdo): void
{
    if (!setup_v0930_ready($pdo)) setup_apply_v0930($pdo);
    if (!setup_v0930_ready($pdo)) {
        throw new RuntimeException('The v0.9.3.0 operational-health and recovery baseline is incomplete.');
    }

    $requiredFiles = [
        DENTAL_ROOT . '/config/release_manifest.json',
        DENTAL_ROOT . '/app/query_integrity.php',
        DENTAL_ROOT . '/app/operational_health.php',
        DENTAL_ROOT . '/modules/administration/transaction_integrity.php',
        DENTAL_ROOT . '/modules/administration/system_smoke_test.php',
    ];
    $missing = [];
    foreach ($requiredFiles as $file) {
        if (!is_file($file)) $missing[] = str_replace(DENTAL_ROOT . '/', '', $file);
    }
    if ($missing) {
        throw new RuntimeException('The v0.9.4.0 application files are incomplete: ' . implode(', ', $missing) . '. Re-upload the complete update overlay, then retry.');
    }

    $manifest = dental_hardening_release_manifest();
    if ((string)($manifest['release'] ?? '') !== '0.9.4.0'
        || (string)($manifest['migration_key'] ?? '') !== '0.9.4.0_query_regression_joined_column_safety') {
        throw new RuntimeException('The v0.9.4.0 machine release manifest is missing or does not match this package.');
    }

    require_once DENTAL_ROOT . '/app/query_integrity.php';
    $snapshot = dental_query_integrity_snapshot($pdo);
    if ((string)($snapshot['overall'] ?? 'blocked') !== 'pass') {
        $failures = [];
        foreach ((array)($snapshot['checks'] ?? []) as $row) {
            if (($row['status'] ?? '') === 'pass') continue;
            $failures[] = (string)($row['label'] ?? 'Query check') . ': ' . (string)($row['detail'] ?? 'failed');
        }
        throw new RuntimeException('Query-regression validation failed: ' . implode(' | ', array_slice($failures, 0, 3)));
    }

    $now = date('c');
    setting_set('system','application_version','0.9.4.0');
    setting_set('system','query_integrity_v0940_ready','1');
    setting_set('system','joined_query_alias_guard_v0940_ready','1');
    setting_set('system','release_query_regression_gate_v0940_ready','1');
    setting_set('system','last_query_integrity_fingerprint',(string)($snapshot['fingerprint'] ?? ''));
    setting_set('system','last_query_integrity_checked_at',$now);
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','0.9.4.0');
    setting_set('production_acceptance','release_candidate_version','0.9.4.0');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'0.9.4.0','query_regression_joined_column_safety');
    try {
        audit_required('system_update_applied','system',null,['version'=>'0.9.4.0','features'=>[
            'alias_qualified_transaction_integrity_filters','read_only_joined_query_probes',
            'source_level_query_regression_guard','operational_health_query_gate','qa_smoke_query_gate',
        ],'query_integrity_fingerprint'=>(string)($snapshot['fingerprint'] ?? '')]);
    } catch (Throwable $exception) {
        error_log('v0.9.4.0 migration audit write skipped after successful state application: '.$exception->getMessage());
    }
}


function setup_v1000_ready(PDO $pdo): bool
{
    try {
        if (!version_compare(installed_application_version(), '1.0.0.0', '>=')) return false;
        if (!setup_v0940_ready($pdo)) return false;
        if (!is_file(DENTAL_ROOT . '/app/data_integrity.php')) return false;
        if (!setup_table_exists($pdo, table('data_integrity_snapshots'))) return false;
        if (!setup_index_exists($pdo, table('data_integrity_snapshots'), 'idx_data_integrity_created')) return false;
        if (!setup_index_exists($pdo, table('data_integrity_snapshots'), 'idx_data_integrity_status')) return false;
        if (!setup_index_exists($pdo, table('operation_requests'), 'idx_operation_cleanup')) return false;
        if (!function_exists('dental_data_integrity_snapshot')) require_once DENTAL_ROOT . '/app/data_integrity.php';
        $snapshot = dental_data_integrity_snapshot($pdo);
        return (string)($snapshot['overall'] ?? 'blocked') !== 'blocked'
            && (string)setting_get('system','production_data_integrity_v1000_ready','0') === '1'
            && (string)setting_get('system','database_contract_v1000_ready','0') === '1'
            && (string)setting_get('system','integrity_evidence_v1000_ready','0') === '1'
            && (string)setting_get('system','request_ledger_maintenance_v1000_ready','0') === '1';
    } catch (Throwable) {
        return false;
    }
}

function setup_apply_v1000(PDO $pdo): void
{
    if (!setup_v0940_ready($pdo)) setup_apply_v0940($pdo);
    if (!setup_v0940_ready($pdo)) {
        throw new RuntimeException('The v0.9.4.0 query-regression and joined-column safety baseline is incomplete.');
    }

    $requiredFiles = [
        DENTAL_ROOT . '/config/release_manifest.json',
        DENTAL_ROOT . '/app/data_integrity.php',
        DENTAL_ROOT . '/app/operational_health.php',
        DENTAL_ROOT . '/modules/administration/data_integrity_center.php',
        DENTAL_ROOT . '/modules/administration/system_smoke_test.php',
        DENTAL_ROOT . '/config/routes.php',
        DENTAL_ROOT . '/config/route_slugs.php',
        DENTAL_ROOT . '/config/route_access.php',
    ];
    $missing = [];
    foreach ($requiredFiles as $file) {
        if (!is_file($file)) $missing[] = str_replace(DENTAL_ROOT . '/', '', $file);
    }
    if ($missing) {
        throw new RuntimeException('The v1.0.0.0 application files are incomplete: ' . implode(', ', $missing) . '. Re-upload the complete update overlay, then retry.');
    }

    $manifest = dental_hardening_release_manifest();
    if ((string)($manifest['release'] ?? '') !== '1.0.0.0'
        || (string)($manifest['migration_key'] ?? '') !== '1.0.0.0_production_data_integrity_baseline') {
        throw new RuntimeException('The v1.0.0.0 machine release manifest is missing or does not match this package.');
    }

    $snapshotTable = table('data_integrity_snapshots');
    $pdo->exec('CREATE TABLE IF NOT EXISTS `' . $snapshotTable . '` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `release_version` VARCHAR(32) NOT NULL,
        `installed_version` VARCHAR(32) NOT NULL,
        `overall_status` ENUM("pass","review","blocked") NOT NULL DEFAULT "review",
        `pass_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `review_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `blocked_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `fingerprint_sha256` CHAR(64) NOT NULL,
        `snapshot_json` LONGTEXT NOT NULL,
        `created_by_user_id` BIGINT UNSIGNED NULL,
        `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        KEY `idx_data_integrity_created` (`created_at`),
        KEY `idx_data_integrity_status` (`overall_status`,`created_at`),
        KEY `idx_data_integrity_user` (`created_by_user_id`,`created_at`),
        CONSTRAINT `' . table('fk_data_integrity_user') . '` FOREIGN KEY (`created_by_user_id`) REFERENCES `' . table('users') . '` (`id`) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

    if (!setup_index_exists($pdo,$snapshotTable,'idx_data_integrity_created')) {
        $pdo->exec('ALTER TABLE `' . $snapshotTable . '` ADD KEY `idx_data_integrity_created` (`created_at`)');
    }
    if (!setup_index_exists($pdo,$snapshotTable,'idx_data_integrity_status')) {
        $pdo->exec('ALTER TABLE `' . $snapshotTable . '` ADD KEY `idx_data_integrity_status` (`overall_status`,`created_at`)');
    }
    if (!setup_index_exists($pdo,$snapshotTable,'idx_data_integrity_user')) {
        $pdo->exec('ALTER TABLE `' . $snapshotTable . '` ADD KEY `idx_data_integrity_user` (`created_by_user_id`,`created_at`)');
    }
    if (!setup_index_exists($pdo,table('operation_requests'),'idx_operation_cleanup')) {
        $pdo->exec('ALTER TABLE `' . table('operation_requests') . '` ADD KEY `idx_operation_cleanup` (`status`,`completed_at`,`expires_at`)');
    }

    if (setup_table_exists($pdo, table('permissions'))) {
        $permission = ['system.data_integrity.view','View production data integrity','System','Review database contracts, referential integrity, duplicate identifiers, query safety, transaction evidence, and request-ledger health.'];
        $insert = $pdo->prepare('INSERT INTO `' . table('permissions') . '` (code,name,module_group,description) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),module_group=VALUES(module_group),description=VALUES(description)');
        $insert->execute($permission);
        if (setup_table_exists($pdo, table('role_permissions')) && setup_table_exists($pdo, table('roles'))) {
            $grant = $pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) SELECT r.id,p.id,1 FROM `' . table('roles') . '` r JOIN `' . table('permissions') . '` p ON p.code=? WHERE r.code=? ON DUPLICATE KEY UPDATE allowed=1');
            foreach (['super_admin','clinic_admin'] as $roleCode) $grant->execute([$permission[0],$roleCode]);
        }
    }

    require_once DENTAL_ROOT . '/app/data_integrity.php';
    $snapshot = dental_data_integrity_snapshot($pdo);
    if ((string)($snapshot['overall'] ?? 'blocked') === 'blocked') {
        $failures = [];
        foreach ((array)($snapshot['checks'] ?? []) as $row) {
            if (($row['status'] ?? '') !== 'blocked') continue;
            $failures[] = (string)($row['label'] ?? 'Integrity check') . ': ' . (string)($row['detail'] ?? 'failed');
        }
        throw new RuntimeException('Production data-integrity validation is blocked: ' . implode(' | ', array_slice($failures,0,3)));
    }

    $now = date('c');
    setting_set('system','application_version','1.0.0.0');
    setting_set('system','production_data_integrity_v1000_ready','1');
    setting_set('system','database_contract_v1000_ready','1');
    setting_set('system','integrity_evidence_v1000_ready','1');
    setting_set('system','request_ledger_maintenance_v1000_ready','1');
    setting_set('system','last_data_integrity_fingerprint',(string)($snapshot['fingerprint'] ?? ''));
    setting_set('system','last_data_integrity_checked_at',$now);
    setting_set('system','last_update_completed_at',$now);
    setting_set('system','release_candidate_required_since',$now);
    setting_set('system','release_candidate_version','1.0.0.0');
    setting_set('production_acceptance','release_candidate_version','1.0.0.0');
    setting_set('production_acceptance','release_candidate_approval','');
    setting_set('production_acceptance','release_candidate_status','pending');
    setting_set('production_acceptance','automated_validation_evidence','');
    unset($_SESSION['dental_update_pending_cache']);
    if (function_exists('setting_request_cache_clear')) setting_request_cache_clear();

    setup_record_history($pdo,'1.0.0.0','production_data_integrity_baseline');
    try {
        audit_required('system_update_applied','system',null,['version'=>'1.0.0.0','features'=>[
            'production_data_integrity_center','database_engine_and_sql_mode_contracts',
            'orphan_and_duplicate_detection','critical_transaction_boundary_evidence',
            'integrity_snapshot_fingerprints','safe_request_ledger_recovery_and_retention',
            'operational_health_and_qa_integration',
        ],'data_integrity_fingerprint'=>(string)($snapshot['fingerprint'] ?? '')]);
    } catch (Throwable $exception) {
        error_log('v1.0.0.0 migration audit write skipped after successful state application: ' . $exception->getMessage());
    }
}


function setup_resumable_update_plan(PDO $pdo): array
{
    if (version_compare(installed_application_version(), '0.8.1.2', '<')) {
        throw new RuntimeException('This installation is below the supported v0.8.1.2 baseline. Install the supported baseline before DentalCare v1.0.0.0.');
    }

    $definitions = [
        [
            'version' => '0.9.0.0',
            'migration_key' => '0.9.0.0_hardening_foundation',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.0_hardening_foundation.sql',
            'label' => 'Recover and verify the hardening foundation',
            'apply' => 'setup_apply_v0900',
            'ready' => 'setup_v0900_ready',
        ],
        [
            'version' => '0.9.0.1',
            'migration_key' => '0.9.0.1_migration_recovery_hardening_tracker',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.1_migration_recovery_hardening_tracker.sql',
            'label' => 'Record migration recovery and hardening tracker',
            'apply' => 'setup_apply_v0901',
            'ready' => 'setup_v0901_ready',
        ],
        [
            'version' => '0.9.0.2',
            'migration_key' => '0.9.0.2_emergency_release_sanitization_access_control',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.2_emergency_release_sanitization_access_control.sql',
            'label' => 'Apply emergency release sanitization and access-control hardening',
            'apply' => 'setup_apply_v0902',
            'ready' => 'setup_v0902_ready',
        ],
        [
            'version' => '0.9.0.3',
            'migration_key' => '0.9.0.3_privileged_mfa_session_security_bulk_service_staffing',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.3_privileged_mfa_session_security_bulk_service_staffing.sql',
            'label' => 'Enable privileged MFA, stronger sessions, and bulk service staffing',
            'apply' => 'setup_apply_v0903',
            'ready' => 'setup_v0903_ready',
        ],
        [
            'version' => '0.9.0.4',
            'migration_key' => '0.9.0.4_authorization_minimum_necessary_access',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.4_authorization_minimum_necessary_access.sql',
            'label' => 'Enforce granular authorization, independent approvals, and minimum-necessary patient access',
            'apply' => 'setup_apply_v0904',
            'ready' => 'setup_v0904_ready',
        ],
        [
            'version' => '0.9.0.5',
            'migration_key' => '0.9.0.5_controlled_break_glass_superuser_isolation',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.5_controlled_break_glass_superuser_isolation.sql',
            'label' => 'Isolate Superuser Task Views, enable controlled emergency access, and block hard schedule conflicts',
            'apply' => 'setup_apply_v0905',
            'ready' => 'setup_v0905_ready',
        ],
        [
            'version' => '0.9.0.6',
            'migration_key' => '0.9.0.6_tamper_evident_audit_privacy_governance',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.6_tamper_evident_audit_privacy_governance.sql',
            'label' => 'Enable tamper-evident audit chains, privacy governance, rights requests, and incident tracking',
            'apply' => 'setup_apply_v0906',
            'ready' => 'setup_v0906_ready',
        ],
        [
            'version' => '0.9.0.7',
            'migration_key' => '0.9.0.7_clinical_safety_medication_review',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.7_clinical_safety_medication_review.sql',
            'label' => 'Enable structured clinical safety data, medication review, prescription hard stops, and safety evidence',
            'apply' => 'setup_apply_v0907',
            'ready' => 'setup_v0907_ready',
        ],
        [
            'version' => '0.9.0.8',
            'migration_key' => '0.9.0.8_patient_context_isolation_dental_scope',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.8_patient_context_isolation_dental_scope.sql',
            'label' => 'Separate viewed patients from active visits and enforce dental-facility-only clinical scope',
            'apply' => 'setup_apply_v0908',
            'ready' => 'setup_v0908_ready',
        ],
        [
            'version' => '0.9.0.9',
            'migration_key' => '0.9.0.9_unified_profile_center_staff_work_assignment',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.0.9_unified_profile_center_staff_work_assignment.sql',
            'label' => 'Unify staff identity, work assignment, and Dentist professional-record boundaries',
            'apply' => 'setup_apply_v0909',
            'ready' => 'setup_v0909_ready',
        ],
        [
            'version' => '0.9.1.0',
            'migration_key' => '0.9.1.0_first_seating_task_context_role_switch',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.1.0_first_seating_task_context_role_switch.sql',
            'label' => 'Move dental-safety review to first seating, isolate Task View context, and enforce pre-treatment readiness',
            'apply' => 'setup_apply_v0910',
            'ready' => 'setup_v0910_ready',
        ],
        [
            'version' => '0.9.1.1',
            'migration_key' => '0.9.1.1_allergy_canonicalization_timeline_print_activity',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.1.1_allergy_canonicalization_timeline_print_activity.sql',
            'label' => 'Canonicalize current dental-safety records, preserve the change timeline, and expose print activity',
            'apply' => 'setup_apply_v0911',
            'ready' => 'setup_v0911_ready',
        ],
        [
            'version' => '0.9.1.2',
            'migration_key' => '0.9.1.2_patient_portal_controlled_record_release',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.1.2_patient_portal_controlled_record_release.sql',
            'label' => 'Add patient portal navigation and controlled dental-record release',
            'apply' => 'setup_apply_v0912',
            'ready' => 'setup_v0912_ready',
        ],
        [
            'version' => '0.9.1.3',
            'migration_key' => '0.9.1.3_navigation_role_integrity_workflow_recovery',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.1.3_navigation_role_integrity_workflow_recovery.sql',
            'label' => 'Stabilize navigation, centralize route authorization, and enable safe workspace recovery',
            'apply' => 'setup_apply_v0913',
            'ready' => 'setup_v0913_ready',
        ],
        [
            'version' => '0.9.1.4',
            'migration_key' => '0.9.1.4_transaction_integrity_duplicate_submission',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.1.4_transaction_integrity_duplicate_submission.sql',
            'label' => 'Prevent duplicate submissions, record concurrent operations, and expose transaction-integrity monitoring',
            'apply' => 'setup_apply_v0914',
            'ready' => 'setup_v0914_ready',
        ],
        [
            'version' => '0.9.2.0',
            'migration_key' => '0.9.2.0_update_state_release_manifest_client_docs',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.2.0_update_state_release_manifest_client_docs.sql',
            'label' => 'Unify update status, move release state outside documentation, and install the current client documentation pack',
            'apply' => 'setup_apply_v0920',
            'ready' => 'setup_v0920_ready',
        ],
        [
            'version' => '0.9.3.0',
            'migration_key' => '0.9.3.0_operational_health_recovery_sidebar',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.3.0_operational_health_recovery_sidebar.sql',
            'label' => 'Add operational health and recovery evidence, keep optional warnings non-blocking, and separate system recovery navigation',
            'apply' => 'setup_apply_v0930',
            'ready' => 'setup_v0930_ready',
        ],
        [
            'version' => '0.9.4.0',
            'migration_key' => '0.9.4.0_query_regression_joined_column_safety',
            'migration_file' => DENTAL_ROOT . '/database/migrations/0.9.4.0_query_regression_joined_column_safety.sql',
            'label' => 'Repair joined-column ambiguity and add automated read-only SQL query regression gates',
            'apply' => 'setup_apply_v0940',
            'ready' => 'setup_v0940_ready',
        ],
        [
            'version' => '1.0.0.0',
            'migration_key' => '1.0.0.0_production_data_integrity_baseline',
            'migration_file' => DENTAL_ROOT . '/database/migrations/1.0.0.0_production_data_integrity_baseline.sql',
            'label' => 'Install production database contracts, data-integrity evidence, and safe request-ledger maintenance',
            'apply' => 'setup_apply_v1000',
            'ready' => 'setup_v1000_ready',
        ],
    ];

    dental_migration_ledger_ensure_schema($pdo);
    $plan = [];
    foreach ($definitions as $step) {
        $file = (string)$step['migration_file'];
        $step['checksum_sha256'] = is_file($file) ? (string)hash_file('sha256', $file) : '';
        if (!dental_migration_ledger_completed($pdo, (string)$step['migration_key'], (string)$step['checksum_sha256'])) {
            $plan[] = $step;
        }
    }
    return $plan;
}

function setup_v0586_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo,table('patient_portal_requests'))
        && setup_index_exists($pdo,table('patient_portal_requests'),'idx_portal_request_patient')
        && setup_index_exists($pdo,table('patient_portal_requests'),'idx_portal_request_status')
        && setup_index_exists($pdo,table('patient_portal_requests'),'idx_portal_request_appointment')
        && setup_table_exists($pdo,table('dental_condition_catalog'))
        && setup_table_exists($pdo,table('dental_chart_reviews'))
        && setup_column_exists($pdo,table('odontogram_entries'),'finding_type')
        && setup_column_exists($pdo,table('odontogram_entries'),'severity')
        && setup_column_exists($pdo,table('treatment_plan_items'),'odontogram_entry_id')
        && setup_column_exists($pdo,table('treatment_plan_items'),'clinical_rationale');
}

function setup_backup_printing_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo, table('print_logs'))
        && setup_column_exists($pdo, table('backup_logs'), 'backup_type')
        && setup_column_exists($pdo, table('backup_logs'), 'checksum_sha256')
        && setup_column_exists($pdo, table('backup_logs'), 'file_count')
        && setup_column_exists($pdo, table('backup_logs'), 'verified_at');
}

function setup_data_tools_ready(PDO $pdo): bool
{
    return setup_table_exists($pdo, table('data_import_batches'))
        && setup_table_exists($pdo, table('demo_record_registry'))
        && setup_table_exists($pdo, table('data_reset_logs'))
        && setup_column_exists($pdo, table('users'), 'employee_no')
        && setup_column_exists($pdo, table('patients'), 'is_demo')
        && setup_column_exists($pdo, table('appointment_requests'), 'is_demo');
}

function setup_create_backup(PDO $pdo): array
{
    $prefix = (string) app_config('database.prefix', 'dent_');
    $likePrefix = str_replace(['\\','%','_'], ['\\\\','\\%','\\_'], $prefix) . '%';
    $stmt = $pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema=? AND table_name LIKE ? ESCAPE \'\\\\\' ORDER BY table_name');
    $stmt->execute([(string) app_config('database.name'), $likePrefix]);
    $tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
    if (!$tables) throw new RuntimeException('No application tables were found.');
    $fileName = 'dentalcare_' . date('Ymd_His') . '.sql';
    $path = backup_directory() . '/' . $fileName;
    $handle = fopen($path, 'wb');
    if (!$handle) throw new RuntimeException('Unable to create backup file.');
    fwrite($handle, "-- DentalCare database backup\n-- Created " . date(DATE_ATOM) . "\nSET FOREIGN_KEY_CHECKS=0;\n\n");
    foreach ($tables as $tableName) {
        $safe = str_replace('`','``',(string)$tableName);
        $create = $pdo->query('SHOW CREATE TABLE `' . $safe . '`')->fetch(PDO::FETCH_NUM);
        fwrite($handle, 'DROP TABLE IF EXISTS `' . $safe . "`;\n" . ($create[1] ?? '') . ";\n\n");
        $rows = $pdo->query('SELECT * FROM `' . $safe . '`');
        while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
            $columns = array_map(static fn(string $column): string => '`' . str_replace('`','``',$column) . '`', array_keys($row));
            $values = array_map(static fn($value): string => $value === null ? 'NULL' : $pdo->quote((string)$value), array_values($row));
            fwrite($handle, 'INSERT INTO `' . $safe . '` (' . implode(',', $columns) . ') VALUES (' . implode(',', $values) . ");\n");
        }
        fwrite($handle, "\n");
    }
    fwrite($handle, "SET FOREIGN_KEY_CHECKS=1;\n");
    fclose($handle);
    $size = filesize($path) ?: 0;
    $pdo->prepare('INSERT INTO `' . table('backup_logs') . '` (file_name,file_size,status,created_by,created_at) VALUES (?,?,"created",?,NOW())')
        ->execute([$fileName,$size,(int) current_user()['id']]);
    audit('database_backup_created','backup',null,['file'=>$fileName,'size'=>$size]);
    return [$fileName,$size];
}

function setup_store_uploaded_backup(PDO $pdo): array
{
    if (!isset($_FILES['backup_file']) || $_FILES['backup_file']['error'] !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Choose a SQL or compressed SQL backup to upload.');
    }
    $file = $_FILES['backup_file'];
    if ((int) $file['size'] <= 0 || (int) $file['size'] > 100 * 1024 * 1024) {
        throw new RuntimeException('Backup files must be between 1 byte and 100 MB.');
    }
    $original = basename((string) $file['name']);
    $extension = strtolower(pathinfo($original, PATHINFO_EXTENSION));
    $allowed = ['sql','gz','zip'];
    if (!in_array($extension, $allowed, true)) {
        throw new RuntimeException('Only .sql, .gz, and .zip backup files are accepted.');
    }
    $fileName = 'uploaded_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $extension;
    $path = backup_directory() . '/' . $fileName;
    if (!move_uploaded_file((string) $file['tmp_name'], $path)) {
        throw new RuntimeException('The backup could not be stored. Check storage/backups permissions.');
    }
    @chmod($path, 0640);
    $size = filesize($path) ?: 0;
    $checksum = hash_file('sha256', $path) ?: '';
    $backupType = $extension === 'zip' ? 'uploaded_package' : 'database';
    $pdo->prepare('INSERT INTO `' . table('backup_logs') . '` (file_name,file_size,status,created_by,notes,created_at,backup_type,checksum_sha256,file_count,is_encrypted) VALUES (?,?,"uploaded",?, ?,NOW(),?,?,0,0)')
        ->execute([$fileName,$size,(int) current_user()['id'],'Uploaded backup: ' . $original,$backupType,$checksum]);
    audit('database_backup_uploaded','backup',null,['file'=>$fileName,'original'=>$original,'size'=>$size]);
    return [$fileName,$size];
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        $action = (string) ($_POST['action'] ?? '');
        if (in_array($action, ['create_backup', 'verify_backup', 'upload_backup'], true)) {
            verify_csrf_or_redirect('system_setup.php?tab=backups', 'backup request');
        } else {
            verify_csrf();
        }
        if ($action === 'save_deployment_mode') {
            $mode = strtolower(trim((string)($_POST['deployment_mode'] ?? 'production')));
            if (!function_exists('dental_deployment_modes') || !array_key_exists($mode, dental_deployment_modes())) {
                throw new RuntimeException('Choose a valid deployment mode.');
            }
            $pdo->beginTransaction();
            setting_set('system', 'deployment_mode', $mode);
            setting_set('system', 'show_internal_qa_tools', isset($_POST['show_internal_qa_tools']) ? '1' : '0');
            audit_required('deployment_mode_updated', 'system', null, [
                'mode' => $mode,
                'show_internal_qa_tools' => isset($_POST['show_internal_qa_tools']) ? 1 : 0,
            ]);
            $pdo->commit();
            flash('success', 'Deployment environment saved: ' . dental_deployment_mode_label($mode) . '.');
            header('Location: system_setup.php?tab=environment'); exit;
        }
        if ($action === 'apply_update' || $action === 'apply_update_step') {
            $plan = setup_resumable_update_plan($pdo);
            $result = dental_update_checkpoint_run_next($pdo, $plan, app_release_version());
            $isAjax = (string)($_POST['ajax'] ?? '') === '1'
                || str_contains(strtolower((string)($_SERVER['HTTP_ACCEPT'] ?? '')), 'application/json');
            if ($isAjax) {
                // Discard any incidental warning/output emitted by a legacy
                // readiness callback so the response remains valid JSON.
                while (ob_get_level() > 0) {
                    ob_end_clean();
                }
                header('Content-Type: application/json; charset=utf-8');
                header('Cache-Control: no-store, no-cache, must-revalidate');
                http_response_code((string)($result['status'] ?? '') === 'failed' ? 422 : 200);
                echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
                exit;
            }
            if ((string)($result['status'] ?? '') === 'completed') {
                flash('success', (string)($result['message'] ?? 'Update completed.'));
            } elseif ((string)($result['status'] ?? '') === 'failed') {
                flash('error', (string)($result['message'] ?? 'Migration failed.') . ' Reference: ' . (string)($result['error_reference'] ?? ''));
            } else {
                flash('success', (string)($result['message'] ?? 'Update step completed. Click Resume update to continue.'));
            }
            header('Location: system_setup.php?tab=updates');
            exit;
        }
        if ($action === 'save_permissions') {
            require_documentation_schema();
            $roleId = (int) ($_POST['role_id'] ?? 0);
            if ($roleId <= 0) throw new RuntimeException('Select a role.');
            $permissionIds = array_map('intval', (array) ($_POST['permissions'] ?? []));
            $pdo->beginTransaction();
            $pdo->prepare('DELETE FROM `' . table('role_permissions') . '` WHERE role_id=?')->execute([$roleId]);
            $insert = $pdo->prepare('INSERT INTO `' . table('role_permissions') . '` (role_id,permission_id,allowed) VALUES (?,?,1)');
            foreach ($permissionIds as $permissionId) if ($permissionId > 0) $insert->execute([$roleId,$permissionId]);
            $pdo->commit();
            audit('role_permissions_updated','role',$roleId,['permissions'=>$permissionIds]);
            flash('success','Role permissions saved.');
            header('Location: system_setup.php?tab=permissions&role_id=' . $roleId); exit;
        }
        if ($action === 'create_backup') {
            require_documentation_schema();
            if (!setup_backup_printing_ready($pdo)) throw new RuntimeException('Apply Version 0.5.3 before creating managed system backups.');
            $type = (string) ($_POST['backup_type'] ?? 'complete');
            $password = trim((string) ($_POST['backup_password'] ?? ''));
            $service = new SystemBackupService($pdo);
            $result = $service->create($type, $password !== '' ? $password : null);
            flash('success', ucfirst($type) . ' backup created: ' . $result['file_name'] . '. SHA-256: ' . $result['checksum']);
            header('Location: system_setup.php?tab=backups'); exit;
        }
        if ($action === 'verify_backup') {
            require_documentation_schema();
            if (!setup_backup_printing_ready($pdo)) throw new RuntimeException('Apply Version 0.5.3 before verifying backups.');
            $backupId = (int) ($_POST['backup_id'] ?? 0);
            $stmt = $pdo->prepare('SELECT * FROM `' . table('backup_logs') . '` WHERE id=? LIMIT 1');
            $stmt->execute([$backupId]);
            $backup = $stmt->fetch();
            if (!$backup) throw new RuntimeException('Backup record not found.');
            (new SystemBackupService($pdo))->verify($backup);
            flash('success','Backup checksum and package structure verified.');
            header('Location: system_setup.php?tab=backups'); exit;
        }
        if ($action === 'upload_backup') {
            require_documentation_schema();
            if (!setup_backup_printing_ready($pdo)) throw new RuntimeException('Apply Version 0.5.3 before uploading managed backup packages.');
            [$fileName] = setup_store_uploaded_backup($pdo);
            flash('success','Backup uploaded securely: ' . $fileName . '. It has not been restored.');
            header('Location: system_setup.php?tab=backups'); exit;
        }
        if ($action === 'save_security') {
            $timeout = max(5, min(480, (int) ($_POST['session_timeout'] ?? 60)));
            $requirePrivilegedMfa = isset($_POST['require_mfa_privileged']);
            if ($requirePrivilegedMfa && !extension_loaded('openssl')) {
                throw new RuntimeException('Privileged MFA cannot be enabled because the PHP OpenSSL extension is unavailable. Enable OpenSSL, then save the policy again.');
            }
            setting_set('security','session_timeout_minutes',(string)$timeout);
            setting_set('security','require_mfa_privileged',$requirePrivilegedMfa?'1':'0');
            setting_set('security','record_export_reason',isset($_POST['record_export_reason'])?'1':'0');
            audit('security_settings_updated','settings',null,['session_timeout'=>$timeout]);
            flash('success','Security policy saved.');
            header('Location: system_setup.php?tab=security'); exit;
        }
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $reference = strtoupper(substr(hash('sha256', microtime(true) . '|' . get_class($exception) . '|' . $exception->getMessage()), 0, 10));
        if (function_exists('dental_runtime_write_log')) {
            dental_runtime_write_log('System Setup request failed [' . $reference . ']: ' . $exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine());
        } else {
            error_log('DentalCare System Setup request failed [' . $reference . ']: ' . $exception->getMessage());
        }
        if (!empty($__dentalUpdateAjaxRequest)) {
            while (ob_get_level() > 0) {
                ob_end_clean();
            }
            header('Content-Type: application/json; charset=utf-8');
            header('Cache-Control: no-store, no-cache, must-revalidate');
            http_response_code(422);
            echo json_encode([
                'status' => 'failed',
                'message' => 'The update step could not start. The checkpoint remains safe.',
                'last_error' => $exception->getMessage(),
                'error_reference' => $reference,
                'percent' => 0,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
            exit;
        }
        flash('error',$exception->getMessage() . ' Reference: ' . $reference);
        header('Location: system_setup.php?tab=' . urlencode($tab)); exit;
    }
}

$license = dental_license_status();
$mail = (array) app_config('mail', []);
$storageWritable = is_writable(DENTAL_ROOT . '/storage');
$configWritable = is_writable(DENTAL_ROOT . '/config');
$currentVersion = installed_application_version();
$releaseVersion = app_release_version();
$automaticUpdateReconciliation = ['ready'=>false,'reconciled'=>[],'pending'=>[],'errors'=>[]];
$deterministicUpdatePlan = [];
try {
    // A previous request may have completed all idempotent writes and failed only
    // before recording the ledger/checkpoint. Repair that status automatically;
    // no migration apply function is called from this GET-time reconciliation.
    $deterministicUpdatePlan = setup_resumable_update_plan($pdo);
    if ($deterministicUpdatePlan && function_exists('dental_update_checkpoint_reconcile_ready')) {
        $automaticUpdateReconciliation = dental_update_checkpoint_reconcile_ready($pdo, $deterministicUpdatePlan, $releaseVersion);
        $currentVersion = installed_application_version();
        $deterministicUpdatePlan = setup_resumable_update_plan($pdo);
    }
} catch (Throwable $exception) {
    $automaticUpdateReconciliation['errors']['status'] = mb_substr($exception->getMessage(), 0, 1000);
}
$deterministicPlanPending = $deterministicUpdatePlan !== [];
$updateReadinessReport = dental_update_readiness_report($pdo);
$updateReadinessSummary = dental_update_readiness_summary($pdo);
$updateRecoverySummary = dental_update_recovery_summary($pdo, $releaseVersion);
$releaseHardeningSummary = function_exists('dental_release_hardening_summary') ? dental_release_hardening_summary($pdo) : ['ready'=>false,'package_policy'=>[],'runtime_warnings'=>[],'backup'=>['ready'=>false,'status'=>'Unavailable'],'storage'=>[]];
$auditIntegrity = $tab === 'security' && function_exists('audit_integrity_summary') ? audit_integrity_summary(500) : ['ready'=>false,'checked'=>0,'valid'=>0,'invalid'=>0,'unhashed'=>0,'latest_id'=>0,'error'=>null];
$coreUpdatePending = version_compare($currentVersion, $releaseVersion, '<');
if (version_compare($releaseVersion, '0.9.0.0', '>=')) {
    // v0.9+ update buttons are driven only by a newer package version or an
    // exact pending migration plan. Readiness diagnostics cannot create a
    // button after the current-release ledger is complete.
    $readinessPending = false;
} elseif (version_compare($releaseVersion, '0.8.1.2', '>=')) {
    $readinessPending = !setup_v0812_ready($pdo)
        || (function_exists('dental_update_terminal_apply_ready')
            && !dental_update_terminal_apply_ready($pdo, $releaseVersion));
} else {
    $readinessPending = !empty($updateRecoverySummary['missing_versions'])
        || !empty($updateRecoverySummary['missing_apply_functions'])
        || !empty($updateRecoverySummary['schema_drift_detected'])
        || !empty($updateReadinessSummary['missing'])
        || (version_compare($releaseVersion,'0.5.9.8.56','>=') && !setup_v059856_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.57','>=') && !setup_v059857_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.58','>=') && !setup_v059858_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.59','>=') && !setup_v059859_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.60','>=') && !setup_v059860_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.61','>=') && !setup_v059861_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.62','>=') && !setup_v059862_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.63','>=') && !setup_v059863_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.64','>=') && !setup_v059864_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.65','>=') && !setup_v059865_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.65.1','>=') && !setup_v0598651_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.66','>=') && !setup_v059866_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.66.2','>=') && !setup_v0598662_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.66.3','>=') && !setup_v0598663_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.66.4','>=') && !setup_v0598664_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.67','>=') && !setup_v059867_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.68','>=') && !setup_v059868_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.69','>=') && !setup_v059869_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.70','>=') && !setup_v059870_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.71','>=') && !setup_v059871_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.71.1','>=') && !setup_v0598711_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.71.2','>=') && !setup_v0598712_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.74','>=') && !setup_v059874_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.75','>=') && !setup_v059875_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.76','>=') && !setup_v059876_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.79','>=') && !setup_v059879_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.80','>=') && !setup_v059880_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.81','>=') && !setup_v059881_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.82','>=') && !setup_v059882_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.83','>=') && !setup_v059883_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.84','>=') && !setup_v059884_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.85','>=') && !setup_v059885_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.86','>=') && !setup_v059886_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.87','>=') && !setup_v059887_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.88','>=') && !setup_v059888_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.89','>=') && !setup_v059889_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.90','>=') && !setup_v059890_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.91','>=') && !setup_v059891_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.97','>=') && !setup_v059897_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.98','>=') && !setup_v059898_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.99','>=') && !setup_v059899_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.100','>=') && !setup_v0598100_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.101','>=') && !setup_v0598101_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.102','>=') && !setup_v0598102_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.103','>=') && !setup_v0598103_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.104','>=') && !setup_v0598104_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.105','>=') && !setup_v0598105_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.106','>=') && !setup_v0598106_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.107','>=') && !setup_v0598107_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.108','>=') && !setup_v0598108_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.109','>=') && !setup_v0598109_ready($pdo))
        || (version_compare($releaseVersion,'0.5.9.8.110','>=') && !setup_v0598110_ready($pdo))
        || (version_compare($releaseVersion,'0.6.0','>=') && !setup_v0600_ready($pdo))
        || (version_compare($releaseVersion,'0.6.1','>=') && function_exists('setup_v0601_ready') && !setup_v0601_ready($pdo))
        || (version_compare($releaseVersion,'0.6.2','>=') && function_exists('setup_v0602_ready') && !setup_v0602_ready($pdo))
        || (version_compare($releaseVersion,'0.6.2.1','>=') && function_exists('setup_v06021_ready') && !setup_v06021_ready($pdo))
        || (version_compare($releaseVersion,'0.6.2.2','>=') && function_exists('setup_v06022_ready') && !setup_v06022_ready($pdo))
        || (version_compare($releaseVersion,'0.6.3','>=') && function_exists('setup_v0603_ready') && !setup_v0603_ready($pdo))
        || (version_compare($releaseVersion,'0.6.3.1','>=') && function_exists('setup_v06031_ready') && !setup_v06031_ready($pdo))
        || (version_compare($releaseVersion,'0.6.4','>=') && function_exists('setup_v0604_ready') && !setup_v0604_ready($pdo))
        || (version_compare($releaseVersion,'0.6.5','>=') && function_exists('setup_v0605_ready') && !setup_v0605_ready($pdo))
        || (version_compare($releaseVersion,'0.6.6','>=') && function_exists('setup_v0606_ready') && !setup_v0606_ready($pdo))
        || (version_compare($releaseVersion,'0.6.7','>=') && function_exists('setup_v0607_ready') && !setup_v0607_ready($pdo))
        || (version_compare($releaseVersion,'0.6.8','>=') && function_exists('setup_v0608_ready') && !setup_v0608_ready($pdo))
        || (version_compare($releaseVersion,'0.6.9','>=') && function_exists('setup_v0609_ready') && !setup_v0609_ready($pdo))
        || (version_compare($releaseVersion,'0.6.9.1','>=') && function_exists('setup_v06091_ready') && !setup_v06091_ready($pdo))
        || (version_compare($releaseVersion,'0.7.0','>=') && function_exists('setup_v0700_ready') && !setup_v0700_ready($pdo))
        || (version_compare($releaseVersion,'0.7.1','>=') && function_exists('setup_v0710_ready') && !setup_v0710_ready($pdo))
        || (version_compare($releaseVersion,'0.7.2','>=') && function_exists('setup_v0720_ready') && !setup_v0720_ready($pdo))
        || (version_compare($releaseVersion,'0.7.3','>=') && function_exists('setup_v0730_ready') && !setup_v0730_ready($pdo))
        || (version_compare($releaseVersion,'0.7.4','>=') && function_exists('setup_v0740_ready') && !setup_v0740_ready($pdo))
        || (version_compare($releaseVersion,'0.7.5','>=') && function_exists('setup_v0750_ready') && !setup_v0750_ready($pdo))
        || (version_compare($releaseVersion,'0.7.5.1','>=') && function_exists('setup_v0751_ready') && !setup_v0751_ready($pdo))
        || (version_compare($releaseVersion,'0.7.5.2','>=') && function_exists('setup_v0752_ready') && !setup_v0752_ready($pdo))
        || (version_compare($releaseVersion,'0.7.6','>=') && function_exists('setup_v0760_ready') && !setup_v0760_ready($pdo))
        || (version_compare($releaseVersion,'0.7.6.1','>=') && function_exists('setup_v0761_ready') && !setup_v0761_ready($pdo))
        || (version_compare($releaseVersion,'0.7.6.2','>=') && function_exists('setup_v0762_ready') && !setup_v0762_ready($pdo))
        || (version_compare($releaseVersion,'0.7.6.3','>=') && function_exists('setup_v0763_ready') && !setup_v0763_ready($pdo))
        || (version_compare($releaseVersion,'0.7.6.4','>=') && function_exists('setup_v0764_ready') && !setup_v0764_ready($pdo))
        || (version_compare($releaseVersion,'0.7.6.5','>=') && function_exists('setup_v0765_ready') && !setup_v0765_ready($pdo))
        || (version_compare($releaseVersion,'0.8.0.5','>=') && function_exists('setup_v0805_ready') && !setup_v0805_ready($pdo))
        || (version_compare($releaseVersion,'0.8.1.0','>=') && function_exists('setup_v0810_ready') && !setup_v0810_ready($pdo))
        || (version_compare($releaseVersion,'0.8.1.1','>=') && function_exists('setup_v0811_ready') && !setup_v0811_ready($pdo))
        || (version_compare($releaseVersion,'0.8.1.2','>=') && function_exists('setup_v0812_ready') && !setup_v0812_ready($pdo))
        || (function_exists('dental_update_terminal_apply_ready') && !dental_update_terminal_apply_ready($pdo, $releaseVersion));
}
$pending = $coreUpdatePending || $readinessPending || $deterministicPlanPending;
$updatePendingReasons = $pending && function_exists('dental_update_pending_reason_rows') ? dental_update_pending_reason_rows($pdo, $releaseVersion) : [];
if ($deterministicPlanPending) {
    $updatePendingReasons[] = [
        'label' => 'Deterministic migration ledger',
        'status' => 'The current release still has an incomplete or failed exact migration ledger entry.',
    ];
}
$latestUpdateRun = function_exists('dental_update_checkpoint_latest') ? dental_update_checkpoint_latest($pdo) : null;
$latestUpdateRunPublic = $latestUpdateRun ? dental_update_checkpoint_public($latestUpdateRun) : [];
$updateCanResume = $pending
    && in_array((string)($latestUpdateRunPublic['status'] ?? ''), ['pending','running','failed'], true)
    && (string)($latestUpdateRunPublic['target_version'] ?? '') === $releaseVersion;

render_header('System Control Center');
?>
<div class="setup-tabs tabs">
    <?php foreach (['overview'=>'Overview','updates'=>'Updates & Database','permissions'=>'Roles & Permissions','email'=>'Email','backups'=>'Backup & Upload','environment'=>'Environment','security'=>'Security'] as $key=>$label): ?><a class="<?= $tab===$key?'active':'' ?>" href="system_setup.php?tab=<?= e($key) ?>"><?= e($label) ?></a><?php endforeach; ?>
</div>

<?php if ($tab === 'overview'): ?>
<section class="stats setup-stats">
    <?php stat_card('Installed version',$currentVersion,$pending?'Update available':'Current'); ?>
    <?php stat_card('Database',application_schema_ready()?'Connected':'Needs attention',(string) app_config('database.name')); ?>
    <?php stat_card('Email',strtoupper((string)($mail['driver']??'smtp')),($mail['from_email']??'Not configured')); ?>
    <?php stat_card('License',($license['valid']??false)?'Valid':'Required',dental_current_host()); ?>
</section>
<div class="setup-grid">
    <section class="card system-setup-paged-card" data-system-setup-paginate data-system-setup-item-selector=".health-list > div" data-system-setup-default-size="10"><div class="card-head"><div><h2>System readiness</h2><p>One place for updates, configuration, security, and operational setup.</p></div></div>
        <div class="health-list">
            <div><span>Core patient and appointment schema</span><strong class="<?= application_schema_ready()?'ok':'bad' ?>"><?= application_schema_ready()?'Ready':'Missing' ?></strong></div>
            <div><span>Doctor schedule builder and operation hours</span><strong class="<?= availability_schema_ready()?'ok':'bad' ?>"><?= availability_schema_ready()?'Ready':'Missing' ?></strong></div>
            <div><span>Appointment, walk-in, and cashier queue</span><strong class="<?= queue_cashier_schema_ready()?'ok':'bad' ?>"><?= queue_cashier_schema_ready()?'Ready':'Missing' ?></strong></div>
            <div><span>Documented clinical and operations modules</span><strong class="<?= documentation_schema_ready()?'ok':'warn' ?>"><?= documentation_schema_ready()?'Ready':'Pending update' ?></strong></div>
            <div><span>Dentist professional setup, Clinical Day, and SMTP diagnostics</span><strong class="<?= doctor_workflow_schema_ready()?'ok':'warn' ?>"><?= doctor_workflow_schema_ready()?'Ready':'Pending update' ?></strong></div>
            <div><span>Superuser QA smoke test and release deny-list</span><strong class="<?= setup_v059862_ready($pdo)?'ok':'warn' ?>"><?= setup_v059862_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Staff invitations and email templates</span><strong class="<?= email_template_schema_ready()&&staff_invite_schema_ready()?'ok':'warn' ?>"><?= email_template_schema_ready()&&staff_invite_schema_ready()?'Ready':'Pending update' ?></strong></div>
            <div><span>SMS readiness, provider setup, and templates</span><strong class="<?= function_exists('setup_v059868_ready')&&setup_v059868_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v059868_ready')&&setup_v059868_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Formal chairs and dentist bindings</span><strong class="<?= clinic_chair_schema_ready()?'ok':'warn' ?>"><?= clinic_chair_schema_ready()?'Ready':'Pending update' ?></strong></div>
            <div><span>In-house care teams and shared-chair assignment</span><strong class="<?= setup_v0584_ready($pdo)?'ok':'warn' ?>"><?= setup_v0584_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Automatic email delivery and entity tracking</span><strong class="<?= setup_v0585_ready($pdo)?'ok':'warn' ?>"><?= setup_v0585_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Patient self-service and clinic request review</span><strong class="<?= setup_v0586_ready($pdo)?'ok':'warn' ?>"><?= setup_v0586_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Self-service full and demo installer generation</span><strong class="<?= setup_v0587_ready($pdo)?'ok':'warn' ?>"><?= setup_v0587_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Doctor treatment dashboard and anatomical odontogram</span><strong class="<?= setup_v0588_ready($pdo)?'ok':'warn' ?>"><?= setup_v0588_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Exclusive chair and treatment transitions</span><strong class="<?= setup_v0589_ready($pdo)?'ok':'warn' ?>"><?= setup_v0589_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Interactive tooth chart and doctor profile images</span><strong class="<?= setup_v0590_ready($pdo)?'ok':'warn' ?>"><?= setup_v0590_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Fully visible wrapping workspace navigation</span><strong class="<?= setup_v0591_ready($pdo)?'ok':'warn' ?>"><?= setup_v0591_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Doctor completion, X-rays, keyboard entry, and complete records</span><strong class="<?= setup_v0592_ready($pdo)?'ok':'warn' ?>"><?= setup_v0592_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Back navigation, strict queue flow, and cashier POS</span><strong class="<?= setup_v0593_ready($pdo)?'ok':'warn' ?>"><?= setup_v0593_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Inventory procedure branch repair and separated Website navigation</span><strong class="<?= setup_v05981_ready($pdo)?'ok':'warn' ?>"><?= setup_v05981_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Future patient access, walk-in autofill, stock catalog, Super navigation, treatment handoff, in-house checkups, and unused-profile correction</span><strong class="<?= setup_v05982_ready($pdo)?'ok':'warn' ?>"><?= setup_v05982_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Provider choice, clinical task routing, privilege-aware dashboards, credential history, and privilege-panel clarity</span><strong class="<?= setup_v05983_ready($pdo)?'ok':'warn' ?>"><?= setup_v05983_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Dentist dashboard compatibility, branch-scoped records, cashier privacy, and guardian/dependent portal access</span><strong class="<?= setup_v05984_ready($pdo)?'ok':'warn' ?>"><?= setup_v05984_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Unlimited medicine catalog, staff follow-up booking, and Dentist diagnostic review tasks</span><strong class="<?= setup_v05985_ready($pdo)?'ok':'warn' ?>"><?= setup_v05985_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Reliable consent saving and protected local session storage</span><strong class="<?= setup_v05986_ready($pdo)?'ok':'warn' ?>"><?= setup_v05986_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Guided Clinical Encounter, source-traceable supplies/dispensing, and live patient filtering</span><strong class="<?= setup_v05987_ready($pdo)?'ok':'warn' ?>"><?= setup_v05987_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Consent signer guidance, required-field keyboard flow, and preserved clinical sidebar/topbar</span><strong class="<?= setup_v05988_ready($pdo)?'ok':'warn' ?>"><?= setup_v05988_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Consistent finalization checks, intake/HPI synchronization, and explicit follow-up disposition</span><strong class="<?= setup_v05989_ready($pdo)?'ok':'warn' ?>"><?= setup_v05989_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Billing compatibility, tooth-specific caries surfaces, and synchronized patient dental charts</span><strong class="<?= setup_v059810_ready($pdo)?'ok':'warn' ?>"><?= setup_v059810_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Procedure-first Cashier Register, patient/payee lookup, and payer identity</span><strong class="<?= setup_v059811_ready($pdo)?'ok':'warn' ?>"><?= setup_v059811_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Cashier page-load compatibility and partial billing-schema repair</span><strong class="<?= setup_v059812_ready($pdo)?'ok':'warn' ?>"><?= setup_v059812_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Workflow governance, Dentist handoff, Cashier correction routing, and in-house billing</span><strong class="<?= setup_v059816_ready($pdo)?'ok':'warn' ?>"><?= setup_v059816_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Four-phase Dentist workspace and separated Cashier Exceptions</span><strong class="<?= setup_v059817_ready($pdo)?'ok':'warn' ?>"><?= setup_v059817_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Scalable management workspaces and update-registry alignment</span><strong class="<?= (setup_v059818_ready($pdo) && setup_v0598181_ready($pdo))?'ok':'warn' ?>"><?= (setup_v059818_ready($pdo) && setup_v0598181_ready($pdo))?'Ready':'Pending update' ?></strong></div>
            <div><span>Itemized payment receipts and compact weekly appointment status view</span><strong class="<?= setup_v059819_ready($pdo)?'ok':'warn' ?>"><?= setup_v059819_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Receipt resilience, exact service breakdown, and visit-centric Patient Journey</span><strong class="<?= (setup_v059820_ready($pdo) && setup_v0598201_ready($pdo) && setup_v059821_ready($pdo))?'ok':'warn' ?>"><?= (setup_v059820_ready($pdo) && setup_v0598201_ready($pdo) && setup_v059821_ready($pdo))?'Ready':'Pending update' ?></strong></div>
            <div><span>Canonical visit state and Operations Exception Center</span><strong class="<?= setup_v059822_ready($pdo)?'ok':'warn' ?>"><?= setup_v059822_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Workflow Exception and Reconciliation Center</span><strong class="<?= setup_v059829_ready($pdo)?'ok':'warn' ?>"><?= setup_v059829_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Compact dashboard guidance and Help &amp; Support</span><strong class="<?= setup_v059830_ready($pdo)?'ok':'warn' ?>"><?= setup_v059830_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Monthly schedule and usability hardening</span><strong class="<?= setup_v059831_ready($pdo)?'ok':'warn' ?>"><?= setup_v059831_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Appointment booking, profile names, and service disposition integrity</span><strong class="<?= setup_v059832_ready($pdo)?'ok':'warn' ?>"><?= setup_v059832_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Returning booking, API bootstrap, and package hygiene hotfix</span><strong class="<?= setup_v059833_ready($pdo)?'ok':'warn' ?>"><?= setup_v059833_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Update safety, database readiness, and transaction foundation</span><strong class="<?= setup_v059834_ready($pdo)?'ok':'warn' ?>"><?= setup_v059834_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Cashier-only payment workspace and all-payables register</span><strong class="<?= setup_v059835_ready($pdo)?'ok':'warn' ?>"><?= setup_v059835_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Treatment-to-billing final flow and performed-services-only cashier handoff</span><strong class="<?= setup_v059836_ready($pdo)?'ok':'warn' ?>"><?= setup_v059836_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Schedule action overflow repair and collection report totals</span><strong class="<?= setup_v059837_ready($pdo)?'ok':'warn' ?>"><?= setup_v059837_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Patient account statement and payables print polish</span><strong class="<?= setup_v059838_ready($pdo)?'ok':'warn' ?>"><?= setup_v059838_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Print reason policy correction</span><strong class="<?= setup_v059839_ready($pdo)?'ok':'warn' ?>"><?= setup_v059839_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Patient portal statement transparency</span><strong class="<?= setup_v059840_ready($pdo)?'ok':'warn' ?>"><?= setup_v059840_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Major what-if resilience and balance-only payment</span><strong class="<?= setup_v059841_ready($pdo)?'ok':'warn' ?>"><?= setup_v059841_ready($pdo)?'Ready':'Pending update' ?></strong></div><div><span>Skipped-update recovery and compatibility guard</span><strong class="<?= setup_v059842_ready($pdo)?'ok':'warn' ?>"><?= setup_v059842_ready($pdo)?'Ready':'Pending update' ?></strong></div><div><span>Security hardening</span><strong class="<?= setup_v059843_ready($pdo)?'ok':'warn' ?>"><?= setup_v059843_ready($pdo)?'Ready':'Pending update' ?></strong></div><div><span>Appointment and schedule final guard</span><strong class="<?= setup_v059844_ready($pdo)?'ok':'warn' ?>"><?= setup_v059844_ready($pdo)?'Ready':'Pending update' ?></strong></div><div><span>Clinical record completion</span><strong class="<?= setup_v059845_ready($pdo)?'ok':'warn' ?>"><?= setup_v059845_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Role flow and active Dentist treatment continuity</span><strong class="<?= setup_v059823_ready($pdo)?'ok':'warn' ?>"><?= setup_v059823_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Structured chairside vital-sign capture</span><strong class="<?= setup_v059824_ready($pdo)?'ok':'warn' ?>"><?= setup_v059824_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Data import, demo records, and protected reset tools</span><strong class="<?= setup_data_tools_ready($pdo)?'ok':'warn' ?>"><?= setup_data_tools_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Major 360 role-flow stabilization</span><strong class="<?= setup_v0600_ready($pdo)?'ok':'warn' ?>"><?= setup_v0600_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Role-flow QA command center and sell-ready blocker gate</span><strong class="<?= setup_v0601_ready($pdo)?'ok':'warn' ?>"><?= setup_v0601_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.2 live workflow enforcement rails</span><strong class="<?= function_exists('setup_v0602_ready') && setup_v0602_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0602_ready') && setup_v0602_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.2.1 invoice modal polish and inventory batch upload</span><strong class="<?= function_exists('setup_v06021_ready') && setup_v06021_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v06021_ready') && setup_v06021_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.2.2 previous-day carryover queue resolution</span><strong class="<?= function_exists('setup_v06022_ready') && setup_v06022_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v06022_ready') && setup_v06022_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.3 global navigation and scroll governance</span><strong class="<?= function_exists('setup_v0603_ready') && setup_v0603_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0603_ready') && setup_v0603_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.3.1 queue cancellation, schedule sync, and offline guard</span><strong class="<?= function_exists('setup_v06031_ready') && setup_v06031_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v06031_ready') && setup_v06031_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.4 calendar history visibility and click-target governance</span><strong class="<?= function_exists('setup_v0604_ready') && setup_v0604_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0604_ready') && setup_v0604_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.5 demo reset integrity, email mail paths, and runtime cache hardening</span><strong class="<?= function_exists('setup_v0605_ready') && setup_v0605_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0605_ready') && setup_v0605_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.6 landing page editor and full public copy control</span><strong class="<?= function_exists('setup_v0606_ready') && setup_v0606_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0606_ready') && setup_v0606_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.7 role permission/navigation hardening</span><strong class="<?= function_exists('setup_v0607_ready') && setup_v0607_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0607_ready') && setup_v0607_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.8 update self-healing and inventory-treatment reconciliation</span><strong class="<?= function_exists('setup_v0608_ready') && setup_v0608_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0608_ready') && setup_v0608_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.9 unified mail engine and production deployment mode</span><strong class="<?= function_exists('setup_v0609_ready') && setup_v0609_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0609_ready') && setup_v0609_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.6.9.1 test mail diagnostics and patient queue ticket visibility</span><strong class="<?= function_exists('setup_v06091_ready') && setup_v06091_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v06091_ready') && setup_v06091_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.0 doctor treatment focus and live field validation</span><strong class="<?= function_exists('setup_v0700_ready') && setup_v0700_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0700_ready') && setup_v0700_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.1 treatment path matrix and odontogram baseline review</span><strong class="<?= function_exists('setup_v0710_ready') && setup_v0710_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0710_ready') && setup_v0710_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.2 dentist cockpit, patient clinical context, and update-chain repair</span><strong class="<?= function_exists('setup_v0720_ready') && setup_v0720_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0720_ready') && setup_v0720_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.3 single-task dentist cockpit and exact focus enforcement</span><strong class="<?= function_exists('setup_v0730_ready') && setup_v0730_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0730_ready') && setup_v0730_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.4 compact guide popouts and cleaner dentist form</span><strong class="<?= function_exists('setup_v0740_ready') && setup_v0740_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0740_ready') && setup_v0740_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.5 task-first cashier and odontogram layout priority</span><strong class="<?= function_exists('setup_v0750_ready') && setup_v0750_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0750_ready') && setup_v0750_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.5.1 request-failure guard and task-first dependency repair</span><strong class="<?= function_exists('setup_v0751_ready') && setup_v0751_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0751_ready') && setup_v0751_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.5.2 System Setup readiness guard</span><strong class="<?= function_exists('setup_v0752_ready') && setup_v0752_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0752_ready') && setup_v0752_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.6 role-first flow stabilization</span><strong class="<?= function_exists('setup_v0760_ready') && setup_v0760_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0760_ready') && setup_v0760_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.6.1 Dentist task guard and performance</span><strong class="<?= function_exists('setup_v0761_ready') && setup_v0761_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0761_ready') && setup_v0761_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.6.2 task-first cashier and retail product flow</span><strong class="<?= function_exists('setup_v0762_ready') && setup_v0762_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0762_ready') && setup_v0762_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.6.3 Superuser navigation and scalable email history</span><strong class="<?= function_exists('setup_v0763_ready') && setup_v0763_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0763_ready') && setup_v0763_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.6.4 route ownership and completed-task context repair</span><strong class="<?= function_exists('setup_v0764_ready') && setup_v0764_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0764_ready') && setup_v0764_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>0.7.6.5 security, role-flow, and transaction integrity</span><strong class="<?= function_exists('setup_v0765_ready') && setup_v0765_ready($pdo)?'ok':'warn' ?>"><?= function_exists('setup_v0765_ready') && setup_v0765_ready($pdo)?'Ready':'Pending update' ?></strong></div>
            <div><span>Protected storage</span><strong class="<?= $storageWritable?'ok':'bad' ?>"><?= $storageWritable?'Writable':'Not writable' ?></strong></div>
            <div><span>Configuration directory</span><strong class="<?= $configWritable?'warn':'ok' ?>"><?= $configWritable?'Writable — lock after setup':'Protected' ?></strong></div>
        </div>
        <?php if ($pending): ?><a class="btn primary" href="system_setup.php?tab=updates">Review pending update</a><?php endif; ?>
    </section>
    <section class="card"><h2>Clinic configuration</h2><div class="setup-link-grid">
        <a href="settings.php"><strong>Website Content</strong><span>Public contact information and landing-page content</span></a>
        <a href="operation_hours.php"><strong>Operation Hours</strong><span>Branch schedule and closure dates</span></a>
        <a href="doctor_schedule.php"><strong>Doctor Schedule Builder</strong><span>Recurring schedule, leave, and exceptions</span></a>
        <a href="services.php"><strong>Dental Services</strong><span>Duration, fees, preparation, and cleanup</span></a>
        <a href="staff.php"><strong>Staff Accounts &amp; Roles</strong><span>Accounts, work assignments, branches, and role access</span></a>
        <a href="chairs.php"><strong>Chairs & Resources</strong><span>Formal chair inventory, dentist binding, and maintenance</span></a>
        <a href="inhouse_team.php"><strong>In-house Care Team</strong><span>Assign staff to branch services and verify shared-chair coverage</span></a>
        <a href="email_templates.php"><strong>Email Templates</strong><span>Account, appointment, reminder, and receipt messages</span></a>
        <a href="branding.php"><strong>Branding</strong><span>Logo, favicon, colors, and footer</span></a>
        <a href="uploads.php"><strong>Files & Uploads</strong><span>Protected clinic policies, forms, templates, and references</span></a>
        <a href="data_tools.php"><strong>Data Import & Reset</strong><span>Import Excel or CSV demo records and safely reset clinic data</span></a>
        <a href="system_setup.php?tab=backups"><strong>Backup & Recovery</strong><span>Create, upload, and download protected database backups</span></a>
        <a href="installer_builder.php"><strong>Installer Builder</strong><span>Create licensed full and locked read-only demo installers</span></a>
        <a href="license.php"><strong>License</strong><span>Domain authorization and licensed features</span></a>
        <a href="reports.php"><strong>Reports</strong><span>Clinic activity, finance, and compliance</span></a>
    </div></section>
</div>
<?php elseif ($tab === 'updates'): ?>
<section class="card update-center">
    <div class="card-head"><div><h2>Application updates</h2><p>DentalCare applies one verified release step per request. If the browser closes, PHP times out, or the network drops, reopen this page and choose Resume update.</p></div><span class="version-pill">Release <?= e($releaseVersion) ?></span></div>
    <div class="update-version-row"><div><span>Installed</span><strong><?= e($currentVersion) ?></strong></div><div><span>Package release</span><strong><?= e($releaseVersion) ?></strong></div><div><span>Status</span><strong class="<?= $pending?'warn':'ok' ?>"><?= $pending?'Update pending':'Up to date' ?></strong></div></div>
    <?php if (!empty($automaticUpdateReconciliation['reconciled']) && !$pending): ?>
        <div class="alert success"><strong>Update status repaired automatically.</strong> The already-applied migration is now recorded as Completed in the deterministic ledger and the failed checkpoint has been closed.</div>
    <?php elseif (!empty($automaticUpdateReconciliation['errors'])): ?>
        <div class="alert error"><strong>Update status could not be reconciled automatically.</strong> The Apply/Retry button remains available because the exact migration ledger is not complete.</div>
    <?php endif; ?>
    <?php if ($updateCanResume): ?>
        <div class="alert <?= (string)($latestUpdateRunPublic['status']??'')==='failed'?'error':'warning' ?>">
            <strong><?= (string)($latestUpdateRunPublic['status']??'')==='failed'?'Update paused safely.':'Interrupted or incomplete update detected.' ?></strong>
            Completed <?= (int)($latestUpdateRunPublic['step_index']??0) ?> of <?= (int)($latestUpdateRunPublic['step_count']??0) ?> release steps.
            <?php if (!empty($latestUpdateRunPublic['current_label'])): ?> Last activity: <?=e((string)$latestUpdateRunPublic['current_label'])?>.<?php endif; ?>
            <?php if (!empty($latestUpdateRunPublic['error_reference'])): ?> Reference: <code><?=e((string)$latestUpdateRunPublic['error_reference'])?></code>.<?php endif; ?>
            <?php if (!empty($latestUpdateRunPublic['last_error'])): ?><small><?=e((string)$latestUpdateRunPublic['last_error'])?></small><?php endif; ?>
        </div>
    <?php endif; ?>
    <?php if ($pending): ?>
        <div class="update-notes">
            <h3>Current release reconciliation — <?= e($releaseVersion) ?></h3>
            <p>DentalCare found an incomplete requirement for the current uploaded release. Historical release notes and old readiness markers no longer create a pending update after a newer version has already been applied.</p>
            <?php if (!empty($updatePendingReasons)): ?><div class="table-wrap"><table><thead><tr><th>Pending reason</th><th>What it means</th></tr></thead><tbody><?php foreach ($updatePendingReasons as $reasonRow): ?><tr><td><strong><?= e((string)$reasonRow['label']) ?></strong></td><td><?= e((string)$reasonRow['status']) ?></td></tr><?php endforeach; ?></tbody></table></div><?php endif; ?>
        </div>
        <form method="post" id="dental-update-form" data-update-progress-form>
            <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
            <input type="hidden" name="action" value="apply_update_step">
            <button class="btn primary" type="submit" data-update-submit><?= $updateCanResume ? 'Resume update' : 'Apply pending update' ?></button>
        </form>
    <?php else: ?>
        <div class="alert success">The database and application files are aligned at version <?= e($releaseVersion) ?>. Production release controls, corrected shared-hosting storage gates, quota-aware health reporting, automated validation, resumable updates, deep backup verification, adaptive dental encounters, patient lifetime records, and the controlled General Staff role are active.</div>
        <?php if (!empty($latestUpdateRunPublic) && (string)($latestUpdateRunPublic['status']??'')==='completed'): ?><p class="muted">Last update run completed <?=e((string)($latestUpdateRunPublic['completed_at']??''))?>.</p><?php endif; ?>
    <?php endif; ?>
</section>
<section class="card">
    <div class="card-head"><div><h2>Interrupted-update protection</h2><p>Each release step is idempotent and verified before the next step begins.</p></div><span class="badge success">Resumable</span></div>
    <div class="health-list">
        <div><span>Persistent update checkpoint</span><strong class="<?=setup_table_exists($pdo,table('system_update_runs'))?'ok':'warn'?>"><?=setup_table_exists($pdo,table('system_update_runs'))?'Ready':'Created during update'?></strong></div>
        <div><span>Duplicate update protection</span><strong class="ok">Database lock</strong></div>
        <div><span>Request scope</span><strong>One release step</strong></div>
        <div><span>Recovery after timeout</span><strong class="ok">Resume from last verified step</strong></div>
    </div>
</section>
<?php elseif ($tab === 'permissions'): ?>
<?php if (!documentation_schema_ready()): ?><div class="card"><h2>Update required</h2><p>Apply version 0.4.0 before editing role permissions.</p><a class="btn primary" href="system_setup.php?tab=updates">Open updates</a></div><?php else:
$roleSatisfactionMatrix = function_exists('dental_role_satisfaction_permissions') ? dental_role_satisfaction_permissions() : [];
$roles=$pdo->query('SELECT id,code,name FROM `' . table('roles') . '` WHERE code<>"super_admin" ORDER BY name')->fetchAll();$selectedRole=(int)($_GET['role_id']??($roles[0]['id']??0));$permissions=$pdo->query('SELECT * FROM `' . table('permissions') . '` ORDER BY module_group,name')->fetchAll();$assigned=[];if($selectedRole){$s=$pdo->prepare('SELECT permission_id FROM `' . table('role_permissions') . '` WHERE role_id=? AND allowed=1');$s->execute([$selectedRole]);$assigned=array_map('intval',$s->fetchAll(PDO::FETCH_COLUMN));} ?>
<?php if($roleSatisfactionMatrix): ?><section class="card table-wrap role-satisfaction-matrix system-setup-audit-list system-setup-paged-card" data-system-setup-paginate data-system-setup-default-size="10"><div class="card-head"><div><h2>Role satisfaction matrix</h2><p>Use this as the operational checklist: what each role should do, which reports they need, and what must remain outside their task view.</p></div></div><table><thead><tr><th>Role</th><th>Must be able to do</th><th>Required reports</th><th>Must not do</th></tr></thead><tbody><?php foreach($roleSatisfactionMatrix as $roleCode=>$row):?><tr><td><strong><?=e((string)$row['label'])?></strong><br><small><?=e((string)$roleCode)?></small></td><td><ul><?php foreach((array)$row['must_do'] as $item):?><li><?=e((string)$item)?></li><?php endforeach;?></ul></td><td><ul><?php foreach((array)$row['reports'] as $item):?><li><?=e((string)$item)?></li><?php endforeach;?></ul></td><td><ul><?php foreach((array)$row['must_not_do'] as $item):?><li><?=e((string)$item)?></li><?php endforeach;?></ul></td></tr><?php endforeach;?></tbody></table></section><?php endif; ?>
<div class="permissions-layout"><aside class="card role-list"><h2>Roles</h2><?php foreach($roles as $role):?><a class="<?= $selectedRole===(int)$role['id']?'active':'' ?>" href="system_setup.php?tab=permissions&role_id=<?=(int)$role['id']?>"><strong><?=e($role['name'])?></strong><span><?=e($role['code'])?></span></a><?php endforeach;?></aside><form method="post" class="card permission-matrix system-setup-paged-card" data-system-setup-paginate data-system-setup-item-selector=".permission-row" data-system-setup-default-size="20"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="save_permissions"><input type="hidden" name="role_id" value="<?=$selectedRole?>"><h2>Allowed functions</h2><?php $group='';foreach($permissions as $permission):if($group!==$permission['module_group']):$group=$permission['module_group'];?><h3><?=e($group)?></h3><?php endif;?><label class="permission-row"><input type="checkbox" name="permissions[]" value="<?=(int)$permission['id']?>" <?=in_array((int)$permission['id'],$assigned,true)?'checked':''?>><span><strong><?=e($permission['name'])?></strong><small><?=e($permission['code'])?></small></span></label><?php endforeach;?><button class="btn primary" type="submit">Save role permissions</button></form></div>
<?php endif; ?>
<?php elseif ($tab === 'email'): ?>
<?php $outbox=[];try{$rows=$pdo->query('SELECT status,COUNT(*) total FROM `' . table('email_outbox') . '` GROUP BY status');foreach($rows as $row)$outbox[$row['status']]=(int)$row['total'];}catch(Throwable){} ?>
<div class="setup-grid"><section class="card"><h2>Email configuration</h2><dl><dt>Driver</dt><dd><?=e(strtoupper((string)($mail['driver']??'smtp')))?></dd><dt>From name</dt><dd><?=e((string)($mail['from_name']??''))?></dd><dt>From email</dt><dd><?=e((string)($mail['from_email']??''))?></dd><?php if(($mail['driver']??'')==='smtp'):?><dt>SMTP host</dt><dd><?=e((string)($mail['smtp']['host']??''))?>:<?=e((string)($mail['smtp']['port']??''))?></dd><dt>Encryption</dt><dd><?=e(strtoupper((string)($mail['smtp']['encryption']??'')))?></dd><?php endif;?></dl><p>The authoritative SMTP credentials remain in <code>config/generated.php</code> from the original installer.</p><a class="btn primary" href="email_settings.php">Configure & Diagnose SMTP</a> <a class="btn ghost" href="sms_settings.php">SMS readiness center</a></section><section class="card"><h2>Email queue</h2><section class="stats mini-stats"><?php stat_card('Pending',$outbox['pending']??0,'Waiting');stat_card('Retry',$outbox['retry']??0,'Scheduled');stat_card('Sent',$outbox['sent']??0,'Delivered');stat_card('Failed',$outbox['failed']??0,'Needs review');?></section><p>Queue processing can be run through the protected email-queue endpoint or from a scheduled task.</p></section></div>
<?php elseif ($tab === 'backups'): ?>
<?php if (!setup_backup_printing_ready($pdo)): ?><div class="card"><h2>Update required</h2><p>Apply Version 0.5.3 to enable complete system backups, checksums, and package verification.</p><a class="btn primary" href="system_setup.php?tab=updates">Open updates</a></div><?php else:?>
<div class="setup-grid">
<section class="card"><div class="card-head"><div><h2>Create protected backup</h2><p>Choose the scope needed for recovery.</p></div></div>
<form method="post" class="form-grid"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="create_backup">
<label>Backup type<select name="backup_type"><option value="complete">Complete system — database and all managed files</option><option value="database">Database only</option><option value="files">Files only — private files, uploads, branding, licensing, and configuration</option></select></label>
<label>Optional archive password<input type="password" name="backup_password" autocomplete="new-password"><small>Applies to ZIP backups when AES encryption is supported. Record the password outside this server.</small></label>
<div class="wide"><button class="btn primary" type="submit">Create backup now</button></div></form>
<div class="alert warning"><strong>Complete backups contain confidential records and credentials.</strong> Download a copy outside this hosting account and protect it from unauthorized access.</div></section>
<section class="card"><div class="card-head"><div><h2>Upload existing backup</h2><p>Store a prior DentalCare package or SQL backup securely.</p></div></div><form method="post" enctype="multipart/form-data" class="form-grid"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="upload_backup"><label class="wide">Backup file<input type="file" name="backup_file" accept=".sql,.gz,.zip" required><small>Maximum 100 MB. Uploading does not restore the system.</small></label><div class="wide"><button class="btn ghost" type="submit">Upload backup</button></div></form></section>
</div>
<section class="card backup-coverage"><h2>Complete backup coverage</h2><div class="coverage-grid"><span>Database tables</span><span>Patient and clinical files</span><span>Doctor credentials and signatures</span><span>Clinic uploads and branding</span><span>License state</span><span>Generated configuration and SMTP settings</span><span>Installation lock</span><span>Manifest and SHA-256 checksums</span></div><p class="muted">The backup repository itself is intentionally excluded to prevent recursive archives.</p></section>
<section class="card table-wrap system-setup-paged-card" data-system-setup-paginate data-system-setup-default-size="10"><div class="card-head"><div><h2>Backup repository</h2><p>Managed packages stored outside public access.</p></div></div><table><thead><tr><th>File</th><th>Type</th><th>Size</th><th>Files</th><th>Protection</th><th>Verification</th><th>Created by</th><th>Date</th><th></th></tr></thead><tbody><?php $backupRows=$pdo->query('SELECT b.*,u.name creator FROM `' . table('backup_logs') . '` b LEFT JOIN `' . table('users') . '` u ON u.id=b.created_by ORDER BY b.created_at DESC LIMIT 50')->fetchAll();foreach($backupRows as $backup):?><tr><td><strong><?=e($backup['file_name'])?></strong><?php if($backup['notes']):?><small><?=e($backup['notes'])?></small><?php endif;?><?php if(!empty($backup['checksum_sha256'])):?><small class="checksum">SHA-256 <?=e(substr((string)$backup['checksum_sha256'],0,18))?>…</small><?php endif;?></td><td><span class="badge"><?=e($backup['backup_type']??'database')?></span></td><td><?=e(number_format(((int)$backup['file_size'])/1024/1024,2))?> MB</td><td><?=e((string)($backup['file_count']??0))?></td><td><?=!empty($backup['is_encrypted'])?'AES encrypted':'Server protected'?></td><td><?=!empty($backup['verified_at'])?'<span class="status-ok">Verified</span>':'Not verified'?></td><td><?=e($backup['creator']??'System')?></td><td><?=e(date('M j, Y g:i A',strtotime($backup['created_at'])))?></td><td><div class="inline-actions"><a href="download_backup.php?id=<?=(int)$backup['id']?>">Download</a><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="verify_backup"><input type="hidden" name="backup_id" value="<?=(int)$backup['id']?>"><button class="link-btn" type="submit">Verify</button></form></div></td></tr><?php endforeach;?><?php if(!$backupRows):?><tr><td colspan="9" class="muted">No backups have been created or uploaded.</td></tr><?php endif;?></tbody></table></section><?php endif; ?>
<?php elseif ($tab === 'environment'): ?>
<?php
$deploymentMode = function_exists('dental_deployment_mode') ? dental_deployment_mode() : 'production';
$deploymentModes = function_exists('dental_deployment_modes') ? dental_deployment_modes() : ['production'=>'Production'];
$productionMode = function_exists('dental_production_mode') ? dental_production_mode() : $deploymentMode === 'production';
$showInternalQa = function_exists('dental_internal_qa_visible') ? dental_internal_qa_visible() : false;
?>
<section class="card deployment-mode-card">
  <div class="card-head"><div><span class="eyebrow">Superuser environment control</span><h2>Deployment Mode: <?=e(function_exists('dental_deployment_mode_label') ? dental_deployment_mode_label($deploymentMode) : ucfirst($deploymentMode))?></h2><p>This setting changes installation-wide visibility and release behavior. It is intentionally unavailable from Clinic Administration and from operational task views.</p></div><span class="badge <?= $productionMode && !$showInternalQa ? 'success' : 'warning' ?>"><?= $productionMode && !$showInternalQa ? 'Client clean' : 'Internal tools visible' ?></span></div>
  <form method="post" class="form-grid compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="save_deployment_mode"><label>Environment<select name="deployment_mode"><?php foreach($deploymentModes as $code=>$label): ?><option value="<?=e((string)$code)?>" <?=$deploymentMode===$code?'selected':''?>><?=e((string)$label)?></option><?php endforeach; ?></select><small>Production is the normal client mode. Pilot and Development are for controlled stabilization work.</small></label><label class="choice wide"><input type="checkbox" name="show_internal_qa_tools" value="1" <?= $showInternalQa && $productionMode ? 'checked' : '' ?>><span><strong>Temporarily show internal QA tools in Production</strong><small>Use only while troubleshooting. Turn this off before handing the system to clinic users.</small></span></label><div class="wide"><button class="btn primary" type="submit">Save environment</button></div></form>
</section>
<section class="card"><h2>Ownership boundary</h2><div class="health-list"><div><span>Clinic Administrator</span><strong>Read-only status only</strong></div><div><span>Superuser</span><strong class="ok">May change environment</strong></div><div><span>Task View switching</span><strong>Cannot grant access</strong></div></div></section>
<?php elseif ($tab === 'security'): ?>
<section class="card"><div class="card-head"><div><span class="eyebrow">Protected history</span><h2>Audit integrity fingerprint</h2><p>The latest protected audit records are recalculated from their stored database values. Critical clinical, payment, role, and environment changes now fail instead of committing when their required audit record cannot be written.</p></div><span class="badge <?=!empty($auditIntegrity['ready'])&&(int)$auditIntegrity['invalid']===0?'success':'warning'?>"><?=!empty($auditIntegrity['ready'])?(int)$auditIntegrity['invalid']===0?'Verified':'Review required':'Update required'?></span></div>
<div class="stats-grid"><div><span>Checked</span><strong><?=number_format((int)$auditIntegrity['checked'])?></strong></div><div><span>Valid fingerprints</span><strong><?=number_format((int)$auditIntegrity['valid'])?></strong></div><div><span>Invalid fingerprints</span><strong><?=number_format((int)$auditIntegrity['invalid'])?></strong></div><div><span>Older unhashed rows</span><strong><?=number_format((int)$auditIntegrity['unhashed'])?></strong></div></div>
<?php if(!empty($auditIntegrity['error'])):?><div class="alert warning"><?=e((string)$auditIntegrity['error'])?></div><?php elseif((int)$auditIntegrity['invalid']>0):?><div class="alert error"><strong>Do not ignore this result.</strong> One or more recent audit rows no longer match their stored fingerprint. Preserve the database, export logs, and investigate before changing financial or clinical records.</div><?php elseif((int)$auditIntegrity['unhashed']>0):?><p class="muted">Rows created before v0.7.6.5 may not have a fingerprint. They remain historical records but cannot be retroactively certified as unchanged.</p><?php endif;?>
<p class="muted">This protects application-level history against silent ordinary edits. A compromised database/root account can still rewrite rows and fingerprints; production clinics should export audit logs and backups to separate immutable storage.</p></section>
<form method="post" class="card form-grid"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="save_security"><h2 class="wide">Security policy</h2><label>Session timeout in minutes<input type="number" min="5" max="480" name="session_timeout" value="<?=e((string)setting_get('security','session_timeout_minutes','60'))?>"></label><label class="check-option"><input type="checkbox" name="require_mfa_privileged" <?=setting_get('security','require_mfa_privileged','0')==='1'?'checked':''?> <?=extension_loaded('openssl')?'':'disabled'?>><span>Require multi-factor authentication enrollment and verification for privileged roles.<?php if(!extension_loaded('openssl')): ?><small>Unavailable until the PHP OpenSSL extension is enabled.</small><?php endif; ?></span></label><label class="check-option wide"><input type="checkbox" name="record_export_reason" <?=setting_get('security','record_export_reason','1')==='1'?'checked':''?>><span>Require and audit a reason when confidential records are exported.</span></label><div class="wide"><button class="btn primary" type="submit">Save security policy</button></div></form>
<?php endif; ?>

<div id="dental-update-progress" class="dental-update-progress" hidden aria-hidden="true">
  <div class="dental-update-progress__panel" role="status" aria-live="polite">
    <div class="dental-update-progress__spinner" aria-hidden="true"></div>
    <div>
      <span class="eyebrow">Resumable system update</span>
      <h2 id="dental-update-progress-title">Preparing the update</h2>
      <p id="dental-update-progress-message">DentalCare will apply one verified release step at a time.</p>
      <div class="dental-update-progress__bar" aria-hidden="true"><span id="dental-update-progress-bar"></span></div>
      <div class="dental-update-progress__meta"><span>Elapsed: <strong id="dental-update-progress-time">00:00</strong></span><span id="dental-update-progress-state">Starting</span></div>
      <p class="muted" id="dental-update-progress-detail">If this request is interrupted, reopen Updates &amp; Database and click Resume update.</p>
      <div class="alert warning" id="dental-update-progress-warning" hidden><strong>Connection interrupted or the server took too long.</strong><span>The completed checkpoint is safe. Refresh this page and click Resume update; do not re-upload the package.</span></div>
      <div class="status-actions"><button type="button" class="btn ghost" id="dental-update-progress-close" hidden>Close and resume later</button></div>
    </div>
  </div>
</div>
<style>
.dental-update-progress{position:fixed;inset:0;z-index:100000;background:rgba(15,23,42,.78);display:grid;place-items:center;padding:24px;backdrop-filter:blur(4px)}.dental-update-progress[hidden]{display:none!important}.dental-update-progress__panel{width:min(680px,100%);max-height:calc(100vh - 48px);overflow:auto;background:#fff;border-radius:18px;padding:28px;box-shadow:0 24px 80px rgba(0,0,0,.3);display:grid;grid-template-columns:52px 1fr;gap:20px;color:#172033}.dental-update-progress__spinner{width:44px;height:44px;border:4px solid #dbe4ef;border-top-color:#2563eb;border-radius:50%;animation:dental-update-spin .85s linear infinite}.dental-update-progress__panel h2{margin:.2rem 0 .4rem}.dental-update-progress__panel p{margin:.2rem 0 1rem;color:#526076}.dental-update-progress__bar{height:10px;border-radius:999px;background:#e7edf5;overflow:hidden}.dental-update-progress__bar span{display:block;height:100%;width:0;border-radius:inherit;background:linear-gradient(90deg,#2563eb,#60a5fa);transition:width .25s ease}.dental-update-progress__meta{display:flex;justify-content:space-between;gap:16px;margin:10px 0 14px;font-size:.9rem;color:#526076}#dental-update-progress-warning{margin-top:16px;display:grid;gap:4px}#dental-update-progress-warning[hidden]{display:none!important}body.dental-update-running{overflow:hidden}@keyframes dental-update-spin{to{transform:rotate(360deg)}}@media(max-width:640px){.dental-update-progress__panel{grid-template-columns:1fr;padding:22px}.dental-update-progress__meta{flex-direction:column;gap:4px}}
</style>
<script>
(function(){
  var form=document.querySelector('[data-update-progress-form]'),overlay=document.getElementById('dental-update-progress');
  if(!form||!overlay)return;
  var submit=form.querySelector('[data-update-submit]'),title=document.getElementById('dental-update-progress-title'),message=document.getElementById('dental-update-progress-message'),timer=document.getElementById('dental-update-progress-time'),state=document.getElementById('dental-update-progress-state'),detail=document.getElementById('dental-update-progress-detail'),warning=document.getElementById('dental-update-progress-warning'),bar=document.getElementById('dental-update-progress-bar'),close=document.getElementById('dental-update-progress-close'),started=0,running=false,timerId=null;
  function fmt(v){return String(Math.floor(v/60)).padStart(2,'0')+':'+String(v%60).padStart(2,'0')}
  function open(){started=Date.now();overlay.hidden=false;overlay.setAttribute('aria-hidden','false');document.body.classList.add('dental-update-running');warning.hidden=true;close.hidden=true;if(submit){submit.disabled=true;submit.textContent='Update running…'}timerId=setInterval(function(){timer.textContent=fmt(Math.floor((Date.now()-started)/1000))},1000)}
  function stop(canResume){running=false;clearInterval(timerId);document.body.classList.remove('dental-update-running');close.hidden=false;if(submit){submit.disabled=false;submit.textContent=canResume?'Resume update':'Apply pending update'}}
  function render(data){var percent=Math.max(0,Math.min(100,Number(data.percent||0)));bar.style.width=percent+'%';state.textContent=(data.step_count?('Step '+Math.min(Number(data.step_index||0)+1,Number(data.step_count))+' of '+data.step_count):'Verifying')+' · '+percent+'%';title.textContent=data.status==='completed'?'Update completed':(data.status==='failed'?'Update paused safely':(data.current_label||'Applying verified update step'));message.textContent=data.message||'The current release step is being verified.';detail.textContent=data.error_reference?('Reference '+data.error_reference+(data.last_error?' · '+data.last_error:'')):(data.current_version?('Current release step: '+data.current_version):'The update checkpoint is stored in the database.');}
  async function next(){
    if(!running)return;
    var payload=new FormData(form);payload.set('ajax','1');payload.set('action','apply_update_step');
    try{
      var response=await fetch(form.action||window.location.href,{method:'POST',body:payload,headers:{'Accept':'application/json','X-Requested-With':'XMLHttpRequest'},credentials:'same-origin',cache:'no-store'});
      var raw=await response.text(),data=null;
      try{data=JSON.parse(raw)}catch(parseError){
        // Shared-hosting security layers may replace JSON with an HTML redirect
        // or error page. Fall back once to the standard form transport, which
        // uses the same persistent checkpoint and surfaces the server message.
        title.textContent='Switching to standard update mode';
        message.textContent='The server returned a non-JSON response. DentalCare will continue through the reliable form update path.';
        detail.textContent='No completed checkpoint will be repeated.';
        state.textContent='Transport fallback';
        warning.hidden=true;
        clearInterval(timerId);
        setTimeout(function(){HTMLFormElement.prototype.submit.call(form)},650);
        return;
      }
      render(data);
      if(data.status==='completed'){bar.style.width='100%';stop(false);setTimeout(function(){window.location.href='system_setup.php?tab=updates'},900);return}
      if(data.status==='failed'){warning.hidden=false;stop(true);return}
      if(data.status==='busy'){setTimeout(next,1200);return}
      setTimeout(next,350);
    }catch(error){
      title.textContent='Switching to standard update mode';
      message.textContent='The asynchronous update channel was unavailable. DentalCare will retry through the normal form request.';
      detail.textContent='The database checkpoint prevents a completed step from being repeated.';
      state.textContent='Transport fallback';warning.hidden=true;clearInterval(timerId);
      setTimeout(function(){HTMLFormElement.prototype.submit.call(form)},650)
    }
  }
  form.addEventListener('submit',function(event){event.preventDefault();if(running)return;running=true;open();next()});
  close.addEventListener('click',function(){overlay.hidden=true;overlay.setAttribute('aria-hidden','true');document.body.classList.remove('dental-update-running')});
})();
</script>

<?php render_footer(); ?>
