<?php
declare(strict_types=1);

// B-only, one exact journal. No database DML, no business HTTP, no broad delete.
$phase = (string)($argv[1] ?? '');
if (!in_array($phase, ['--preflight', '--close'], true) || count($argv) !== 2) {
    fwrite(STDERR, "DENIED: exact phase only\n");
    exit(78);
}
if ($phase === '--close') {
    $reviewed = (string)getenv('CDX_GOODS_JOURNAL_CLOSE_SHA256');
    if ((string)getenv('CDX_GOODS_JOURNAL_CLOSE_APPROVAL')
        !== 'CLOSE_ONLY_B_GOODS_3F10DC6CEA_V1'
        || !preg_match('/^[a-f0-9]{64}$/', $reviewed)
        || hash_file('sha256', __FILE__) !== $reviewed) {
        fwrite(STDERR, "DENIED: reviewed exact closer SHA/gate required\n");
        exit(78);
    }
}
$stage = 'start';
set_exception_handler(static function (Throwable $e) use (&$stage): void {
    fwrite(STDERR, 'B_GOODS_JOURNAL_CLOSE_STOP stage=' . $stage
        . ' class=' . get_class($e) . "\n");
    exit(3);
});
$marker = 'CDXGOODS3F10DC6CEA';
$probe = '/Users/wiscourper/Documents/workspace/zhct/zhctprompt'
    . '/work_ai_api/2026-09-30-dm8-interface-acceptance'
    . '/parallel-ai-goods-read3-20261006/probe_goods.php';
$probeSha = 'c9f55f18cf25137f5adf442bd6110972b5d2217fe861a11245d59b06c605977e';
$php = '/opt/homebrew/opt/php@7.4/bin/php';
if (!is_file($probe) || hash_file('sha256', $probe) !== $probeSha
    || !is_file($php)) {
    throw new RuntimeException('Reviewed read-only probe or PHP runtime drift');
}
$journalDir = sys_get_temp_dir() . '/cdx-goods-b-cdxgoods3f10dc6cea';
$journalPath = $journalDir . '/journal.json';
$canonicalDir = realpath(sys_get_temp_dir()) . '/cdx-goods-b-cdxgoods3f10dc6cea';
if (is_link($journalDir) || is_link($journalPath)
    || !is_dir($journalDir) || !is_file($journalPath)
    || realpath($journalDir) !== $canonicalDir
    || (fileperms($journalDir) & 0777) !== 0700
    || (fileperms($journalPath) & 0777) !== 0600
    || scandir($journalDir) !== ['.', '..', 'journal.json']) {
    throw new RuntimeException('Private journal path/mode/contents drift');
}
$journal = json_decode((string)file_get_contents($journalPath), true);
$expectedIds = ['goods' => 10012, 'sku' => 10033,
    'category' => 10007, 'file' => 24353,
    'image' => 10044, 'relation' => 10015];
if (!is_array($journal) || ($journal['lane'] ?? '') !== 'B'
    || ($journal['marker'] ?? '') !== $marker
    || ($journal['phase'] ?? '') !== 'cleanup_committed'
    || ($journal['ids'] ?? []) !== $expectedIds
    || (int)($journal['created_at'] ?? 0) !== 1791353618
    || ($journal['natural_sha256'] ?? '')
        !== '9e5e20beb054ff88188c97df1c3f305e7328078d6c2a72ca29a55134350b2a71') {
    throw new RuntimeException('Exact journal identity/phase/baseline drift');
}
foreach (['/cdx-goods-w5-token-b-cdxgoods3f10dc6cea',
    '/cdx-goods-token-b-cdxgoods3f10dc6cea',
    '/cdx-goods-ae-b-cdxgoods3f10dc6cea'] as $suffix) {
    if (file_exists(sys_get_temp_dir() . $suffix)) {
        throw new RuntimeException('Related token/write journal remains');
    }
}
$stage = 'fresh-readonly-reconcile';
$spec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$proc = proc_open([$php, $probe, 'B', '--reconcile', $marker],
    $spec, $pipes, null, null, ['bypass_shell' => true]);
if (!is_resource($proc)) throw new RuntimeException('Read-only probe failed to start');
$raw = stream_get_contents($pipes[1], 65537);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2], 2049);
fclose($pipes[2]);
$exit = proc_close($proc);
if ($exit !== 0 || !is_string($raw) || strlen($raw) > 65536
    || !is_string($stderr) || strlen($stderr) > 2048) {
    throw new RuntimeException('Fresh read-only probe failed/oversized');
}
$proof = json_decode($raw, true);
if (!is_array($proof) || json_last_error() !== JSON_ERROR_NONE
    || ($proof['lane'] ?? '') !== 'B'
    || ($proof['mode'] ?? '') !== 'reconcile'
    || ($proof['marker'] ?? '') !== $marker
    || (int)($proof['journal_created_at'] ?? 0) !== 1791353618
    || ($proof['baseline_sha256'] ?? '') !== $journal['natural_sha256']
    || ($proof['excluding_post_fixture_ids_sha256'] ?? '')
        !== $journal['natural_sha256']
    || ($proof['baseline_restored_by_excluding_new_ids'] ?? false) !== true
    || ($proof['own_rows_and_references_zero'] ?? false) !== true
    || ($proof['fixture_journal_retained'] ?? false) !== true
    || ($proof['nonzero_references'] ?? null) !== []
    || (int)($proof['category_relation_count'] ?? -1) !== 0
    || (int)($proof['category_children_count'] ?? -1) !== 0
    || (int)($proof['business_http'] ?? -1) !== 0
    || (int)($proof['business_dml'] ?? -1) !== 0) {
    throw new RuntimeException('Fresh read-only reconciliation not accepted');
}
foreach (['goods', 'sku', 'image', 'relation', 'category', 'file'] as $key) {
    if ((int)($proof['exact_id_counts'][$key] ?? -1) !== 0) {
        throw new RuntimeException('Owned primary key still present');
    }
}
foreach (['goods', 'sku', 'category', 'file'] as $key) {
    if ((int)($proof['marker_prefix_counts'][$key] ?? -1) !== 0) {
        throw new RuntimeException('Owned marker still present');
    }
}
$post = $proof['post_fixture_rows'] ?? null;
$counts = $proof['table_counts'] ?? null;
if (!is_array($post) || !is_array($counts)) {
    throw new RuntimeException('Post-fixture source inventory absent');
}
foreach (['goods', 'sku', 'image', 'relation', 'category'] as $key) {
    if (($post[$key] ?? null) !== []
        || (int)($counts[$key]['excluded_post_fixture'] ?? -1) !== 0
        || (int)($counts[$key]['id_at_fixture'] ?? 0) !== $expectedIds[$key]) {
        throw new RuntimeException('Unexpected non-file natural row change');
    }
}
if (!is_array($post['file'] ?? null)
    || count($post['file']) < 8 || count($post['file']) > 40
    || (int)($counts['file']['excluded_post_fixture'] ?? -1) !== count($post['file'])
    || (int)($counts['file']['id_at_fixture'] ?? 0) !== 24353) {
    throw new RuntimeException('Post-fixture file-only inventory drift');
}
$expectedReviewed = [
    24354 => '754ef5627130242776bea21e468995126d4a21fc5ab2e46334065a2adc03ac5b',
    24355 => '1bd11a42f5517417b76b4df5acb197fecaf4d946ef3688d038c11d34aa6e1aab',
    24356 => '8c84cea34e920933170ea76de3b8d980f7d5f31ad2f575f49c84a05ebb675e80',
    24357 => '9c5c5224e7cc80993dcbdf8967e7e6d1dae41c555c7ed614c4b98582e6a52a61',
    24358 => '1763e462438926fd246cf6641a2dcbcd970d8c198502e2e1a9257022b74d3017',
    24359 => '4ed7a37ec04db5baa2774a96de9dceac1581f33597d6b5151c4756c9f6f8e6ca',
    24360 => '124258c7ffe8eb2fb6a30c4f3358ed9cc7fdc52b0199cee1c08cb16dac7db7a4',
    24361 => 'aaf761ace14baaf4bcfbed75c54be9920b1439da81d457134dbe76e7661192d4',
];
$seenIds = [];
$summary = [];
foreach ($post['file'] as $row) {
    $id = (int)($row['id'] ?? 0);
    $created = (int)($row['create_time'] ?? 0);
    $digest = (string)($row['row_sha256'] ?? '');
    if ($id <= 24353 || isset($seenIds[$id]) || $created <= 1791353618
        || !preg_match('/^[a-f0-9]{64}$/', $digest)
        || (isset($expectedReviewed[$id]) && $digest !== $expectedReviewed[$id])) {
        throw new RuntimeException('Post-fixture file identity/time/hash drift');
    }
    $seenIds[$id] = true;
    $summary[] = ['id' => $id, 'create_time' => $created];
}
foreach ($expectedReviewed as $id => $digest) {
    if (!isset($seenIds[$id])) throw new RuntimeException('Reviewed external row missing');
}
$stage = 'qualified';
if ($phase === '--preflight') {
    echo json_encode(['phase' => $phase, 'qualified' => true,
        'marker' => $marker, 'baseline_sha256' => $journal['natural_sha256'],
        'current_sha256' => $proof['current_sha256'],
        'external_file_rows' => $summary,
        'owned_ids_markers_references_zero' => true,
        'journal_retained' => true,
        'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
    exit(0);
}
if (!unlink($journalPath) || !rmdir($journalDir)
    || file_exists($journalPath) || is_dir($journalDir)) {
    throw new RuntimeException('Exact private journal removal failed');
}
echo json_encode(['phase' => $phase, 'qualified' => true,
    'marker' => $marker, 'external_file_count' => count($summary),
    'journal_absent' => true, 'database_writes' => 0,
    'business_http' => 0]), "\n";
