openbox-temporal-sdk-python 1.0.0__tar.gz → 1.0.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 (43) hide show
  1. openbox_temporal_sdk_python-1.0.2/.github/workflows/publish.yml +46 -0
  2. openbox_temporal_sdk_python-1.0.2/.github/workflows/sonarqube.yaml +182 -0
  3. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/.gitignore +7 -0
  4. openbox_temporal_sdk_python-1.0.2/.repomixignore +22 -0
  5. openbox_temporal_sdk_python-1.0.2/PKG-INFO +374 -0
  6. openbox_temporal_sdk_python-1.0.2/README.md +325 -0
  7. openbox_temporal_sdk_python-1.0.2/docs/code-standards.md +742 -0
  8. openbox_temporal_sdk_python-1.0.2/docs/codebase-summary.md +503 -0
  9. openbox_temporal_sdk_python-1.0.2/docs/configuration.md +63 -0
  10. openbox_temporal_sdk_python-1.0.2/docs/project-overview-pdr.md +357 -0
  11. openbox_temporal_sdk_python-1.0.2/docs/system-architecture.md +760 -0
  12. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/__init__.py +2 -0
  13. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/activities.py +1 -0
  14. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/activity_interceptor.py +8 -2
  15. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/config.py +51 -3
  16. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/otel_setup.py +42 -4
  17. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/worker.py +72 -71
  18. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/pyproject.toml +2 -1
  19. openbox_temporal_sdk_python-1.0.2/release-manifest.json +10908 -0
  20. openbox_temporal_sdk_python-1.0.2/repomix-output.xml +15911 -0
  21. openbox_temporal_sdk_python-1.0.2/sonar-project.properties +13 -0
  22. openbox_temporal_sdk_python-1.0.2/tests/__init__.py +2 -0
  23. openbox_temporal_sdk_python-1.0.2/tests/test_activities.py +758 -0
  24. openbox_temporal_sdk_python-1.0.2/tests/test_activity_interceptor.py +2081 -0
  25. openbox_temporal_sdk_python-1.0.2/tests/test_config.py +979 -0
  26. openbox_temporal_sdk_python-1.0.2/tests/test_otel_setup.py +1774 -0
  27. openbox_temporal_sdk_python-1.0.2/tests/test_span_processor.py +1145 -0
  28. openbox_temporal_sdk_python-1.0.2/tests/test_tracing.py +868 -0
  29. openbox_temporal_sdk_python-1.0.2/tests/test_types.py +802 -0
  30. openbox_temporal_sdk_python-1.0.2/tests/test_worker.py +1884 -0
  31. openbox_temporal_sdk_python-1.0.2/tests/test_workflow_interceptor.py +1350 -0
  32. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/uv.lock +268 -61
  33. openbox_temporal_sdk_python-1.0.0/.claude/settings.local.json +0 -8
  34. openbox_temporal_sdk_python-1.0.0/PKG-INFO +0 -1214
  35. openbox_temporal_sdk_python-1.0.0/PLAN.md +0 -234
  36. openbox_temporal_sdk_python-1.0.0/README.md +0 -1165
  37. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/.python-version +0 -0
  38. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/LICENSE +0 -0
  39. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/py.typed +0 -0
  40. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/span_processor.py +0 -0
  41. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/tracing.py +0 -0
  42. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/types.py +0 -0
  43. {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/workflow_interceptor.py +0 -0
@@ -0,0 +1,46 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish:
9
+ name: Build and Publish
10
+ runs-on: ubuntu-latest
11
+ environment: release
12
+ permissions:
13
+ id-token: write # Required for trusted publishing
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.11"
23
+
24
+ - name: Install build dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install hatch twine
28
+
29
+ - name: Verify version matches tag
30
+ run: |
31
+ TAG_VERSION=${GITHUB_REF#refs/tags/v}
32
+ PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
33
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
34
+ echo "Error: Tag version ($TAG_VERSION) does not match package version ($PKG_VERSION)"
35
+ exit 1
36
+ fi
37
+ echo "Version verified: $PKG_VERSION"
38
+
39
+ - name: Build package
40
+ run: hatch build
41
+
42
+ - name: Publish to PyPI
43
+ env:
44
+ TWINE_USERNAME: __token__
45
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
46
+ run: twine upload dist/*
@@ -0,0 +1,182 @@
1
+ # .github/workflows/sonarqube.yml
2
+ name: SonarQube
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pull-requests: write
13
+ issues: write
14
+
15
+ jobs:
16
+ sonarqube:
17
+ runs-on: ubuntu-latest
18
+ env:
19
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
25
+
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: '3.11'
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v5
33
+
34
+ - name: Install dependencies
35
+ run: uv sync --all-extras --dev
36
+
37
+ - name: Run tests with coverage
38
+ run: |
39
+ uv run pytest tests/ \
40
+ --cov=openbox \
41
+ --cov-report=xml:coverage.xml \
42
+ --cov-report=term-missing \
43
+ -v
44
+
45
+ - name: Compute Sonar project name
46
+ shell: bash
47
+ run: |
48
+ REPO_NAME="${{ github.event.repository.name }}"
49
+
50
+ if [ "${GITHUB_REF_NAME}" = "staging" ]; then
51
+ echo "SONAR_PROJECT_NAME=${REPO_NAME}-staging" >> $GITHUB_ENV
52
+ else
53
+ echo "SONAR_PROJECT_NAME=${REPO_NAME}" >> $GITHUB_ENV
54
+ fi
55
+
56
+ - name: SonarQube Scan
57
+ uses: SonarSource/sonarqube-scan-action@v5.3.0
58
+ env:
59
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
60
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
61
+ with:
62
+ args: >
63
+ -Dsonar.projectKey=${{ env.SONAR_PROJECT_NAME }}
64
+ -Dsonar.projectName="${{ env.SONAR_PROJECT_NAME }}"
65
+
66
+ - name: Quality Gate
67
+ uses: SonarSource/sonarqube-quality-gate-action@v1.2.0
68
+ continue-on-error: true
69
+ env:
70
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
71
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
72
+ with:
73
+ pollingTimeoutSec: 600
74
+
75
+
76
+ - name: Wait for quality gate
77
+ if: always()
78
+ run: sleep 30
79
+
80
+ - name: Build SonarQube Quality Gate report
81
+ if: always()
82
+ id: qg
83
+ env:
84
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
85
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
86
+ SONAR_PROJECT_NAME: ${{ env.SONAR_PROJECT_NAME }}
87
+ run: |
88
+ set -euo pipefail
89
+
90
+ HOST_TRIM="${SONAR_HOST_URL%/}"
91
+ TOKEN="${SONAR_TOKEN}"
92
+ PKEY="${SONAR_PROJECT_NAME}" # projectKey
93
+ PNAME="${SONAR_PROJECT_NAME}" # projectName hiển thị
94
+
95
+ urlencode() { jq -nr --arg v "$1" '$v|@uri'; }
96
+
97
+ PKEY_ENC="$(urlencode "$PKEY")"
98
+ API_URL="${HOST_TRIM}/api/qualitygates/project_status?projectKey=${PKEY_ENC}"
99
+
100
+ JSON="$(curl -fsSL -u "${TOKEN}:" "$API_URL")"
101
+
102
+ STATUS="$(jq -r '.projectStatus.status' <<<"$JSON")" # OK | WARN | ERROR
103
+ QG_NAME="$(jq -r '.projectStatus.qualityGate.name // "Default"' <<<"$JSON")"
104
+ if [ -z "${SONAR_HOST_URL:-}" ]; then
105
+ echo "::warning::SONAR_HOST_URL is empty"
106
+ fi
107
+ DASH_URL="${SONAR_HOST_URL}/dashboard?id=$(urlencode "$PKEY")"
108
+
109
+ {
110
+ echo "### SonarQube Quality Gate: ${QG_NAME}"
111
+ echo
112
+ echo "- **Project**: ${PNAME}"
113
+ echo "- **Status**: **${STATUS}**"
114
+ echo "- **Detail**: [Open in SonarQube](${DASH_URL})"
115
+ } > qg.md
116
+
117
+ cat qg.md >> "$GITHUB_STEP_SUMMARY"
118
+ echo "STATUS=${STATUS}" >> "$GITHUB_ENV"
119
+ echo "QG_NAME=${QG_NAME}" >> "$GITHUB_ENV"
120
+ echo "PNAME=${PNAME}" >> "$GITHUB_ENV"
121
+
122
+ # (2) Upsert Issue cho
123
+ - name: Comment (or create) Issue with QG report
124
+ if: always() && env.STATUS != 'OK'
125
+ uses: actions/github-script@v7
126
+ env:
127
+ ISSUE_NUMBER: ${{ vars.SONAR_ISSUE_NUMBER }}
128
+ SONAR_PROJECT_NAME: ${{ env.SONAR_PROJECT_NAME }}
129
+ QG_NAME: ${{ env.QG_NAME }}
130
+ STATUS: ${{ env.STATUS }}
131
+ with:
132
+ script: |
133
+ const fs = require('fs');
134
+ const owner = context.repo.owner;
135
+ const repo = context.repo.repo;
136
+
137
+ const body = fs.readFileSync('qg.md', 'utf8');
138
+ const projectName = process.env.SONAR_PROJECT_NAME;
139
+ const status = process.env.STATUS;
140
+ const qgName = process.env.QG_NAME;
141
+
142
+
143
+ const stableTitle = `SonarQube QG • ${projectName}`;
144
+
145
+ const displayTitle = `${stableTitle} — ${qgName} (${status})`;
146
+
147
+ let issueNumber = process.env.ISSUE_NUMBER ? Number(process.env.ISSUE_NUMBER) : null;
148
+
149
+ if (!issueNumber) {
150
+ const { data: search } = await github.rest.search.issuesAndPullRequests({
151
+ q: `repo:${owner}/${repo} is:issue state:open in:title "${stableTitle}" label:sonarqube`
152
+ });
153
+
154
+ if (search.total_count > 0) {
155
+ issueNumber = search.items[0].number;
156
+ core.info(`Found existing issue #${issueNumber} for project "${projectName}".`);
157
+ }
158
+ }
159
+
160
+ if (issueNumber) {
161
+ try {
162
+ await github.rest.issues.update({ owner, repo, issue_number: issueNumber, title: stableTitle });
163
+ } catch (e) {
164
+ core.warning(`Failed to update title to stableTitle: ${e.message}`);
165
+ }
166
+ await github.rest.issues.createComment({ owner, repo, issue_number: issueNumber, body });
167
+ core.info(`Appended comment to issue #${issueNumber}`);
168
+ } else {
169
+ const { data: issue } = await github.rest.issues.create({
170
+ owner, repo,
171
+ title: stableTitle,
172
+ body,
173
+ labels: ['sonarqube','quality-gate']
174
+ });
175
+ core.info(`Created issue #${issue.number} for project "${projectName}"`);
176
+ }
177
+
178
+ - name: Fail job when Quality Gate not OK
179
+ if: env.STATUS != 'OK'
180
+ run: |
181
+ echo "::error title=Quality Gate Failed::STATUS=${{ env.STATUS }} (expected OK)"
182
+ exit 1
@@ -33,3 +33,10 @@ htmlcov/
33
33
  # OS
34
34
  .DS_Store
35
35
  Thumbs.db
36
+
37
+ .opencode/
38
+ AGENTS.md
39
+
40
+ .claude/
41
+ CLAUDE.md
42
+ plans/
@@ -0,0 +1,22 @@
1
+ docs/*
2
+ plans/*
3
+ assets/*
4
+ dist/*
5
+ coverage/*
6
+ build/*
7
+ ios/*
8
+ android/*
9
+ tests/*
10
+ __tests__/*
11
+ __pycache__/*
12
+ node_modules/*
13
+
14
+ .opencode/*
15
+ .claude/*
16
+ .serena/*
17
+ .pnpm-store/*
18
+ .github/*
19
+ .dart_tool/*
20
+ .idea/*
21
+ .husky/*
22
+ .venv/*
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: openbox-temporal-sdk-python
3
+ Version: 1.0.2
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.38.0
30
+ Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.59b0
31
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.59b0
32
+ Requires-Dist: opentelemetry-instrumentation-mysql>=0.59b0
33
+ Requires-Dist: opentelemetry-instrumentation-psycopg2>=0.59b0
34
+ Requires-Dist: opentelemetry-instrumentation-pymongo>=0.59b0
35
+ Requires-Dist: opentelemetry-instrumentation-pymysql>=0.59b0
36
+ Requires-Dist: opentelemetry-instrumentation-redis>=0.59b0
37
+ Requires-Dist: opentelemetry-instrumentation-requests>=0.59b0
38
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.59b0
39
+ Requires-Dist: opentelemetry-instrumentation-urllib3>=0.59b0
40
+ Requires-Dist: opentelemetry-instrumentation-urllib>=0.59b0
41
+ Requires-Dist: opentelemetry-sdk>=1.38.0
42
+ Requires-Dist: psycopg2-binary>=2.9.10
43
+ Requires-Dist: pymongo>=4.0.0
44
+ Requires-Dist: pymysql>=1.0.0
45
+ Requires-Dist: redis>=5.0.0
46
+ Requires-Dist: sqlalchemy>=2.0.0
47
+ Requires-Dist: temporalio<2,>=1.8.0
48
+ Description-Content-Type: text/markdown
49
+
50
+ # OpenBox SDK for Temporal Workflows
51
+
52
+ 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.
53
+
54
+ **Key Features:**
55
+ - 6 event types (WorkflowStarted, WorkflowCompleted, WorkflowFailed, SignalReceived, ActivityStarted, ActivityCompleted)
56
+ - 5-tier verdict system (ALLOW, CONSTRAIN, REQUIRE_APPROVAL, BLOCK, HALT)
57
+ - HTTP/Database/File I/O instrumentation via OpenTelemetry
58
+ - Guardrails: Input/output validation and redaction
59
+ - Human-in-the-loop approval with expiration handling
60
+ - Zero-code setup via `create_openbox_worker()` factory
61
+
62
+ ---
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install openbox-temporal-sdk-python
68
+ ```
69
+
70
+ **Requirements:**
71
+ - Python 3.9+
72
+ - Temporal SDK 1.8+
73
+ - OpenTelemetry API/SDK 1.38.0+
74
+
75
+ ---
76
+
77
+ ## Quick Start
78
+
79
+ Use the `create_openbox_worker()` factory for simple integration:
80
+
81
+ ```python
82
+ import os
83
+ from openbox import create_openbox_worker
84
+
85
+ worker = create_openbox_worker(
86
+ client=client,
87
+ task_queue="my-task-queue",
88
+ workflows=[MyWorkflow],
89
+ activities=[my_activity],
90
+ # OpenBox config
91
+ openbox_url=os.getenv("OPENBOX_URL"),
92
+ openbox_api_key=os.getenv("OPENBOX_API_KEY"),
93
+ )
94
+
95
+ await worker.run()
96
+ ```
97
+
98
+ The factory automatically:
99
+ 1. Validates the API key
100
+ 2. Creates span processor
101
+ 3. Sets up OpenTelemetry instrumentation
102
+ 4. Creates governance interceptors
103
+ 5. Adds `send_governance_event` activity
104
+ 6. Returns fully configured Worker
105
+
106
+ ---
107
+
108
+ ## Configuration
109
+
110
+ ### Environment Variables
111
+
112
+ ```bash
113
+ OPENBOX_URL=http://localhost:8086
114
+ OPENBOX_API_KEY=obx_test_key_1
115
+ OPENBOX_GOVERNANCE_TIMEOUT=30.0
116
+ OPENBOX_GOVERNANCE_POLICY=fail_open # or fail_closed
117
+ ```
118
+
119
+ ### Factory Function Parameters
120
+
121
+ ```python
122
+ worker = create_openbox_worker(
123
+ client=client,
124
+ task_queue="my-task-queue",
125
+ workflows=[MyWorkflow],
126
+ activities=[my_activity],
127
+
128
+ # OpenBox config
129
+ openbox_url="http://localhost:8086",
130
+ openbox_api_key="obx_test_key_1",
131
+ governance_timeout=30.0,
132
+ governance_policy="fail_open",
133
+
134
+ # Event filtering
135
+ send_start_event=True,
136
+ send_activity_start_event=True,
137
+ skip_workflow_types={"InternalWorkflow"},
138
+ skip_activity_types={"send_governance_event"},
139
+ skip_signals={"heartbeat"},
140
+
141
+ # Database instrumentation
142
+ instrument_databases=True,
143
+ db_libraries={"psycopg2", "sqlalchemy"}, # None = all available
144
+ sqlalchemy_engine=engine, # pass pre-existing engine for query capture
145
+
146
+ # File I/O instrumentation
147
+ instrument_file_io=False, # disabled by default
148
+
149
+ # Standard Worker options (all supported)
150
+ activity_executor=my_executor,
151
+ max_concurrent_activities=10,
152
+ )
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Governance Verdicts
158
+
159
+ OpenBox Core returns a verdict indicating what action the SDK should take.
160
+
161
+ | Verdict | Behavior |
162
+ |---------|----------|
163
+ | `ALLOW` | Continue execution normally |
164
+ | `CONSTRAIN` | Log constraints, continue |
165
+ | `REQUIRE_APPROVAL` | Pause, poll for human approval |
166
+ | `BLOCK` | Raise error, stop activity |
167
+ | `HALT` | Raise error, terminate workflow |
168
+
169
+ **v1.0 Backward Compatibility:**
170
+ - `"continue"` → `ALLOW`
171
+ - `"stop"` → `HALT`
172
+ - `"require-approval"` → `REQUIRE_APPROVAL`
173
+
174
+ ---
175
+
176
+ ## Event Types
177
+
178
+ | Event | Trigger | Captured Fields |
179
+ |-------|---------|-----------------|
180
+ | WorkflowStarted | Workflow begins | workflow_id, run_id, workflow_type, task_queue |
181
+ | WorkflowCompleted | Workflow succeeds | workflow_id, run_id, workflow_type |
182
+ | WorkflowFailed | Workflow fails | workflow_id, run_id, workflow_type, error |
183
+ | SignalReceived | Signal received | workflow_id, signal_name, signal_args |
184
+ | ActivityStarted | Activity begins | activity_id, activity_type, activity_input |
185
+ | ActivityCompleted | Activity ends | activity_id, activity_type, activity_input, activity_output, spans, status, duration |
186
+
187
+ ---
188
+
189
+ ## Guardrails (Input/Output Redaction)
190
+
191
+ OpenBox Core can validate and redact sensitive data before/after activity execution:
192
+
193
+ ```python
194
+ # Request
195
+ {
196
+ "verdict": "allow",
197
+ "guardrails_result": {
198
+ "input_type": "activity_input",
199
+ "redacted_input": {"prompt": "[REDACTED]", "user_id": "123"},
200
+ "validation_passed": true,
201
+ "reasons": []
202
+ }
203
+ }
204
+
205
+ # If validation fails:
206
+ {
207
+ "validation_passed": false,
208
+ "reasons": [
209
+ {"type": "pii", "field": "email", "reason": "Contains PII"}
210
+ ]
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Error Handling
217
+
218
+ Configure error policy via `on_api_error`:
219
+
220
+ | Policy | Behavior |
221
+ |--------|----------|
222
+ | `fail_open` (default) | If governance API fails, allow workflow to continue |
223
+ | `fail_closed` | If governance API fails, terminate workflow |
224
+
225
+ ---
226
+
227
+ ## Supported Instrumentation
228
+
229
+ ### HTTP Libraries
230
+ - `httpx` (sync + async) - full body capture
231
+ - `requests` - full body capture
232
+ - `urllib3` - full body capture
233
+ - `urllib` - request body only
234
+
235
+ ### Databases
236
+ - PostgreSQL: `psycopg2`, `asyncpg`
237
+ - MySQL: `mysql-connector-python`, `pymysql`
238
+ - MongoDB: `pymongo`
239
+ - Redis: `redis`
240
+ - ORM: `sqlalchemy`
241
+
242
+ **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.
243
+
244
+ ```python
245
+ from db.engine import engine
246
+
247
+ worker = create_openbox_worker(
248
+ ...,
249
+ db_libraries={"psycopg2", "sqlalchemy"},
250
+ sqlalchemy_engine=engine,
251
+ )
252
+ ```
253
+
254
+ ### File I/O
255
+ - `open()`, `read()`, `write()`, `readline()`, `readlines()`
256
+ - Skips system paths (`/dev/`, `/proc/`, `/sys/`, `__pycache__`)
257
+
258
+ ---
259
+
260
+ ## Architecture
261
+
262
+ See [System Architecture](./docs/system-architecture.md) for detailed component design.
263
+
264
+ **High-Level Flow:**
265
+
266
+ ```
267
+ Workflow/Activity → Interceptors → Span Processor → OpenBox Core API
268
+
269
+ Returns Verdict
270
+
271
+ (ALLOW, BLOCK, HALT, etc.)
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Advanced Usage
277
+
278
+ For manual control, import individual components:
279
+
280
+ ```python
281
+ from openbox import (
282
+ initialize,
283
+ WorkflowSpanProcessor,
284
+ GovernanceInterceptor,
285
+ GovernanceConfig,
286
+ )
287
+ from openbox.otel_setup import setup_opentelemetry_for_governance
288
+ from openbox.activity_interceptor import ActivityGovernanceInterceptor
289
+ from openbox.activities import send_governance_event
290
+
291
+ # 1. Initialize SDK
292
+ initialize(api_url="http://localhost:8086", api_key="obx_test_key_1")
293
+
294
+ # 2. Create span processor
295
+ span_processor = WorkflowSpanProcessor(
296
+ ignored_url_prefixes=["http://localhost:8086"]
297
+ )
298
+
299
+ # 3. Setup OTel instrumentation
300
+ setup_opentelemetry_for_governance(
301
+ span_processor,
302
+ sqlalchemy_engine=engine, # optional: instrument pre-existing engine
303
+ )
304
+
305
+ # 4. Create governance config
306
+ config = GovernanceConfig(
307
+ on_api_error="fail_closed",
308
+ api_timeout=30.0,
309
+ )
310
+
311
+ # 5. Create interceptors
312
+ workflow_interceptor = GovernanceInterceptor(
313
+ api_url="http://localhost:8086",
314
+ api_key="obx_test_key_1",
315
+ span_processor=span_processor,
316
+ config=config,
317
+ )
318
+
319
+ activity_interceptor = ActivityGovernanceInterceptor(
320
+ api_url="http://localhost:8086",
321
+ api_key="obx_test_key_1",
322
+ span_processor=span_processor,
323
+ config=config,
324
+ )
325
+
326
+ # 6. Create worker
327
+ from temporalio.worker import Worker
328
+ worker = Worker(
329
+ client=client,
330
+ task_queue="my-task-queue",
331
+ workflows=[MyWorkflow],
332
+ activities=[my_activity, send_governance_event],
333
+ interceptors=[workflow_interceptor, activity_interceptor],
334
+ )
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Documentation
340
+
341
+ - **[Project Overview & PDR](./docs/project-overview-pdr.md)** - Requirements, features, constraints
342
+ - **[System Architecture](./docs/system-architecture.md)** - Component design, data flows, security
343
+ - **[Codebase Summary](./docs/codebase-summary.md)** - Code structure and component details
344
+ - **[Code Standards](./docs/code-standards.md)** - Coding conventions and best practices
345
+ - **[Project Roadmap](./docs/project-roadmap.md)** - Future enhancements and timeline
346
+
347
+ ---
348
+
349
+ ## Testing
350
+
351
+ The SDK includes comprehensive test coverage with 10 test files:
352
+
353
+ ```bash
354
+ pytest tests/
355
+ ```
356
+
357
+ Test files: `test_activities.py`, `test_activity_interceptor.py`, `test_config.py`, `test_otel_setup.py`, `test_span_processor.py`, `test_tracing.py`, `test_types.py`, `test_worker.py`, `test_workflow_interceptor.py`
358
+
359
+ ---
360
+
361
+ ## License
362
+
363
+ MIT License - See LICENSE file for details
364
+
365
+ ---
366
+
367
+ ## Support
368
+
369
+ - **Issues:** GitHub Issues
370
+ - **Documentation:** See `./docs/`
371
+
372
+ ---
373
+
374
+ **Version:** 1.0.2 | **Last Updated:** 2026-02-12