openbox-temporal-sdk-python 1.0.2__tar.gz → 1.1.0__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.
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.github/workflows/sonarqube.yaml +1 -1
- openbox_temporal_sdk_python-1.1.0/CHANGELOG.md +68 -0
- openbox_temporal_sdk_python-1.1.0/PKG-INFO +515 -0
- openbox_temporal_sdk_python-1.1.0/README.md +465 -0
- openbox_temporal_sdk_python-1.1.0/docs/changelog-hook-level-governance.md +146 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/code-standards.md +43 -19
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/codebase-summary.md +306 -42
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/configuration.md +11 -2
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/project-overview-pdr.md +99 -19
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/system-architecture.md +425 -67
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/__init__.py +61 -2
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/activities.py +62 -28
- openbox_temporal_sdk_python-1.1.0/openbox/activity_interceptor.py +626 -0
- openbox_temporal_sdk_python-1.1.0/openbox/client.py +181 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/config.py +18 -29
- openbox_temporal_sdk_python-1.1.0/openbox/context_propagation.py +53 -0
- openbox_temporal_sdk_python-1.1.0/openbox/db_governance_hooks.py +874 -0
- openbox_temporal_sdk_python-1.1.0/openbox/errors.py +164 -0
- openbox_temporal_sdk_python-1.1.0/openbox/file_governance_hooks.py +354 -0
- openbox_temporal_sdk_python-1.1.0/openbox/hitl.py +120 -0
- openbox_temporal_sdk_python-1.1.0/openbox/hook_governance.py +376 -0
- openbox_temporal_sdk_python-1.1.0/openbox/http_governance_hooks.py +749 -0
- openbox_temporal_sdk_python-1.1.0/openbox/otel_setup.py +487 -0
- openbox_temporal_sdk_python-1.1.0/openbox/span_processor.py +212 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/tracing.py +92 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/types.py +13 -3
- openbox_temporal_sdk_python-1.1.0/openbox/verdict_handler.py +93 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/worker.py +21 -1
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/workflow_interceptor.py +19 -8
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/pyproject.toml +16 -15
- openbox_temporal_sdk_python-1.1.0/tests/conftest.py +91 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_activities.py +92 -50
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_activity_interceptor.py +294 -18
- openbox_temporal_sdk_python-1.1.0/tests/test_db_governance_hooks.py +808 -0
- openbox_temporal_sdk_python-1.1.0/tests/test_file_governance_hooks.py +716 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_otel_setup.py +570 -286
- openbox_temporal_sdk_python-1.1.0/tests/test_psycopg2_hooks_verify.py +53 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_span_processor.py +17 -469
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_tracing.py +384 -1
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_worker.py +10 -6
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_workflow_interceptor.py +7 -7
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/uv.lock +98 -110
- openbox_temporal_sdk_python-1.0.2/PKG-INFO +0 -374
- openbox_temporal_sdk_python-1.0.2/README.md +0 -325
- openbox_temporal_sdk_python-1.0.2/openbox/activity_interceptor.py +0 -761
- openbox_temporal_sdk_python-1.0.2/openbox/otel_setup.py +0 -1007
- openbox_temporal_sdk_python-1.0.2/openbox/span_processor.py +0 -361
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.github/workflows/publish.yml +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.gitignore +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.python-version +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.repomixignore +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/LICENSE +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/py.typed +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/release-manifest.json +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/repomix-output.xml +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/sonar-project.properties +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/__init__.py +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_config.py +0 -0
- {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_types.py +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to OpenBox SDK for Temporal Workflows.
|
|
4
|
+
|
|
5
|
+
## [1.1.0] - 2026-03-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Hook-level governance** — real-time, per-operation governance evaluation during activity execution
|
|
10
|
+
- Every HTTP request, database query, file operation, and traced function call is evaluated at `started` (before, can block) and `completed` (after, informational) stages
|
|
11
|
+
- Same `POST /api/v1/governance/evaluate` endpoint with new `hook_trigger` field in payload
|
|
12
|
+
- Automatically enabled when using `create_openbox_worker()`
|
|
13
|
+
- **Database query governance** — per-query started/completed evaluations for psycopg2, pymysql, mysql-connector, asyncpg, pymongo, redis, sqlalchemy
|
|
14
|
+
- **File I/O governance** — per-operation evaluations for open, read, write, readline, readlines, writelines, close (opt-in via `instrument_file_io=True`)
|
|
15
|
+
- **`@traced` decorator** (`openbox.tracing`) — function-level governance with OTel spans; zero overhead when governance not configured
|
|
16
|
+
- **`GovernanceBlockedError`** — new exception type for hook-level blocking with verdict, reason, and resource identifier
|
|
17
|
+
- **Abort propagation** — once one hook blocks, all subsequent hooks for the same activity short-circuit immediately
|
|
18
|
+
- **HALT workflow termination** from hook-level governance via `client.terminate()`
|
|
19
|
+
- **REQUIRE_APPROVAL** from hook-level governance enters the same HITL approval polling flow as activity-level approvals
|
|
20
|
+
- **`duration_ns`** computed for all hook span types (HTTP, file, function — DB already had it)
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **`hook_trigger` simplified to boolean** — was a dict with type/stage/data, now just `true`. All data moved to span root fields
|
|
25
|
+
- **Span data consolidation** — all type-specific fields at span root (`hook_type`, `http_method`, `db_system`, `file_path`, `function`, etc.)
|
|
26
|
+
- **`attributes` is OTel-original only** — no custom `openbox.*`, `http.request.*`, `db.result.*` fields injected
|
|
27
|
+
- Hook governance payloads send only the current span per evaluation (not accumulated history)
|
|
28
|
+
- Event-level payloads (ActivityStarted/Completed, Workflow events) no longer include spans
|
|
29
|
+
- Simplified `WorkflowSpanProcessor` — removed span buffering, governed span tracking, body data merging; `on_end()` now only forwards to fallback exporters
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- HALT verdict from hooks now correctly terminates the workflow (previously only stopped the activity like BLOCK)
|
|
34
|
+
- REQUIRE_APPROVAL from hooks now enters the approval polling flow (previously raised unhandled error)
|
|
35
|
+
- Stale buffer/verdict from previous workflow run no longer carries over when workflow_id is reused
|
|
36
|
+
- Subsequent hooks no longer fire after the first hook blocks an activity
|
|
37
|
+
|
|
38
|
+
## [1.0.21] - 2026-03-04
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Human-in-the-loop approval with expiration handling
|
|
43
|
+
- Approval polling via `POST /api/v1/governance/approval`
|
|
44
|
+
- Guardrails: input/output validation and redaction
|
|
45
|
+
- `GovernanceVerdictResponse.from_dict()` with guardrails_result parsing
|
|
46
|
+
- Output redaction for activity results
|
|
47
|
+
- `_deep_update_dataclass()` for in-place dataclass field updates from redacted dicts
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Temporal Payload objects no longer slip through as non-serializable in governance payloads
|
|
52
|
+
- Stale buffer detection via run_id comparison
|
|
53
|
+
|
|
54
|
+
## [1.0.0] - 2026-02-15
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- Initial release
|
|
59
|
+
- 6 event types: WorkflowStarted, WorkflowCompleted, WorkflowFailed, SignalReceived, ActivityStarted, ActivityCompleted
|
|
60
|
+
- 5-tier verdict system: ALLOW, CONSTRAIN, REQUIRE_APPROVAL, BLOCK, HALT
|
|
61
|
+
- HTTP instrumentation via OpenTelemetry (httpx, requests, urllib3, urllib)
|
|
62
|
+
- Database instrumentation (psycopg2, pymysql, asyncpg, pymongo, redis, sqlalchemy)
|
|
63
|
+
- File I/O instrumentation (opt-in)
|
|
64
|
+
- Zero-code setup via `create_openbox_worker()` factory
|
|
65
|
+
- Workflow and activity interceptors for governance
|
|
66
|
+
- Span buffering and activity context tracking
|
|
67
|
+
- `fail_open` / `fail_closed` error policies
|
|
68
|
+
- v1.0 backward compatibility for legacy verdict strings
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openbox-temporal-sdk-python
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: OpenBox SDK - Governance and observability for Temporal workflows
|
|
5
|
+
Project-URL: Homepage, https://github.com/OpenBox-AI/temporal-sdk-python
|
|
6
|
+
Project-URL: Documentation, https://github.com/OpenBox-AI/temporal-sdk-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/OpenBox-AI/temporal-sdk-python.git
|
|
8
|
+
Project-URL: Issues, https://github.com/OpenBox-AI/temporal-sdk-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/OpenBox-AI/temporal-sdk-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: OpenBox Team <tino@openbox.ai>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: governance,hitl,human-in-the-loop,observability,opentelemetry,temporal,workflow
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
27
|
+
Requires-Dist: httpx<1,>=0.28.0
|
|
28
|
+
Requires-Dist: mysql-connector-python>=8.0.0
|
|
29
|
+
Requires-Dist: opentelemetry-api<1.40.0,>=1.38.0
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-asyncpg<0.61b0,>=0.59b0
|
|
31
|
+
Requires-Dist: opentelemetry-instrumentation-httpx<0.61b0,>=0.59b0
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-mysql<0.61b0,>=0.59b0
|
|
33
|
+
Requires-Dist: opentelemetry-instrumentation-psycopg2<0.61b0,>=0.59b0
|
|
34
|
+
Requires-Dist: opentelemetry-instrumentation-pymongo<0.61b0,>=0.59b0
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-pymysql<0.61b0,>=0.59b0
|
|
36
|
+
Requires-Dist: opentelemetry-instrumentation-redis<0.61b0,>=0.59b0
|
|
37
|
+
Requires-Dist: opentelemetry-instrumentation-requests<0.61b0,>=0.59b0
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy<0.61b0,>=0.59b0
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-sqlite3<0.61b0,>=0.59b0
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-urllib3<0.61b0,>=0.59b0
|
|
41
|
+
Requires-Dist: opentelemetry-instrumentation-urllib<0.61b0,>=0.59b0
|
|
42
|
+
Requires-Dist: opentelemetry-sdk<1.40.0,>=1.38.0
|
|
43
|
+
Requires-Dist: psycopg2-binary>=2.9.10
|
|
44
|
+
Requires-Dist: pymongo>=4.0.0
|
|
45
|
+
Requires-Dist: pymysql>=1.0.0
|
|
46
|
+
Requires-Dist: redis>=5.0.0
|
|
47
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
48
|
+
Requires-Dist: temporalio<2,>=1.8.0
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# OpenBox SDK for Temporal Workflows
|
|
52
|
+
|
|
53
|
+
OpenBox SDK provides **governance and observability** for Temporal workflows by capturing workflow/activity lifecycle events, HTTP telemetry, database queries, and file operations, then sending them to OpenBox Core for policy evaluation.
|
|
54
|
+
|
|
55
|
+
**Key Features:**
|
|
56
|
+
- 6 event types (WorkflowStarted, WorkflowCompleted, WorkflowFailed, SignalReceived, ActivityStarted, ActivityCompleted)
|
|
57
|
+
- 5-tier verdict system (ALLOW, CONSTRAIN, REQUIRE_APPROVAL, BLOCK, HALT)
|
|
58
|
+
- **Hook-level governance** — per-operation evaluation (HTTP requests, file I/O, database queries, function tracing) with started/completed stages
|
|
59
|
+
- HTTP/Database/File I/O instrumentation via OpenTelemetry
|
|
60
|
+
- Guardrails: Input/output validation and redaction
|
|
61
|
+
- Human-in-the-loop approval with expiration handling
|
|
62
|
+
- Zero-code setup via `create_openbox_worker()` factory
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install openbox-temporal-sdk-python
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Requirements:**
|
|
73
|
+
- Python 3.9+
|
|
74
|
+
- Temporal SDK 1.8+
|
|
75
|
+
- OpenTelemetry API/SDK 1.38.0+
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
Use the `create_openbox_worker()` factory for simple integration:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import os
|
|
85
|
+
from openbox import create_openbox_worker
|
|
86
|
+
|
|
87
|
+
worker = create_openbox_worker(
|
|
88
|
+
client=client,
|
|
89
|
+
task_queue="my-task-queue",
|
|
90
|
+
workflows=[MyWorkflow],
|
|
91
|
+
activities=[my_activity],
|
|
92
|
+
# OpenBox config
|
|
93
|
+
openbox_url=os.getenv("OPENBOX_URL"),
|
|
94
|
+
openbox_api_key=os.getenv("OPENBOX_API_KEY"),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
await worker.run()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The factory automatically:
|
|
101
|
+
1. Validates the API key
|
|
102
|
+
2. Creates span processor
|
|
103
|
+
3. Sets up OpenTelemetry instrumentation
|
|
104
|
+
4. Creates governance interceptors
|
|
105
|
+
5. Adds `send_governance_event` activity
|
|
106
|
+
6. Returns fully configured Worker
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
### Environment Variables
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
OPENBOX_URL=http://localhost:8086
|
|
116
|
+
OPENBOX_API_KEY=obx_test_key_1
|
|
117
|
+
OPENBOX_GOVERNANCE_TIMEOUT=30.0
|
|
118
|
+
OPENBOX_GOVERNANCE_POLICY=fail_open # or fail_closed
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Factory Function Parameters
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
worker = create_openbox_worker(
|
|
125
|
+
client=client,
|
|
126
|
+
task_queue="my-task-queue",
|
|
127
|
+
workflows=[MyWorkflow],
|
|
128
|
+
activities=[my_activity],
|
|
129
|
+
|
|
130
|
+
# OpenBox config
|
|
131
|
+
openbox_url="http://localhost:8086",
|
|
132
|
+
openbox_api_key="obx_test_key_1",
|
|
133
|
+
governance_timeout=30.0,
|
|
134
|
+
governance_policy="fail_open",
|
|
135
|
+
|
|
136
|
+
# Event filtering
|
|
137
|
+
send_start_event=True,
|
|
138
|
+
send_activity_start_event=True,
|
|
139
|
+
skip_workflow_types={"InternalWorkflow"},
|
|
140
|
+
skip_activity_types={"send_governance_event"},
|
|
141
|
+
skip_signals={"heartbeat"},
|
|
142
|
+
|
|
143
|
+
# Database instrumentation
|
|
144
|
+
instrument_databases=True,
|
|
145
|
+
db_libraries={"psycopg2", "sqlalchemy"}, # None = all available
|
|
146
|
+
sqlalchemy_engine=engine, # pass pre-existing engine for query capture
|
|
147
|
+
|
|
148
|
+
# File I/O instrumentation
|
|
149
|
+
instrument_file_io=False, # disabled by default
|
|
150
|
+
|
|
151
|
+
# Standard Worker options (all supported)
|
|
152
|
+
activity_executor=my_executor,
|
|
153
|
+
max_concurrent_activities=10,
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Governance Verdicts
|
|
160
|
+
|
|
161
|
+
OpenBox Core returns a verdict indicating what action the SDK should take.
|
|
162
|
+
|
|
163
|
+
| Verdict | Behavior |
|
|
164
|
+
|---------|----------|
|
|
165
|
+
| `ALLOW` | Continue execution normally |
|
|
166
|
+
| `CONSTRAIN` | Log constraints, continue |
|
|
167
|
+
| `REQUIRE_APPROVAL` | Pause, poll for human approval |
|
|
168
|
+
| `BLOCK` | Raise error, stop activity |
|
|
169
|
+
| `HALT` | Raise error, terminate workflow |
|
|
170
|
+
|
|
171
|
+
**v1.0 Backward Compatibility:**
|
|
172
|
+
- `"continue"` → `ALLOW`
|
|
173
|
+
- `"stop"` → `HALT`
|
|
174
|
+
- `"require-approval"` → `REQUIRE_APPROVAL`
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Event Types
|
|
179
|
+
|
|
180
|
+
| Event | Trigger | Captured Fields |
|
|
181
|
+
|-------|---------|-----------------|
|
|
182
|
+
| WorkflowStarted | Workflow begins | workflow_id, run_id, workflow_type, task_queue |
|
|
183
|
+
| WorkflowCompleted | Workflow succeeds | workflow_id, run_id, workflow_type |
|
|
184
|
+
| WorkflowFailed | Workflow fails | workflow_id, run_id, workflow_type, error |
|
|
185
|
+
| SignalReceived | Signal received | workflow_id, signal_name, signal_args |
|
|
186
|
+
| ActivityStarted | Activity begins | activity_id, activity_type, activity_input |
|
|
187
|
+
| ActivityCompleted | Activity ends | activity_id, activity_type, activity_input, activity_output, spans, status, duration |
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Guardrails (Input/Output Redaction)
|
|
192
|
+
|
|
193
|
+
OpenBox Core can validate and redact sensitive data before/after activity execution:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Request
|
|
197
|
+
{
|
|
198
|
+
"verdict": "allow",
|
|
199
|
+
"guardrails_result": {
|
|
200
|
+
"input_type": "activity_input",
|
|
201
|
+
"redacted_input": {"prompt": "[REDACTED]", "user_id": "123"},
|
|
202
|
+
"validation_passed": true,
|
|
203
|
+
"reasons": []
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# If validation fails:
|
|
208
|
+
{
|
|
209
|
+
"validation_passed": false,
|
|
210
|
+
"reasons": [
|
|
211
|
+
{"type": "pii", "field": "email", "reason": "Contains PII"}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Error Handling
|
|
219
|
+
|
|
220
|
+
Configure error policy via `on_api_error` (constants available as `hook_governance.FAIL_OPEN` / `FAIL_CLOSED`):
|
|
221
|
+
|
|
222
|
+
| Policy | Behavior |
|
|
223
|
+
|--------|----------|
|
|
224
|
+
| `fail_open` (default) | If governance API fails, allow workflow to continue |
|
|
225
|
+
| `fail_closed` | If governance API fails, terminate workflow |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Supported Instrumentation
|
|
230
|
+
|
|
231
|
+
### HTTP Libraries
|
|
232
|
+
- `httpx` (sync + async) - full body capture
|
|
233
|
+
- `requests` - full body capture
|
|
234
|
+
- `urllib3` - full body capture
|
|
235
|
+
- `urllib` - request body only
|
|
236
|
+
|
|
237
|
+
### Databases
|
|
238
|
+
|
|
239
|
+
**Fully supported** — any dbapi-compatible library using OTel's `CursorTracer.traced_execution()`:
|
|
240
|
+
- `psycopg2`, `pymysql`, `mysql-connector-python`, and other dbapi-compliant drivers
|
|
241
|
+
|
|
242
|
+
**Custom hooks (best-effort)** — these libraries use non-dbapi instrumentation paths; governance hooks may not work correctly in all scenarios:
|
|
243
|
+
- `asyncpg` — wrapt wrapper on Connection methods (governance runs outside OTel span context)
|
|
244
|
+
- `pymongo` — CommandListener monitoring + wrapt Collection wrappers (dedup via thread-local flag; some internal commands like `endSessions` only produce `completed` stage)
|
|
245
|
+
- `redis` — native OTel `request_hook`/`response_hook`
|
|
246
|
+
- `sqlalchemy` — `before/after_cursor_execute` event listeners
|
|
247
|
+
|
|
248
|
+
**SQLAlchemy Note:** If your SQLAlchemy engine is created before `create_openbox_worker()` runs (e.g., at module import time), you must pass it via the `sqlalchemy_engine` parameter. Without this, `SQLAlchemyInstrumentor` only patches future `create_engine()` calls and won't capture queries on pre-existing engines.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from db.engine import engine
|
|
252
|
+
|
|
253
|
+
worker = create_openbox_worker(
|
|
254
|
+
...,
|
|
255
|
+
db_libraries={"psycopg2", "sqlalchemy"},
|
|
256
|
+
sqlalchemy_engine=engine,
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### File I/O
|
|
261
|
+
- `open()`, `read()`, `write()`, `readline()`, `readlines()`
|
|
262
|
+
- Skips system paths (`/dev/`, `/proc/`, `/sys/`, `__pycache__`)
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Hook-Level Governance
|
|
267
|
+
|
|
268
|
+
Every HTTP request, file operation, and database query made during an activity is evaluated by OpenBox Core in real-time at two stages:
|
|
269
|
+
|
|
270
|
+
### HTTP Requests
|
|
271
|
+
|
|
272
|
+
| Stage | Trigger | Data Available |
|
|
273
|
+
|-------|---------|----------------|
|
|
274
|
+
| `started` | Before request is sent | Method, URL, request headers, request body |
|
|
275
|
+
| `completed` | After response received | All of above + response headers, response body, status code |
|
|
276
|
+
|
|
277
|
+
### File Operations
|
|
278
|
+
|
|
279
|
+
Per-operation governance evaluates **every** `read()`/`write()`/`readline()`/`readlines()`/`writelines()` call, not just open/close:
|
|
280
|
+
|
|
281
|
+
| Operation | Stage | Trigger | Data Available |
|
|
282
|
+
|-----------|-------|---------|----------------|
|
|
283
|
+
| `open` | `started` | Before file is opened | File path, open mode |
|
|
284
|
+
| `read` | `started` | Before read executes | File path, mode |
|
|
285
|
+
| `read` | `completed` | After read returns | data (content read), bytes_read |
|
|
286
|
+
| `readline` | `started` | Before readline executes | File path, mode |
|
|
287
|
+
| `readline` | `completed` | After readline returns | data (line read), bytes_read |
|
|
288
|
+
| `readlines` | `started` | Before readlines executes | File path, mode |
|
|
289
|
+
| `readlines` | `completed` | After readlines returns | data (lines read), bytes_read, lines_count |
|
|
290
|
+
| `write` | `started` | Before write executes | File path, mode |
|
|
291
|
+
| `write` | `completed` | After write returns | data (content written), bytes_written |
|
|
292
|
+
| `writelines` | `started` | Before writelines executes | File path, mode |
|
|
293
|
+
| `writelines` | `completed` | After writelines returns | data (lines written), bytes_written, lines_count |
|
|
294
|
+
| `close` | `completed` | After file is closed | bytes_read, bytes_written, operations list |
|
|
295
|
+
|
|
296
|
+
**How it works (HTTP):**
|
|
297
|
+
|
|
298
|
+
1. OTel httpx instrumentation fires a **request hook** → SDK sends `started` governance evaluation with request data
|
|
299
|
+
2. If verdict is BLOCK/HALT → request is aborted before it leaves the process
|
|
300
|
+
3. After response arrives → SDK sends `completed` governance evaluation with full request+response data
|
|
301
|
+
4. If verdict is BLOCK/HALT → `GovernanceBlockedError` is raised, activity fails with `GovernanceStop`
|
|
302
|
+
|
|
303
|
+
**How it works (File I/O):**
|
|
304
|
+
|
|
305
|
+
1. Activity calls `open()` → SDK sends `started` governance evaluation with file path and mode
|
|
306
|
+
2. If verdict is BLOCK/HALT → file is never opened, `GovernanceBlockedError` is raised
|
|
307
|
+
3. Each `read()`/`write()`/`readline()`/`readlines()`/`writelines()` call sends `started` (before) and `completed` (after) governance evaluations — enabling content-based policy enforcement
|
|
308
|
+
4. After file is closed → SDK sends `completed` governance with lifecycle summary (total bytes, operations list)
|
|
309
|
+
5. File governance requires `instrument_file_io=True` (disabled by default)
|
|
310
|
+
|
|
311
|
+
A simple open-read-close produces **4 governance evaluations**: open(started) → read(started) → read(completed) → close(completed).
|
|
312
|
+
|
|
313
|
+
### Database Queries
|
|
314
|
+
|
|
315
|
+
Every database operation is evaluated at `started` (pre-query, can block) and `completed` (post-query, reports outcome):
|
|
316
|
+
|
|
317
|
+
| Field | Started | Completed |
|
|
318
|
+
|-------|---------|-----------|
|
|
319
|
+
| `type` | `"db_query"` | `"db_query"` |
|
|
320
|
+
| `stage` | `"started"` | `"completed"` |
|
|
321
|
+
| `db_system` | postgresql, mysql, mongodb, redis, sqlite | same |
|
|
322
|
+
| `db_name` | Database name | same |
|
|
323
|
+
| `db_operation` | SQL verb or command (SELECT, INSERT, GET, etc.) | same |
|
|
324
|
+
| `db_statement` | Query string or command | same |
|
|
325
|
+
| `server_address` | Host | same |
|
|
326
|
+
| `server_port` | Port | same |
|
|
327
|
+
| `duration_ms` | — | Query duration in ms |
|
|
328
|
+
| `error` | — | Error message or None |
|
|
329
|
+
|
|
330
|
+
**How it works:**
|
|
331
|
+
|
|
332
|
+
1. Activity executes a DB query (via any supported library)
|
|
333
|
+
2. SDK governance hook intercepts **before** the query → sends `started` evaluation
|
|
334
|
+
3. If verdict is BLOCK/HALT → query is aborted, `GovernanceBlockedError` raised
|
|
335
|
+
4. Query executes normally → SDK sends `completed` evaluation with duration and error status
|
|
336
|
+
5. DB governance is automatic when `instrument_databases=True` (default)
|
|
337
|
+
|
|
338
|
+
**Per-library strategy:**
|
|
339
|
+
|
|
340
|
+
| Library | Hook Method | Can Block? | Reliability |
|
|
341
|
+
|---------|------------|------------|-------------|
|
|
342
|
+
| psycopg2, pymysql, mysql-connector-python | `CursorTracer.traced_execution` patch | Yes | Fully supported |
|
|
343
|
+
| asyncpg | `wrapt` wrapper on Connection methods | Yes | Best-effort |
|
|
344
|
+
| pymongo | CommandListener + `wrapt` Collection wrappers | Yes (wrapt only) | Best-effort |
|
|
345
|
+
| redis | Native OTel `request_hook`/`response_hook` | Yes | Best-effort |
|
|
346
|
+
| sqlalchemy | `before/after_cursor_execute` events | Yes | Best-effort |
|
|
347
|
+
|
|
348
|
+
> **Note:** Libraries marked "Fully supported" use OTel's `CursorTracer`, which guarantees governance hooks run inside the OTel span context. Best-effort libraries use custom hooks that may produce inconsistencies (e.g., missing stages for internal commands). Some C extension types (e.g., `psycopg2.extensions.cursor`) cannot be patched with `wrapt` — in those cases governance hooks are silently skipped, but OTel span capture still works normally.
|
|
349
|
+
|
|
350
|
+
### Function Tracing
|
|
351
|
+
|
|
352
|
+
Functions decorated with `@traced` are automatically governed when `hook_governance` is configured:
|
|
353
|
+
|
|
354
|
+
| Stage | Trigger | Data Available |
|
|
355
|
+
|-------|---------|----------------|
|
|
356
|
+
| `started` | Before function executes | Function name, module, arguments (if `capture_args=True`) |
|
|
357
|
+
| `completed` | After function returns/raises | All of above + result (if `capture_result=True`) or error info |
|
|
358
|
+
|
|
359
|
+
**How it works (Function Tracing):**
|
|
360
|
+
|
|
361
|
+
1. `@traced` decorator wrapper starts OTel span
|
|
362
|
+
2. → SDK sends `started` governance evaluation with function name and module
|
|
363
|
+
3. If verdict is BLOCK/HALT → `GovernanceBlockedError` raised, function never executes
|
|
364
|
+
4. Function executes normally
|
|
365
|
+
5. → SDK sends `completed` governance evaluation with result or error info
|
|
366
|
+
6. If verdict is BLOCK/HALT → `GovernanceBlockedError` raised after execution
|
|
367
|
+
7. When `hook_governance` is not configured → zero overhead, no governance calls
|
|
368
|
+
|
|
369
|
+
**Governed span tracking:** When hook-level governance is active, the SDK marks HTTP spans as "governed" so the OTel `on_end` processor skips buffering them — preventing duplicate spans.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Architecture
|
|
374
|
+
|
|
375
|
+
See [System Architecture](./docs/system-architecture.md) for detailed component design.
|
|
376
|
+
|
|
377
|
+
**High-Level Flow:**
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
Workflow/Activity → Interceptors → Span Processor → OpenBox Core API
|
|
381
|
+
↓
|
|
382
|
+
Returns Verdict
|
|
383
|
+
↓
|
|
384
|
+
(ALLOW, BLOCK, HALT, etc.)
|
|
385
|
+
|
|
386
|
+
Hook-Level (per HTTP request):
|
|
387
|
+
Activity HTTP Call → OTel Hook → hook_governance → API (started) → Allow/Block
|
|
388
|
+
→ Response → hook_governance → API (completed) → Allow/Block
|
|
389
|
+
|
|
390
|
+
Hook-Level (per file operation):
|
|
391
|
+
Activity open() → hook_governance → API (started) → Allow/Block
|
|
392
|
+
Activity read/write() → hook_governance → API (started) → Allow/Block
|
|
393
|
+
→ hook_governance → API (completed) → Allow/Block
|
|
394
|
+
Activity close() → hook_governance → API (completed) → Allow/Block
|
|
395
|
+
|
|
396
|
+
Hook-Level (per DB query):
|
|
397
|
+
Activity DB Call → db_governance_hooks → hook_governance → API (started) → Allow/Block
|
|
398
|
+
→ Query executes → hook_governance → API (completed)
|
|
399
|
+
|
|
400
|
+
Hook-Level (per @traced function):
|
|
401
|
+
@traced call → hook_governance → API (started) → Allow/Block
|
|
402
|
+
→ Function runs → hook_governance → API (completed)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Module responsibilities:**
|
|
406
|
+
- `otel_setup.py` — OTel instrumentation, hooks, TracedFile wrapper
|
|
407
|
+
- `hook_governance.py` — Shared governance evaluator (payload building, API calls, verdict handling)
|
|
408
|
+
- `db_governance_hooks.py` — Per-library DB governance wrappers (wrapt, OTel hooks, SQLAlchemy events)
|
|
409
|
+
- `span_processor.py` — Span buffering, activity context tracking
|
|
410
|
+
- `activity_interceptor.py` — Activity lifecycle governance events
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Advanced Usage
|
|
415
|
+
|
|
416
|
+
For manual control, import individual components:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from openbox import (
|
|
420
|
+
initialize,
|
|
421
|
+
WorkflowSpanProcessor,
|
|
422
|
+
GovernanceInterceptor,
|
|
423
|
+
GovernanceConfig,
|
|
424
|
+
)
|
|
425
|
+
from openbox.otel_setup import setup_opentelemetry_for_governance
|
|
426
|
+
from openbox.activity_interceptor import ActivityGovernanceInterceptor
|
|
427
|
+
from openbox.activities import send_governance_event
|
|
428
|
+
from openbox.hook_governance import FAIL_OPEN, FAIL_CLOSED # error policy constants
|
|
429
|
+
|
|
430
|
+
# 1. Initialize SDK
|
|
431
|
+
initialize(api_url="http://localhost:8086", api_key="obx_test_key_1")
|
|
432
|
+
|
|
433
|
+
# 2. Create span processor
|
|
434
|
+
span_processor = WorkflowSpanProcessor(
|
|
435
|
+
ignored_url_prefixes=["http://localhost:8086"]
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# 3. Setup OTel instrumentation (governance always enabled)
|
|
439
|
+
setup_opentelemetry_for_governance(
|
|
440
|
+
span_processor,
|
|
441
|
+
api_url="http://localhost:8086",
|
|
442
|
+
api_key="obx_test_key_1",
|
|
443
|
+
sqlalchemy_engine=engine, # optional: instrument pre-existing engine
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# 4. Create governance config
|
|
447
|
+
config = GovernanceConfig(
|
|
448
|
+
on_api_error="fail_closed",
|
|
449
|
+
api_timeout=30.0,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# 5. Create interceptors
|
|
453
|
+
workflow_interceptor = GovernanceInterceptor(
|
|
454
|
+
api_url="http://localhost:8086",
|
|
455
|
+
api_key="obx_test_key_1",
|
|
456
|
+
span_processor=span_processor,
|
|
457
|
+
config=config,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
activity_interceptor = ActivityGovernanceInterceptor(
|
|
461
|
+
api_url="http://localhost:8086",
|
|
462
|
+
api_key="obx_test_key_1",
|
|
463
|
+
span_processor=span_processor,
|
|
464
|
+
config=config,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# 6. Create worker
|
|
468
|
+
from temporalio.worker import Worker
|
|
469
|
+
worker = Worker(
|
|
470
|
+
client=client,
|
|
471
|
+
task_queue="my-task-queue",
|
|
472
|
+
workflows=[MyWorkflow],
|
|
473
|
+
activities=[my_activity, send_governance_event],
|
|
474
|
+
interceptors=[workflow_interceptor, activity_interceptor],
|
|
475
|
+
)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Documentation
|
|
481
|
+
|
|
482
|
+
- **[Project Overview & PDR](./docs/project-overview-pdr.md)** - Requirements, features, constraints
|
|
483
|
+
- **[System Architecture](./docs/system-architecture.md)** - Component design, data flows, security
|
|
484
|
+
- **[Codebase Summary](./docs/codebase-summary.md)** - Code structure and component details
|
|
485
|
+
- **[Code Standards](./docs/code-standards.md)** - Coding conventions and best practices
|
|
486
|
+
- **[Project Roadmap](./docs/project-roadmap.md)** - Future enhancements and timeline
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Testing
|
|
491
|
+
|
|
492
|
+
The SDK includes comprehensive test coverage with 13 test files:
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
pytest tests/
|
|
496
|
+
```
|
|
497
|
+
|
|
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`
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## License
|
|
503
|
+
|
|
504
|
+
MIT License - See LICENSE file for details
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Support
|
|
509
|
+
|
|
510
|
+
- **Issues:** GitHub Issues
|
|
511
|
+
- **Documentation:** See `./docs/`
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
**Version:** 1.1.0 | **Last Updated:** 2026-03-09
|