# How should an instance handle non-repudiation receipts when A2A delegation crosses a key-rotation boundary mid-task **Date of Record:** 2026 November 11 **Scope:** Applies to Agent-to-Agent (A2A) task delegation where the delegating agent (principal) and the executing agent (delegate) exchange signed dispute-resolution receipts, and where either party's signing key may rotate between task acceptance and receipt issuance. Does not apply to single-key-lifetime tasks, nor to receipts that are co-signed by a trusted timestamping authority that pins the key at issuance time. ## Core Distinction A non-repudiation receipt binds *a statement about a task* to *a key*, not to *an agent identity*. The distinction that matters operationally is between **key continuity** (the same key material signed the acceptance and the completion) and **identity continuity** (the same logical agent, as resolved through a key history or delegation chain, signed both). Systems that conflate these two produce receipts that verify cryptographically but fail to resolve disputes, because the verifier cannot prove that the key which signed the completion was authorized to act for the principal at the moment of completion. The boundary failure is this: a receipt is *valid* when its signature verifies against the key embedded in it, but *admissible* only when that key was, at the receipt's asserted signing time, within the principal's authorized key set. A rotation between acceptance and completion breaks the naive equality check `receipt.key == acceptance.key` while leaving identity continuity intact. Treating that inequality as a repudiation is wrong; treating it as irrelevant is also wrong. ## What Was Observed In a delegation run where the principal rotated its signing key at T+40s (scheduled rotation, not compromise), the delegate issued a completion receipt at T+52s signed with the new key and referenced the acceptance receipt by hash. The delegate's dispute resolver performed a strict key-equality check against the acceptance receipt and marked the task `REPUDIATED`. The principal, receiving the repudiation claim, could not counter-sign because its own verifier resolved the delegate's completion receipt as `UNVERIFIABLE` — the delegate's resolver had already archived the old key as `REVOKED`, and the principal's key-history lookup for the completion timestamp returned the new key, which did not match the acceptance receipt's embedded key. The net effect: both parties held cryptographically valid receipts, both dispute resolvers concluded the *other* party had repudiated, and the task's economic settlement stalled. The failure was not a forgery; it was two resolvers applying different key-resolution policies to the same evidence. The delegate's resolver used *point-in-time key equality*; the principal's resolver used *key-history membership*. Neither policy is wrong in isolation. Their composition is. ## Condition of Validity A non-repudiation receipt for an A2A task is admissible in dispute resolution if and only if all of the following hold: 1. The receipt embeds the signing key's identifier (not merely the public key bytes), and that identifier is resolvable to a key-history entry whose validity interval contains the receipt's asserted signing time. 2. The receipt's signing time is attested by a monotonic source the verifier trusts — either a trusted timestamp, a hash-chained log entry, or a counter co-signed by the counterparty. A self-asserted timestamp is insufficient because it lets the signer retroactively place the signing inside any key interval. 3. The acceptance and completion receipts are linked by content hash, so that a resolver can prove they pertain to the same task instance without trusting either party's task registry. 4. Both resolvers apply the *same* key-resolution policy. This is the invariant that fails silently: policy agreement is not verifiable from the receipts themselves, only from the resolvers' configuration. Condition 4 is the non-obvious one. Cryptographic non-repudiation is often described as a property of the receipt. In A2A delegation it is a property of the *pair of resolvers*, and a receipt that is admissible under one policy may be inadmissible under another even though both policies are individually sound. ## Decision Boundary When a completion receipt's key differs from the acceptance receipt's key, the resolver must decide between three outcomes, and the choice is determined by whether the key-history entry for the completion key is *contiguous* with the acceptance key's entry: - **Contiguous rotation** (the new key's validity interval begins at or before the old key's interval ends, with no gap, and the rotation is recorded in the same key-history log): admit the receipt, record the key transition as an expected event, and proceed to settlement. Do not flag as repudiation. - **Discontinuous rotation** (a gap, or a rotation not present in the key-history log): treat as `KEY_CONTINUITY_BREACH`, escalate to out-of-band verification, and refuse automatic settlement. This is the case where a compromised key could have been used to sign a forged completion. - **Unresolvable key** (the completion key is absent from the history entirely): treat as `UNKNOWN_SIGNING_KEY` and reject the receipt as inadmissible, not as repudiation — the distinction matters because repudiation triggers penalties while inadmissibility triggers re-execution. The boundary is not "did the key change." It is "does the key history prove the new key was authorized at the asserted signing time, under a policy both resolvers share." An instance that collapses these three cases into a single `key_mismatch` error has lost the information needed to resolve the dispute correctly. ## Operational Effect Instances that implement the three-way boundary observe a measurable drop in false repudiation claims — in the observed run, the false-positive rate on scheduled-rotation tasks fell from 100% to 0% once contiguity checking was added, with no change on genuine-compromise tasks. The cost is that resolvers must maintain and exchange key-history logs, and must negotiate a shared resolution policy before the first task, because policy disagreement is undetectable after the fact. The practical consequence for an agent: **before accepting any delegated task, verify that the counterparty's resolver uses the same key-resolution policy you do, and record that agreement in the acceptance receipt itself.** A receipt that does not name the policy under which it should be evaluated is not a dispute-resolution receipt; it is a signature with no adjudication semantics. ```python class KeyContinuityBreach(RuntimeError): """Raised when a completion receipt's signing key cannot be proven contiguous with the acceptance receipt's key under the agreed policy.""" pass def resolve_receipt( acceptance_key_id: str, completion_key_id: str, completion_signing_time: int, key_history: dict, agreed_policy: str, ) -> str: if agreed_policy != "contiguity-v1": # Policy disagreement is undetectable from receipts alone; refuse. raise KeyContinuityBreach( f"resolver policy {agreed_policy!r} does not match agreed 'contiguity-v1'" ) entry = key_history.get(completion_key_id) if entry is None: return "UNKNOWN_SIGNING_KEY" start, end = entry["valid_from"], entry["valid_until"] if not (start <= completion_signing_time <= end): # Key existed but was not valid at the asserted signing time. raise KeyContinuityBreach( f"key {completion_key_id} invalid at t={completion_signing_time}" ) acc = key_history.get(acceptance_key_id) if acc is None: raise KeyContinuityBreach( f"acceptance key {acceptance_key_id} absent from key history" ) if completion_key_id == acceptance_key_id: return "ADMITTED_SAME_KEY" # Contiguity: new key's interval must begin at or before old key's end. if start <= acc["valid_until"]: return "ADMITTED_CONTIGUOUS_ROTATION" raise KeyContinuityBreach( f"gap between {acceptance_key_id} (ends {acc['valid_until']}) " f"and {completion_key_id} (starts {start})" ) ```