# In what scenarios must an instance refuse to negotiate capability scope during cross-org agent federation handshakes before attestation chain closure **Date of Record:** 2026 November 11 **Scope:** Applies to federated agent-to-agent handshakes where capability grants are exchanged between trust domains under signed delegation chains. Does not apply to intra-org agent meshes operating under a single root of trust, nor to stateless capability discovery queries that carry no grant semantics. ## Core Distinction The failure mode is not a rejected handshake. It is a *successful* handshake that completes before the counterparty's attestation chain has closed. The distinction is between **capability negotiation** (agreeing on what an agent may do) and **capability attestation** (proving the negotiating agent is entitled to grant it). These two activities are frequently interleaved in federation protocols because the negotiation payload is the natural place to carry the attestation. When a peer proposes a scope reduction or expansion mid-negotiation, the tempting optimization is to accept the reduced scope provisionally and continue negotiating the remaining scope while the attestation chain is still being validated asynchronously. This is the boundary: **a scope agreement reached against an unattested grantor is not a weaker agreement, it is a forged one** — the grantor may have no authority to grant the reduced scope either, and a reduced scope that is later silently widened by the same unattested party is indistinguishable from a full-scope grant. The non-obvious part: the invariant that must hold is not "attestation before execution" (everyone knows that) but "attestation before *scope mutation*." Scope mutation during negotiation is itself an authorization event, and it inherits the trustworthiness of the grantor at the moment of mutation, not at the moment of final execution. ## What Was Observed In a two-org federation between a data-custody agent (Org A) and an analytics agent (Org B), the handshake proceeded as follows. Org B proposed `{read:dataset:42, aggregate:true}`. Org A's policy engine returned a counter-proposal narrowing to `{read:dataset:42, aggregate:true, sample_rate:0.1}`. Org B's agent then sent a *revision* that dropped `sample_rate` and added `export:summary`. Org A's negotiation layer, treating revisions as ordinary protocol traffic, applied the revision to its working state and re-evaluated policy — and because the policy engine had already cached the partially-validated attestation chain from the first proposal, it accepted the revision as "same grantor, continued session." The attestation chain for Org B's grantor had, in fact, failed closure on the third link: the intermediate delegation from Org B's root to the analytics sub-agent was signed with a key whose revocation list entry had propagated between the first and second proposals. The negotiation layer never re-checked because it keyed the session on the grantor identity, not on the attestation chain state. The result was a scope that included `export:summary`, granted by an entity whose authority had been revoked mid-handshake, and the audit log recorded a clean, fully-negotiated agreement. The failure was invisible in the negotiation transcript. It only surfaced when a downstream data-loss-prevention check flagged the export and the DLP team asked for the delegation chain — which, when reconstructed, did not close. ## Condition of Validity The refusal-to-negotiate rule is valid under these conditions: 1. **The counterparty's grantor authority is carried in the same message stream as the scope proposal.** If attestation is delivered out-of-band and pinned to a session epoch, the rule relaxes to "re-validate on epoch change" rather than "block all mutation." 2. **Scope mutations are authorization events, not protocol noise.** This holds whenever the mutation can expand or reclassify the resource set. Pure narrowing (subset of already-attested scope, no new resource classes) may proceed provisionally under a quarantine flag. 3. **Revocation propagation is asynchronous and can land mid-handshake.** If revocation is synchronous and blocking, the window closes and the rule is unnecessary. In practice, cross-org revocation is always asynchronous — CRL/OCSP propagation, gossip delay, or webhook lag. 4. **The federation protocol permits revision messages.** Protocols that require a fresh handshake per proposal sidestep the problem by construction; the rule matters only for protocols with in-session revision. ## Decision Boundary Refuse to apply a scope mutation and refuse to continue negotiation when *any* of the following is true at the moment the revision arrives: - The attestation chain for the *current* grantor has not closed (missing link, unverifiable signature, expired intermediate, or revocation state unknown). - The revision changes the resource class set, the principal set, or the delegation depth relative to the last attested proposal. - The session epoch has advanced since the last full attestation check (clock skew, reconnect, or transport renegotiation). - The revision's signer differs from the signer of the last attested proposal, even if the claimed grantor identity is unchanged. Do **not** refuse when the revision is a strict subset of an already-attested scope, the signer is unchanged, and the attestation chain was validated within the current epoch. In that case, apply provisionally and re-validate at epoch close. ## Operational Effect Implementing this rule converts a silent forgery into a loud, attributable handshake abort. The cost is a modest increase in handshake latency for legitimate renegotiations (one extra attestation check per mutation) and a small increase in false aborts during revocation storms, when chains transiently fail to close for reasons unrelated to the counterparty. The benefit is that the audit log's "agreed scope" becomes a statement about attested authority rather than about message ordering. The rule also forces the negotiation layer to key its session state on attestation-chain identity rather than grantor identity — a change that, in the observed incident, would have caught the revocation within one message. The deeper lesson: in federation, the boundary between "negotiating" and "granting" is not a phase transition in the protocol; it is a per-message property. Any message that changes scope is a grant, and every grant must stand on a closed attestation chain at the instant it is applied. ```python class UnattestedScopeMutationError(RuntimeError): """Raised when a scope mutation arrives before the grantor's attestation chain closes.""" pass def apply_scope_revision(session, revision): # A revision is an authorization event, not protocol noise. if revision.signer != session.last_attested_signer: raise UnattestedScopeMutationError( f"revision signer {revision.signer!r} differs from last attested " f"signer {session.last_attested_signer!r}; refusing to mutate scope" ) chain = session.attestation_chain if not chain.is_closed(): raise UnattestedScopeMutationError( f"attestation chain for grantor {session.grantor!r} has not closed " f"(missing={chain.missing_links()!r}, revoked={chain.revoked_links()!r}); " f"scope mutation {revision.mutation!r} refused" ) if session.epoch != chain.epoch: raise UnattestedScopeMutationError( f"session epoch {session.epoch} advanced past attested epoch " f"{chain.epoch}; re-attestation required before mutation" ) if not revision.is_subset_of(session.attested_scope): # Expansion or reclassification: must be fully attested, which we just verified. session.attested_scope = revision.mutation else: session.provisional_scope = revision.mutation session.last_attested_signer = revision.signer return session.attested_scope ```