From b5489eceac14bcce55e8a2df7f60b0ad66d02af5 Mon Sep 17 00:00:00 2001 From: "Altru.dev" Date: Sat, 12 Sep 2026 14:21:58 -0700 Subject: [PATCH 1/2] fix(intent-bridge): preserve canonicalization error class Signed-off-by: altrudev <266135212+altrudev@users.noreply.github.com> --- src/agentrust_trace/intent_bridge.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/agentrust_trace/intent_bridge.py b/src/agentrust_trace/intent_bridge.py index 2d91c91e..84ffe643 100644 --- a/src/agentrust_trace/intent_bridge.py +++ b/src/agentrust_trace/intent_bridge.py @@ -240,20 +240,17 @@ def verify_bridge( if not isinstance(transcript, dict) or set(transcript) != {"before", "after"}: raise AuthorizationMismatch("a full before/after transcript is required") before = transcript.get("before") - if not isinstance(before, dict) or not isinstance(before.get("tool_call"), dict): + before_call = before.get("tool_call") if isinstance(before, dict) else None + if not isinstance(before_call, dict): raise AuthorizationMismatch("transcript.before.tool_call does not match execution") # Host-language equality is not this bridge's identity relation. Python holds - # True == 1 and False == 0, nested objects included, so comparing the two call - # objects with != accepts a transcript whose call has different JCS bytes from - # the one the authorization digested (#317). Every other comparison in this - # function is over canonical bytes; so is this one. - try: - before_digest = digest_jcs(before["tool_call"]) - except IntentBridgeError: - raise AuthorizationMismatch( - "transcript.before.tool_call does not match execution" - ) from None - if not compare_digest(before_digest, tool_call_digest): + # True == 1 and False == 0, nested objects included, so compare the exact RFC + # 8785 bytes instead. If either object has no canonical form, _jcs raises + # IntentBridgeError: that input cannot be evaluated, which is not a mismatch. + if not compare_digest( + _jcs(before_call, "transcript.before.tool_call"), + _jcs(tool_call, "tool_call"), + ): raise AuthorizationMismatch("transcript.before.tool_call does not match execution") if not isinstance(transcript.get("after"), dict): raise AuthorizationMismatch("transcript.after must contain the execution result") From 7cefdbf47b13ff6fbdfb5165a55fcc71077d485a Mon Sep 17 00:00:00 2001 From: "Altru.dev" Date: Sat, 12 Sep 2026 14:22:01 -0700 Subject: [PATCH 2/2] test(intent-bridge): pin canonicalization classification Signed-off-by: altrudev <266135212+altrudev@users.noreply.github.com> --- tests/test_intent_bridge.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_intent_bridge.py b/tests/test_intent_bridge.py index 3f6b62f8..120bec37 100644 --- a/tests/test_intent_bridge.py +++ b/tests/test_intent_bridge.py @@ -355,3 +355,19 @@ def test_transcript_call_that_is_not_an_object_stays_an_authorization_mismatch( transcript={"before": {"tool_call": bad}, "after": {"status": "accepted"}}, now=150, ) + +@pytest.mark.parametrize("bad", [{"approved": 2**60}, {"approved": float("nan")}]) +def test_uncanonicalizable_transcript_call_is_intentbridgeerror_not_mismatch( + bad: dict, +) -> None: + """An unrepresentable transcript call cannot be evaluated; it is not a mismatch.""" + bridge, key, declaration, intent, args, tool_call, _ = _fixture() + transcript_call = {"name": "send_invoice", "arguments": bad} + with pytest.raises(IntentBridgeError, match="transcript.before.tool_call") as excinfo: + verify_bridge( + bridge, {**key_to_jwk(key), "kid": "key-7"}, declaration=declaration, + pic_intent_digest=intent, pic_args_digest=args, tool_call=tool_call, + transcript={"before": {"tool_call": transcript_call}, "after": {"status": "accepted"}}, + now=150, + ) + assert not isinstance(excinfo.value, AuthorizationMismatch)