openbox-sdk-python 1.2.0__tar.gz → 1.3.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_sdk_python-1.3.0/CHANGELOG.md +116 -0
- openbox_sdk_python-1.3.0/LICENSE +21 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/PKG-INFO +76 -7
- openbox_sdk_python-1.3.0/README.md +124 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/__init__.py +1 -1
- openbox_sdk_python-1.3.0/openbox_core/bootstrap.py +296 -0
- openbox_sdk_python-1.3.0/openbox_core/client.py +1279 -0
- openbox_sdk_python-1.3.0/openbox_core/config.py +563 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/errors.py +64 -3
- openbox_sdk_python-1.3.0/openbox_core/identity_okta.py +302 -0
- openbox_sdk_python-1.3.0/openbox_core/identity_transitions.py +134 -0
- openbox_sdk_python-1.3.0/openbox_core/identity_types.py +74 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/db.py +20 -3
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/http.py +17 -0
- openbox_sdk_python-1.3.0/openbox_core/jwk_thumbprint.py +78 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/runtime.py +6 -1
- openbox_sdk_python-1.3.0/openbox_core/workload_identity.py +383 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/pyproject.toml +2 -2
- openbox_sdk_python-1.3.0/tests/client/identity_fixtures.py +71 -0
- openbox_sdk_python-1.3.0/tests/client/test_auth_failure_classification.py +158 -0
- openbox_sdk_python-1.3.0/tests/client/test_bootstrap.py +713 -0
- openbox_sdk_python-1.3.0/tests/client/test_keycloak_workload_identity.py +414 -0
- openbox_sdk_python-1.3.0/tests/client/test_transition_preflight.py +239 -0
- openbox_sdk_python-1.3.0/tests/client/test_v2_endpoint_selection.py +194 -0
- openbox_sdk_python-1.3.0/tests/config/test_bootstrap_mode.py +235 -0
- openbox_sdk_python-1.3.0/tests/config/test_identity_verification_method.py +145 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/MANIFEST.sha256 +45 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/approval.json +33 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/auth-validate.json +33 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/evaluate.json +33 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/handoff.json +33 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/keypair.json +54 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/README.md +71 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/alg-none.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/alg-symmetric.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/body-tampered.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/embedded-jku.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/embedded-jwk.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/embedded-x5u.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/empty-jti.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/excessive-lifetime.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/expired.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/future-iat.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/htu-with-host.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/htu-with-query.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/malformed-jwt-truncated.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/malformed-jwt.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/missing-assertion.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/missing-exp.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/normal-assertion-on-transition-proof-route.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/paired-with-different-api-key.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/signature-by-other-key.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/transition-proof-on-validate-route.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/transition-proof-signed-by-other-credential.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/undersized-rsa-key.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/unknown-kid.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/v1-path.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-audience.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-deployment-id.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-http-method.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-iss.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-path.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-signed-agent-id.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-signed-organization-id.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-sub.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-transition-challenge.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-transition-purpose.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/tamper/wrong-typ.json +14 -0
- openbox_sdk_python-1.3.0/tests/signing/identity_v2/transition-proof.json +36 -0
- openbox_sdk_python-1.3.0/tests/signing/test_identity_okta_unit.py +222 -0
- openbox_sdk_python-1.3.0/tests/signing/test_identity_v2_fixtures.py +163 -0
- openbox_sdk_python-1.3.0/tests/signing/test_identity_v2_signing.py +160 -0
- openbox_sdk_python-1.3.0/tests/signing/test_jwk_thumbprint.py +127 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/uv.lock +1 -1
- openbox_sdk_python-1.2.0/CHANGELOG.md +0 -41
- openbox_sdk_python-1.2.0/README.md +0 -55
- openbox_sdk_python-1.2.0/openbox_core/client.py +0 -298
- openbox_sdk_python-1.2.0/openbox_core/config.py +0 -260
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/.github/instructions/openbox-sdk-python.instructions.md +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/.github/workflows/ci.yml +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/.github/workflows/publish.yml +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/.gitignore +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/.python-version +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/adapters/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/adapters/base.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/approvals.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/conformance/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/conformance/fake_core.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/conformance/hook_preflight.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/conformance/instrumentation.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/context.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/contracts/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/contracts/context.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/contracts/events.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/contracts/otel_spans.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/contracts/results.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/gate.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/hooks/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/hooks/events.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/hooks/preflight.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/hooks/wrappers.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/identity.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/file.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/function.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/llm.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/manager.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/instrumentation/shared.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/otel/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/otel/propagation.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/otel/provider.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/otel/setup.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/otel/span_processor.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/otel/trace_context.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/py.typed +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/sdk_version.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/serialization.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/validation/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/validation/diagnostics.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/validation/event_rules.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/validation/registry.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/validation/span_normalization.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/wire/__init__.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/wire/core_span.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/openbox_core/wire/evaluate_payload.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/client/test_approval_poll.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/client/test_fail_modes.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/config/test_resolution_order.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/conformance/test_required_cases.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/context/test_bind_reset.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/context/test_trace_key.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/contracts/test_approval_parsing.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/contracts/test_event_classify.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/contracts/test_patch.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/contracts/test_result_parsing.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/gate/test_diagnostics.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/gate/test_strict_failures.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/conftest.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/instrumented_env.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_db_block.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_db_redis_mongo.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_file_function_block.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_hook_failclosed_and_sync_approval.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_hook_runtime.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_http_preflight_block.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_http_urllib.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/instrumentation/test_manager_and_otel_lifecycle.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/runtime/test_runtime_delegation.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/signing/generate_golden_fixture_from_temporal_signer.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/signing/golden_temporal_signed_request.json +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/signing/test_golden_signing.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/test_import_safety.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/test_sdk_version.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/go_spandata_compat/go.mod +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/go_spandata_compat/main.go +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/span_fixtures.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/test_backend_compat.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/test_core_span_projection.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/test_flat_hook_contract.py +0 -0
- {openbox_sdk_python-1.2.0 → openbox_sdk_python-1.3.0}/tests/wire/test_hex_ids.py +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Okta identity bootstrap** — an `okta_ai_agent` runtime now needs only
|
|
12
|
+
`OPENBOX_API_URL`, `OPENBOX_API_KEY`, and `OPENBOX_OKTA_AGENT_PRIVATE_KEY`. The SDK
|
|
13
|
+
fetches the remaining identity metadata (agent id, organization id, deployment id,
|
|
14
|
+
assertion audience, external Okta agent id, credential `kid`, algorithm) from
|
|
15
|
+
`GET /api/v2/auth/bootstrap` on first use and caches it in memory for the lifetime of
|
|
16
|
+
the client. The signed assertion and its verification are unchanged — only how the SDK
|
|
17
|
+
obtains its non-secret configuration.
|
|
18
|
+
- Before the first governed request the SDK derives its private key's RFC 7638 public-key
|
|
19
|
+
thumbprint and compares it (constant-time) with the one Core reports for the agent's
|
|
20
|
+
selected credential. A mismatch raises `OpenBoxConfigError` with actionable guidance;
|
|
21
|
+
no governed request is sent.
|
|
22
|
+
- `EvaluationClient.refresh_identity_metadata()` / `arefresh_identity_metadata()` for
|
|
23
|
+
long-running agents whose selected credential rotated. The thumbprint is re-verified
|
|
24
|
+
*before* cached metadata is replaced, so a credential rotated to a key this process
|
|
25
|
+
does not hold fails loudly and leaves the client on its previous identity. The client
|
|
26
|
+
never refreshes automatically after an auth failure — a blind retry cannot repair a key
|
|
27
|
+
mismatch and would hide it.
|
|
28
|
+
- `EvaluationClient.identity_metadata()` exposes the validated bootstrap document
|
|
29
|
+
(non-secret).
|
|
30
|
+
- New modules `openbox_core.bootstrap` and `openbox_core.jwk_thumbprint`.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Okta configuration now resolves into exactly one of three modes, never a blend:
|
|
34
|
+
**bootstrap** (private key only), **legacy explicit** (every metadata field set, works
|
|
35
|
+
unchanged), or **invalid mixed** (only some metadata fields set) — which raises
|
|
36
|
+
`OpenBoxConfigError` naming the offending fields rather than merging stale local values
|
|
37
|
+
over what Core would have supplied.
|
|
38
|
+
- `OPENBOX_DEPLOYMENT_ID` is no longer required in bootstrap mode. Core returns its own
|
|
39
|
+
deployment id, which prevents a runtime from signing for one deployment while calling
|
|
40
|
+
another.
|
|
41
|
+
- No failure path downgrades to v1, to an unsigned request, or to a different identity
|
|
42
|
+
method. A Core that predates the endpoint (`404`) produces actionable upgrade guidance.
|
|
43
|
+
|
|
44
|
+
## [1.3.0] - 2026-07-30
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- v2 (Okta AI Agent) identity verification support, alongside existing v1 (OpenBox DID)
|
|
48
|
+
and inferred `legacy_unsigned` support: tagged identity configuration
|
|
49
|
+
(`OpenBoxDidIdentityConfig` / `OktaAiAgentIdentityConfig` in
|
|
50
|
+
`openbox_core.identity_types`), RS256 assertion signing
|
|
51
|
+
(`openbox_core.identity_okta`), and version-aware endpoint selection —
|
|
52
|
+
`okta_ai_agent` identities route to `/api/v2/governance/evaluate`,
|
|
53
|
+
`/api/v2/governance/approval`, `GET /api/v2/auth/validate`, and
|
|
54
|
+
`/api/v2/handoffs`; there is no cross-version retry.
|
|
55
|
+
- New env vars: `OPENBOX_AGENT_IDENTITY_METHOD`, `OPENBOX_OKTA_AGENT_ID`,
|
|
56
|
+
`OPENBOX_OKTA_AGENT_KEY_ID`, `OPENBOX_OKTA_AGENT_PRIVATE_KEY`,
|
|
57
|
+
`OPENBOX_OKTA_AGENT_ALGORITHM`, `OPENBOX_AGENT_ID`, `OPENBOX_ORGANIZATION_ID`,
|
|
58
|
+
`OPENBOX_DEPLOYMENT_ID`, `OPENBOX_AGENT_PROOF_AUDIENCE`. DID and Okta identity
|
|
59
|
+
fields are mutually exclusive; the canonical Okta private-key encoding is PKCS8 PEM.
|
|
60
|
+
- `EvaluationClient.emit_handoff` / `aemit_handoff` — source-authenticated handoff
|
|
61
|
+
(`POST /api/v1/handoffs` for openbox_did, `/api/v2/handoffs` for okta_ai_agent).
|
|
62
|
+
Inferred unsigned mode raises `OpenBoxConfigError` (no source identity to prove)
|
|
63
|
+
instead of silently falling back to the legacy receiver-authenticated event.
|
|
64
|
+
- `EvaluationClient.validate_okta_identity_transition` /
|
|
65
|
+
`avalidate_okta_identity_transition` and `.validate_openbox_did_identity_transition` /
|
|
66
|
+
`avalidate_openbox_did_identity_transition` — transition-preflight helpers that sign
|
|
67
|
+
with an **explicit candidate identity** and never fall back to the client's active
|
|
68
|
+
signer, even if the client happens to already use the same identity method.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- **BREAKING (security fix):** `EvaluationClient.evaluate` / `aevaluate` and
|
|
72
|
+
`.poll_approval` / `.apoll_approval` now raise an actionable
|
|
73
|
+
`OpenBoxAuthError`/`OpenBoxSigningError` on HTTP 401/403, regardless of
|
|
74
|
+
`on_api_error`. Previously, evaluate routed every `>= 400` response — including
|
|
75
|
+
401/403 — through the network-failure path, which under the default `fail_open`
|
|
76
|
+
produced a fallback `ALLOW`; an agent whose key was revoked kept operating,
|
|
77
|
+
ungoverned. Approval polling treated 401/403 the same as a transient poll failure
|
|
78
|
+
(returned `None`, read by callers as "still pending"), so a revoked agent's
|
|
79
|
+
approval wait never terminated. Both are now hard authentication failures: they
|
|
80
|
+
fail closed unconditionally and are never retried against the other API version.
|
|
81
|
+
A genuine network error or 5xx is unaffected and still respects `on_api_error`.
|
|
82
|
+
|
|
83
|
+
## [1.2.0] - 2026-07-23
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
- **BREAKING:** Renamed the public BLOCK remediation contract from `retry_plan` to `patch`.
|
|
87
|
+
`RetryPlan` is now `Patch`, `RetryDirective` is now `PatchDirective`, and
|
|
88
|
+
`handle_retryable_block()` is now `handle_patch()`. `EvaluationResult.retry_plan` and
|
|
89
|
+
`ApprovalResult.retry_plan` are now `.patch`. The parser now reads only the canonical outer
|
|
90
|
+
wire key `patch`; the old `retry_plan` key is no longer recognized.
|
|
91
|
+
- The verdict gate is unchanged: a directive is still surfaced only for an exact `BLOCK` verdict
|
|
92
|
+
carrying a valid patch — never for HALT/`should_stop()`, and never for an expired approval
|
|
93
|
+
result.
|
|
94
|
+
|
|
95
|
+
### Notes
|
|
96
|
+
- This is a breaking release: the old public names (`RetryPlan`, `RetryDirective`,
|
|
97
|
+
`handle_retryable_block`, `.retry_plan`) are removed, not aliased. Consumers still pinned to
|
|
98
|
+
`1.1.0` remain fail-safe — they ignore the unknown `patch` field but still enforce the `BLOCK`
|
|
99
|
+
verdict.
|
|
100
|
+
|
|
101
|
+
## [1.1.0] - 2026-07-21
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
- `RetryPlan` and `RetryDirective` dataclasses in `openbox_core.contracts.results`.
|
|
105
|
+
- Optional `retry_plan` directive parsing on both `EvaluationResult` and `ApprovalResult`.
|
|
106
|
+
A `_MISSING` sentinel keeps a present `new_input: null` distinct from an absent field; falsy
|
|
107
|
+
values (`null`, `""`, `0`, `[]`, `{}`) are preserved; a boolean `new_input` is rejected; and every
|
|
108
|
+
number (recursively) must be finite and, if integral, a JS-safe integer (`|n| <= 2^53 - 1`).
|
|
109
|
+
- `handle_retryable_block(result)` — an opt-in, pure inspector that returns a `RetryDirective` only
|
|
110
|
+
for a `BLOCK` verdict carrying a valid plan. Returns `None` for a plain BLOCK, every non-BLOCK
|
|
111
|
+
verdict (including HALT), a pending verdict, and an expired `ApprovalResult`.
|
|
112
|
+
|
|
113
|
+
### Notes
|
|
114
|
+
- Default enforcement is unchanged: a `BLOCK` verdict still raises `GovernanceBlockedError`. The new
|
|
115
|
+
helper is opt-in and never triggers an automatic retry; malformed or ineligible plans are treated
|
|
116
|
+
as absent (never fail open).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenBox AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: openbox-sdk-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: OpenBox base SDK - governance contracts, strict gate, identity/signing, evaluate client, context runtime, OTel span wire serialization, and generic instrumentation shared by every OpenBox framework SDK
|
|
5
5
|
Author-email: OpenBox Team <tino@openbox.ai>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
7
8
|
Keywords: governance,observability,openbox,opentelemetry,sdk
|
|
8
9
|
Classifier: Development Status :: 3 - Alpha
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -46,10 +46,14 @@ framework SDK builds on. It owns:
|
|
|
46
46
|
`EvaluationResult`, `ApprovalResult`, `Verdict`).
|
|
47
47
|
- **Always-strict governance gate** — malformed event/runtime contracts raise
|
|
48
48
|
`ContractError` before send; there are no configurable gate modes.
|
|
49
|
-
- **Identity & signing** — AIP DID validation + Ed25519 request
|
|
50
|
-
byte-compatible with the Temporal SDK signing contract.
|
|
51
|
-
|
|
52
|
-
(
|
|
49
|
+
- **Identity & signing** — v1 OpenBox DID: AIP DID validation + Ed25519 request
|
|
50
|
+
signing, byte-compatible with the Temporal SDK signing contract. v2 Okta AI
|
|
51
|
+
Agent: tagged identity configuration + RS256 JWT assertion signing
|
|
52
|
+
(`openbox_core.identity_okta`, `openbox_core.identity_types`).
|
|
53
|
+
- **Evaluate client** — sync + async calls to OpenBox Core, routed by
|
|
54
|
+
configured identity: `/api/v1/governance/evaluate` (`/approval`,
|
|
55
|
+
`/auth/validate`, `/handoffs`) for OpenBox DID / inferred unsigned mode, or
|
|
56
|
+
the `/api/v2/*` equivalents for Okta AI Agent — no cross-version retry.
|
|
53
57
|
- **Context & runtime** — `ActivityContext`, `ContextStore`, trace
|
|
54
58
|
correlation, `FrameworkAdapter` protocol, `OpenBoxRuntime`.
|
|
55
59
|
- **OTel span wire serialization** — OTel spans are the internal source of
|
|
@@ -78,6 +82,64 @@ uv sync --extra http # + HTTP instrumentation targets
|
|
|
78
82
|
uv sync --extra db # + DB instrumentation targets
|
|
79
83
|
```
|
|
80
84
|
|
|
85
|
+
## Okta AI Agent identity (`okta_ai_agent`)
|
|
86
|
+
|
|
87
|
+
An agent whose OpenBox identity is verified against an Okta AI Agent credential
|
|
88
|
+
signs every request with an RS256 assertion. That assertion binds seven values
|
|
89
|
+
OpenBox Core already owns, so the runtime does **not** configure them — it fetches
|
|
90
|
+
them from Core after authenticating with its API key:
|
|
91
|
+
|
|
92
|
+
```dotenv
|
|
93
|
+
OPENBOX_API_URL=https://core.example.com
|
|
94
|
+
OPENBOX_API_KEY=obx_live_...
|
|
95
|
+
OPENBOX_OKTA_AGENT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
|
|
96
|
+
...
|
|
97
|
+
-----END PRIVATE KEY-----"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That is the complete configuration. The private key is the only value Core can
|
|
101
|
+
never supply — **OpenBox never receives, stores, or returns it.** Everything else
|
|
102
|
+
(agent id, organization id, deployment id, assertion audience, external Okta agent
|
|
103
|
+
id, credential `kid`, algorithm) comes from `GET /api/v2/auth/bootstrap` on first
|
|
104
|
+
use and is cached in memory for the lifetime of the client.
|
|
105
|
+
|
|
106
|
+
Before the first governed request the SDK derives its private key's RFC 7638
|
|
107
|
+
public-key thumbprint and compares it with the one Core reports for the agent's
|
|
108
|
+
selected credential. A mismatch raises `OpenBoxConfigError` immediately, rather
|
|
109
|
+
than sending an assertion that could only be rejected:
|
|
110
|
+
|
|
111
|
+
> The configured private key does not match the selected Okta credential for this
|
|
112
|
+
> OpenBox agent. Export the private key associated with the selected credential,
|
|
113
|
+
> or rotate the agent credential.
|
|
114
|
+
|
|
115
|
+
**Credential rotation.** Long-running agents can refresh explicitly:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
document = client.refresh_identity_metadata() # async: await client.arefresh_identity_metadata()
|
|
119
|
+
print(document.okta.credential_kid)
|
|
120
|
+
print(document.authority.activation_version)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The document also carries required, non-secret assignment, provider-generation,
|
|
124
|
+
activation, credential, and projection identifiers. The SDK fails closed when an
|
|
125
|
+
IAM-v3 Core deployment omits or returns malformed authority metadata.
|
|
126
|
+
The refresh re-runs the thumbprint check *before* replacing cached metadata, so a
|
|
127
|
+
credential that rotated to a key this process does not hold fails loudly and leaves
|
|
128
|
+
the client on its previous identity. The SDK never refreshes automatically after an
|
|
129
|
+
auth failure: rotation may have selected a new public key while the process still
|
|
130
|
+
holds the old private key, and a silent retry would hide that rather than fix it.
|
|
131
|
+
|
|
132
|
+
**Requirements.** The key must be a PKCS8 PEM RSA key of at least 2048 bits, and
|
|
133
|
+
its public half must already be registered in Okta for the selected credential.
|
|
134
|
+
|
|
135
|
+
**Explicit configuration** (every metadata field set locally) remains supported for
|
|
136
|
+
compatibility. The two styles cannot be mixed: a configuration carrying only *some*
|
|
137
|
+
metadata fields raises `OpenBoxConfigError` naming the offending fields, rather than
|
|
138
|
+
quietly merging stale local values over what Core would have supplied. If Core
|
|
139
|
+
answers `404`, the SDK reports that the deployment predates bootstrap and asks you
|
|
140
|
+
to upgrade Core or supply the complete explicit configuration — it never downgrades
|
|
141
|
+
to an unsigned request or to a different identity method.
|
|
142
|
+
|
|
81
143
|
## Import safety
|
|
82
144
|
|
|
83
145
|
`openbox_core.__init__` and all `openbox_core.contracts.*` modules import
|
|
@@ -85,6 +147,9 @@ without pulling in `httpx`, `cryptography`, `requests`, or OTel
|
|
|
85
147
|
instrumentation — safe for constrained framework paths such as the Temporal
|
|
86
148
|
workflow sandbox. `tests/test_import_safety.py` enforces this in CI.
|
|
87
149
|
|
|
150
|
+
`openbox_core.bootstrap` and `openbox_core.jwk_thumbprint` follow the same rule —
|
|
151
|
+
both keep `httpx`/`cryptography` behind function-local imports.
|
|
152
|
+
|
|
88
153
|
## Development
|
|
89
154
|
|
|
90
155
|
```bash
|
|
@@ -92,3 +157,7 @@ uv sync --all-extras
|
|
|
92
157
|
uv run pytest
|
|
93
158
|
uv run ruff check .
|
|
94
159
|
```
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# openbox-sdk-python
|
|
2
|
+
|
|
3
|
+
OpenBox base SDK (`import openbox_core`) — the standalone core every OpenBox
|
|
4
|
+
framework SDK builds on. It owns:
|
|
5
|
+
|
|
6
|
+
- **Contracts** — event and result dataclasses (`EventEnvelope`,
|
|
7
|
+
`EvaluationResult`, `ApprovalResult`, `Verdict`).
|
|
8
|
+
- **Always-strict governance gate** — malformed event/runtime contracts raise
|
|
9
|
+
`ContractError` before send; there are no configurable gate modes.
|
|
10
|
+
- **Identity & signing** — v1 OpenBox DID: AIP DID validation + Ed25519 request
|
|
11
|
+
signing, byte-compatible with the Temporal SDK signing contract. v2 Okta AI
|
|
12
|
+
Agent: tagged identity configuration + RS256 JWT assertion signing
|
|
13
|
+
(`openbox_core.identity_okta`, `openbox_core.identity_types`).
|
|
14
|
+
- **Evaluate client** — sync + async calls to OpenBox Core, routed by
|
|
15
|
+
configured identity: `/api/v1/governance/evaluate` (`/approval`,
|
|
16
|
+
`/auth/validate`, `/handoffs`) for OpenBox DID / inferred unsigned mode, or
|
|
17
|
+
the `/api/v2/*` equivalents for Okta AI Agent — no cross-version retry.
|
|
18
|
+
- **Context & runtime** — `ActivityContext`, `ContextStore`, trace
|
|
19
|
+
correlation, `FrameworkAdapter` protocol, `OpenBoxRuntime`.
|
|
20
|
+
- **OTel span wire serialization** — OTel spans are the internal source of
|
|
21
|
+
truth, projected to the current Core `SpanData` wire shape before send.
|
|
22
|
+
- **Generic instrumentation** — HTTP/DB/file/function wrappers with a
|
|
23
|
+
started/completed hook runtime enforcing preflight before real operations.
|
|
24
|
+
- **Conformance kit** — reusable fixtures framework SDKs run to prove parity.
|
|
25
|
+
|
|
26
|
+
Framework SDKs (Temporal, LangGraph, LangChain, DeepAgent, CrewAI) are thin
|
|
27
|
+
adapters: they bind framework lifecycle into `ActivityContext`, emit lifecycle
|
|
28
|
+
events, and translate `EvaluationResult` into native behavior.
|
|
29
|
+
|
|
30
|
+
## Framework SDK integration
|
|
31
|
+
|
|
32
|
+
New framework SDK developers and agents should start with
|
|
33
|
+
[the OpenBox Python Framework SDK Integration guide](.github/instructions/openbox-sdk-python.instructions.md).
|
|
34
|
+
It describes how to build a framework SDK on top of this base package without
|
|
35
|
+
reimplementing config, signing, governance gates, hook wire payloads, or
|
|
36
|
+
conformance fixtures.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv sync # core
|
|
42
|
+
uv sync --extra http # + HTTP instrumentation targets
|
|
43
|
+
uv sync --extra db # + DB instrumentation targets
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Okta AI Agent identity (`okta_ai_agent`)
|
|
47
|
+
|
|
48
|
+
An agent whose OpenBox identity is verified against an Okta AI Agent credential
|
|
49
|
+
signs every request with an RS256 assertion. That assertion binds seven values
|
|
50
|
+
OpenBox Core already owns, so the runtime does **not** configure them — it fetches
|
|
51
|
+
them from Core after authenticating with its API key:
|
|
52
|
+
|
|
53
|
+
```dotenv
|
|
54
|
+
OPENBOX_API_URL=https://core.example.com
|
|
55
|
+
OPENBOX_API_KEY=obx_live_...
|
|
56
|
+
OPENBOX_OKTA_AGENT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
|
|
57
|
+
...
|
|
58
|
+
-----END PRIVATE KEY-----"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
That is the complete configuration. The private key is the only value Core can
|
|
62
|
+
never supply — **OpenBox never receives, stores, or returns it.** Everything else
|
|
63
|
+
(agent id, organization id, deployment id, assertion audience, external Okta agent
|
|
64
|
+
id, credential `kid`, algorithm) comes from `GET /api/v2/auth/bootstrap` on first
|
|
65
|
+
use and is cached in memory for the lifetime of the client.
|
|
66
|
+
|
|
67
|
+
Before the first governed request the SDK derives its private key's RFC 7638
|
|
68
|
+
public-key thumbprint and compares it with the one Core reports for the agent's
|
|
69
|
+
selected credential. A mismatch raises `OpenBoxConfigError` immediately, rather
|
|
70
|
+
than sending an assertion that could only be rejected:
|
|
71
|
+
|
|
72
|
+
> The configured private key does not match the selected Okta credential for this
|
|
73
|
+
> OpenBox agent. Export the private key associated with the selected credential,
|
|
74
|
+
> or rotate the agent credential.
|
|
75
|
+
|
|
76
|
+
**Credential rotation.** Long-running agents can refresh explicitly:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
document = client.refresh_identity_metadata() # async: await client.arefresh_identity_metadata()
|
|
80
|
+
print(document.okta.credential_kid)
|
|
81
|
+
print(document.authority.activation_version)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The document also carries required, non-secret assignment, provider-generation,
|
|
85
|
+
activation, credential, and projection identifiers. The SDK fails closed when an
|
|
86
|
+
IAM-v3 Core deployment omits or returns malformed authority metadata.
|
|
87
|
+
The refresh re-runs the thumbprint check *before* replacing cached metadata, so a
|
|
88
|
+
credential that rotated to a key this process does not hold fails loudly and leaves
|
|
89
|
+
the client on its previous identity. The SDK never refreshes automatically after an
|
|
90
|
+
auth failure: rotation may have selected a new public key while the process still
|
|
91
|
+
holds the old private key, and a silent retry would hide that rather than fix it.
|
|
92
|
+
|
|
93
|
+
**Requirements.** The key must be a PKCS8 PEM RSA key of at least 2048 bits, and
|
|
94
|
+
its public half must already be registered in Okta for the selected credential.
|
|
95
|
+
|
|
96
|
+
**Explicit configuration** (every metadata field set locally) remains supported for
|
|
97
|
+
compatibility. The two styles cannot be mixed: a configuration carrying only *some*
|
|
98
|
+
metadata fields raises `OpenBoxConfigError` naming the offending fields, rather than
|
|
99
|
+
quietly merging stale local values over what Core would have supplied. If Core
|
|
100
|
+
answers `404`, the SDK reports that the deployment predates bootstrap and asks you
|
|
101
|
+
to upgrade Core or supply the complete explicit configuration — it never downgrades
|
|
102
|
+
to an unsigned request or to a different identity method.
|
|
103
|
+
|
|
104
|
+
## Import safety
|
|
105
|
+
|
|
106
|
+
`openbox_core.__init__` and all `openbox_core.contracts.*` modules import
|
|
107
|
+
without pulling in `httpx`, `cryptography`, `requests`, or OTel
|
|
108
|
+
instrumentation — safe for constrained framework paths such as the Temporal
|
|
109
|
+
workflow sandbox. `tests/test_import_safety.py` enforces this in CI.
|
|
110
|
+
|
|
111
|
+
`openbox_core.bootstrap` and `openbox_core.jwk_thumbprint` follow the same rule —
|
|
112
|
+
both keep `httpx`/`cryptography` behind function-local imports.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
uv sync --all-extras
|
|
118
|
+
uv run pytest
|
|
119
|
+
uv run ruff check .
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
@@ -37,7 +37,7 @@ from .errors import (
|
|
|
37
37
|
# governance; eagerly it can deadlock package init as a circular import, and
|
|
38
38
|
# lazily it can recurse unboundedly when a per-request header builder resolves
|
|
39
39
|
# the version. Keep in sync with pyproject.toml on release.
|
|
40
|
-
__version__ = "1.
|
|
40
|
+
__version__ = "1.3.0"
|
|
41
41
|
|
|
42
42
|
__all__ = [
|
|
43
43
|
"__version__",
|