Files
approved-plan-continuity-ha…/scripts/approved_plan_dispatch_binding.mjs

195 lines
4.3 KiB
JavaScript
Executable File

#!/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];
if (arg === '--input') {
inputPath = argv[i + 1] ?? null;
i += 1;
continue;
}
if (arg.startsWith('--input=')) {
inputPath = arg.slice('--input='.length);
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, receiptDir };
}
function readInput(inputPath) {
if (!inputPath) {
return {
ok: false,
error: 'missing_required_input',
};
}
try {
const raw = fs.readFileSync(inputPath, 'utf8');
const parsed = JSON.parse(raw);
return {
ok: true,
bytes: Buffer.byteLength(raw, 'utf8'),
parsed,
};
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
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);
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,
},
};
} 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`);