refactor(gateway): centralize system.run approval context and errors

This commit is contained in:
Peter Steinberger
2026-02-26 22:01:16 +01:00
parent d06632ba45
commit 4e690e09c7
4 changed files with 239 additions and 107 deletions

View File

@@ -0,0 +1,29 @@
export type SystemRunApprovalGuardError = {
ok: false;
message: string;
details: Record<string, unknown>;
};
export function systemRunApprovalGuardError(params: {
code: string;
message: string;
details?: Record<string, unknown>;
}): SystemRunApprovalGuardError {
const details = params.details ? { ...params.details } : {};
return {
ok: false,
message: params.message,
details: {
code: params.code,
...details,
},
};
}
export function systemRunApprovalRequired(runId: string): SystemRunApprovalGuardError {
return systemRunApprovalGuardError({
code: "APPROVAL_REQUIRED",
message: "approval required",
details: { runId },
});
}

View File

@@ -1,6 +1,10 @@
import { normalizeSystemRunApprovalPlanV2 } from "../infra/system-run-approval-binding.js"; import { resolveSystemRunApprovalRuntimeContext } from "../infra/system-run-approval-context.js";
import { resolveSystemRunCommand } from "../infra/system-run-command.js"; import { resolveSystemRunCommand } from "../infra/system-run-command.js";
import type { ExecApprovalRecord } from "./exec-approval-manager.js"; import type { ExecApprovalRecord } from "./exec-approval-manager.js";
import {
systemRunApprovalGuardError,
systemRunApprovalRequired,
} from "./node-invoke-system-run-approval-errors.js";
import { import {
evaluateSystemRunApprovalMatch, evaluateSystemRunApprovalMatch,
toSystemRunApprovalMismatchError, toSystemRunApprovalMismatchError,
@@ -125,62 +129,60 @@ export function sanitizeSystemRunParamsForForwarding(opts: {
const runId = normalizeString(p.runId); const runId = normalizeString(p.runId);
if (!runId) { if (!runId) {
return { return systemRunApprovalGuardError({
ok: false, code: "MISSING_RUN_ID",
message: "approval override requires params.runId", message: "approval override requires params.runId",
details: { code: "MISSING_RUN_ID" }, });
};
} }
const manager = opts.execApprovalManager; const manager = opts.execApprovalManager;
if (!manager) { if (!manager) {
return { return systemRunApprovalGuardError({
ok: false, code: "APPROVALS_UNAVAILABLE",
message: "exec approvals unavailable", message: "exec approvals unavailable",
details: { code: "APPROVALS_UNAVAILABLE" }, });
};
} }
const snapshot = manager.getSnapshot(runId); const snapshot = manager.getSnapshot(runId);
if (!snapshot) { if (!snapshot) {
return { return systemRunApprovalGuardError({
ok: false, code: "UNKNOWN_APPROVAL_ID",
message: "unknown or expired approval id", message: "unknown or expired approval id",
details: { code: "UNKNOWN_APPROVAL_ID", runId }, details: { runId },
}; });
} }
const nowMs = typeof opts.nowMs === "number" ? opts.nowMs : Date.now(); const nowMs = typeof opts.nowMs === "number" ? opts.nowMs : Date.now();
if (nowMs > snapshot.expiresAtMs) { if (nowMs > snapshot.expiresAtMs) {
return { return systemRunApprovalGuardError({
ok: false, code: "APPROVAL_EXPIRED",
message: "approval expired", message: "approval expired",
details: { code: "APPROVAL_EXPIRED", runId }, details: { runId },
}; });
} }
const targetNodeId = normalizeString(opts.nodeId); const targetNodeId = normalizeString(opts.nodeId);
if (!targetNodeId) { if (!targetNodeId) {
return { return systemRunApprovalGuardError({
ok: false, code: "MISSING_NODE_ID",
message: "node.invoke requires nodeId", message: "node.invoke requires nodeId",
details: { code: "MISSING_NODE_ID", runId }, details: { runId },
}; });
} }
const approvalNodeId = normalizeString(snapshot.request.nodeId); const approvalNodeId = normalizeString(snapshot.request.nodeId);
if (!approvalNodeId) { if (!approvalNodeId) {
return { return systemRunApprovalGuardError({
ok: false, code: "APPROVAL_NODE_BINDING_MISSING",
message: "approval id missing node binding", message: "approval id missing node binding",
details: { code: "APPROVAL_NODE_BINDING_MISSING", runId }, details: { runId },
}; });
} }
if (approvalNodeId !== targetNodeId) { if (approvalNodeId !== targetNodeId) {
return { return systemRunApprovalGuardError({
ok: false, code: "APPROVAL_NODE_MISMATCH",
message: "approval id not valid for this node", message: "approval id not valid for this node",
details: { code: "APPROVAL_NODE_MISMATCH", runId }, details: { runId },
}; });
} }
// Prefer binding by device identity (stable across reconnects / per-call clients like callGateway()). // Prefer binding by device identity (stable across reconnects / per-call clients like callGateway()).
@@ -189,79 +191,69 @@ export function sanitizeSystemRunParamsForForwarding(opts: {
const clientDeviceId = opts.client?.connect?.device?.id ?? null; const clientDeviceId = opts.client?.connect?.device?.id ?? null;
if (snapshotDeviceId) { if (snapshotDeviceId) {
if (snapshotDeviceId !== clientDeviceId) { if (snapshotDeviceId !== clientDeviceId) {
return { return systemRunApprovalGuardError({
ok: false, code: "APPROVAL_DEVICE_MISMATCH",
message: "approval id not valid for this device", message: "approval id not valid for this device",
details: { code: "APPROVAL_DEVICE_MISMATCH", runId }, details: { runId },
}; });
} }
} else if ( } else if (
snapshot.requestedByConnId && snapshot.requestedByConnId &&
snapshot.requestedByConnId !== (opts.client?.connId ?? null) snapshot.requestedByConnId !== (opts.client?.connId ?? null)
) { ) {
return { return systemRunApprovalGuardError({
ok: false, code: "APPROVAL_CLIENT_MISMATCH",
message: "approval id not valid for this client", message: "approval id not valid for this client",
details: { code: "APPROVAL_CLIENT_MISMATCH", runId }, details: { runId },
}; });
} }
const planV2 = normalizeSystemRunApprovalPlanV2(snapshot.request.systemRunPlanV2 ?? null); const runtimeContext = resolveSystemRunApprovalRuntimeContext({
let approvalArgv: string[]; planV2: snapshot.request.systemRunPlanV2 ?? null,
let approvalCwd: string | null; command: p.command,
let approvalAgentId: string | null; rawCommand: p.rawCommand,
let approvalSessionKey: string | null; cwd: p.cwd,
if (planV2) { agentId: p.agentId,
approvalArgv = [...planV2.argv]; sessionKey: p.sessionKey,
approvalCwd = planV2.cwd; });
approvalAgentId = planV2.agentId; if (!runtimeContext.ok) {
approvalSessionKey = planV2.sessionKey; return {
next.command = [...planV2.argv]; ok: false,
if (planV2.rawCommand) { message: runtimeContext.message,
next.rawCommand = planV2.rawCommand; details: runtimeContext.details,
};
}
if (runtimeContext.planV2) {
next.command = [...runtimeContext.planV2.argv];
if (runtimeContext.rawCommand) {
next.rawCommand = runtimeContext.rawCommand;
} else { } else {
delete next.rawCommand; delete next.rawCommand;
} }
if (planV2.cwd) { if (runtimeContext.cwd) {
next.cwd = planV2.cwd; next.cwd = runtimeContext.cwd;
} else { } else {
delete next.cwd; delete next.cwd;
} }
if (planV2.agentId) { if (runtimeContext.agentId) {
next.agentId = planV2.agentId; next.agentId = runtimeContext.agentId;
} else { } else {
delete next.agentId; delete next.agentId;
} }
if (planV2.sessionKey) { if (runtimeContext.sessionKey) {
next.sessionKey = planV2.sessionKey; next.sessionKey = runtimeContext.sessionKey;
} else { } else {
delete next.sessionKey; delete next.sessionKey;
} }
} else {
const cmdTextResolution = resolveSystemRunCommand({
command: p.command,
rawCommand: p.rawCommand,
});
if (!cmdTextResolution.ok) {
return {
ok: false,
message: cmdTextResolution.message,
details: cmdTextResolution.details,
};
}
approvalArgv = cmdTextResolution.argv;
approvalCwd = normalizeString(p.cwd) ?? null;
approvalAgentId = normalizeString(p.agentId) ?? null;
approvalSessionKey = normalizeString(p.sessionKey) ?? null;
} }
const approvalMatch = evaluateSystemRunApprovalMatch({ const approvalMatch = evaluateSystemRunApprovalMatch({
argv: approvalArgv, argv: runtimeContext.argv,
request: snapshot.request, request: snapshot.request,
binding: { binding: {
cwd: approvalCwd, cwd: runtimeContext.cwd,
agentId: approvalAgentId, agentId: runtimeContext.agentId,
sessionKey: approvalSessionKey, sessionKey: runtimeContext.sessionKey,
env: p.env, env: p.env,
}, },
}); });
@@ -272,11 +264,7 @@ export function sanitizeSystemRunParamsForForwarding(opts: {
// Normal path: enforce the decision recorded by the gateway. // Normal path: enforce the decision recorded by the gateway.
if (snapshot.decision === "allow-once") { if (snapshot.decision === "allow-once") {
if (typeof manager.consumeAllowOnce !== "function" || !manager.consumeAllowOnce(runId)) { if (typeof manager.consumeAllowOnce !== "function" || !manager.consumeAllowOnce(runId)) {
return { return systemRunApprovalRequired(runId);
ok: false,
message: "approval required",
details: { code: "APPROVAL_REQUIRED", runId },
};
} }
next.approved = true; next.approved = true;
next.approvalDecision = "allow-once"; next.approvalDecision = "allow-once";
@@ -306,9 +294,5 @@ export function sanitizeSystemRunParamsForForwarding(opts: {
return { ok: true, params: next }; return { ok: true, params: next };
} }
return { return systemRunApprovalRequired(runId);
ok: false,
message: "approval required",
details: { code: "APPROVAL_REQUIRED", runId },
};
} }

View File

@@ -3,11 +3,8 @@ import {
DEFAULT_EXEC_APPROVAL_TIMEOUT_MS, DEFAULT_EXEC_APPROVAL_TIMEOUT_MS,
type ExecApprovalDecision, type ExecApprovalDecision,
} from "../../infra/exec-approvals.js"; } from "../../infra/exec-approvals.js";
import { import { buildSystemRunApprovalBindingV1 } from "../../infra/system-run-approval-binding.js";
buildSystemRunApprovalBindingV1, import { resolveSystemRunApprovalRequestContext } from "../../infra/system-run-approval-context.js";
normalizeSystemRunApprovalPlanV2,
} from "../../infra/system-run-approval-binding.js";
import { formatExecCommand } from "../../infra/system-run-command.js";
import type { ExecApprovalManager } from "../exec-approval-manager.js"; import type { ExecApprovalManager } from "../exec-approval-manager.js";
import { import {
ErrorCodes, ErrorCodes,
@@ -72,21 +69,20 @@ export function createExecApprovalHandlers(
const explicitId = typeof p.id === "string" && p.id.trim().length > 0 ? p.id.trim() : null; const explicitId = typeof p.id === "string" && p.id.trim().length > 0 ? p.id.trim() : null;
const host = typeof p.host === "string" ? p.host.trim() : ""; const host = typeof p.host === "string" ? p.host.trim() : "";
const nodeId = typeof p.nodeId === "string" ? p.nodeId.trim() : ""; const nodeId = typeof p.nodeId === "string" ? p.nodeId.trim() : "";
const commandArgv = Array.isArray(p.commandArgv) const approvalContext = resolveSystemRunApprovalRequestContext({
? p.commandArgv.map((entry) => String(entry)) host,
: undefined; command: p.command,
const systemRunPlanV2 = commandArgv: p.commandArgv,
host === "node" ? normalizeSystemRunApprovalPlanV2(p.systemRunPlanV2) : null; systemRunPlanV2: p.systemRunPlanV2,
const effectiveCommandArgv = systemRunPlanV2?.argv ?? commandArgv; cwd: p.cwd,
const effectiveCwd = systemRunPlanV2?.cwd ?? p.cwd; agentId: p.agentId,
const effectiveAgentId = systemRunPlanV2?.agentId ?? p.agentId; sessionKey: p.sessionKey,
const effectiveSessionKey = systemRunPlanV2?.sessionKey ?? p.sessionKey; });
const effectiveCommandText = (() => { const effectiveCommandArgv = approvalContext.commandArgv;
if (!systemRunPlanV2) { const effectiveCwd = approvalContext.cwd;
return p.command; const effectiveAgentId = approvalContext.agentId;
} const effectiveSessionKey = approvalContext.sessionKey;
return systemRunPlanV2.rawCommand ?? formatExecCommand(systemRunPlanV2.argv); const effectiveCommandText = approvalContext.commandText;
})();
if (host === "node" && !nodeId) { if (host === "node" && !nodeId) {
respond( respond(
false, false,
@@ -129,7 +125,7 @@ export function createExecApprovalHandlers(
commandArgv: effectiveCommandArgv, commandArgv: effectiveCommandArgv,
envKeys: systemRunBindingV1?.envKeys?.length ? systemRunBindingV1.envKeys : undefined, envKeys: systemRunBindingV1?.envKeys?.length ? systemRunBindingV1.envKeys : undefined,
systemRunBindingV1: systemRunBindingV1?.binding ?? null, systemRunBindingV1: systemRunBindingV1?.binding ?? null,
systemRunPlanV2: systemRunPlanV2, systemRunPlanV2: approvalContext.planV2,
cwd: effectiveCwd ?? null, cwd: effectiveCwd ?? null,
nodeId: host === "node" ? nodeId : null, nodeId: host === "node" ? nodeId : null,
host: host || null, host: host || null,

View File

@@ -0,0 +1,123 @@
import type { SystemRunApprovalPlanV2 } from "./exec-approvals.js";
import { normalizeSystemRunApprovalPlanV2 } from "./system-run-approval-binding.js";
import { formatExecCommand, resolveSystemRunCommand } from "./system-run-command.js";
type PreparedRunPayload = {
cmdText: string;
plan: SystemRunApprovalPlanV2;
};
type SystemRunApprovalRequestContext = {
planV2: SystemRunApprovalPlanV2 | null;
commandArgv: string[] | undefined;
commandText: string;
cwd: string | null;
agentId: string | null;
sessionKey: string | null;
};
type SystemRunApprovalRuntimeContext =
| {
ok: true;
planV2: SystemRunApprovalPlanV2 | null;
argv: string[];
cwd: string | null;
agentId: string | null;
sessionKey: string | null;
rawCommand: string | null;
}
| {
ok: false;
message: string;
details?: Record<string, unknown>;
};
function normalizeString(value: unknown): string | null {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed ? trimmed : null;
}
function normalizeStringArray(value: unknown): string[] {
return Array.isArray(value) ? value.map((entry) => String(entry)) : [];
}
function normalizeCommandText(value: unknown): string {
return typeof value === "string" ? value : "";
}
export function parsePreparedSystemRunPayload(payload: unknown): PreparedRunPayload | null {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
return null;
}
const raw = payload as { cmdText?: unknown; plan?: unknown };
const cmdText = normalizeString(raw.cmdText);
const plan = normalizeSystemRunApprovalPlanV2(raw.plan);
if (!cmdText || !plan) {
return null;
}
return { cmdText, plan };
}
export function resolveSystemRunApprovalRequestContext(params: {
host?: unknown;
command?: unknown;
commandArgv?: unknown;
systemRunPlanV2?: unknown;
cwd?: unknown;
agentId?: unknown;
sessionKey?: unknown;
}): SystemRunApprovalRequestContext {
const host = normalizeString(params.host) ?? "";
const planV2 = host === "node" ? normalizeSystemRunApprovalPlanV2(params.systemRunPlanV2) : null;
const fallbackArgv = normalizeStringArray(params.commandArgv);
const fallbackCommand = normalizeCommandText(params.command);
return {
planV2,
commandArgv: planV2?.argv ?? (fallbackArgv.length > 0 ? fallbackArgv : undefined),
commandText: planV2 ? (planV2.rawCommand ?? formatExecCommand(planV2.argv)) : fallbackCommand,
cwd: planV2?.cwd ?? normalizeString(params.cwd),
agentId: planV2?.agentId ?? normalizeString(params.agentId),
sessionKey: planV2?.sessionKey ?? normalizeString(params.sessionKey),
};
}
export function resolveSystemRunApprovalRuntimeContext(params: {
planV2?: unknown;
command?: unknown;
rawCommand?: unknown;
cwd?: unknown;
agentId?: unknown;
sessionKey?: unknown;
}): SystemRunApprovalRuntimeContext {
const normalizedPlan = normalizeSystemRunApprovalPlanV2(params.planV2 ?? null);
if (normalizedPlan) {
return {
ok: true,
planV2: normalizedPlan,
argv: [...normalizedPlan.argv],
cwd: normalizedPlan.cwd,
agentId: normalizedPlan.agentId,
sessionKey: normalizedPlan.sessionKey,
rawCommand: normalizedPlan.rawCommand,
};
}
const command = resolveSystemRunCommand({
command: params.command,
rawCommand: params.rawCommand,
});
if (!command.ok) {
return { ok: false, message: command.message, details: command.details };
}
return {
ok: true,
planV2: null,
argv: command.argv,
cwd: normalizeString(params.cwd),
agentId: normalizeString(params.agentId),
sessionKey: normalizeString(params.sessionKey),
rawCommand: normalizeString(params.rawCommand),
};
}