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
1 change: 1 addition & 0 deletions tests/conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ Spec: [trace-a2a-profile.md](../../docs/spec/trace-a2a-profile.md), [provenance-
| ACTION-005 | MUST | A requested action outside the effective delegated scope is classified as authorization-invalid, not malformed provenance. | `SCOPE_NOT_PERMITTED`. |
| ACTION-006 | MUST | A valid delegated action denied by local policy is classified as authorization-invalid, not malformed provenance. | `SCOPE_NOT_PERMITTED`. |
| ACTION-007 | MUST | A valid delegated action whose controller outcome is negative remains valid evidence of a negative outcome. | `valid_negative_outcome`. |
| ACTION-008 | MUST | An action whose delegation chain contains a credential with an invalid signature is rejected as provenance-invalid before authorization or outcome handling. | `INVALID_CREDENTIAL`. |
33 changes: 32 additions & 1 deletion tests/conformance/test_profile_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, replace

import pytest
from cryptography.hazmat.primitives.asymmetric import ec
Expand Down Expand Up @@ -404,3 +404,34 @@ def test_action_007_controller_rejection_is_valid_negative_outcome() -> None:
LocalPolicy.of(["robot.move"]),
)
assert result == _ActionEvidenceResult("valid_negative_outcome", "CONTROLLER_REJECTED")


def test_action_008_invalid_delegation_signature_is_provenance_invalid() -> None:
chain = _action_chain()
records = _records(chain)
leaf = chain[-1]
tampered_signature = f"{int(leaf.signature[:2], 16) ^ 1:02x}{leaf.signature[2:]}"
bad_chain = [*chain[:-1], replace(leaf, signature=tampered_signature)]
evidence = _action_evidence(
records,
requested_capability="robot.inspect",
controller_decision="rejected",
)
restrictive_policy = LocalPolicy.of(["robot.move"])
permissive_policy = LocalPolicy.of(["robot.move", "robot.inspect"])

# The controls establish both downstream classifications that invalid provenance must preempt.
assert _verify_action_evidence(chain, records, evidence, restrictive_policy) == (
_ActionEvidenceResult("authorization_invalid", "SCOPE_NOT_PERMITTED")
)
assert _verify_action_evidence(chain, records, evidence, permissive_policy) == (
_ActionEvidenceResult("valid_negative_outcome", "CONTROLLER_REJECTED")
)

# ACTION-008 verifies provenance validation precedes authorization and controller outcome classification.
assert _verify_action_evidence(bad_chain, records, evidence, restrictive_policy) == (
_ActionEvidenceResult("provenance_invalid", "INVALID_CREDENTIAL")
)
assert _verify_action_evidence(bad_chain, records, evidence, permissive_policy) == (
_ActionEvidenceResult("provenance_invalid", "INVALID_CREDENTIAL")
)
Loading