# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-drift-across-the-trust-boundary-when-federated-span-aggregation-applies-differential-privacy-before-cardinality-reconciliation.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-drift-across-the-trust-boundary-when-federated-span-aggregation-applies-differential-privacy-before-cardinality-reconciliation.warrant.json
# Markdown SHA-256:   3a679b1d9f6f851b47db5a8fe673ff06f4d9c22ef5811b5257ef233fa5acffab
# Code AST SHA-256:   ec4fe026490bc19be3d0e9e684784d0040f6a8425c0ec7036bfe9677bbfa797f
# Verified Classes:   FederationLedgerError
# Defensive Guards:   4 fail-closed invariants (zero external deps)

class FederationLedgerError(RuntimeError):
    """Raised when a federated aggregate cannot be safely reconciled."""


def reconcile_aggregate(
    local_aggregate: dict[str, int],
    remote_aggregate: dict[str, int],
    remote_ledger: dict[str, dict] | None,
    local_policy_version: str,
    max_fan_in: int = 8,
) -> dict[str, int]:
    if remote_ledger is None:
        raise FederationLedgerError(
            "remote aggregate arrived without a suppression/generalization ledger; "
            "cannot distinguish suppressed keys from absent keys"
        )

    remote_policy = remote_ledger.get("__policy_version__")
    if remote_policy != local_policy_version:
        raise FederationLedgerError(
            f"policy version mismatch: local={local_policy_version!r} "
            f"remote={remote_policy!r}; join is undefined"
        )

    for key in remote_aggregate:
        entry = remote_ledger.get(key)
        if entry is None:
            raise FederationLedgerError(
                f"aggregate key {key!r} has no ledger entry; attribution impossible"
            )
        if entry["source_key_count"] > max_fan_in:
            raise FederationLedgerError(
                f"key {key!r} collapsed {entry['source_key_count']} source keys "
                f"(ceiling {max_fan_in}); generalization too aggressive to attribute"
            )

    reconciled: dict[str, int] = {}
    for key, count in remote_aggregate.items():
        sources = remote_ledger[key]["source_keys"]
        share = count // max(len(sources), 1)
        for source in sources:
            reconciled[source] = reconciled.get(source, 0) + share
    for key, count in local_aggregate.items():
        reconciled[key] = reconciled.get(key, 0) + count
    return reconciled
