# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-commits-a-speculative-branch-whose-merge-precondition-was-satisfied-only-by-a-sibling-branch-that-later-rolls-back.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-commits-a-speculative-branch-whose-merge-precondition-was-satisfied-only-by-a-sibling-branch-that-later-rolls-back.warrant.json
# Markdown SHA-256:   19e183dd1f68ef6a74a7d0937170c3884d4a85825db8087c73eb65577903accb
# Code AST SHA-256:   01f26b15a8bf57e31ee05211cb20d3fa3f7d5337d356fce17d142d5a26a7a5d4
# Verified Classes:   DanglingDependencyError, StateStore
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class DanglingDependencyError(RuntimeError):
    """Raised when a merge precondition is satisfied only by speculative sibling state."""
    pass


class StateStore:
    def __init__(self):
        # key -> list of (branch_id, status, value)
        self._writes = {}

    def write(self, branch_id, status, key, value):
        self._writes.setdefault(key, []).append((branch_id, status, value))

    def rollback(self, branch_id):
        for key, entries in self._writes.items():
            self._writes[key] = [e for e in entries if e[0] != branch_id]

    def committed_value(self, key):
        for branch_id, status, value in reversed(self._writes.get(key, [])):
            if status == "committed":
                return value
        return None

    def speculative_provenance(self, key):
        return [b for b, status, _ in self._writes.get(key, []) if status == "speculative"]


def merge_gate(store, branch_id, key, expected):
    committed = store.committed_value(key)
    if committed == expected:
        return True
    speculative = store.speculative_provenance(key)
    if speculative:
        raise DanglingDependencyError(
            f"branch {branch_id} merge requires {key}={expected!r}, "
            f"but it is held only by speculative sibling(s) {speculative}; "
            f"commit or pin the sibling before merging"
        )
    return False


def rollback_branch(store, branch_id, pinned_by):
    if pinned_by.get(branch_id):
        raise DanglingDependencyError(
            f"cannot roll back {branch_id}: committed dependent(s) {pinned_by[branch_id]}"
        )
    store.rollback(branch_id)
