{"record":{"id":"31bb7474ee38d234","repo":"ruvnet/ruflo","slug":"policy-decision-enforcedoutcome-decision-reas","errorCode":null,"errorMessage":"policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId}","messagePattern":"policy-(.+?):(.+?); receipt=(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-client.ts","lineNumber":259,"sourceCode":"  // Look up tool in registry\n  const tool = TOOL_REGISTRY.get(toolName);\n\n  if (!tool) {\n    throw new MCPClientError(\n      `MCP tool not found: ${toolName}`,\n      toolName\n    );\n  }\n\n  try {\n    // ADR-324: one policy chokepoint for every local CLI/MCP invocation.\n    // Policy administration is not exempt: authorization calls the engine\n    // directly, so there is no recursive MCP dispatch. In enforce mode an\n    // administrator must explicitly allow policy.* actions or use the local\n    // CLI bootstrap path.\n    const decision = await authorizeMcpTool(toolName, input, context, classifyMcpTool(toolName));\n    if (decision.enforcedOutcome !== 'allowed') {\n      throw new Error(`policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId}`);\n    }\n    // Call the tool handler\n    const result = await tool.handler(input, context);\n    // ADR-146 P2: scan every tool result for indirect-injection before it\n    // returns to the caller. The screen is opt-in via env (default off in\n    // 3.10.34 — flip to default in v4) so existing pipelines keep their\n    // exact behaviour while the call site is exercised by tests and\n    // adopters. Telemetry from the screen lands in the shared\n    // GuardrailEvent sink (P5).\n    return applyContentBoundaryGuardrail(toolName, result) as T;\n  } catch (error) {\n    // Wrap and re-throw with context\n    throw new MCPClientError(\n      `Failed to execute MCP tool '${toolName}': ${error instanceof Error ? error.message : String(error)}`,\n      toolName,\n      error instanceof Error ? error : undefined\n    );\n  }","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/mcp-client.ts#L241-L277","documentation":"Thrown when ADR-324's authorizeMcpTool() returns a decision whose enforcedOutcome is not 'allowed' (e.g. denied, quarantined, require-approval). This is the single policy chokepoint on the local CLI/MCP dispatch path — every tool call passes through it in enforce mode. The message embeds the outcome, the human-readable reason, and a receiptId the operator can use to audit the decision in the policy log.","triggerScenarios":"Running with policy mode set to 'enforce' (not 'observe' or 'legacy') and invoking a tool whose action is not in the allow-list; calling a policy.* tool from the MCP path without the administrator bootstrap; a deny rule in the loaded policy bundle matching the tool name, input shape, or classification.","commonSituations":"A new policy bundle shipped with a stricter deny list; a tool was reclassified (e.g. from network-egress to filesystem-write) and now trips a different rule; an operator switched policy mode from legacy to enforce without pre-allowing the actions their pipeline uses; CI running under enforce with a stale allow-list.","solutions":["Look up the receiptId in the policy log / GuardrailEvent sink to see which rule fired and why.","Run `npx ruflo policy status` and `npx ruflo policy verify` to see the active mode and the deny rules.","Switch policy mode to 'observe' while building the allow-list: set the policy mode in your config (this logs without denying).","Explicitly allow the action via the local CLI bootstrap path, or grant the policy.<action> claim for the calling principal.","If the rule is wrong, edit the policy bundle and re-verify before switching back to 'enforce'."],"exampleFix":"// before — call denied under enforce\nawait callMCPTool('file_write', { path: '/etc/x' });\n// after — operator grants the claim, or you route via the bootstrap path\nawait runViaLocalCliBootstrap('file_write', { path: '/etc/x' });","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  await callMCPTool(toolName, input, ctx);\n} catch (e) {\n  const m = String(e?.message ?? '').match(/^policy-(?<outcome>[^:]+):(?<reason>[^;]*); receipt=(?<rid>[^\\s]+)/);\n  if (m) {\n    // log the receipt for audit, decide on fallback vs surface\n    auditPolicyDenial(m.groups!.outcome, m.groups!.reason, m.groups!.rid);\n  }\n  throw e;\n}","preventionTips":["Run policy in 'observe' mode first and review the deny log before switching to 'enforce'.","Pre-allow the actions your pipeline uses (policy.<action> claims) in the bundle.","Treat the receiptId as required context in your error-reporting pipeline."],"tags":["policy","adr-324","authorization","mcp","security"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}