UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,247 lines 56.7 kB
import { t as safeParseJson } from "./json-coercion-AulM0PZ6.js";
import { s as normalizeNullableString } from "./string-coerce-CIXf7egm.js";
import { _ as normalizeUniqueTrimmedStringList } from "./string-normalization-DsCfAx8q.js";
import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { E as tableExists } from "./openclaw-state-db-cache-C7ljO0xP.js";
import { r as withExistingOpenClawStateDatabaseReadOnly } from "./openclaw-state-db-readonly-BRgmrGHt.js";
import { i as openOpenClawStateDatabase, s as runOpenClawStateWriteTransaction, vt as normalizeSqliteNumber } from "./openclaw-state-db-BRTnL-D8.js";
import { n as isApprovalResolutionRef, t as buildApprovalResolutionRef } from "./approval-resolution-ref-BMBlVd2b.js";
import { h as validateApprovalPresentation } from "./src-BiL5aQto.js";
import { n as isWellFormedApprovalId } from "./approval-id-BTRnO3t1.js";
import { a as mintMcpToolGrantLocked } from "./exec-approvals-sqlite-BK42k6JF.js";
import { i as mintCronStandingGrantLocked } from "./operator-approval-standing-grants-DyquMQeP.js";
import { createHash } from "node:crypto";
import { sql } from "kysely";
//#region src/gateway/operator-approval-store.ts
const OPERATOR_APPROVAL_TERMINAL_RETENTION_MS = 2592e6;
const OPERATOR_APPROVAL_RECEIPT_SUMMARY_MAX_ROWS = 128;
const OPERATOR_APPROVAL_RECEIPT_MAX_PAYLOAD_BYTES = 65536;
const OPERATOR_APPROVAL_PENDING_SCAN_PAGE_SIZE = 256;
const OPERATOR_APPROVAL_MAX_LIST_LIMIT = 1001;
const OPERATOR_APPROVAL_HISTORY_DEFAULT_LIMIT = 50;
const OPERATOR_APPROVAL_HISTORY_MAX_LIMIT = 100;
var OperatorApprovalHistoryCursorError = class extends Error {
	constructor() {
		super("invalid operator approval history cursor");
		this.name = "OperatorApprovalHistoryCursorError";
	}
};
const OPERATOR_APPROVAL_DECISIONS = /* @__PURE__ */ new Set([
	"allow-once",
	"allow-always",
	"deny"
]);
const OPERATOR_APPROVAL_KINDS = /* @__PURE__ */ new Set([
	"exec",
	"plugin",
	"system-agent"
]);
const OPERATOR_APPROVAL_STATUSES = /* @__PURE__ */ new Set([
	"pending",
	"allowed",
	"denied",
	"expired",
	"cancelled"
]);
const OPERATOR_APPROVAL_TERMINAL_REASONS = /* @__PURE__ */ new Set([
	"user",
	"timeout",
	"malformed-verdict",
	"no-route",
	"run-aborted",
	"gateway-restart",
	"storage-corrupt"
]);
const OPERATOR_APPROVAL_RESOLVER_KINDS = /* @__PURE__ */ new Set([
	"device",
	"channel",
	"runtime",
	"system"
]);
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.some((entry) => typeof entry !== "string" || !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 = 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 || !OPERATOR_APPROVAL_KINDS.has(kind) || !OPERATOR_APPROVAL_STATUSES.has(status) || !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 && !OPERATOR_APPROVAL_DECISIONS.has(decision) || terminalReason !== null && !OPERATOR_APPROVAL_TERMINAL_REASONS.has(terminalReason) || resolverKind !== null && !OPERATOR_APPROVAL_RESOLVER_KINDS.has(resolverKind)) 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 operatorApprovalReasonCode(record) {
	if (record.status === "allowed") return record.decision === "allow-always" ? "operator_approval_allowed_always" : "operator_approval_allowed_once";
	if (record.status === "expired") return "operator_approval_expired";
	if (record.status === "cancelled") return record.terminalReason === "gateway-restart" ? "operator_approval_cancelled_gateway_restart" : "operator_approval_cancelled_run_aborted";
	switch (record.terminalReason) {
		case "malformed-verdict": return "operator_approval_denied_malformed_verdict";
		case "no-route": return "operator_approval_denied_no_route";
		case "storage-corrupt": return "operator_approval_denied_storage_corrupt";
		default: return "operator_approval_denied_by_reviewer";
	}
}
function operatorApprovalPolicyRefs(record) {
	const refs = ["operator-approval:first-answer-wins"];
	switch (record.terminalReason) {
		case "user":
			refs.push("operator-approval:human-decision");
			break;
		case "timeout":
			refs.push("operator-approval:deadline");
			break;
		case "no-route":
			refs.push("operator-approval:delivery-route-required");
			break;
		case "run-aborted":
			refs.push("operator-approval:run-lifecycle");
			break;
		case "gateway-restart":
			refs.push("operator-approval:runtime-lifecycle");
			break;
		case "malformed-verdict":
			refs.push("operator-approval:valid-verdict-required");
			break;
		case "storage-corrupt": refs.push("operator-approval:fail-closed-storage");
	}
	return refs.toSorted();
}
function operatorApprovalRemediation(record) {
	if (record.status === "allowed") return [];
	switch (record.terminalReason) {
		case "timeout": return [{
			code: "request_approval_again",
			text: "Request the action again and resolve the new approval before its deadline."
		}];
		case "no-route": return [{
			code: "restore_approval_route",
			text: "Connect an eligible approval client or configure an approval delivery route, then request the action again."
		}];
		case "run-aborted":
			if (record.resolver?.kind === "system" && (record.resolver.id === "permission-change" || record.resolver.id === "approval-scope-closed")) return [{
				code: "request_approval_again",
				text: "Request the action again under the current permissions if it is still needed."
			}];
			return [{
				code: "start_new_run",
				text: "Start a new run and request the action again if it is still needed."
			}];
		case "gateway-restart": return [{
			code: "request_after_restart",
			text: "After the Gateway is available, request the action again to create a current approval."
		}];
		case "malformed-verdict": return [{
			code: "submit_supported_decision",
			text: "Request the action again and resolve it with one of the decisions shown by the approval prompt."
		}];
		case "storage-corrupt": return [{
			code: "inspect_state_integrity",
			text: "Run openclaw doctor and inspect the shared state database before requesting the action again."
		}];
		default: return [{
			code: "review_and_request_again",
			text: "Review the denial, then request the action again only if an eligible reviewer should reconsider it."
		}];
	}
}
function projectOperatorApprovalReceipt(record, context) {
	const allowed = record.status === "allowed";
	const sourceRef = record.resolutionRef;
	return {
		schemaVersion: 1,
		receiptId: `approval:${sourceRef}`,
		contextId: context.contextId,
		executionId: context.executionId,
		runId: context.runId,
		actionId: sourceRef,
		occurredAt: record.resolvedAtMs ?? record.updatedAtMs,
		action: {
			family: record.kind,
			operation: "approval",
			summary: allowed ? `A ${record.kind} approval allowed the requested action.` : `A ${record.kind} approval stopped the requested action.`
		},
		decision: {
			outcome: allowed ? "allowed" : "denied",
			reasonCode: operatorApprovalReasonCode(record)
		},
		enforcement: {
			coverageState: "enforced",
			evaluatorRef: `operator-approval:${record.resolver?.kind ?? "system"}`,
			policyRefs: operatorApprovalPolicyRefs(record),
			grantRefs: allowed ? [`operator-approval-grant:${sourceRef}`] : [],
			contextFieldsUsed: [
				"contextId",
				"executionId",
				"runId"
			]
		},
		source: {
			owner: "operator_approvals",
			recordRef: sourceRef,
			decisionBoundary: "gateway.operator-approval.first-answer"
		},
		missingEvidence: [],
		remediation: operatorApprovalRemediation(record)
	};
}
function projectUnlinkedOperatorApprovalReceipt(record, context, linkState) {
	const sourceRef = record.resolutionRef;
	return {
		schemaVersion: 1,
		receiptId: `approval-unlinked:${createHash("sha256").update(sourceRef, "utf8").update("\0", "utf8").update(context.contextId, "utf8").digest("base64url")}`,
		contextId: context.contextId,
		executionId: context.executionId,
		runId: context.runId,
		actionId: sourceRef,
		occurredAt: record.resolvedAtMs ?? record.updatedAtMs,
		action: {
			family: record.kind,
			operation: "approval",
			summary: `A terminal ${record.kind} approval shares this run correlation, but its retained binding does not match this exact execution.`
		},
		decision: {
			outcome: "unknown",
			reasonCode: `operator_approval_execution_link_${linkState}`
		},
		enforcement: {
			coverageState: "unknown",
			policyRefs: operatorApprovalPolicyRefs(record),
			grantRefs: [],
			contextFieldsUsed: [
				"contextId",
				"executionId",
				"runId"
			]
		},
		source: {
			owner: "operator_approvals",
			recordRef: sourceRef,
			decisionBoundary: "gateway.operator-approval.first-answer"
		},
		missingEvidence: ["decision.execution_link"],
		remediation: [{
			code: "inspect_exact_approval_binding",
			text: "Treat this approval only as run-correlated; inspect its retained execution binding before trusting attribution."
		}]
	};
}
function projectCorruptOperatorApprovalReceipt(row, context) {
	const kind = OPERATOR_APPROVAL_KINDS.has(row.kind) ? row.kind : "exec";
	const sourceRef = isApprovalResolutionRef(row.resolution_ref) ? row.resolution_ref : buildApprovalResolutionRef({
		approvalId: row.approval_id,
		approvalKind: kind
	});
	const occurredAt = isValidTimestamp(row.resolved_at_ms ?? -1) ? row.resolved_at_ms : isValidTimestamp(row.updated_at_ms) ? row.updated_at_ms : 0;
	return {
		schemaVersion: 1,
		receiptId: `approval:${sourceRef}`,
		contextId: context.contextId,
		executionId: context.executionId,
		runId: context.runId,
		actionId: sourceRef,
		occurredAt,
		action: {
			family: kind,
			operation: "approval"
		},
		decision: {
			outcome: "unknown",
			reasonCode: "operator_approval_record_corrupt"
		},
		enforcement: {
			coverageState: "unknown",
			policyRefs: [],
			grantRefs: [],
			contextFieldsUsed: ["runId"]
		},
		source: {
			owner: "operator_approvals",
			recordRef: sourceRef,
			decisionBoundary: "gateway.operator-approval.first-answer"
		},
		missingEvidence: ["operator_approval.valid"],
		remediation: [{
			code: "inspect_state_integrity",
			text: "Run openclaw doctor and inspect the shared state database before trusting this approval."
		}]
	};
}
function projectOversizedOperatorApprovalReceipt(row, context) {
	return {
		...projectCorruptOperatorApprovalReceipt(row, context),
		decision: {
			outcome: "unknown",
			reasonCode: "operator_approval_payload_bounded"
		},
		missingEvidence: ["operator_approval.payload_bounded"],
		remediation: [{
			code: "inspect_approval_record",
			text: "Inspect the retained approval directly; its presentation exceeds the bounded audit projection."
		}]
	};
}
function terminalApprovalsForRunQuery(database, runId, nowMs) {
	return database.selectFrom("operator_approvals").where("source_run_id", "=", runId).where("status", "!=", "pending").where("resolved_at_ms", "is not", null).where("resolved_at_ms", ">=", nowMs - OPERATOR_APPROVAL_TERMINAL_RETENTION_MS);
}
function operatorApprovalRowId() {
	return sql`operator_approvals.rowid`;
}
function operatorApprovalSelectorId(row) {
	const rowId = normalizeSqliteNumber(row.receipt_rowid);
	if (rowId === void 0 || rowId < 1) throw new Error("invalid operator approval receipt rowid");
	return `approval-decision:${rowId}`;
}
function operatorApprovalPayloadBytes() {
	return sql`
    length(CAST(operator_approvals.presentation_json AS BLOB)) +
    length(CAST(operator_approvals.reviewer_device_ids_json AS BLOB)) +
    length(CAST(operator_approvals.audience_session_keys_json AS BLOB))
  `;
}
const OPERATOR_APPROVAL_PAYLOAD_COLUMNS = {
	presentation_json: sql`operator_approvals.presentation_json`,
	reviewer_device_ids_json: sql`operator_approvals.reviewer_device_ids_json`,
	audience_session_keys_json: sql`operator_approvals.audience_session_keys_json`
};
function boundedOperatorApprovalPayload(column) {
	return sql`CASE WHEN ${operatorApprovalPayloadBytes()} <= ${OPERATOR_APPROVAL_RECEIPT_MAX_PAYLOAD_BYTES} THEN ${OPERATOR_APPROVAL_PAYLOAD_COLUMNS[column]} ELSE NULL END`;
}
function operatorApprovalReceiptSnapshotColumns(hasExecutionIdentityTable) {
	const bindingContextId = hasExecutionIdentityTable ? sql`operator_approval_execution_identities.source_context_id` : sql`NULL`;
	const bindingExecutionId = hasExecutionIdentityTable ? sql`operator_approval_execution_identities.source_execution_id` : sql`NULL`;
	return sql`
    operator_approvals.approval_id,
    operator_approvals.consumed_at_ms,
    operator_approvals.consumed_by,
    operator_approvals.created_at_ms,
    operator_approvals.decision,
    operator_approvals.expires_at_ms,
    operator_approvals.kind,
    ${boundedOperatorApprovalPayload("presentation_json")} AS presentation_json,
    operator_approvals.requested_by_client_id,
    operator_approvals.requested_by_device_id,
    operator_approvals.requested_by_device_token_auth,
    operator_approvals.resolution_ref,
    operator_approvals.resolved_at_ms,
    operator_approvals.resolver_id,
    operator_approvals.resolver_kind,
    ${boundedOperatorApprovalPayload("reviewer_device_ids_json")} AS reviewer_device_ids_json,
    operator_approvals.runtime_epoch,
    operator_approvals.source_agent_id,
    operator_approvals.source_run_id,
    operator_approvals.source_session_id,
    operator_approvals.source_session_key,
    operator_approvals.source_tool_call_id,
    operator_approvals.source_tool_name,
    operator_approvals.status,
    operator_approvals.terminal_reason,
    operator_approvals.updated_at_ms,
    ${boundedOperatorApprovalPayload("audience_session_keys_json")} AS audience_session_keys_json,
    ${bindingContextId} AS binding_context_id,
    ${bindingExecutionId} AS binding_execution_id,
    ${operatorApprovalRowId()} AS receipt_rowid,
    ${operatorApprovalPayloadBytes()} AS payload_bytes
  `;
}
function terminalApprovalReceiptPageRows(params) {
	const hasExecutionIdentityTable = tableExists(params.db, "operator_approval_execution_identities");
	const executionIdentityJoin = hasExecutionIdentityTable ? sql`LEFT JOIN operator_approval_execution_identities
          ON operator_approval_execution_identities.approval_id = operator_approvals.approval_id` : sql``;
	const cutoffMs = params.nowMs - OPERATOR_APPROVAL_TERMINAL_RETENTION_MS;
	const offset = params.offset ?? 0;
	const pageStatement = params.after ? sql`
        WITH cursor_boundary AS (
          SELECT approval_id, resolved_at_ms, ${operatorApprovalRowId()} AS receipt_rowid
          FROM operator_approvals
          WHERE ${operatorApprovalRowId()} = ${params.after.rowId}
            AND source_run_id = ${params.runId}
            AND resolved_at_ms = ${params.after.occurredAt}
        ), approval_page AS (
          SELECT ${operatorApprovalReceiptSnapshotColumns(hasExecutionIdentityTable)}
          FROM operator_approvals
          ${executionIdentityJoin}
          CROSS JOIN cursor_boundary
          WHERE operator_approvals.source_run_id = ${params.runId}
            AND operator_approvals.status != 'pending'
            AND operator_approvals.resolved_at_ms IS NOT NULL
            AND operator_approvals.resolved_at_ms >= ${cutoffMs}
            AND (
              operator_approvals.resolved_at_ms > cursor_boundary.resolved_at_ms
              OR (
                operator_approvals.resolved_at_ms = cursor_boundary.resolved_at_ms
                AND operator_approvals.approval_id > cursor_boundary.approval_id
              )
            )
          ORDER BY operator_approvals.resolved_at_ms ASC, operator_approvals.approval_id ASC
          LIMIT ${params.limit} OFFSET ${offset}
        )
        SELECT
          cursor_boundary.receipt_rowid AS cursor_boundary_rowid,
          CASE WHEN approval_page.receipt_rowid IS NULL THEN 0 ELSE 1 END AS page_present,
          approval_page.*
        FROM (SELECT 1) AS snapshot_seed
        LEFT JOIN cursor_boundary ON TRUE
        LEFT JOIN approval_page ON TRUE
        ORDER BY approval_page.resolved_at_ms ASC, approval_page.approval_id ASC
      ` : sql`
        SELECT
          NULL AS cursor_boundary_rowid,
          1 AS page_present,
          ${operatorApprovalReceiptSnapshotColumns(hasExecutionIdentityTable)}
        FROM operator_approvals
        ${executionIdentityJoin}
        WHERE operator_approvals.source_run_id = ${params.runId}
          AND operator_approvals.status != 'pending'
          AND operator_approvals.resolved_at_ms IS NOT NULL
          AND operator_approvals.resolved_at_ms >= ${cutoffMs}
        ORDER BY operator_approvals.resolved_at_ms ASC, operator_approvals.approval_id ASC
        LIMIT ${params.limit} OFFSET ${offset}
      `;
	const stateDb = getNodeSqliteKysely(params.db);
	const rows = executeSqliteQuerySync(params.db, stateDb.selectFrom(sql`(${pageStatement})`.as("approval_snapshot")).selectAll()).rows;
	if (params.after && rows[0]?.cursor_boundary_rowid === null) throw new Error("operator approval decision cursor is no longer retained");
	if (rows.length === 1 && rows[0]?.page_present === 0) return [];
	return rows.map((row) => {
		if (row.page_present !== 1) throw new Error("operator approval page snapshot is malformed");
		return row;
	});
}
function materializeBoundedOperatorApprovalRow(row) {
	return typeof row.presentation_json === "string" && typeof row.reviewer_device_ids_json === "string" && typeof row.audience_session_keys_json === "string" ? {
		...row,
		presentation_json: row.presentation_json,
		reviewer_device_ids_json: row.reviewer_device_ids_json,
		audience_session_keys_json: row.audience_session_keys_json
	} : null;
}
function operatorApprovalExecutionLinkState(row, context) {
	if (row.binding_context_id === null && row.binding_execution_id === null) return "missing";
	if (typeof row.binding_context_id !== "string" || typeof row.binding_execution_id !== "string" || row.binding_context_id.length === 0 || row.binding_execution_id.length === 0 || row.binding_context_id.length > 256 || row.binding_execution_id.length > 256 || row.binding_context_id.trim() !== row.binding_context_id || row.binding_execution_id.trim() !== row.binding_execution_id) return "malformed";
	return row.binding_context_id === context.contextId && row.binding_execution_id === context.executionId && row.source_run_id === context.runId ? "exact" : "mismatch";
}
/** Probe for an authoritative retained approval without scanning the full run history. */
function hasOperatorApprovalReceiptsForRun(params) {
	return withExistingOpenClawStateDatabaseReadOnly(({ db }) => {
		if (!tableExists(db, "operator_approvals")) return false;
		const stateDb = getNodeSqliteKysely(db);
		return Boolean(executeSqliteQueryTakeFirstSync(db, terminalApprovalsForRunQuery(stateDb, params.runId, params.nowMs ?? Date.now()).clearSelect().select("approval_id").limit(1)));
	}, params.databaseOptions) ?? false;
}
/** Summarize at most 128 owner rows; the 129th makes coverage explicitly unknown. */
function summarizeOperatorApprovalReceiptsForRun(params) {
	return withExistingOpenClawStateDatabaseReadOnly(({ db }) => {
		if (!tableExists(db, "operator_approvals")) return {
			count: 0,
			missingEvidence: []
		};
		const stateDb = getNodeSqliteKysely(db);
		const snapshotRows = terminalApprovalReceiptPageRows({
			db,
			runId: params.context.runId,
			nowMs: params.nowMs ?? Date.now(),
			limit: 129
		});
		const boundedCount = snapshotRows.length;
		const count = params.exactCount ? executeSqliteQueryTakeFirstSync(db, terminalApprovalsForRunQuery(stateDb, params.context.runId, params.nowMs ?? Date.now()).clearSelect().select((eb) => eb.fn.countAll().as("count")))?.count ?? 0 : boundedCount;
		if (boundedCount === 0) return {
			count: 0,
			missingEvidence: []
		};
		if (boundedCount > OPERATOR_APPROVAL_RECEIPT_SUMMARY_MAX_ROWS) return {
			count,
			coverageState: "unknown",
			missingEvidence: ["operator_approval.summary_bounded"]
		};
		const hasOversizedRecord = snapshotRows.some((row) => row.payload_bytes > OPERATOR_APPROVAL_RECEIPT_MAX_PAYLOAD_BYTES);
		const rows = snapshotRows.filter((row) => row.payload_bytes <= OPERATOR_APPROVAL_RECEIPT_MAX_PAYLOAD_BYTES).map(materializeBoundedOperatorApprovalRow);
		const hasMissingBoundedRow = rows.some((row) => row === null);
		const records = rows.map((row) => row === null ? null : decodeOperatorApprovalRow(row));
		const hasCorruptRecord = records.some((record) => record === null);
		const hasUnlinkedRecord = rows.some((row, index) => row !== null && records[index] !== null && operatorApprovalExecutionLinkState(row, params.context) !== "exact");
		return {
			count,
			coverageState: hasOversizedRecord || hasMissingBoundedRow || hasCorruptRecord || hasUnlinkedRecord ? "unknown" : "enforced",
			missingEvidence: [
				...hasUnlinkedRecord ? ["decision.execution_link"] : [],
				...hasCorruptRecord ? ["operator_approval.valid"] : [],
				...hasOversizedRecord || hasMissingBoundedRow ? ["operator_approval.payload_bounded"] : []
			]
		};
	}, params.databaseOptions) ?? {
		count: 0,
		missingEvidence: []
	};
}
/** Project authoritative approval rows directly; no generic decision fact is written. */
function pageOperatorApprovalReceiptsForRun(params) {
	return withExistingOpenClawStateDatabaseReadOnly(({ db }) => {
		if (!tableExists(db, "operator_approvals")) return { entries: [] };
		const snapshotRows = terminalApprovalReceiptPageRows({
			db,
			runId: params.context.runId,
			nowMs: params.nowMs ?? Date.now(),
			after: params.after,
			offset: params.offset,
			limit: params.limit + 1
		});
		const pageRows = snapshotRows.slice(0, params.limit);
		const entries = pageRows.map((snapshot) => {
			let receipt;
			if (snapshot.payload_bytes > OPERATOR_APPROVAL_RECEIPT_MAX_PAYLOAD_BYTES) receipt = projectOversizedOperatorApprovalReceipt(snapshot, params.context);
			else {
				const row = materializeBoundedOperatorApprovalRow(snapshot);
				const record = row === null ? null : decodeOperatorApprovalRow(row);
				if (row === null || record === null) receipt = projectCorruptOperatorApprovalReceipt(snapshot, params.context);
				else {
					const linkState = operatorApprovalExecutionLinkState(row, params.context);
					receipt = linkState === "exact" ? projectOperatorApprovalReceipt(record, params.context) : projectUnlinkedOperatorApprovalReceipt(record, params.context, linkState);
				}
			}
			return {
				receipt,
				selectorId: operatorApprovalSelectorId(snapshot)
			};
		});
		const last = pageRows.at(-1);
		return {
			entries,
			...snapshotRows.length > params.limit && last && last.resolved_at_ms !== null ? { nextCursor: {
				occurredAt: last.resolved_at_ms,
				rowId: last.receipt_rowid
			} } : {}
		};
	}, params.databaseOptions) ?? { entries: [] };
}
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;
}
function insertOperatorApproval(params) {
	const input = params.approval;
	const id = requireApprovalId(input.id);
	const resolutionRef = buildApprovalResolutionRef({
		approvalId: id,
		approvalKind: input.kind
	});
	const runtimeEpoch = requireString(input.runtimeEpoch, "operator approval runtime epoch");
	if (!isValidTimestamp(input.createdAtMs) || !isValidTimestamp(input.expiresAtMs)) throw new Error("operator approval timestamps must be non-negative safe integers");
	if (input.expiresAtMs < input.createdAtMs) throw new Error("operator approval expiry cannot precede creation");
	const presentationJson = stringifyPresentation(input.presentation);
	if (input.presentation.kind !== input.kind) throw new Error("operator approval kind must match its safe presentation");
	const reviewerDeviceIdsJson = JSON.stringify(normalizeUniqueTrimmedStringList(input.reviewerDeviceIds));
	const audienceSessionKeys = normalizeUniqueTrimmedStringList(input.audienceSessionKeys);
	if (audienceSessionKeys.length > 64) throw new Error(`operator approval audience exceeds 64 sessions`);
	const audienceSessionKeysJson = JSON.stringify(audienceSessionKeys);
	const serialized = {
		presentationJson,
		reviewerDeviceIdsJson,
		audienceSessionKeysJson
	};
	const executionIdentityBinding = normalizeExecutionIdentityBinding(input);
	return runOpenClawStateWriteTransaction((database) => {
		const stateDb = getNodeSqliteKysely(database.db);
		executeSqliteQuerySync(database.db, stateDb.deleteFrom("operator_approvals").where("status", "!=", "pending").where("resolved_at_ms", "is not", null).where("resolved_at_ms", "<=", input.createdAtMs - OPERATOR_APPROVAL_TERMINAL_RETENTION_MS));
		if (hasApprovalLocatorNamespaceConflict({
			database,
			id,
			resolutionRef
		})) return { outcome: "conflict" };
		const source = input.source ?? {};
		const result = executeSqliteQuerySync(database.db, stateDb.insertInto("operator_approvals").values({
			approval_id: id,
			resolution_ref: resolutionRef,
			kind: input.kind,
			status: "pending",
			presentation_json: presentationJson,
			requested_by_device_id: normalizeNullableString(input.requester?.deviceId),
			requested_by_client_id: normalizeNullableString(input.requester?.clientId),
			requested_by_device_token_auth: input.requester?.deviceTokenAuth === true ? 1 : 0,
			reviewer_device_ids_json: reviewerDeviceIdsJson,
			source_agent_id: normalizeNullableString(source.agentId),
			source_session_key: normalizeNullableString(source.sessionKey),
			source_session_id: normalizeNullableString(source.sessionId),
			source_run_id: normalizeNullableString(source.runId),
			source_tool_call_id: normalizeNullableString(source.toolCallId),
			source_tool_name: normalizeNullableString(source.toolName),
			audience_session_keys_json: audienceSessionKeysJson,
			runtime_epoch: runtimeEpoch,
			created_at_ms: input.createdAtMs,
			expires_at_ms: input.expiresAtMs,
			updated_at_ms: input.createdAtMs,
			decision: null,
			terminal_reason: null,
			resolved_at_ms: null,
			resolver_kind: null,
			resolver_id: null,
			consumed_at_ms: null,
			consumed_by: null
		}).onConflict((conflict) => conflict.column("approval_id").doNothing()));
		const row = selectOperatorApprovalRow(database, id);
		if (!row) throw new Error(`operator approval '${id}' was not readable after insert`);
		const record = decodeOperatorApprovalRow(row);
		if (!record) {
			denyCorruptPendingRow({
				database,
				id,
				nowMs: input.createdAtMs,
				createdAtMs: row.created_at_ms
			});
			return { outcome: "conflict" };
		}
		if (result.numAffectedRows === 1n) {
			if (executionIdentityBinding) {
				database.db.exec(OPERATOR_APPROVAL_EXECUTION_IDENTITY_SCHEMA_SQL);
				executeSqliteQuerySync(database.db, stateDb.insertInto("operator_approval_execution_identities").values({
					approval_id: id,
					source_context_id: executionIdentityBinding.sourceContextId,
					source_execution_id: executionIdentityBinding.sourceExecutionId
				}));
			}
			return {
				outcome: "inserted",
				record
			};
		}
		if (!inputMatchesExistingRow(input, row, serialized)) return { outcome: "conflict" };
		if (executionIdentityBinding) {
			if (!tableExists(database.db, "operator_approval_execution_identities")) return { outcome: "conflict" };
			const existingBinding = executeSqliteQueryTakeFirstSync(database.db, stateDb.selectFrom("operator_approval_execution_identities").select(["source_context_id", "source_execution_id"]).where("approval_id", "=", id));
			if (existingBinding?.source_context_id !== executionIdentityBinding.sourceContextId || existingBinding.source_execution_id !== executionIdentityBinding.sourceExecutionId) return { outcome: "conflict" };
		}
		return {
			outcome: "existing",
			record
		};
	}, params.databaseOptions);
}
function getOperatorApprovalDetailed(params) {
	const locator = requireApprovalId(params.id);
	return runOpenClawStateWriteTransaction((database) => {
		const nowMs = params.nowMs ?? Date.now();
		let row = params.allowTransportRef ? selectOperatorApprovalRowByLocator(database, locator) : selectOperatorApprovalRow(database, locator);
		if (!row) return { outcome: "not-found" };
		const id = row.approval_id;
		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" };
		}
		const record = decodeOperatorApprovalRow(row);
		if (record) return {
			outcome: "found",
			record
		};
		denyCorruptPendingRow({
			database,
			id,
			nowMs,
			createdAtMs: row.created_at_ms
		});
		return params.allowTransportRef ? {
			outcome: "corrupt",
			id
		} : { outcome: "corrupt" };
	}, params.databaseOptions);
}
function listPendingOperatorApprovals(params = {}) {
	expireDueOperatorApprovals({
		nowMs: params.nowMs,
		databaseOptions: params.databaseOptions
	});
	return runOpenClawStateWriteTransaction((database) => {
		const nowMs = params.nowMs ?? Date.now();
		const stateDb = getNodeSqliteKysely(database.db);
		const resultLimit = Math.max(1, Math.min(params.limit ?? 1e3, OPERATOR_APPROVAL_MAX_LIST_LIMIT));
		const audienceSessionKey = params.audienceSessionKey === void 0 ? void 0 : requireString(params.audienceSessionKey, "operator approval audience session key");
		const requiresPostFilter = audienceSessionKey !== void 0 || params.recordFilter !== void 0;
		const records = [];
		let cursor;
		while (records.length < resultLimit) {
			let query = stateDb.selectFrom("operator_approvals").selectAll().where("status", "=", "pending").where("expires_at_ms", ">", nowMs).orderBy("created_at_ms", "asc").orderBy("approval_id", "asc").limit(requiresPostFilter ? OPERATOR_APPROVAL_PENDING_SCAN_PAGE_SIZE : resultLimit);
			if (params.kind) query = query.where("kind", "=", params.kind);
			if (params.sourceSessionKey) query = query.where("source_session_key", "=", params.sourceSessionKey);
			if (cursor) {
				const pageCursor = cursor;
				query = query.where((eb) => eb.or([eb("created_at_ms", ">", pageCursor.createdAtMs), eb.and([eb("created_at_ms", "=", pageCursor.createdAtMs), eb("approval_id", ">", pageCursor.id)])]));
			}
			const rows = executeSqliteQuerySync(database.db, query).rows;
			for (const row of rows) {
				const record = decodeOperatorApprovalRow(row);
				if (!record) {
					denyCorruptPendingRow({
						database,
						id: row.approval_id,
						nowMs,
						createdAtMs: row.created_at_ms
					});
					continue;
				}
				if ((!audienceSessionKey || record.audienceSessionKeys.includes(audienceSessionKey)) && (!params.recordFilter || params.recordFilter(record))) {
					records.push(record);
					if (records.length === resultLimit) break;
				}
			}
			const last = rows.at(-1);
			if (!requiresPostFilter || rows.length < OPERATOR_APPROVAL_PENDING_SCAN_PAGE_SIZE || !last) break;
			cursor = {
				createdAtMs: last.created_at_ms,
				id: last.approval_id
			};
		}
		return records;
	}, params.databaseOptions);
}
function listTerminalOperatorApprovals(params = {}) {
	const requestedLimit = Number.isSafeInteger(params.limit) ? params.limit ?? OPERATOR_APPROVAL_HISTORY_DEFAULT_LIMIT : OPERATOR_APPROVAL_HISTORY_DEFAULT_LIMIT;
	const resultLimit = Math.max(1, Math.min(requestedLimit, OPERATOR_APPROVAL_HISTORY_MAX_LIMIT));
	const retentionCutoffMs = (params.nowMs ?? Date.now()) - OPERATOR_APPROVAL_TERMINAL_RETENTION_MS;
	let cursor = params.cursor === void 0 ? void 0 : decodeOperatorApprovalHistoryCursor(params.cursor);
	const database = openOpenClawStateDatabase(params.databaseOptions);
	const stateDb = getNodeSqliteKysely(database.db);
	const records = [];
	const pageSize = resultLimit + 1;
	while (records.length < pageSize) {
		const batchLimit = pageSize - records.length;
		let query = stateDb.selectFrom("operator_approvals").selectAll().where("status", "!=", "pending").where("resolved_at_ms", "is not", null).where("resolved_at_ms", ">=", retentionCutoffMs).orderBy("resolved_at_ms", "desc").orderBy("approval_id", "desc").limit(batchLimit);
		if (params.kind) query = query.where("kind", "=", params.kind);
		if (cursor) {
			const pageCursor = cursor;
			query = query.where((eb) => eb.or([eb("resolved_at_ms", "<", pageCursor.resolvedAtMs), eb.and([eb("resolved_at_ms", "=", pageCursor.resolvedAtMs), eb("approval_id", "<", pageCursor.id)])]));
		}
		const rows = executeSqliteQuerySync(database.db, query).rows;
		for (const row of rows) {
			const record = decodeOperatorApprovalRow(row);
			if (record) records.push(record);
		}
		const last = rows.at(-1);
		if (rows.length < batchLimit || !last || last.resolved_at_ms === null) break;
		cursor = {
			resolvedAtMs: last.resolved_at_ms,
			id: last.approval_id
		};
	}
	const page = records.slice(0, resultLimit);
	const last = page.at(-1);
	return {
		records: page,
		...records.length > resultLimit && last && last.resolvedAtMs !== null ? { nextCursor: encodeOperatorApprovalHistoryCursor({
			resolvedAtMs: last.resolvedAtMs,
			id: last.id
		}) } : {}
	};
}
function resolveOperatorApproval(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 forceDenyOperatorApproval(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 expireDueOperatorApprovals(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 result = executeSqliteQuerySync(database.db, stateDb.updateTable("operator_approvals").set({
			status: "expired",
			decision: "deny",
			terminal_reason: "timeout",
			resolved_at_ms: nowMs,
			resolver_kind: "system",
			resolver_id: null,
			updated_at_ms: nowMs
		}).where("status", "=", "pending").where("expires_at_ms", "<=", nowMs));
		const terminalRows = [];
		for (const row of dueRows) terminalRows.push({
			...row,
			status: "expired",
			decision: "deny",
			terminal_reason: "timeout",
			resolved_at_ms: nowMs,
			resolver_kind: "system",
			resolver_id: null,
			updated_at_ms: nowMs
		});
		return {
			affected: Number(result.numAffectedRows ?? 0n),
			records: terminalRows.map((row) => decodeOperatorApprovalRow(row)).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 result = executeSqliteQuerySync(database.db, stateDb.updateTable("operator_approvals").set({
				status: "cancelled",
				decision: "deny",
				terminal_reason: "gateway-restart",
				resolved_at_ms: auditTimestampMs,
				resolver_kind: "system",
				resolver_id: null,
				updated_at_ms: auditTimestampMs
			}).where("approval_id", "=", row.approval_id).where("status", "=", "pending"));
			const rowAffected = Number(result.numAffectedRows ?? 0n);
			affected += rowAffected;
			if (rowAffected === 1) terminalRows.push({
				...row,
				status: "cancelled",
				decision: "deny",
				terminal_reason: "gateway-restart",
				resolved_at_ms: auditTimestampMs,
				resolver_kind: "system",
				resolver_id: null,
				updated_at_ms: auditTimestampMs
			});
		}
		return {
			affected,
			records: terminalRows.map((row) => decodeOperatorApprovalRow(row)).filter((record) => record !== null)
		};
	}, params.databaseOptions);
}
function consumeOperatorApprovalAllowOnce(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 ?? OPERATOR_APPROVAL_TERMINAL_RETENTION_MS;
	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 { forceDenyOperatorApproval as a, insertOperatorApproval as c, pageOperatorApprovalReceiptsForRun as d, pruneTerminalOperatorApprovals as f, expireDueOperatorApprovals as i, listPendingOperatorApprovals as l, summarizeOperatorApprovalReceiptsForRun as m, closeOrphanedOperatorApprovals as n, getOperatorApprovalDetailed as o, resolveOperatorApproval as p, consumeOperatorApprovalAllowOnce as r, hasOperatorApprovalReceiptsForRun as s, OperatorApprovalHistoryCursorError as t, listTerminalOperatorApprovals as u };