bylaw-python 0.4.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 (38) hide show
  1. bylaw_python-0.4.0/.github/workflows/ci.yml +16 -0
  2. bylaw_python-0.4.0/.gitignore +33 -0
  3. bylaw_python-0.4.0/.shipsafeignore +45 -0
  4. bylaw_python-0.4.0/CHANGELOG.md +112 -0
  5. bylaw_python-0.4.0/PKG-INFO +227 -0
  6. bylaw_python-0.4.0/README.md +183 -0
  7. bylaw_python-0.4.0/demo.py +184 -0
  8. bylaw_python-0.4.0/docs/MIGRATION_0.4.md +138 -0
  9. bylaw_python-0.4.0/pyproject.toml +69 -0
  10. bylaw_python-0.4.0/requirements.txt +17 -0
  11. bylaw_python-0.4.0/src/bylaw_python/__init__.py +114 -0
  12. bylaw_python-0.4.0/src/bylaw_python/adapters/__init__.py +1 -0
  13. bylaw_python-0.4.0/src/bylaw_python/adapters/_core.py +58 -0
  14. bylaw_python-0.4.0/src/bylaw_python/adapters/crewai.py +99 -0
  15. bylaw_python-0.4.0/src/bylaw_python/adapters/langchain.py +167 -0
  16. bylaw_python-0.4.0/src/bylaw_python/adapters/llamaindex.py +90 -0
  17. bylaw_python-0.4.0/src/bylaw_python/cli.py +366 -0
  18. bylaw_python-0.4.0/src/bylaw_python/client.py +1595 -0
  19. bylaw_python-0.4.0/src/bylaw_python/config.py +95 -0
  20. bylaw_python-0.4.0/src/bylaw_python/counterparty.py +145 -0
  21. bylaw_python-0.4.0/src/bylaw_python/enforce.py +561 -0
  22. bylaw_python-0.4.0/src/bylaw_python/exceptions.py +104 -0
  23. bylaw_python-0.4.0/src/bylaw_python/manifest.py +152 -0
  24. bylaw_python-0.4.0/src/bylaw_python/models.py +330 -0
  25. bylaw_python-0.4.0/src/bylaw_python/pending.py +128 -0
  26. bylaw_python-0.4.0/src/bylaw_python/webhook.py +44 -0
  27. bylaw_python-0.4.0/tests/__init__.py +0 -0
  28. bylaw_python-0.4.0/tests/conftest.py +188 -0
  29. bylaw_python-0.4.0/tests/test_adapters.py +260 -0
  30. bylaw_python-0.4.0/tests/test_client.py +951 -0
  31. bylaw_python-0.4.0/tests/test_client_cache.py +364 -0
  32. bylaw_python-0.4.0/tests/test_counterparty.py +78 -0
  33. bylaw_python-0.4.0/tests/test_enforce.py +212 -0
  34. bylaw_python-0.4.0/tests/test_jwks_kid_rotation.py +108 -0
  35. bylaw_python-0.4.0/tests/test_manifest.py +220 -0
  36. bylaw_python-0.4.0/tests/test_models.py +173 -0
  37. bylaw_python-0.4.0/tests/test_replay_detection.py +105 -0
  38. bylaw_python-0.4.0/tests/test_retry_after.py +277 -0
@@ -0,0 +1,16 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
12
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
13
+ with:
14
+ python-version: "3.12"
15
+ - run: pip install -r requirements.txt && pip install -e ".[yaml]"
16
+ - run: pytest
@@ -0,0 +1,33 @@
1
+ # Virtual environments
2
+ .venv/
3
+ venv/
4
+ env/
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.egg-info/
11
+ *.egg
12
+ dist/
13
+ build/
14
+ *.whl
15
+
16
+ # IDE
17
+ .vscode/
18
+ .idea/
19
+ *.swp
20
+ *.swo
21
+
22
+ # OS
23
+ .DS_Store
24
+ Thumbs.db
25
+
26
+ # Testing
27
+ .pytest_cache/
28
+ .coverage
29
+ htmlcov/
30
+
31
+ # Env files
32
+ .env
33
+ .env.local
@@ -0,0 +1,45 @@
1
+ # Ship Safe ignore rules for python-sdk
2
+ # Published as ledgix-python on PyPI. Agent-agnostic SDK shim.
3
+
4
+ # --- Test fixtures ---
5
+
6
+ # Test files use dummy API key loaded from env var with test fallback.
7
+ Generic API Key Assignment
8
+
9
+ # Test assertions use == for values, not secrets.
10
+ TIMING_ATTACK_COMPARISON
11
+
12
+ # --- CLI (ledgix init / status / teardown) ---
13
+
14
+ # CLI commands invoke docker-compose and subprocess.run for local dev
15
+ # orchestration. These are user-invoked commands, not LLM-driven actions.
16
+ AGENT_OUTPUT_TO_ACTION
17
+
18
+ # Recursive invocation flag is a Pydantic data field, not agent behavior.
19
+ AGENT_RECURSIVE_INVOCATION
20
+
21
+ # --- Docker-compose defaults ---
22
+
23
+ # cli.py embeds docker-compose templates with localhost Postgres URLs
24
+ # and dev-only default passwords for local development.
25
+ Database URL with Credentials
26
+ SSRF_INTERNAL_IP
27
+ Password Assignment
28
+
29
+ # --- Dependencies ---
30
+
31
+ # httpx>=0.25.0 is a standard PyPI version specifier, not a git/URL dep.
32
+ GIT_PYTHON_DEP
33
+
34
+ # --- JWT ---
35
+
36
+ # jwt.decode with a key argument verifies the signature. The scanner
37
+ # flags the call as "verification disabled" but it is not.
38
+ JWT_VERIFY_DISABLED
39
+
40
+ # --- Git history ---
41
+ # No real secrets were ever committed; placeholder values only.
42
+ GIT_HISTORY_SECRET
43
+
44
+ # --- General ---
45
+ MCP_SHADOW_CONFIG
@@ -0,0 +1,112 @@
1
+ # Changelog
2
+
3
+ All notable changes to `bylaw-python` will be 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
+ ## [0.4.0]
9
+
10
+ ### Breaking changes — categorical confidence buckets
11
+
12
+ This release replaces the legacy decimal `confidence: float` field with five
13
+ categorical buckets (`extra_high | high | medium | low | none`), and splits
14
+ the overloaded `approved=True + confidence=0.00` "needs human review"
15
+ sentinel into an explicit `decision_status` field
16
+ (`approved | denied | approved_pending_review`). See
17
+ [`docs/MIGRATION_0.4.md`](docs/MIGRATION_0.4.md) for the migration guide.
18
+
19
+ > Note: the wire format change is breaking, but the SemVer bump is 0.3.1 →
20
+ > 0.4.0 (still in 0.x.0). The 0.x line is pre-1.0 and minor bumps are
21
+ > allowed to carry breaking changes per SemVer §4. Customers pinning
22
+ > `^0.3` will NOT auto-upgrade; they must explicitly bump to `^0.4`
23
+ > after reading the migration guide.
24
+
25
+ #### `ClearanceResponse` — fields removed
26
+ - `approved: bool`
27
+ - `confidence: float`
28
+ - `minimum_confidence_score: float`
29
+
30
+ #### `ClearanceResponse` — fields added
31
+ - `decision_status: Literal["approved", "denied", "approved_pending_review"]`
32
+ - `confidence_bucket: Literal["extra_high", "high", "medium", "low", "none"]`
33
+ - `minimum_confidence_bucket: Literal[...]` (same five values)
34
+ - `is_approved` property — convenience boolean for the common
35
+ "may the agent proceed?" check, returns `True` for both `approved` and
36
+ `approved_pending_review`.
37
+
38
+ #### `LedgerEntry`
39
+ - `confidence_bucket` and `decision_status` added (populated for
40
+ canonical_version=2 events).
41
+ - Legacy `confidence: float` and `approved: bool` retained on the model so
42
+ canonical_version=1 hash verification of historical rows still works.
43
+
44
+ #### Why this changed
45
+ The previous design overloaded `confidence=0.00` to mean both "extreme low
46
+ confidence" (deny path) and "needs human review" (gated approval). Customer
47
+ code doing `if response.confidence < threshold: reject` would accidentally
48
+ reject the very review-pending decisions the platform was trying to surface.
49
+ The bucket migration retires the cents-level decimal precision the model
50
+ couldn't reliably produce and gives review-pending its own dedicated state.
51
+
52
+ #### Migration in one line
53
+ - Old: `if response.approved and response.confidence >= 0.8: ...`
54
+ - New: `if response.decision_status == "approved": ...`
55
+ (or `if response.is_approved: ...` if you also want to treat
56
+ `approved_pending_review` as "may proceed eventually").
57
+
58
+ ## [0.3.1]
59
+
60
+ ### Added
61
+ - `ClearanceRequest.data_categories`, `purpose`, and
62
+ `processing_register_ref` — Phase 2 GDPR Article 30 processing-register
63
+ matching. When set, the Vault's pre-LLM validator chain checks for an
64
+ active register that covers the (data_categories ⊇ requested,
65
+ purpose ∈ register.purposes, recipient ∈ register.recipients) tuple.
66
+ Unmatched requests deny with `reason_code='processing_no_register_match'`.
67
+ - `ClearanceRequest.dataset_ref` — Phase 6 dataset lineage. Logical
68
+ reference (filename, S3 path, table name, etc.) for the production
69
+ data this action reads/writes. Auto-derived dataset sheets group on
70
+ this field for row counts, schema fingerprints, and consent-basis
71
+ breakdowns.
72
+ - All four fields are also surfaced as kwargs on `enforce()`, `tool()`,
73
+ `vault_enforce()`, and `VaultContext` so the high-level decorator API
74
+ can populate them without dropping to `LedgixClient.request_clearance`.
75
+ - `/mint-token` cache-replay forwards the new fields alongside the 0.3.0
76
+ destination set.
77
+
78
+ ### Compatibility
79
+ - Backwards-compatible. All four fields are optional. Vault ignores
80
+ unknown wire fields prior to the matching schema migration; older SDKs
81
+ continue to work against the new Vault.
82
+
83
+ ## [0.3.0]
84
+
85
+ ### Added
86
+ - `ClearanceRequest.destination_uri`, `destination_provider`, and
87
+ `destination_account_ref` — typed counterparty attribution that replaces
88
+ per-tool guessing in downstream policy checks. All three fields are
89
+ optional; existing callers are unaffected.
90
+ - `bylaw_python.counterparty.extract()` — best-effort SDK-side hint that
91
+ fills the new fields when the caller doesn't supply them. Recognizes
92
+ Stripe (`api_key` prefix → 12-char account ref), Twilio (`account_sid`),
93
+ Slack (`team_id` / `workspace`), AWS Bedrock (`model_id`), OpenAI
94
+ (`organization`), Anthropic (`organization`), and a generic URL-host
95
+ fallback. The Vault re-runs its own extractor chain server-side, so
96
+ this is a hint — caller-supplied values always win.
97
+ - `/mint-token` cache-replay path forwards the destination fields so
98
+ re-minted A-JWTs share attribution with the original decision.
99
+
100
+ ### Compatibility
101
+ - Backwards-compatible against Vault 0.x (Vault ignores unknown wire
102
+ fields prior to the matching schema migration). Older SDKs continue
103
+ to work against the new Vault — destination columns are simply NULL
104
+ on those rows.
105
+
106
+ ## [0.2.1]
107
+ - Honor `Retry-After` on 429 from Vault backpressure.
108
+
109
+ ## [0.2.0]
110
+ - Idempotency-Key on POSTs, JWKS async lock, adapter dedup.
111
+ - Security: JWKS kid matching, jti replay detection.
112
+ - HITL: `PendingApproval`, `review_mode="detach"`, `verify_webhook`.
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: bylaw-python
3
+ Version: 0.4.0
4
+ Summary: Agent-agnostic compliance shim for SOX 404 policy enforcement via the ALCV Vault
5
+ Project-URL: Homepage, https://github.com/bylaw-dev/python-sdk
6
+ Project-URL: Documentation, https://docs.bylaw.dev
7
+ Project-URL: Repository, https://github.com/bylaw-dev/python-sdk
8
+ Author-email: Bylaw <team@bylaw.dev>
9
+ License: MIT
10
+ Keywords: agents,ai,compliance,security,sox404,vault
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: cachetools>=5.3.0
24
+ Requires-Dist: click>=8.1.0
25
+ Requires-Dist: httpx>=0.25.0
26
+ Requires-Dist: pydantic-settings>=2.0.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: pyjwt[crypto]>=2.8.0
29
+ Provides-Extra: crewai
30
+ Requires-Dist: crewai>=0.1.0; extra == 'crewai'
31
+ Provides-Extra: dev
32
+ Requires-Dist: build>=1.0.0; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
34
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
35
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
36
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
37
+ Provides-Extra: langchain
38
+ Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
39
+ Provides-Extra: llamaindex
40
+ Requires-Dist: llama-index-core>=0.10.0; extra == 'llamaindex'
41
+ Provides-Extra: yaml
42
+ Requires-Dist: pyyaml>=6.0; extra == 'yaml'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # Ledgix ALCV — Python SDK
46
+
47
+ [![PyPI](https://img.shields.io/badge/pypi-v0.1.13-blue)](https://pypi.org/project/bylaw-python/)
48
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://python.org)
49
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
50
+
51
+ Agent-agnostic compliance shim for SOX 404 policy enforcement. Intercepts AI agent tool calls, validates them against your policies via the ALCV Vault, and ensures only approved actions receive a cryptographically signed A-JWT (Agentic JSON Web Token).
52
+
53
+ ## Quick Start
54
+
55
+ ```bash
56
+ pip install bylaw-python
57
+ ```
58
+
59
+ ```python
60
+ # ledgix.yaml
61
+ # enforce:
62
+ # - tool: "stripe_*"
63
+ # policy_id: "financial-high-risk"
64
+ # - tool: "*"
65
+ # policy_id: "default"
66
+
67
+ import tools
68
+ import bylaw_python as ledgix
69
+
70
+ ledgix.configure(agent_id="payments-agent")
71
+ ledgix.auto_instrument(tools)
72
+
73
+ result = tools.stripe_refund(45, "Late package")
74
+ print(result)
75
+ ```
76
+
77
+ `auto_instrument()` reads `ledgix.yaml`, `ledgix.yml`, or `ledgix.json` from the current working directory by default, wraps matching functions in place, and leaves unmatched functions alone.
78
+
79
+ ## Configuration
80
+
81
+ Set environment variables (prefix: `LEDGIX_`):
82
+
83
+ | Variable | Default | Description |
84
+ |---|---|---|
85
+ | `LEDGIX_VAULT_URL` | `http://localhost:8000` | Vault server URL |
86
+ | `LEDGIX_VAULT_API_KEY` | `""` | API key for Vault auth |
87
+ | `LEDGIX_VAULT_TIMEOUT` | `30.0` | Request timeout (seconds) |
88
+ | `LEDGIX_VERIFY_JWT` | `true` | Verify A-JWT signatures |
89
+ | `LEDGIX_JWT_ISSUER` | `alcv-vault` | Expected A-JWT issuer |
90
+ | `LEDGIX_JWT_AUDIENCE` | `ledgix-sdk` | Expected A-JWT audience |
91
+ | `LEDGIX_AGENT_ID` | `default-agent` | Agent identifier |
92
+
93
+ Or pass a `VaultConfig` directly:
94
+
95
+ ```python
96
+ from bylaw_python import LedgixClient, VaultConfig
97
+
98
+ config = VaultConfig(vault_url="https://vault.mycompany.com", vault_api_key="sk-...")
99
+ client = LedgixClient(config=config)
100
+ ```
101
+
102
+ ## Manifest-driven auto-instrumentation
103
+
104
+ ```python
105
+ import tools
106
+ import bylaw_python as ledgix
107
+
108
+ ledgix.configure(agent_id="payments-agent")
109
+
110
+ # Auto-discover ledgix.yaml / ledgix.yml / ledgix.json from the CWD
111
+ wrapped = ledgix.auto_instrument(tools)
112
+
113
+ # Or pass an inline manifest
114
+ ledgix.auto_instrument(
115
+ tools,
116
+ manifest={"enforce": [{"tool": "stripe_*", "policy_id": "financial-high-risk"}]},
117
+ )
118
+ ```
119
+
120
+ YAML manifests require `pyyaml`:
121
+
122
+ ```bash
123
+ pip install bylaw-python[yaml]
124
+ ```
125
+
126
+ ### Escape hatch
127
+
128
+ ```python
129
+ @ledgix.tool
130
+ def special_refund(amount: float):
131
+ return ledgix.current_token()
132
+
133
+ @ledgix.tool(policy_id="override-policy")
134
+ def stripe_charge(amount: float):
135
+ return ledgix.current_token()
136
+ ```
137
+
138
+ ## Framework Adapters
139
+
140
+ ### LangChain
141
+
142
+ ```bash
143
+ pip install bylaw-python[langchain]
144
+ ```
145
+
146
+ ```python
147
+ from bylaw_python.adapters.langchain import LedgixCallbackHandler, LedgixTool
148
+
149
+ # Option 1: Callback handler (intercepts ALL tool calls)
150
+ handler = LedgixCallbackHandler(client)
151
+ agent = create_agent(callbacks=[handler])
152
+
153
+ # Option 2: Wrap individual tools
154
+ guarded_tool = LedgixTool.wrap(client, my_tool, policy_id="refund-policy")
155
+ ```
156
+
157
+ ### LlamaIndex
158
+
159
+ ```bash
160
+ pip install bylaw-python[llamaindex]
161
+ ```
162
+
163
+ ```python
164
+ from bylaw_python.adapters.llamaindex import wrap_tool
165
+
166
+ guarded_tool = wrap_tool(client, my_function_tool, policy_id="refund-policy")
167
+ ```
168
+
169
+ ### CrewAI
170
+
171
+ ```bash
172
+ pip install bylaw-python[crewai]
173
+ ```
174
+
175
+ ```python
176
+ from bylaw_python.adapters.crewai import LedgixCrewAITool
177
+
178
+ guarded_tool = LedgixCrewAITool.wrap(client, my_tool, policy_id="refund-policy")
179
+ ```
180
+
181
+ ## Context Manager
182
+
183
+ ```python
184
+ from bylaw_python import VaultContext
185
+
186
+ with VaultContext(client, "stripe_refund", {"amount": 45}) as ctx:
187
+ print(ctx.clearance.token) # Use the A-JWT
188
+
189
+ # Async
190
+ async with VaultContext(client, "stripe_refund", {"amount": 45}) as ctx:
191
+ print(ctx.clearance.token)
192
+ ```
193
+
194
+ ## Error Handling
195
+
196
+ ```python
197
+ from bylaw_python import ClearanceDeniedError, VaultConnectionError, TokenVerificationError
198
+
199
+ try:
200
+ result = process_refund(amount=5000, reason="...")
201
+ except ClearanceDeniedError as e:
202
+ print(f"Blocked: {e.reason} (request: {e.request_id})")
203
+ except VaultConnectionError:
204
+ print("Cannot reach Vault — fail-closed")
205
+ except TokenVerificationError:
206
+ print("A-JWT signature invalid")
207
+ ```
208
+
209
+ ## Development
210
+
211
+ ```bash
212
+ git clone https://github.com/ledgix-dev/python-sdk.git
213
+ cd python-sdk
214
+ python -m venv .venv && source .venv/bin/activate
215
+ pip install -e ".[dev]"
216
+ pytest tests/ -v --cov
217
+ ```
218
+
219
+ ## Demo
220
+
221
+ ```bash
222
+ python demo.py
223
+ ```
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,183 @@
1
+ # Ledgix ALCV — Python SDK
2
+
3
+ [![PyPI](https://img.shields.io/badge/pypi-v0.1.13-blue)](https://pypi.org/project/bylaw-python/)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://python.org)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
6
+
7
+ Agent-agnostic compliance shim for SOX 404 policy enforcement. Intercepts AI agent tool calls, validates them against your policies via the ALCV Vault, and ensures only approved actions receive a cryptographically signed A-JWT (Agentic JSON Web Token).
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ pip install bylaw-python
13
+ ```
14
+
15
+ ```python
16
+ # ledgix.yaml
17
+ # enforce:
18
+ # - tool: "stripe_*"
19
+ # policy_id: "financial-high-risk"
20
+ # - tool: "*"
21
+ # policy_id: "default"
22
+
23
+ import tools
24
+ import bylaw_python as ledgix
25
+
26
+ ledgix.configure(agent_id="payments-agent")
27
+ ledgix.auto_instrument(tools)
28
+
29
+ result = tools.stripe_refund(45, "Late package")
30
+ print(result)
31
+ ```
32
+
33
+ `auto_instrument()` reads `ledgix.yaml`, `ledgix.yml`, or `ledgix.json` from the current working directory by default, wraps matching functions in place, and leaves unmatched functions alone.
34
+
35
+ ## Configuration
36
+
37
+ Set environment variables (prefix: `LEDGIX_`):
38
+
39
+ | Variable | Default | Description |
40
+ |---|---|---|
41
+ | `LEDGIX_VAULT_URL` | `http://localhost:8000` | Vault server URL |
42
+ | `LEDGIX_VAULT_API_KEY` | `""` | API key for Vault auth |
43
+ | `LEDGIX_VAULT_TIMEOUT` | `30.0` | Request timeout (seconds) |
44
+ | `LEDGIX_VERIFY_JWT` | `true` | Verify A-JWT signatures |
45
+ | `LEDGIX_JWT_ISSUER` | `alcv-vault` | Expected A-JWT issuer |
46
+ | `LEDGIX_JWT_AUDIENCE` | `ledgix-sdk` | Expected A-JWT audience |
47
+ | `LEDGIX_AGENT_ID` | `default-agent` | Agent identifier |
48
+
49
+ Or pass a `VaultConfig` directly:
50
+
51
+ ```python
52
+ from bylaw_python import LedgixClient, VaultConfig
53
+
54
+ config = VaultConfig(vault_url="https://vault.mycompany.com", vault_api_key="sk-...")
55
+ client = LedgixClient(config=config)
56
+ ```
57
+
58
+ ## Manifest-driven auto-instrumentation
59
+
60
+ ```python
61
+ import tools
62
+ import bylaw_python as ledgix
63
+
64
+ ledgix.configure(agent_id="payments-agent")
65
+
66
+ # Auto-discover ledgix.yaml / ledgix.yml / ledgix.json from the CWD
67
+ wrapped = ledgix.auto_instrument(tools)
68
+
69
+ # Or pass an inline manifest
70
+ ledgix.auto_instrument(
71
+ tools,
72
+ manifest={"enforce": [{"tool": "stripe_*", "policy_id": "financial-high-risk"}]},
73
+ )
74
+ ```
75
+
76
+ YAML manifests require `pyyaml`:
77
+
78
+ ```bash
79
+ pip install bylaw-python[yaml]
80
+ ```
81
+
82
+ ### Escape hatch
83
+
84
+ ```python
85
+ @ledgix.tool
86
+ def special_refund(amount: float):
87
+ return ledgix.current_token()
88
+
89
+ @ledgix.tool(policy_id="override-policy")
90
+ def stripe_charge(amount: float):
91
+ return ledgix.current_token()
92
+ ```
93
+
94
+ ## Framework Adapters
95
+
96
+ ### LangChain
97
+
98
+ ```bash
99
+ pip install bylaw-python[langchain]
100
+ ```
101
+
102
+ ```python
103
+ from bylaw_python.adapters.langchain import LedgixCallbackHandler, LedgixTool
104
+
105
+ # Option 1: Callback handler (intercepts ALL tool calls)
106
+ handler = LedgixCallbackHandler(client)
107
+ agent = create_agent(callbacks=[handler])
108
+
109
+ # Option 2: Wrap individual tools
110
+ guarded_tool = LedgixTool.wrap(client, my_tool, policy_id="refund-policy")
111
+ ```
112
+
113
+ ### LlamaIndex
114
+
115
+ ```bash
116
+ pip install bylaw-python[llamaindex]
117
+ ```
118
+
119
+ ```python
120
+ from bylaw_python.adapters.llamaindex import wrap_tool
121
+
122
+ guarded_tool = wrap_tool(client, my_function_tool, policy_id="refund-policy")
123
+ ```
124
+
125
+ ### CrewAI
126
+
127
+ ```bash
128
+ pip install bylaw-python[crewai]
129
+ ```
130
+
131
+ ```python
132
+ from bylaw_python.adapters.crewai import LedgixCrewAITool
133
+
134
+ guarded_tool = LedgixCrewAITool.wrap(client, my_tool, policy_id="refund-policy")
135
+ ```
136
+
137
+ ## Context Manager
138
+
139
+ ```python
140
+ from bylaw_python import VaultContext
141
+
142
+ with VaultContext(client, "stripe_refund", {"amount": 45}) as ctx:
143
+ print(ctx.clearance.token) # Use the A-JWT
144
+
145
+ # Async
146
+ async with VaultContext(client, "stripe_refund", {"amount": 45}) as ctx:
147
+ print(ctx.clearance.token)
148
+ ```
149
+
150
+ ## Error Handling
151
+
152
+ ```python
153
+ from bylaw_python import ClearanceDeniedError, VaultConnectionError, TokenVerificationError
154
+
155
+ try:
156
+ result = process_refund(amount=5000, reason="...")
157
+ except ClearanceDeniedError as e:
158
+ print(f"Blocked: {e.reason} (request: {e.request_id})")
159
+ except VaultConnectionError:
160
+ print("Cannot reach Vault — fail-closed")
161
+ except TokenVerificationError:
162
+ print("A-JWT signature invalid")
163
+ ```
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ git clone https://github.com/ledgix-dev/python-sdk.git
169
+ cd python-sdk
170
+ python -m venv .venv && source .venv/bin/activate
171
+ pip install -e ".[dev]"
172
+ pytest tests/ -v --cov
173
+ ```
174
+
175
+ ## Demo
176
+
177
+ ```bash
178
+ python demo.py
179
+ ```
180
+
181
+ ## License
182
+
183
+ MIT