feat(transport): A2A extension adapter for PeerRequest#48
Conversation
Add ca2a_runtime.transport to map A2A SendMessage metadata to PeerRequest (and reverse) under https://agentrust.io/extensions/ca2a/v0.1. Fail closed on malformed cA2A fields; absence of all keys returns None (ordinary A2A). Docs and LIMITATIONS keep the claim boundary: no live server or attestation. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
carloshvp
left a comment
There was a problem hiding this comment.
Requesting changes on one transport-boundary issue.
The new adapter promises that malformed cA2A metadata fails closed with TRANSPORT_ERROR, but sealed_payload currently uses base64.urlsafe_b64decode() without alphabet validation. Python's default decoder can ignore some non-base64url characters, so malformed extension metadata can be accepted and converted into opaque bytes instead of failing closed.
Repro against this PR head:
PYTHONPATH=src python3 - <<'PY'
from ca2a_runtime.transport import (
KEY_DELEGATION_CHAIN,
KEY_PARENT_RECORD_HASH,
KEY_RECORD_ID,
KEY_REQUESTED_CAPABILITY,
KEY_SEALED_PAYLOAD,
parse_peer_request,
)
from tests.unit.test_a2a_adapter import _cred_dicts
meta = {
KEY_DELEGATION_CHAIN: _cred_dicts([frozenset({"read", "write"}), frozenset({"read"})]),
KEY_REQUESTED_CAPABILITY: "read",
KEY_RECORD_ID: "rec-invalid-b64",
KEY_PARENT_RECORD_HASH: None,
KEY_SEALED_PAYLOAD: "abcd?",
}
try:
req = parse_peer_request({"metadata": meta})
print("ACCEPTED", req.sealed_payload)
except Exception as exc:
print("RAISED", type(exc).__name__, str(exc), getattr(exc, "code", None))
PYActual result: ACCEPTED b'i\xb7\x1d'.
That conflicts with the PR's fail-closed contract for any present-but-malformed cA2A metadata, and it leaves the parser accepting a value that is not actually base64url. Please make the decode strict, for example by validating the URL-safe alphabet/padding before decoding or by translating to standard base64 and using base64.b64decode(..., validate=True), and add a regression test for a character outside the base64url alphabet such as abcd?.
Validation performed: inspected the PR diff, ran ruff check src/ tests/ successfully, ran pytest tests/unit/test_a2a_adapter.py tests/unit/test_errors.py successfully, reproduced the malformed sealed_payload acceptance above. mypy src/ca2a_runtime/ src/ca2a_verify/ did not complete in my local temp checkout because types-PyYAML is not installed; pytest tests/unit/ also did not complete locally because cedarpy is not installed. GitHub CI for the PR is green except the maintainer-approval gate.
base64.urlsafe_b64decode silently ignores characters outside the alphabet, so a value like "abcd?" was accepted as opaque bytes instead of failing closed. Validate the base64url alphabet before decoding and add regression tests for characters outside it. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
|
Good catch, thanks. Fixed in 9e5a2b7.
Added a parametrized regression test covering characters outside the alphabet: |
carloshvp
left a comment
There was a problem hiding this comment.
Requesting changes on one remaining adapter-shape issue.
The strict sealed_payload fix on this head addresses my previous blocker. The remaining problem is in attach_ca2a_metadata(): it only writes to out["metadata"]. If a caller passes the full A2A JSON-RPC message/send / SendMessage envelope that parse_peer_request() explicitly accepts, the helper attaches cA2A fields at the envelope root instead of under params.message.metadata or params.metadata. That means the helper can produce an object that the companion parser immediately treats as ordinary A2A.
Repro against 9e5a2b76ce40c523ac3599edb2cd37918f1f01dd:
PYTHONPATH=src python3 - <<'PY'
from ca2a_runtime.peer import PeerRequest
from ca2a_runtime.transport import attach_ca2a_metadata, parse_peer_request, KEY_DELEGATION_CHAIN
from tests.unit.conftest import build_chain
request = PeerRequest(
chain=build_chain([frozenset({"read", "write"}), frozenset({"read"})]),
requested_capability="read",
record_id="rec-envelope",
sealed_payload=None,
parent_record_hash=None,
)
envelope = {
"jsonrpc": "2.0",
"id": "1",
"method": "message/send",
"params": {
"message": {
"messageId": "msg-1",
"role": "user",
"parts": [{"text": "task"}],
"metadata": {"https://example.com/ext/other/v1/note": "keep"},
}
},
}
attached = attach_ca2a_metadata(envelope, request)
print("top_level_has_metadata", "metadata" in attached)
print("message_has_ca2a", KEY_DELEGATION_CHAIN in attached["params"]["message"].get("metadata", {}))
print("parse_attached_returns", parse_peer_request(attached))
PYActual result:
top_level_has_metadata True
message_has_ca2a False
parse_attached_returns None
That is a transport-boundary bug, not just a test-shape nit: the PR advertises a parse/attach helper for A2A SendMessage-shaped data, but the attach half can silently drop the cA2A envelope from the actual A2A metadata location. Please either make attach_ca2a_metadata() mirror collect_metadata()'s supported shapes and attach under params.message.metadata / params.metadata for envelopes, or narrow the API/docs/tests so it only accepts a message object and fails closed on a JSON-RPC envelope instead of producing a misleading root-level metadata field. Add a round-trip regression for the full envelope shape.
Validation performed: inspected the updated PR diff; ran ruff check src/ tests/ successfully; ran pytest tests/unit/test_a2a_adapter.py tests/unit/test_errors.py -q successfully; reproduced the envelope round-trip failure above in a temp checkout. GitHub CI is green except the maintainer-approval gate.
attach_ca2a_metadata only wrote to the root metadata, so a full JSON-RPC message/send envelope got cA2A fields at the root while parse_peer_request reads params.message.metadata / params.metadata. The result was an object the parser treated as ordinary A2A. Attach now mirrors collect_metadata's shapes so attach then parse round-trips for both bare messages and envelopes. Add round-trip regressions for the envelope shapes. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
|
Fixed in a321bb6.
Your repro now prints: Added round-trip regressions for the full |
Summary
ca2a_runtime.transportto parse A2ASendMessagemetadata intoPeerRequest, and attach the same fields on the way out.https://agentrust.io/extensions/ca2a/v0.1. Malformed cA2A metadata fails closed (TRANSPORT_ERROR); no cA2A keys means ordinary A2A (None).docs/spec/transport.mdandLIMITATIONS.mdso this is clearly parse/attach only. No HTTP server,ca2a start, or seal-to-verified-measurement binding.Closes the first Tier 2 checkbox on #47 (adapter only), matching the acceptance notes from @carloshvp.
Test plan
ruff check src/ tests/mypy src/ca2a_runtime/ src/ca2a_verify/pytest tests/unit/(141 passed locally)