{"version":3,"file":"policy-evaluator.mjs","names":[],"sources":["../../../src/services/policy-evaluator.ts"],"sourcesContent":["/**\n * Policy Evaluator — Runtime enforcement of user-defined policies.\n *\n * Checks an ActionContext against all active policies for a user.\n * Returns the most restrictive decision: block > confirm > allow.\n *\n * Also handles:\n * - Usage recording after successful tool execution\n * - Extracting ActionContext from tool args (best-effort)\n * - Rendering policy displays for user verification\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { getPolicyStore } from './policy-store.js';\nimport {\n  type ActionContext,\n  type Policy,\n  type PolicyAction,\n  type PolicyDecision,\n  type PolicyDisplay,\n  type PolicyRule,\n  type PolicyScope,\n  type UsageEntry,\n  TOOL_CATEGORIES,\n  TOOL_TO_CATEGORY,\n  periodToMs,\n  describeRule,\n  describeScope,\n} from './policy-types.js';\n\n// ─── Evaluation ─────────────────────────────────────────────────────────\n\n/** Priority: block > confirm > allow. */\nconst ACTION_PRIORITY: Record<PolicyAction, number> = {\n  block: 3,\n  confirm: 2,\n  allow: 1,\n};\n\n/**\n * Evaluate all active policies for a user against a proposed action.\n * Returns the most restrictive decision across all matching policies.\n */\nexport function evaluatePolicies(ctx: ActionContext): PolicyDecision {\n  const store = getPolicyStore();\n\n  // Fail-closed: if the policy store is corrupted, block everything\n  if (store.isCorrupted(ctx.userId)) {\n    return {\n      action: 'block',\n      reason: 'Policy store is corrupted — all write operations are blocked for safety. Check ~/.openclawnch/policies/ for .corrupt files and restore or delete them.',\n    };\n  }\n\n  const policies = store.getActivePolicies(ctx.userId);\n\n  if (policies.length === 0) {\n    return { action: 'allow' };\n  }\n\n  let worst: PolicyDecision = { action: 'allow' };\n\n  for (const policy of policies) {\n    if (!policyApplies(policy, ctx.toolName)) continue;\n\n    for (const rule of policy.rules) {\n      const decision = evaluateRule(rule, ctx, policy);\n      if (ACTION_PRIORITY[decision.action] > ACTION_PRIORITY[worst.action]) {\n        worst = decision;\n      }\n      // Short-circuit: can't get worse than block\n      if (worst.action === 'block') return worst;\n    }\n  }\n\n  return worst;\n}\n\n/** Check if a policy's scope covers a given tool. */\nfunction policyApplies(policy: Policy, toolName: string): boolean {\n  const { scope } = policy;\n  switch (scope.type) {\n    case 'all_write':\n      return true;\n    case 'tools':\n      return (scope.tools ?? []).includes(toolName);\n    case 'categories': {\n      const toolCategory = TOOL_TO_CATEGORY[toolName];\n      if (!toolCategory) return false;\n      return (scope.categories ?? []).includes(toolCategory);\n    }\n  }\n}\n\n/** Evaluate a single rule against an action context. */\nfunction evaluateRule(\n  rule: PolicyRule,\n  ctx: ActionContext,\n  policy: Policy,\n): PolicyDecision {\n  const base = { policyId: policy.id, policyName: policy.name };\n\n  switch (rule.type) {\n    case 'spending_limit': {\n      if (ctx.amountUsd == null) {\n        // Unknown amount — require confirmation rather than guessing\n        return {\n          ...base,\n          action: 'confirm',\n          reason: `Policy \"${policy.name}\" has a spending limit but the USD value of this action is unknown. Confirm to proceed.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      const windowMs = periodToMs(rule.period);\n      const store = getPolicyStore();\n      const spent = store.getSpendInWindow(ctx.userId, policy.id, windowMs);\n      const remaining = rule.maxAmountUsd - spent;\n      if (ctx.amountUsd > remaining) {\n        return {\n          ...base,\n          action: 'block',\n          reason: `Policy \"${policy.name}\": spending limit $${rule.maxAmountUsd}/${rule.period}. Already spent $${spent.toFixed(2)}, remaining $${remaining.toFixed(2)}. This action ($${ctx.amountUsd.toFixed(2)}) would exceed the limit.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      return { action: 'allow' };\n    }\n\n    case 'rate_limit': {\n      const store = getPolicyStore();\n      const calls = store.getCallsInWindow(ctx.userId, policy.id, rule.periodMs);\n      if (calls >= rule.maxCalls) {\n        return {\n          ...base,\n          action: 'block',\n          reason: `Policy \"${policy.name}\": rate limit ${rule.maxCalls} calls reached. Wait for the window to reset.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      return { action: 'allow' };\n    }\n\n    case 'allowlist': {\n      const value = getFieldValue(rule.field, ctx);\n      if (value == null) {\n        return {\n          ...base,\n          action: 'confirm',\n          reason: `Policy \"${policy.name}\": cannot determine ${rule.field} for allowlist check. Confirm to proceed.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      const allowed = rule.values.map(v => v.toLowerCase());\n      if (!allowed.includes(value.toLowerCase())) {\n        return {\n          ...base,\n          action: 'block',\n          reason: `Policy \"${policy.name}\": ${rule.field} \"${value}\" is not in the allowlist [${rule.values.join(', ')}].`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      return { action: 'allow' };\n    }\n\n    case 'blocklist': {\n      const value = getFieldValue(rule.field, ctx);\n      if (value == null) {\n        return {\n          ...base,\n          action: 'confirm',\n          reason: `Policy \"${policy.name}\": cannot determine ${rule.field} for blocklist check. Confirm to proceed.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      const blocked = rule.values.map(v => v.toLowerCase());\n      if (blocked.includes(value.toLowerCase())) {\n        return {\n          ...base,\n          action: 'block',\n          reason: `Policy \"${policy.name}\": ${rule.field} \"${value}\" is blocked [${rule.values.join(', ')}].`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      return { action: 'allow' };\n    }\n\n    case 'time_window': {\n      const tz = rule.timezone ?? 'UTC';\n      const now = new Date();\n      let hour: number;\n      let day: number;\n      try {\n        const parts = new Intl.DateTimeFormat('en-US', {\n          timeZone: tz,\n          hour: 'numeric',\n          hour12: false,\n          weekday: 'short',\n        }).formatToParts(now);\n        hour = parseInt(parts.find(p => p.type === 'hour')?.value ?? '0', 10);\n        const weekday = parts.find(p => p.type === 'weekday')?.value ?? 'Mon';\n        const dayMap: Record<string, number> = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };\n        day = dayMap[weekday] ?? 1;\n      } catch {\n        // Bad timezone — allow to avoid blocking on config error\n        return { action: 'allow' };\n      }\n\n      if (rule.allowedHours) {\n        const { start, end } = rule.allowedHours;\n        const inWindow = start <= end\n          ? (hour >= start && hour < end)\n          : (hour >= start || hour < end); // wraps midnight\n        if (!inWindow) {\n          return {\n            ...base,\n            action: 'block',\n            reason: `Policy \"${policy.name}\": outside allowed hours (${start}:00–${end}:00 ${tz}). Current hour: ${hour}:00.`,\n            ruleSummary: describeRule(rule),\n          };\n        }\n      }\n\n      if (rule.allowedDays && rule.allowedDays.length > 0) {\n        if (!rule.allowedDays.includes(day)) {\n          const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n          return {\n            ...base,\n            action: 'block',\n            reason: `Policy \"${policy.name}\": not an allowed day. Today: ${dayNames[day]}. Allowed: ${rule.allowedDays.map(d => dayNames[d]).join(', ')}.`,\n            ruleSummary: describeRule(rule),\n          };\n        }\n      }\n\n      return { action: 'allow' };\n    }\n\n    case 'approval_threshold': {\n      if (ctx.amountUsd == null) {\n        return {\n          ...base,\n          action: 'confirm',\n          reason: `Policy \"${policy.name}\" requires confirmation above $${rule.amountUsd}, but USD value is unknown. Confirm to proceed.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      if (ctx.amountUsd > rule.amountUsd) {\n        return {\n          ...base,\n          action: 'confirm',\n          reason: `Policy \"${policy.name}\": this action ($${ctx.amountUsd.toFixed(2)}) exceeds the $${rule.amountUsd} confirmation threshold.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      return { action: 'allow' };\n    }\n\n    case 'max_amount': {\n      if (ctx.amountUsd == null) {\n        return {\n          ...base,\n          action: 'confirm',\n          reason: `Policy \"${policy.name}\" blocks actions above $${rule.maxAmountUsd}, but USD value is unknown. Confirm to proceed.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      if (ctx.amountUsd > rule.maxAmountUsd) {\n        return {\n          ...base,\n          action: 'block',\n          reason: `Policy \"${policy.name}\": this action ($${ctx.amountUsd.toFixed(2)}) exceeds the hard limit of $${rule.maxAmountUsd}.`,\n          ruleSummary: describeRule(rule),\n        };\n      }\n      return { action: 'allow' };\n    }\n\n    case 'erc20_limit': {\n      // ERC-20 limits are enforced on-chain by ERC20TransferAmountEnforcer.\n      // At the app layer, allow the action — the caveat enforcer handles the cap.\n      return { action: 'allow' };\n    }\n  }\n}\n\n/** Extract a field value from ActionContext for allowlist/blocklist checks. */\nfunction getFieldValue(\n  field: 'tokens' | 'chains' | 'addresses' | 'contracts',\n  ctx: ActionContext,\n): string | undefined {\n  switch (field) {\n    case 'tokens':    return ctx.token;\n    case 'chains':    return ctx.chain?.toString();\n    case 'addresses': return ctx.toAddress;\n    case 'contracts': return ctx.toAddress; // contract = address for now\n  }\n}\n\n// ─── Usage Recording ────────────────────────────────────────────────────\n\n/**\n * Record a tool execution against all matching active policies.\n * Called AFTER successful execution (not on block/confirm).\n */\nexport function recordToolExecution(ctx: ActionContext): void {\n  const store = getPolicyStore();\n  const policies = store.getActivePolicies(ctx.userId);\n\n  const entry: UsageEntry = {\n    timestamp: Date.now(),\n    toolName: ctx.toolName,\n    action: ctx.action,\n    amountUsd: ctx.amountUsd,\n  };\n\n  for (const policy of policies) {\n    if (!policyApplies(policy, ctx.toolName)) continue;\n    store.recordUsage(ctx.userId, policy.id, entry);\n  }\n}\n\n// ─── User ID Extraction ─────────────────────────────────────────────────\n\n/**\n * Shared helper to extract userId from tool execution context.\n * Used by both the enforcement gate (index.ts) and the policy_manage tool.\n * Ensures consistent fallback chain everywhere.\n */\nexport function extractPolicyUserId(ctx?: any): string {\n  return ctx?.senderId ?? ctx?.from ?? ctx?.metadata?.senderId ?? 'owner';\n}\n\n// ─── Context Extraction ─────────────────────────────────────────────────\n\n/**\n * Best-effort extraction of ActionContext from tool args.\n * Different tools encode amounts, tokens, addresses differently.\n * Returns what we can determine; undefined fields are left out.\n */\nexport function extractActionContext(\n  toolName: string,\n  args: Record<string, unknown>,\n  userId: string,\n): ActionContext {\n  const ctx: ActionContext = { toolName, userId };\n\n  // Sub-action\n  if (typeof args.action === 'string') ctx.action = args.action;\n\n  // Token\n  if (typeof args.token === 'string') ctx.token = args.token;\n  else if (typeof args.tokenIn === 'string') ctx.token = args.tokenIn;\n  else if (typeof args.asset === 'string') ctx.token = args.asset;\n\n  // Destination address\n  if (typeof args.to === 'string') ctx.toAddress = args.to;\n  else if (typeof args.recipient === 'string') ctx.toAddress = args.recipient;\n  else if (typeof args.address === 'string') ctx.toAddress = args.address;\n\n  // Chain\n  if (typeof args.chain === 'number') ctx.chain = args.chain;\n  else if (typeof args.chainId === 'number') ctx.chain = args.chainId;\n  else if (typeof args.toChain === 'number') ctx.chain = args.toChain;\n\n  // Amount — we store raw, caller can convert to USD if price is available\n  // For fiat_payment the amount is already in fiat units\n  if (typeof args.amount === 'string') {\n    const num = parseFloat(args.amount);\n    if (!isNaN(num)) {\n      // fiat_payment amounts are already USD\n      if (toolName === 'fiat_payment') {\n        ctx.amountUsd = num;\n      }\n      // Other tools: amount is in token units, would need price lookup\n      // We leave amountUsd undefined — evaluator handles unknown amounts\n    }\n  }\n  if (typeof args.amountUsd === 'number') ctx.amountUsd = args.amountUsd;\n\n  return ctx;\n}\n\n// ─── Policy Display ─────────────────────────────────────────────────────\n\n/**\n * Build a full display object for user verification.\n * Shows both the original NL and the exact structured interpretation.\n */\nexport function buildPolicyDisplay(policy: Policy, userId: string): PolicyDisplay {\n  const store = getPolicyStore();\n  const ruleDescriptions = policy.rules.map(describeRule);\n  const scopeDescription = describeScope(policy.scope);\n\n  // Build usage summary for spending/rate limits\n  const usageParts: string[] = [];\n  for (const rule of policy.rules) {\n    if (rule.type === 'spending_limit') {\n      const windowMs = periodToMs(rule.period);\n      const spent = store.getSpendInWindow(userId, policy.id, windowMs);\n      usageParts.push(`Spent $${spent.toFixed(2)} of $${rule.maxAmountUsd} (${rule.period})`);\n    }\n    if (rule.type === 'rate_limit') {\n      const calls = store.getCallsInWindow(userId, policy.id, rule.periodMs);\n      usageParts.push(`${calls} of ${rule.maxCalls} calls used`);\n    }\n  }\n\n  return {\n    name: policy.name,\n    status: policy.status,\n    description: policy.description,\n    ruleDescriptions,\n    scopeDescription,\n    usageSummary: usageParts.length > 0 ? usageParts.join('; ') : undefined,\n  };\n}\n\n/**\n * Render a PolicyDisplay as a formatted text block for chat output.\n * Both the user's original words and the structured interpretation\n * are shown so there's no room for misunderstanding.\n */\nexport function renderPolicyDisplay(display: PolicyDisplay): string {\n  const lines: string[] = [];\n  const statusEmoji = display.status === 'active' ? '[ACTIVE]'\n    : display.status === 'draft' ? '[DRAFT]'\n    : '[DISABLED]';\n\n  lines.push(`**${display.name}** ${statusEmoji}`);\n  lines.push('');\n  lines.push(`You said: \"${display.description}\"`);\n  lines.push('');\n  lines.push('**What is enforced:**');\n  for (const rd of display.ruleDescriptions) {\n    lines.push(`  - ${rd}`);\n  }\n  lines.push(`  - Applies to: ${display.scopeDescription}`);\n  if (display.usageSummary) {\n    lines.push('');\n    lines.push(`**Current usage:** ${display.usageSummary}`);\n  }\n\n  return lines.join('\\n');\n}\n\n// ─── Confirmation Nonce System ──────────────────────────────────────────\n//\n// When a policy returns \"confirm\", a nonce is generated and included in the\n// response. The LLM must pass this nonce back as `policyConfirmationNonce` in\n// the tool args to prove the user actually confirmed. Nonces expire after TTL.\n\n/** Default TTL for confirmation nonces: 5 minutes. */\nconst NONCE_TTL_MS = 5 * 60 * 1000;\n\n/** Max nonces stored (prevent unbounded growth). Old entries are evicted. */\nconst MAX_NONCES = 500;\n\ninterface NonceEntry {\n  nonce: string;\n  userId: string;\n  toolName: string;\n  createdAt: number;\n}\n\nclass PolicyConfirmationStore {\n  private nonces = new Map<string, NonceEntry>();\n\n  /** Generate a nonce for a confirm decision. */\n  create(userId: string, toolName: string): string {\n    this.prune();\n    const nonce = randomUUID();\n    this.nonces.set(nonce, { nonce, userId, toolName, createdAt: Date.now() });\n    return nonce;\n  }\n\n  /** Validate and consume a nonce. Returns true if valid. */\n  consume(nonce: string, userId: string, toolName: string): boolean {\n    const entry = this.nonces.get(nonce);\n    if (!entry) return false;\n    this.nonces.delete(nonce);\n    // Check expiry\n    if (Date.now() - entry.createdAt > NONCE_TTL_MS) return false;\n    // Check user + tool match\n    if (entry.userId !== userId) return false;\n    if (entry.toolName !== toolName) return false;\n    return true;\n  }\n\n  /** Prune expired entries and enforce max size. */\n  private prune(): void {\n    const now = Date.now();\n    for (const [key, entry] of this.nonces) {\n      if (now - entry.createdAt > NONCE_TTL_MS) {\n        this.nonces.delete(key);\n      }\n    }\n    // Evict oldest if over limit\n    if (this.nonces.size > MAX_NONCES) {\n      const sorted = [...this.nonces.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);\n      const toRemove = sorted.slice(0, sorted.length - MAX_NONCES);\n      for (const [key] of toRemove) this.nonces.delete(key);\n    }\n  }\n\n  /** Clear all nonces (for testing). */\n  reset(): void {\n    this.nonces.clear();\n  }\n}\n\nlet _confirmStore: PolicyConfirmationStore | null = null;\n\nexport function getPolicyConfirmationStore(): PolicyConfirmationStore {\n  if (!_confirmStore) _confirmStore = new PolicyConfirmationStore();\n  return _confirmStore;\n}\n\nexport function resetPolicyConfirmationStore(): void {\n  _confirmStore?.reset();\n  _confirmStore = null;\n}\n\n/** Expand category names into tool lists for scope rendering. */\nexport function expandScope(scope: PolicyScope): string[] {\n  switch (scope.type) {\n    case 'all_write': {\n      const all: string[] = [];\n      for (const tools of Object.values(TOOL_CATEGORIES)) all.push(...tools);\n      return all;\n    }\n    case 'tools':\n      return scope.tools ?? [];\n    case 'categories': {\n      const result: string[] = [];\n      for (const cat of scope.categories ?? []) {\n        result.push(...(TOOL_CATEGORIES[cat] ?? []));\n      }\n      return result;\n    }\n    default:\n      return [];\n  }\n}\n"],"mappings":"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"}