# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-resolve-a-skill-dependency-before-signature-verification-completes.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-resolve-a-skill-dependency-before-signature-verification-completes.warrant.json
# Markdown SHA-256:   5960b37a3d2c9c48defe1cd7e2be31aefef75a7dde6f0c10c3fe5b7759602be4
# Code AST SHA-256:   d596a4edfe5356714c7ddb1c0dac152977a5570d3db4afb552153d3e781323dc
# Verified Classes:   ResolutionOrderViolation, SkillNode
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class ResolutionOrderViolation(RuntimeError):
    """Raised when an edge is used to direct resolution before its origin is verified."""
    pass


class SkillNode:
    def __init__(self, name, version, signature_valid, requires):
        self.name = name
        self.version = version
        self.signature_valid = signature_valid
        self.requires = requires  # list of dicts: {"name", "range", "source"}


def resolve_graph(root, fetch, verify):
    """
    Verify-before-expand resolver.

    `fetch(name, version_range, source)` returns a SkillNode or None.
    `verify(node)` returns True iff the node's signature is valid.
    """
    resolved = {}
    queue = [root]

    while queue:
        node = queue.pop(0)

        # Gate: never read `requires` from an unverified node.
        if not verify(node):
            # Discard the node AND every edge it would have contributed.
            # Do not enqueue its requires; do not touch the cache under
            # any key it might have influenced.
            continue

        resolved[(node.name, node.version)] = node

        for edge in node.requires:
            origin = (node.name, node.version)
            if not node.signature_valid:
                # Defensive: verify() already gated this, but if a caller
                # bypasses verify, refuse rather than resolve.
                raise ResolutionOrderViolation(
                    f"edge {edge['name']}@{edge['range']} from unverified "
                    f"origin {origin} attempted to direct resolution"
                )

            key = (edge["name"], edge["range"], edge.get("source"))
            if key in resolved:
                continue

            child = fetch(edge["name"], edge["range"], edge.get("source"))
            if child is None:
                raise ResolutionOrderViolation(
                    f"unresolvable edge {edge['name']}@{edge['range']} "
                    f"declared by verified origin {origin}"
                )

            # Cache key includes provenance; a later rejection of `child`
            # cannot leave a poisoned entry under a trusted name.
            if not verify(child):
                # Child rejected; its own requires are never read.
                continue

            queue.append(child)

    return resolved
