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.
- bylaw_python-0.4.0/.github/workflows/ci.yml +16 -0
- bylaw_python-0.4.0/.gitignore +33 -0
- bylaw_python-0.4.0/.shipsafeignore +45 -0
- bylaw_python-0.4.0/CHANGELOG.md +112 -0
- bylaw_python-0.4.0/PKG-INFO +227 -0
- bylaw_python-0.4.0/README.md +183 -0
- bylaw_python-0.4.0/demo.py +184 -0
- bylaw_python-0.4.0/docs/MIGRATION_0.4.md +138 -0
- bylaw_python-0.4.0/pyproject.toml +69 -0
- bylaw_python-0.4.0/requirements.txt +17 -0
- bylaw_python-0.4.0/src/bylaw_python/__init__.py +114 -0
- bylaw_python-0.4.0/src/bylaw_python/adapters/__init__.py +1 -0
- bylaw_python-0.4.0/src/bylaw_python/adapters/_core.py +58 -0
- bylaw_python-0.4.0/src/bylaw_python/adapters/crewai.py +99 -0
- bylaw_python-0.4.0/src/bylaw_python/adapters/langchain.py +167 -0
- bylaw_python-0.4.0/src/bylaw_python/adapters/llamaindex.py +90 -0
- bylaw_python-0.4.0/src/bylaw_python/cli.py +366 -0
- bylaw_python-0.4.0/src/bylaw_python/client.py +1595 -0
- bylaw_python-0.4.0/src/bylaw_python/config.py +95 -0
- bylaw_python-0.4.0/src/bylaw_python/counterparty.py +145 -0
- bylaw_python-0.4.0/src/bylaw_python/enforce.py +561 -0
- bylaw_python-0.4.0/src/bylaw_python/exceptions.py +104 -0
- bylaw_python-0.4.0/src/bylaw_python/manifest.py +152 -0
- bylaw_python-0.4.0/src/bylaw_python/models.py +330 -0
- bylaw_python-0.4.0/src/bylaw_python/pending.py +128 -0
- bylaw_python-0.4.0/src/bylaw_python/webhook.py +44 -0
- bylaw_python-0.4.0/tests/__init__.py +0 -0
- bylaw_python-0.4.0/tests/conftest.py +188 -0
- bylaw_python-0.4.0/tests/test_adapters.py +260 -0
- bylaw_python-0.4.0/tests/test_client.py +951 -0
- bylaw_python-0.4.0/tests/test_client_cache.py +364 -0
- bylaw_python-0.4.0/tests/test_counterparty.py +78 -0
- bylaw_python-0.4.0/tests/test_enforce.py +212 -0
- bylaw_python-0.4.0/tests/test_jwks_kid_rotation.py +108 -0
- bylaw_python-0.4.0/tests/test_manifest.py +220 -0
- bylaw_python-0.4.0/tests/test_models.py +173 -0
- bylaw_python-0.4.0/tests/test_replay_detection.py +105 -0
- 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
|
+
[](https://pypi.org/project/bylaw-python/)
|
|
48
|
+
[](https://python.org)
|
|
49
|
+
[](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
|
+
[](https://pypi.org/project/bylaw-python/)
|
|
4
|
+
[](https://python.org)
|
|
5
|
+
[](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
|