How must an instance bound cascading approval requests from adversarial escalation?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session operates in an approval-based workflow, and an adversary can trigger cascading approval requests (each request requiring approval, each approval request triggering more requests), causing the approval path to be overwhelmed by volume rather than by importance. Does not apply to sessions with unlimited approval capacity or approval workflows that are inherently bounded.

Core Distinction

The separation between **approval necessity** (each approval request is individually justified) and **approval cascade** (the aggregate of justified requests overwhelms the approval mechanism). Relying on necessity creates the **Approval Flood**: each request is legitimate, so all are granted or escalated, but the aggregate volume is adversarial, and the session's approval mechanism becomes the attack surface.

What Was Observed

In approval-based agent architectures, failing to bound cascades produces three primary failure modes:

  1. **Approval Storm:** The adversary triggers many legitimate-looking requests, each requiring approval, and the approval queue expands faster than approvals can be processed. The session's critical approvals are delayed behind the flood.
  2. **Escalation Chaining:** Each approval request triggers a sub-approval (for the resources needed), which triggers another sub-approval, and the chain of approvals exhausts the session's approval depth.
  3. **Urgency Depletion:** The adversary adds urgency signals to some requests, and the session's attention is captured by these, even though their individual urgency doesn't justify the aggregate escalation.

Recorded Elements

  1. **`approval-request-attribution-receipt`:** For each approval request, the originating principal, so cascades from specific principals can be detected.
  2. **`approval-cascade-signature`:** For each principal, the rate of approval requests and the escalation pattern (each request triggers more requests), so cascade is measurable.
  3. **`approval-priority-receipt`:** For each request, the priority based on the request's actual importance, so adversarial volume is distinguished from legitimate urgency.
  4. **`cascade-breaker-signature`:** For cascading principals, the exact intervention (rate limit, batch approval, or requirement of grouped justification), so the cascade is bounded.

Condition of Validity

* **Applies when:** The session operates in an approval workflow, adversaries can trigger approval requests, and approval volume can overwhelm the mechanism. * **Does not apply when:** Approval capacity is unlimited, approvals are inherently bounded, or adversaries cannot trigger approval requests.

Decision Boundary

  1. **Are approval requests coming from multiple principals?** If no, single-principal approval applies. If yes, proceed.
  2. **Does `approval-cascade-signature` show cascade patterns?** If yes, cascade intervention is required. If no, standard approval processing.
  3. **Is `approval-priority-receipt` distinguishing priority from urgency?** If yes, priorities are principled. If no, urgency may dominate.
  4. **Conversely, when can a session process approvals without cascade detection?** Only when approval capacity is verified sufficient for any possible cascade (unbounded), or approvals are strictly batched (no individual requests).

Operational Effect

  1. **Approval Storm Prevention:** Cascading principals are rate-limited.
  2. **Escalation Chaining Prevention:** Chains are bounded, not recursive.
  3. **Urgency Integrity:** Priorities are based on actual importance, not urgency signals.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class ApprovalVerdict(Enum):
    NO_REQUESTS = auto()
    SINGLE_REQUEST = auto()
    CASCADE_DETECTED = auto()
    ESCALATION_CHAIN_DETECTED = auto()

@dataclass(frozen=True)
class ApprovalRequest:
    request_id: str
    principal_id: str
    is_cascaded_from: str | None
    urgency_signal: float
    actual_importance: float
    cascade_rate_for_principal: float  # requests/sec from this principal

@dataclass(frozen=True)
class ApprovalPolicy:
    max_acceptable_cascade_rate: float
    require_importance_for_high_urgency: float

@dataclass(frozen=True)
class ApprovalManifest:
    verdict: ApprovalVerdict
    directive: str
    manifest_digest: str

class ApprovalCascadeGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_request(
        cls, req: ApprovalRequest, policy: ApprovalPolicy
    ) -> ApprovalManifest:
        
        # Gate 1: No request
        if not req.request_id:
            digest = cls._hash(json.dumps({
                "request": "NONE"
            }, sort_keys=True))
            return ApprovalManifest(
                verdict=ApprovalVerdict.NO_REQUESTS,
                directive=f"PROCEED: No approval request.",
                manifest_digest=digest
            )

        # Gate 2: Escalation chain
        if req.is_cascaded_from:
            return ApprovalManifest(
                verdict=ApprovalVerdict.ESCALATION_CHAIN_DETECTED,
                directive=f"FLAG: Request {req.request_id} cascaded from {req.is_cascaded_from}.",
                manifest_digest=cls._hash(f"CHAIN:{req.request_id}")
            )

        # Gate 3: Cascade rate
        if req.cascade_rate_for_principal > policy.max_acceptable_cascade_rate:
            return ApprovalManifest(
                verdict=ApprovalVerdict.CASCADE_DETECTED,
                directive=f"FLAG: Principal {req.principal_id} cascade rate {req.cascade_rate_for_principal:.2f} > {policy.max_acceptable_cascade_rate:.2f}.",
                manifest_digest=cls._hash(f"CASCADE:{req.principal_id}")
            )

        # Gate 4: Urgency vs importance
        if req.urgency_signal > 0.8 and req.actual_importance < policy.require_importance_for_high_urgency:
            return ApprovalManifest(
                verdict=ApprovalVerdict.CASCADE_DETECTED,
                directive=f"FLAG: Request {req.request_id} high urgency ({req.urgency_signal:.2f}) low importance ({req.actual_importance:.2f}).",
                manifest_digest=cls._hash(f"URGENCY:{req.request_id}")
            )

        # Gate 5: Single request
        digest = cls._hash(json.dumps({
            "request": req.request_id,
            "principal": req.principal_id,
            "importance": f"{req.actual_importance:.3f}"
        }, sort_keys=True))
        
        return ApprovalManifest(
            verdict=ApprovalVerdict.SINGLE_REQUEST,
            directive=f"PROCEED: Request {req.request_id} normal.",
            manifest_digest=digest
        )