#!/usr/bin/env node import { createHash } from 'node:crypto'; import { readFile, rename, writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; const TEST_MARKER = 'TEST-202609'; const FACT_OUTCOMES = new Set([ 'concrete_shared_children_with_requested_facts', 'concrete_shared_children_with_fact_mismatch', 'parent_row_facts_without_concrete_child', 'requested_split_facts_not_visible_and_no_concrete_child' ]); function object(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; } function text(value) { return typeof value === 'string' ? value.trim() : String(value ?? '').trim(); } function canonicalDate(value) { const match = text(value).match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/); if (!match) return ''; const candidate = `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`; const parsed = new Date(`${candidate}T00:00:00.000Z`); return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === candidate ? candidate : ''; } function unique(values) { return [...new Set(values)]; } function clone(value) { return structuredClone(value); } function refIdentifier(ref = {}) { return text(ref.identifier || ref.group_no || ref.child_order_no || ref.parent_group_no || ref.ddid || ref.tid); } function refKey(ref = {}) { return [text(ref.kind), text(ref.tid), text(ref.ddid), refIdentifier(ref)].join('|'); } function mergeRefs(existing = [], incoming = []) { const result = new Map(); for (const ref of [...existing, ...incoming]) { const key = refKey(ref); if (!text(ref.kind) || !refIdentifier(ref) || !text(ref.tid)) throw new Error(`invalid_created_ref:${key}`); const prior = result.get(key); if (prior && JSON.stringify(prior) !== JSON.stringify(ref)) { const critical = ['kind', 'identifier', 'group_no', 'child_order_no', 'parent_group_no', 'tid', 'ddid', 'departure_date', 'owner_account', 'marker']; if (critical.some((field) => text(prior[field]) && text(ref[field]) && text(prior[field]) !== text(ref[field]))) { throw new Error(`created_ref_conflict:${key}`); } } result.set(key, { ...(prior || {}), ...clone(ref) }); } return [...result.values()]; } function reportFromPayload(payload) { const root = object(payload); const extension = object(root.extension); if (Object.keys(extension).length) return extension; const report = object(root.report); return Object.keys(report).length ? report : root; } function assertExactSet(actual, expected, label) { const left = unique(actual.map(text).filter(Boolean)).sort(); const right = unique(expected.map(text).filter(Boolean)).sort(); if (JSON.stringify(left) !== JSON.stringify(right)) { throw new Error(`${label}:expected=${right.join(',')}:actual=${left.join(',')}`); } } export function recordSplitProbeResult(stateValue, allowlistValue, payloadValue, expectedRunId) { const state = clone(stateValue); const allowlist = clone(allowlistValue); const report = reportFromPayload(payloadValue); const run = object(state.run); const shared = object(object(state.creation).shared); const receipt = object(report.erp_receipt); const probe = object(receipt.split_order_probe); const verification = object(report.verification); const verificationProbe = object(verification.split_order_probe); const rawExpectedDates = (Array.isArray(shared.batch_dates) ? shared.batch_dates : []).map(text).filter(Boolean); const expectedDates = rawExpectedDates.map(canonicalDate).filter(Boolean).sort(); const blockers = []; if (!text(expectedRunId) || run.run_id !== expectedRunId) blockers.push('expected_run_id_mismatch'); if (run.marker !== TEST_MARKER || allowlist.marker !== TEST_MARKER) blockers.push('marker_mismatch'); if (!text(run.account) || allowlist.account !== run.account) blockers.push('account_mismatch'); if (allowlist.run_id !== run.run_id || allowlist.allowlist_id !== state.cleanup?.allowlist_id) blockers.push('allowlist_scope_mismatch'); if (shared.batch_split_order_probe !== true || expectedDates.length < 2) blockers.push('state_not_configured_for_split_probe'); if (expectedDates.length !== rawExpectedDates.length) blockers.push('state_batch_dates_invalid'); if (state.cleanup?.delete_authorized === true || allowlist.delete_authorized === true || allowlist.delete_enabled === true) { blockers.push('delete_must_remain_disabled_while_recording'); } if (report.write_attempted !== true || report.no_erp_write === true) blockers.push('probe_write_boundary_not_proven'); if (probe.status !== 'all_dates_facts_determined' || verificationProbe.facts_status !== 'all_dates_facts_determined') blockers.push('probe_facts_not_complete'); const perDate = Array.isArray(probe.per_date) ? probe.per_date.map(object) : []; if (perDate.length !== expectedDates.length) blockers.push('probe_date_count_mismatch'); if (perDate.some((fact) => !canonicalDate(fact.date))) blockers.push('probe_date_invalid'); try { assertExactSet(perDate.map((fact) => canonicalDate(fact.date)), expectedDates, 'probe_dates_mismatch'); } catch (error) { blockers.push(error.message); } const groupNumbers = Array.isArray(receipt.group_numbers) ? receipt.group_numbers.map(text).filter(Boolean) : []; try { assertExactSet(perDate.map((fact) => fact.parent_group_no), groupNumbers, 'probe_group_numbers_mismatch'); } catch (error) { blockers.push(error.message); } if (blockers.length) throw new Error(`split_probe_record_blocked:${unique(blockers).join(',')}`); const recordedRefs = []; const factSummary = []; const suffix = text(state.creation?.order_suffix); for (const fact of perDate) { const date = canonicalDate(fact.date); const groupNo = text(fact.parent_group_no); const tid = text(fact.parent_tid); const ownership = object(fact.ownership); const childRefs = Array.isArray(fact.child_refs) ? fact.child_refs.map(object) : []; const childCount = Number(fact.child_count); const factBlockers = []; if (fact.facts_determined !== true) factBlockers.push('facts_not_determined'); if (!FACT_OUTCOMES.has(text(fact.outcome))) factBlockers.push('unknown_outcome'); if (!expectedDates.includes(date)) factBlockers.push('date_out_of_scope'); if (!groupNo.includes(TEST_MARKER) || (suffix && !groupNo.includes(suffix))) factBlockers.push('group_marker_or_suffix_mismatch'); if (!/^\d+$/.test(tid)) factBlockers.push('parent_tid_invalid'); if (fact.child_reference_scan_complete !== true) factBlockers.push('child_scan_incomplete'); if (ownership.group_suffix_matched !== true || ownership.marker_matched !== true || ownership.account_matched !== true) { factBlockers.push('ownership_evidence_mismatch'); } if (fact.customer_requested !== true || fact.passenger_counts_requested !== true) factBlockers.push('expected_probe_facts_not_requested'); if (!Number.isInteger(childCount) || childCount < 0 || childRefs.length !== childCount) factBlockers.push('child_count_mismatch'); for (const child of childRefs) { if (text(child.tid) !== tid || !/^\d+$/.test(text(child.ddid)) || text(child.ddid) === '0') { factBlockers.push('child_ref_invalid'); } } if (factBlockers.length) throw new Error(`split_probe_fact_blocked:${date}:${unique(factBlockers).join(',')}`); recordedRefs.push({ identifier: groupNo, kind: 'shared_plan', group_no: groupNo, plan_no: groupNo, tid, departure_date: date, owner_account: run.account, marker: TEST_MARKER, run_id: run.run_id, creation_slot: `shared_batch:${date}` }); for (const child of childRefs) { const ddid = text(child.ddid); const childOrderNo = /^D\d+$/i.test(text(child.child_order_no)) ? text(child.child_order_no).toUpperCase() : `D${ddid}`; if (childOrderNo !== `D${ddid}`) throw new Error(`split_probe_child_number_mismatch:${date}:${childOrderNo}:${ddid}`); recordedRefs.push({ identifier: childOrderNo, kind: 'shared_child_order', child_order_no: childOrderNo, ddid, tid, parent_group_no: groupNo, plan_no: groupNo, departure_date: date, owner_account: run.account, marker: TEST_MARKER, run_id: run.run_id, creation_slot: `shared_batch_child:${date}` }); } factSummary.push({ date, parent_group_no: groupNo, parent_tid: tid, child_count: childCount, child_order_numbers: childRefs.map((child) => `D${text(child.ddid)}`), outcome: text(fact.outcome), customer_persisted_on_native_list: fact.customer_persisted_on_native_list === true, passenger_counts_persisted_on_native_list: fact.passenger_counts_persisted_on_native_list === true }); } const existingStateRefs = Array.isArray(state.cleanup?.objects) ? state.cleanup.objects : []; const legacyAllowlistRefs = Array.isArray(allowlist.created_objects) ? allowlist.created_objects : []; const existingAllowlistRefs = Array.isArray(allowlist.created_refs) ? allowlist.created_refs : legacyAllowlistRefs; state.cleanup.objects = mergeRefs(existingStateRefs, recordedRefs); allowlist.created_refs = mergeRefs(existingAllowlistRefs, recordedRefs); delete allowlist.created_objects; allowlist.status = 'collecting_created_refs'; allowlist.order_suffix = suffix; allowlist.native_baseline_id = text(run.native_baseline_id); allowlist.target_dates = unique((run.target_dates || []).map(text).filter(Boolean)); allowlist.permitted_delete_kinds = ['independent_order', 'shared_plan', 'shared_child_order']; allowlist.post_delete_requery_required = true; allowlist.delete_enabled = false; allowlist.delete_authorized = false; state.creation_evidence = { ...(object(state.creation_evidence)), shared_batch_split_order_probe: { facts_status: probe.status, group_numbers: groupNumbers, fact_summary: factSummary, manual_review_required: report.manual_review_required === true, business_child_creation_passed: factSummary.every((fact) => ( fact.outcome === 'concrete_shared_children_with_requested_facts' && fact.child_count > 0 && fact.customer_persisted_on_native_list && fact.passenger_counts_persisted_on_native_list )), source_result_sha256: createHash('sha256').update(JSON.stringify(payloadValue)).digest('hex') } }; return { state, allowlist, recorded_refs: recordedRefs, fact_summary: factSummary, business_child_creation_passed: state.creation_evidence.shared_batch_split_order_probe.business_child_creation_passed }; } function parseArgs(argv) { const args = { state: '', allowlist: '', result: '', expectedRunId: '', apply: false }; for (let index = 0; index < argv.length; index += 1) { const value = argv[index]; if (value === '--state') args.state = argv[++index] || ''; else if (value === '--allowlist') args.allowlist = argv[++index] || ''; else if (value === '--result') args.result = argv[++index] || ''; else if (value === '--expected-run-id') args.expectedRunId = argv[++index] || ''; else if (value === '--apply') args.apply = true; else throw new Error(`unknown argument:${value}`); } for (const key of ['state', 'allowlist', 'result', 'expectedRunId']) { if (!text(args[key])) throw new Error(`--${key === 'expectedRunId' ? 'expected-run-id' : key} is required`); } return args; } async function writePreparedFiles(statePath, allowlistPath, result) { const stateTemp = `${statePath}.tmp-${process.pid}`; const allowlistTemp = `${allowlistPath}.tmp-${process.pid}`; await writeFile(stateTemp, `${JSON.stringify(result.state, null, 2)}\n`, { flag: 'wx' }); await writeFile(allowlistTemp, `${JSON.stringify(result.allowlist, null, 2)}\n`, { flag: 'wx' }); await rename(stateTemp, statePath); await rename(allowlistTemp, allowlistPath); } async function main() { const args = parseArgs(process.argv.slice(2)); const [stateText, allowlistText, resultText] = await Promise.all([ readFile(args.state, 'utf8'), readFile(args.allowlist, 'utf8'), readFile(args.result, 'utf8') ]); const result = recordSplitProbeResult( JSON.parse(stateText), JSON.parse(allowlistText), JSON.parse(resultText), args.expectedRunId ); if (args.apply) await writePreparedFiles(resolve(args.state), resolve(args.allowlist), result); console.log(JSON.stringify({ applied: args.apply, recorded_ref_count: result.recorded_refs.length, parent_count: result.recorded_refs.filter((ref) => ref.kind === 'shared_plan').length, child_count: result.recorded_refs.filter((ref) => ref.kind === 'shared_child_order').length, business_child_creation_passed: result.business_child_creation_passed, fact_summary: result.fact_summary }, null, 2)); } if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { await main(); }