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.
Files changed (59) hide show
  1. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.github/workflows/sonarqube.yaml +1 -1
  2. openbox_temporal_sdk_python-1.1.0/CHANGELOG.md +68 -0
  3. openbox_temporal_sdk_python-1.1.0/PKG-INFO +515 -0
  4. openbox_temporal_sdk_python-1.1.0/README.md +465 -0
  5. openbox_temporal_sdk_python-1.1.0/docs/changelog-hook-level-governance.md +146 -0
  6. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/code-standards.md +43 -19
  7. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/codebase-summary.md +306 -42
  8. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/configuration.md +11 -2
  9. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/project-overview-pdr.md +99 -19
  10. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/docs/system-architecture.md +425 -67
  11. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/__init__.py +61 -2
  12. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/activities.py +62 -28
  13. openbox_temporal_sdk_python-1.1.0/openbox/activity_interceptor.py +626 -0
  14. openbox_temporal_sdk_python-1.1.0/openbox/client.py +181 -0
  15. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/config.py +18 -29
  16. openbox_temporal_sdk_python-1.1.0/openbox/context_propagation.py +53 -0
  17. openbox_temporal_sdk_python-1.1.0/openbox/db_governance_hooks.py +874 -0
  18. openbox_temporal_sdk_python-1.1.0/openbox/errors.py +164 -0
  19. openbox_temporal_sdk_python-1.1.0/openbox/file_governance_hooks.py +354 -0
  20. openbox_temporal_sdk_python-1.1.0/openbox/hitl.py +120 -0
  21. openbox_temporal_sdk_python-1.1.0/openbox/hook_governance.py +376 -0
  22. openbox_temporal_sdk_python-1.1.0/openbox/http_governance_hooks.py +749 -0
  23. openbox_temporal_sdk_python-1.1.0/openbox/otel_setup.py +487 -0
  24. openbox_temporal_sdk_python-1.1.0/openbox/span_processor.py +212 -0
  25. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/tracing.py +92 -0
  26. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/types.py +13 -3
  27. openbox_temporal_sdk_python-1.1.0/openbox/verdict_handler.py +93 -0
  28. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/worker.py +21 -1
  29. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/workflow_interceptor.py +19 -8
  30. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/pyproject.toml +16 -15
  31. openbox_temporal_sdk_python-1.1.0/tests/conftest.py +91 -0
  32. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_activities.py +92 -50
  33. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_activity_interceptor.py +294 -18
  34. openbox_temporal_sdk_python-1.1.0/tests/test_db_governance_hooks.py +808 -0
  35. openbox_temporal_sdk_python-1.1.0/tests/test_file_governance_hooks.py +716 -0
  36. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_otel_setup.py +570 -286
  37. openbox_temporal_sdk_python-1.1.0/tests/test_psycopg2_hooks_verify.py +53 -0
  38. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_span_processor.py +17 -469
  39. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_tracing.py +384 -1
  40. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_worker.py +10 -6
  41. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_workflow_interceptor.py +7 -7
  42. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/uv.lock +98 -110
  43. openbox_temporal_sdk_python-1.0.2/PKG-INFO +0 -374
  44. openbox_temporal_sdk_python-1.0.2/README.md +0 -325
  45. openbox_temporal_sdk_python-1.0.2/openbox/activity_interceptor.py +0 -761
  46. openbox_temporal_sdk_python-1.0.2/openbox/otel_setup.py +0 -1007
  47. openbox_temporal_sdk_python-1.0.2/openbox/span_processor.py +0 -361
  48. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.github/workflows/publish.yml +0 -0
  49. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.gitignore +0 -0
  50. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.python-version +0 -0
  51. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/.repomixignore +0 -0
  52. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/LICENSE +0 -0
  53. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/openbox/py.typed +0 -0
  54. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/release-manifest.json +0 -0
  55. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/repomix-output.xml +0 -0
  56. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/sonar-project.properties +0 -0
  57. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/__init__.py +0 -0
  58. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_config.py +0 -0
  59. {openbox_temporal_sdk_python-1.0.2 → openbox_temporal_sdk_python-1.1.0}/tests/test_types.py +0 -0
@@ -54,7 +54,7 @@ jobs:
54
54
  fi
55
55
 
56
56
  - name: SonarQube Scan
57
- uses: SonarSource/sonarqube-scan-action@v5.3.0
57
+ uses: SonarSource/sonarqube-scan-action@v6.0.0
58
58
  env:
59
59
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
60
60
  SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
@@ -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