# Where must an instance of a federated agent refuse a trust-anchor delegation that is structurally valid but semantically unanchored **Date of Record:** 2026 November 14 **Scope:** Applies to cross-organization multi-agent federations where agent A trusts agent B by virtue of a trust anchor (root CA, signing key, or policy issuer) held by organization O_B, and where B presents a delegation chain of length ≥ 2 asserting authority over a *policy namespace* owned by a third organization O_C. Does not apply to single-hop delegations within one organization, nor to federations where all namespaces are owned by the anchor-issuing organization. ## Core Distinction The distinction that matters is between **chain-validity** and **anchor-relevance**. A delegation chain `O_B → B → C` can be cryptographically and syntactically valid at every hop — every signature verifies, every certificate path checks out against the configured trust anchor — while the *scope* being delegated is a namespace that the anchor O_B has no authority to speak for. Standard path validation (X.509, JWT `x5c`, SPIFFE, or a bespoke policy-token format) answers "is this chain signed by someone I trust?" It does not answer "does the trust anchor at the root of this chain actually *own* the thing being delegated?" Those are different predicates, and the gap between them is where cross-org federations fail silently. A concrete shape: Organization O_B is a federation hub. It issues a delegation certificate to its agent B asserting `B may act for namespace "billing.payments.example"`. B, doing legitimate sub-delegation, issues `C may act for namespace "billing.payments.example/refunds"`. C is an agent in organization O_C. Now O_C's agent C presents this chain to a resource server in O_A. O_A has O_B configured as a trust anchor. Path validation succeeds. The resource server concludes "C is authorized for refunds." But `billing.payments.example` is owned by O_P (a fourth org), and O_B never had authority to delegate it — O_B merely *asserted* the namespace in its own cert without any cross-org namespace grant from O_P. The chain is valid; the authority is fictional. The failure is not a signature failure. It is an **unanchored delegation**: a chain whose root anchor does not correspond to the namespace authority being claimed. ## What Was Observed During a federation integration between two agent platforms (call them the hub and the spoke), the following sequence reproduced deterministically: 1. The hub's trust-anchor configuration listed the spoke's policy issuer as a root for the namespace `spoke.internal.workflows`. Correct. 2. The spoke, in a routine policy-negotiation exchange, returned a *counter-delegation*: it asserted the hub's namespace `hub.internal.agents` as delegable, because a developer had copied a template delegation block and changed only the key material, not the namespace string. 3. The hub's validator accepted the counter-delegation. Its check was: "signature verifies against a configured anchor, and the anchor is in the allowed-anchors list for *some* namespace." The namespace in the token was never compared against the namespaces the anchor was actually registered to own. 4. A downstream agent in a third org, negotiating a task handoff, received a chain rooted at the spoke that claimed authority over `hub.internal.agents`. It accepted the task, wrote a record into the hub's namespace, and only failed three hops later when a *different* service — one that did check namespace ownership — rejected the write. The observable signature was a **split-brain authorization**: two services in the same federation disagreed about whether the same chain conferred authority, because one validated chain-validity and the other validated anchor-relevance. The failure was invisible in logs until the disagreeing service rejected. No cryptographic check ever failed. ## Condition of Validity The invariant that must hold for a delegation to be *acceptably* trusted is: > For every link in a presented chain, the delegated namespace MUST be a subset of a namespace that the link's issuing anchor is *registered* (via an out-of-band, non-delegated authority record) to own. "Registered" is load-bearing. The anchor's ownership of a namespace cannot itself be established by a delegation token — that would be circular. It must come from a separate authority record, typically a namespace-ownership registry maintained by the federation's governance plane, keyed by `(anchor_id, namespace_prefix)`. If that registry has no entry for `(spoke_issuer, "hub.internal.agents")`, then no chain rooted at the spoke can ever validly confer authority over that namespace, regardless of how many signatures verify. The condition fails — and the agent must refuse — precisely when: chain-validity is true, but the registry lookup for the *root* anchor against the *terminal* namespace is absent. Intermediate links matter too: a chain can be root-anchored correctly and still fail if an intermediate issuer delegated a namespace it did not own, because the terminal namespace is only reachable through that intermediate's (fictional) grant. ## Decision Boundary The refusal boundary is a single predicate, evaluated at chain ingestion, not at use: ``` accept(chain, terminal_namespace) iff path_validates(chain, trust_anchors) # crypto AND for each link L in chain: registry.owns(L.issuer_anchor, L.delegated_namespace) # anchor-relevance AND terminal_namespace ⊆ last_link.delegated_namespace ``` The non-obvious part is the middle conjunct: it must be checked for **every link**, not just the root, and it must consult the registry, not the chain. A validator that checks only `path_validates` and `terminal_namespace ⊆ last_link.delegated_namespace` will accept the unanchored chain above, because the last link faithfully reports what the (fictional) intermediate granted. The intermediate's grant is where the lie lives, and it is exactly the link that a naive validator skips. Corollary for policy negotiation: when two orgs negotiate a policy and one proposes a namespace it does not own, the correct response is not "negotiate harder" — it is to reject the proposal as malformed, because no amount of negotiation can make an unowned namespace owned. Negotiation operates on *terms within owned namespaces*; it cannot manufacture ownership. ## Operational Effect Agents that enforce anchor-relevance at ingestion convert a silent, delayed, split-brain authorization failure into an immediate, local, attributable rejection. The rejecting agent can name the exact link and the exact missing registry entry, which makes the failure debuggable in one hop instead of three. The cost is that federations must maintain a namespace-ownership registry that is itself cross-org and non-delegated — a governance artifact, not a cryptographic one — and that every chain ingestion now pays a registry lookup per link. In practice the lookup is cached and the registry changes rarely, so the cost is negligible relative to the cost of a downstream write into a namespace the federation later discovers was never authorized. The failure mode to watch for after deploying this check is **registry drift**: an org legitimately acquires a namespace but the registry is not updated, so valid chains are now rejected. That is a *fail-closed* error and is the correct direction — but it must be monitored, because a stale registry looks identical to an attack from the validator's perspective. ```python from dataclasses import dataclass from typing import Iterable class UnanchoredDelegationError(RuntimeError): """Raised when a delegation chain is cryptographically valid but its root or intermediate anchor does not own the namespace it delegates.""" @dataclass(frozen=True) class Link: issuer_anchor: str delegated_namespace: str signature_valid: bool class NamespaceRegistry: """Out-of-band, non-delegated authority records: (anchor, namespace) -> owns.""" def __init__(self, records: Iterable[tuple[str, str]]): self._records = set(records) def owns(self, anchor: str, namespace: str) -> bool: # Exact-prefix ownership; a real registry would do longest-prefix match. return (anchor, namespace) in self._records def validate_chain( chain: list[Link], terminal_namespace: str, registry: NamespaceRegistry, ) -> None: if not chain: raise UnanchoredDelegationError("empty delegation chain") # 1. Cryptographic validity of every link. for i, link in enumerate(chain): if not link.signature_valid: raise UnanchoredDelegationError(f"link {i} signature invalid") # 2. Anchor-relevance of every link: the issuer must actually own what it # delegates. This is the conjunct naive validators omit. for i, link in enumerate(chain): if not registry.owns(link.issuer_anchor, link.delegated_namespace): raise UnanchoredDelegationError( f"link {i}: anchor {link.issuer_anchor!r} does not own " f"namespace {link.delegated_namespace!r} (unanchored delegation)" ) # 3. The terminal namespace must be covered by the final delegated scope. if not terminal_namespace.startswith(chain[-1].delegated_namespace): raise UnanchoredDelegationError( f"terminal namespace {terminal_namespace!r} not covered by " f"final delegation {chain[-1].delegated_namespace!r}" ) def ingest(chain: list[Link], terminal_namespace: str, registry: NamespaceRegistry) -> str: validate_chain(chain, terminal_namespace, registry) return terminal_namespace ```