{"version":3,"file":"policy.mjs","names":[],"sources":["../../../../../../../ai/src/human/policy.ts"],"sourcesContent":["import type { InterruptPolicy, PolicyContext } from \"./contracts\";\n\n/**\n * Verdict of evaluating an {@link InterruptPolicy} against a single\n * pending tool call.\n *\n * - `requiresApproval` — `true` when the call must be routed to a human\n *   before the real tool runs; `false` when the policy lets it through\n *   untouched.\n * - `tags` — author-supplied labels from the matched rule (e.g.\n *   `\"destructive\"`, `\"money\"`), surfaced verbatim on the resulting\n *   `ApprovalRequest.context.tags`. Only ever present when\n *   `requiresApproval` is `true`; `undefined` when the rule produced no\n *   tags.\n */\nexport interface PolicyVerdict {\n  /** Whether this tool call must be approved by a human. */\n  requiresApproval: boolean;\n\n  /** Author-supplied tags from the matched rule, when any. */\n  tags?: string[];\n}\n\n/**\n * A verdict that lets a call through untouched. Frozen and shared so the\n * (common) skip path allocates nothing.\n */\nconst SKIP: PolicyVerdict = Object.freeze({ requiresApproval: false });\n\n/**\n * Normalize an author-supplied tags array into the verdict shape — an\n * empty array is treated as \"no tags\" so callers never have to\n * distinguish `[]` from `undefined`.\n */\nfunction withTags(tags: string[] | undefined): PolicyVerdict {\n  if (tags === undefined || tags.length === 0) {\n    return { requiresApproval: true };\n  }\n\n  return { requiresApproval: true, tags };\n}\n\n/**\n * Decide whether a single pending tool call requires human approval —\n * the pure core behind the `ai.human.approval` middleware's gate.\n *\n * **Pure.** No IO, no throwing, no mutation of `policy` or `context`. The\n * middleware calls this once per tool dispatch and routes to a human only\n * when {@link PolicyVerdict.requiresApproval} is `true`.\n *\n * **The three rule types** ({@link InterruptPolicy}):\n * - `allowlist` — gate the call **only** when its tool name is listed; an\n *   optional `tags(toolName)` callback derives the verdict tags.\n * - `denylist` — gate **every** call **except** the listed tool names;\n *   the same optional `tags(toolName)` callback applies to the gated\n *   (non-listed) name.\n * - `predicate` — gate the call when `requiresApproval(context)` returns a\n *   truthy result. A non-empty `string[]` both gates the call **and**\n *   supplies the verdict tags; `true` gates with no tags; `false` (or an\n *   **empty** array — \"no rule matched\") lets the call through.\n *\n * @param policy - The interrupt policy to evaluate.\n * @param context - The read-only view of the pending tool call.\n * @returns A {@link PolicyVerdict} — gate-or-skip plus any tags.\n *\n * @example\n * const verdict = evaluatePolicy(\n *   { type: \"allowlist\", tools: [\"refundCustomer\"], tags: () => [\"money\"] },\n *   { toolName: \"refundCustomer\", args: { amount: 50 }, agentName: \"support\", tripIndex: 0 },\n * );\n * // → { requiresApproval: true, tags: [\"money\"] }\n */\nexport function evaluatePolicy(\n  policy: InterruptPolicy,\n  context: PolicyContext,\n): PolicyVerdict {\n  if (policy.type === \"allowlist\") {\n    if (!policy.tools.includes(context.toolName)) {\n      return SKIP;\n    }\n\n    return withTags(policy.tags?.(context.toolName));\n  }\n\n  if (policy.type === \"denylist\") {\n    if (policy.tools.includes(context.toolName)) {\n      return SKIP;\n    }\n\n    return withTags(policy.tags?.(context.toolName));\n  }\n\n  // Predicate: a truthy result gates the call; a `string[]` doubles as the\n  // verdict tags.\n  const outcome = policy.requiresApproval(context);\n\n  if (outcome === false) {\n    return SKIP;\n  }\n\n  if (outcome === true) {\n    return { requiresApproval: true };\n  }\n\n  // `outcome` is a `string[]`. Per the contract, an EMPTY array means \"no\n  // rule matched\" and skips approval; a non-empty array gates the call and\n  // doubles as the verdict tags.\n  if (outcome.length === 0) {\n    return SKIP;\n  }\n\n  return withTags(outcome);\n}\n"],"mappings":";;;;;AA2BA,MAAM,OAAsB,OAAO,OAAO,EAAE,kBAAkB,MAAM,CAAC;;;;;;AAOrE,SAAS,SAAS,MAA2C;CAC3D,IAAI,SAAS,UAAa,KAAK,WAAW,GACxC,OAAO,EAAE,kBAAkB,KAAK;CAGlC,OAAO;EAAE,kBAAkB;EAAM;CAAK;AACxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,eACd,QACA,SACe;CACf,IAAI,OAAO,SAAS,aAAa;EAC/B,IAAI,CAAC,OAAO,MAAM,SAAS,QAAQ,QAAQ,GACzC,OAAO;EAGT,OAAO,SAAS,OAAO,OAAO,QAAQ,QAAQ,CAAC;CACjD;CAEA,IAAI,OAAO,SAAS,YAAY;EAC9B,IAAI,OAAO,MAAM,SAAS,QAAQ,QAAQ,GACxC,OAAO;EAGT,OAAO,SAAS,OAAO,OAAO,QAAQ,QAAQ,CAAC;CACjD;CAIA,MAAM,UAAU,OAAO,iBAAiB,OAAO;CAE/C,IAAI,YAAY,OACd,OAAO;CAGT,IAAI,YAAY,MACd,OAAO,EAAE,kBAAkB,KAAK;CAMlC,IAAI,QAAQ,WAAW,GACrB,OAAO;CAGT,OAAO,SAAS,OAAO;AACzB"}