Skip to content
Open
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
25 changes: 19 additions & 6 deletions conformance/src/trace_tests/inclusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,36 @@ class InclusionError(ValueError):
def _outside_profile(value: Any) -> str | None:
"""Name the first value registry-anchor-v1 section 1 excludes, or None.

Non-integer numbers (NaN and the infinities among them) and integers outside
-(2**53 - 1) to 2**53 - 1 are outside the anchor-leaf profile: two
implementations of the four serialization rules write different bytes for
them, so a proof over them only proves what Python's ``json.dumps`` wrote.
Iterative, because the claim is attacker-controlled and may be deep.
Non-integer numbers (NaN and the infinities among them), integers outside
-(2**53 - 1) to 2**53 - 1, and unpaired UTF-16 surrogates are outside the
anchor-leaf profile: two implementations of the five serialization rules
write different bytes or cannot represent them, so a proof over them only
proves what Python's ``json.dumps`` wrote. Iterative, because the claim is
attacker-controlled and may be deep.
"""
stack = [value]
while stack:
v = stack.pop()
if isinstance(v, bool) or v is None or isinstance(v, str):
if isinstance(v, bool) or v is None:
continue
if isinstance(v, str):
try:
v.encode("utf-8")
except UnicodeEncodeError:
return "unpaired UTF-16 surrogate"
continue
if isinstance(v, int):
if not -_SAFE_INT <= v <= _SAFE_INT:
return f"integer {v} is outside the safe-integer range"
elif isinstance(v, float):
return f"non-integer number {v!r}"
elif isinstance(v, dict):
for k in v:
if isinstance(k, str):
try:
k.encode("utf-8")
except UnicodeEncodeError:
return "unpaired UTF-16 surrogate in object key"
stack.extend(v.values())
elif isinstance(v, (list, tuple)):
stack.extend(v)
Expand Down
6 changes: 3 additions & 3 deletions docs/crosswalks/acta-decision-receipts.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Acta is an IETF draft format for signed receipts that record whether an AI agent

[Spec section 3.3.3](../../spec/trace-v0.2.md#333-action-receipts-for-embodied-workflows-informative) describes action receipts as evidence that sits below a Trust Record: a per-action signed statement, bound to a call or session, that a verifier checks independently of the core record. The [embodied-workflow fixtures](https://github.com/agentrust-io/trace-spec/blob/main/examples/action-receipts/README.md) show one profile of that pattern, for a physical controller (a robot arm, a safety monitor) signing an assertion about a physical action.

This cross-walk describes a second profile of the same pattern: an **Acta decision receipt**, evidencing a software decision (an AI agent's tool call, decided by a local policy gate before it runs) rather than a physical one. The verification pattern in 3.3.3 is deliberately domain-agnostic; the intent here is to show it composes with an existing, independently specified receipt format without a wire-format change to either side.
This cross-walk describes a second profile of the same pattern: an **Acta decision receipt**, evidencing a software decision (an AI agent's tool call, decided by a local policy gate before it runs) rather than a physical one. The external-evidence verification rule in 3.3.2 underlies the informative action-receipt profile in 3.3.3; the intent here is to show it composes with an existing, independently specified receipt format without a wire-format change to either side.

This follows the acceptance bar raised on [trace-spec#97](https://github.com/agentrust-io/trace-spec/issues/97): carry Acta as external evidence by digest/chain head and issuer/key metadata rather than embedding a second receipt schema into the Trust Record; state exactly which Acta fields satisfy the action-receipt verifier obligations; and keep the physical/software boundary explicit.

Expand Down Expand Up @@ -49,15 +49,15 @@ Working fixtures: [`examples/action-receipts/acta/`](https://github.com/agentrus

## Field mapping

Field names on the left are exact TRACE terms as used in [spec section 3.3.3](../../spec/trace-v0.2.md#333-action-receipts-for-embodied-workflows-informative) and the [embodied fixture shape](https://github.com/agentrust-io/trace-spec/blob/main/examples/action-receipts/README.md#shared-receipt-shape). Field names on the right are exact Acta revision 03 fields; the revision 02 envelopes of fixtures `01` to `06` carry the same names.
Field names on the left follow the external-evidence fields in [spec section 3.3.2](../../spec/trace-v0.2.md#332-external-execution-evidence-optional), the informative profile in [section 3.3.3](../../spec/trace-v0.2.md#333-action-receipts-for-embodied-workflows-informative) and the [embodied fixture shape](https://github.com/agentrust-io/trace-spec/blob/main/examples/action-receipts/README.md#shared-receipt-shape). Field names on the right are exact Acta revision 03 fields; the revision 02 envelopes of fixtures `01` to `06` carry the same names.

| TRACE action-receipt obligation | Acta field(s) | Notes |
|---|---|---|
| `receipt.issuer` | `payload.issuer_id` | REQUIRED common field (Acta s2.2); MUST match `signature.kid`, so issuer identity and key identity cannot silently diverge inside one receipt. |
| `receipt.issuer_key_id` | `signature.kid` | Resolved the same way 3.3.3 describes: through a pinned, manifest-bound, or otherwise trusted key set, not carried in the receipt as a public key. |
| `receipt.linked_call_id` / `session_id` | `payload.session_id` | Acta s3.1 binds at session level; `session_id` is OPTIONAL, and when present MUST NOT contain PII or be correlatable across sessions unless the operator explicitly configures session binding. Acta has no per-call identifier field; a TRACE-composing deployment gets call-level binding by carrying the tool input's digest in `payload_digest` (below) or by a deployment extension field, and should say which. |
| `receipt.evidence_type` | `payload.type` | Namespaced receipt type (Acta s2.2), here `protectmcp:decision`. |
| `receipt.evidence_hash` | `payload.payload_digest` | OPTIONAL common field (Acta s2.2): `{hash, size, preview?}` over associated data (tool input/output) too large to embed. Serves exactly the external-evidence-by-digest role 3.3.3 describes. |
| `receipt.evidence_hash` | `payload.payload_digest` | OPTIONAL common field (Acta s2.2): `{hash, size, preview?}` over associated data (tool input/output) too large to embed. Serves the external-evidence-by-digest role 3.3.2 describes; 3.3.3 builds on that rule for action receipts. |
| `receipt.previous_receipt_hash` | `payload.previousReceiptHash` | `"sha256:"` plus lowercase hex over the predecessor's full signed receipt including its signature, omitted on the first receipt (Acta s6.7). Note the deliberate difference from formats that hash payload only: re-signing an identical payload yields a distinct chain link. |
| `receipt.decision` | `payload.decision` (+ `payload.reason`) | `reason` (OPTIONAL) names the machine-readable cause, e.g. `policy_block`, `tier_insufficient`, beyond the bare outcome. |
| `receipt.signature` | `signature.sig` (with `signature.alg`) | `EdDSA` is mandatory-to-implement; `ES256` optional, `ML-DSA-65` recommended for new deployments (Acta s6.9). |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"profile": "trace.verifier_compatibility.proposal.v0",
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text"
"status": "Obligations 2 and 3 normative in spec/trace-v0.2.md \u00a73.3; Obligations 1 and 4 deferred"
},
"verifier": {
"accepted_profiles": [
Expand Down
46 changes: 26 additions & 20 deletions examples/verifier-compatibility/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# Verifier-compatibility vectors (proposed, not accepted)
# Verifier-compatibility vectors

> These fixtures encode the obligations proposed in
> [agentrust-io/trace-spec#116](https://github.com/agentrust-io/trace-spec/issues/116).
> **No normative text for them has been accepted.** Do not read a passing vector here
> as a conformance requirement.
> These fixtures encode Obligations 2 and 3 of
> [agentrust-io/trace-spec#116](https://github.com/agentrust-io/trace-spec/issues/116),
> which are normative in `spec/trace-v0.2.md` §3.3 and documented in
> `docs/verification.md` ("Step 4: Check the EAT profile"). Obligations 1 (emitter
> version declarations) and 4 (downgrade disclosure) remain deferred.

Compliance evidence is verified years after it is issued, so the artifact routinely
outlives the verifier build that reads it. Nothing in `docs/verification.md` currently
says what a verifier must do about version skew, which leaves two failure modes open:
a revision silently orphans existing evidence, or a verifier "helpfully" accepts an
artifact under semantics it does not implement.

The proposal is four obligations: records carry an explicit profile, a verifier
declares the set it supports and refuses anything outside it, the verification
statement names the profile it ran under, and any fallback is disclosed rather than
silent.
outlives the verifier build that reads it. `spec/trace-v0.2.md` §3.3 and
`docs/verification.md` ("Step 4: Check the EAT profile") address version skew by
requiring a nonempty `accepted_profiles` set and recording both the verified `profile`
and the complete `accepted_profiles` set in the verification result. Without those
checks, two failure modes stay open: a revision silently orphans existing evidence, or
a verifier "helpfully" accepts an artifact under semantics it does not implement.

#116 proposed four obligations: records carry an explicit profile (Obligation 1,
deferred for standalone emitter version declarations beyond `eat_profile`), a verifier
declares the nonempty `accepted_profiles` set it supports and refuses anything outside
it (Obligation 2, normative in `spec/trace-v0.2.md` §3.3), the verification statement
records both the verified `profile` and the complete `accepted_profiles` set
(Obligation 3, normative in `spec/trace-v0.2.md` §3.3), and any fallback is disclosed
rather than silent (Obligation 4, deferred).

## The vectors

Expand Down Expand Up @@ -85,12 +91,12 @@ reports one generic refusal for all of them conforms, and two controls hold that
`test_a_generic_refusal_is_separated_by_nothing` in the separation module. Both fail the
day an assertion on the refusal's cause comes back.

This was settled on review, and the set was stricter than its text until it was. The draft
normative text for #116, held for a maintainer to carry and not part of this change, says
a verifier SHOULD report refusal-for-an-unimplemented-profile distinguishably from a
verification failure, which is coarser than a rule name and is a SHOULD, and it declines
to mandate any field name. Asserting the label was this set asking more of a foreign
implementation than the text it encodes.
This was settled on review, and the set was stricter than its text until it was. The
proposal for #116 said a verifier SHOULD report refusal-for-an-unimplemented-profile
distinguishably from a verification failure, which is coarser than a rule name, and
neither #116 nor `spec/trace-v0.2.md` §3.3 mandates any refusal field name. Asserting
the label was this set asking more of a foreign implementation than the specification
requires.

The label stays in the JSON because it tells a reader what each vector is for. This
library's own refusal messages are pinned separately, in
Expand Down
5 changes: 4 additions & 1 deletion examples/verifier-compatibility/gen_vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ def fixture(
"profile": PROFILE,
"proposal": {
"issue": "agentrust-io/trace-spec#116",
"status": "under review, not accepted normative text",
"status": (
"Obligations 2 and 3 normative in spec/trace-v0.2.md §3.3; "
"Obligations 1 and 4 deferred"
),
},
"verifier": {
"accepted_profiles": accepted_profiles,
Expand Down
Loading
Loading