openbox-temporal-sdk-python 1.1.0__tar.gz → 1.1.2__tar.gz

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.
Files changed (63) hide show
  1. openbox_temporal_sdk_python-1.1.2/CHANGELOG.md +124 -0
  2. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/PKG-INFO +91 -17
  3. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/README.md +88 -12
  4. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/docs/code-standards.md +29 -15
  5. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/docs/codebase-summary.md +28 -9
  6. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/docs/system-architecture.md +33 -0
  7. openbox_temporal_sdk_python-1.1.2/docs/temporal-plugin-integration-guide.md +147 -0
  8. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/__init__.py +13 -0
  9. openbox_temporal_sdk_python-1.1.2/openbox/activities.py +250 -0
  10. openbox_temporal_sdk_python-1.1.2/openbox/activity_interceptor.py +676 -0
  11. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/client.py +11 -4
  12. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/config.py +13 -7
  13. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/context_propagation.py +4 -1
  14. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/db_governance_hooks.py +247 -345
  15. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/errors.py +33 -4
  16. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/file_governance_hooks.py +67 -25
  17. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/hitl.py +5 -0
  18. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/hook_governance.py +83 -22
  19. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/http_governance_hooks.py +193 -83
  20. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/otel_setup.py +104 -117
  21. openbox_temporal_sdk_python-1.1.2/openbox/plugin.py +210 -0
  22. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/span_processor.py +40 -12
  23. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/tracing.py +83 -106
  24. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/types.py +17 -5
  25. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/verdict_handler.py +17 -7
  26. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/worker.py +38 -16
  27. openbox_temporal_sdk_python-1.1.2/openbox/workflow_interceptor.py +365 -0
  28. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/pyproject.toml +5 -7
  29. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/conftest.py +9 -4
  30. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_activities.py +41 -15
  31. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_activity_interceptor.py +410 -195
  32. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_config.py +16 -7
  33. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_db_governance_hooks.py +149 -71
  34. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_file_governance_hooks.py +111 -43
  35. openbox_temporal_sdk_python-1.1.2/tests/test_http_body_truncation.py +75 -0
  36. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_otel_setup.py +372 -175
  37. openbox_temporal_sdk_python-1.1.2/tests/test_plugin.py +266 -0
  38. openbox_temporal_sdk_python-1.1.2/tests/test_plugin_integration.py +281 -0
  39. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_psycopg2_hooks_verify.py +10 -3
  40. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_span_processor.py +22 -9
  41. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_tracing.py +54 -17
  42. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_worker.py +81 -50
  43. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_workflow_interceptor.py +134 -99
  44. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/uv.lock +107 -619
  45. openbox_temporal_sdk_python-1.1.0/CHANGELOG.md +0 -68
  46. openbox_temporal_sdk_python-1.1.0/openbox/activities.py +0 -198
  47. openbox_temporal_sdk_python-1.1.0/openbox/activity_interceptor.py +0 -626
  48. openbox_temporal_sdk_python-1.1.0/openbox/workflow_interceptor.py +0 -275
  49. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/.github/workflows/publish.yml +0 -0
  50. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/.github/workflows/sonarqube.yaml +0 -0
  51. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/.gitignore +0 -0
  52. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/.python-version +0 -0
  53. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/.repomixignore +0 -0
  54. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/LICENSE +0 -0
  55. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/docs/changelog-hook-level-governance.md +0 -0
  56. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/docs/configuration.md +0 -0
  57. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/docs/project-overview-pdr.md +0 -0
  58. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/openbox/py.typed +0 -0
  59. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/release-manifest.json +0 -0
  60. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/repomix-output.xml +0 -0
  61. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/sonar-project.properties +0 -0
  62. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/__init__.py +0 -0
  63. {openbox_temporal_sdk_python-1.1.0 → openbox_temporal_sdk_python-1.1.2}/tests/test_types.py +0 -0
@@ -0,0 +1,124 @@
1
+ # Changelog
2
+
3
+ All notable changes to OpenBox SDK for Temporal Workflows.
4
+
5
+ ## [1.1.2] - 2026-04-22
6
+
7
+ ### Security
8
+
9
+ - **API key no longer flows through workflow history.** `send_governance_event` refactored into a `GovernanceActivities` class that holds credentials on `self`; activity inputs carry only `payload`, `timeout`, and `on_api_error`. Before this change, anyone with Describe permissions on the namespace could read the API key from the recorded activity input.
10
+
11
+ ### Added
12
+
13
+ - W3C trace propagation through Temporal headers, on by default via `enable_trace_propagation=True` on `OpenBoxPlugin` and `create_openbox_worker()`. Uses `temporalio.contrib.opentelemetry.TracingInterceptor` so spans started by the caller stitch to workflow/activity spans on the worker side.
14
+ - `ApplicationError` type constants in `errors.py` (`GOVERNANCE_HALT_ERROR_TYPE`, `GOVERNANCE_BLOCK_ERROR_TYPE`, `GOVERNANCE_API_ERROR_TYPE`, `GOVERNANCE_STOP_ERROR_TYPE`) — single source of truth for governance error routing.
15
+ - `openbox.activities.GovernanceActivities` class + `build_governance_activities()` factory.
16
+
17
+ ### Fixed
18
+
19
+ - **Workflow exception shadowing** — `WorkflowFailed` event send is now wrapped in `try/except`, so a `GovernanceHaltError` from `fail_closed + API down` no longer replaces the real workflow error.
20
+ - **String-matching on exception types** — `workflow_interceptor` now inspects `ApplicationError.type` via the exception chain instead of `"GovernanceHalt" in str(e)`. Eliminates false positives when a user workflow happens to emit an error message containing a governance keyword.
21
+ - **Race creating governance HTTP client** — `hook_governance._get_sync_client` / `_get_async_client` now use double-checked locking. Previously two concurrent activities could each create a client, with the losing instance getting garbage-collected while its connection pool leaked.
22
+ - **Replayer plugin coverage** — `test_plugin_integration.py` now passes `plugins=[plugin]` to `Replayer`, so replay tests validate interceptor determinism, not just user workflow code.
23
+ - Version-pin mismatch — comments in `openbox/__init__.py` said `temporalio >= 1.24.0` but pyproject/README pin `1.23.0`. Corrected to `1.23.0`.
24
+
25
+ ### Changed
26
+
27
+ - `plugin.py` / `worker.py` now use `logging.getLogger(__name__)` for initialization status messages instead of `print()`.
28
+
29
+ ## [1.1.1] - 2026-04-07
30
+
31
+ ### Added
32
+
33
+ - **OpenBoxPlugin** — drop-in `SimplePlugin` integration for Temporal Workers. Single-line setup: `plugins=[OpenBoxPlugin(openbox_url=..., openbox_api_key=...)]`. Auto-registers interceptors, OTel instrumentation, sandbox passthrough, and `send_governance_event` activity
34
+ - Plugin integration guide for Temporal AI Partner Ecosystem (`docs/temporal-plugin-integration-guide.md`)
35
+ - HTTP body truncation tests (`tests/test_http_body_truncation.py`)
36
+ - Plugin unit tests (`tests/test_plugin.py`) and integration/replay tests (`tests/test_plugin_integration.py`)
37
+
38
+ ### Fixed
39
+
40
+ - **HTTP body truncation** — enforce `max_body_size` (default 64KB) on request/response bodies in governance spans
41
+ - **File I/O spans** — remove raw file content from governance payloads; only `bytes_read`/`bytes_written` metadata sent
42
+ - **error_type sanitization** — prevent serialized error objects from being sent as `error.cause.error_type` string in WorkflowFailed payloads
43
+ - Remove useless f-strings, redundant `(ImportError, Exception)` clauses, merge nested if statements
44
+ - Prefix unused `span` param in urllib hook
45
+
46
+ ### Changed
47
+
48
+ - `temporalio>=1.23.0` (from 1.8.0) for SimplePlugin support
49
+ - `GovernanceConfig.max_body_size` default changed from `None` (unlimited) to `65536` (64KB)
50
+ - `\w` regex shorthand in API key pattern
51
+
52
+ ### Refactored
53
+
54
+ - Reduce cognitive complexity across 7 modules: `activity_interceptor.py` (126→split), `workflow_interceptor.py` (40→split), `activities.py` (20→split), `db_governance_hooks.py` (34→split), `otel_setup.py` (51→split), `tracing.py` (85→split), `verdict_handler.py` (16→15)
55
+ - Extract shared helpers: `_run_governed_query_sync/async`, `_build_error_dict`, `_extract_dbapi_context`, `_instrument_sqlalchemy`
56
+
57
+ ### Dependencies
58
+
59
+ - Bump Pygments 2.19.2 → 2.20.0 (ReDoS fix, CVSS 1.9)
60
+
61
+ ## [1.1.0] - 2026-03-09
62
+
63
+ ### Added
64
+
65
+ - **Hook-level governance** — real-time, per-operation governance evaluation during activity execution
66
+ - Every HTTP request, database query, file operation, and traced function call is evaluated at `started` (before, can block) and `completed` (after, informational) stages
67
+ - Same `POST /api/v1/governance/evaluate` endpoint with new `hook_trigger` field in payload
68
+ - Automatically enabled when using `create_openbox_worker()`
69
+ - **Database query governance** — per-query started/completed evaluations for psycopg2, pymysql, mysql-connector, asyncpg, pymongo, redis, sqlalchemy
70
+ - **File I/O governance** — per-operation evaluations for open, read, write, readline, readlines, writelines, close (opt-in via `instrument_file_io=True`)
71
+ - **`@traced` decorator** (`openbox.tracing`) — function-level governance with OTel spans; zero overhead when governance not configured
72
+ - **`GovernanceBlockedError`** — new exception type for hook-level blocking with verdict, reason, and resource identifier
73
+ - **Abort propagation** — once one hook blocks, all subsequent hooks for the same activity short-circuit immediately
74
+ - **HALT workflow termination** from hook-level governance via `client.terminate()`
75
+ - **REQUIRE_APPROVAL** from hook-level governance enters the same HITL approval polling flow as activity-level approvals
76
+ - **`duration_ns`** computed for all hook span types (HTTP, file, function — DB already had it)
77
+
78
+ ### Changed
79
+
80
+ - **`hook_trigger` simplified to boolean** — was a dict with type/stage/data, now just `true`. All data moved to span root fields
81
+ - **Span data consolidation** — all type-specific fields at span root (`hook_type`, `http_method`, `db_system`, `file_path`, `function`, etc.)
82
+ - **`attributes` is OTel-original only** — no custom `openbox.*`, `http.request.*`, `db.result.*` fields injected
83
+ - Hook governance payloads send only the current span per evaluation (not accumulated history)
84
+ - Event-level payloads (ActivityStarted/Completed, Workflow events) no longer include spans
85
+ - Simplified `WorkflowSpanProcessor` — removed span buffering, governed span tracking, body data merging; `on_end()` now only forwards to fallback exporters
86
+
87
+ ### Fixed
88
+
89
+ - HALT verdict from hooks now correctly terminates the workflow (previously only stopped the activity like BLOCK)
90
+ - REQUIRE_APPROVAL from hooks now enters the approval polling flow (previously raised unhandled error)
91
+ - Stale buffer/verdict from previous workflow run no longer carries over when workflow_id is reused
92
+ - Subsequent hooks no longer fire after the first hook blocks an activity
93
+
94
+ ## [1.0.21] - 2026-03-04
95
+
96
+ ### Added
97
+
98
+ - Human-in-the-loop approval with expiration handling
99
+ - Approval polling via `POST /api/v1/governance/approval`
100
+ - Guardrails: input/output validation and redaction
101
+ - `GovernanceVerdictResponse.from_dict()` with guardrails_result parsing
102
+ - Output redaction for activity results
103
+ - `_deep_update_dataclass()` for in-place dataclass field updates from redacted dicts
104
+
105
+ ### Fixed
106
+
107
+ - Temporal Payload objects no longer slip through as non-serializable in governance payloads
108
+ - Stale buffer detection via run_id comparison
109
+
110
+ ## [1.0.0] - 2026-02-15
111
+
112
+ ### Added
113
+
114
+ - Initial release
115
+ - 6 event types: WorkflowStarted, WorkflowCompleted, WorkflowFailed, SignalReceived, ActivityStarted, ActivityCompleted
116
+ - 5-tier verdict system: ALLOW, CONSTRAIN, REQUIRE_APPROVAL, BLOCK, HALT
117
+ - HTTP instrumentation via OpenTelemetry (httpx, requests, urllib3, urllib)
118
+ - Database instrumentation (psycopg2, pymysql, asyncpg, pymongo, redis, sqlalchemy)
119
+ - File I/O instrumentation (opt-in)
120
+ - Zero-code setup via `create_openbox_worker()` factory
121
+ - Workflow and activity interceptors for governance
122
+ - Span buffering and activity context tracking
123
+ - `fail_open` / `fail_closed` error policies
124
+ - v1.0 backward compatibility for legacy verdict strings
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openbox-temporal-sdk-python
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: OpenBox SDK - Governance and observability for Temporal workflows
5
5
  Project-URL: Homepage, https://github.com/OpenBox-AI/temporal-sdk-python
6
6
  Project-URL: Documentation, https://github.com/OpenBox-AI/temporal-sdk-python#readme
@@ -15,14 +15,12 @@ Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
- Classifier: Programming Language :: Python :: 3.10
20
18
  Classifier: Programming Language :: Python :: 3.11
21
19
  Classifier: Programming Language :: Python :: 3.12
22
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
21
  Classifier: Topic :: System :: Monitoring
24
22
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.9
23
+ Requires-Python: >=3.11
26
24
  Requires-Dist: asyncpg>=0.29.0
27
25
  Requires-Dist: httpx<1,>=0.28.0
28
26
  Requires-Dist: mysql-connector-python>=8.0.0
@@ -45,7 +43,7 @@ Requires-Dist: pymongo>=4.0.0
45
43
  Requires-Dist: pymysql>=1.0.0
46
44
  Requires-Dist: redis>=5.0.0
47
45
  Requires-Dist: sqlalchemy>=2.0.0
48
- Requires-Dist: temporalio<2,>=1.8.0
46
+ Requires-Dist: temporalio<2,>=1.23.0
49
47
  Description-Content-Type: text/markdown
50
48
 
51
49
  # OpenBox SDK for Temporal Workflows
@@ -70,13 +68,70 @@ pip install openbox-temporal-sdk-python
70
68
  ```
71
69
 
72
70
  **Requirements:**
73
- - Python 3.9+
74
- - Temporal SDK 1.8+
71
+ - Python 3.11+
72
+ - Temporal SDK 1.23+ (1.8+ for factory-only usage)
75
73
  - OpenTelemetry API/SDK 1.38.0+
76
74
 
77
75
  ---
78
76
 
79
- ## Quick Start
77
+ ## Plugin Integration (Recommended)
78
+
79
+ Use `OpenBoxPlugin` for drop-in integration with Temporal Workers:
80
+
81
+ ```python
82
+ import os
83
+ from temporalio.worker import Worker
84
+ from openbox.plugin import OpenBoxPlugin
85
+
86
+ worker = Worker(
87
+ client,
88
+ task_queue="my-task-queue",
89
+ workflows=[MyWorkflow],
90
+ activities=[my_activity],
91
+ plugins=[
92
+ OpenBoxPlugin(
93
+ openbox_url=os.getenv("OPENBOX_URL"),
94
+ openbox_api_key=os.getenv("OPENBOX_API_KEY"),
95
+ )
96
+ ],
97
+ )
98
+
99
+ await worker.run()
100
+ ```
101
+
102
+ The plugin automatically configures governance interceptors, OTel instrumentation,
103
+ sandbox passthrough, W3C trace propagation through Temporal headers (via
104
+ `temporalio.contrib.opentelemetry.TracingInterceptor`), and the
105
+ `send_governance_event` activity.
106
+
107
+ **Credentials never leave the plugin.** `openbox_api_key` is captured on the
108
+ governance activity instance itself — it does **not** flow through activity
109
+ inputs, so it is never written to workflow history. To opt out of trace
110
+ propagation (e.g., if you already wire `OpenTelemetryPlugin`), pass
111
+ `enable_trace_propagation=False`.
112
+
113
+ ### Composing with Other Plugins
114
+
115
+ ```python
116
+ from temporalio.contrib.opentelemetry import OpenTelemetryPlugin
117
+
118
+ worker = Worker(
119
+ client,
120
+ task_queue="my-task-queue",
121
+ workflows=[MyWorkflow],
122
+ activities=[my_activity],
123
+ plugins=[
124
+ OpenTelemetryPlugin(),
125
+ OpenBoxPlugin(openbox_url=..., openbox_api_key=...),
126
+ ],
127
+ )
128
+ ```
129
+
130
+ > **Requires** `temporalio >= 1.23.0`. For older versions, use `create_openbox_worker()` below.
131
+
132
+ ---
133
+
134
+ ## Quick Start (Factory)
80
135
 
81
136
  Use the `create_openbox_worker()` factory for simple integration:
82
137
 
@@ -101,8 +156,10 @@ The factory automatically:
101
156
  1. Validates the API key
102
157
  2. Creates span processor
103
158
  3. Sets up OpenTelemetry instrumentation
104
- 4. Creates governance interceptors
105
- 5. Adds `send_governance_event` activity
159
+ 4. Creates governance interceptors (incl. W3C trace propagation)
160
+ 5. Builds the `GovernanceActivities` instance with credentials captured on
161
+ `self` and registers its `send_governance_event` method — the API key is
162
+ never passed through activity inputs / workflow history
106
163
  6. Returns fully configured Worker
107
164
 
108
165
  ---
@@ -148,6 +205,11 @@ worker = create_openbox_worker(
148
205
  # File I/O instrumentation
149
206
  instrument_file_io=False, # disabled by default
150
207
 
208
+ # Header-based W3C trace propagation (client → workflow → activities).
209
+ # Default True. Set False if you already wire OpenTelemetryPlugin or a
210
+ # custom propagator.
211
+ enable_trace_propagation=True,
212
+
151
213
  # Standard Worker options (all supported)
152
214
  activity_executor=my_executor,
153
215
  max_concurrent_activities=10,
@@ -424,7 +486,7 @@ from openbox import (
424
486
  )
425
487
  from openbox.otel_setup import setup_opentelemetry_for_governance
426
488
  from openbox.activity_interceptor import ActivityGovernanceInterceptor
427
- from openbox.activities import send_governance_event
489
+ from openbox.activities import build_governance_activities
428
490
  from openbox.hook_governance import FAIL_OPEN, FAIL_CLOSED # error policy constants
429
491
 
430
492
  # 1. Initialize SDK
@@ -464,14 +526,26 @@ activity_interceptor = ActivityGovernanceInterceptor(
464
526
  config=config,
465
527
  )
466
528
 
467
- # 6. Create worker
529
+ # 6. Build the governance activity instance (credentials captured on `self`
530
+ # so they never flow through workflow history).
531
+ governance_activities = build_governance_activities(
532
+ api_url="http://localhost:8086",
533
+ api_key="obx_test_key_1",
534
+ )
535
+
536
+ # 7. Create worker
468
537
  from temporalio.worker import Worker
538
+ from temporalio.contrib.opentelemetry import TracingInterceptor
469
539
  worker = Worker(
470
540
  client=client,
471
541
  task_queue="my-task-queue",
472
542
  workflows=[MyWorkflow],
473
- activities=[my_activity, send_governance_event],
474
- interceptors=[workflow_interceptor, activity_interceptor],
543
+ activities=[my_activity, governance_activities.send_governance_event],
544
+ interceptors=[
545
+ workflow_interceptor,
546
+ activity_interceptor,
547
+ TracingInterceptor(), # W3C header propagation for OTel spans
548
+ ],
475
549
  )
476
550
  ```
477
551
 
@@ -489,13 +563,13 @@ worker = Worker(
489
563
 
490
564
  ## Testing
491
565
 
492
- The SDK includes comprehensive test coverage with 13 test files:
566
+ The SDK includes comprehensive test coverage with 15 test files:
493
567
 
494
568
  ```bash
495
569
  pytest tests/
496
570
  ```
497
571
 
498
- Test files: `test_activities.py`, `test_activity_interceptor.py`, `test_config.py`, `test_db_governance_hooks.py`, `test_file_governance_hooks.py`, `test_otel_hook_pause.py`, `test_otel_hook_pause_db.py`, `test_otel_setup.py`, `test_span_processor.py`, `test_tracing.py`, `test_types.py`, `test_worker.py`, `test_workflow_interceptor.py`
572
+ Test files: `test_activities.py`, `test_activity_interceptor.py`, `test_config.py`, `test_db_governance_hooks.py`, `test_file_governance_hooks.py`, `test_otel_hook_pause.py`, `test_otel_hook_pause_db.py`, `test_otel_setup.py`, `test_plugin.py`, `test_plugin_integration.py`, `test_span_processor.py`, `test_tracing.py`, `test_types.py`, `test_worker.py`, `test_workflow_interceptor.py`
499
573
 
500
574
  ---
501
575
 
@@ -512,4 +586,4 @@ MIT License - See LICENSE file for details
512
586
 
513
587
  ---
514
588
 
515
- **Version:** 1.1.0 | **Last Updated:** 2026-03-09
589
+ **Version:** 1.2.0 | **Last Updated:** 2026-04-05
@@ -20,13 +20,70 @@ pip install openbox-temporal-sdk-python
20
20
  ```
21
21
 
22
22
  **Requirements:**
23
- - Python 3.9+
24
- - Temporal SDK 1.8+
23
+ - Python 3.11+
24
+ - Temporal SDK 1.23+ (1.8+ for factory-only usage)
25
25
  - OpenTelemetry API/SDK 1.38.0+
26
26
 
27
27
  ---
28
28
 
29
- ## Quick Start
29
+ ## Plugin Integration (Recommended)
30
+
31
+ Use `OpenBoxPlugin` for drop-in integration with Temporal Workers:
32
+
33
+ ```python
34
+ import os
35
+ from temporalio.worker import Worker
36
+ from openbox.plugin import OpenBoxPlugin
37
+
38
+ worker = Worker(
39
+ client,
40
+ task_queue="my-task-queue",
41
+ workflows=[MyWorkflow],
42
+ activities=[my_activity],
43
+ plugins=[
44
+ OpenBoxPlugin(
45
+ openbox_url=os.getenv("OPENBOX_URL"),
46
+ openbox_api_key=os.getenv("OPENBOX_API_KEY"),
47
+ )
48
+ ],
49
+ )
50
+
51
+ await worker.run()
52
+ ```
53
+
54
+ The plugin automatically configures governance interceptors, OTel instrumentation,
55
+ sandbox passthrough, W3C trace propagation through Temporal headers (via
56
+ `temporalio.contrib.opentelemetry.TracingInterceptor`), and the
57
+ `send_governance_event` activity.
58
+
59
+ **Credentials never leave the plugin.** `openbox_api_key` is captured on the
60
+ governance activity instance itself — it does **not** flow through activity
61
+ inputs, so it is never written to workflow history. To opt out of trace
62
+ propagation (e.g., if you already wire `OpenTelemetryPlugin`), pass
63
+ `enable_trace_propagation=False`.
64
+
65
+ ### Composing with Other Plugins
66
+
67
+ ```python
68
+ from temporalio.contrib.opentelemetry import OpenTelemetryPlugin
69
+
70
+ worker = Worker(
71
+ client,
72
+ task_queue="my-task-queue",
73
+ workflows=[MyWorkflow],
74
+ activities=[my_activity],
75
+ plugins=[
76
+ OpenTelemetryPlugin(),
77
+ OpenBoxPlugin(openbox_url=..., openbox_api_key=...),
78
+ ],
79
+ )
80
+ ```
81
+
82
+ > **Requires** `temporalio >= 1.23.0`. For older versions, use `create_openbox_worker()` below.
83
+
84
+ ---
85
+
86
+ ## Quick Start (Factory)
30
87
 
31
88
  Use the `create_openbox_worker()` factory for simple integration:
32
89
 
@@ -51,8 +108,10 @@ The factory automatically:
51
108
  1. Validates the API key
52
109
  2. Creates span processor
53
110
  3. Sets up OpenTelemetry instrumentation
54
- 4. Creates governance interceptors
55
- 5. Adds `send_governance_event` activity
111
+ 4. Creates governance interceptors (incl. W3C trace propagation)
112
+ 5. Builds the `GovernanceActivities` instance with credentials captured on
113
+ `self` and registers its `send_governance_event` method — the API key is
114
+ never passed through activity inputs / workflow history
56
115
  6. Returns fully configured Worker
57
116
 
58
117
  ---
@@ -98,6 +157,11 @@ worker = create_openbox_worker(
98
157
  # File I/O instrumentation
99
158
  instrument_file_io=False, # disabled by default
100
159
 
160
+ # Header-based W3C trace propagation (client → workflow → activities).
161
+ # Default True. Set False if you already wire OpenTelemetryPlugin or a
162
+ # custom propagator.
163
+ enable_trace_propagation=True,
164
+
101
165
  # Standard Worker options (all supported)
102
166
  activity_executor=my_executor,
103
167
  max_concurrent_activities=10,
@@ -374,7 +438,7 @@ from openbox import (
374
438
  )
375
439
  from openbox.otel_setup import setup_opentelemetry_for_governance
376
440
  from openbox.activity_interceptor import ActivityGovernanceInterceptor
377
- from openbox.activities import send_governance_event
441
+ from openbox.activities import build_governance_activities
378
442
  from openbox.hook_governance import FAIL_OPEN, FAIL_CLOSED # error policy constants
379
443
 
380
444
  # 1. Initialize SDK
@@ -414,14 +478,26 @@ activity_interceptor = ActivityGovernanceInterceptor(
414
478
  config=config,
415
479
  )
416
480
 
417
- # 6. Create worker
481
+ # 6. Build the governance activity instance (credentials captured on `self`
482
+ # so they never flow through workflow history).
483
+ governance_activities = build_governance_activities(
484
+ api_url="http://localhost:8086",
485
+ api_key="obx_test_key_1",
486
+ )
487
+
488
+ # 7. Create worker
418
489
  from temporalio.worker import Worker
490
+ from temporalio.contrib.opentelemetry import TracingInterceptor
419
491
  worker = Worker(
420
492
  client=client,
421
493
  task_queue="my-task-queue",
422
494
  workflows=[MyWorkflow],
423
- activities=[my_activity, send_governance_event],
424
- interceptors=[workflow_interceptor, activity_interceptor],
495
+ activities=[my_activity, governance_activities.send_governance_event],
496
+ interceptors=[
497
+ workflow_interceptor,
498
+ activity_interceptor,
499
+ TracingInterceptor(), # W3C header propagation for OTel spans
500
+ ],
425
501
  )
426
502
  ```
427
503
 
@@ -439,13 +515,13 @@ worker = Worker(
439
515
 
440
516
  ## Testing
441
517
 
442
- The SDK includes comprehensive test coverage with 13 test files:
518
+ The SDK includes comprehensive test coverage with 15 test files:
443
519
 
444
520
  ```bash
445
521
  pytest tests/
446
522
  ```
447
523
 
448
- Test files: `test_activities.py`, `test_activity_interceptor.py`, `test_config.py`, `test_db_governance_hooks.py`, `test_file_governance_hooks.py`, `test_otel_hook_pause.py`, `test_otel_hook_pause_db.py`, `test_otel_setup.py`, `test_span_processor.py`, `test_tracing.py`, `test_types.py`, `test_worker.py`, `test_workflow_interceptor.py`
524
+ Test files: `test_activities.py`, `test_activity_interceptor.py`, `test_config.py`, `test_db_governance_hooks.py`, `test_file_governance_hooks.py`, `test_otel_hook_pause.py`, `test_otel_hook_pause_db.py`, `test_otel_setup.py`, `test_plugin.py`, `test_plugin_integration.py`, `test_span_processor.py`, `test_tracing.py`, `test_types.py`, `test_worker.py`, `test_workflow_interceptor.py`
449
525
 
450
526
  ---
451
527
 
@@ -462,4 +538,4 @@ MIT License - See LICENSE file for details
462
538
 
463
539
  ---
464
540
 
465
- **Version:** 1.1.0 | **Last Updated:** 2026-03-09
541
+ **Version:** 1.2.0 | **Last Updated:** 2026-04-05
@@ -83,10 +83,13 @@ This document defines coding standards, architectural patterns, and best practic
83
83
  async def execute_workflow(self, input: ExecuteWorkflowInput) -> Any:
84
84
  info = workflow.info()
85
85
 
86
- # Send event via activity (deterministic)
86
+ # Send event via activity (deterministic). NEVER pass credentials
87
+ # (api_key) in activity inputs — workflow history is readable by anyone
88
+ # with Describe permissions on the namespace. The activity holds the
89
+ # credentials on its instance (see rule 3).
87
90
  await workflow.execute_activity(
88
91
  "send_governance_event",
89
- args=[{"api_url": api_url, "payload": {...}}],
92
+ args=[{"payload": {...}, "timeout": 30.0, "on_api_error": "fail_open"}],
90
93
  start_to_close_timeout=timedelta(seconds=30),
91
94
  )
92
95
 
@@ -122,25 +125,36 @@ async def execute_workflow(self, input: ExecuteWorkflowInput) -> Any:
122
125
  #### Solution: Lazy Imports
123
126
 
124
127
  ```python
125
- # ✅ CORRECT - Lazy import in activity function
126
- @activity.defn(name="send_governance_event")
127
- async def send_governance_event(input: Dict[str, Any]):
128
- import httpx # ✅ Loaded only when activity executes
129
- from datetime import datetime, timezone # ✅ Lazy import
130
-
131
- async with httpx.AsyncClient() as client:
132
- response = await client.post(...)
133
- return response.json()
128
+ # ✅ CORRECT - Class-based activity with credentials captured on self
129
+ class GovernanceActivities:
130
+ def __init__(self, api_url: str, api_key: str):
131
+ self._api_url = api_url
132
+ self._api_key = api_key
133
+
134
+ @activity.defn(name="send_governance_event")
135
+ async def send_governance_event(self, input: Dict[str, Any]):
136
+ import httpx # ✅ Loaded only when activity executes
137
+ async with httpx.AsyncClient() as client:
138
+ response = await client.post(
139
+ f"{self._api_url}/api/v1/governance/evaluate",
140
+ headers={"Authorization": f"Bearer {self._api_key}"},
141
+ json=input["payload"],
142
+ )
143
+ return response.json()
134
144
  ```
135
145
 
136
146
  ```python
137
- # ❌ WRONG - Module-level import
138
- import httpx # ❌ Loaded when module imports, triggers sandbox
139
-
147
+ # ❌ WRONG - Credentials in activity input (leaks to workflow history)
140
148
  @activity.defn(name="send_governance_event")
141
149
  async def send_governance_event(input: Dict[str, Any]):
150
+ # ❌ input["api_key"] is persisted in workflow history — readable by
151
+ # anyone with Describe access to the namespace.
142
152
  async with httpx.AsyncClient() as client:
143
- response = await client.post(...)
153
+ response = await client.post(
154
+ input["api_url"],
155
+ headers={"Authorization": f"Bearer {input['api_key']}"},
156
+ json=input["payload"],
157
+ )
144
158
  return response.json()
145
159
  ```
146
160
 
@@ -22,6 +22,7 @@ openbox-temporal-sdk-python/
22
22
  │ ├── types.py # Type definitions (workflow-safe)
23
23
  │ ├── config.py # Configuration and initialization
24
24
  │ ├── worker.py # Worker factory function
25
+ │ ├── plugin.py # OpenBoxPlugin(SimplePlugin) for Temporal AI Partner Ecosystem
25
26
  │ ├── errors.py # Unified exception hierarchy
26
27
  │ ├── client.py # GovernanceClient (centralized HTTP client)
27
28
  │ ├── hitl.py # HITL approval handling
@@ -327,30 +328,48 @@ def create_openbox_worker(
327
328
  ### 11. Governance Event Activity (`activities.py`)
328
329
 
329
330
  **Purpose:** Execute HTTP calls to OpenBox Core from workflow context
330
- **Activity:** `send_governance_event`
331
+ **Activity:** `send_governance_event` (method of `GovernanceActivities` class)
331
332
 
332
- **Function Signature:**
333
+ **Class Signature:**
333
334
  ```python
334
- @activity.defn(name="send_governance_event")
335
- async def send_governance_event(input: Dict[str, Any]) -> Optional[Dict[str, Any]]
335
+ class GovernanceActivities:
336
+ def __init__(self, api_url: str, api_key: str): ...
337
+
338
+ @activity.defn(name="send_governance_event")
339
+ async def send_governance_event(
340
+ self, input: Dict[str, Any]
341
+ ) -> Optional[Dict[str, Any]]: ...
342
+
343
+
344
+ def build_governance_activities(api_url: str, api_key: str) -> GovernanceActivities
336
345
  ```
337
346
 
338
- **Input Fields:**
339
- - `api_url` - OpenBox Core URL
340
- - `api_key` - Bearer token
347
+ The plugin / `create_openbox_worker()` factory build one instance and register
348
+ its bound `send_governance_event` method with the Temporal Worker.
349
+ Credentials live on `self` they do **not** flow through activity inputs,
350
+ so the API key is never written to workflow history.
351
+
352
+ **Input Fields (activity input dict):**
341
353
  - `payload` - Event data (without timestamp)
342
354
  - `timeout` - Request timeout
343
355
  - `on_api_error` - "fail_open" or "fail_closed"
344
356
 
357
+ > `api_url` / `api_key` are deliberately absent from the input dict to avoid
358
+ > leaking credentials into workflow history. They are held on the activity
359
+ > instance itself.
360
+
345
361
  **Behavior:**
346
362
  - Adds RFC3339 timestamp to payload
347
- - POSTs to `/api/v1/governance/evaluate`
363
+ - POSTs to `{self._api_url}/api/v1/governance/evaluate`
348
364
  - Parses verdict from response
349
365
  - For SignalReceived: Returns verdict dict (workflow interceptor stores it)
350
366
  - For other events with BLOCK/HALT: Raises non-retryable `ApplicationError`
367
+ with `type="GovernanceBlock"` / `"GovernanceHalt"`
351
368
  - On API failure with fail_closed: Raises `GovernanceAPIError`
352
369
 
353
- **Lines of Code:** 163
370
+ A module-level `send_governance_event(input)` helper remains for direct
371
+ callers (tests, scripts) that already hold credentials; it instantiates
372
+ `GovernanceActivities` internally and delegates.
354
373
 
355
374
  ---
356
375
 
@@ -89,6 +89,39 @@ OpenBox SDK for Temporal Workflows is a governance and observability layer that
89
89
 
90
90
  ---
91
91
 
92
+ ## Plugin Integration
93
+
94
+ ### OpenBoxPlugin (SimplePlugin)
95
+
96
+ **File:** `openbox/plugin.py`
97
+
98
+ `OpenBoxPlugin` extends Temporal's `SimplePlugin` base class, providing a drop-in integration for the AI Partner Ecosystem. It composes all existing SDK components (interceptors, OTel setup, governance activities) without modifying them.
99
+
100
+ ```
101
+ Worker(client, task_queue, workflows, activities,
102
+ plugins=[OpenBoxPlugin(openbox_url=..., openbox_api_key=...)])
103
+
104
+ OpenBoxPlugin.__init__():
105
+ → validate_api_key() # config.py
106
+ → WorkflowSpanProcessor() # span_processor.py
107
+ → setup_opentelemetry_for_governance() # otel_setup.py
108
+ → GovernanceInterceptor() # workflow_interceptor.py
109
+ → ActivityGovernanceInterceptor() # activity_interceptor.py
110
+ → SimplePlugin.__init__(interceptors, activities, workflow_runner)
111
+
112
+ OpenBoxPlugin.configure_worker(config):
113
+ → set_temporal_client(config["client"]) # activities.py
114
+ → super().configure_worker(config) # appends interceptors, activities
115
+ ```
116
+
117
+ **Key design choices:**
118
+ - Composition-only — no changes to existing modules
119
+ - Sandbox passthrough for `opentelemetry` via `workflow_runner` callback
120
+ - `configure_worker()` captures Temporal client ref for HALT terminate calls
121
+ - Plugin name: `"openbox.OpenBoxPlugin"` per Temporal's naming standard
122
+
123
+ ---
124
+
92
125
  ## Component Architecture
93
126
 
94
127
  ### 1. Interceptor Layer