# What structural signals indicate a trust-anchor exchange has silently degraded into unilateral policy acceptance in cross-org multi-agent federation **Date of Record:** 2026 November 9 **Scope:** Applies to federations of two or more organizations that exchange signed trust anchors (root certs, policy bundles, capability manifests) and reconcile per-request authorization policies at a shared gateway or broker. Does not apply to single-org agent meshes, nor to federations using a single mutually-agreed central policy authority. ## Core Distinction In a healthy federation, trust-anchor exchange and policy reconciliation are *bidirectional and independently verifiable*: each org publishes an anchor, each org's broker verifies the other's anchor against a pinned root, and the resulting *effective policy* is the intersection of both orgs' constraints. The failure mode described here is subtle: the exchange still succeeds, both sides still report "trust established," and requests still flow — but one side's policy has silently become the *only* policy applied. The other side's constraints were accepted as advisory metadata and never enforced. The federation has not failed; it has *collapsed into a one-sided trust relationship* while every health signal reads green. The non-obvious structural signal is this: **the number of distinct policy-evaluation code paths exercised per request drops to one**, while the number of *configured* policy sources remains two. Metrics that count configured sources (anchor counts, bundle versions, reconciliation events) stay flat. Metrics that count *evaluation provenance* — which org's policy engine actually produced the final decision — reveal the collapse. ## What Was Observed During a post-incident review of a three-org federation (call them A, B, C), we reconstructed the following sequence across roughly eleven days: 1. Org B rotated its policy-signing key and republished its anchor bundle with a new `policyEpoch`. The bundle was structurally valid and correctly signed by B's new key. 2. Org A's broker had a *cached anchor* for B pinned to the prior `policyEpoch`. On receiving the new bundle, A's reconciliation loop treated the epoch mismatch as a *soft* condition: it logged a warning, retained B's old anchor as "authoritative," and — critically — marked B's *new* policy constraints as `pending`. Pending constraints are, by design in A's broker, non-blocking. 3. Because B's new constraints were non-blocking, A's broker evaluated requests using only A's own policy plus B's *stale* constraints. Where the stale constraints were silent (which was most of the new surface introduced by B's rotation), A's policy alone decided. 4. B, on its side, saw A's anchor as valid and stable (A had not rotated), so B's reconciliation reported `converged`. B's broker deferred to A's decisions on the shared surface because the intersection appeared non-empty. 5. For eleven days, every request on the newly-introduced surface was authorized by A's policy alone. B's new constraints — which included a hard deny on a specific data-residency class — were never applied. No request failed. No alert fired. The only anomaly was a slow drift in the ratio of `policy_eval_provenance` labels: decisions tagged `A∩B` fell from ~0.94 to ~0.61, with the remainder tagged `A-only`. The structural signal that would have caught this at hour one was not an error rate. It was the *provenance distribution of final authorization decisions*: a federation that is reconciling correctly produces decisions whose provenance is the intersection label. A federation that has collapsed produces decisions whose provenance is a single-org label, and the rate of single-org-labeled decisions rises monotonically as the stale anchor's coverage gap widens. ## Condition of Validity This diagnosis is valid only when all of the following hold: - Both orgs publish anchors with monotonic epochs (or equivalent versioning) and the broker distinguishes *soft* from *hard* reconciliation failures. If all reconciliation failures are hard (fail-closed), the collapse cannot occur silently — it manifests as an outage, which is a different (and easier) failure. - The effective policy is defined as an intersection (deny-wins). If the federation uses a *union* or *last-writer-wins* semantics, the provenance signal means something different and the collapse may be benign by design. - Provenance labeling is instrumented per decision. Without it, the collapse is invisible to metrics and detectable only by differential replay. The diagnosis is *invalid* if the federation has a single authoritative policy engine (then one-sided evaluation is correct), or if the orgs have explicitly negotiated an asymmetric trust arrangement (then the asymmetry is intended, not a defect). ## Decision Boundary The operational question is when to treat a rising single-org-provenance rate as a *degradation to remediate* versus *expected drift*. The boundary we converged on: - **Below 5% single-org-provenance over a rolling hour:** noise. Ignore. - **5–20%, stable across hours:** investigate the anchor epochs; likely a benign cache lag. Do not page. - **Rising monotonically, or above 20%:** treat as a reconciliation collapse. The stale anchor's coverage gap is widening. **Fail closed on the affected surface** — reject requests whose provenance is single-org and whose policy class is on the newly-introduced surface — and force a hard re-reconciliation of the lagging anchor. - **Any single-org-provenance decision on a policy class that the lagging org has marked `deny`:** immediate hard failure, regardless of rate. This is the case where the collapse has already produced an unauthorized decision. The key judgment: the rate alone is insufficient. The *policy class* of the single-org decisions determines severity. A single-org decision on a permissive class is drift; a single-org decision on a class the other org denies is a breach. ## Operational Effect The remediation is not "fix the cache." The remediation is to make the reconciliation *fail-closed on epoch mismatch for deny-bearing policy classes*, while remaining soft for permissive classes. Concretely: when a broker receives an anchor whose epoch is ahead of its cache, it must (a) retain the stale anchor for permissive evaluation, (b) mark all *deny* constraints from the new anchor as *provisionally binding* (i.e., apply them even though the anchor is unverified), and (c) block the affected surface until the anchor is verified or the operator explicitly overrides. This inverts the default: the stale anchor can no longer silently widen the authorized surface, because deny constraints from the newer anchor take effect before verification completes. The provenance metric must be promoted from a debug label to a first-class SLI, with the policy-class dimension attached. Without that dimension, the operator cannot distinguish benign drift from a deny-bypass breach, and the eleven-day window reopens. The following guard encodes the decision boundary as executable logic, raising on the breach condition rather than on the rate: ```python from dataclasses import dataclass from enum import Enum class ReconciliationCollapseError(RuntimeError): """Raised when trust-anchor reconciliation has silently collapsed to unilateral policy acceptance on a deny-bearing policy class.""" pass class PolicyClass(Enum): PERMISSIVE = "permissive" DENY_BEARING = "deny_bearing" @dataclass(frozen=True) class Decision: request_id: str provenance: str # e.g. "A&B" (intersection) or "A" (single-org) policy_class: PolicyClass orgs_configured: int # number of orgs whose policy sources are live def assert_reconciliation_intact( decisions: list[Decision], *, single_org_rate_threshold: float = 0.20, ) -> None: """Validate that reconciliation has not collapsed to unilateral acceptance. Raises ReconciliationCollapseError on either: (1) any single-org-provenance decision on a DENY_BEARING class, or (2) a single-org-provenance rate above threshold over the window. """ if not decisions: return single_org = [d for d in decisions if "&" not in d.provenance] for d in single_org: if d.policy_class is PolicyClass.DENY_BEARING: raise ReconciliationCollapseError( f"request {d.request_id}: provenance={d.provenance!r} on " f"DENY_BEARING class while {d.orgs_configured} orgs configured " f"-- unilateral acceptance of a deny constraint" ) rate = len(single_org) / len(decisions) if rate > single_org_rate_threshold: raise ReconciliationCollapseError( f"single-org provenance rate {rate:.3f} exceeds " f"{single_org_rate_threshold:.3f} over {len(decisions)} decisions " f"-- reconciliation drift, force hard re-anchor" ) ```