openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
553 lines (552 loc) • 27 kB
JavaScript
import { s as normalizeNullableString } from "./string-coerce-6kR-86XC.mjs";
import { t as safeParseJson } from "./json-coercion-pfKbZnCJ.mjs";
import { i as getNodeSqliteKysely, n as executeSqliteQuerySync, r as executeSqliteQueryTakeFirstSync } from "./kysely-sync-BMSZn9yz.mjs";
import { n as isApprovalResolutionRef, t as buildApprovalResolutionRef } from "./approval-resolution-ref-BMBlVd2b.mjs";
import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-VZFEEleX.mjs";
import { n as isWellFormedApprovalId } from "./approval-id-BTRnO3t1.mjs";
import { r as validateApprovalPresentation } from "./approval-result-validators-BY5WgUqT.mjs";
import { a as mintMcpToolGrantLocked } from "./exec-approvals-sqlite-mBooZADp.mjs";
import { i as mintCronStandingGrantLocked } from "./operator-approval-standing-grants-JZrXGNox.mjs";
//#region src/gateway/operator-approval-store.rows.ts
const OPERATOR_APPROVAL_TERMINAL_RETENTION_MS = 2592e6;
const OPERATOR_APPROVAL_MAX_LIST_LIMIT = 1001;
var OperatorApprovalHistoryCursorError = class extends Error {
constructor() {
super("invalid operator approval history cursor");
this.name = "OperatorApprovalHistoryCursorError";
}
};
/** Match an observed terminal row to this operation's separately retained commit receipt. */
function getOperatorApprovalResolutionKey(record) {
return JSON.stringify([
record.id,
record.kind,
record.runtimeEpoch,
record.createdAtMs,
record.resolvedAtMs,
record.status,
record.decision,
record.terminalReason,
record.resolver?.kind ?? null,
record.resolver?.id ?? null
]);
}
function parseOperatorApprovalKind(value) {
return value === "exec" || value === "plugin" || value === "system-agent" ? value : null;
}
const OPERATOR_APPROVAL_EXECUTION_IDENTITY_SCHEMA_SQL = `
CREATE TABLE IF NOT EXISTS operator_approval_execution_identities (
approval_id TEXT NOT NULL PRIMARY KEY
REFERENCES operator_approvals(approval_id) ON DELETE CASCADE,
source_context_id TEXT NOT NULL CHECK (
length(source_context_id) BETWEEN 1 AND 256 AND source_context_id = trim(source_context_id)
),
source_execution_id TEXT NOT NULL CHECK (
length(source_execution_id) BETWEEN 1 AND 256 AND source_execution_id = trim(source_execution_id)
)
) STRICT;
`;
function normalizeExecutionIdentityBinding(input) {
const binding = input.executionIdentityToken;
const sourceRunId = normalizeNullableString(input.source?.runId);
if (!binding || normalizeNullableString(binding.runId) !== sourceRunId) return;
const sourceContextId = normalizeNullableString(binding.contextId);
const sourceExecutionId = normalizeNullableString(binding.executionId);
if (!sourceContextId || !sourceExecutionId || sourceContextId.length > 256 || sourceExecutionId.length > 256) return;
return {
sourceContextId,
sourceExecutionId
};
}
function parseApprovalPresentation(raw) {
const value = safeParseJson(raw);
return validateApprovalPresentation(value) ? value : null;
}
function parseStringArray(raw) {
const value = safeParseJson(raw);
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string" && Boolean(entry.trim()))) return null;
return value;
}
function requireString(value, label) {
const normalized = normalizeNullableString(value);
if (!normalized) throw new Error(`${label} must not be empty`);
return normalized;
}
function requireApprovalId(value) {
if (!isWellFormedApprovalId(value)) throw new Error("operator approval id must be non-empty, well-formed Unicode, and not . or ..");
return value;
}
function encodeOperatorApprovalHistoryCursor(cursor) {
return Buffer.from(JSON.stringify({
v: 1,
...cursor
}), "utf8").toString("base64url");
}
function decodeOperatorApprovalHistoryCursor(raw) {
try {
const bytes = Buffer.from(raw, "base64url");
if (bytes.toString("base64url") !== raw) throw new OperatorApprovalHistoryCursorError();
const parsed = JSON.parse(bytes.toString("utf8"));
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || !("v" in parsed) || parsed.v !== 1 || !("resolvedAtMs" in parsed) || typeof parsed.resolvedAtMs !== "number" || !Number.isSafeInteger(parsed.resolvedAtMs) || parsed.resolvedAtMs < 0 || !("id" in parsed) || typeof parsed.id !== "string" || !isWellFormedApprovalId(parsed.id)) throw new OperatorApprovalHistoryCursorError();
const cursor = {
resolvedAtMs: parsed.resolvedAtMs,
id: parsed.id
};
if (encodeOperatorApprovalHistoryCursor(cursor) !== raw) throw new OperatorApprovalHistoryCursorError();
return cursor;
} catch (error) {
if (error instanceof OperatorApprovalHistoryCursorError) throw error;
throw new OperatorApprovalHistoryCursorError();
}
}
function stringifyPresentation(presentation) {
if (!validateApprovalPresentation(presentation)) throw new Error("operator approval presentation must match the safe protocol schema");
let raw;
try {
raw = JSON.stringify(presentation);
} catch (error) {
throw new Error(`operator approval presentation is not JSON serializable: ${String(error)}`, { cause: error });
}
if (!parseApprovalPresentation(raw)) throw new Error("operator approval presentation must serialize to the safe protocol schema");
return raw;
}
function isValidTimestamp(value) {
return Number.isSafeInteger(value) && value >= 0;
}
function clampAuditTimestamp(nowMs, ...minimums) {
return Math.max(nowMs, ...minimums.filter((value) => value !== null));
}
function hasValidLifecycleTuple(params) {
const { row, status, decision, terminalReason, resolverKind } = params;
const noConsumption = row.consumed_at_ms === null && row.consumed_by === null;
if (status === "pending") return decision === null && terminalReason === null && row.resolved_at_ms === null && resolverKind === null && row.resolver_id === null && noConsumption;
if (row.resolved_at_ms === null || resolverKind === null) return false;
if (status === "allowed") {
const validConsumption = decision === "allow-once" ? noConsumption || row.consumed_at_ms !== null && Boolean(row.consumed_by?.trim()) : noConsumption;
return (decision === "allow-once" || decision === "allow-always") && terminalReason === "user" && validConsumption;
}
if (decision !== "deny" || !noConsumption) return false;
if (status === "denied") return terminalReason === "user" || terminalReason === "malformed-verdict" || terminalReason === "no-route" || terminalReason === "storage-corrupt";
if (status === "expired") return terminalReason === "timeout";
return status === "cancelled" && (terminalReason === "run-aborted" || terminalReason === "gateway-restart");
}
function decodeOperatorApprovalRow(row) {
const presentation = parseApprovalPresentation(row.presentation_json);
const reviewerDeviceIds = parseStringArray(row.reviewer_device_ids_json);
const audienceSessionKeys = parseStringArray(row.audience_session_keys_json);
const kind = parseOperatorApprovalKind(row.kind);
const status = row.status;
const decision = row.decision;
const terminalReason = row.terminal_reason;
const resolverKind = row.resolver_kind;
if (!presentation || !isWellFormedApprovalId(row.approval_id) || !isApprovalResolutionRef(row.resolution_ref) || !reviewerDeviceIds || !audienceSessionKeys || audienceSessionKeys.length > 64 || !kind || status !== "pending" && status !== "allowed" && status !== "denied" && status !== "expired" && status !== "cancelled" || !isValidTimestamp(row.created_at_ms) || !isValidTimestamp(row.expires_at_ms) || !isValidTimestamp(row.updated_at_ms) || row.expires_at_ms < row.created_at_ms || row.updated_at_ms < row.created_at_ms || row.resolved_at_ms !== null && (!isValidTimestamp(row.resolved_at_ms) || row.resolved_at_ms < row.created_at_ms || row.resolved_at_ms > row.updated_at_ms) || row.consumed_at_ms !== null && (!isValidTimestamp(row.consumed_at_ms) || row.resolved_at_ms === null || row.consumed_at_ms < row.resolved_at_ms || row.consumed_at_ms > row.updated_at_ms) || row.requested_by_device_token_auth !== 0 && row.requested_by_device_token_auth !== 1 || decision !== null && decision !== "allow-once" && decision !== "allow-always" && decision !== "deny" || terminalReason !== null && terminalReason !== "user" && terminalReason !== "timeout" && terminalReason !== "malformed-verdict" && terminalReason !== "no-route" && terminalReason !== "run-aborted" && terminalReason !== "gateway-restart" && terminalReason !== "storage-corrupt" || resolverKind !== null && resolverKind !== "device" && resolverKind !== "channel" && resolverKind !== "runtime" && resolverKind !== "system") return null;
if (presentation.kind !== kind || row.resolution_ref !== buildApprovalResolutionRef({
approvalId: row.approval_id,
approvalKind: kind
}) || !hasValidLifecycleTuple({
row,
status,
decision,
terminalReason,
resolverKind
}) || status === "allowed" && (!decision || !Array.prototype.includes.call(presentation.allowedDecisions, decision))) return null;
return {
id: row.approval_id,
resolutionRef: row.resolution_ref,
kind,
status,
presentation,
requester: {
deviceId: row.requested_by_device_id,
clientId: row.requested_by_client_id,
deviceTokenAuth: row.requested_by_device_token_auth === 1
},
reviewerDeviceIds,
source: {
agentId: row.source_agent_id,
sessionKey: row.source_session_key,
sessionId: row.source_session_id,
runId: row.source_run_id,
toolCallId: row.source_tool_call_id,
toolName: row.source_tool_name
},
audienceSessionKeys,
runtimeEpoch: row.runtime_epoch,
createdAtMs: row.created_at_ms,
expiresAtMs: row.expires_at_ms,
updatedAtMs: row.updated_at_ms,
decision,
terminalReason,
resolvedAtMs: row.resolved_at_ms,
resolver: resolverKind === null ? null : {
kind: resolverKind,
id: row.resolver_id
},
consumedAtMs: row.consumed_at_ms,
consumedBy: row.consumed_by
};
}
function selectOperatorApprovalRow(database, id) {
const stateDb = getNodeSqliteKysely(database.db);
return executeSqliteQueryTakeFirstSync(database.db, stateDb.selectFrom("operator_approvals").selectAll().where("approval_id", "=", id));
}
function selectOperatorApprovalRowByLocator(database, locator) {
const stateDb = getNodeSqliteKysely(database.db);
const rows = executeSqliteQuerySync(database.db, stateDb.selectFrom("operator_approvals").selectAll().where((eb) => eb.or([eb("approval_id", "=", locator), eb("resolution_ref", "=", locator)])).limit(2)).rows;
return rows.length === 1 ? rows[0] : void 0;
}
function hasApprovalLocatorNamespaceConflict(params) {
const stateDb = getNodeSqliteKysely(params.database.db);
return executeSqliteQueryTakeFirstSync(params.database.db, stateDb.selectFrom("operator_approvals").select("approval_id").where((eb) => eb.or([eb("approval_id", "=", params.resolutionRef), eb("resolution_ref", "=", params.id)])).where("approval_id", "!=", params.id)) !== void 0;
}
function matchesExpectedApprovalOwner(params) {
return (params.expectedKind === void 0 || params.row.kind === params.expectedKind) && (params.runtimeEpoch === void 0 || params.row.runtime_epoch === params.runtimeEpoch);
}
function denyCorruptPendingRow(params) {
const auditTimestampMs = clampAuditTimestamp(params.nowMs, params.createdAtMs);
const stateDb = getNodeSqliteKysely(params.database.db);
executeSqliteQuerySync(params.database.db, stateDb.updateTable("operator_approvals").set({
status: "denied",
decision: "deny",
terminal_reason: "storage-corrupt",
resolved_at_ms: auditTimestampMs,
resolver_kind: "system",
resolver_id: null,
updated_at_ms: auditTimestampMs
}).where("approval_id", "=", params.id).where("status", "=", "pending"));
}
function expirePendingRow(params) {
const auditTimestampMs = clampAuditTimestamp(params.nowMs, params.createdAtMs);
const stateDb = getNodeSqliteKysely(params.database.db);
executeSqliteQuerySync(params.database.db, stateDb.updateTable("operator_approvals").set({
status: "expired",
decision: "deny",
terminal_reason: "timeout",
resolved_at_ms: auditTimestampMs,
resolver_kind: "system",
resolver_id: null,
updated_at_ms: auditTimestampMs
}).where("approval_id", "=", params.id).where("status", "=", "pending").where("expires_at_ms", "<=", params.nowMs));
return selectOperatorApprovalRow(params.database, params.id);
}
function requireDecodedRecord(row) {
const record = decodeOperatorApprovalRow(row);
if (!record) throw new Error(`operator approval '${row.approval_id}' became corrupt during a transaction`);
return record;
}
function inputMatchesExistingRow(input, row, serialized) {
const source = input.source ?? {};
return row.status === "pending" && row.kind === input.kind && row.presentation_json === serialized.presentationJson && row.requested_by_device_id === normalizeNullableString(input.requester?.deviceId) && row.requested_by_client_id === normalizeNullableString(input.requester?.clientId) && row.requested_by_device_token_auth === (input.requester?.deviceTokenAuth === true ? 1 : 0) && row.reviewer_device_ids_json === serialized.reviewerDeviceIdsJson && row.source_agent_id === normalizeNullableString(source.agentId) && row.source_session_key === normalizeNullableString(source.sessionKey) && row.source_session_id === normalizeNullableString(source.sessionId) && row.source_run_id === normalizeNullableString(source.runId) && row.source_tool_call_id === normalizeNullableString(source.toolCallId) && row.source_tool_name === normalizeNullableString(source.toolName) && row.audience_session_keys_json === serialized.audienceSessionKeysJson && row.runtime_epoch === input.runtimeEpoch.trim() && row.created_at_ms === input.createdAtMs && row.expires_at_ms === input.expiresAtMs;
}
//#endregion
//#region src/gateway/operator-approval-store.transitions.ts
function resolveOperatorApprovalInDatabase(params) {
const id = requireApprovalId(params.id);
const resolverId = normalizeNullableString(params.resolver.id);
const runtimeEpoch = params.runtimeEpoch === void 0 ? void 0 : requireString(params.runtimeEpoch, "operator approval runtime epoch");
return runOpenClawStateWriteTransaction((database) => {
const nowMs = params.nowMs ?? Date.now();
let row = selectOperatorApprovalRow(database, id);
if (!row) return { outcome: "not-found" };
if (!matchesExpectedApprovalOwner({
row,
expectedKind: params.expectedKind,
runtimeEpoch
})) return { outcome: "not-found" };
let record = decodeOperatorApprovalRow(row);
if (!record) {
denyCorruptPendingRow({
database,
id,
nowMs,
createdAtMs: row.created_at_ms
});
return { outcome: "corrupt" };
}
if (record.status !== "pending") return {
outcome: "already-resolved",
retry: record.decision === params.decision ? "same" : "conflict",
record
};
if (record.expiresAtMs <= nowMs) {
row = expirePendingRow({
database,
id,
nowMs,
createdAtMs: row.created_at_ms
});
if (!row) return { outcome: "not-found" };
record = requireDecodedRecord(row);
return {
outcome: "expired",
record
};
}
if (!Array.prototype.includes.call(record.presentation.allowedDecisions, params.decision)) return {
outcome: "decision-not-allowed",
record
};
const auditTimestampMs = clampAuditTimestamp(nowMs, record.createdAtMs);
let resolveQuery = getNodeSqliteKysely(database.db).updateTable("operator_approvals").set({
status: params.decision === "deny" ? "denied" : "allowed",
decision: params.decision,
terminal_reason: "user",
resolved_at_ms: auditTimestampMs,
resolver_kind: params.resolver.kind,
resolver_id: resolverId,
updated_at_ms: auditTimestampMs
}).where("approval_id", "=", id).where("status", "=", "pending").where("expires_at_ms", ">", nowMs);
if (params.expectedKind !== void 0) resolveQuery = resolveQuery.where("kind", "=", params.expectedKind);
if (runtimeEpoch !== void 0) resolveQuery = resolveQuery.where("runtime_epoch", "=", runtimeEpoch);
const result = executeSqliteQuerySync(database.db, resolveQuery);
row = selectOperatorApprovalRow(database, id);
if (!row) return { outcome: "not-found" };
record = requireDecodedRecord(row);
if (result.numAffectedRows === 1n) {
if (params.decision === "allow-always" && params.mcpToolGrant && record.kind === "plugin" && record.source.agentId === params.mcpToolGrant.agentId) mintMcpToolGrantLocked(database.db, params.mcpToolGrant, auditTimestampMs);
if (params.decision === "allow-always" && params.standingGrant) mintCronStandingGrantLocked(database, {
...params.standingGrant,
approvalId: id,
nowMs: auditTimestampMs
});
return {
outcome: "resolved",
record
};
}
if (record.status === "pending" && record.expiresAtMs <= nowMs) {
const expiredRow = expirePendingRow({
database,
id,
nowMs,
createdAtMs: record.createdAtMs
});
if (!expiredRow) return { outcome: "not-found" };
return {
outcome: "expired",
record: requireDecodedRecord(expiredRow)
};
}
return {
outcome: "already-resolved",
retry: record.decision === params.decision ? "same" : "conflict",
record
};
}, params.databaseOptions);
}
function forceDenyOperatorApprovalInDatabase(params) {
const id = requireApprovalId(params.id);
const runtimeEpoch = params.runtimeEpoch === void 0 ? void 0 : requireString(params.runtimeEpoch, "operator approval runtime epoch");
return runOpenClawStateWriteTransaction((database) => {
const nowMs = params.nowMs ?? Date.now();
const row = selectOperatorApprovalRow(database, id);
if (!row) return { outcome: "not-found" };
if (!matchesExpectedApprovalOwner({
row,
expectedKind: params.expectedKind,
runtimeEpoch
})) return { outcome: "not-found" };
if (row.status === "pending" && row.expires_at_ms <= nowMs) {
const expiredRow = expirePendingRow({
database,
id,
nowMs,
createdAtMs: row.created_at_ms
});
if (!expiredRow) return { outcome: "not-found" };
const expiredRecord = decodeOperatorApprovalRow(expiredRow);
return expiredRecord ? {
outcome: "expired",
record: expiredRecord
} : { outcome: "corrupt" };
}
const record = decodeOperatorApprovalRow(row);
if (!record) {
denyCorruptPendingRow({
database,
id,
nowMs,
createdAtMs: row.created_at_ms
});
return { outcome: "corrupt" };
}
if (record.status !== "pending") return {
outcome: "already-terminal",
record
};
if (params.status === "expired" && params.requireDue === true && record.expiresAtMs > nowMs) return {
outcome: "not-due",
record
};
const auditTimestampMs = clampAuditTimestamp(nowMs, record.createdAtMs);
let denyQuery = getNodeSqliteKysely(database.db).updateTable("operator_approvals").set({
status: params.status ?? "denied",
decision: "deny",
terminal_reason: params.reason,
resolved_at_ms: auditTimestampMs,
resolver_kind: params.resolver.kind,
resolver_id: normalizeNullableString(params.resolver.id),
updated_at_ms: auditTimestampMs
}).where("approval_id", "=", id).where("status", "=", "pending");
if (params.expectedKind !== void 0) denyQuery = denyQuery.where("kind", "=", params.expectedKind);
if (runtimeEpoch !== void 0) denyQuery = denyQuery.where("runtime_epoch", "=", runtimeEpoch);
executeSqliteQuerySync(database.db, denyQuery);
const terminalRow = selectOperatorApprovalRow(database, id);
if (!terminalRow) return { outcome: "not-found" };
return {
outcome: "denied",
record: requireDecodedRecord(terminalRow)
};
}, params.databaseOptions);
}
function expireDueOperatorApprovalsInDatabase(params) {
return runOpenClawStateWriteTransaction((database) => {
const nowMs = params.nowMs ?? Date.now();
const stateDb = getNodeSqliteKysely(database.db);
const dueRows = executeSqliteQuerySync(database.db, stateDb.selectFrom("operator_approvals").selectAll().where("status", "=", "pending").where("expires_at_ms", "<=", nowMs).orderBy("expires_at_ms", "asc").orderBy("approval_id", "asc")).rows;
if (dueRows.length === 0) return {
affected: 0,
records: []
};
const terminalFields = {
status: "expired",
decision: "deny",
terminal_reason: "timeout",
resolved_at_ms: nowMs,
resolver_kind: "system",
resolver_id: null,
updated_at_ms: nowMs
};
const result = executeSqliteQuerySync(database.db, stateDb.updateTable("operator_approvals").set(terminalFields).where("status", "=", "pending").where("expires_at_ms", "<=", nowMs));
return {
affected: Number(result.numAffectedRows ?? 0n),
records: dueRows.map((row) => decodeOperatorApprovalRow({
...row,
...terminalFields
})).filter((record) => record !== null)
};
}, params.databaseOptions);
}
function closeOrphanedOperatorApprovals(params) {
const runtimeEpoch = requireString(params.runtimeEpoch, "operator approval runtime epoch");
return runOpenClawStateWriteTransaction((database) => {
const nowMs = params.nowMs ?? Date.now();
const stateDb = getNodeSqliteKysely(database.db);
const orphanRows = executeSqliteQuerySync(database.db, stateDb.selectFrom("operator_approvals").selectAll().where("status", "=", "pending").where("runtime_epoch", "!=", runtimeEpoch).orderBy("created_at_ms", "asc").orderBy("approval_id", "asc")).rows;
if (orphanRows.length === 0) return {
affected: 0,
records: []
};
let affected = 0;
const terminalRows = [];
for (const row of orphanRows) {
const auditTimestampMs = clampAuditTimestamp(nowMs, row.created_at_ms);
const terminalFields = {
status: "cancelled",
decision: "deny",
terminal_reason: "gateway-restart",
resolved_at_ms: auditTimestampMs,
resolver_kind: "system",
resolver_id: null,
updated_at_ms: auditTimestampMs
};
const result = executeSqliteQuerySync(database.db, stateDb.updateTable("operator_approvals").set(terminalFields).where("approval_id", "=", row.approval_id).where("status", "=", "pending"));
const rowAffected = Number(result.numAffectedRows ?? 0n);
affected += rowAffected;
if (rowAffected === 1) terminalRows.push({
...row,
...terminalFields
});
}
return {
affected,
records: terminalRows.map((row) => decodeOperatorApprovalRow(row)).filter((record) => record !== null)
};
}, params.databaseOptions);
}
function consumeOperatorApprovalAllowOnceInDatabase(params) {
const id = requireApprovalId(params.id);
const consumerId = requireString(params.consumerId, "operator approval consumer id");
const runtimeEpoch = params.runtimeEpoch === void 0 ? void 0 : requireString(params.runtimeEpoch, "operator approval runtime epoch");
if (params.redemptionWindowMs !== void 0 && !isValidTimestamp(params.redemptionWindowMs)) throw new Error("operator approval redemption window must be a non-negative safe integer");
return runOpenClawStateWriteTransaction((database) => {
const nowMs = params.nowMs ?? Date.now();
const redemptionThresholdMs = params.redemptionWindowMs === void 0 ? void 0 : nowMs - params.redemptionWindowMs;
let row = selectOperatorApprovalRow(database, id);
if (!row) return { outcome: "not-found" };
if (!matchesExpectedApprovalOwner({
row,
expectedKind: params.expectedKind,
runtimeEpoch
})) return { outcome: "not-found" };
if (row.status === "pending" && row.expires_at_ms <= nowMs) {
row = expirePendingRow({
database,
id,
nowMs,
createdAtMs: row.created_at_ms
});
if (!row) return { outcome: "not-found" };
}
let record = decodeOperatorApprovalRow(row);
if (!record) {
denyCorruptPendingRow({
database,
id,
nowMs,
createdAtMs: row.created_at_ms
});
return { outcome: "corrupt" };
}
if (record.status !== "allowed" || record.decision !== "allow-once") return {
outcome: "not-allow-once",
record
};
if (record.consumedAtMs !== null) return {
outcome: "already-consumed",
record
};
if (record.resolvedAtMs === null) return { outcome: "corrupt" };
if (redemptionThresholdMs !== void 0 && record.resolvedAtMs <= redemptionThresholdMs) return {
outcome: "redemption-expired",
record
};
const auditTimestampMs = clampAuditTimestamp(nowMs, record.createdAtMs, record.resolvedAtMs, record.updatedAtMs);
let consumeQuery = getNodeSqliteKysely(database.db).updateTable("operator_approvals").set({
consumed_at_ms: auditTimestampMs,
consumed_by: consumerId,
updated_at_ms: auditTimestampMs
}).where("approval_id", "=", id).where("status", "=", "allowed").where("decision", "=", "allow-once").where("consumed_at_ms", "is", null);
if (redemptionThresholdMs !== void 0) consumeQuery = consumeQuery.where("resolved_at_ms", ">", redemptionThresholdMs);
if (params.expectedKind !== void 0) consumeQuery = consumeQuery.where("kind", "=", params.expectedKind);
if (runtimeEpoch !== void 0) consumeQuery = consumeQuery.where("runtime_epoch", "=", runtimeEpoch);
const result = executeSqliteQuerySync(database.db, consumeQuery);
row = selectOperatorApprovalRow(database, id);
if (!row) return { outcome: "not-found" };
record = requireDecodedRecord(row);
if (result.numAffectedRows === 1n) return {
outcome: "consumed",
record
};
if (redemptionThresholdMs !== void 0 && record.resolvedAtMs !== null && record.resolvedAtMs <= redemptionThresholdMs) return {
outcome: "redemption-expired",
record
};
return {
outcome: "already-consumed",
record
};
}, params.databaseOptions);
}
function pruneTerminalOperatorApprovals(params) {
const retentionMs = params.retentionMs ?? 2592e6;
if (!Number.isSafeInteger(retentionMs) || retentionMs < 0) throw new Error("operator approval retention must be a non-negative safe integer");
return runOpenClawStateWriteTransaction((database) => {
const cutoffMs = (params.nowMs ?? Date.now()) - retentionMs;
const stateDb = getNodeSqliteKysely(database.db);
const result = executeSqliteQuerySync(database.db, stateDb.deleteFrom("operator_approvals").where("status", "!=", "pending").where("resolved_at_ms", "is not", null).where("resolved_at_ms", "<=", cutoffMs));
return Number(result.numAffectedRows ?? 0n);
}, params.databaseOptions);
}
//#endregion
export { selectOperatorApprovalRowByLocator as C, selectOperatorApprovalRow as S, inputMatchesExistingRow as _, pruneTerminalOperatorApprovals as a, requireApprovalId as b, OPERATOR_APPROVAL_MAX_LIST_LIMIT as c, decodeOperatorApprovalHistoryCursor as d, decodeOperatorApprovalRow as f, hasApprovalLocatorNamespaceConflict as g, getOperatorApprovalResolutionKey as h, forceDenyOperatorApprovalInDatabase as i, OPERATOR_APPROVAL_TERMINAL_RETENTION_MS as l, expirePendingRow as m, consumeOperatorApprovalAllowOnceInDatabase as n, resolveOperatorApprovalInDatabase as o, denyCorruptPendingRow as p, expireDueOperatorApprovalsInDatabase as r, OPERATOR_APPROVAL_EXECUTION_IDENTITY_SCHEMA_SQL as s, closeOrphanedOperatorApprovals as t, OperatorApprovalHistoryCursorError as u, isValidTimestamp as v, stringifyPresentation as w, requireString as x, normalizeExecutionIdentityBinding as y };