<?php
declare(strict_types=1);

// DEFAULT DENY. Only nonproduction B/C, own CDXGOODS marker, six table rows.
// This file is a review artifact. Do not enable before an independent safety review.
$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if (!in_array($phase, ['--probe', '--create', '--cleanup', '--audit'], true)
    || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXGOODS[0-9A-F]{10}$/', $marker)) {
    fwrite(STDERR, "DENIED: exact phase/lane/marker required\n");
    exit(78);
}
if (in_array($phase, ['--create', '--cleanup', '--audit'], true)) {
    $sha = (string)getenv('CDX_GOODS_REVIEWED_SHA256');
    if ((string)getenv('CDX_GOODS_WRITE_APPROVAL') !== 'OWNED_GOODS_FIXTURE_V1'
        || !preg_match('/^[0-9a-f]{64}$/', $sha)
        || ($lane === 'B' && hash_file('sha256', __FILE__) !== $sha)
        || ($lane === 'C' && (string)getenv('CDX_GOODS_SOURCE_CHECKED') !== '1')) {
        fwrite(STDERR, "DENIED: exact reviewed SHA/write gate required\n");
        exit(78);
    }
}
$stage = 'start';
set_exception_handler(static function (Throwable $e) use (&$stage, $lane, $marker): void {
    fwrite(STDERR, 'GOODS_FIXTURE_STOP lane=' . $lane . ' marker=' . $marker
        . ' stage=' . $stage . ' class=' . get_class($e) . "\n");
    exit(3);
});
$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;
foreach ([
    'app/store/controller/Goods.php' => 'f96dd2dac9e05d3ca86cf3dc9630b601ab984fad9ff042ba49bbf1ed6d6f22a4',
    'app/store/model/Goods.php' => '808061924d69a82d1578661a35f87c725d1c65d8e204e8042250aa310a2669a9',
] as $file => $sha) {
    if (!is_file($root . '/' . $file) || hash_file('sha256', $root . '/' . $file) !== $sha) {
        throw new RuntimeException('Isolated source drift');
    }
}
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/config 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 .env.local');
        }
    }
    $connect = static function () use ($cfg): PDO {
        return 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]);
    };
    $table = ['goods' => 'yoshop_goods', 'sku' => 'yoshop_goods_sku',
        'image' => 'yoshop_goods_image', 'relation' => 'yoshop_goods_category_rel',
        'category' => 'yoshop_category', 'file' => 'yoshop_upload_file'];
} 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/config drift');
    }
    $connect = static function () use ($env): PDO {
        $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');
        return $db;
    };
    $table = ['goods' => 'YOSHOP_GOODS', 'sku' => 'YOSHOP_GOODS_SKU',
        'image' => 'YOSHOP_GOODS_IMAGE', 'relation' => 'YOSHOP_GOODS_CATEGORY_REL',
        'category' => 'YOSHOP_CATEGORY', 'file' => 'YOSHOP_UPLOAD_FILE'];
}
$db = $connect();
$query = static function (PDO $d, string $sql, array $args = []): array {
    $s = $d->prepare($sql);
    $s->execute($args);
    return $s->fetchAll(PDO::FETCH_ASSOC);
};
$count = static function (PDO $d, string $sql, array $args = []): int {
    $s = $d->prepare($sql);
    $s->execute($args);
    return (int)$s->fetchColumn();
};
$owned = static function (PDO $d) use ($table, $marker, $query): array {
    return [
        'goods' => $query($d, 'SELECT * FROM ' . $table['goods'] . ' WHERE STORE_ID=10001 AND GOODS_NAME=?', [$marker]),
        'sku' => $query($d, 'SELECT * FROM ' . $table['sku'] . ' WHERE STORE_ID=10001 AND GOODS_SKU_NO=?', [$marker . 'SKU']),
        'category' => $query($d, 'SELECT * FROM ' . $table['category'] . ' WHERE STORE_ID=10001 AND NAME=?', [$marker . 'CAT']),
        'file' => $query($d, 'SELECT * FROM ' . $table['file'] . ' WHERE STORE_ID=10001 AND FILE_NAME=?', [$marker . '.png']),
    ];
};
$stage = 'preflight';
$self = $owned($db);
$counts = array_map('count', $self);
$allZero = $counts === ['goods' => 0, 'sku' => 0, 'category' => 0, 'file' => 0];
$physical = array_values($table);
$quoted = "'" . implode("','", $physical) . "'";
if ($lane === 'B') {
    $marks = implode(',', array_fill(0, count($physical), '?'));
    $triggers = $count($db, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (' . $marks . ')', $physical);
    $fks = $count($db, 'SELECT COUNT(*) 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 . '))', array_merge($physical, $physical));
} else {
    $triggers = $count($db, 'SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME IN (' . $quoted . ')');
    $fks = $count($db, "SELECT COUNT(*) 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 . ')))');
}
if ($triggers !== 0 || $fks !== 0) throw new RuntimeException('Trigger/FK gate changed');
$natural = static function (PDO $d) use ($table): string {
    $pk = ['goods' => 'GOODS_ID', 'sku' => 'ID', 'image' => 'ID',
        'relation' => 'ID', 'category' => 'CATEGORY_ID', 'file' => 'FILE_ID'];
    $h = hash_init('sha256');
    foreach ($table as $logical => $name) {
        hash_update($h, $logical . "\0");
        $s = $d->query('SELECT * FROM ' . $name
            . ' WHERE STORE_ID=10001 ORDER BY ' . $pk[$logical]);
        $seen = 0;
        while (($r = $s->fetch(PDO::FETCH_NUM)) !== false) {
            hash_update($h, serialize($r));
            if (++$seen > 30000) throw new RuntimeException('Natural snapshot cap exceeded');
        }
        hash_update($h, "\0" . $seen . "\0");
    }
    return hash_final($h);
};
$domainColumn = $lane === 'C' ? '"DOMAIN"' : 'DOMAIN';
$insertSql = [
    'category' => 'INSERT INTO ' . $table['category']
        . ' (NAME,PARENT_ID,IMAGE_ID,STATUS,SORT,STORE_ID,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,0,0,0,0,10001,?,?)',
    'file' => 'INSERT INTO ' . $table['file']
        . ' (GROUP_ID,CHANNEL,STORAGE,' . $domainColumn . ',FILE_TYPE,FILE_NAME,FILE_PATH,FILE_SIZE,FILE_EXT,COVER,UPLOADER_ID,IS_RECYCLE,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (0,10,?,?,?,?,?,0,?,?,0,0,0,10001,?,?)',
    'goods' => 'INSERT INTO ' . $table['goods']
        . ' (GOODS_NAME,GOODS_NO,SPEC_TYPE,GOODS_PRICE_MIN,GOODS_PRICE_MAX,LINE_PRICE_MIN,LINE_PRICE_MAX,STOCK_TOTAL,CONTENT,STATUS,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,?,10,12.34,12.34,15.00,15.00,0,?,20,0,10001,?,?)',
    'sku' => 'INSERT INTO ' . $table['sku']
        . ' (GOODS_SKU_ID,GOODS_ID,IMAGE_ID,GOODS_SKU_NO,GOODS_PRICE,LINE_PRICE,STOCK_NUM,GOODS_WEIGHT,GOODS_PROPS,SPEC_VALUE_IDS,STORE_ID,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,?,?,?,12.34,15.00,0,0.5,?,?,10001,?,?)',
    'image' => 'INSERT INTO ' . $table['image']
        . ' (GOODS_ID,IMAGE_ID,STORE_ID,CREATE_TIME) VALUES (?,?,10001,?)',
    'relation' => 'INSERT INTO ' . $table['relation']
        . ' (GOODS_ID,CATEGORY_ID,STORE_ID,CREATE_TIME) VALUES (?,?,10001,?)',
];
if (in_array($phase, ['--probe', '--create'], true)) {
    foreach ($insertSql as $logical => $sql) {
        $db->prepare($sql); // compile-only, before any journal or business DML
    }
}
if ($phase === '--probe') {
    echo json_encode(['phase' => $phase, 'lane' => $lane, 'marker' => $marker,
        'marker_zero' => $allZero, 'owned_counts' => $counts,
        'triggers' => $triggers, 'foreign_keys' => $fks,
        'insert_prepare_ready' => true,
        'business_http' => 0, 'business_dml' => 0]), "\n";
    exit($allZero ? 0 : 3);
}
$journalDir = sys_get_temp_dir() . '/cdx-goods-' . strtolower($lane) . '-' . strtolower($marker);
$journalPath = $journalDir . '/journal.json';
$journal = null;
$saveJournal = static function (array $value) use ($journalPath): void {
    $body = json_encode($value, JSON_UNESCAPED_SLASHES);
    $tmp = $journalPath . '.tmp';
    if (!is_string($body) || file_put_contents($tmp, $body, LOCK_EX) !== strlen($body)
        || !chmod($tmp, 0600) || !rename($tmp, $journalPath)) {
        throw new RuntimeException('Private journal write failed');
    }
};
if ($phase === '--create') {
    $original = $natural($db);
    if (!$allZero || file_exists($journalDir)
        || !mkdir($journalDir, 0700) || !chmod($journalDir, 0700)) {
        throw new RuntimeException('Marker/journal not pristine');
    }
    $journal = ['lane' => $lane, 'marker' => $marker,
        'phase' => 'before_insert', 'ids' => [], 'created_at' => time(),
        'natural_sha256' => $original];
    $saveJournal($journal);
    echo json_encode(['phase' => 'journal_ready', 'lane' => $lane,
        'marker' => $marker, 'journal' => $journalPath,
        'business_dml' => 0]), "\n";
    flush();
    $stage = 'fixture-transaction';
    $ids = [];
    $now = time();
    $db->beginTransaction();
    try {
        $exec = static function (string $sql, array $args) use ($db): void {
            $s = $db->prepare($sql);
            $s->execute($args);
            if ($s->rowCount() !== 1) throw new RuntimeException('Insert affected-row mismatch');
        };
        $oneId = static function (string $logical, string $pk, string $field, string $value)
            use ($db, $query, $table): int {
            $r = $query($db, 'SELECT ' . $pk . ' FROM ' . $table[$logical]
                . ' WHERE STORE_ID=10001 AND ' . $field . '=?', [$value]);
            if (count($r) !== 1 || (int)$r[0][strtolower($pk)] <= 0) {
                throw new RuntimeException('Identity unresolved');
            }
            return (int)$r[0][strtolower($pk)];
        };
        $exec($insertSql['category'], [$marker . 'CAT', $now, $now]);
        $ids['category'] = $oneId('category', 'CATEGORY_ID', 'NAME', $marker . 'CAT');
        $exec($insertSql['file'],
            ['local', '', 10, $marker . '.png', 'codex/' . strtolower($marker) . '.png', 'png', '', $now, $now]);
        $ids['file'] = $oneId('file', 'FILE_ID', 'FILE_NAME', $marker . '.png');
        $exec($insertSql['goods'],
            [$marker, $marker, '<p>' . $marker . '</p>', $now, $now]);
        $ids['goods'] = $oneId('goods', 'GOODS_ID', 'GOODS_NAME', $marker);
        $exec($insertSql['sku'],
            ['0', $ids['goods'], $ids['file'], $marker . 'SKU', '[]', '[]', $now, $now]);
        $ids['sku'] = $oneId('sku', 'ID', 'GOODS_SKU_NO', $marker . 'SKU');
        $exec($insertSql['image'],
            [$ids['goods'], $ids['file'], $now]);
        $exec($insertSql['relation'],
            [$ids['goods'], $ids['category'], $now]);
        foreach (['image', 'relation'] as $logical) {
            $r = $query($db, 'SELECT ID FROM ' . $table[$logical]
                . ' WHERE GOODS_ID=? AND STORE_ID=10001', [$ids['goods']]);
            if (count($r) !== 1) throw new RuntimeException('Child identity unresolved');
            $ids[$logical] = (int)$r[0]['id'];
        }
        if (min($ids) <= 0 || array_map('count', $owned($db))
            !== ['goods' => 1, 'sku' => 1, 'category' => 1, 'file' => 1]) {
            throw new RuntimeException('Fixture link/marker mismatch');
        }
        $db->commit();
        $journal['phase'] = 'fixture_committed';
        $journal['ids'] = $ids;
        $saveJournal($journal);
        echo json_encode(['phase' => 'fixture_committed', 'lane' => $lane,
            'marker' => $marker, 'journal' => $journalPath,
            'ids' => $ids, 'business_dml' => 6, 'business_http' => 0]), "\n";
        exit(0);
    } catch (Throwable $e) {
        if ($db->inTransaction()) $db->rollBack();
        $journal['phase'] = 'needs_recovery_review';
        $journal['ids'] = $ids;
        $saveJournal($journal);
        throw $e;
    }
}

if (!is_dir($journalDir) || !is_file($journalPath)) {
    throw new RuntimeException('Exact recovery journal absent');
}
$journal = json_decode((string)file_get_contents($journalPath), true);
if (!is_array($journal) || ($journal['lane'] ?? '') !== $lane
    || ($journal['marker'] ?? '') !== $marker) {
    throw new RuntimeException('Recovery journal identity mismatch');
}
$stage = 'owned-resolution';
$self = $owned($db);
$counts = array_map('count', $self);
$allZero = $counts === ['goods' => 0, 'sku' => 0, 'category' => 0, 'file' => 0];
if ($phase === '--audit') {
    $verify = $connect(); // second process and fresh DB connection
    $verifyCounts = array_map('count', $owned($verify));
    $prior = $journal['ids'] ?? [];
    $childZero = isset($prior['goods'], $prior['category'], $prior['file'])
        && (int)$prior['goods'] > 0 && (int)$prior['category'] > 0
        && (int)$prior['file'] > 0
        && $count($verify, 'SELECT COUNT(*) FROM ' . $table['image']
            . ' WHERE GOODS_ID=?', [(int)$prior['goods']]) === 0
        && $count($verify, 'SELECT COUNT(*) FROM ' . $table['relation']
            . ' WHERE GOODS_ID=? OR CATEGORY_ID=?',
            [(int)$prior['goods'], (int)$prior['category']]) === 0
        && $count($verify, 'SELECT COUNT(*) FROM ' . $table['sku']
            . ' WHERE GOODS_ID=?', [(int)$prior['goods']]) === 0;
    $naturalUnchanged = isset($journal['natural_sha256'])
        && hash_equals((string)$journal['natural_sha256'], $natural($verify));
    $ok = $verifyCounts === ['goods' => 0, 'sku' => 0,
        'category' => 0, 'file' => 0]
        && $childZero && $naturalUnchanged
        && ($journal['phase'] ?? '') === 'cleanup_committed';
    if ($ok) {
        if (!unlink($journalPath) || !rmdir($journalDir)
            || file_exists($journalPath) || is_dir($journalDir)) {
            throw new RuntimeException('Verified journal removal failed');
        }
    }
    echo json_encode(['phase' => $phase, 'lane' => $lane, 'marker' => $marker,
        'owned_counts' => $verifyCounts, 'child_zero' => $childZero,
        'natural_unchanged' => $naturalUnchanged,
        'journal_absent' => $ok && !file_exists($journalPath) && !is_dir($journalDir),
        'independent_zero' => $ok,
        'business_http' => 0, 'business_dml' => 0]), "\n";
    exit($ok ? 0 : 3);
}
if ($phase !== '--cleanup' || $counts !== ['goods' => 1, 'sku' => 1, 'category' => 1, 'file' => 1]) {
    throw new RuntimeException('Ambiguous owned fixture; cleanup refused');
}
$ids = [
    'goods' => (int)$self['goods'][0]['goods_id'],
    'sku' => (int)$self['sku'][0]['id'],
    'category' => (int)$self['category'][0]['category_id'],
    'file' => (int)$self['file'][0]['file_id'],
];
foreach (($journal['ids'] ?? []) as $key => $value) {
    if (isset($ids[$key]) && (int)$value > 0 && $ids[$key] !== (int)$value) {
        throw new RuntimeException('Journal identity drift');
    }
}
if (min($ids) <= 0 || (int)$self['sku'][0]['goods_id'] !== $ids['goods']
    || (int)$self['sku'][0]['image_id'] !== $ids['file']) {
    throw new RuntimeException('Owned link drift');
}
$image = $query($db, 'SELECT ID FROM ' . $table['image']
    . ' WHERE GOODS_ID=? AND IMAGE_ID=? AND STORE_ID=10001',
    [$ids['goods'], $ids['file']]);
$relation = $query($db, 'SELECT ID FROM ' . $table['relation']
    . ' WHERE GOODS_ID=? AND CATEGORY_ID=? AND STORE_ID=10001',
    [$ids['goods'], $ids['category']]);
if (count($image) !== 1 || count($relation) !== 1) {
    throw new RuntimeException('Owned child relation drift');
}
$ids['image'] = (int)$image[0]['id'];
$ids['relation'] = (int)$relation[0]['id'];
foreach (['image', 'relation'] as $key) {
    if (isset($journal['ids'][$key]) && (int)$journal['ids'][$key] > 0
        && $ids[$key] !== (int)$journal['ids'][$key]) {
        throw new RuntimeException('Child journal identity drift');
    }
}
$stage = 'all-reference-scan';
$metaSql = $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')";
$refs = $query($db, $metaSql);
if (count($refs) > 250) throw new RuntimeException('Reference scan cap exceeded');
foreach ($refs as $ref) {
    $name = (string)$ref['table_name'];
    $column = (string)$ref['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 unsafe');
    }
    if (strcasecmp($name, $table['goods']) === 0 && strcasecmp($column, 'GOODS_ID') === 0) continue;
    if (strcasecmp($name, $table['file']) === 0 && strcasecmp($column, 'FILE_ID') === 0) continue;
    $goodsRef = strcasecmp($column, 'GOODS_ID') === 0;
    $target = $goodsRef ? $ids['goods'] : $ids['file'];
    $expected = 0;
    if ($goodsRef && in_array(strtolower($name),
        [strtolower($table['sku']), strtolower($table['image']), strtolower($table['relation'])], true)) $expected = 1;
    if (!$goodsRef && strcasecmp($column, 'IMAGE_ID') === 0
        && in_array(strtolower($name), [strtolower($table['sku']), strtolower($table['image'])], true)) $expected = 1;
    if ($count($db, 'SELECT COUNT(*) FROM ' . $name . ' WHERE ' . $column . '=?', [$target]) !== $expected) {
        throw new RuntimeException('Unexpected reference; cleanup refused');
    }
}
if ($count($db, 'SELECT COUNT(*) FROM ' . $table['relation']
    . ' WHERE CATEGORY_ID=?', [$ids['category']]) !== 1
    || $count($db, 'SELECT COUNT(*) FROM ' . $table['category']
    . ' WHERE PARENT_ID=?', [$ids['category']]) !== 0) {
    throw new RuntimeException('Unexpected category reference; cleanup refused');
}
$stage = 'exact-cleanup';
$db->beginTransaction();
try {
    foreach ([
        ['image', 'ID', $ids['image'], 'GOODS_ID', $ids['goods']],
        ['relation', 'ID', $ids['relation'], 'GOODS_ID', $ids['goods']],
        ['sku', 'ID', $ids['sku'], 'GOODS_SKU_NO', $marker . 'SKU'],
        ['goods', 'GOODS_ID', $ids['goods'], 'GOODS_NAME', $marker],
        ['category', 'CATEGORY_ID', $ids['category'], 'NAME', $marker . 'CAT'],
        ['file', 'FILE_ID', $ids['file'], 'FILE_NAME', $marker . '.png'],
    ] as [$logical, $pk, $id, $field, $value]) {
        $s = $db->prepare('DELETE FROM ' . $table[$logical]
            . ' WHERE ' . $pk . '=? AND STORE_ID=10001 AND ' . $field . '=?');
        $s->execute([$id, $value]);
        if ($s->rowCount() !== 1) throw new RuntimeException('Exact cleanup affected-row mismatch');
    }
    $db->commit();
} catch (Throwable $e) {
    if ($db->inTransaction()) $db->rollBack();
    throw $e;
}
$journal['phase'] = 'cleanup_committed';
$journal['ids'] = $ids;
$saveJournal($journal);
echo json_encode(['phase' => 'cleanup_committed', 'lane' => $lane,
    'marker' => $marker, 'journal' => $journalPath,
    'deleted_exact_rows' => 6, 'independent_audit_required' => true]), "\n";
