# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-delegated-credential-scope-exhaustion-in-aaif-before-the-upstream-verifier-returns-a-hard-denial.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-delegated-credential-scope-exhaustion-in-aaif-before-the-upstream-verifier-returns-a-hard-denial.warrant.json
# Markdown SHA-256:   f30088bea8fc9b72adbabb10628853fbcfaf9f38c6a19d44daded3d25a662ae6
# Code AST SHA-256:   30f3e51fd5c7485c0bb29ea46478334c50ad5df42892f04d0eed386532177963
# Verified Classes:   ScopeExhaustionHorizon, ScopeShadowDrift, DelegatedScope
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class ScopeExhaustionHorizon(RuntimeError):
    """Raised when a delegated credential is within one op of exhaustion."""


class ScopeShadowDrift(RuntimeError):
    """Raised when local accounting has drifted past verifier state."""


@dataclass(frozen=True)
class DelegatedScope:
    caveat_limit: int
    local_consumed: int
    inflight_presentations: int

    def residual_estimate(self, retry_budget: int) -> int:
        return (
            self.caveat_limit
            - self.local_consumed
            - self.inflight_presentations
            - retry_budget
        )


def assert_dispatchable(scope: DelegatedScope, retry_budget: int = 1) -> None:
    residual = scope.residual_estimate(retry_budget)
    if residual < 0:
        raise ScopeShadowDrift(
            f"local accounting exceeded verifier state: residual={residual} "
            f"limit={scope.caveat_limit} consumed={scope.local_consumed} "
            f"inflight={scope.inflight_presentations} retry_budget={retry_budget}"
        )
    if residual < 1:
        raise ScopeExhaustionHorizon(
            f"request would consume final unit of delegated scope: residual={residual} "
            f"limit={scope.caveat_limit} consumed={scope.local_consumed} "
            f"inflight={scope.inflight_presentations} retry_budget={retry_budget}; "
            f"escalate to parent for re-delegation before dispatch"
        )
