Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **Extension URI moved to `https://agentrust-io.com/extensions/ca2a/v0.1`.** The previous `agentrust.io` host is not ours: it resolves to parked AWS addresses. An extension identifier is something we are asking other operators to copy into their Agent Cards, so it must not depend on a domain we do not control. A2A treats these URIs as identifiers rather than fetchable URLs, so nothing breaks functionally, but any peer pinning the old string must update. The adapter constant, the transport spec, the fixture, and the test that pins the constant all move together.

### Fixed

- Two spec pages contradicted `hardware-validation.md` after the live hardware run landed. `profile.md` P-6 still read "not yet met ... `assurance="none"`" and `call-graph.md` still called hardware appraisal "the remaining hardware step", when `verify_offer` had already returned `assurance="hardware"` off a live SEV-SNP quote on 2026-07-27. Both now state what is true, including the two limits that remain: the appraisal is one-directional (the caller appraised the callee, not the reverse), and the committed examples stay software-attested because genuine evidence embeds per-CPU identifiers.

### Added

- **A refusal is now evidence.** `enforce_peer_call` previously raised on an over-scoped call and emitted nothing, so a denial left no artifact and an auditor saw a gap where a hop should be. It now builds a linked denial record (`provenance.denial_record_for`) carrying the requested capability, the effective scope it fell outside, and the reason, and attaches it to `ScopeNotPermitted.record`. The call still fails closed; the record is evidence of the refusal, not a way to continue. `verify_dag` treats a denial as terminal and rejects any chain that continues past a refused hop, `cross_check_chain` matches only hop records positionally against the chain while still requiring a denial to name a credential that is in it, and `ca2a verify-dag` reports `outcome: denied` with the requested capability and effective scope. Denial fields are omitted from an allow record's hashed body, so existing allow-record hashes are unchanged.
Expand All @@ -25,7 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- A2A transport adapter (`ca2a_runtime.transport`): parse/attach cA2A extension
metadata on A2A `SendMessage`-shaped messages into `PeerRequest` (and the
reverse). Extension URI `https://agentrust.io/extensions/ca2a/v0.1`. Fail closed
reverse). Extension URI `https://agentrust-io.com/extensions/ca2a/v0.1`. Fail closed
on malformed cA2A metadata; absence of all cA2A keys returns `None` (ordinary
A2A). The adapter itself adds no HTTP serving or seal-to-verified-measurement
binding; the reference transport below adds serving, and hardware measurement
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/call-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ A capability is granted only when it is both delegated down the chain and allowe

## Step 4: seal the payload to the peer's attested key (crypto implemented)

Once the peer's attested public key is known (from its report, step 2), the task payload is sealed to it so only the holder of the peer's private key can open it. The channel is implemented (`SealedChannel(peer_pub).seal(...)` and `open_sealed(...)`, an HPKE-style X25519 -> HKDF-SHA256 -> ChaCha20-Poly1305 scheme). `open_sealed` fails closed with `SEALED_CHANNEL_ERROR` on a wrong key or tampered ciphertext. On a live call the handshake (`ca2a_runtime.attestation.verify_offer`) now gates the seal on a channel key the caller has appraised under a fresh nonce, but in software mode that appraisal is `assurance="none"`. The property that the payload decrypts *only inside the attested measurement* rests on the private key being enclave-bound and on a hardware-verified measurement, which is the remaining hardware step. See [sealed-channel.md](sealed-channel.md).
Once the peer's attested public key is known (from its report, step 2), the task payload is sealed to it so only the holder of the peer's private key can open it. The channel is implemented (`SealedChannel(peer_pub).seal(...)` and `open_sealed(...)`, an HPKE-style X25519 -> HKDF-SHA256 -> ChaCha20-Poly1305 scheme). `open_sealed` fails closed with `SEALED_CHANNEL_ERROR` on a wrong key or tampered ciphertext. On a live call the handshake (`ca2a_runtime.attestation.verify_offer`) gates the seal on a channel key the caller has appraised under a fresh nonce. As of 2026-07-27 that appraisal has run at `assurance="hardware"` off a live SEV-SNP quote, so the property that the payload decrypts *only inside the attested measurement* now rests on a hardware-verified measurement rather than on a software-mode stand-in. It holds one-directionally: the caller appraised the callee, and mutual simultaneous attestation is still outstanding. In software mode the appraisal remains `assurance="none"`, which is what the committed examples exercise. See [sealed-channel.md](sealed-channel.md).

## Step 5: emit a linked provenance record (implemented)

Expand Down
4 changes: 3 additions & 1 deletion docs/spec/profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ A callee MUST compute the effective scope as the leaf credential's delegated sco

Before accepting a delegated task across a trust boundary, a peer SHOULD be required to present attestation evidence binding its channel key to a measured runtime, and the caller MUST appraise that evidence against its expected measurement before sealing anything to that key. A caller that cannot appraise the evidence MUST NOT treat the peer as attested.

> Not yet met by this implementation. The handshake gates the seal on an appraised channel key, but the appraisal runs in software mode at `assurance="none"`. See [attestation](attestation.md) and [hardware-validation](../hardware-validation.md).
> Met one-directionally as of 2026-07-27. Driven off a live SEV-SNP quote on a running confidential VM, `verify_offer` returned `assurance="hardware"` and a payload was sealed to a channel key that a hardware-verified measurement vouches for. A cross-operator, cross-TEE run followed: an Azure SEV-SNP peer calling a GCP Intel TDX peer.
>
> Two limits remain, and P-6 is not fully met until both close. The appraisal is **one-directional**: the caller appraised the callee, not the reverse, so neither side has yet proven itself to a peer that is simultaneously proving itself back. And the committed harness in `examples/` still runs software-attested against synthetic vectors, because genuine evidence embeds per-CPU identifiers and is not shippable as a fixture. See [attestation](attestation.md) and [hardware-validation](../hardware-validation.md).

### P-7 Sealed payload

Expand Down
14 changes: 7 additions & 7 deletions docs/spec/transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ Per [A2A v1.0 extensions](https://a2a-protocol.org/v1.0.0/topics/extensions/), a

| Item | Value |
|---|---|
| Extension URI | `https://agentrust.io/extensions/ca2a/v0.1` |
| Opt-in header | `A2A-Extensions: https://agentrust.io/extensions/ca2a/v0.1` |
| Extension URI | `https://agentrust-io.com/extensions/ca2a/v0.1` |
| Opt-in header | `A2A-Extensions: https://agentrust-io.com/extensions/ca2a/v0.1` |

Namespaced keys on A2A `metadata` (message and/or params):

| Metadata key | JSON type | Meaning |
|---|---|---|
| `https://agentrust.io/extensions/ca2a/v0.1/delegation_chain` | array of credential objects | Root-to-leaf delegation chain |
| `https://agentrust.io/extensions/ca2a/v0.1/requested_capability` | string | Capability the callee must grant |
| `https://agentrust.io/extensions/ca2a/v0.1/record_id` | string | Provenance record id for this hop |
| `https://agentrust.io/extensions/ca2a/v0.1/parent_record_hash` | string or `null` | Parent TRACE/provenance hash; `null` for a root hop |
| `https://agentrust.io/extensions/ca2a/v0.1/sealed_payload` | string (base64url) or omitted | Opaque sealed ciphertext only, not a verified measurement binding |
| `https://agentrust-io.com/extensions/ca2a/v0.1/delegation_chain` | array of credential objects | Root-to-leaf delegation chain |
| `https://agentrust-io.com/extensions/ca2a/v0.1/requested_capability` | string | Capability the callee must grant |
| `https://agentrust-io.com/extensions/ca2a/v0.1/record_id` | string | Provenance record id for this hop |
| `https://agentrust-io.com/extensions/ca2a/v0.1/parent_record_hash` | string or `null` | Parent TRACE/provenance hash; `null` for a root hop |
| `https://agentrust-io.com/extensions/ca2a/v0.1/sealed_payload` | string (base64url) or omitted | Opaque sealed ciphertext only, not a verified measurement binding |

Constants and helpers live in `ca2a_runtime.transport`.

Expand Down
18 changes: 9 additions & 9 deletions examples/rejection-with-proof/chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"chain": [
{
"credential_id": "cred-0-orchestrator",
"issuer": "30831ec7f2a3abdcc223d22dc89311e3e8870cfbea931149d50e07077d518e9b",
"subject": "3f542267fb293071807ea37ac01a1be5f4b4bd9823f10156f1c998545fb2abca",
"issuer": "0aca4cf1251cb3648cd1f8605481d8dece5cd3a106a362a11b86f43d270da087",
"subject": "41eebfb4e9ac7f0944041b6c8fad485159f3d0391721dcec1bbe478986c6879f",
"scope": [
"task:read",
"task:write",
Expand All @@ -12,31 +12,31 @@
],
"depth": 0,
"parent_id": null,
"signature": "493358a97965be0a78a81cc832028dd494e65a16d7622842661080b08af7b800f4c71e267c4d2745485964504209f836f93d1923d10453ba6375427b68399803"
"signature": "2380d30571a6493f6236c3aa9d381fd28a9cd3fcd4fa0fd5316166867575b206d8eb882c39fc7362c89cd407eef77848c228410a7816a10b563888eb99789a08"
},
{
"credential_id": "cred-1-researcher",
"issuer": "3f542267fb293071807ea37ac01a1be5f4b4bd9823f10156f1c998545fb2abca",
"subject": "f327db8ed54be433ee43fec9f4c144d3f0d84555eb4654989c4458087e23b99d",
"issuer": "41eebfb4e9ac7f0944041b6c8fad485159f3d0391721dcec1bbe478986c6879f",
"subject": "cb42a0b6b9ec7839846a0ddbcb67d851d9b1013fc04066687181938d9ea7dce8",
"scope": [
"task:read",
"tool:purchase",
"tool:search"
],
"depth": 1,
"parent_id": "cred-0-orchestrator",
"signature": "35ba31dccb83829c4c8f9f4215aeb19b49aafd0033aef1d5ebb876a8a3d0110797f6f5a013ab55171a969c4940a1288b1591d6a090edf5df6b0a8b4db8b08300"
"signature": "ad8d7b0aa00bd04e45bd9dca84f92bd7f23b4f4dde9e6eeff441ccf7c5222c5a0ccb45b5ba1b8399b6625cd3b4cfd97bc953b16bba01034a05ae01d55ba50503"
},
{
"credential_id": "cred-2-retriever",
"issuer": "f327db8ed54be433ee43fec9f4c144d3f0d84555eb4654989c4458087e23b99d",
"subject": "555d4389edcb44043216dbce5079bd61edaa5873f0674b8c57b4c7b79f441cd5",
"issuer": "cb42a0b6b9ec7839846a0ddbcb67d851d9b1013fc04066687181938d9ea7dce8",
"subject": "2014e45331bb1717e9983dbba3fd4b6adc81ee48257afe5c0e027f12f61e8d7d",
"scope": [
"tool:search"
],
"depth": 2,
"parent_id": "cred-1-researcher",
"signature": "f134bd6fd0f55573c285dc42debaf2944360d060ded3596d2ba85f6a0a223cbd7d99a7a111cf358793f01ca2454f3d7e301021b3789f84b6e90553e726d34904"
"signature": "0a18f20de83626e8f32960f3a3a543fed07b4c9f1ccce1eec4e3a6b7798311b68d08052ec408a675cd153fbfb9594d369c6333b672d2c1ba16854c517ebebf05"
}
]
}
14 changes: 7 additions & 7 deletions examples/rejection-with-proof/dag.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"record_id": "rec-0-orchestrator",
"credential_id": "cred-0-orchestrator",
"subject": "3f542267fb293071807ea37ac01a1be5f4b4bd9823f10156f1c998545fb2abca",
"subject": "41eebfb4e9ac7f0944041b6c8fad485159f3d0391721dcec1bbe478986c6879f",
"scope": [
"task:read",
"task:write",
Expand All @@ -15,31 +15,31 @@
{
"record_id": "rec-1-researcher",
"credential_id": "cred-1-researcher",
"subject": "f327db8ed54be433ee43fec9f4c144d3f0d84555eb4654989c4458087e23b99d",
"subject": "cb42a0b6b9ec7839846a0ddbcb67d851d9b1013fc04066687181938d9ea7dce8",
"scope": [
"task:read",
"tool:purchase",
"tool:search"
],
"parent_record_hash": "d2b7837dc31713ee5ce1adac414e787f88a42e83663ac4c333318127ff4606b5"
"parent_record_hash": "143db9ccd89d3cf4c50426e5e8dedc32e7feac0bbc2735f63b605bba45eef613"
},
{
"record_id": "rec-2-retriever",
"credential_id": "cred-2-retriever",
"subject": "555d4389edcb44043216dbce5079bd61edaa5873f0674b8c57b4c7b79f441cd5",
"subject": "2014e45331bb1717e9983dbba3fd4b6adc81ee48257afe5c0e027f12f61e8d7d",
"scope": [
"tool:search"
],
"parent_record_hash": "3dac51b23f24cde0396bc9bc3ae0f06aaf7bf13c9e40800f24d499869beee8e4"
"parent_record_hash": "2e0ac019c2a7949a0cb5c8eab207ddef3351405db20dd4360144b6c4e605c6a0"
},
{
"record_id": "rec-denied-purchase",
"credential_id": "cred-2-retriever",
"subject": "555d4389edcb44043216dbce5079bd61edaa5873f0674b8c57b4c7b79f441cd5",
"subject": "2014e45331bb1717e9983dbba3fd4b6adc81ee48257afe5c0e027f12f61e8d7d",
"scope": [
"tool:search"
],
"parent_record_hash": "6b10750d625c1cff83b5868948eeb7a6ebdbbe427ce3ba2bc802c258c8cdc82d",
"parent_record_hash": "c8ce42ebbbe084bbe88571a12576d8a4c1d5d3753d071e35dd19838274605f09",
"decision": "deny",
"requested_capability": "tool:purchase",
"effective_scope": [
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "Confidential agent-to-agent runtime: attested, attenuated delegat
readme = "README.md"
license = { text = "MIT" }
authors = [
{ name = "AgenTrust Contributors", email = "oss@agentrust.io" },
{ name = "AgenTrust Contributors", email = "oss@agentrust-io.com" },
]
keywords = ["a2a", "agent-to-agent", "delegation", "tee", "attestation", "confidential-computing", "ai-agents"]
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion src/ca2a_runtime/transport/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from __future__ import annotations

EXTENSION_URI = "https://agentrust.io/extensions/ca2a/v0.1"
EXTENSION_URI = "https://agentrust-io.com/extensions/ca2a/v0.1"

KEY_DELEGATION_CHAIN = f"{EXTENSION_URI}/delegation_chain"
KEY_REQUESTED_CAPABILITY = f"{EXTENSION_URI}/requested_capability"
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/a2a/send_message_ca2a.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
],
"metadata": {
"https://example.com/ext/unrelated/v1/note": "preserved by non-cA2A peers",
"https://agentrust.io/extensions/ca2a/v0.1/delegation_chain": [],
"https://agentrust.io/extensions/ca2a/v0.1/requested_capability": "read",
"https://agentrust.io/extensions/ca2a/v0.1/record_id": "rec-fixture",
"https://agentrust.io/extensions/ca2a/v0.1/parent_record_hash": null
"https://agentrust-io.com/extensions/ca2a/v0.1/delegation_chain": [],
"https://agentrust-io.com/extensions/ca2a/v0.1/requested_capability": "read",
"https://agentrust-io.com/extensions/ca2a/v0.1/record_id": "rec-fixture",
"https://agentrust-io.com/extensions/ca2a/v0.1/parent_record_hash": null
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_a2a_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,5 @@ def test_sealed_payload_is_opaque_bytes_only() -> None:


def test_extension_uri_is_stable() -> None:
assert EXTENSION_URI == "https://agentrust.io/extensions/ca2a/v0.1"
assert EXTENSION_URI == "https://agentrust-io.com/extensions/ca2a/v0.1"
assert KEY_DELEGATION_CHAIN.startswith(EXTENSION_URI)