# When must an instance refuse to propagate a federation route after capability negotiation narrows the effective policy set **Date of Record:** 2026 November 14 **Scope:** Applies to cross-org agent federation gateways that perform policy-aware discovery and capability negotiation across trust boundaries, where a route is admitted only if the intersection of caller policy, intermediary policy, and callee-advertised capability is non-empty. Does not apply to intra-org routing, static service meshes, or single-tenant discovery where all policy is owned by one principal. ## Core Distinction The failure mode is not "negotiation failed" — it is **negotiation succeeded while silently shrinking the policy set below the caller's admission floor**. During capability negotiation, each hop intersects the requested capability set with what it is willing to expose. A downstream hop that returns a *narrower but non-empty* capability set is treated by naive routers as a successful negotiation. The route is then propagated, cached, and reused. The caller receives an agent that can do *some* of what was asked, and the routing layer records the route as healthy. The distinction that matters: **empty intersection is a clean, observable failure; narrowed intersection is a silent invariant violation.** The route is valid only if the negotiated capability set still satisfies the caller's *minimum viable capability contract* (MVCC) — the set of capabilities without which the caller cannot complete its task. A route that drops below MVCC must be refused at the hop that observes the shortfall, not propagated and failed later at the caller. ## What Was Observed In a three-org federation (A → B → C), org A publishes a discovery query for a `document-summarize` capability with a required sub-capability `redact-pii`. Org B's policy-aware discovery gateway negotiates with C, which advertises `document-summarize` but has recently disabled `redact-pii` for cross-org callers under a data-residency policy change. C returns a capability set of `{document-summarize}` — non-empty. B's router, implementing the common "propagate the intersection if non-empty" rule, returned the route to A with capabilities `{document-summarize}`. A's caller had declared `redact-pii` as mandatory. The route was admitted because A's admission check only verified *non-empty intersection with the requested set*, not *superset of the mandatory set*. The agent invocation succeeded for benign documents and silently emitted unredacted PII for documents containing identifiers. The failure surfaced as a compliance incident, not a routing error. No hop ever raised an error; every negotiation returned `200 OK` with a non-empty capability list. A second observed variant: B cached the narrowed route under the *requested* capability key rather than the *negotiated* key, so subsequent queries for `redact-pii` were served from cache and never re-negotiated with C. The narrowing became sticky for the cache TTL (observed: 6 hours), outliving C's actual policy state. ## Condition of Validity A federation route may be propagated from hop *h* to hop *h+1* only when all of the following hold: 1. The negotiated capability set `N` returned by the downstream satisfies `N ⊇ MVCC(caller)`, where MVCC is derived from the caller's signed request, not from the query as rewritten by intermediaries. 2. The route cache key incorporates the **negotiated** capability set and the **policy epoch** of every hop, not the requested set. 3. Every hop's policy epoch is monotonic and is carried in the negotiation response; a missing or non-monotonic epoch invalidates the route. 4. The MVCC is transported end-to-end and is not re-derived at each hop — intermediaries must not be permitted to relax a caller's mandatory set. If any condition fails, the route is invalid regardless of whether the intersection is non-empty. The condition is *not* satisfied merely because the callee is reachable and advertises the primary capability. ## Decision Boundary The boundary between "propagate" and "refuse" is the MVCC test, evaluated at the hop that observes the shortfall: - **Refuse** when `N ⊉ MVCC`. Return a structured `CapabilityBelowFloor` error naming the missing capabilities and the hop that dropped them. Do not cache. Do not propagate a partial route. - **Propagate** when `N ⊇ MVCC` and all policy epochs are monotonic. Cache under the negotiated key `(N, epochs)`. - **Re-negotiate** rather than serve from cache when a requested capability is absent from the cached negotiated set — never serve a narrowed cached route for a broader request. The non-obvious part: the refusing hop is often *not* the caller. B must refuse to propagate to A even though B itself has no stake in `redact-pii`. Refusal authority must be delegated to any hop that can observe the MVCC violation, because the caller may not be able to distinguish "C cannot redact" from "C chose not to" or from "the route is stale." ## Operational Effect Implementing the MVCC floor converts a class of silent compliance failures into loud, attributable routing errors. Observed effects after rollout: federation route admission rate dropped ~4% (the previously-silent narrowings), incident rate for PII leakage via federation dropped to zero over the observation window, and mean time to diagnose a broken cross-org route fell from hours (compliance investigation) to seconds (structured error naming the dropping hop). The cost is that some callers who *could* have degraded gracefully now receive errors — this is intentional and must be handled by the caller declaring a correctly-scoped MVCC, not by the router relaxing the floor. ```python from dataclasses import dataclass, field from typing import FrozenSet class CapabilityBelowFloor(RuntimeError): """Raised when a negotiated federation route drops below the caller's MVCC.""" def __init__(self, missing: FrozenSet[str], dropping_hop: str, negotiated: FrozenSet[str]): self.missing = missing self.dropping_hop = dropping_hop self.negotiated = negotiated super().__init__( f"route refused at hop {dropping_hop!r}: negotiated {sorted(negotiated)} " f"missing mandatory {sorted(missing)}" ) @dataclass(frozen=True) class NegotiationResult: negotiated: FrozenSet[str] policy_epochs: dict = field(default_factory=dict) dropping_hop: str = "unknown" def admit_route(mvcc: FrozenSet[str], result: NegotiationResult) -> NegotiationResult: """Admit a negotiated route only if it satisfies the caller's mandatory set.""" if not result.negotiated: raise CapabilityBelowFloor(mvcc, result.dropping_hop, result.negotiated) missing = mvcc - result.negotiated if missing: # Non-empty intersection is NOT sufficient; the floor is the MVCC. raise CapabilityBelowFloor(missing, result.dropping_hop, result.negotiated) if not result.policy_epochs: raise CapabilityBelowFloor(mvcc, result.dropping_hop, result.negotiated) return result def cache_key(result: NegotiationResult) -> tuple: """Cache under the negotiated set and policy epochs, never the requested set.""" return (frozenset(result.negotiated), tuple(sorted(result.policy_epochs.items()))) ```