# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-silent-scope-widening-in-aaif-scoped-delegation-chains-for-federated-multi-agent-task-orchestration.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-scope-widening-in-aaif-scoped-delegation-chains-for-federated-multi-agent-task-orchestration.warrant.json
# Markdown SHA-256:   ee79b22771e4fa1ca9e87658c911e6a0345c49b477dc8ffda0370fd5feb72253
# Code AST SHA-256:   57c047844b26d3c9cd56064f60d32cc140dfd1237e804c09ffc3772a7f00d035
# Verified Classes:   ScopeWideningError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class ScopeWideningError(RuntimeError):
    """Raised when a delegated scope is not provably a subset of its parent's scope."""
    pass


def pattern_subsumes(child: str, parent: str) -> bool:
    # Placeholder: real implementation must decide glob subsumption,
    # not string equality and not overlap. Raises if undecidable.
    if child == parent:
        return True
    # Example: parent "tenant/acme/*" subsumes child "tenant/acme/dataset/x"
    if parent.endswith("/*") and child.startswith(parent[:-1]):
        return True
    return False


def validate_delegation(parent_scope, child_scope, root_scope):
    for c_action, c_pattern, c_constraint in child_scope:
        matched = False
        for p_action, p_pattern, p_constraint in parent_scope:
            if c_action != p_action:
                continue
            if not pattern_subsumes(c_pattern, p_pattern):
                continue
            if not constraint_implies(c_constraint, p_constraint):
                continue
            matched = True
            break
        if not matched:
            raise ScopeWideningError(
                f"child triple ({c_action}, {c_pattern}) is not subsumed by parent scope"
            )
        # Root re-check: the child must also be subsumed by the original grant.
        root_ok = any(
            r_action == c_action
            and pattern_subsumes(c_pattern, r_pattern)
            and constraint_implies(c_constraint, r_constraint)
            for r_action, r_pattern, r_constraint in root_scope
        )
        if not root_ok:
            raise ScopeWideningError(
                f"child triple ({c_action}, {c_pattern}) widens beyond root scope"
            )
    return True


def constraint_implies(child, parent) -> bool:
    # Monotone constraints only; incomparable constraints are widening.
    if child.get("region") is not None and parent.get("region") is not None:
        if not set(child["region"]).issubset(set(parent["region"])):
            return False
    if child.get("budget", 0) > parent.get("budget", float("inf")):
        return False
    return True
