diff --git a/docs/spec/execution-action-binding-v1-vectors.json b/docs/spec/execution-action-binding-v1-vectors.json new file mode 100644 index 0000000..52705c1 --- /dev/null +++ b/docs/spec/execution-action-binding-v1-vectors.json @@ -0,0 +1,104 @@ +{ + "profile": "cmcp.execution-action-binding-vectors/1", + "contract": "docs/spec/execution-action-binding.md", + "vectors": [ + { + "id": "EAB-001-ascii-sha256", + "preimage": { + "domain": "cmcp.execution-action-binding", + "version": 1, + "agent_id": "spiffe://agent.example/payments/prod", + "action_type": "transfer", + "action_scope": "account:merchant-7", + "action_timestamp": "2026-09-10T16:30:00Z" + }, + "jcs_utf8": "{\"action_scope\":\"account:merchant-7\",\"action_timestamp\":\"2026-09-10T16:30:00Z\",\"action_type\":\"transfer\",\"agent_id\":\"spiffe://agent.example/payments/prod\",\"domain\":\"cmcp.execution-action-binding\",\"version\":1}", + "algorithm": "sha256", + "binding": "sha256:305b05b249b159fa9dea8cbbfaa0c19b71aa867a6da718693c1c7d4b83f52fe4", + "expected": "accept" + }, + { + "id": "EAB-002-nonascii-sha256", + "preimage": { + "domain": "cmcp.execution-action-binding", + "version": 1, + "agent_id": "spiffe://agent.example/payments/prod", + "action_type": "transfer", + "action_scope": "工具/merchant-7", + "action_timestamp": "2026-09-10T16:30:00Z" + }, + "jcs_utf8": "{\"action_scope\":\"工具/merchant-7\",\"action_timestamp\":\"2026-09-10T16:30:00Z\",\"action_type\":\"transfer\",\"agent_id\":\"spiffe://agent.example/payments/prod\",\"domain\":\"cmcp.execution-action-binding\",\"version\":1}", + "algorithm": "sha256", + "binding": "sha256:6c6a43dd37e7762b65063a24fcbf77bb914f95e1ab976405e3e2cafa54b1d08a", + "expected": "accept" + }, + { + "id": "EAB-003-domain-separation", + "preimage": { + "domain": "cmcp.other-binding", + "version": 1, + "agent_id": "spiffe://agent.example/payments/prod", + "action_type": "transfer", + "action_scope": "account:merchant-7", + "action_timestamp": "2026-09-10T16:30:00Z" + }, + "jcs_utf8": "{\"action_scope\":\"account:merchant-7\",\"action_timestamp\":\"2026-09-10T16:30:00Z\",\"action_type\":\"transfer\",\"agent_id\":\"spiffe://agent.example/payments/prod\",\"domain\":\"cmcp.other-binding\",\"version\":1}", + "algorithm": "sha256", + "binding": "sha256:c9fc3b387ec2cf3fc981210a81d6dee3544e272a49c53f836abc726bbfac655a", + "expected": "reject_as_v1_execution_action_binding" + }, + { + "id": "EAB-004-version-separation", + "preimage": { + "domain": "cmcp.execution-action-binding", + "version": 2, + "agent_id": "spiffe://agent.example/payments/prod", + "action_type": "transfer", + "action_scope": "account:merchant-7", + "action_timestamp": "2026-09-10T16:30:00Z" + }, + "jcs_utf8": "{\"action_scope\":\"account:merchant-7\",\"action_timestamp\":\"2026-09-10T16:30:00Z\",\"action_type\":\"transfer\",\"agent_id\":\"spiffe://agent.example/payments/prod\",\"domain\":\"cmcp.execution-action-binding\",\"version\":2}", + "algorithm": "sha256", + "binding": "sha256:c737be0ffa196bbaba266b9d8a375c6b42c76d6f8d47155965cf7111f4bb4010", + "expected": "reject_as_v1_execution_action_binding" + }, + { + "id": "EAB-005-ascii-sha384", + "preimage": { + "domain": "cmcp.execution-action-binding", + "version": 1, + "agent_id": "spiffe://agent.example/payments/prod", + "action_type": "transfer", + "action_scope": "account:merchant-7", + "action_timestamp": "2026-09-10T16:30:00Z" + }, + "jcs_utf8": "{\"action_scope\":\"account:merchant-7\",\"action_timestamp\":\"2026-09-10T16:30:00Z\",\"action_type\":\"transfer\",\"agent_id\":\"spiffe://agent.example/payments/prod\",\"domain\":\"cmcp.execution-action-binding\",\"version\":1}", + "algorithm": "sha384", + "binding": "sha384:14e4e39e8eaaccd693e4120e6df5baaf2a351d4f74d0648ad586232403569d3b7313cb54b5517de7777565633bf30021", + "expected": "accept" + }, + { + "id": "EAB-006-unsupported-algorithm", + "preimage": { + "domain": "cmcp.execution-action-binding", + "version": 1, + "agent_id": "spiffe://agent.example/payments/prod", + "action_type": "transfer", + "action_scope": "account:merchant-7", + "action_timestamp": "2026-09-10T16:30:00Z" + }, + "algorithm": "sha512", + "expected": "reject_unsupported_algorithm" + } + ], + "semantic_adjudication_witnesses": [ + { + "id": "EAB-S001-json-number-equivalence", + "request_a": {"x": 1}, + "request_b": {"x": 1.0}, + "rfc8785_relation": "same_canonical_number_representation", + "observed_downstream_relation": "may_differ_under_existing_cMCP_consumers", + "required_disposition": "adjudicate_against_governing_schema_or_policy_before_action_binding" + } + ] +} diff --git a/docs/spec/execution-action-binding.md b/docs/spec/execution-action-binding.md new file mode 100644 index 0000000..2cc68c0 --- /dev/null +++ b/docs/spec/execution-action-binding.md @@ -0,0 +1,115 @@ +# Execution Action Binding v1 + +Status: proposed normative contract for [#588](https://github.com/agentrust-io/cmcp/issues/588). + +This document defines the canonical action binding consumed by session-independent execution correlation. It does not activate the execution registry by itself; activation remains subject to the admission, terminal-state, audit-transaction, crash/recovery, and replay requirements in [execution-correlation.md](execution-correlation.md). + +## Normative contract + +An execution action binding MUST be computed from the RFC 8785 / JSON Canonicalization Scheme (JCS) UTF-8 encoding of exactly this object: + +```json +{ + "domain": "cmcp.execution-action-binding", + "version": 1, + "agent_id": "", + "action_type": "", + "action_scope": "", + "action_timestamp": "" +} +``` + +The six members above are the complete v1 preimage. A producer MUST NOT add, remove, rename, or reinterpret a member while continuing to call the result a v1 execution action binding. + +### 1. Canonicalization + +The preimage MUST be serialized with RFC 8785/JCS and encoded as UTF-8. Implementations MUST NOT substitute host-language key ordering, ASCII escaping, implementation-defined float rendering, or another serializer that merely agrees on ordinary ASCII examples. + +The canonicalizer used by the current cMCP embodied-action verifier is the existing RFC 8785 implementation exposed through `cmcp_verify.embodied_action.canonical_json_bytes()`. A shared canonicalization primitive does not make two bindings equivalent unless they also share the same domain, version, and preimage definition. + +### 2. Digest representation + +The binding MUST be rendered as: + +```text +: +``` + +`sha256` and `sha384` are accepted algorithms. Any other algorithm identifier MUST be refused. A `sha256` digest therefore carries 64 lowercase hexadecimal characters and a `sha384` digest carries 96. + +The complete rendered digest string is the value stored and compared by execution correlation. Changing the algorithm under an already-reserved `(authenticated agent identity, execution_id)` changes the binding and MUST NOT be treated as the same reservation. + +### 3. Action preimage field set + +The action-specific members are exactly: + +- `agent_id` +- `action_type` +- `action_scope` +- `action_timestamp` + +These are the field set already used by the embodied-action `action_ref` construction. The execution binding adds the domain and version members below so the bytes answer one unambiguous question and remain distinguishable across contract revisions. + +`agent_id` MUST identify the authenticated agent identity under which `execution_id` is reserved. An adapter that derives `action_type`, `action_scope`, or `action_timestamp` from a richer request MUST preserve every distinction that the governing action semantics require for logical-operation identity, or refuse the request before producing a binding. + +### 4. Domain separation + +`domain` MUST be the exact string: + +```text +cmcp.execution-action-binding +``` + +The domain member is inside the JCS preimage and therefore inside the digest. It MUST NOT be carried only beside the digest or inferred from the call site. + +This prevents the same canonical JSON object, hashed for a different cMCP purpose, from acquiring execution-action semantics merely because its digest bytes happen to match. + +### 5. Version discrimination + +`version` MUST be the JSON integer `1` for this contract. The version member is inside the JCS preimage and therefore inside the digest. + +A later version that changes the field set or interpretation MUST use a different version value. An implementation MUST NOT reinterpret a v1 digest using a later contract or accept a later-version binding as v1 merely because the remaining action fields happen to match. + +## Correlation, replay, and comparison semantics + +The authoritative reservation key is `(authenticated agent identity, execution_id)`. The registry consumes the action binding as an opaque rendered digest and compares it exactly. + +For an existing reservation: + +- the same binding may enter the correlation/retry classification path, subject to the terminal-state rules; +- a different binding is a conflicting or mutated logical operation and MUST be refused before upstream invocation; +- a matching binding is not permission to replay an action after a terminal or `outcome_unknown` state; +- the binding does not establish that an external effect occurred. + +The three identities remain distinct: + +```text +attempt identity != logical operation identity != external outcome identity +``` + +`call_id` identifies an attempt, the execution action binding contributes to logical-operation identity, and independently verifiable external evidence is required for claims about external outcome. + +## Semantic adequacy of the binding + +Byte-level interoperability and semantic adequacy are separate requirements. + +Two implementations can correctly produce the same JCS bytes while a downstream authorization, admission, gating, or invocation-input consumer still treats the originating requests differently. Such a differential is an adjudication witness, not automatic proof that JCS or the binding is wrong. + +The governing semantics of that consumer determine the correction: + +- if the governing schema or policy requires the values to remain distinct, the adapter constructing the v1 preimage MUST preserve or exclude the distinction so one binding equivalence class cannot span materially different operations; +- if the governing semantics define the values as equivalent, the downstream consumer MUST NOT over-discriminate based only on host-language representation. + +The reproduced `{"x":1}` versus `{"x":1.0}` cases are retained as this kind of witness. RFC 8785 canonicalizes them to the same JSON number representation, while current cMCP paths have demonstrated different downstream treatment. The expected outcome therefore comes from the governing schema/policy semantics, not from Python's representation and not from canonicalization alone. + +## Conformance evidence + +The companion [execution-action-binding-v1-vectors.json](execution-action-binding-v1-vectors.json) fixes exact JCS bytes and digests for ASCII, non-ASCII, domain, version, and digest-algorithm controls. + +A conforming implementation MUST reproduce the canonical bytes and expected digest for each vector using an RFC 8785 implementation independent of the vector file. Tests that compute expected output with the same helper under test do not establish interoperability. + +The semantic-adjudication witness in the vector file is deliberately not assigned an automatic `same_operation` or `different_operation` verdict. Its purpose is to require the implementation or integrating profile to name the governing semantics before admission. + +## Non-claims + +This contract does not by itself provide exactly-once execution, external-effect proof, crash-safe audit consistency, or replay permission. It also does not authorize activation of the non-operational execution registry foundation from #606. Those remain the integration and durability requirements owned by #565.