import { ensureExecApprovals, normalizeExecApprovals, readExecApprovalsSnapshot, resolveExecApprovalsSocketPath, saveExecApprovals, type ExecApprovalsFile, type ExecApprovalsSnapshot, } from "../../infra/exec-approvals.js"; import { ErrorCodes, errorShape, formatValidationErrors, validateExecApprovalsGetParams, validateExecApprovalsNodeGetParams, validateExecApprovalsNodeSetParams, validateExecApprovalsSetParams, } from "../protocol/index.js"; import { respondUnavailableOnThrow, safeParseJson } from "./nodes.helpers.js"; import type { GatewayRequestHandlers, RespondFn } from "./types.js"; function resolveBaseHash(params: unknown): string | null { const raw = (params as { baseHash?: unknown })?.baseHash; if (typeof raw !== "string") return null; const trimmed = raw.trim(); return trimmed ? trimmed : null; } function requireApprovalsBaseHash( params: unknown, snapshot: ExecApprovalsSnapshot, respond: RespondFn, ): boolean { if (!snapshot.exists) return true; if (!snapshot.hash) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, "exec approvals base hash unavailable; re-run exec.approvals.get and retry", ), ); return false; } const baseHash = resolveBaseHash(params); if (!baseHash) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, "exec approvals base hash required; re-run exec.approvals.get and retry", ), ); return false; } if (baseHash !== snapshot.hash) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, "exec approvals changed since last load; re-run exec.approvals.get and retry", ), ); return false; } return true; } function redactExecApprovals(file: ExecApprovalsFile): ExecApprovalsFile { const socketPath = file.socket?.path?.trim(); return { ...file, socket: socketPath ? { path: socketPath } : undefined, }; } export const execApprovalsHandlers: GatewayRequestHandlers = { "exec.approvals.get": ({ params, respond }) => { if (!validateExecApprovalsGetParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid exec.approvals.get params: ${formatValidationErrors(validateExecApprovalsGetParams.errors)}`, ), ); return; } ensureExecApprovals(); const snapshot = readExecApprovalsSnapshot(); respond( true, { path: snapshot.path, exists: snapshot.exists, hash: snapshot.hash, file: redactExecApprovals(snapshot.file), }, undefined, ); }, "exec.approvals.set": ({ params, respond }) => { if (!validateExecApprovalsSetParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid exec.approvals.set params: ${formatValidationErrors(validateExecApprovalsSetParams.errors)}`, ), ); return; } ensureExecApprovals(); const snapshot = readExecApprovalsSnapshot(); if (!requireApprovalsBaseHash(params, snapshot, respond)) { return; } const incoming = (params as { file?: unknown }).file; if (!incoming || typeof incoming !== "object") { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "exec approvals file is required"), ); return; } const normalized = normalizeExecApprovals(incoming as ExecApprovalsFile); const currentSocketPath = snapshot.file.socket?.path?.trim(); const currentToken = snapshot.file.socket?.token?.trim(); const socketPath = normalized.socket?.path?.trim() ?? currentSocketPath ?? resolveExecApprovalsSocketPath(); const token = normalized.socket?.token?.trim() ?? currentToken ?? ""; const next: ExecApprovalsFile = { ...normalized, socket: { path: socketPath, token, }, }; saveExecApprovals(next); const nextSnapshot = readExecApprovalsSnapshot(); respond( true, { path: nextSnapshot.path, exists: nextSnapshot.exists, hash: nextSnapshot.hash, file: redactExecApprovals(nextSnapshot.file), }, undefined, ); }, "exec.approvals.node.get": async ({ params, respond, context }) => { if (!validateExecApprovalsNodeGetParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid exec.approvals.node.get params: ${formatValidationErrors(validateExecApprovalsNodeGetParams.errors)}`, ), ); return; } const { nodeId } = params as { nodeId: string }; const id = nodeId.trim(); if (!id) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required")); return; } await respondUnavailableOnThrow(respond, async () => { const res = await context.nodeRegistry.invoke({ nodeId: id, command: "system.execApprovals.get", params: {}, }); if (!res.ok) { respond( false, undefined, errorShape(ErrorCodes.UNAVAILABLE, res.error?.message ?? "node invoke failed", { details: { nodeError: res.error ?? null }, }), ); return; } const payload = res.payloadJSON ? safeParseJson(res.payloadJSON) : res.payload; respond(true, payload, undefined); }); }, "exec.approvals.node.set": async ({ params, respond, context }) => { if (!validateExecApprovalsNodeSetParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid exec.approvals.node.set params: ${formatValidationErrors(validateExecApprovalsNodeSetParams.errors)}`, ), ); return; } const { nodeId, file, baseHash } = params as { nodeId: string; file: ExecApprovalsFile; baseHash?: string; }; const id = nodeId.trim(); if (!id) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required")); return; } await respondUnavailableOnThrow(respond, async () => { const res = await context.nodeRegistry.invoke({ nodeId: id, command: "system.execApprovals.set", params: { file, baseHash }, }); if (!res.ok) { respond( false, undefined, errorShape(ErrorCodes.UNAVAILABLE, res.error?.message ?? "node invoke failed", { details: { nodeError: res.error ?? null }, }), ); return; } const payload = safeParseJson(res.payloadJSON ?? null); respond(true, payload, undefined); }); }, };