<?php
declare(strict_types=1);

// Read-only gate. Never print connection secrets or business row values.
$lane = (string)($argv[1] ?? '');
$mode = (string)($argv[2] ?? '--natural');
if (!in_array($lane, ['B', 'C'], true)) {
    fwrite(STDERR, "DENIED: lane B or C required\n");
    exit(78);
}
if (!in_array($mode, ['--natural', '--schema', '--refs', '--refcounts', '--ownedrefs', '--delivery', '--natural-sha', '--reconcile'], true)) {
    fwrite(STDERR, "DENIED: unsupported read-only mode\n");
    exit(78);
}
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$pid = $lane === 'B' ? 13127 : 53772;
$port = $lane === 'B' ? 18101 : 18102;
$wanted = [
    'app/store/controller/Goods.php' => 'f96dd2dac9e05d3ca86cf3dc9630b601ab984fad9ff042ba49bbf1ed6d6f22a4',
    'app/store/model/Goods.php' => '808061924d69a82d1578661a35f87c725d1c65d8e204e8042250aa310a2669a9',
];
foreach ($wanted as $file => $sha) {
    if (hash_file('sha256', $root . '/' . $file) !== $sha) {
        throw new RuntimeException('HTTP source drift: ' . $file);
    }
}
if ($lane === 'B') {
    $cfg = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW)['DATABASE'] ?? null;
    $ps = (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=');
    $owner = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:' . $port . ' -sTCP:LISTEN 2>/dev/null'));
    if (!is_array($cfg) || strtolower((string)($cfg['TYPE'] ?? '')) !== 'mysql'
        || (string)($cfg['HOSTPORT'] ?? '') !== '3306' || $owner !== (string)$pid
        || strpos($ps, 'POC_DIALECT=mysql') === false || strpos($ps, 'POC_EXTERNAL_STUBS=1') === false) {
        throw new RuntimeException('B worker identity drift');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA', 'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        $value = (string)($cfg[$from] ?? '');
        if ($value === '' || strpos($ps, 'POC_AI_' . $to . '=' . $value) === false) {
            throw new RuntimeException('B worker differs from code local config');
        }
    }
    $db = new PDO('mysql:host=' . $cfg['HOSTNAME'] . ';port=' . $cfg['HOSTPORT'] . ';dbname=' . $cfg['DATABASE'] . ';charset=utf8', $cfg['USERNAME'], $cfg['PASSWORD'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $prefix = 'yoshop_';
} else {
    $env = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $item) {
        $pair = explode('=', $item, 2);
        if (count($pair) === 2) $env[$pair[0]] = $pair[1];
    }
    if (($env['POC_TARGET_ROOT'] ?? '') !== $root
        || ($env['POC_BASE_URL'] ?? '') !== 'http://127.0.0.1:' . $port
        || ($env['POC_DIALECT'] ?? '') !== 'dm'
        || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1'
        || ($env['POC_AI_HOST'] ?? '') !== '127.0.0.1'
        || ($env['POC_AI_PORT'] ?? '') !== '5238'
        || ($env['POC_AI_SCHEMA'] ?? '') !== 'AIZHCT_APP'
        || ($env['POC_AI_USER'] ?? '') !== 'AIZHCT_APP'
        || !in_array('dm', PDO::getAvailableDrivers(), true)) {
        throw new RuntimeException('C worker identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP', $env['POC_AI_USER'], $env['POC_AI_PASSWORD'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C schema drift');
    }
    $prefix = 'YOSHOP_';
}
$tables = ['goods', 'goods_sku', 'goods_image', 'upload_file',
    'goods_category_rel', 'category', 'goods_spec_rel', 'goods_service_rel'];
if ($mode === '--reconcile') {
    $marker = (string)($argv[3] ?? '');
    if (!preg_match('/^CDXGOODS[0-9A-F]{10}$/', $marker)) {
        throw new RuntimeException('Exact marker required');
    }
    $path = sys_get_temp_dir() . '/cdx-goods-' . strtolower($lane)
        . '-' . strtolower($marker) . '/journal.json';
    $j = is_file($path) ? json_decode((string)file_get_contents($path), true) : null;
    if (!is_array($j) || ($j['lane'] ?? '') !== $lane
        || ($j['marker'] ?? '') !== $marker
        || ($j['phase'] ?? '') !== 'cleanup_committed'
        || !preg_match('/^[a-f0-9]{64}$/', (string)($j['natural_sha256'] ?? ''))) {
        throw new RuntimeException('Exact cleanup journal absent');
    }
    $ordered = [
        'goods' => ['goods', 'GOODS_ID', 'goods'],
        'sku' => ['goods_sku', 'ID', 'sku'],
        'image' => ['goods_image', 'ID', 'image'],
        'relation' => ['goods_category_rel', 'ID', 'relation'],
        'category' => ['category', 'CATEGORY_ID', 'category'],
        'file' => ['upload_file', 'FILE_ID', 'file'],
    ];
    $before = (string)$j['natural_sha256'];
    $currentH = hash_init('sha256');
    $withoutNewH = hash_init('sha256');
    $added = [];
    $counts = [];
    foreach ($ordered as $logical => [$suffix, $pk, $idKey]) {
        $name = $prefix . ($lane === 'B' ? $suffix : strtoupper($suffix));
        $floor = (int)($j['ids'][$idKey] ?? 0);
        if ($floor <= 0) throw new RuntimeException('Fixture ID floor absent');
        hash_update($currentH, $logical . "\0");
        hash_update($withoutNewH, $logical . "\0");
        $s = $db->query('SELECT * FROM ' . $name
            . ' WHERE STORE_ID=10001 ORDER BY ' . $pk);
        $nowCount = 0;
        $oldCount = 0;
        $newRows = [];
        while (($row = $s->fetch(PDO::FETCH_ASSOC)) !== false) {
            $id = (int)($row[strtolower($pk)] ?? 0);
            if ($id <= 0) throw new RuntimeException('Primary key read invalid');
            $encoded = serialize(array_values($row));
            hash_update($currentH, $encoded);
            $nowCount++;
            if ($id > $floor) {
                $newRows[] = ['id' => $id,
                    'create_time' => $row['create_time'] ?? null,
                    'update_time' => $row['update_time'] ?? null,
                    'row_sha256' => hash('sha256', $encoded)];
                if (count($newRows) > 40) throw new RuntimeException('New-row cap exceeded');
            } else {
                hash_update($withoutNewH, $encoded);
                $oldCount++;
            }
            if ($nowCount > 30000) throw new RuntimeException('Natural source cap exceeded');
        }
        hash_update($currentH, "\0" . $nowCount . "\0");
        hash_update($withoutNewH, "\0" . $oldCount . "\0");
        $added[$logical] = $newRows;
        $counts[$logical] = ['current' => $nowCount, 'id_at_fixture' => $floor,
            'excluded_post_fixture' => count($newRows), 'remaining' => $oldCount];
    }
    $current = hash_final($currentH);
    $withoutNew = hash_final($withoutNewH);
    $exactIdCounts = [];
    $markerCounts = [];
    foreach ($ordered as $logical => [$suffix, $pk, $idKey]) {
        $name = $prefix . ($lane === 'B' ? $suffix : strtoupper($suffix));
        $s = $db->prepare('SELECT COUNT(*) FROM ' . $name
            . ' WHERE STORE_ID=10001 AND ' . $pk . '=?');
        $s->execute([(int)$j['ids'][$idKey]]);
        $exactIdCounts[$logical] = (int)$s->fetchColumn();
    }
    foreach (['goods' => 'GOODS_NAME', 'sku' => 'GOODS_SKU_NO',
        'category' => 'NAME', 'file' => 'FILE_NAME'] as $logical => $col) {
        [$suffix] = $ordered[$logical];
        $name = $prefix . ($lane === 'B' ? $suffix : strtoupper($suffix));
        $s = $db->prepare('SELECT COUNT(*) FROM ' . $name
            . ' WHERE STORE_ID=10001 AND ' . $col . ' LIKE ?');
        $s->execute([$marker . '%']);
        $markerCounts[$logical] = (int)$s->fetchColumn();
    }
    $meta = $lane === 'B'
        ? "SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE 'yoshop_%' AND UPPER(COLUMN_NAME) IN ('GOODS_ID','IMAGE_ID','FILE_ID','VIDEO_ID','VIDEO_COVER_ID')"
        : "SELECT TABLE_NAME,COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME LIKE 'YOSHOP_%' AND COLUMN_NAME IN ('GOODS_ID','IMAGE_ID','FILE_ID','VIDEO_ID','VIDEO_COVER_ID')";
    $nonzeroRefs = [];
    $refRows = $db->query($meta)->fetchAll(PDO::FETCH_ASSOC);
    if (count($refRows) > 250) throw new RuntimeException('Reference metadata cap exceeded');
    foreach ($refRows as $ref) {
        $name = (string)$ref['table_name'];
        $col = (string)$ref['column_name'];
        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name)
            || !preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $col)) {
            throw new RuntimeException('Reference metadata invalid');
        }
        if (strtoupper($name) === strtoupper($prefix . ($lane === 'B' ? 'goods' : 'GOODS'))
            && strtoupper($col) === 'GOODS_ID') continue;
        if (strtoupper($name) === strtoupper($prefix . ($lane === 'B' ? 'upload_file' : 'UPLOAD_FILE'))
            && strtoupper($col) === 'FILE_ID') continue;
        $s = $db->prepare('SELECT COUNT(*) FROM ' . $name . ' WHERE ' . $col . '=?');
        $s->execute([strtoupper($col) === 'GOODS_ID'
            ? (int)$j['ids']['goods'] : (int)$j['ids']['file']]);
        $n = (int)$s->fetchColumn();
        if ($n > 0) $nonzeroRefs[$name . '.' . $col] = $n;
    }
    $relName = $prefix . ($lane === 'B' ? 'goods_category_rel' : 'GOODS_CATEGORY_REL');
    $catName = $prefix . ($lane === 'B' ? 'category' : 'CATEGORY');
    $s = $db->prepare('SELECT COUNT(*) FROM ' . $relName . ' WHERE CATEGORY_ID=?');
    $s->execute([(int)$j['ids']['category']]);
    $categoryRelCount = (int)$s->fetchColumn();
    $s = $db->prepare('SELECT COUNT(*) FROM ' . $catName . ' WHERE PARENT_ID=?');
    $s->execute([(int)$j['ids']['category']]);
    $categoryChildCount = (int)$s->fetchColumn();
    $ownZero = array_sum($exactIdCounts) === 0
        && array_sum($markerCounts) === 0
        && count($nonzeroRefs) === 0
        && $categoryRelCount === 0 && $categoryChildCount === 0;
    echo json_encode(['lane' => $lane, 'mode' => 'reconcile',
        'marker' => $marker, 'journal_created_at' => (int)$j['created_at'],
        'baseline_sha256' => $before, 'current_sha256' => $current,
        'excluding_post_fixture_ids_sha256' => $withoutNew,
        'baseline_restored_by_excluding_new_ids' => hash_equals($before, $withoutNew),
        'table_counts' => $counts, 'post_fixture_rows' => $added,
        'exact_id_counts' => $exactIdCounts, 'marker_prefix_counts' => $markerCounts,
        'nonzero_references' => $nonzeroRefs,
        'category_relation_count' => $categoryRelCount,
        'category_children_count' => $categoryChildCount,
        'own_rows_and_references_zero' => $ownZero,
        'fixture_journal_retained' => is_file($path),
        'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
    exit(0);
}
if ($mode === '--natural-sha') {
    $marker = (string)($argv[3] ?? '');
    if (!preg_match('/^CDXGOODS[0-9A-F]{10}$/', $marker)) {
        throw new RuntimeException('Exact marker required');
    }
    $ordered = ['goods' => 'GOODS_ID', 'sku' => 'ID',
        'image' => 'ID', 'relation' => 'ID',
        'category' => 'CATEGORY_ID', 'file' => 'FILE_ID'];
    $suffix = ['goods' => 'goods', 'sku' => 'goods_sku',
        'image' => 'goods_image', 'relation' => 'goods_category_rel',
        'category' => 'category', 'file' => 'upload_file'];
    $h = hash_init('sha256');
    $counts = [];
    foreach ($ordered as $logical => $pk) {
        $table = $prefix . ($lane === 'B' ? $suffix[$logical] : strtoupper($suffix[$logical]));
        hash_update($h, $logical . "\0");
        $s = $db->query('SELECT * FROM ' . $table
            . ' WHERE STORE_ID=10001 ORDER BY ' . $pk);
        $seen = 0;
        while (($row = $s->fetch(PDO::FETCH_NUM)) !== false) {
            hash_update($h, serialize($row));
            if (++$seen > 30000) throw new RuntimeException('Natural source cap exceeded');
        }
        $counts[$logical] = $seen;
        hash_update($h, "\0" . $seen . "\0");
    }
    $journal = sys_get_temp_dir() . '/cdx-goods-' . strtolower($lane)
        . '-' . strtolower($marker);
    $tokenJournal = sys_get_temp_dir() . '/cdx-goods-w5-token-'
        . strtolower($lane) . '-' . strtolower($marker);
    $marks = [];
    foreach (['goods' => 'GOODS_NAME', 'sku' => 'GOODS_SKU_NO',
        'category' => 'NAME', 'file' => 'FILE_NAME'] as $logical => $column) {
        $table = $prefix . ($lane === 'B' ? $suffix[$logical] : strtoupper($suffix[$logical]));
        $s = $db->prepare('SELECT COUNT(*) FROM ' . $table
            . ' WHERE STORE_ID=10001 AND ' . $column . ' LIKE ?');
        $s->execute([$marker . '%']);
        $marks[$logical] = (int)$s->fetchColumn();
    }
    echo json_encode(['lane' => $lane, 'mode' => 'natural-sha',
        'marker' => $marker, 'six_table_sha256' => hash_final($h),
        'source_counts' => $counts, 'marker_prefix_counts' => $marks,
        'fixture_journal_absent' => !file_exists($journal),
        'token_journal_absent' => !file_exists($tokenJournal),
        'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
    exit(0);
}
if ($mode === '--delivery') {
    $name = $prefix . ($lane === 'B' ? 'delivery' : 'DELIVERY');
    $rows = $db->query('SELECT DELIVERY_ID FROM ' . $name
        . ' WHERE STORE_ID=10001 AND IS_DELETE=0 ORDER BY DELIVERY_ID')
        ->fetchAll(PDO::FETCH_ASSOC);
    echo json_encode(['lane' => $lane, 'mode' => 'delivery',
        'active_count' => count($rows),
        'ids' => array_slice(array_map(static function (array $r): int {
            return (int)$r['delivery_id'];
        }, $rows), 0, 10), 'business_http' => 0, 'business_dml' => 0]), "\n";
    exit(0);
}
$counts = [];
foreach ($tables as $suffix) {
    $table = $lane === 'B' ? $prefix . $suffix : $prefix . strtoupper($suffix);
    $sql = 'SELECT COUNT(*) FROM ' . $table . ' WHERE STORE_ID=10001';
    $counts[$suffix] = (int)$db->query($sql)->fetchColumn();
}
$sql = 'SELECT GOODS_ID, STORE_ID, STATUS, IS_DELETE, GOODS_PRICE_MIN, GOODS_PRICE_MAX, STOCK_TOTAL FROM ' . $prefix . ($lane === 'B' ? 'goods' : 'GOODS') . ' WHERE STORE_ID=10001 AND IS_DELETE=0 ORDER BY GOODS_ID';
$rows = $db->query($sql)->fetchAll(PDO::FETCH_ASSOC);
$eligible = array_values(array_filter($rows, static function (array $r): bool {
    return in_array((int)$r['status'], [10, 20], true);
}));
if ($mode === '--refs' || $mode === '--refcounts' || $mode === '--ownedrefs') {
    $names = ['GOODS_ID', 'CATEGORY_ID', 'IMAGE_ID', 'FILE_ID', 'VIDEO_ID', 'VIDEO_COVER_ID'];
    $physical = $lane === 'B' ? 'yoshop_%' : 'YOSHOP_%';
    if ($lane === 'B') {
        $st = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE ? AND UPPER(COLUMN_NAME) IN (?,?,?,?,?,?) ORDER BY TABLE_NAME,COLUMN_NAME');
    } else {
        $st = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME LIKE ? AND COLUMN_NAME IN (?,?,?,?,?,?) ORDER BY TABLE_NAME,COLUMN_NAME');
    }
    $st->execute(array_merge([$physical], $names));
    $refs = $st->fetchAll(PDO::FETCH_ASSOC);
    if (count($refs) > 250) throw new RuntimeException('Reference metadata unbounded');
    if ($mode === '--ownedrefs') {
        $marker = (string)($argv[3] ?? '');
        if (!preg_match('/^CDXGOODS[0-9A-F]{10}$/', $marker)) throw new RuntimeException('Exact marker required');
        $journal = sys_get_temp_dir() . '/cdx-goods-' . strtolower($lane)
            . '-' . strtolower($marker) . '/journal.json';
        $j = is_file($journal) ? json_decode((string)file_get_contents($journal), true) : null;
        if (!is_array($j) || ($j['lane'] ?? '') !== $lane
            || ($j['marker'] ?? '') !== $marker
            || ($j['phase'] ?? '') !== 'fixture_committed') throw new RuntimeException('Fixture journal identity absent');
        $ids = $j['ids'] ?? [];
        foreach (['goods','sku','image','relation','category','file'] as $key) {
            if (!isset($ids[$key]) || (int)$ids[$key] <= 0) throw new RuntimeException('Journal ID absent');
        }
        $actual = [];
        foreach ($refs as $r) {
            $name = (string)$r['table_name'];
            $col = (string)$r['column_name'];
            if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name)
                || !preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $col)) throw new RuntimeException('Unsafe metadata');
            if (strtoupper($name) === strtoupper($prefix . ($lane === 'B' ? 'goods' : 'GOODS')) && strtoupper($col) === 'GOODS_ID') continue;
            if (strtoupper($name) === strtoupper($prefix . ($lane === 'B' ? 'upload_file' : 'UPLOAD_FILE')) && strtoupper($col) === 'FILE_ID') continue;
            $target = strtoupper($col) === 'GOODS_ID' ? (int)$ids['goods'] : (int)$ids['file'];
            $s = $db->prepare('SELECT COUNT(*) FROM ' . $name . ' WHERE ' . $col . '=?');
            $s->execute([$target]);
            $n = (int)$s->fetchColumn();
            if ($n > 0) $actual[$name . '.' . $col] = $n;
        }
        $rel = $prefix . ($lane === 'B' ? 'goods_category_rel' : 'GOODS_CATEGORY_REL');
        $cat = $prefix . ($lane === 'B' ? 'category' : 'CATEGORY');
        $s = $db->prepare('SELECT COUNT(*) FROM ' . $rel . ' WHERE CATEGORY_ID=?');
        $s->execute([(int)$ids['category']]);
        $actual['category_relation_count'] = (int)$s->fetchColumn();
        $s = $db->prepare('SELECT COUNT(*) FROM ' . $cat . ' WHERE PARENT_ID=?');
        $s->execute([(int)$ids['category']]);
        $actual['category_children_count'] = (int)$s->fetchColumn();
        echo json_encode(['lane' => $lane, 'mode' => 'ownedrefs', 'marker' => $marker,
            'journal_ids' => $ids, 'nonzero_references' => $actual,
            'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
        exit(0);
    }
    if ($mode === '--refcounts') {
        $totals = [];
        foreach ($refs as $r) {
            $name = (string)$r['table_name'];
            $column = (string)$r['column_name'];
            if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name)
                || !preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $column)) {
                throw new RuntimeException('Reference metadata invalid');
            }
            if (strtoupper($column) !== 'GOODS_ID' || strtoupper($name) === strtoupper($prefix . ($lane === 'B' ? 'goods' : 'GOODS'))) continue;
            $totals[$name] = (int)$db->query('SELECT COUNT(*) FROM ' . $name . ' WHERE ' . $column . '>0')->fetchColumn();
        }
        echo json_encode(['lane' => $lane, 'mode' => 'refcounts', 'goods_id_reference_counts' => $totals,
            'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
        exit(0);
    }
    echo json_encode(['lane' => $lane, 'mode' => 'refs', 'reference_columns' => $refs,
        'reference_column_count' => count($refs), 'business_http' => 0,
        'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
    exit(0);
}
if ($mode === '--schema') {
    $physical = array_map(static function (string $suffix) use ($lane, $prefix): string {
        return $prefix . ($lane === 'B' ? $suffix : strtoupper($suffix));
    }, $tables);
    $columns = [];
    foreach ($physical as $table) {
        if ($lane === 'B') {
            $st = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION');
        } else {
            $st = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID');
        }
        $st->execute([$table]);
        $all = $st->fetchAll(PDO::FETCH_ASSOC);
        if (count($all) < 3) throw new RuntimeException('Schema table missing');
        $columns[$table] = ['count' => count($all), 'no_default_nonnull' => []];
        foreach ($all as $c) {
            $nullable = strtoupper((string)($c['is_nullable'] ?? $c['nullable'] ?? ''));
            if ($nullable === 'NO' || $nullable === 'N') {
                if (!isset($c['column_default']) && !isset($c['data_default'])) {
                    $columns[$table]['no_default_nonnull'][] = [$c['column_name'], $c['data_type'], $c['extra'] ?? ''];
                }
            }
        }
    }
    if ($lane === 'B') {
        $marks = implode(',', array_fill(0, count($physical), '?'));
        $st = $db->prepare('SELECT EVENT_OBJECT_TABLE,TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (' . $marks . ')');
        $st->execute($physical);
        $triggers = $st->fetchAll(PDO::FETCH_ASSOC);
        $st = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND (TABLE_NAME IN (' . $marks . ') OR REFERENCED_TABLE_NAME IN (' . $marks . '))');
        $st->execute(array_merge($physical, $physical));
        $fks = $st->fetchAll(PDO::FETCH_ASSOC);
    } else {
        $quoted = "'" . implode("','", $physical) . "'";
        $triggers = $db->query('SELECT TABLE_NAME,TRIGGER_NAME FROM USER_TRIGGERS WHERE TABLE_NAME IN (' . $quoted . ')')->fetchAll(PDO::FETCH_ASSOC);
        $fks = $db->query("SELECT TABLE_NAME,CONSTRAINT_NAME,R_CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND (TABLE_NAME IN (" . $quoted . ") OR R_CONSTRAINT_NAME IN (SELECT CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME IN (" . $quoted . ')))')->fetchAll(PDO::FETCH_ASSOC);
    }
    $markers = [];
    foreach (['goods' => 'GOODS_NAME', 'goods_sku' => 'GOODS_SKU_NO', 'category' => 'NAME', 'upload_file' => 'FILE_NAME'] as $suffix => $column) {
        $table = $prefix . ($lane === 'B' ? $suffix : strtoupper($suffix));
        $markers[$suffix] = (int)$db->query('SELECT COUNT(*) FROM ' . $table . ' WHERE STORE_ID=10001 AND ' . $column . " LIKE 'CDXGOODS%'")->fetchColumn();
    }
    echo json_encode(['lane' => $lane, 'mode' => 'schema', 'tables' => $columns, 'triggers' => $triggers, 'foreign_keys' => $fks, 'marker_prefix_counts' => $markers, 'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
    exit(0);
}
echo json_encode(['lane' => $lane, 'target' => $lane === 'B' ? 'code-local-mysql' : 'nonproduction-dm', 'source_counts' => $counts, 'nondeleted_goods' => count($rows), 'eligible_goods' => count($eligible), 'eligible_ids' => array_slice(array_map(static function (array $r): int { return (int)$r['goods_id']; }, $eligible), 0, 5), 'price_types' => array_slice(array_map(static function (array $r): array { return [gettype($r['goods_price_min']), gettype($r['goods_price_max'])]; }, $eligible), 0, 5), 'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
