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
53 changes: 47 additions & 6 deletions examples/action-receipts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,55 @@ The verifier checks the receipt independently of the core Trust Record:
- verify `previous_receipt_hash` when the profile uses hash-chain ordering; and
- report the receipt result separately from the controller decision.

## Fixture cases
## Conformance fixtures

| Case | Receipt verification result | Controller outcome | Interpretation |
The files under `conformance/` provide machine-checkable cases for the
informative action-receipt rules. The fixtures use
`trace.action_receipt.conformance.v0` as a test-profile identifier. This
fixture set leaves the TRACE wire profile and `schema/trace-claim.json`
unchanged.

Each fixture contains:

- `context`, which supplies the expected session, call, receipt-chain
predecessor, and freshness policy;
- `action`, including the canonical action preimage and its `action_ref`;
- `trusted_issuer_keys`, keyed by the pinned `issuer_key_id`;
- detached `evidence` and a signed `receipt`, except in the missing-receipt
case; and
- `expected`, which records the result, controller outcome, failure codes, and
warnings a conforming verifier should return.

The fixture contract uses RFC 8785 JSON Canonicalization Scheme (JCS) bytes for
three operations:

1. `action_ref` is SHA-256 over `agent_id`, `action_type`, `action_scope`, and
`action_timestamp`.
2. `evidence_hash` is SHA-256 over the detached `evidence` object.
3. The Ed25519 signature covers the receipt object with only `signature`
removed. The verifier resolves the key through `trusted_issuer_keys`; the
receipt cannot authenticate itself with an embedded key.

| Fixture | Receipt result | Controller outcome | Required interpretation |
|---|---|---|---|
| Valid accepted action | `receipt_valid_accepted` | `accepted` | The action request was well evidenced and accepted by the controller. |
| Missing required receipt | `receipt_missing_required` | unknown | The profile required action evidence, but the consequential action had no receipt. |
| Signature or key mismatch | `receipt_invalid` | unknown | The receipt cannot be trusted, even if its payload claims success. |
| Valid controller rejection | `receipt_valid_rejected` | `rejected` | The downstream authority rejected the action; this is valid negative evidence, not malformed evidence. |
| `01-valid-controller-accepted.json` | `receipt_valid_accepted` | `accepted` | The controller accepted the bound action. Physical completion remains unproven. |
| `02-valid-controller-rejected.json` | `receipt_valid_rejected` | `aborted` | The verifier accepts the signed abort as valid negative evidence. |
| `03-missing-required-receipt.json` | `receipt_missing_required` | unknown | The profile required a receipt and the action has none. |
| `04-signature-key-mismatch.json` | `receipt_invalid` | unknown | The signature does not verify under the pinned issuer key. |
| `05-action-ref-mismatch.json` | `receipt_invalid` | unknown | The signed receipt binds to a different action. |
| `06-stale-receipt.json` | `receipt_invalid` | unknown | The authentic receipt falls outside the configured freshness window. |
| `07-receipt-chain-gap.json` | `receipt_invalid` | unknown | The receipt does not link to the expected predecessor. |
| `08-same-party-self-report.json` | `receipt_valid_accepted` with warning | `accepted` | The evidence verifies, but the issuer is not independent from the gateway. |
| `09-unsupported-physical-completion.json` | `receipt_invalid` | unknown | Base TRACE cannot verify the asserted physical-completion claim. |

`tests/test_action_receipt_fixtures.py` recomputes each digest, verifies each
signature against the pinned key, checks session and call binding, enforces
freshness and receipt-chain ordering, and compares the result with each
fixture's `expected` object. The tests need no ROS installation or network
access.

The pinned JWKs and signatures are public test material. Deployments must use
their own trusted issuer keys.

## Boundary

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "valid-controller-accepted",
"description": "A trusted controller accepts the bound action without claiming physical completion.",
"profile": "trace.action_receipt.conformance.v0",
"context": {
"call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"require_receipt": true,
"verification_time": "2026-07-06T15:24:00Z",
"max_receipt_age_seconds": 300,
"expected_previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1"
},
"action": {
"agent_id": "spiffe://factory.example/agent/ros2-fibonacci/dev",
"action_type": "ros2.action.example_interfaces/Fibonacci",
"action_scope": "/abort_fibonacci_process",
"action_timestamp": "2026-07-06T15:22:13Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034"
},
"trusted_issuer_keys": {
"did:web:factory.example:safety-controller#ed25519-2026q3": {
"kty": "OKP",
"crv": "Ed25519",
"x": "0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc"
}
},
"evidence": {
"terminal_state": "accepted",
"physical_completion_claim": "none",
"completeness_claim": "not_proven"
},
"receipt": {
"issuer": "did:web:factory.example:safety-controller",
"issuer_key_id": "did:web:factory.example:safety-controller#ed25519-2026q3",
"issuer_independence": "separate_process",
"linked_call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034",
"evidence_type": "application/vnd.agentrust.action-receipt+json",
"evidence_hash": "sha256:cf43260df01d89b5ce9ed0dce484ae1e3331fb477efda92cd0fd9365df9089d7",
"previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1",
"issued_at": "2026-07-06T15:22:15Z",
"decision": "accepted",
"signature": "rJTXe4XR0yUz1h3rMFP-KFra2mds_zFvHl3L5xmD_7m4jxokIPa87oaBAnAI4j3-WQ_lnzeuYf_rb11O3JcbDg"
},
"expected": {
"status": "receipt_valid_accepted",
"controller_outcome": "accepted",
"failures": [],
"warnings": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "valid-controller-rejected",
"description": "A trusted controller aborts the bound action; the receipt remains valid negative evidence.",
"profile": "trace.action_receipt.conformance.v0",
"context": {
"call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"require_receipt": true,
"verification_time": "2026-07-06T15:24:00Z",
"max_receipt_age_seconds": 300,
"expected_previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1"
},
"action": {
"agent_id": "spiffe://factory.example/agent/ros2-fibonacci/dev",
"action_type": "ros2.action.example_interfaces/Fibonacci",
"action_scope": "/abort_fibonacci_process",
"action_timestamp": "2026-07-06T15:22:13Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034"
},
"trusted_issuer_keys": {
"did:web:factory.example:safety-controller#ed25519-2026q3": {
"kty": "OKP",
"crv": "Ed25519",
"x": "0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc"
}
},
"evidence": {
"terminal_state": "aborted",
"physical_completion_claim": "none",
"completeness_claim": "not_proven"
},
"receipt": {
"issuer": "did:web:factory.example:safety-controller",
"issuer_key_id": "did:web:factory.example:safety-controller#ed25519-2026q3",
"issuer_independence": "separate_process",
"linked_call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034",
"evidence_type": "application/vnd.agentrust.action-receipt+json",
"evidence_hash": "sha256:828f180b492a22d616fbe0dcdd9af66a7a4e75f62f3dd7c29b5715c46365d40f",
"previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1",
"issued_at": "2026-07-06T15:22:15Z",
"decision": "rejected",
"signature": "Hf0PYlVG3ACx9k0dtNbkDy_zxRBcUtlFFvXLQ1PcdtxLUqx-oPPlXKSnzSZNUYOtMNChKPVo_3dP0QcGOpGkCw"
},
"expected": {
"status": "receipt_valid_rejected",
"controller_outcome": "aborted",
"failures": [],
"warnings": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "missing-required-receipt",
"description": "The profile requires an action receipt, but the consequential action has none.",
"profile": "trace.action_receipt.conformance.v0",
"context": {
"call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"require_receipt": true,
"verification_time": "2026-07-06T15:24:00Z",
"max_receipt_age_seconds": 300,
"expected_previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1"
},
"action": {
"agent_id": "spiffe://factory.example/agent/ros2-fibonacci/dev",
"action_type": "ros2.action.example_interfaces/Fibonacci",
"action_scope": "/abort_fibonacci_process",
"action_timestamp": "2026-07-06T15:22:13Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034"
},
"trusted_issuer_keys": {
"did:web:factory.example:safety-controller#ed25519-2026q3": {
"kty": "OKP",
"crv": "Ed25519",
"x": "0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc"
}
},
"expected": {
"status": "receipt_missing_required",
"controller_outcome": "unknown",
"failures": [
"receipt_missing"
],
"warnings": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "signature-key-mismatch",
"description": "The receipt signature does not verify against the pinned issuer key.",
"profile": "trace.action_receipt.conformance.v0",
"context": {
"call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"require_receipt": true,
"verification_time": "2026-07-06T15:24:00Z",
"max_receipt_age_seconds": 300,
"expected_previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1"
},
"action": {
"agent_id": "spiffe://factory.example/agent/ros2-fibonacci/dev",
"action_type": "ros2.action.example_interfaces/Fibonacci",
"action_scope": "/abort_fibonacci_process",
"action_timestamp": "2026-07-06T15:22:13Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034"
},
"trusted_issuer_keys": {
"did:web:factory.example:safety-controller#ed25519-2026q3": {
"kty": "OKP",
"crv": "Ed25519",
"x": "0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc"
}
},
"evidence": {
"terminal_state": "accepted",
"physical_completion_claim": "none",
"completeness_claim": "not_proven"
},
"receipt": {
"issuer": "did:web:factory.example:safety-controller",
"issuer_key_id": "did:web:factory.example:safety-controller#ed25519-2026q3",
"issuer_independence": "separate_process",
"linked_call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034",
"evidence_type": "application/vnd.agentrust.action-receipt+json",
"evidence_hash": "sha256:cf43260df01d89b5ce9ed0dce484ae1e3331fb477efda92cd0fd9365df9089d7",
"previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1",
"issued_at": "2026-07-06T15:22:15Z",
"decision": "accepted",
"signature": "AXHgPUo3mdXmZcTlre0bzKQuVLi3AqyJ4zYTG2hZ_jrRVI_auF5ktk2j2NFDvdqrIaFPwZmcOgTaxNKTNTbmCQ"
},
"expected": {
"status": "receipt_invalid",
"controller_outcome": "unknown",
"failures": [
"signature_or_key_mismatch"
],
"warnings": []
}
}
54 changes: 54 additions & 0 deletions examples/action-receipts/conformance/05-action-ref-mismatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "action-ref-mismatch",
"description": "The signed receipt is not bound to the expected canonical action reference.",
"profile": "trace.action_receipt.conformance.v0",
"context": {
"call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"require_receipt": true,
"verification_time": "2026-07-06T15:24:00Z",
"max_receipt_age_seconds": 300,
"expected_previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1"
},
"action": {
"agent_id": "spiffe://factory.example/agent/ros2-fibonacci/dev",
"action_type": "ros2.action.example_interfaces/Fibonacci",
"action_scope": "/abort_fibonacci_process",
"action_timestamp": "2026-07-06T15:22:13Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034"
},
"trusted_issuer_keys": {
"did:web:factory.example:safety-controller#ed25519-2026q3": {
"kty": "OKP",
"crv": "Ed25519",
"x": "0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc"
}
},
"evidence": {
"terminal_state": "accepted",
"physical_completion_claim": "none",
"completeness_claim": "not_proven"
},
"receipt": {
"issuer": "did:web:factory.example:safety-controller",
"issuer_key_id": "did:web:factory.example:safety-controller#ed25519-2026q3",
"issuer_independence": "separate_process",
"linked_call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"action_ref": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"evidence_type": "application/vnd.agentrust.action-receipt+json",
"evidence_hash": "sha256:cf43260df01d89b5ce9ed0dce484ae1e3331fb477efda92cd0fd9365df9089d7",
"previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1",
"issued_at": "2026-07-06T15:22:15Z",
"decision": "accepted",
"signature": "mwnX6Uh36J6wZkcKEagfROP35AQLZ6zcfcHDKlOL_XJ20pk-6zZKeX2L5o3aArz3uDkc0-F_TNEiYgMu0McNAg"
},
"expected": {
"status": "receipt_invalid",
"controller_outcome": "unknown",
"failures": [
"action_ref_mismatch"
],
"warnings": []
}
}
54 changes: 54 additions & 0 deletions examples/action-receipts/conformance/06-stale-receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "stale-receipt",
"description": "The receipt is authentic but older than the profile freshness window.",
"profile": "trace.action_receipt.conformance.v0",
"context": {
"call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"require_receipt": true,
"verification_time": "2026-07-06T15:24:00Z",
"max_receipt_age_seconds": 300,
"expected_previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1"
},
"action": {
"agent_id": "spiffe://factory.example/agent/ros2-fibonacci/dev",
"action_type": "ros2.action.example_interfaces/Fibonacci",
"action_scope": "/abort_fibonacci_process",
"action_timestamp": "2026-07-06T15:22:13Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034"
},
"trusted_issuer_keys": {
"did:web:factory.example:safety-controller#ed25519-2026q3": {
"kty": "OKP",
"crv": "Ed25519",
"x": "0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc"
}
},
"evidence": {
"terminal_state": "accepted",
"physical_completion_claim": "none",
"completeness_claim": "not_proven"
},
"receipt": {
"issuer": "did:web:factory.example:safety-controller",
"issuer_key_id": "did:web:factory.example:safety-controller#ed25519-2026q3",
"issuer_independence": "separate_process",
"linked_call_id": "01986d7c-6b2f-7c68-9ff8-3e2f9d0db337",
"session_id": "trace-session-2026-07-06T15:22:11Z",
"action_ref": "sha256:c2e97956de8b86d825780fcecea601a6eea02616855e6f0aa19d10bd351fe034",
"evidence_type": "application/vnd.agentrust.action-receipt+json",
"evidence_hash": "sha256:cf43260df01d89b5ce9ed0dce484ae1e3331fb477efda92cd0fd9365df9089d7",
"previous_receipt_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1",
"issued_at": "2026-07-06T15:00:00Z",
"decision": "accepted",
"signature": "M4yALg3h8lfb0syFk-c2kKGZg9HlIhE8bniCMsvmz-VAIBOSpkfqRGxT2UzS-fvuTi3sGhobGwiioXAaQSxxCQ"
},
"expected": {
"status": "receipt_invalid",
"controller_outcome": "unknown",
"failures": [
"receipt_stale"
],
"warnings": []
}
}
Loading
Loading