编码母题 / P1 / Agent 运行时 / 05

支持权限与人工审批的工具执行器

先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。

重要程度高价值难度困难预计完成50–70 分钟

难度按 LeetCode 简单/中等/困难三档估计;工程题并非 LeetCode 官方题目。用时指独立编码并检查主要边界的参考时间,因经验而异。

题目是什么

工具按权限和风险等级执行。高风险调用先产生审批单;批准后仅允许原参数、未过期、具备权限的一次执行。

输入输出示例

批准了参数 A 后改成参数 B,恢复执行必须拒绝。

练习起手式与验证

复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。

/*
 * 题目:支持权限与人工审批的工具执行器
 * 要求:工具按权限和风险等级执行。高风险调用先产生审批单;批准后仅允许原参数、未过期、
 * 具备权限的一次执行。
 * 示例:批准了参数 A 后改成参数 B,恢复执行必须拒绝。
 */
const stable = (value) =>
  value && typeof value === "object"
    ? Array.isArray(value)
      ? `[${value.map(stable)}]`
      : `{${Object.keys(value)
          .sort()
          .map((k) => `${JSON.stringify(k)}:${stable(value[k])}`)
          .join(",")}}`
    : JSON.stringify(value);
class ApprovalStore {
  constructor() {}
  create(call, expiresAt) {}
  decide(id, approved) {}
  consume(id, call, now) {}
}
class ApprovedToolExecutor {
  constructor(registry, approvals, now = Date.now) {}
  async execute(call, context) {}
}

(async () => {
  let effects = 0;
  const registry = new Map([
    [
      "pay",
      { risk: "high", requiredPermissions: ["pay"], execute: () => ++effects },
    ],
  ]);
  const store = new ApprovalStore();
  const executor = new ApprovedToolExecutor(registry, store, () => 100);
  const call = { id: "t1", name: "pay", arguments: { amount: 10 } };
  const pending = await executor.execute(call, { permissions: ["pay"] });
  console.log(pending.status); // 预期:"waiting_approval"
  store.decide(pending.approvalId, true);
  console.log((
      await executor.execute(call, {
        permissions: ["pay"],
        approvalId: pending.approvalId,
      })
    ).output); // 预期:1
  console.log(await Promise.resolve(executor.execute(call, {
      permissions: ["pay"],
      approvalId: pending.approvalId,
    })).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);

解题思路

展开思路、不变量与常见边界

关键切入点:审批绑定完整调用摘要;consume 原子标记防重复,并在恢复时重新校验权限。

核心模型:审批绑定 toolCall 的稳定摘要,而不是只绑定工具名;恢复时重新校验权限 和摘要。状态 approved 与 executed 分离,并以 consume 原子操作防止重复副作用。 本例使用稳定 JSON 序列化与内存仓库,生产环境需数据库事务、强哈希和审计身份。

参考代码与验证

“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。

const stable = (value) =>
  value && typeof value === "object"
    ? Array.isArray(value)
      ? `[${value.map(stable)}]`
      : `{${Object.keys(value)
          .sort()
          .map((k) => `${JSON.stringify(k)}:${stable(value[k])}`)
          .join(",")}}`
    : JSON.stringify(value);
// 关键机制:审批绑定完整调用摘要;consume 原子标记防重复,并在恢复时重新校验权限。
class ApprovalStore {
  constructor() {
    this.items = new Map();
    this.sequence = 0;
  }
  create(call, expiresAt) {
    const item = {
      id: `a${++this.sequence}`,
      digest: stable(call),
      status: "pending",
      expiresAt,
    };
    this.items.set(item.id, item);
    return { ...item };
  }
  decide(id, approved) {
    const item = this.items.get(id);
    if (!item || item.status !== "pending")
      throw new Error("approval unavailable");
    item.status = approved ? "approved" : "rejected";
  }
  consume(id, call, now) {
    const item = this.items.get(id);
    if (!item || item.status !== "approved")
      throw new Error("approval not granted");
    if (item.expiresAt <= now) {
      item.status = "expired";
      throw new Error("approval expired");
    }
    if (item.digest !== stable(call))
      throw new Error("approved call was changed");
    item.status = "executed";
  }
}
class ApprovedToolExecutor {
  constructor(registry, approvals, now = Date.now) {
    this.registry = registry;
    this.approvals = approvals;
    this.now = now;
  }
  async execute(call, context) {
    const tool = this.registry.get(call.name);
    if (!tool) return { status: "error", code: "TOOL_NOT_FOUND" };
    const permissions = new Set(context.permissions || []);
    if ((tool.requiredPermissions || []).some((p) => !permissions.has(p)))
      return { status: "error", code: "FORBIDDEN" };
    if (tool.risk === "high" && !context.approvalId) {
      const approval = this.approvals.create(
        call,
        this.now() + (context.approvalTtl || 300000),
      );
      return { status: "waiting_approval", approvalId: approval.id };
    }
    if (tool.risk === "high")
      this.approvals.consume(context.approvalId, call, this.now());
    return {
      status: "completed",
      output: await tool.execute(call.arguments, context),
    };
  }
}

// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
  let effects = 0;
  const registry = new Map([
    [
      "pay",
      { risk: "high", requiredPermissions: ["pay"], execute: () => ++effects },
    ],
  ]);
  const store = new ApprovalStore();
  const executor = new ApprovedToolExecutor(registry, store, () => 100);
  const call = { id: "t1", name: "pay", arguments: { amount: 10 } };
  const pending = await executor.execute(call, { permissions: ["pay"] });
  console.log(pending.status); // 预期:"waiting_approval"
  store.decide(pending.approvalId, true);
  console.log((
      await executor.execute(call, {
        permissions: ["pay"],
        approvalId: pending.approvalId,
      })
    ).output); // 预期:1
  console.log(await Promise.resolve(executor.execute(call, {
      permissions: ["pay"],
      approvalId: pending.approvalId,
    })).then(() => false, () => true)); // 预期:true(拒绝)
  console.log(effects); // 预期:1


})().catch(console.error);

复杂度分析

时间复杂度:O(P log P)/审批校验。稳定摘要需遍历参数并对对象键排序;执行工具耗时另计。

空间复杂度:O(A+P)。保存 A 张审批单,并为当前参数构造至多 P 大小的摘要。

下载原始 JS 文件