# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-bft-consensus-for-multi-agent-task-commit-fail-at-the-f-1-boundary-when-agents-are-ephemeral-and-rejoin-under-new-identities.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-bft-consensus-for-multi-agent-task-commit-fail-at-the-f-1-boundary-when-agents-are-ephemeral-and-rejoin-under-new-identities.warrant.json
# Markdown SHA-256:   bf839f66820b010be19cd5519d88d60680190a69d76cf815ca3fa6c7ddfdac2f
# Code AST SHA-256:   ee2178bf678d2d10ac40cbf06b41a168c2ac59a46f5c6d22fd0491b17e4df638
# Verified Classes:   IdentityReuseViolation, VoteLedger
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class IdentityReuseViolation(RuntimeError):
    """Raised when a rejoining agent would be admitted as a fresh voter
    while its prior identity holds votes in the current view."""
    pass


class VoteLedger:
    def __init__(self):
        # votes[view][proposal_id] = set of voter identities
        self._votes = {}

    def record(self, view, proposal_id, identity):
        self._votes.setdefault(view, {}).setdefault(proposal_id, set()).add(identity)

    def prior_votes(self, view, identity):
        return [
            pid for pid, voters in self._votes.get(view, {}).items()
            if identity in voters
        ]


def admit_rejoin(ledger, view, prior_identity, new_identity, force_view_change):
    """Decide whether a rejoining agent may be admitted under new_identity.

    Returns the identity the agent should proceed under. Raises
    IdentityReuseViolation if the prior identity has votes in this view
    and no view change has been forced.
    """
    outstanding = ledger.prior_votes(view, prior_identity)
    if not outstanding:
        return new_identity
    if force_view_change:
        return new_identity
    raise IdentityReuseViolation(
        f"agent rejoining as {new_identity} has prior identity "
        f"{prior_identity} with votes in view {view} on proposals "
        f"{outstanding}; admit only via view change or identity resumption"
    )
