# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-non-idempotent-tool-side-effects-during-speculative-branch-merge-the-write-set-provenance-invariant.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-non-idempotent-tool-side-effects-during-speculative-branch-merge-the-write-set-provenance-invariant.warrant.json
# Markdown SHA-256:   7de9b7d7487a3e95b1ee90bd4555b2c60c2ab5cb7399249940e432f4655dfdde
# Code AST SHA-256:   0fa97ab10dc912b5b293e03b8d73105404e960565aa88437281073968eb426ac
# Verified Classes:   EffectProvenanceError, EffectRecord
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class EffectProvenanceError(RuntimeError):
    """Raised when a speculative merge cannot prove effect safety."""


@dataclass(frozen=True)
class EffectRecord:
    branch_id: str
    base_version: int
    effect_id: str
    tool: str
    commutative_with: frozenset  # tool names this effect may commute with


def merge_effects(effects: list[EffectRecord]) -> list[EffectRecord]:
    """Return the deduplicated effect set, or raise on unprovable conflict."""
    by_id: dict[str, EffectRecord] = {}
    for e in effects:
        if e.effect_id in by_id:
            prior = by_id[e.effect_id]
            if (prior.branch_id, prior.base_version) != (e.branch_id, e.base_version):
                raise EffectProvenanceError(
                    f"effect_id {e.effect_id} reused across branches "
                    f"{prior.branch_id} and {e.branch_id}; journal is corrupt"
                )
            continue
        by_id[e.effect_id] = e

    deduped = list(by_id.values())
    for i, a in enumerate(deduped):
        for b in deduped[i + 1:]:
            if a.base_version == b.base_version and a.branch_id != b.branch_id:
                if a.tool not in b.commutative_with or b.tool not in a.commutative_with:
                    raise EffectProvenanceError(
                        f"non-commutative effects from unordered branches "
                        f"{a.branch_id} and {b.branch_id}: "
                        f"{a.tool} vs {b.tool} at base v{a.base_version}"
                    )
    return deduped
