Embodied Action Evidence Profile¶
Status: Proposal v0.1 | Related: verification-library.md, session-policy.md, issue #337, trace-spec#66
This profile defines a small evidence shape for embodied-agent workflows where an agent requests a physical-world action and cMCP records the governance decision, controller handoff, and optional external receipt as auditable evidence.
The profile builds on the existing external_execution_evidence audit entry field. It does not change the core audit schema in v0.1. Instead, it defines how an embodied-action producer should construct the detached evidence payload that is committed by external_execution_evidence.evidence_hash.
Scope¶
This profile is about evidence binding, not actuation or safety certification.
It can prove that:
- the cMCP gateway recorded a specific tool call and policy decision;
- the call was bound to a detached embodied-action evidence payload;
- an external issuer signed an evidence envelope for the same audit
call_id; - a verifier with the detached payload and issuer key can recompute the hashes and verify the binding.
It cannot prove that:
- a physical action occurred;
- the action was safe;
- a robot, controller, or plant-floor system satisfied IEC 61508, ISO 13849, or any other functional-safety regime;
- the external issuer is trustworthy beyond the configured issuer key.
Evidence Layers¶
An embodied-action record has three layers:
| Layer | Field or artifact | Purpose |
|---|---|---|
| cMCP audit entry | call_id, policy_decision, request_payload_hash, response_payload_hash | Records what the gateway decided and forwarded. |
| Evidence envelope | external_execution_evidence | Binds an external issuer signature to the audit call_id. |
| Detached payload | Embodied action evidence JSON | Defines the action, governance context, controller handoff, and optional downstream receipt. |
The evidence envelope remains the existing cMCP shape:
{
"issuer": "spiffe://factory.example/controller/robot-cell-7",
"issuer_key_id": "<sha256 of issuer public key>",
"signature": "<base64url Ed25519 signature>",
"evidence_hash": "sha256:<hash of detached embodied-action evidence payload>",
"evidence_type": "controller-execution-receipt/v1",
"linked_call_id": "<audit entry call_id>"
}
linked_call_id MUST equal the cMCP audit entry call_id. It MUST NOT be overloaded with a controller-specific action_ref. If a controller or receipt system uses a content-derived action identifier, that identifier belongs in the detached payload as action_ref.
Relationship to TRACE Action Receipts¶
TRACE verification.action_receipts defines whether a verifier expects per-action evidence below the session-level TRACE Claim. This cMCP profile is a concrete embodied-action evidence shape that can satisfy that axis for externally consequential tool or controller handoffs.
For embodied AI, verification.action_receipts: required SHOULD mean every externally consequential action has offline-verifiable receipt evidence bound to the session or cMCP audit call_id. It SHOULD NOT mean TRACE proves physical completion, controller safety, functional-safety certification, or that the real world changed as intended.
Profile-aware verifiers SHOULD keep three layers separate:
| Layer | Verifier question | Example evidence |
|---|---|---|
| Session | Did this agent session run under the appraised policy/runtime context? | TRACE Claim, audit bundle, policy hashes |
| Action | Was this specific action handoff authorized and receipt-bound? | Detached payload, action_ref, receipt signature |
| Physical outcome | What did a controller, monitor, human, or safety system observe? | Controller verdicts, monitor logs, external safety records |
When action receipts are required by verifier policy, profile-aware verifiers SHOULD:
- recompute
action_reffrom the canonical action preimage; - verify receipt signatures against pinned or Agent Manifest-bound issuer keys, not only keys embedded in the receipt itself;
- verify receipt ordering when receipts are hash-chained;
- verify the receipt binds to the TRACE session, cMCP
call_id, oraction_ref; - classify missing, stale, mismatched, or unverifiable receipts distinctly from negative controller outcomes;
- treat a valid
rejectedreceipt as evidence that the controller rejected the action, not as a verifier failure by itself.
This gives cMCP an action-level evidence convention that composes with TRACE without making the gateway an actuation path or a safety authority.
Detached Payload¶
The detached payload is the JSON object hashed by external_execution_evidence.evidence_hash. For this profile, JSON payloads MUST be canonicalized with RFC 8785/JCS and hashed as UTF-8 bytes.
Minimum v0.1 payload:
{
"profile": "cmcp.embodied_action_evidence.v0",
"call_id": "<audit entry call_id>",
"action_ref": "sha256:<hash of canonical action preimage>",
"agent_id": "spiffe://factory.example/agent/material-movement/dev",
"action_type": "move_material",
"action_scope": "robot-cell-7/material-bin-a",
"action_timestamp": "2026-06-25T16:30:00Z",
"governance_decision": "allow",
"policy_bundle_hash": "sha256:<policy bundle hash>",
"tool_catalog_hash": "sha256:<tool catalog hash>",
"controller_target": "spiffe://factory.example/controller/robot-cell-7",
"handoff_timestamp": "2026-06-25T16:30:01Z",
"receipt": {
"receipt_type": "controller-execution-receipt/v1",
"receipt_hash": "sha256:<hash of controller-native receipt>",
"verdict": "accepted"
},
"limitations": [
"receipt proves issuer signature over an assertion, not physical completion"
]
}
Required fields:
profilecall_idaction_refagent_idaction_typeaction_scopeaction_timestampgovernance_decisionpolicy_bundle_hashtool_catalog_hashcontroller_targethandoff_timestamp
Optional fields:
receiptapproval_contextoperator_approval_idlimitations
action_timestamp and handoff_timestamp MUST be RFC3339 UTC strings with a Z suffix. Producers SHOULD NOT use integer millisecond timestamps in the profile preimage because different producers otherwise hash the same moment into different identifiers.
Action Reference¶
action_ref is a content-derived identifier for the action request. It is separate from cMCP call_id.
For v0.1, compute:
action_ref = "sha256:" + SHA-256(JCS({
"agent_id": agent_id,
"action_type": action_type,
"action_scope": action_scope,
"action_timestamp": action_timestamp
}))
This keeps call_id as the audit-chain binding and gives external controllers a stable content identifier they can reproduce without knowing cMCP internals.
Governance Decision¶
governance_decision records the decision cMCP made before the handoff. Allowed values:
allowdenyadvisory_denyfault
The value SHOULD match the audit entry policy_decision. If it does not match, a profile-aware verifier MUST report the detached payload as inconsistent with the audit entry.
Agent Manifest Binding¶
When the TRACE Claim includes gateway.agent_identity, profile-aware verifiers SHOULD compare:
- detached payload
agent_id; gateway.agent_identity.agent_id;- signed Agent Manifest
agent_id, when the manifest is supplied.
If all three are present, they MUST match. A mismatch means the evidence no longer answers "which reviewed agent identity requested this embodied action?"
When gateway.agent_identity is absent, verifiers MAY still verify the external receipt and audit-chain binding, but they MUST NOT claim that the action was bound to a signed Agent Manifest identity.
Receipt States¶
The receipt object is optional because some embodied-action evidence is produced before a downstream controller responds.
Profile-aware verifiers SHOULD classify receipt state as:
| State | Meaning |
|---|---|
absent | No downstream receipt was attached. The audit entry can still verify, but no controller assertion was provided. |
untrusted | Receipt is present, but the verifier has no trusted issuer key. |
invalid | Receipt is present but hash, signature, or call binding fails. |
stale | Receipt timestamp is outside verifier policy. |
accepted | Receipt verifies and issuer verdict is accepted. |
rejected | Receipt verifies and issuer verdict is rejected. |
The base cMCP audit bundle verifier validates the evidence envelope signature and linked_call_id when external_evidence_keys are provided. The profile-aware verify_embodied_action_evidence() helper adds detached-payload checks without changing the base audit bundle verification contract.
ROS 2 Action Fixture¶
The repository includes a ROS 2 Kilted rclpy fixture at tests/fixtures/embodied-action-evidence/ros2-fibonacci-aborted.json.
The fixture is based on a tutorial-scope example_interfaces/action/Fibonacci action run where:
- the client-side and server-side serialized goal preimage hashes matched;
- the ROS action goal UUID matched on both sides;
- the server reported a terminal
abortedoutcome; - the receipt uses
physical_completion_claim: none.
The fixture exercises profile fields for:
ros2.goal_uuid;ros2.goal_preimage_hash;ros2.goal_preimage_method;- ROS distribution and RMW implementation;
- terminal controller state;
- receipt issuer role and issuer independence.
It is intentionally a binding fixture, not a live ROS dependency in CI. It does not prove physical completion, controller honesty, functional safety, cross-RMW stability, or arbitrary ROS interface compatibility.
Verifier Flow¶
Given a TRACE Claim, audit bundle, detached embodied-action payload, optional Agent Manifest, and trusted issuer keys:
- Verify the TRACE Claim and audit chain as usual.
- Locate the audit entry by
call_id. - Verify
external_execution_evidence.linked_call_id == audit_entry.call_id. - Verify the external evidence envelope signature with
external_evidence_keys. - Canonicalize the detached payload with JCS and verify its hash equals
external_execution_evidence.evidence_hash. - Verify detached payload
call_id == audit_entry.call_id. - Recompute
action_reffrom the action preimage fields. - Compare
governance_decisionto the audit entrypolicy_decision. - Compare
policy_bundle_hashandtool_catalog_hashto the TRACE Claim. - If
gateway.agent_identityis present, compare itsagent_idto the detached payloadagent_id. - Classify receipt state using the table above.
A verifier that completes steps 1-10 can claim the embodied-action evidence is bound to the cMCP audit chain and runtime policy context. It still cannot claim physical completion or safety certification.
Compatibility Notes¶
Existing producers that emit a controller-native receipt with a field such as action_ref SHOULD wrap that receipt in the detached payload and keep the cMCP evidence envelope linked_call_id equal to the audit entry call_id.
The profile intentionally preserves the distinction between:
response_payload_hash: the exact response bytes the gateway forwarded;external_execution_evidence.evidence_hash: the detached evidence payload asserted by an external issuer;action_ref: the content-derived identifier for the requested embodied action.
Keeping these three identifiers separate avoids making the gateway claim it observed physical execution.