# When must a federation gateway refuse to honor a peer's trust anchor after policy renegotiation? **Date of Record:** 2026 November 6 **Scope:** Applies to cross-organization multi-agent federations where interoperability is governed by a negotiated policy document and each peer presents a trust anchor (root CA or signing key) that authorizes its agents. Does not apply to single-org meshes, or to federations where trust anchors are immutable for the lifetime of a session. ## Core Distinction The failure mode is not "the peer is untrusted." It is "the peer is trusted under a policy that is no longer the policy in force." Trust anchor validity and policy validity are two independent axes. A trust anchor can remain cryptographically valid (unexpired, correctly signed, unrevoked) while the *policy envelope* it was issued under has been superseded by a renegotiation. The dangerous boundary is the window in which a gateway validates a peer's anchor against the *cached* policy from the previous negotiation round rather than the *active* policy from the current one. The non-obvious part: most implementations re-verify the anchor signature on every message but verify the *policy binding* only at session establishment. When policy is renegotiated mid-session (a common event when an agent's capability set changes, or when a data-classification constraint is tightened), the anchor-to-policy binding silently goes stale. The anchor still verifies. The agent is still authenticated. But the authorization it carries was issued under terms both parties have already abandoned. ## What Was Observed In a three-party federation (Org A gateway, Org B gateway, Org C agent acting as a delegated caller), Org B renegotiated its policy envelope at T+40s to add a constraint: delegated agents from Org C may not invoke Org A's `export` capability unless Org C's anchor carries the `data-sovereignty` extension. The renegotiation completed successfully at the policy layer. Org B's gateway updated its active policy. Org C's agent continued to present its original anchor, which verified cleanly against the trust store. Org B's gateway, however, was still evaluating the anchor-to-policy binding using the policy snapshot captured at session start (T+0s), which predated the constraint. The delegated call to Org A's `export` was authorized and executed. The constraint was not violated by a forged credential — it was violated by a *stale binding between a valid credential and a superseded policy*. The log signature is distinctive: anchor verification succeeds, capability check succeeds, but the policy-version recorded in the authorization decision does not match the policy-version active at the gateway. The two diverge by exactly one renegotiation epoch. ## Condition of Validity A trust anchor remains honorable after policy renegotiation **only if** the anchor's embedded policy reference (policy hash, epoch counter, or envelope identifier) is re-checked against the active policy at the moment of authorization — not at session establishment. If the anchor carries no policy reference, or if the reference is checked only at handshake, the anchor must be treated as *policy-unbound* and revalidated through a fresh negotiation before any capability that the new policy constrains is exercised. The condition is strict: a policy renegotiation that *adds or tightens* a constraint invalidates every anchor whose binding predates the renegotiation for the constrained capabilities. A renegotiation that only *relaxes* constraints does not require anchor revalidation, because the old binding is a subset of the new policy. ## Decision Boundary The gateway must choose between two actions when it detects `anchor.policy_epoch < active_policy.epoch` for a constrained capability: 1. **Reject and force renegotiation** — safe, but causes a session reset and loses in-flight agent state. 2. **Honor the anchor under the old policy for unconstrained capabilities, reject only the constrained ones** — preserves session continuity but requires per-capability policy-epoch accounting. The boundary is: honor-under-old-policy is permissible **only** when the capability being invoked is not among those the new policy constrains *and* the old policy's grant for that capability is a strict subset of the new policy's grant. If either condition fails, the gateway must reject and force renegotiation. There is no safe third option. Honoring the anchor under old policy for a newly-constrained capability is the exact failure observed above. ## Operational Effect The operational consequence is that policy renegotiation is not a control-plane-only event. It has a data-plane footprint: every in-flight authorization decision made after the renegotiation must carry the policy epoch it was evaluated under, and any decision whose epoch is stale for a constrained capability must be invalidated retroactively (or, if already executed, flagged for post-hoc audit). Gateways that treat renegotiation as a pure control-plane update will silently authorize constrained capabilities under superseded terms. The audit trail will show valid signatures and valid policy checks at every step, which is precisely why the failure is hard to detect after the fact — the only tell is the epoch mismatch between the authorization record and the active policy at execution time. ```python from dataclasses import dataclass class StalePolicyBindingError(RuntimeError): """Raised when a trust anchor is valid but bound to a superseded policy for a capability the active policy now constrains.""" @dataclass(frozen=True) class TrustAnchor: peer_id: str policy_epoch: int signature_valid: bool @dataclass(frozen=True) class ActivePolicy: epoch: int constrained_capabilities: frozenset def authorize(anchor: TrustAnchor, policy: ActivePolicy, capability: str) -> bool: if not anchor.signature_valid: raise StalePolicyBindingError( f"anchor for {anchor.peer_id} failed signature verification" ) if anchor.policy_epoch >= policy.epoch: return True # Anchor predates active policy. Safe only if capability is unconstrained. if capability in policy.constrained_capabilities: raise StalePolicyBindingError( f"anchor epoch {anchor.policy_epoch} < active epoch {policy.epoch} " f"for constrained capability {capability!r}; force renegotiation" ) return True ```