diff --git a/industrial-embodied-ai/.gitignore b/industrial-embodied-ai/.gitignore index 4bdd894..652dd80 100644 --- a/industrial-embodied-ai/.gitignore +++ b/industrial-embodied-ai/.gitignore @@ -1,4 +1,5 @@ __pycache__/ *.pyc +.venv/ audit.db trace-output/latest-*.json diff --git a/industrial-embodied-ai/README.md b/industrial-embodied-ai/README.md index 0d67f6e..e5b4c42 100644 --- a/industrial-embodied-ai/README.md +++ b/industrial-embodied-ai/README.md @@ -19,11 +19,13 @@ to produce durable evidence: 1. **Allowed and completed:** cMCP authorizes the declared workflow, then the independent controller accepts and completes the simulated motion. -2. **Scope denied:** the agent requests a motion whose physical parameters sit - inside the safety envelope (an approved zone, an in-limit speed), but under - an undeclared workflow. cMCP denies it on scope before the controller is - consulted. Physical safety was never the question: the trust layer withholds - the action because it falls outside the agent's declared purpose. +2. **Scope denied, the same motion out of scope:** the agent requests the same + in-envelope motion as the authorized path (the same approved zone and + in-limit speed, a fresh valid safety token) but under an undeclared workflow. + cMCP denies it on scope before the controller is consulted, so the safety + token is never checked. The only difference from the authorized path is the + workflow scope, not the motion or its safety: the trust layer withholds the + action because it is outside the agent's declared purpose. 3. **Safety rejected:** cMCP authorizes the declared workflow, but the controller rejects motion after its current state reports a person in the safeguarded area. @@ -162,15 +164,15 @@ python agent/material_movement_agent.py Expected summary: ```text +SCOPE DENY + cMCP policy: denied (out of declared scope) + controller: not consulted + SUCCESS cMCP policy: authorized controller: accepted execution: completed -POLICY DENY - cMCP policy: denied - controller: not invoked - SAFETY REJECT cMCP policy: authorized controller: rejected diff --git a/industrial-embodied-ai/agent/material_movement_agent.py b/industrial-embodied-ai/agent/material_movement_agent.py index 0e41387..3fa6c35 100644 --- a/industrial-embodied-ai/agent/material_movement_agent.py +++ b/industrial-embodied-ai/agent/material_movement_agent.py @@ -90,6 +90,8 @@ def _request_motion( request_id: int, snapshot: dict[str, Any], motion_id: str, + *, + workflow_id: str = WORKFLOW_ID, ) -> dict[str, Any]: return call_tool( client, @@ -102,6 +104,7 @@ def _request_motion( "safety_state_token": snapshot["state_token"], }, request_id, + workflow_id=workflow_id, ) @@ -169,15 +172,39 @@ def run( ) -> None: session_id: str | None = None with httpx.Client(headers=_headers()) as client: - print("SUCCESS") + print("SCOPE DENY") + # The same in-envelope motion as the authorized path below (an approved + # zone, an in-limit speed, a fresh valid safety token), requested under + # an undeclared workflow. cMCP denies it on scope before the controller + # is consulted, so the safety token is never even checked. The only + # difference from the authorized path is the workflow scope, not the + # motion or its physical safety. state = _read_state(client, gateway, 1) session_id = state["session_id"] - success = _request_motion( + denied = _request_motion( client, gateway, 2, state["payload"], "move-0001", + workflow_id="unapproved-diagnostics", + ) + if denied["ok"] or denied["status_code"] != 403: + raise RuntimeError("Out-of-scope motion was not denied by cMCP") + print(" cMCP policy: denied (out of declared scope)") + print(" controller: not consulted") + print() + + print("SUCCESS") + # The identical motion, now under the declared workflow. cMCP authorizes + # it and the independent controller accepts and completes it. + state = _read_state(client, gateway, 3) + success = _request_motion( + client, + gateway, + 4, + state["payload"], + "move-0002", ) if not success["ok"]: raise RuntimeError(f"Success path failed: {success['error']}") @@ -191,32 +218,12 @@ def run( print(f" execution: {success['payload']['execution_status']}") print() - print("POLICY DENY") - denied = call_tool( - client, - gateway, - "robot.request_motion", - { - "motion_id": "move-0002", - "target": "transfer-station-b", - "max_speed_mps": 0.2, - "safety_state_token": "not-forwarded", - }, - 3, - workflow_id="unapproved-diagnostics", - ) - if denied["ok"] or denied["status_code"] != 403: - raise RuntimeError("Unapproved workflow was not denied by cMCP") - print(" cMCP policy: denied") - print(" controller: not invoked") - print() - print("SAFETY REJECT") - state = _read_state(client, gateway, 4) + state = _read_state(client, gateway, 5) rejected = _request_motion( client, gateway, - 5, + 6, state["payload"], "move-0003", ) diff --git a/industrial-embodied-ai/trace-output/example-audit-bundle.json b/industrial-embodied-ai/trace-output/example-audit-bundle.json index 70fb388..7b314af 100644 --- a/industrial-embodied-ai/trace-output/example-audit-bundle.json +++ b/industrial-embodied-ai/trace-output/example-audit-bundle.json @@ -1,11 +1,11 @@ { - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", "entries": [ { - "entry_id": "4ad2ba95-1bdf-4172-95e5-5b3b32c82db9", + "entry_id": "36e22359-cc5f-438c-9c44-1a8b1e88d589", "sequence_number": 0, - "timestamp_utc": "2026-06-11T22:40:55.477088+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", + "timestamp_utc": "2026-06-14T06:17:40.761165+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", "call_id": null, "entry_type": "session_start", "tool_name": null, @@ -21,20 +21,20 @@ "detail": null, "workflow_id": null, "prev_entry_hash": "genesis", - "entry_hash": "fd9e4d49b299f3088024a69ed1fc1e3d288593c62186ab4e1d8cb152ef226d61" + "entry_hash": "321d0076a1a987074def0fb6603198ec3291a9970feff5bc404918bb5557f7cb" }, { - "entry_id": "9aa358b5-a3c3-4568-8cd3-add87dd6bb45", + "entry_id": "ad9ec034-6203-4393-94f5-d02b718fd30c", "sequence_number": 1, - "timestamp_utc": "2026-06-11T22:41:00.509721+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", - "call_id": "a5db9210-0d8f-42b8-accb-1376da5a9dd0", + "timestamp_utc": "2026-06-14T06:17:41.201402+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", + "call_id": "b3686ea8-ba04-4c08-8533-7f44df436e90", "entry_type": "tool_call", "tool_name": "cell.read_safety_state", "server_identity": "http://localhost:8080/mcp", "policy_decision": "allow", "policy_rule_matched": "Cedar (cedarpy): allowed", - "latency_us": 26832, + "latency_us": 13893, "request_payload_hash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_payload_hash": null, "response_inspection_result": null, @@ -42,65 +42,87 @@ "session_sensitivity_after": "confidential", "detail": null, "workflow_id": "industrial-material-movement", - "prev_entry_hash": "fd9e4d49b299f3088024a69ed1fc1e3d288593c62186ab4e1d8cb152ef226d61", - "entry_hash": "6a52c7f67d06c65c960e79bc74873a0fa10cfc006c764d0ef3c1822beafe591c" + "prev_entry_hash": "321d0076a1a987074def0fb6603198ec3291a9970feff5bc404918bb5557f7cb", + "entry_hash": "0e05f8ce5ada7c52af804bfb76b2ed7d62a39e3d75e5306ed274dfd8c3452791" }, { - "entry_id": "000236bc-e37b-4e17-9f27-956707263512", + "entry_id": "82ef16ae-5635-40a5-8088-293838dc2959", "sequence_number": 2, - "timestamp_utc": "2026-06-11T22:41:00.513614+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", - "call_id": "244fcfe4-3be8-45e4-ae19-297f13084330", + "timestamp_utc": "2026-06-14T06:17:41.202773+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", + "call_id": "f5834ae3-a65a-4390-b50b-98f9b19fac0b", "entry_type": "tool_call", "tool_name": "robot.request_motion", "server_identity": "http://localhost:8080/mcp", + "policy_decision": "deny", + "policy_rule_matched": "Policy denied tool call: robot.request_motion", + "latency_us": null, + "request_payload_hash": "sha256:55f3c91fb6551c28fbddbaba9b8cf3f4c904479c83d2dfdb23972a9bc4fd83d1", + "response_payload_hash": null, + "response_inspection_result": null, + "session_sensitivity_before": "confidential", + "session_sensitivity_after": "confidential", + "detail": null, + "workflow_id": "unapproved-diagnostics", + "prev_entry_hash": "0e05f8ce5ada7c52af804bfb76b2ed7d62a39e3d75e5306ed274dfd8c3452791", + "entry_hash": "bc4412b9ecb817abcdbad6466101992e06cbf41901c5378e5f24273e045f0042" + }, + { + "entry_id": "e313fa3b-ac7c-4b8d-b109-f13f6f2f54c4", + "sequence_number": 3, + "timestamp_utc": "2026-06-14T06:17:41.205384+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", + "call_id": "f84f13bd-5886-46d1-84a1-c9871d439e6c", + "entry_type": "tool_call", + "tool_name": "cell.read_safety_state", + "server_identity": "http://localhost:8080/mcp", "policy_decision": "allow", "policy_rule_matched": "Cedar (cedarpy): allowed", - "latency_us": 2273, - "request_payload_hash": "sha256:f4ebe51008ab5b1288beccb5b17a16e0f2fbb5bd644c8902859a261e2d92e426", + "latency_us": 1737, + "request_payload_hash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_payload_hash": null, "response_inspection_result": null, "session_sensitivity_before": "confidential", "session_sensitivity_after": "confidential", "detail": null, "workflow_id": "industrial-material-movement", - "prev_entry_hash": "6a52c7f67d06c65c960e79bc74873a0fa10cfc006c764d0ef3c1822beafe591c", - "entry_hash": "e8368d7e9fd8e54f233f402a63ee4dfc839931b2d455f6bb45e6a191b3f83137" + "prev_entry_hash": "bc4412b9ecb817abcdbad6466101992e06cbf41901c5378e5f24273e045f0042", + "entry_hash": "61100d5616b3069a33fd3649a44b659fd041f7577fc10053d44d1c6005ce3fbf" }, { - "entry_id": "8e52cfae-1ebe-40bc-ba99-ae934dae68ec", - "sequence_number": 3, - "timestamp_utc": "2026-06-11T22:41:00.514876+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", - "call_id": "0fab65d6-6867-44ff-94c5-c0c585d7e855", + "entry_id": "1c20b388-c834-42bb-b109-f0370d764312", + "sequence_number": 4, + "timestamp_utc": "2026-06-14T06:17:41.207728+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", + "call_id": "48f80a39-6ac6-4247-ae10-76c9385e085f", "entry_type": "tool_call", "tool_name": "robot.request_motion", "server_identity": "http://localhost:8080/mcp", - "policy_decision": "deny", - "policy_rule_matched": "Policy denied tool call: robot.request_motion", - "latency_us": null, - "request_payload_hash": "sha256:338998c57bf57fcd05cea50a8d463d39f81ccb184ddad984a31fb7dab162f5ce", + "policy_decision": "allow", + "policy_rule_matched": "Cedar (cedarpy): allowed", + "latency_us": 1522, + "request_payload_hash": "sha256:53457ccb81c8c3d1c80811535c7ab5cbe4c5e9c0a2ef244fd7dff9bef066d361", "response_payload_hash": null, "response_inspection_result": null, "session_sensitivity_before": "confidential", "session_sensitivity_after": "confidential", "detail": null, - "workflow_id": "unapproved-diagnostics", - "prev_entry_hash": "e8368d7e9fd8e54f233f402a63ee4dfc839931b2d455f6bb45e6a191b3f83137", - "entry_hash": "6b2ef02224320d6146c269b8d9817a82b72623350a0aa0845b1aab4023f34e23" + "workflow_id": "industrial-material-movement", + "prev_entry_hash": "61100d5616b3069a33fd3649a44b659fd041f7577fc10053d44d1c6005ce3fbf", + "entry_hash": "cd60e91a4a0b519591fafb2b50d08c07fa5a20c0031d16bc3051774e84673c5f" }, { - "entry_id": "af8838a7-7bce-4c03-8352-9ed55ecf0868", - "sequence_number": 4, - "timestamp_utc": "2026-06-11T22:41:00.517758+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", - "call_id": "decf2427-c918-4a17-a677-88f049d8e2cb", + "entry_id": "888a950c-c2e6-445d-8fa3-f9d42f85c7a0", + "sequence_number": 5, + "timestamp_utc": "2026-06-14T06:17:41.210018+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", + "call_id": "991303a3-0f7c-4748-9609-d7dbc0868250", "entry_type": "tool_call", "tool_name": "cell.read_safety_state", "server_identity": "http://localhost:8080/mcp", "policy_decision": "allow", "policy_rule_matched": "Cedar (cedarpy): allowed", - "latency_us": 1985, + "latency_us": 1590, "request_payload_hash": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_payload_hash": null, "response_inspection_result": null, @@ -108,36 +130,36 @@ "session_sensitivity_after": "confidential", "detail": null, "workflow_id": "industrial-material-movement", - "prev_entry_hash": "6b2ef02224320d6146c269b8d9817a82b72623350a0aa0845b1aab4023f34e23", - "entry_hash": "d8a7985af6b52b903eb11664fe16e29fc78759d2e947f287dc73fb46bf16e181" + "prev_entry_hash": "cd60e91a4a0b519591fafb2b50d08c07fa5a20c0031d16bc3051774e84673c5f", + "entry_hash": "6b3bdc64d0bf88a50e5a6190dff3d43f8bce79947710857000ee6e7ae01739e9" }, { - "entry_id": "0f65e244-2329-4f7e-a0d0-181252fe52a2", - "sequence_number": 5, - "timestamp_utc": "2026-06-11T22:41:00.520467+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", - "call_id": "e030ccfc-6ec8-4454-9305-1b5eb48adb0b", + "entry_id": "e1d2accd-ae46-4066-b5b2-be395e6fd29f", + "sequence_number": 6, + "timestamp_utc": "2026-06-14T06:17:41.212275+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", + "call_id": "fe27a71c-66bc-40a2-931f-3f9ecd24c781", "entry_type": "tool_call", "tool_name": "robot.request_motion", "server_identity": "http://localhost:8080/mcp", "policy_decision": "allow", "policy_rule_matched": "Cedar (cedarpy): allowed", - "latency_us": 1773, - "request_payload_hash": "sha256:c01b77fa8da1e5875b3b173043d0f4ea1cbf82b1eae2cb8c5547178effb2d3ff", + "latency_us": 1509, + "request_payload_hash": "sha256:309143d21f5916c254793a9f37202ebafda1258ae1eb6de1309de0d1160af3ec", "response_payload_hash": null, "response_inspection_result": null, "session_sensitivity_before": "confidential", "session_sensitivity_after": "confidential", "detail": null, "workflow_id": "industrial-material-movement", - "prev_entry_hash": "d8a7985af6b52b903eb11664fe16e29fc78759d2e947f287dc73fb46bf16e181", - "entry_hash": "214a2ce17f8b0333c2b543493f61f21b205ccc8faa594b877a34108e83d25cd8" + "prev_entry_hash": "6b3bdc64d0bf88a50e5a6190dff3d43f8bce79947710857000ee6e7ae01739e9", + "entry_hash": "d8e3f5030ab24526e5a549eada49a07317c0063164aa7df4ae012f62692a4041" }, { - "entry_id": "c8cc8737-3689-4be5-8b61-09b13e46bbbc", - "sequence_number": 6, - "timestamp_utc": "2026-06-11T22:41:00.521203+00:00", - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", + "entry_id": "221905ea-9db4-4d20-a3f0-765d023a3a14", + "sequence_number": 7, + "timestamp_utc": "2026-06-14T06:17:41.212940+00:00", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", "call_id": null, "entry_type": "session_end", "tool_name": null, @@ -152,9 +174,9 @@ "session_sensitivity_after": "confidential", "detail": null, "workflow_id": null, - "prev_entry_hash": "214a2ce17f8b0333c2b543493f61f21b205ccc8faa594b877a34108e83d25cd8", - "entry_hash": "278f15cce18a1fcb6a22c121facdab23060c700f7cff55ea622ecc8701c9ed7c" + "prev_entry_hash": "d8e3f5030ab24526e5a549eada49a07317c0063164aa7df4ae012f62692a4041", + "entry_hash": "9cb9d91fb224e7020cd24830651b63524f6fbb69a6539d389cfc315ff21d38aa" } ], - "bundle_signature": "l6En40w_6kLkxc4h5KjOna7SdOvCs3DhWeqriz1qFNr4RaxIVg2_5728A-D8PcThd4t8ce777pGb4XixoIPqAw" + "bundle_signature": "q3iOArOC3lZTXC4kDQAcdzZ9l9PLiPhMFMZhV0nZoux62wFFjSyO7FTF-fSrs1wQMpVL_sUB813jf2WnVQc0AQ" } diff --git a/industrial-embodied-ai/trace-output/example-trust-record.json b/industrial-embodied-ai/trace-output/example-trust-record.json index 0a95ab7..5fd173d 100644 --- a/industrial-embodied-ai/trace-output/example-trust-record.json +++ b/industrial-embodied-ai/trace-output/example-trust-record.json @@ -2,8 +2,8 @@ "cmcp_version": "1.0", "trace": { "eat_profile": "tag:agentrust.io,2026:trace-v0.1", - "iat": 1781217660, - "subject": "spiffe://cmcp.gateway/session/fdbdd187-b276-4c71-8e08-50f839d13bd1", + "iat": 1781417861, + "subject": "spiffe://cmcp.gateway/session/8fe66a2a-836b-4cff-9d05-7518d7f6f464", "runtime": { "platform": "software-only", "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000", @@ -16,30 +16,30 @@ }, "data_class": "confidential", "tool_transcript": { - "hash": "sha256:278f15cce18a1fcb6a22c121facdab23060c700f7cff55ea622ecc8701c9ed7c", - "call_count": 5 + "hash": "sha256:9cb9d91fb224e7020cd24830651b63524f6fbb69a6539d389cfc315ff21d38aa", + "call_count": 6 }, "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", - "x": "ebBLjGSAJYOTs9npzclQLGqGwZw_2fnG5YZ-Sbc0l54", - "kid": "cmcp-79b04b8c" + "x": "5L_3-8KHdhLKTycgp-WXB85OyEiRGLtoq0CcCzvzNg8", + "kid": "cmcp-e4bff7fb" } } }, "gateway": { - "session_id": "fdbdd187-b276-4c71-8e08-50f839d13bd1", + "session_id": "8fe66a2a-836b-4cff-9d05-7518d7f6f464", "gateway_version": "0.1.0", "sequence_number": 1, "audit_chain": { - "root": "fd9e4d49b299f3088024a69ed1fc1e3d288593c62186ab4e1d8cb152ef226d61", - "tip": "278f15cce18a1fcb6a22c121facdab23060c700f7cff55ea622ecc8701c9ed7c", - "length": 7 + "root": "321d0076a1a987074def0fb6603198ec3291a9970feff5bc404918bb5557f7cb", + "tip": "9cb9d91fb224e7020cd24830651b63524f6fbb69a6539d389cfc315ff21d38aa", + "length": 8 }, "call_summary": { - "tool_calls_total": 5, - "tool_calls_allowed": 4, + "tool_calls_total": 6, + "tool_calls_allowed": 5, "tool_calls_denied": 1, "tool_calls_faulted": 0, "tools_invoked": [ @@ -59,12 +59,12 @@ "hash": "sha256:792c86ff8152fa9713d52584c084611eb4929fa5ebf3ec8271dd21f0e0aa7eeb", "drift_detected": false }, - "attestation_generated_at": "2026-06-11T22:40:55.264023+00:00", + "attestation_generated_at": "2026-06-14T06:17:40.551552+00:00", "attestation_validity_seconds": 86400, "attestation_stale": false, "catalog_exceptions": [], "call_log_summary": { - "total_calls": 5, + "total_calls": 6, "tools_called": [ "cell.read_safety_state", "robot.request_motion" @@ -72,5 +72,5 @@ "suspicious_sequences_detected": 0 } }, - "signature": "i3w2GxrsrGR9pHwO-VLeopxG9WTQTCVpy212-alhZB7bI8jxcH9mpW-AcKaKAu_TQTuGr-50IJo4gGitIKG6Dw" + "signature": "ss46cb_B7LzyqkHH20-76kJBMfz0qqf5CYUoKOvO04m0TwsVf-G4251PAmyJgATZ6cDiv3ZwqpOq-tXu5gNrCg" }