<?php
declare(strict_types=1);

// REVIEW-ONLY by default. Only owned synthetic menu/device/operator rows may
// be written after exact root approval. Never touches real files or devices.
set_exception_handler(static function (Throwable $error): void {
    echo json_encode(['status' => 'harness-error', 'class' => get_class($error),
        'code' => (string)$error->getCode(),
        'message_sha256' => hash('sha256', $error->getMessage())], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(2);
});
$mode = (string)($argv[1] ?? '');
if ($mode !== '--preflight'
    && ($mode !== '--execute'
        || getenv('CDX_UNBIND_FAULT_EXECUTE') !== 'ROOT_APPROVED_UNBIND_FAULT_V1')) {
    echo json_encode(['status' => 'DENIED', 'reason' => 'root-review-required']), PHP_EOL;
    exit(0);
}
$lane = (string)getenv('CDX_UNBIND_LANE');
$marker = (string)getenv('CDX_UNBIND_MARKER');
$selfSha = (string)getenv('CDX_UNBIND_SCRIPT_SHA');
if (!in_array($lane, ['A', 'B', 'C'], true)
    || !preg_match('/^CDXUF[A-F0-9]{16}$/', $marker)
    || $selfSha === '' || hash_file('sha256', __FILE__) !== $selfSha) {
    throw new RuntimeException('Exact lane/marker/script gate failed');
}
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 21023, 'mysql'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001, 'mysql'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175, 'dm'],
];
[$root, $base, $pid, $dialect] = $targets[$lane];
if (getenv('POC_APP_ROOT') !== $root || getenv('POC_BASE_URL') !== $base
    || getenv('POC_DIALECT') !== $dialect || is_link($root)
    || !is_file($root . '/application/database.php')) {
    throw new RuntimeException('Isolated root/config boundary mismatch');
}
foreach ([
    'application/common/service/ElectronicMenuDevice.php' =>
        '3335769aee94126f7abfae2f2a305ba09cca9270df2ab7590657c00fabef855f',
    'application/p/controller/ElectronicMenu.php' =>
        'a93de90caced9884bb40374b9f3da7cbb04997930023e6cb229a7ca7ffefc2d7',
] as $relative => $expected) {
    if (hash_file('sha256', $root . '/' . $relative) !== $expected) {
        throw new RuntimeException('Actual e-menu source drift');
    }
}
$cmd = is_file('/proc/' . $pid . '/cmdline')
    ? (string)file_get_contents('/proc/' . $pid . '/cmdline') : '';
if (strpos($cmd, '127.0.0.1:' . parse_url($base, PHP_URL_PORT)) === false
    || strpos($cmd, $root . '/public') === false) {
    throw new RuntimeException('Listener PID/docroot mismatch');
}
$ownsLoopbackListener = static function (int $targetPid, int $port): bool {
    $portHex = strtoupper(str_pad(dechex($port), 4, '0', STR_PAD_LEFT));
    $rows = @file('/proc/net/tcp', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if (!is_array($rows)) return false;
    $socketInodes = [];
    foreach (array_slice($rows, 1) as $line) {
        $parts = preg_split('/\s+/', trim($line));
        if (!is_array($parts) || count($parts) < 10) continue;
        if (($parts[1] ?? '') === '0100007F:' . $portHex
            && ($parts[3] ?? '') === '0A') {
            $socketInodes[] = (string)$parts[9];
        }
    }
    if (count($socketInodes) !== 1) return false;
    $fds = glob('/proc/' . $targetPid . '/fd/*');
    if (!is_array($fds)) return false;
    foreach ($fds as $fd) {
        if (@readlink($fd) === 'socket:[' . $socketInodes[0] . ']') return true;
    }
    return false;
};
if (!$ownsLoopbackListener($pid, (int)parse_url($base, PHP_URL_PORT))) {
    throw new RuntimeException('Listener socket inode is not owned by fixed PID');
}
$singleWorker = static function (int $targetPid): bool {
    $env = (string)@file_get_contents('/proc/' . $targetPid . '/environ');
    $children = (string)@file_get_contents('/proc/' . $targetPid . '/task/' . $targetPid . '/children');
    if ($env === '' || trim($children) !== '') return false;
    return !preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=([2-9]|[1-9][0-9]+)(?:\x00|$)/', $env);
};
if (!$singleWorker($pid)) throw new RuntimeException('Single-worker barrier unavailable');
$boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
if (!is_array($boundary) || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated']) || !empty($boundary['real_queue'])) {
    throw new RuntimeException('External/Redis/queue isolation unavailable');
}
$runtime = require $root . '/application/database.php';
if (!is_array($runtime)) throw new RuntimeException('Runtime config invalid');
if ($dialect === 'mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    if (!is_array($local)) throw new RuntimeException('Code local config unavailable');
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if ((string)($runtime[$key] ?? '') !== (string)($local[$key] ?? '')) {
            throw new RuntimeException('Runtime differs from code-local development MySQL');
        }
    }
    if ((int)($runtime['hostport'] ?? 3306) !== (int)($local['hostport'] ?? 3306)) {
        throw new RuntimeException('Runtime MySQL port differs from code-local default');
    }
    $dsn = 'mysql:host=' . $runtime['hostname'] . ';port=' . (int)$runtime['hostport']
        . ';dbname=' . $runtime['database'] . ';charset=' . ($runtime['charset'] ?? 'utf8');
    $user = (string)$runtime['username'];
    $password = (string)$runtime['password'];
    $expectedDb = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
} else {
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $key => $envKey) {
        if ((string)($runtime[$key] ?? '') !== (string)getenv($envKey)) {
            throw new RuntimeException('C worker DB config mismatch');
        }
    }
    if (getenv('POC_HOST') !== '127.0.0.1' || getenv('POC_PORT') !== '5237'
        || getenv('POC_SCHEMA') !== 'ZHCT_APP' || getenv('POC_USER') !== 'ZHCT_APP') {
        throw new RuntimeException('C is not approved nonprod Store DM');
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $user = (string)getenv('POC_USER');
    $password = (string)getenv('POC_PASSWORD');
    $expectedDb = 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
}
$connect = static function () use ($dsn, $user, $password): PDO {
    return new PDO($dsn, $user, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_CASE => PDO::CASE_LOWER,
        PDO::ATTR_TIMEOUT => 5,
    ]);
};
$db = $connect();
if ($dialect === 'mysql') {
    $identity = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())"
    )->fetchColumn();
    $hostHash = null;
} else {
    $schema = strtoupper((string)$db->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn());
    $identity = 'ZHCT_APP|5237|' . $schema;
    $machineId = trim((string)@file_get_contents('/etc/machine-id'));
    $host = gethostname();
    if (!is_string($host) || $machineId === '') throw new RuntimeException('C host unknown');
    $hostHash = hash('sha256', $host . '|' . $machineId);
    if ($hostHash !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('C host mismatch');
    }
    $lines = [];
    @exec('/usr/sbin/ss -ltnp 2>/dev/null', $lines);
    $dmListener = false;
    foreach ($lines as $line) {
        if (preg_match('/\*:5237\s+.*users:\(\("dmserver",pid=(\d+),/', $line, $m)) {
            $exe = @readlink('/proc/' . $m[1] . '/exe');
            $dmListener = is_string($exe) && basename($exe) === 'dmserver';
            break;
        }
    }
    if (!$dmListener) throw new RuntimeException('C 5237 not local dmserver');
}
$dbHash = hash('sha256', $identity);
if ($dbHash !== $expectedDb) throw new RuntimeException('Approved DB identity mismatch');
$table = static function (string $short) use ($dialect): string {
    if (!preg_match('/^[a-z_]+$/', $short)) throw new RuntimeException('Invalid table name');
    return $dialect === 'dm' ? strtoupper('ydy_' . $short) : 'ydy_' . $short;
};
$restaurant = $db->query('SELECT menu.ID AS SOURCE_MENU_ID,'
    . ' restaurant.ID AS RESTAURANT_ID FROM ' . $table('electronic_menu') . ' menu'
    . ' JOIN ' . $table('restaurants') . ' restaurant'
    . ' ON restaurant.ID=menu.LEFT_RESTAURANT_ID'
    . ' WHERE menu.STORE_ID=10001 AND menu.STATUS=1 AND menu.IS_DELETE=0'
    . ' AND restaurant.STATUS=1 AND restaurant.IS_DELETE=2'
    . ' ORDER BY menu.ID')->fetch(PDO::FETCH_ASSOC);
$restaurantId = (int)($restaurant['restaurant_id'] ?? 0);
$sourceMenuId = (int)($restaurant['source_menu_id'] ?? 0);
if ($restaurantId < 1 || $sourceMenuId < 1) {
    throw new RuntimeException('No active same-store visible menu/restaurant relation');
}
foreach (['user' => 'UUID,ACCOUNT,TYPE,CREATE_BY',
    'electronic_menu' => 'ID,UUID,PUBLIC_TOKEN,CREATE_BY,IS_DELETE',
    'electronic_menu_device' => 'ID,UUID,DEVICE_ID,MENU_ID,CREATE_BY,STATUS'] as $short => $columns) {
    $db->query('SELECT ' . $columns . ' FROM ' . $table($short) . ' WHERE 1=0');
}
$collision = $db->prepare('SELECT COUNT(*) FROM ' . $table('user') . ' WHERE UUID=? OR ACCOUNT=?');
$collision->execute([$marker, $marker]);
if ((int)$collision->fetchColumn() !== 0) throw new RuntimeException('Actor marker collision');
$collision = $db->prepare('SELECT COUNT(*) FROM ' . $table('electronic_menu') . ' WHERE CREATE_BY=?');
$collision->execute([$marker]);
if ((int)$collision->fetchColumn() !== 0) throw new RuntimeException('Menu marker collision');
$deviceId = $marker . 'TV';
$collision = $db->prepare('SELECT COUNT(*) FROM ' . $table('electronic_menu_device')
    . ' WHERE STORE_ID=10001 AND DEVICE_ID=?');
$collision->execute([$deviceId]);
if ((int)$collision->fetchColumn() !== 0) throw new RuntimeException('Device marker collision');
if ($mode === '--preflight') {
    echo json_encode(['status' => 'read-only-preflight-pass', 'lane' => $lane,
        'marker' => $marker, 'restaurant_id' => $restaurantId,
        'existing_same_store_menu_id' => $sourceMenuId,
        'source_sha_verified' => true, 'listener_single_worker' => true,
        'listener_socket_inode_owned' => true,
        'external_stubs' => true, 'database_fingerprint' => $dbHash,
        'host_fingerprint' => $hostHash, 'http_and_dml' => 0],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
// Execution branch remains unreachable until exact root approval and SHA gate.
$journalDir = '/tmp/zhct-electronic-menu-unbind-fault-' . $lane;
if (!is_dir($journalDir) && !mkdir($journalDir, 0700)) {
    throw new RuntimeException('Private journal directory unavailable');
}
if (is_link($journalDir) || fileowner($journalDir) !== posix_geteuid()
    || (fileperms($journalDir) & 0777) !== 0700) {
    throw new RuntimeException('Private journal directory unsafe');
}
$journal = $journalDir . '/' . $marker . '.json';
if (file_exists($journal)) throw new RuntimeException('Marker journal exists');
$record = ['lane' => $lane, 'marker' => $marker, 'db_fingerprint' => $dbHash,
    'restaurant_id' => $restaurantId, 'phase' => 'before-insert'];
$oldUmask = umask(0077);
$created = file_put_contents($journal, json_encode($record), LOCK_EX);
umask($oldUmask);
if ($created === false) throw new RuntimeException('Journal unavailable');
chmod($journal, 0600);
if (is_link($journal) || fileowner($journal) !== posix_geteuid()
    || (fileperms($journal) & 0777) !== 0600) {
    throw new RuntimeException('Private journal file unsafe');
}
$actorId = 0;
$menuId = 0;
$deviceRecordId = 0;
$menuUuid = '';
$deviceUuid = '';
$token = '';
$outcomes = [];
$failure = null;
$cleaned = false;
$post = static function (int $id, string $authToken) use ($base): array {
    $url = $base . '/p/electronicMenu/unbindDevice';
    $context = stream_context_create(['http' => ['method' => 'POST',
        'timeout' => 15, 'ignore_errors' => true,
        'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: "
            . $authToken . "\r\n",
        'content' => http_build_query(['device_record_id' => $id])]]);
    $start = microtime(true);
    $stream = @fopen($url, 'rb', false, $context);
    if ($stream === false) throw new RuntimeException('Bounded HTTP unavailable');
    try { $body = stream_get_contents($stream, 1048577); }
    finally { fclose($stream); }
    if (!is_string($body) || strlen($body) > 1048576) {
        throw new RuntimeException('HTTP response over one MiB');
    }
    $line = $http_response_header[0] ?? '';
    $http = preg_match('/HTTP\/\S+\s+(\d+)/', $line, $m) ? (int)$m[1] : 0;
    $json = json_decode($body, true);
    return ['http' => $http, 'code' => is_array($json) ? ($json['code'] ?? null) : null,
        'message_sha256' => is_array($json)
            ? hash('sha256', (string)($json['message'] ?? '')) : null,
        'response_sha256' => hash('sha256', $body),
        'elapsed_ms' => (int)round((microtime(true) - $start) * 1000)];
};
try {
    $db->beginTransaction();
    $now = date('Y-m-d H:i:s');
    $insert = $db->prepare('INSERT INTO ' . $table('user')
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $insert->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, $now, 'CODEX', 0]);
    $actor = $db->prepare('SELECT ID FROM ' . $table('user')
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
    $actor->execute([$marker, $marker, 'CODEX']);
    $actorId = (int)$actor->fetchColumn();
    if ($actorId < 1) throw new RuntimeException('Owned operator missing');
    $menuUuid = sprintf('%s-%s-%s-%s-%s', substr($marker, 5, 8),
        substr($marker, 13, 4), '4000', '8000', substr(hash('sha256', $marker), 0, 12));
    $publicToken = bin2hex(random_bytes(32));
    $insert = $db->prepare('INSERT INTO ' . $table('electronic_menu')
        . ' (UUID,PUBLIC_TOKEN,DISPLAY_MODE,LEFT_RESTAURANT_ID,RIGHT_RESTAURANT_ID,'
        . ' CAROUSEL_INTERVAL,STATUS,STORE_ID,IS_DELETE,CREATE_BY,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,?,1,?,0,8,1,10001,0,?,?,?)');
    $insert->execute([$menuUuid, $publicToken, $restaurantId, $marker, $now, $now]);
    $menu = $db->prepare('SELECT ID FROM ' . $table('electronic_menu')
        . ' WHERE UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=? AND STORE_ID=10001');
    $menu->execute([$menuUuid, $publicToken, $marker]);
    $menuId = (int)$menu->fetchColumn();
    if ($menuId < 1) throw new RuntimeException('Owned menu missing');
    $deviceUuid = sprintf('%s-%s-%s-%s-%s', substr(hash('sha256', $deviceId), 0, 8),
        '0001', '4000', '8000', substr(hash('sha256', $deviceId), 8, 12));
    $deviceTokenHash = hash('sha256', bin2hex(random_bytes(32)));
    $insert = $db->prepare('INSERT INTO ' . $table('electronic_menu_device')
        . ' (UUID,DEVICE_ID,DEVICE_TOKEN_HASH,DEVICE_NAME,MENU_ID,STATUS,'
        . ' LAST_SEEN_TIME,STORE_ID,IS_DELETE,CREATE_BY,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,?,?,?,?,1,?,10001,0,?,?,?)');
    $insert->execute([$deviceUuid, $deviceId, $deviceTokenHash,
        $marker, $menuId, $now, $marker, $now, $now]);
    $device = $db->prepare('SELECT ID FROM ' . $table('electronic_menu_device')
        . ' WHERE UUID=? AND DEVICE_ID=? AND MENU_ID=? AND STORE_ID=10001');
    $device->execute([$deviceUuid, $deviceId, $menuId]);
    $deviceRecordId = (int)$device->fetchColumn();
    if ($deviceRecordId < 1) throw new RuntimeException('Owned device missing');
    $db->commit();
    $record += ['actor_id' => $actorId, 'menu_id' => $menuId,
        'menu_uuid' => $menuUuid, 'device_record_id' => $deviceRecordId,
        'device_uuid' => $deviceUuid, 'device_id' => $deviceId];
    $record['phase'] = 'fixture-committed';
    file_put_contents($journal, json_encode($record), LOCK_EX);
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t",
        [(string)$actorId, $marker, $marker, '0', '', '', $marker]));
    if (!is_string($token) || $token === '') throw new RuntimeException('Synthetic token unavailable');

    $readDevice = static function () use ($db, $table, $deviceRecordId,
        $deviceUuid, $deviceId, $menuId): array {
        $q = $db->prepare('SELECT * FROM ' . $table('electronic_menu_device')
            . ' WHERE ID=? AND UUID=? AND DEVICE_ID=? AND MENU_ID=? AND STORE_ID=10001');
        $q->execute([$deviceRecordId, $deviceUuid, $deviceId, $menuId]);
        return $q->fetch(PDO::FETCH_ASSOC) ?: [];
    };
    $before = $readDevice();
    if ((int)($before['status'] ?? 0) !== 1) throw new RuntimeException('Fixture device not active');
    $disable = $db->prepare('UPDATE ' . $table('electronic_menu')
        . ' SET IS_DELETE=1 WHERE ID=? AND UUID=? AND CREATE_BY=? AND STORE_ID=10001 AND IS_DELETE=0');
    $disable->execute([$menuId, $menuUuid, $marker]);
    if ($disable->rowCount() !== 1) throw new RuntimeException('Owned menu disable failed');
    $rejected = $post($deviceRecordId, $token);
    $afterRejected = $readDevice();
    $noMutation = hash('sha256', serialize($before)) === hash('sha256', serialize($afterRejected));
    $outcomes['disabled_menu_rejection'] = $rejected + ['device_row_unchanged' => $noMutation];
    if ($rejected['http'] !== 200 || (int)$rejected['code'] !== 1 || !$noMutation) {
        throw new RuntimeException('Disabled owned menu not rejected without device write');
    }
    $restore = $db->prepare('UPDATE ' . $table('electronic_menu')
        . ' SET IS_DELETE=0 WHERE ID=? AND UUID=? AND CREATE_BY=? AND STORE_ID=10001 AND IS_DELETE=1');
    $restore->execute([$menuId, $menuUuid, $marker]);
    if ($restore->rowCount() !== 1) throw new RuntimeException('Owned menu restore failed');
    $unbind = $post($deviceRecordId, $token);
    $afterUnbind = $readDevice();
    $outcomes['owned_unbind_positive'] = $unbind + [
        'status_two' => (int)($afterUnbind['status'] ?? 0) === 2,
        'same_device_id' => (int)($afterUnbind['id'] ?? 0) === $deviceRecordId];
    if ($unbind['http'] !== 200 || (int)$unbind['code'] !== 0
        || (int)($afterUnbind['status'] ?? 0) !== 2
        || (int)($afterUnbind['id'] ?? 0) !== $deviceRecordId) {
        throw new RuntimeException('Owned unbind status mismatch');
    }
} catch (Throwable $error) {
    $failure = ['class' => get_class($error),
        'message_sha256' => hash('sha256', $error->getMessage())];
} finally {
    $token = '';
    if ($db->inTransaction()) $db->rollBack();
    // Same single-worker loopback endpoint barriers in-flight requests before cleanup.
    $barrier = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    $barrierSafe = is_array($barrier) && !empty($barrier['external_stubs'])
        && !empty($barrier['redis_isolated']) && empty($barrier['real_queue'])
        && $singleWorker($pid);
    if ($barrierSafe && $actorId > 0 && $menuId > 0 && $deviceRecordId > 0) {
        try {
            $db = $connect();
            foreach (['electronic_menu_bind_code', 'electronic_menu_device_log'] as $short) {
                $related = $db->prepare('SELECT COUNT(*) FROM ' . $table($short)
                    . ' WHERE MENU_ID=?');
                $related->execute([$menuId]);
                if ((int)$related->fetchColumn() !== 0) {
                    throw new RuntimeException('Unexpected owned-menu relations; refusing cleanup');
                }
            }
            $db->beginTransaction();
            $remove = $db->prepare('DELETE FROM ' . $table('electronic_menu_device')
                . ' WHERE ID=? AND UUID=? AND DEVICE_ID=? AND MENU_ID=?'
                . ' AND STORE_ID=10001 AND CREATE_BY=?');
            $remove->execute([$deviceRecordId, $deviceUuid, $deviceId, $menuId, $marker]);
            if ($remove->rowCount() !== 1) throw new RuntimeException('Exact device cleanup failed');
            $remove = $db->prepare('DELETE FROM ' . $table('electronic_menu')
                . ' WHERE ID=? AND UUID=? AND CREATE_BY=? AND STORE_ID=10001');
            $remove->execute([$menuId, $menuUuid, $marker]);
            if ($remove->rowCount() !== 1) throw new RuntimeException('Exact menu cleanup failed');
            $remove = $db->prepare('DELETE FROM ' . $table('user')
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
            $remove->execute([$actorId, $marker, $marker, 'CODEX']);
            if ($remove->rowCount() !== 1) throw new RuntimeException('Exact actor cleanup failed');
            $db->commit();
            $cleaned = true;
            $record['phase'] = 'cleaned';
        } catch (Throwable $cleanupError) {
            if ($db->inTransaction()) $db->rollBack();
            $record['phase'] = 'manual-recovery-required';
            $failure = ['stage' => 'cleanup', 'class' => get_class($cleanupError),
                'message_sha256' => hash('sha256', $cleanupError->getMessage())];
        }
    } else {
        $record['phase'] = 'manual-recovery-required';
        if ($failure === null) $failure = ['stage' => 'barrier-or-incomplete-fixture'];
    }
    file_put_contents($journal, json_encode($record), LOCK_EX);
}
$pass = $failure === null && $cleaned
    && ($outcomes['disabled_menu_rejection']['device_row_unchanged'] ?? false)
    && ($outcomes['owned_unbind_positive']['status_two'] ?? false);
echo json_encode(['status' => $pass ? 'unbind-fault-pass' : 'unbind-fault-fail',
    'lane' => $lane, 'marker' => $marker, 'actor_id' => $actorId,
    'menu_id' => $menuId, 'device_record_id' => $deviceRecordId,
    'outcomes' => $outcomes, 'cleanup_same_process' => $cleaned,
    'journal' => $journal, 'failure' => $failure,
    'database_fingerprint' => $dbHash], JSON_UNESCAPED_SLASHES), PHP_EOL;
exit($pass ? 0 : 3);
