From b4331bf9d25d78574b23136a9e4dc29bd79be1c6 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 24 Apr 2026 12:22:53 +0800 Subject: [PATCH] feat: write approved-plan continuity dispatch receipts --- scripts/approved_plan_dispatch_binding.mjs | 160 +++++++++++++++++---- 1 file changed, 136 insertions(+), 24 deletions(-) diff --git a/scripts/approved_plan_dispatch_binding.mjs b/scripts/approved_plan_dispatch_binding.mjs index bacc97f..b1ce01e 100755 --- a/scripts/approved_plan_dispatch_binding.mjs +++ b/scripts/approved_plan_dispatch_binding.mjs @@ -1,9 +1,13 @@ #!/usr/bin/env node import fs from 'node:fs'; +import path from 'node:path'; + +const DEFAULT_RECEIPT_DIR = path.resolve(process.cwd(), 'state/approved-plan-continuity'); function parseArgs(argv) { let inputPath = null; let compact = false; + let receiptDir = DEFAULT_RECEIPT_DIR; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; @@ -19,13 +23,24 @@ function parseArgs(argv) { continue; } + if (arg === '--receipt-dir') { + receiptDir = argv[i + 1] ? path.resolve(argv[i + 1]) : receiptDir; + i += 1; + continue; + } + + if (arg.startsWith('--receipt-dir=')) { + receiptDir = path.resolve(arg.slice('--receipt-dir='.length)); + continue; + } + if (arg === '--compact') { compact = true; continue; } } - return { inputPath, compact }; + return { inputPath, compact, receiptDir }; } function readInput(inputPath) { @@ -38,9 +53,11 @@ function readInput(inputPath) { try { const raw = fs.readFileSync(inputPath, 'utf8'); + const parsed = JSON.parse(raw); return { ok: true, bytes: Buffer.byteLength(raw, 'utf8'), + parsed, }; } catch (error) { return { @@ -50,33 +67,128 @@ function readInput(inputPath) { } } -const { inputPath, compact } = parseArgs(process.argv.slice(2)); +function slugifySegment(value) { + return String(value) + .trim() + .toLowerCase() + .replace(/[^a-z0-9._-]+/g, '-') + .replace(/^-+|-+$/g, '') + .replace(/-{2,}/g, '-'); +} + +function buildReceipt(payload) { + const nextAction = payload?.nextDerivedAction ?? payload?.derivedAction ?? null; + const receipt = { + planId: payload?.planId ?? null, + currentTask: payload?.currentTask ?? null, + nextDerivedAction: nextAction, + dispatchedAt: payload?.dispatchedAt ?? null, + dispatchRunId: payload?.dispatchRunId ?? null, + childSessionKey: payload?.childSessionKey ?? null, + replyClosureState: payload?.replyClosureState ?? null, + }; + + return receipt; +} + +function validateReceipt(receipt) { + const missing = []; + + for (const field of [ + 'planId', + 'currentTask', + 'nextDerivedAction', + 'dispatchedAt', + 'dispatchRunId', + 'childSessionKey', + 'replyClosureState', + ]) { + if (receipt[field] == null) { + missing.push(field); + } + } + + const planIdSafe = slugifySegment(receipt.planId ?? ''); + const dispatchRunIdSafe = slugifySegment(receipt.dispatchRunId ?? ''); + + if (!planIdSafe) missing.push('planId_filesystem_safe'); + if (!dispatchRunIdSafe) missing.push('dispatchRunId_filesystem_safe'); + + return { + ok: missing.length === 0, + missing, + planIdSafe, + dispatchRunIdSafe, + }; +} + +function writeReceipt({ receipt, receiptDir, planIdSafe, dispatchRunIdSafe }) { + fs.mkdirSync(receiptDir, { recursive: true }); + const receiptPath = path.join(receiptDir, `receipt-${planIdSafe}-${dispatchRunIdSafe}.json`); + fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, 'utf8'); + return receiptPath; +} + +const { inputPath, compact, receiptDir } = parseArgs(process.argv.slice(2)); const input = readInput(inputPath); -const response = { - ok: input.ok, - status: input.ok ? 'placeholder_receipt' : 'input_error', - binding: 'approved_plan_dispatch', - compact, - inputPath, - receipt: input.ok - ? { - placeholder: true, - planId: null, - currentTask: null, - nextDerivedAction: null, - dispatchedAt: null, - } - : null, - input: input.ok - ? { +let response; + +if (!input.ok) { + response = { + ok: false, + status: 'input_error', + binding: 'approved_plan_dispatch', + compact, + inputPath, + receipt: null, + receiptPath: null, + input: { + ok: false, + error: input.error, + }, + }; +} else { + const receipt = buildReceipt(input.parsed); + const validation = validateReceipt(receipt); + + if (!validation.ok) { + response = { + ok: false, + status: 'missing_required_receipt_fields', + binding: 'approved_plan_dispatch', + compact, + inputPath, + receipt, + receiptPath: null, + missing: validation.missing, + input: { ok: true, bytes: input.bytes, - } - : { - ok: false, - error: input.error, }, -}; + }; + } else { + const receiptPath = writeReceipt({ + receipt, + receiptDir, + planIdSafe: validation.planIdSafe, + dispatchRunIdSafe: validation.dispatchRunIdSafe, + }); + + response = { + ok: true, + status: 'receipt_written', + binding: 'approved_plan_dispatch', + compact, + inputPath, + receipt, + receiptPath, + input: { + ok: true, + bytes: input.bytes, + }, + }; + } +} process.stdout.write(`${JSON.stringify(response)}\n`);