agenticdome-python-sdk 1.2.28__py3-none-any.whl → 1.2.30__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenticdome-python-sdk
3
- Version: 1.2.28
3
+ Version: 1.2.30
4
4
  Summary: Official Python SDK for AgenticDome security guardrails, DLP, tool authorization, and multi-agent delegation enforcement.
5
5
  Author: AgenticDome
6
6
  License-Expression: Apache-2.0
@@ -31,21 +31,22 @@ License-File: LICENSE
31
31
  License-File: NOTICE
32
32
  Requires-Dist: requests>=2.31.0
33
33
  Requires-Dist: urllib3>=2.0.0
34
+ Requires-Dist: cryptography>=42.0.0
34
35
  Provides-Extra: crewai
35
- Requires-Dist: crewai<=1.15.18,>=1.15.5; extra == "crewai"
36
+ Requires-Dist: crewai<=1.15.21,>=1.15.5; extra == "crewai"
36
37
  Provides-Extra: redis
37
38
  Requires-Dist: redis>=4.5.0; extra == "redis"
38
39
  Provides-Extra: pop
39
40
  Requires-Dist: cryptography>=42.0.0; extra == "pop"
40
41
  Provides-Extra: pydanticai
41
- Requires-Dist: pydantic-ai<=2.36.0,>=2.16.0; extra == "pydanticai"
42
+ Requires-Dist: pydantic-ai<=2.42.0,>=2.16.0; extra == "pydanticai"
42
43
  Requires-Dist: anyio>=4.0.0; extra == "pydanticai"
43
44
  Provides-Extra: pydantic
44
- Requires-Dist: pydantic-ai<=2.36.0,>=2.16.0; extra == "pydantic"
45
+ Requires-Dist: pydantic-ai<=2.42.0,>=2.16.0; extra == "pydantic"
45
46
  Requires-Dist: anyio>=4.0.0; extra == "pydantic"
46
47
  Provides-Extra: langgraph
47
48
  Requires-Dist: langgraph<=1.2.11,>=1.2.9; extra == "langgraph"
48
- Requires-Dist: langchain-core<=1.6.1,>=1.5.0; extra == "langgraph"
49
+ Requires-Dist: langchain-core<=1.6.2,>=1.5.0; extra == "langgraph"
49
50
  Requires-Dist: langchain-openai==1.3.5; extra == "langgraph"
50
51
  Requires-Dist: anyio>=4.0.0; extra == "langgraph"
51
52
  Provides-Extra: microsoft
@@ -54,18 +55,18 @@ Requires-Dist: autogen-agentchat==0.7.5; extra == "autogen"
54
55
  Provides-Extra: microsoft-autogen
55
56
  Requires-Dist: autogen-agentchat==0.7.5; extra == "microsoft-autogen"
56
57
  Provides-Extra: foundry
57
- Requires-Dist: azure-ai-projects<=2.5.0,>=2.3.0; extra == "foundry"
58
+ Requires-Dist: azure-ai-projects<=2.6.0,>=2.3.0; extra == "foundry"
58
59
  Requires-Dist: azure-identity==1.25.3; extra == "foundry"
59
60
  Provides-Extra: agno
60
- Requires-Dist: agno<=3.0.4,>=2.8.0; extra == "agno"
61
+ Requires-Dist: agno<=3.0.9,>=2.8.0; extra == "agno"
61
62
  Provides-Extra: openai-agents
62
- Requires-Dist: openai-agents<=0.22.0,>=0.18.3; extra == "openai-agents"
63
+ Requires-Dist: openai-agents<=0.22.2,>=0.18.3; extra == "openai-agents"
63
64
  Provides-Extra: mcp
64
- Requires-Dist: mcp<=1.28.1,>=1.26.0; extra == "mcp"
65
+ Requires-Dist: mcp<=2.2.0,>=1.26.0; extra == "mcp"
65
66
  Provides-Extra: bedrock
66
- Requires-Dist: boto3<=1.43.83,>=1.43.54; extra == "bedrock"
67
+ Requires-Dist: boto3<=1.43.91,>=1.43.54; extra == "bedrock"
67
68
  Provides-Extra: aws-bedrock
68
- Requires-Dist: boto3<=1.43.83,>=1.43.54; extra == "aws-bedrock"
69
+ Requires-Dist: boto3<=1.43.91,>=1.43.54; extra == "aws-bedrock"
69
70
  Provides-Extra: llamaindex
70
71
  Requires-Dist: llama-index<=0.14.24,>=0.14.23; extra == "llamaindex"
71
72
  Provides-Extra: llama-index
@@ -75,30 +76,30 @@ Requires-Dist: google-adk<=2.8.0,>=2.5.0; extra == "google-adk"
75
76
  Provides-Extra: adk
76
77
  Requires-Dist: google-adk<=2.8.0,>=2.5.0; extra == "adk"
77
78
  Provides-Extra: claude
78
- Requires-Dist: claude-agent-sdk<=0.2.148,>=0.2.126; extra == "claude"
79
+ Requires-Dist: claude-agent-sdk<=0.2.152,>=0.2.126; extra == "claude"
79
80
  Provides-Extra: claude-agent-sdk
80
- Requires-Dist: claude-agent-sdk<=0.2.148,>=0.2.126; extra == "claude-agent-sdk"
81
+ Requires-Dist: claude-agent-sdk<=0.2.152,>=0.2.126; extra == "claude-agent-sdk"
81
82
  Provides-Extra: smolagents
82
83
  Requires-Dist: smolagents==1.26.0; extra == "smolagents"
83
84
  Provides-Extra: all
84
- Requires-Dist: crewai<=1.15.18,>=1.15.5; extra == "all"
85
+ Requires-Dist: crewai<=1.15.21,>=1.15.5; extra == "all"
85
86
  Requires-Dist: redis>=4.5.0; extra == "all"
86
87
  Requires-Dist: cryptography>=42.0.0; extra == "all"
87
- Requires-Dist: pydantic-ai<=2.36.0,>=2.16.0; extra == "all"
88
+ Requires-Dist: pydantic-ai<=2.42.0,>=2.16.0; extra == "all"
88
89
  Requires-Dist: anyio>=4.0.0; extra == "all"
89
90
  Requires-Dist: langgraph<=1.2.11,>=1.2.9; extra == "all"
90
- Requires-Dist: langchain-core<=1.6.1,>=1.5.0; extra == "all"
91
+ Requires-Dist: langchain-core<=1.6.2,>=1.5.0; extra == "all"
91
92
  Requires-Dist: langchain-openai==1.3.5; extra == "all"
92
93
  Requires-Dist: autogen-agentchat==0.7.5; extra == "all"
93
- Requires-Dist: agno<=3.0.4,>=2.8.0; extra == "all"
94
- Requires-Dist: azure-ai-projects<=2.5.0,>=2.3.0; extra == "all"
94
+ Requires-Dist: agno<=3.0.9,>=2.8.0; extra == "all"
95
+ Requires-Dist: azure-ai-projects<=2.6.0,>=2.3.0; extra == "all"
95
96
  Requires-Dist: azure-identity==1.25.3; extra == "all"
96
- Requires-Dist: openai-agents<=0.22.0,>=0.18.3; extra == "all"
97
- Requires-Dist: mcp<=1.28.1,>=1.26.0; extra == "all"
98
- Requires-Dist: boto3<=1.43.83,>=1.43.54; extra == "all"
97
+ Requires-Dist: openai-agents<=0.22.2,>=0.18.3; extra == "all"
98
+ Requires-Dist: mcp<=2.2.0,>=1.26.0; extra == "all"
99
+ Requires-Dist: boto3<=1.43.91,>=1.43.54; extra == "all"
99
100
  Requires-Dist: llama-index<=0.14.24,>=0.14.23; extra == "all"
100
101
  Requires-Dist: google-adk<=2.8.0,>=2.5.0; extra == "all"
101
- Requires-Dist: claude-agent-sdk<=0.2.148,>=0.2.126; extra == "all"
102
+ Requires-Dist: claude-agent-sdk<=0.2.152,>=0.2.126; extra == "all"
102
103
  Requires-Dist: smolagents==1.26.0; extra == "all"
103
104
  Provides-Extra: dev
104
105
  Requires-Dist: pytest>=8.0.0; extra == "dev"
@@ -2336,7 +2337,7 @@ Start with the dedicated [MCP Host and Gateway Action Firewall guide](https://gi
2336
2337
  pip install "agenticdome-python-sdk[mcp]"
2337
2338
  ```
2338
2339
 
2339
- **MCP package compatibility:** the `[mcp]` extra currently installs the externally certified PyPI range `mcp>=1.26.0,<=1.28.1`. That is the supported range of the third-party `mcp` package, not the version of AgenticDome's SDK. MCP 2.0 removed the certified `mcp.server.fastmcp` import surface, and CrewAI 1.15.5 declares `mcp~=1.28.1` for combined installs. Keep the ceiling until an isolated MCP 2.x certification passes the native imports, adapter checks, preserved 1.x floor, and package gates. The AgenticDome firewall itself is plain-JSON-RPC and dependency-light, but installing the base SDK beside an independently managed MCP 2.x transport does not constitute a formally certified MCP 2.x support claim.
2340
+ **MCP package compatibility:** the `[mcp]` extra currently installs the externally certified PyPI range `mcp>=1.26.0,<=2.2.0`. That is the supported range of the third-party `mcp` package, not the version of AgenticDome's SDK. MCP 2.0 removed the certified `mcp.server.fastmcp` import surface used by the earlier native compatibility test. AgenticDome's transport-independent JSON-RPC firewall has since passed the isolated MCP 2.x certification matrix through 2.2.0 while preserving the 1.26.0 floor. CrewAI 1.15.x combined installations can still resolve the narrower MCP 1.28.x line according to CrewAI's dependency constraint; this does not reduce the independently certified AgenticDome range.
2340
2341
 
2341
2342
  **Wrap the forwarding boundary** — the full host flow is `JSON-RPC request → rate limit / upstream prompt screen → method authorization → optional delegation-token verification → third-party MCP server → list filtering / result sanitization → client`:
2342
2343
 
@@ -2606,6 +2607,30 @@ result = client.mesh_validate(
2606
2607
  print(result)
2607
2608
  ```
2608
2609
 
2610
+ **Multimodal content descriptors:**
2611
+
2612
+ Use `inspect_content()` when a document, image, audio or video processor has
2613
+ already produced bounded text, a transcript, trusted sensitivity labels, or a
2614
+ content digest. The active tenant policy is evaluated inside the assigned
2615
+ AgenticDome runtime. OCR, transcription and proprietary classification are not
2616
+ shipped in this public SDK, and the returned evidence does not retain raw
2617
+ content.
2618
+
2619
+ ```python
2620
+ verdict = client.inspect_content(
2621
+ direction="output",
2622
+ content_parts=[{
2623
+ "modality": "image",
2624
+ "mime_type": "image/png",
2625
+ "content_sha256": "sha256:" + image_digest,
2626
+ "labels": ["phi"],
2627
+ }],
2628
+ policy_context={"request_purpose": "clinical-summary-delivery"},
2629
+ )
2630
+ if verdict["verdict"] == "BLOCKED":
2631
+ raise PermissionError(verdict["reason"])
2632
+ ```
2633
+
2609
2634
  **Incident reporting:**
2610
2635
 
2611
2636
  ```python
@@ -2719,6 +2744,34 @@ Plus, for every production deployment:
2719
2744
 
2720
2745
  ---
2721
2746
 
2747
+ ## Verified Action lifecycle evidence
2748
+
2749
+ Use `VerifiedActionReporter` for executable tool calls, function calls, MCP operations and external side effects that should appear in **Verified Actions**. It is not intended for health checks or every policy lookup.
2750
+
2751
+ ```bash
2752
+ export AGENTICDOME_EVIDENCE_API_BASE="https://your-control-plane.example"
2753
+ export AGENTICDOME_EVIDENCE_TOKEN="<portal-token-scoped-to-evidence:write>"
2754
+ ```
2755
+
2756
+ ```python
2757
+ from agenticdome_sdk import VerifiedActionReporter, verified_action
2758
+
2759
+ evidence = VerifiedActionReporter.from_env(tenant_id="your-tenant-id")
2760
+
2761
+ @verified_action(
2762
+ reporter=evidence,
2763
+ operation_type="function_call",
2764
+ executor_type="function",
2765
+ target_type="service",
2766
+ )
2767
+ def refresh_customer_record(customer_id: str):
2768
+ ...
2769
+ ```
2770
+
2771
+ The reporter uses a bounded, non-blocking queue and retains hashes plus normalized lifecycle metadata only. It never sends raw arguments, results, prompts, source, or credentials. The dedicated evidence token is separate from the Runtime / SDK key: authorization stays on the assigned sidecar and evidence-delivery failure never changes its allow/deny decision. Integrations built on the shared framework `secure_tool` / `wrap_tool_handler` boundary and the MCP host/gateway wrapper report the same lifecycle automatically when these optional evidence variables are present; other custom boundaries can use the decorator above.
2772
+
2773
+ ---
2774
+
2722
2775
  ## Source Installation and Verification
2723
2776
 
2724
2777
  Most customers should install the published package from PyPI. Contributors evaluating the public source can run the public tests from the SDK root:
@@ -1,37 +1,41 @@
1
- agenticdome_python_sdk-1.2.28.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
- agenticdome_python_sdk-1.2.28.dist-info/licenses/NOTICE,sha256=obvKP6kV1Ny5R8_INFOpf0X5um9U2X43LGwQFobO9kg,550
3
- agenticdome_sdk/__init__.py,sha256=dNi4ndORi_fxskZY4IyUMc9C8jTM84aE2ybr79L84ss,685
4
- agenticdome_sdk/_framework_firewall.py,sha256=NKOUuYlkvm9f_evaaA4Uts9pxaPAvhgGeIKWj7le9ow,29642
1
+ agenticdome_python_sdk-1.2.30.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
+ agenticdome_python_sdk-1.2.30.dist-info/licenses/NOTICE,sha256=obvKP6kV1Ny5R8_INFOpf0X5um9U2X43LGwQFobO9kg,550
3
+ agenticdome_sdk/__init__.py,sha256=UY__0ppEM4MlsTQoLh8alZ7AoMjtegGKe4j_Sj7CpNk,903
4
+ agenticdome_sdk/_framework_firewall.py,sha256=W3gy7WsK50scE7ypJrmVJhHUOBsr0zAWQ85rghqaJk4,31963
5
5
  agenticdome_sdk/_mode.py,sha256=XvOgh5sW-FG8S39V483h3uLiFh8wSt14OmYXz_6eDO8,1283
6
6
  agenticdome_sdk/agno.py,sha256=L3yfjTBHW47xzT67a1DsjtwmvrAoYBkbxADpsr85Cp0,60766
7
+ agenticdome_sdk/attestation.py,sha256=XFMq8UEMU8L_8-wH6gKTkWJQguYDm3pbZ65G506bLiw,10126
7
8
  agenticdome_sdk/autogen.py,sha256=KP4qlIcsNXB-4ZT9D7Slc1xWEeWXRIWkc5HGW0U6k2c,29018
8
9
  agenticdome_sdk/aws_bedrock.py,sha256=2zD9j7xi0jna0sMeMf-hKZdc3Y4RPwzj6tMVOUGgIDQ,69581
9
- agenticdome_sdk/catalog_snapshot.json,sha256=sWkErTQOI3BpAALn2eNP6LvMjvAGusgTtl4eysZm9Xg,951
10
+ agenticdome_sdk/catalog_snapshot.json,sha256=ixwzcK8geYTdan-UrZde0X-twunMHBziPz97fum0LBo,951
10
11
  agenticdome_sdk/claude.py,sha256=XeY9AUu61SYcJjQVz_AtL_WaN6X4KqP5nprEbbr4Q6A,17456
11
- agenticdome_sdk/client.py,sha256=4Kh3FpY0JDZ6pOA_1wzcYVnagbje-fEYcz0djpzsrZI,63601
12
+ agenticdome_sdk/client.py,sha256=Sr43Qp3JWqHGCjWwelgIWGjHPsEdnxaoang70DMOufA,67641
13
+ agenticdome_sdk/connect.py,sha256=yNRh84mqdWr73fXW3S8Zc_lV1QN6gce5T6mZ20cr8ps,16726
12
14
  agenticdome_sdk/copilot_ir.py,sha256=OMkzkVGSP2pFkxZBjAXNCCRcJk21X0wnllWNhQeErp0,16588
13
15
  agenticdome_sdk/copilot_ir_collector.cjs,sha256=oxLY8ByONo7Es4YjYiJuRz_A7IKL65Sy486UXnu-4Rs,9681
14
16
  agenticdome_sdk/crewai.py,sha256=sSJQ49O7-8kqOAD2_jrsMtMOmBOcJmFkeOv7S3-ayMs,52490
15
17
  agenticdome_sdk/demo.py,sha256=dyfqt2N_cJoyUWKnjPYuQxuw2hb2DrCNUf1jQKeYYoc,8350
16
18
  agenticdome_sdk/google_adk.py,sha256=2TA-tUhY0lOnkGcs1BN9CUEz67ebVX4HJ7mnNoRPcZs,57258
17
- agenticdome_sdk/hook_catalog.py,sha256=PABcv8nKm_05Zs9RQ6cW4CLHwJ7iqXk6Eyz-cPmYiAo,29293
19
+ agenticdome_sdk/hook_catalog.py,sha256=PduNxLhnBiIF14mAxABnpBPsvR7sTzDmCyudbU1BvrI,29272
18
20
  agenticdome_sdk/identity.py,sha256=mrymGgZSVsKJ9THRde54qGmMT7j_uZHmPJ-HtWk7PXE,4635
19
21
  agenticdome_sdk/langgraph.py,sha256=FUvF6DSA5aZP3eoBoNeldgGtPrz-dO5xcVmrPPSBEhI,84312
22
+ agenticdome_sdk/lifecycle.py,sha256=9LvxmUU1LC_tMchb5uVFBnZIkVFUxOv3VH9wG_9gPtA,12855
20
23
  agenticdome_sdk/llamaindex.py,sha256=BHplSUIsgMi7k8tBztprzaV9iaOXiljXbdeMRax_2h8,42860
21
- agenticdome_sdk/mcp_host.py,sha256=E2MQ2s0tJQrohOSWTqjFsD7PQ2q0WhceNNFEwqmT8sI,61114
24
+ agenticdome_sdk/mcp_host.py,sha256=bJG0pCfMYkI6ig4tmPvtJKcdFFfDOXNmatLbh_X9lso,63283
22
25
  agenticdome_sdk/mcp_http_gateway.py,sha256=j7B_IVhr0QLSPdsE8iVKcH0mCtQN7EE622JKY3d2LHI,13533
23
26
  agenticdome_sdk/mcp_rehearsal_server.py,sha256=cox4XqUTL8d0gaDzqxDZcEALkaz8otjOjOqHvoUeeCo,2118
24
27
  agenticdome_sdk/mcp_verification.py,sha256=VqQS5SpPVFI3G7qCwrT90JmG8vFE6pRJEB7PViHjKqw,14235
25
28
  agenticdome_sdk/microsoft_agent_framework.py,sha256=GfIIuk0n-VhsZaTyoB7P6ilpz9bFLD95TWuX0rFSWeU,67219
26
29
  agenticdome_sdk/microsoft_ai_foundry.py,sha256=YlO968s1dbAYcaEqhqufL92NFR66GuTtngnCv7-1mYc,56911
27
- agenticdome_sdk/onboarding_cli.py,sha256=nQzxu6WV10YVETP2mwGEqwwhncR_MJ1w6o_BIdt7BeY,87776
30
+ agenticdome_sdk/onboarding_cli.py,sha256=8ymGtNmllNH6lP87pvxHe2GySlcsHoDoLiispfQ5rDs,95417
28
31
  agenticdome_sdk/openai_agents.py,sha256=nNM-YcsUDcE23sGv1buLudcYFz0NLju2Mwiw_wrVaJg,57326
32
+ agenticdome_sdk/outcomes.py,sha256=jadWDzQaZXrHNvpPme8lQY0jihYLCoM3s4CihAq0JRo,2507
29
33
  agenticdome_sdk/proof.py,sha256=ECKc14xSE0V1HHmcpZjfVz8-Q2H_fooah3BfdblFV8M,2878
30
34
  agenticdome_sdk/pydantic.py,sha256=WKb8X0Fgydfid-ep8E-M8Yslz2pWcCP6w7n_aBxt1rI,54961
31
35
  agenticdome_sdk/simulation.py,sha256=UCbZy3u9AzfxQCfY38SCV3CxGxhinV9EA4aHMPlK4mc,8426
32
36
  agenticdome_sdk/smolagents.py,sha256=wjMA1HVAQ-9wRNM38Jr9wlCpKMhF-9vxtTk6R-kwQhs,19094
33
- agenticdome_python_sdk-1.2.28.dist-info/METADATA,sha256=rUEBdKXqmbfj_JG1ybkrwSsmqVNBORweFYLP5vw_FXs,136861
34
- agenticdome_python_sdk-1.2.28.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
35
- agenticdome_python_sdk-1.2.28.dist-info/entry_points.txt,sha256=cRfCphkr5gLrkGIzXJEtL50HdHLajoZLbZtcMLE8NoY,113
36
- agenticdome_python_sdk-1.2.28.dist-info/top_level.txt,sha256=LZbbjXco5mqDrGwlTJpIKI5Sp6R_njzHHjExxAs9c3s,16
37
- agenticdome_python_sdk-1.2.28.dist-info/RECORD,,
37
+ agenticdome_python_sdk-1.2.30.dist-info/METADATA,sha256=VfpIoruBWktFlx3XfM79Psr_SUgJRHllPWbRoDid-I0,139108
38
+ agenticdome_python_sdk-1.2.30.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
39
+ agenticdome_python_sdk-1.2.30.dist-info/entry_points.txt,sha256=cRfCphkr5gLrkGIzXJEtL50HdHLajoZLbZtcMLE8NoY,113
40
+ agenticdome_python_sdk-1.2.30.dist-info/top_level.txt,sha256=LZbbjXco5mqDrGwlTJpIKI5Sp6R_njzHHjExxAs9c3s,16
41
+ agenticdome_python_sdk-1.2.30.dist-info/RECORD,,
@@ -2,6 +2,7 @@ from .client import AgenticDomeClient, AgenticDomeError, AgenticDomeHTTPError
2
2
  from .identity import IDENTITY_CONTEXT_VERSION, canonicalize_identity_context, enrich_policy_context
3
3
  from .proof import create_dpop_proof, generate_rsa_proof_key, jwk_thumbprint
4
4
  from ._mode import LIVE_MODE, LOCAL_SIM_MODE, is_local_sim_mode, resolve_mode
5
+ from .lifecycle import VerifiedActionContext, VerifiedActionDenied, VerifiedActionReporter, verified_action
5
6
 
6
7
  __all__ = [
7
8
  "AgenticDomeClient",
@@ -17,4 +18,8 @@ __all__ = [
17
18
  "LOCAL_SIM_MODE",
18
19
  "is_local_sim_mode",
19
20
  "resolve_mode",
21
+ "VerifiedActionContext",
22
+ "VerifiedActionDenied",
23
+ "VerifiedActionReporter",
24
+ "verified_action",
20
25
  ]
@@ -16,6 +16,7 @@ from threading import Lock, Thread
16
16
  from typing import Any, Awaitable, Callable, Deque, Dict, Optional, Tuple, Type
17
17
 
18
18
  from .client import AgenticDomeClient
19
+ from .lifecycle import VerifiedActionReporter
19
20
  from ._mode import credentials_or_local_sim
20
21
 
21
22
 
@@ -201,6 +202,7 @@ class FrameworkFirewallBase:
201
202
  timeout=config.timeout_s,
202
203
  )
203
204
  self.token_store = token_store or build_token_store(config, logger)
205
+ self.lifecycle_reporter = VerifiedActionReporter.from_env(tenant_id=config.tenant_id)
204
206
  self.denied_error = denied_error
205
207
  self.label = label
206
208
  self.logger = logger
@@ -211,6 +213,7 @@ class FrameworkFirewallBase:
211
213
  self._circuit_open_until = 0.0
212
214
 
213
215
  def close(self) -> None:
216
+ self.lifecycle_reporter.flush(timeout=2.0)
214
217
  try:
215
218
  self.client.close()
216
219
  except Exception:
@@ -611,35 +614,73 @@ class FrameworkFirewallBase:
611
614
  call_args = self.normalize_args(kwargs if kwargs else (args[0] if len(args) == 1 and isinstance(args[0], dict) else {}))
612
615
  sid = self.session_id(session_id)
613
616
  aid = agent_id or self.config.agent_id
614
- decision = await self.aauthorize_tool_call(
615
- session_id=sid, agent_id=aid, tool_name=tool_name, tool_args=call_args,
616
- tool_platform=tool_platform, text=f"[{self.label}] {aid} calls {tool_name}",
617
+ lifecycle = self.lifecycle_reporter.new_context(
618
+ operation_type="tool_call", tool_name=tool_name, arguments=call_args,
619
+ initiator_type="agent", executor_type="tool", target_type="tool",
617
620
  )
621
+ self.lifecycle_reporter.phase(lifecycle, "requested", "requested")
622
+ try:
623
+ decision = await self.aauthorize_tool_call(
624
+ session_id=sid, agent_id=aid, tool_name=tool_name, tool_args=call_args,
625
+ tool_platform=tool_platform, text=f"[{self.label}] {aid} calls {tool_name}",
626
+ )
627
+ except Exception:
628
+ self.lifecycle_reporter.phase(lifecycle, "authorised", "blocked")
629
+ self.lifecycle_reporter.outcome(lifecycle, "not_attempted")
630
+ raise
631
+ self.lifecycle_reporter.phase(lifecycle, "authorised", "allowed")
632
+ self.lifecycle_reporter.phase(lifecycle, "admitted", "admitted")
618
633
  clean = self.sanitized_args(decision, call_args)
619
- if kwargs:
620
- raw = await handler(*args, **clean)
621
- elif len(args) == 1 and isinstance(args[0], dict):
622
- raw = await handler(clean)
623
- else:
624
- raw = await handler(*args, **kwargs)
625
- return await self.areview_value(raw, session_id=sid, agent_id=aid, policy_context={"tool_name": tool_name}) if sanitize_output else raw
634
+ self.lifecycle_reporter.phase(lifecycle, "attempted", "attempted")
635
+ try:
636
+ if kwargs:
637
+ raw = await handler(*args, **clean)
638
+ elif len(args) == 1 and isinstance(args[0], dict):
639
+ raw = await handler(clean)
640
+ else:
641
+ raw = await handler(*args, **kwargs)
642
+ reviewed = await self.areview_value(raw, session_id=sid, agent_id=aid, policy_context={"tool_name": tool_name}) if sanitize_output else raw
643
+ except Exception:
644
+ self.lifecycle_reporter.outcome(lifecycle, "failed")
645
+ raise
646
+ self.lifecycle_reporter.outcome(lifecycle, "succeeded")
647
+ return reviewed
626
648
 
627
649
  def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
628
650
  call_args = self.normalize_args(kwargs if kwargs else (args[0] if len(args) == 1 and isinstance(args[0], dict) else {}))
629
651
  sid = self.session_id(session_id)
630
652
  aid = agent_id or self.config.agent_id
631
- decision = self.authorize_tool_call(
632
- session_id=sid, agent_id=aid, tool_name=tool_name, tool_args=call_args,
633
- tool_platform=tool_platform, text=f"[{self.label}] {aid} calls {tool_name}",
653
+ lifecycle = self.lifecycle_reporter.new_context(
654
+ operation_type="tool_call", tool_name=tool_name, arguments=call_args,
655
+ initiator_type="agent", executor_type="tool", target_type="tool",
634
656
  )
657
+ self.lifecycle_reporter.phase(lifecycle, "requested", "requested")
658
+ try:
659
+ decision = self.authorize_tool_call(
660
+ session_id=sid, agent_id=aid, tool_name=tool_name, tool_args=call_args,
661
+ tool_platform=tool_platform, text=f"[{self.label}] {aid} calls {tool_name}",
662
+ )
663
+ except Exception:
664
+ self.lifecycle_reporter.phase(lifecycle, "authorised", "blocked")
665
+ self.lifecycle_reporter.outcome(lifecycle, "not_attempted")
666
+ raise
667
+ self.lifecycle_reporter.phase(lifecycle, "authorised", "allowed")
668
+ self.lifecycle_reporter.phase(lifecycle, "admitted", "admitted")
635
669
  clean = self.sanitized_args(decision, call_args)
636
- if kwargs:
637
- raw = handler(*args, **clean)
638
- elif len(args) == 1 and isinstance(args[0], dict):
639
- raw = handler(clean)
640
- else:
641
- raw = handler(*args, **kwargs)
642
- return self.review_value(raw, session_id=sid, agent_id=aid, policy_context={"tool_name": tool_name}) if sanitize_output else raw
670
+ self.lifecycle_reporter.phase(lifecycle, "attempted", "attempted")
671
+ try:
672
+ if kwargs:
673
+ raw = handler(*args, **clean)
674
+ elif len(args) == 1 and isinstance(args[0], dict):
675
+ raw = handler(clean)
676
+ else:
677
+ raw = handler(*args, **kwargs)
678
+ reviewed = self.review_value(raw, session_id=sid, agent_id=aid, policy_context={"tool_name": tool_name}) if sanitize_output else raw
679
+ except Exception:
680
+ self.lifecycle_reporter.outcome(lifecycle, "failed")
681
+ raise
682
+ self.lifecycle_reporter.outcome(lifecycle, "succeeded")
683
+ return reviewed
643
684
 
644
685
  wrapper = async_wrapper if is_async else sync_wrapper
645
686
  return functools.wraps(handler)(wrapper)
@@ -0,0 +1,205 @@
1
+ """Signed runtime-coverage evidence with no customer source or proprietary analysis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import threading
10
+ import time
11
+ import urllib.request
12
+ import uuid
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any, Dict, Iterable, Optional
16
+
17
+
18
+ def _crypto():
19
+ try:
20
+ from cryptography.hazmat.primitives import hashes, serialization
21
+ from cryptography.hazmat.primitives.asymmetric import padding, rsa
22
+ except ImportError as exc: # pragma: no cover - packaging/install failure
23
+ raise RuntimeError("Runtime attestation requires the SDK's cryptography dependency.") from exc
24
+ return hashes, serialization, padding, rsa
25
+
26
+
27
+ def canonical_json(value: Dict[str, Any]) -> bytes:
28
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
29
+
30
+
31
+ def ensure_attestation_key(directory: Path) -> tuple[str, str]:
32
+ """Return private/public PEM, creating a local non-uploaded private key once."""
33
+ hashes, serialization, padding, rsa = _crypto()
34
+ del hashes, padding
35
+ directory.mkdir(parents=True, exist_ok=True)
36
+ private_path = directory / "attestation-private.pem"
37
+ public_path = directory / "attestation-public.pem"
38
+ if private_path.exists():
39
+ private_pem = private_path.read_text(encoding="ascii")
40
+ key = serialization.load_pem_private_key(private_pem.encode("ascii"), password=None)
41
+ else:
42
+ key = rsa.generate_private_key(public_exponent=65537, key_size=3072)
43
+ private_pem = key.private_bytes(
44
+ serialization.Encoding.PEM,
45
+ serialization.PrivateFormat.PKCS8,
46
+ serialization.NoEncryption(),
47
+ ).decode("ascii")
48
+ private_path.write_text(private_pem, encoding="ascii")
49
+ os.chmod(private_path, 0o600)
50
+ public_pem = key.public_key().public_bytes(
51
+ serialization.Encoding.PEM,
52
+ serialization.PublicFormat.SubjectPublicKeyInfo,
53
+ ).decode("ascii")
54
+ public_path.write_text(public_pem, encoding="ascii")
55
+ os.chmod(public_path, 0o644)
56
+ return private_pem, public_pem
57
+
58
+
59
+ def sign_claims(claims: Dict[str, Any], private_key_pem: str) -> str:
60
+ hashes, serialization, padding, rsa = _crypto()
61
+ del rsa
62
+ key = serialization.load_pem_private_key(private_key_pem.encode("ascii"), password=None)
63
+ signature = key.sign(canonical_json(claims), padding.PKCS1v15(), hashes.SHA256())
64
+ return base64.b64encode(signature).decode("ascii")
65
+
66
+
67
+ def build_hook_manifest(
68
+ *, tenant_id: str, workload_uuid: str, deployment_id: str,
69
+ expected_hooks: Iterable[str], observed_hooks: Iterable[str],
70
+ private_key_pem: str, sdk_version: str, framework_versions: Optional[Dict[str, str]] = None,
71
+ high_impact_actions: Optional[Iterable[str]] = None, build_sha256: Optional[str] = None,
72
+ sdk_checksum: Optional[str] = None, ttl_seconds: int = 600,
73
+ ) -> Dict[str, Any]:
74
+ now = int(time.time())
75
+ claims: Dict[str, Any] = {
76
+ "schema": "agenticdome.hook-manifest.v1",
77
+ "tenant_id": tenant_id,
78
+ "workload_uuid": workload_uuid,
79
+ "deployment_id": deployment_id,
80
+ "manifest_id": str(uuid.uuid4()),
81
+ "build_sha256": build_sha256,
82
+ "sdk_checksum": sdk_checksum,
83
+ "sdk_version": sdk_version,
84
+ "framework_versions": framework_versions or {},
85
+ "expected_hooks": sorted(set(str(item) for item in expected_hooks if item)),
86
+ "observed_hooks": sorted(set(str(item) for item in observed_hooks if item)),
87
+ "high_impact_actions": sorted(set(str(item) for item in (high_impact_actions or []) if item)),
88
+ "started_at": datetime.fromtimestamp(now, timezone.utc).isoformat(),
89
+ "expires_at": datetime.fromtimestamp(now + max(60, min(ttl_seconds, 600)), timezone.utc).isoformat(),
90
+ }
91
+ return {**claims, "signature": sign_claims(claims, private_key_pem)}
92
+
93
+
94
+ def build_hook_heartbeat(
95
+ *, tenant_id: str, workload_uuid: str, deployment_id: str, manifest_sha256: str,
96
+ active_hooks: Iterable[str], sequence: int, private_key_pem: str, ttl_seconds: int = 120,
97
+ ) -> Dict[str, Any]:
98
+ now = int(time.time())
99
+ claims: Dict[str, Any] = {
100
+ "schema": "agenticdome.hook-heartbeat.v1",
101
+ "tenant_id": tenant_id,
102
+ "workload_uuid": workload_uuid,
103
+ "deployment_id": deployment_id,
104
+ "manifest_sha256": manifest_sha256,
105
+ "sequence": max(1, int(sequence)),
106
+ "active_hooks": sorted(set(str(item) for item in active_hooks if item)),
107
+ "observed_at": datetime.fromtimestamp(now, timezone.utc).isoformat(),
108
+ "expires_at": datetime.fromtimestamp(now + max(30, min(ttl_seconds, 300)), timezone.utc).isoformat(),
109
+ }
110
+ return {**claims, "signature": sign_claims(claims, private_key_pem)}
111
+
112
+
113
+ def manifest_sha256(manifest: Dict[str, Any]) -> str:
114
+ claims = {key: value for key, value in manifest.items() if key != "signature"}
115
+ return hashlib.sha256(canonical_json(claims)).hexdigest()
116
+
117
+
118
+ def _post_json(url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
119
+ request = urllib.request.Request(url, data=canonical_json(payload), method="POST", headers={"Content-Type": "application/json", "Accept": "application/json"})
120
+ with urllib.request.urlopen(request, timeout=15) as response: # noqa: S310 - explicit AgenticDome portal
121
+ value = json.loads(response.read().decode("utf-8"))
122
+ if not isinstance(value, dict):
123
+ raise RuntimeError("AgenticDome returned an invalid attestation response.")
124
+ return value
125
+
126
+
127
+ class RuntimeCoverageAttestor:
128
+ """Background signed manifest/heartbeat reporter; never sends source or arguments."""
129
+
130
+ def __init__(self, *, portal: str, tenant_id: str, workload_uuid: str, deployment_id: str,
131
+ private_key_pem: str, expected_hooks: Iterable[str], sdk_version: str,
132
+ high_impact_actions: Optional[Iterable[str]] = None, heartbeat_seconds: int = 60):
133
+ self.portal = portal.rstrip("/")
134
+ self.tenant_id = tenant_id
135
+ self.workload_uuid = workload_uuid
136
+ self.deployment_id = deployment_id
137
+ self.private_key_pem = private_key_pem
138
+ self.expected_hooks = set(str(item) for item in expected_hooks if item)
139
+ self.observed_hooks: set[str] = set()
140
+ self.sdk_version = sdk_version
141
+ self.high_impact_actions = set(str(item) for item in (high_impact_actions or []) if item)
142
+ self.heartbeat_seconds = max(15, min(int(heartbeat_seconds), 120))
143
+ self.sequence = 0
144
+ self.current_manifest_sha256 = ""
145
+ self.last_error: Optional[str] = None
146
+ self._stop = threading.Event()
147
+ self._thread: Optional[threading.Thread] = None
148
+ self._lock = threading.Lock()
149
+
150
+ @classmethod
151
+ def from_environment(cls, expected_hooks: Iterable[str], high_impact_actions: Optional[Iterable[str]] = None) -> Optional["RuntimeCoverageAttestor"]:
152
+ names = ["AGENTICDOME_CONTROL_PLANE_URL", "AGENTICDOME_TENANT_ID", "AGENTICDOME_WORKLOAD_UUID", "AGENTICDOME_DEPLOYMENT_ID", "AGENTICDOME_ATTESTATION_KEY_PATH"]
153
+ values = {name: os.getenv(name, "").strip() for name in names}
154
+ if not all(values.values()):
155
+ return None
156
+ key = Path(values["AGENTICDOME_ATTESTATION_KEY_PATH"]).read_text(encoding="ascii")
157
+ return cls(portal=values["AGENTICDOME_CONTROL_PLANE_URL"], tenant_id=values["AGENTICDOME_TENANT_ID"],
158
+ workload_uuid=values["AGENTICDOME_WORKLOAD_UUID"], deployment_id=values["AGENTICDOME_DEPLOYMENT_ID"],
159
+ private_key_pem=key, expected_hooks=expected_hooks, sdk_version=os.getenv("AGENTICDOME_SDK_VERSION", "unknown"),
160
+ high_impact_actions=high_impact_actions)
161
+
162
+ def observe(self, hook_id: str) -> None:
163
+ if hook_id:
164
+ with self._lock:
165
+ self.observed_hooks.add(str(hook_id))
166
+
167
+ def start(self) -> None:
168
+ if self._thread and self._thread.is_alive():
169
+ return
170
+ self._thread = threading.Thread(target=self._run, name="agenticdome-coverage", daemon=True)
171
+ self._thread.start()
172
+
173
+ def stop(self, timeout: float = 2.0) -> None:
174
+ self._stop.set()
175
+ if self._thread:
176
+ self._thread.join(timeout=max(0.0, timeout))
177
+
178
+ def _manifest(self) -> None:
179
+ with self._lock:
180
+ observed = sorted(self.observed_hooks)
181
+ manifest = build_hook_manifest(tenant_id=self.tenant_id, workload_uuid=self.workload_uuid,
182
+ deployment_id=self.deployment_id, expected_hooks=self.expected_hooks,
183
+ observed_hooks=observed, private_key_pem=self.private_key_pem,
184
+ sdk_version=self.sdk_version, high_impact_actions=self.high_impact_actions)
185
+ _post_json(self.portal + "/api/agentguard/runtime/hook-manifest", manifest)
186
+ self.current_manifest_sha256 = manifest_sha256(manifest)
187
+
188
+ def _run(self) -> None:
189
+ manifest_at = 0.0
190
+ while not self._stop.is_set():
191
+ try:
192
+ if not self.current_manifest_sha256 or time.monotonic() - manifest_at >= 300:
193
+ self._manifest()
194
+ manifest_at = time.monotonic()
195
+ self.sequence += 1
196
+ with self._lock:
197
+ active = sorted(self.observed_hooks)
198
+ heartbeat = build_hook_heartbeat(tenant_id=self.tenant_id, workload_uuid=self.workload_uuid,
199
+ deployment_id=self.deployment_id, manifest_sha256=self.current_manifest_sha256,
200
+ active_hooks=active, sequence=self.sequence, private_key_pem=self.private_key_pem)
201
+ _post_json(self.portal + "/api/agentguard/runtime/hook-heartbeat", heartbeat)
202
+ self.last_error = None
203
+ except Exception as exc: # evidence failure is surfaced via stale coverage; action fail-closed remains gateway-owned
204
+ self.last_error = exc.__class__.__name__
205
+ self._stop.wait(self.heartbeat_seconds)
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schema": "agenticdome.catalog-snapshot.v1",
3
3
  "catalog_schema": "agenticdome.hook-catalog.v1",
4
- "verified_at": "2026-08-31",
5
- "python_sdk_version": "1.2.28",
4
+ "verified_at": "2026-09-10",
5
+ "python_sdk_version": "1.2.30",
6
6
  "published_packages": {
7
7
  "agenticdome-python-sdk": {
8
8
  "registry": "pypi",
9
- "version": "1.2.28",
10
- "url": "https://pypi.org/project/agenticdome-python-sdk/1.2.28/"
9
+ "version": "1.2.30",
10
+ "url": "https://pypi.org/project/agenticdome-python-sdk/1.2.30/"
11
11
  },
12
12
  "agenticdome-sdk": {
13
13
  "registry": "npm",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "openclaw": {
23
23
  "registry": "npm",
24
- "version": "2026.8.1",
25
- "url": "https://www.npmjs.com/package/openclaw/v/2026.8.1"
24
+ "version": "2026.9.3",
25
+ "url": "https://www.npmjs.com/package/openclaw/v/2026.9.3"
26
26
  }
27
27
  }
28
28
  }