import { parseArgs } from 'node:util' import { pathToFileURL } from 'node:url' export function buildRequest(argv, env) { const { values: args } = parseArgs({ args: argv, options: { help: { type: 'boolean' }, service: { type: 'string' }, base: { type: 'string' }, entry: { type: 'string' }, retry: { type: 'boolean' }, 'request-id': { type: 'string' }, operator: { type: 'string' }, reason: { type: 'string' }, hash: { type: 'string' }, } }) if (args.help) return null if (env.CHONGCHONG_ENV !== 'test') throw new Error('CHONGCHONG_ENV must be test') if (!['firstleg', 'lastleg', 'finance'].includes(args.service)) throw new Error('Unknown producer service') if (!/^[A-Za-z0-9-]{1,64}$/.test(args.entry ?? '')) throw new Error('Invalid entry ID') const base = new URL(args.base) if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash || base.pathname !== '/') { throw new Error('Base must be an explicit service origin without credentials or path') } const token = env.EVENT_OPERATIONS_TOKEN if (!token || Buffer.byteLength(token) < 32) throw new Error('EVENT_OPERATIONS_TOKEN must contain at least 32 bytes') const url = new URL(`/internal/${args.service}/event-operations/${args.entry}${args.retry ? '/retry' : ''}`, base) const options = { method: args.retry ? 'POST' : 'GET', redirect: 'error', headers: { 'X-Event-Operations-Token': token, 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(15000) } if (args.retry) { if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args['request-id'] ?? '')) throw new Error('A stable request UUID is required') for (const [name, limit] of [['operator', 128], ['reason', 500]]) { if (!args[name]?.trim() || args[name].length > limit || /[\x00-\x1f\x7f-\x9f]/.test(args[name])) throw new Error(`Invalid ${name}`) } if (!/^sha256:[0-9a-f]{64}$/i.test(args.hash ?? '')) throw new Error('Expected payload hash is required') options.body = JSON.stringify({ requestId: args['request-id'], operator: args.operator, reason: args.reason, expectedPayloadHash: args.hash }) } return { url, options } } async function main() { const request = buildRequest(process.argv.slice(2), process.env) if (!request) { console.log('Inspect: --service firstleg|lastleg|finance --base --entry \nRetry: add --retry --request-id --operator --reason --hash \nEnvironment: CHONGCHONG_ENV=test, EVENT_OPERATIONS_TOKEN. No automatic retry or redirects. Retry writes Outbox state and audit atomically.') return } const response = await fetch(request.url, request.options) if (!response.ok) throw new Error(`Operations endpoint returned HTTP ${response.status}`) const result = await response.json() if (result?.code !== '200') throw new Error(`Operations rejected: code=${String(result?.code ?? 'missing')}`) console.log(JSON.stringify(result.data, null, 2)) } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { main().catch(error => { console.error(error.message); process.exitCode = 1 }) }