openbox-temporal-sdk-python 1.1.2__py3-none-any.whl → 1.2.0__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.
- openbox/__init__.py +21 -77
- openbox/activities.py +44 -16
- openbox/activity_interceptor.py +123 -199
- openbox/client.py +32 -8
- openbox/config.py +166 -49
- openbox/core_adapter.py +279 -0
- openbox/errors.py +63 -33
- openbox/governance_state.py +101 -0
- openbox/hitl.py +8 -12
- openbox/multi_agent.py +136 -0
- openbox/plugin.py +34 -36
- openbox/request_signing.py +89 -0
- openbox/tracing.py +29 -267
- openbox/types.py +63 -172
- openbox/verdict_handler.py +0 -1
- openbox/worker.py +35 -36
- openbox/workflow_interceptor.py +68 -61
- {openbox_temporal_sdk_python-1.1.2.dist-info → openbox_temporal_sdk_python-1.2.0.dist-info}/METADATA +121 -109
- openbox_temporal_sdk_python-1.2.0.dist-info/RECORD +22 -0
- {openbox_temporal_sdk_python-1.1.2.dist-info → openbox_temporal_sdk_python-1.2.0.dist-info}/WHEEL +1 -1
- openbox/context_propagation.py +0 -56
- openbox/db_governance_hooks.py +0 -776
- openbox/file_governance_hooks.py +0 -396
- openbox/hook_governance.py +0 -437
- openbox/http_governance_hooks.py +0 -859
- openbox/otel_setup.py +0 -474
- openbox/span_processor.py +0 -240
- openbox_temporal_sdk_python-1.1.2.dist-info/RECORD +0 -25
- {openbox_temporal_sdk_python-1.1.2.dist-info → openbox_temporal_sdk_python-1.2.0.dist-info}/licenses/LICENSE +0 -0
openbox/__init__.py
CHANGED
|
@@ -1,38 +1,28 @@
|
|
|
1
1
|
"""OpenBox SDK - Workflow-Boundary Governance with OpenTelemetry"""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
-
# Simple Factories (recommended)
|
|
12
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# STATIC on purpose — never read via importlib.metadata. A metadata lookup
|
|
4
|
+
# OPENS A FILE; with file instrumentation active (traced_open patches
|
|
5
|
+
# builtins.open + io.open) that read re-enters governance: eagerly at import
|
|
6
|
+
# it crashes the workflow sandbox as a circular import, lazily it recurses
|
|
7
|
+
# unboundedly from build_auth_headers on every evaluate. Keep in sync with
|
|
8
|
+
# pyproject.toml on release.
|
|
9
|
+
__version__ = "1.2.0"
|
|
13
10
|
|
|
14
11
|
from .worker import create_openbox_worker
|
|
15
12
|
|
|
16
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
17
|
-
# Core Configuration
|
|
18
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
19
|
-
|
|
20
13
|
from .config import (
|
|
21
14
|
initialize,
|
|
22
15
|
get_global_config,
|
|
23
16
|
GovernanceConfig,
|
|
24
17
|
)
|
|
25
18
|
|
|
26
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
27
|
-
# Errors (unified hierarchy in errors.py)
|
|
28
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
29
|
-
|
|
30
19
|
from .errors import (
|
|
31
20
|
OpenBoxError,
|
|
32
21
|
OpenBoxConfigError,
|
|
33
22
|
OpenBoxAuthError,
|
|
34
23
|
OpenBoxNetworkError,
|
|
35
24
|
OpenBoxInsecureURLError,
|
|
25
|
+
OpenBoxSigningError,
|
|
36
26
|
GovernanceBlockedError,
|
|
37
27
|
GovernanceHaltError,
|
|
38
28
|
GovernanceAPIError,
|
|
@@ -41,36 +31,22 @@ from .errors import (
|
|
|
41
31
|
ApprovalRejectedError,
|
|
42
32
|
ApprovalTimeoutError,
|
|
43
33
|
extract_governance_error,
|
|
34
|
+
map_signing_error,
|
|
44
35
|
)
|
|
45
36
|
|
|
46
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
47
|
-
# Types (sandbox-safe - can be imported in workflow code)
|
|
48
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
49
|
-
|
|
50
37
|
from .types import (
|
|
51
38
|
Verdict,
|
|
52
39
|
WorkflowEventType,
|
|
53
|
-
WorkflowSpanBuffer,
|
|
54
40
|
GovernanceVerdictResponse,
|
|
55
41
|
GuardrailsCheckResult,
|
|
56
42
|
)
|
|
57
43
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
from .span_processor import WorkflowSpanProcessor
|
|
63
|
-
|
|
64
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
65
|
-
# Interceptors
|
|
66
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
44
|
+
# Multi-agent primitives (sandbox-safe — only imports temporalio.workflow eagerly;
|
|
45
|
+
# signing/HTTP routed lazily through the governance activity).
|
|
46
|
+
from .multi_agent import emit_handoff
|
|
67
47
|
|
|
68
48
|
from .workflow_interceptor import GovernanceInterceptor
|
|
69
49
|
|
|
70
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
71
|
-
# Plugin (requires temporalio >= 1.23.0)
|
|
72
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
73
|
-
|
|
74
50
|
try:
|
|
75
51
|
from temporalio.plugin import SimplePlugin # noqa: F401 — probe only
|
|
76
52
|
|
|
@@ -78,31 +54,17 @@ try:
|
|
|
78
54
|
except ImportError:
|
|
79
55
|
pass # temporalio < 1.23.0, plugin not available
|
|
80
56
|
|
|
81
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
82
|
-
# Verdict Handler
|
|
83
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
84
|
-
|
|
85
57
|
from .verdict_handler import enforce_verdict, VerdictEnforcementResult
|
|
86
58
|
|
|
87
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
88
|
-
# HITL — Human-in-the-Loop approval helpers
|
|
89
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
90
|
-
|
|
91
59
|
from .hitl import handle_approval_response, raise_approval_pending, should_skip_hitl
|
|
92
60
|
|
|
93
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
94
|
-
# Governance HTTP Client (sandbox-safe — httpx imported lazily inside methods)
|
|
95
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
96
|
-
|
|
97
61
|
from .client import GovernanceClient
|
|
98
62
|
|
|
99
63
|
# NOTE: ActivityGovernanceInterceptor is NOT imported here because it imports
|
|
100
64
|
# OpenTelemetry which uses importlib_metadata -> os.stat, causing sandbox issues.
|
|
101
65
|
# Users must import directly: from openbox.activity_interceptor import ActivityGovernanceInterceptor
|
|
102
66
|
|
|
103
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
104
67
|
# Activities - DO NOT import here!
|
|
105
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
106
68
|
#
|
|
107
69
|
# IMPORTANT: Do NOT import activities from openbox/__init__.py!
|
|
108
70
|
# activities.py imports httpx which uses os.stat internally. If we re-export them
|
|
@@ -111,25 +73,16 @@ from .client import GovernanceClient
|
|
|
111
73
|
# Users must import directly:
|
|
112
74
|
# from openbox.activities import send_governance_event
|
|
113
75
|
|
|
114
|
-
#
|
|
115
|
-
# OTel Setup - NOT imported here to avoid sandbox issues!
|
|
116
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
76
|
+
# Instrumentation — owned by the openbox_core base runtime
|
|
117
77
|
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
78
|
+
# HTTP/DB/file/function hook instrumentation is installed by the base runtime
|
|
79
|
+
# (create_openbox_worker / OpenBoxPlugin call runtime.install_instrumentation()).
|
|
80
|
+
# There is no Temporal-local OpenTelemetry setup to import.
|
|
121
81
|
#
|
|
122
|
-
# Users must import directly: from openbox.otel_setup import setup_opentelemetry_for_governance
|
|
123
|
-
|
|
124
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
125
82
|
# Tracing Decorators - NOT imported here to avoid sandbox issues!
|
|
126
|
-
# ═══════════════════════════════════════════════════════════════════════════════
|
|
127
|
-
#
|
|
128
|
-
# Use the @traced decorator to capture internal function calls as spans.
|
|
129
|
-
# Import directly: from openbox.tracing import traced, create_span
|
|
130
83
|
#
|
|
131
|
-
#
|
|
132
|
-
# from openbox.tracing import traced
|
|
84
|
+
# @traced wraps the base SDK's governed() decorator. Import directly:
|
|
85
|
+
# from openbox.tracing import traced, create_span
|
|
133
86
|
#
|
|
134
87
|
# @traced
|
|
135
88
|
# def my_function(data):
|
|
@@ -137,20 +90,17 @@ from .client import GovernanceClient
|
|
|
137
90
|
|
|
138
91
|
|
|
139
92
|
__all__ = [
|
|
140
|
-
# Simple Worker Factory (recommended)
|
|
141
93
|
"create_openbox_worker",
|
|
142
|
-
# Plugin (recommended for temporalio >= 1.23.0)
|
|
143
94
|
"OpenBoxPlugin",
|
|
144
|
-
# Configuration
|
|
145
95
|
"initialize",
|
|
146
96
|
"get_global_config",
|
|
147
97
|
"GovernanceConfig",
|
|
148
|
-
# Errors (unified hierarchy)
|
|
149
98
|
"OpenBoxError",
|
|
150
99
|
"OpenBoxConfigError",
|
|
151
100
|
"OpenBoxAuthError",
|
|
152
101
|
"OpenBoxNetworkError",
|
|
153
102
|
"OpenBoxInsecureURLError",
|
|
103
|
+
"OpenBoxSigningError",
|
|
154
104
|
"GovernanceBlockedError",
|
|
155
105
|
"GovernanceHaltError",
|
|
156
106
|
"GovernanceAPIError",
|
|
@@ -159,23 +109,17 @@ __all__ = [
|
|
|
159
109
|
"ApprovalRejectedError",
|
|
160
110
|
"ApprovalTimeoutError",
|
|
161
111
|
"extract_governance_error",
|
|
162
|
-
|
|
112
|
+
"map_signing_error",
|
|
163
113
|
"Verdict",
|
|
164
114
|
"WorkflowEventType",
|
|
165
|
-
"WorkflowSpanBuffer",
|
|
166
115
|
"GovernanceVerdictResponse",
|
|
167
116
|
"GuardrailsCheckResult",
|
|
168
|
-
|
|
169
|
-
"WorkflowSpanProcessor",
|
|
170
|
-
# Interceptors
|
|
117
|
+
"emit_handoff",
|
|
171
118
|
"GovernanceInterceptor",
|
|
172
|
-
# Verdict handler
|
|
173
119
|
"enforce_verdict",
|
|
174
120
|
"VerdictEnforcementResult",
|
|
175
|
-
# HITL helpers
|
|
176
121
|
"handle_approval_response",
|
|
177
122
|
"raise_approval_pending",
|
|
178
123
|
"should_skip_hitl",
|
|
179
|
-
# Governance HTTP client
|
|
180
124
|
"GovernanceClient",
|
|
181
125
|
]
|
openbox/activities.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
# openbox/activities.py
|
|
2
|
-
#
|
|
3
|
-
# IMPORTANT: This module imports httpx which uses os.stat internally.
|
|
4
|
-
# Do NOT import this module from workflow code (workflow_interceptor.py)!
|
|
5
|
-
# The workflow interceptor references this activity by string name "send_governance_event".
|
|
6
1
|
"""
|
|
7
2
|
Governance event activity for workflow-level HTTP calls.
|
|
8
3
|
|
|
4
|
+
NOT sandbox-safe: this module imports httpx. Workflow code references the
|
|
5
|
+
activity by string name instead of importing it.
|
|
6
|
+
|
|
9
7
|
CRITICAL: Temporal workflows must be deterministic. HTTP calls are NOT allowed directly
|
|
10
8
|
in workflow code (including interceptors). WorkflowInboundInterceptor sends events via
|
|
11
9
|
workflow.execute_activity() using this activity.
|
|
@@ -29,13 +27,12 @@ from datetime import datetime, timezone
|
|
|
29
27
|
from typing import Dict, Any, Optional
|
|
30
28
|
|
|
31
29
|
|
|
32
|
-
from .types import rfc3339_now as _rfc3339_now
|
|
30
|
+
from .types import rfc3339_now as _rfc3339_now
|
|
33
31
|
|
|
34
32
|
from temporalio import activity
|
|
35
33
|
from temporalio.exceptions import ApplicationError
|
|
36
34
|
|
|
37
35
|
from .types import Verdict
|
|
38
|
-
from .hook_governance import build_auth_headers
|
|
39
36
|
|
|
40
37
|
logger = logging.getLogger(__name__)
|
|
41
38
|
|
|
@@ -50,7 +47,6 @@ def set_temporal_client(client) -> None:
|
|
|
50
47
|
_temporal_client = client
|
|
51
48
|
|
|
52
49
|
|
|
53
|
-
# Re-export from errors.py for backward compatibility
|
|
54
50
|
from .errors import GovernanceAPIError # noqa: F401
|
|
55
51
|
|
|
56
52
|
|
|
@@ -151,9 +147,13 @@ class GovernanceActivities:
|
|
|
151
147
|
worker-init time by the plugin / create_openbox_worker factory.
|
|
152
148
|
"""
|
|
153
149
|
|
|
154
|
-
def __init__(self, api_url: str, api_key: str):
|
|
150
|
+
def __init__(self, api_url: str, api_key: str, *, agent_did=None, signer=None):
|
|
155
151
|
self._api_url = api_url.rstrip("/")
|
|
156
152
|
self._api_key = api_key
|
|
153
|
+
# AIP signing material — held on the instance so it never flows through
|
|
154
|
+
# activity inputs / workflow history.
|
|
155
|
+
self._agent_did = agent_did
|
|
156
|
+
self._signer = signer
|
|
157
157
|
|
|
158
158
|
@activity.defn(name="send_governance_event")
|
|
159
159
|
async def send_governance_event(
|
|
@@ -173,12 +173,24 @@ class GovernanceActivities:
|
|
|
173
173
|
payload = {**event_payload, "timestamp": _rfc3339_now()}
|
|
174
174
|
event_type = event_payload.get("event_type", "unknown")
|
|
175
175
|
|
|
176
|
+
# Sign once over the exact bytes we transmit (timestamp included).
|
|
177
|
+
from .request_signing import prepare_signed_request
|
|
178
|
+
|
|
179
|
+
headers, body = prepare_signed_request(
|
|
180
|
+
"POST",
|
|
181
|
+
"/api/v1/governance/evaluate",
|
|
182
|
+
payload,
|
|
183
|
+
api_key=self._api_key,
|
|
184
|
+
agent_did=self._agent_did,
|
|
185
|
+
signer=self._signer,
|
|
186
|
+
)
|
|
187
|
+
|
|
176
188
|
try:
|
|
177
189
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
178
190
|
response = await client.post(
|
|
179
191
|
f"{self._api_url}/api/v1/governance/evaluate",
|
|
180
|
-
|
|
181
|
-
headers=
|
|
192
|
+
content=body,
|
|
193
|
+
headers=headers,
|
|
182
194
|
)
|
|
183
195
|
|
|
184
196
|
if response.status_code != 200:
|
|
@@ -220,20 +232,36 @@ class GovernanceActivities:
|
|
|
220
232
|
|
|
221
233
|
|
|
222
234
|
def build_governance_activities(
|
|
223
|
-
api_url: str,
|
|
235
|
+
api_url: str,
|
|
236
|
+
api_key: str,
|
|
237
|
+
*,
|
|
238
|
+
agent_did=None,
|
|
239
|
+
signer=None,
|
|
224
240
|
) -> GovernanceActivities:
|
|
225
|
-
"""Factory used by plugin.py and worker.py to build the activities instance.
|
|
226
|
-
|
|
241
|
+
"""Factory used by plugin.py and worker.py to build the activities instance.
|
|
242
|
+
|
|
243
|
+
agent_did + signer enable AIP signed requests; both stay on the instance
|
|
244
|
+
(never in inputs).
|
|
245
|
+
"""
|
|
246
|
+
# Fall back to the globally-configured signer/DID when omitted (manual setups),
|
|
247
|
+
# so workflow/signal events routed through this activity are signed too.
|
|
248
|
+
from .config import resolve_signing_defaults
|
|
249
|
+
|
|
250
|
+
agent_did, signer = resolve_signing_defaults(agent_did, signer)
|
|
251
|
+
return GovernanceActivities(
|
|
252
|
+
api_url=api_url,
|
|
253
|
+
api_key=api_key,
|
|
254
|
+
agent_did=agent_did,
|
|
255
|
+
signer=signer,
|
|
256
|
+
)
|
|
227
257
|
|
|
228
258
|
|
|
229
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
230
259
|
# Backward-compat module-level helper.
|
|
231
260
|
#
|
|
232
261
|
# Not decorated with @activity.defn — worker/plugin register the class-based
|
|
233
262
|
# version above so credentials never flow through activity inputs. This shim
|
|
234
263
|
# exists for direct callers (tests, scripts) who already hold credentials and
|
|
235
264
|
# want to invoke the HTTP logic without constructing the class themselves.
|
|
236
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
237
265
|
async def send_governance_event(input: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
238
266
|
"""Backward-compat wrapper — delegates to GovernanceActivities.
|
|
239
267
|
|