vella-sdk 1.0.1__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.
- vella_sdk-1.0.1/PKG-INFO +94 -0
- vella_sdk-1.0.1/README.md +69 -0
- vella_sdk-1.0.1/pyproject.toml +49 -0
- vella_sdk-1.0.1/setup.cfg +4 -0
- vella_sdk-1.0.1/tests/test_evaluator.py +61 -0
- vella_sdk-1.0.1/tests/test_proof.py +90 -0
- vella_sdk-1.0.1/vella/__init__.py +70 -0
- vella_sdk-1.0.1/vella/evaluator.py +211 -0
- vella_sdk-1.0.1/vella/policy.py +27 -0
- vella_sdk-1.0.1/vella/proof.py +99 -0
- vella_sdk-1.0.1/vella_sdk.egg-info/PKG-INFO +94 -0
- vella_sdk-1.0.1/vella_sdk.egg-info/SOURCES.txt +13 -0
- vella_sdk-1.0.1/vella_sdk.egg-info/dependency_links.txt +1 -0
- vella_sdk-1.0.1/vella_sdk.egg-info/requires.txt +6 -0
- vella_sdk-1.0.1/vella_sdk.egg-info/top_level.txt +1 -0
vella_sdk-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vella-sdk
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: VELLA — deterministic pre-execution governance substrate. SDK for producing signed decision records.
|
|
5
|
+
Author-email: "Vella Cognitive, LLC" <agent@vellacognitive.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://vellacognitive.com/research/an-inspectable-substrate-for-ai-governance
|
|
8
|
+
Project-URL: Repository, https://github.com/vellacognitive/vella-substrate
|
|
9
|
+
Project-URL: Issues, https://github.com/vellacognitive/vella-substrate/issues
|
|
10
|
+
Keywords: vella,governance,ai-safety,pre-execution,policy,audit,proof-bundle
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: cryptography>=41.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: ruff; extra == "dev"
|
|
23
|
+
Requires-Dist: mypy; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# vella-sdk
|
|
27
|
+
|
|
28
|
+
Python SDK for deterministic pre-execution adjudication and signed proof-bundle generation.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install vella-sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## From source (development)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python3 -m venv .venv
|
|
40
|
+
. .venv/bin/activate
|
|
41
|
+
python -m pip install --upgrade pip
|
|
42
|
+
pip install -e ".[dev]"
|
|
43
|
+
pytest
|
|
44
|
+
ruff check .
|
|
45
|
+
mypy --strict vella/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Basic usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from vella import govern
|
|
52
|
+
|
|
53
|
+
result = govern(
|
|
54
|
+
intent="EXECUTE_CHANGE",
|
|
55
|
+
evidence_mask=1,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(result["decision"]) # ALLOWED | DENIED
|
|
59
|
+
print(result["reason_code"]) # reason code string
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## With proof bundle
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from vella import govern
|
|
66
|
+
|
|
67
|
+
signing_key = open("./proof-signing.pem", "r", encoding="utf-8").read()
|
|
68
|
+
|
|
69
|
+
result = govern(
|
|
70
|
+
intent="EXECUTE_CHANGE",
|
|
71
|
+
evidence_mask=1,
|
|
72
|
+
proof_signing_key=signing_key,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
print(result["proof_bundle"]["kind"]) # vella_proof_bundle_v1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## When to use this SDK
|
|
79
|
+
|
|
80
|
+
This SDK runs VELLA in-process inside your Python application. It is the right choice for agent tool-call hooks, CI/CD gating, edge compute, research notebooks, batch processing, and any context where microsecond-latency adjudication is needed without a separate service.
|
|
81
|
+
|
|
82
|
+
For enterprise service mesh, polyglot environments (Go, Java, .NET), Kubernetes admission control, or multi-tenant deployments, a different VELLA component (the runtime service or sidecar adapter) is the better fit. See the full [deployment scope](../../DEPLOYMENT.md) in the repository root.
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
- `govern(intent, evidence_mask, authority_scope=None, policy_version=None, proof_signing_key=None)`
|
|
87
|
+
- Returns a dict with `decision`, `reason_code`, `latency_us`, and optional `proof_bundle`/`proof_error`
|
|
88
|
+
|
|
89
|
+
## Reference docs
|
|
90
|
+
|
|
91
|
+
See the root repository docs for full protocol details:
|
|
92
|
+
- `spec/icd.md`
|
|
93
|
+
- `spec/schemas/proof.json`
|
|
94
|
+
- `verify/`
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# vella-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for deterministic pre-execution adjudication and signed proof-bundle generation.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install vella-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## From source (development)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python3 -m venv .venv
|
|
15
|
+
. .venv/bin/activate
|
|
16
|
+
python -m pip install --upgrade pip
|
|
17
|
+
pip install -e ".[dev]"
|
|
18
|
+
pytest
|
|
19
|
+
ruff check .
|
|
20
|
+
mypy --strict vella/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Basic usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from vella import govern
|
|
27
|
+
|
|
28
|
+
result = govern(
|
|
29
|
+
intent="EXECUTE_CHANGE",
|
|
30
|
+
evidence_mask=1,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
print(result["decision"]) # ALLOWED | DENIED
|
|
34
|
+
print(result["reason_code"]) # reason code string
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## With proof bundle
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from vella import govern
|
|
41
|
+
|
|
42
|
+
signing_key = open("./proof-signing.pem", "r", encoding="utf-8").read()
|
|
43
|
+
|
|
44
|
+
result = govern(
|
|
45
|
+
intent="EXECUTE_CHANGE",
|
|
46
|
+
evidence_mask=1,
|
|
47
|
+
proof_signing_key=signing_key,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
print(result["proof_bundle"]["kind"]) # vella_proof_bundle_v1
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## When to use this SDK
|
|
54
|
+
|
|
55
|
+
This SDK runs VELLA in-process inside your Python application. It is the right choice for agent tool-call hooks, CI/CD gating, edge compute, research notebooks, batch processing, and any context where microsecond-latency adjudication is needed without a separate service.
|
|
56
|
+
|
|
57
|
+
For enterprise service mesh, polyglot environments (Go, Java, .NET), Kubernetes admission control, or multi-tenant deployments, a different VELLA component (the runtime service or sidecar adapter) is the better fit. See the full [deployment scope](../../DEPLOYMENT.md) in the repository root.
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
- `govern(intent, evidence_mask, authority_scope=None, policy_version=None, proof_signing_key=None)`
|
|
62
|
+
- Returns a dict with `decision`, `reason_code`, `latency_us`, and optional `proof_bundle`/`proof_error`
|
|
63
|
+
|
|
64
|
+
## Reference docs
|
|
65
|
+
|
|
66
|
+
See the root repository docs for full protocol details:
|
|
67
|
+
- `spec/icd.md`
|
|
68
|
+
- `spec/schemas/proof.json`
|
|
69
|
+
- `verify/`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vella-sdk"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "VELLA — deterministic pre-execution governance substrate. SDK for producing signed decision records."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Vella Cognitive, LLC", email = "agent@vellacognitive.com" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"cryptography>=41.0"
|
|
17
|
+
]
|
|
18
|
+
keywords = [
|
|
19
|
+
"vella",
|
|
20
|
+
"governance",
|
|
21
|
+
"ai-safety",
|
|
22
|
+
"pre-execution",
|
|
23
|
+
"policy",
|
|
24
|
+
"audit",
|
|
25
|
+
"proof-bundle"
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Topic :: Security",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["ruff", "mypy", "pytest"]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://vellacognitive.com/research/an-inspectable-substrate-for-ai-governance"
|
|
42
|
+
Repository = "https://github.com/vellacognitive/vella-substrate"
|
|
43
|
+
Issues = "https://github.com/vellacognitive/vella-substrate/issues"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
include = ["vella*"]
|
|
47
|
+
|
|
48
|
+
[tool.mypy]
|
|
49
|
+
strict = true
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from vella import govern
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_execute_change_with_authn_is_allowed() -> None:
|
|
5
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1)
|
|
6
|
+
assert result["decision"] == "ALLOWED"
|
|
7
|
+
assert result["reason_code"] == "POLICY_SATISFIED"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_execute_change_without_evidence_is_denied() -> None:
|
|
11
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=0)
|
|
12
|
+
assert result["decision"] == "DENIED"
|
|
13
|
+
assert result["reason_code"] == "E_EVIDENCE_MISSING"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_escalate_with_authn_authz_is_allowed() -> None:
|
|
17
|
+
result = govern(intent="ESCALATE_PRIVILEGE", evidence_mask=3)
|
|
18
|
+
assert result["decision"] == "ALLOWED"
|
|
19
|
+
assert result["reason_code"] == "POLICY_SATISFIED"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_escalate_with_only_authn_is_denied() -> None:
|
|
23
|
+
result = govern(intent="ESCALATE_PRIVILEGE", evidence_mask=1)
|
|
24
|
+
assert result["decision"] == "DENIED"
|
|
25
|
+
assert result["reason_code"] == "E_EVIDENCE_MISSING"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_data_export_with_authn_authz_is_allowed() -> None:
|
|
29
|
+
result = govern(intent="DATA_EXPORT", evidence_mask=3)
|
|
30
|
+
assert result["decision"] == "ALLOWED"
|
|
31
|
+
assert result["reason_code"] == "POLICY_SATISFIED"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_unknown_intent_is_denied_fast() -> None:
|
|
35
|
+
result = govern(intent="UNKNOWN_INTENT", evidence_mask=15)
|
|
36
|
+
assert result["decision"] == "DENIED"
|
|
37
|
+
assert result["reason_code"] == "DENY_FAST"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_missing_intent_is_denied() -> None:
|
|
41
|
+
result = govern(intent="", evidence_mask=1)
|
|
42
|
+
assert result["decision"] == "DENIED"
|
|
43
|
+
assert result["reason_code"] == "E_INTENT_REQUIRED"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_policy_version_mismatch_is_denied() -> None:
|
|
47
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1, policy_version="v99")
|
|
48
|
+
assert result["decision"] == "DENIED"
|
|
49
|
+
assert result["reason_code"] == "E_POLICY_VERSION_MISMATCH"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_unknown_authority_scope_is_denied_fast() -> None:
|
|
53
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1, authority_scope="unknown")
|
|
54
|
+
assert result["decision"] == "DENIED"
|
|
55
|
+
assert result["reason_code"] == "DENY_FAST"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_symbolic_evidence_list_is_supported() -> None:
|
|
59
|
+
result = govern(intent="DATA_EXPORT", evidence_mask=["AUTHN", "AUTHZ"])
|
|
60
|
+
assert result["decision"] == "ALLOWED"
|
|
61
|
+
assert result["reason_code"] == "POLICY_SATISFIED"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
7
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
8
|
+
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
|
|
9
|
+
|
|
10
|
+
from vella import govern
|
|
11
|
+
|
|
12
|
+
SIGNING_EXCLUDED = {
|
|
13
|
+
"envelope_hash",
|
|
14
|
+
"signature_alg",
|
|
15
|
+
"signature",
|
|
16
|
+
"sha256_bundle",
|
|
17
|
+
"key_id",
|
|
18
|
+
"kind",
|
|
19
|
+
"exported_at",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _read_fixture(name: str) -> str:
|
|
24
|
+
return (Path(__file__).parent / "fixtures" / name).read_text(encoding="utf-8")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_proof_bundle_is_generated() -> None:
|
|
28
|
+
private_key = _read_fixture("test-signing-private.pem")
|
|
29
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1, proof_signing_key=private_key)
|
|
30
|
+
proof_bundle = result.get("proof_bundle")
|
|
31
|
+
assert isinstance(proof_bundle, dict)
|
|
32
|
+
assert proof_bundle["kind"] == "vella_proof_bundle_v1"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_envelope_hash_verifies() -> None:
|
|
36
|
+
private_key = _read_fixture("test-signing-private.pem")
|
|
37
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1, proof_signing_key=private_key)
|
|
38
|
+
proof_bundle = result["proof_bundle"]
|
|
39
|
+
assert isinstance(proof_bundle, dict)
|
|
40
|
+
|
|
41
|
+
envelope = {k: v for k, v in proof_bundle.items() if k not in SIGNING_EXCLUDED}
|
|
42
|
+
canonical_envelope = json.dumps(envelope, sort_keys=True, separators=(",", ":"))
|
|
43
|
+
expected_hash = f"sha256:{hashlib.sha256(canonical_envelope.encode('utf-8')).hexdigest()}"
|
|
44
|
+
assert proof_bundle["envelope_hash"] == expected_hash
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_signature_verifies() -> None:
|
|
48
|
+
private_key = _read_fixture("test-signing-private.pem")
|
|
49
|
+
public_key_pem = _read_fixture("test-signing-public.pem")
|
|
50
|
+
|
|
51
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1, proof_signing_key=private_key)
|
|
52
|
+
proof_bundle = result["proof_bundle"]
|
|
53
|
+
assert isinstance(proof_bundle, dict)
|
|
54
|
+
|
|
55
|
+
public_key = serialization.load_pem_public_key(public_key_pem.encode("utf-8"))
|
|
56
|
+
assert isinstance(public_key, ec.EllipticCurvePublicKey)
|
|
57
|
+
|
|
58
|
+
signature_bytes = base64.b64decode(str(proof_bundle["signature"]))
|
|
59
|
+
r_value = int.from_bytes(signature_bytes[:32], "big")
|
|
60
|
+
s_value = int.from_bytes(signature_bytes[32:], "big")
|
|
61
|
+
der_signature = encode_dss_signature(r_value, s_value)
|
|
62
|
+
|
|
63
|
+
public_key.verify(
|
|
64
|
+
der_signature,
|
|
65
|
+
str(proof_bundle["envelope_hash"]).encode("utf-8"),
|
|
66
|
+
ec.ECDSA(hashes.SHA256()),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_bundle_hash_verifies() -> None:
|
|
71
|
+
private_key = _read_fixture("test-signing-private.pem")
|
|
72
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1, proof_signing_key=private_key)
|
|
73
|
+
proof_bundle = result["proof_bundle"]
|
|
74
|
+
assert isinstance(proof_bundle, dict)
|
|
75
|
+
|
|
76
|
+
envelope = {k: v for k, v in proof_bundle.items() if k not in SIGNING_EXCLUDED}
|
|
77
|
+
bundle_for_hash = {
|
|
78
|
+
**envelope,
|
|
79
|
+
"envelope_hash": proof_bundle["envelope_hash"],
|
|
80
|
+
"signature_alg": proof_bundle["signature_alg"],
|
|
81
|
+
"signature": proof_bundle["signature"],
|
|
82
|
+
}
|
|
83
|
+
canonical_bundle = json.dumps(bundle_for_hash, sort_keys=True, separators=(",", ":"))
|
|
84
|
+
expected_hash = f"sha256:{hashlib.sha256(canonical_bundle.encode('utf-8')).hexdigest()}"
|
|
85
|
+
assert proof_bundle["sha256_bundle"] == expected_hash
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_proof_bundle_absent_when_no_signing_key() -> None:
|
|
89
|
+
result = govern(intent="EXECUTE_CHANGE", evidence_mask=1)
|
|
90
|
+
assert "proof_bundle" not in result
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""VELLA SDK — MIT License — Copyright (c) 2026 Vella Cognitive, LLC"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from .evaluator import create_evaluator
|
|
8
|
+
from .policy import DEFAULT_POLICY
|
|
9
|
+
from .proof import build_envelope, sign_bundle
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.1"
|
|
12
|
+
|
|
13
|
+
_EVALUATOR = create_evaluator(DEFAULT_POLICY)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def govern(
|
|
17
|
+
intent: str | None,
|
|
18
|
+
evidence_mask: object,
|
|
19
|
+
authority_scope: str | None = None,
|
|
20
|
+
policy_version: str | None = None,
|
|
21
|
+
proof_signing_key: str | bytes | None = None,
|
|
22
|
+
) -> dict[str, object]:
|
|
23
|
+
start = time.monotonic_ns()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
result = _EVALUATOR.evaluate(
|
|
27
|
+
{
|
|
28
|
+
"intent_id": intent,
|
|
29
|
+
"evidence_mask": evidence_mask,
|
|
30
|
+
"authority_scope_id": authority_scope,
|
|
31
|
+
"policy_version": policy_version,
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
latency_us = (time.monotonic_ns() - start) // 1000
|
|
35
|
+
output: dict[str, object] = {
|
|
36
|
+
"decision": result["decision"],
|
|
37
|
+
"reason_code": result["reason_code"],
|
|
38
|
+
"latency_us": latency_us,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if proof_signing_key is not None:
|
|
42
|
+
try:
|
|
43
|
+
envelope = build_envelope(
|
|
44
|
+
{
|
|
45
|
+
"intent_id": intent,
|
|
46
|
+
"evidence_mask": evidence_mask,
|
|
47
|
+
"authority_scope_id": authority_scope,
|
|
48
|
+
"policy_version": policy_version,
|
|
49
|
+
},
|
|
50
|
+
result,
|
|
51
|
+
{
|
|
52
|
+
"policyVersion": _EVALUATOR.policy_version,
|
|
53
|
+
"authorityScope": authority_scope,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
output["proof_bundle"] = sign_bundle(envelope, proof_signing_key)
|
|
57
|
+
except Exception as proof_error: # noqa: BLE001
|
|
58
|
+
output["proof_bundle"] = None
|
|
59
|
+
output["proof_error"] = str(proof_error)
|
|
60
|
+
|
|
61
|
+
return output
|
|
62
|
+
except Exception: # noqa: BLE001
|
|
63
|
+
return {
|
|
64
|
+
"decision": "DENIED",
|
|
65
|
+
"reason_code": "E_EVALUATOR_INTERNAL",
|
|
66
|
+
"latency_us": 0,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = ["govern", "DEFAULT_POLICY", "__version__"]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""VELLA SDK — MIT License — Copyright (c) 2026 Vella Cognitive, LLC"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Mapping
|
|
8
|
+
|
|
9
|
+
from .policy import DEFAULT_POLICY
|
|
10
|
+
|
|
11
|
+
Decision = dict[str, str]
|
|
12
|
+
|
|
13
|
+
DECISION_ALLOWED: Decision = {"decision": "ALLOWED", "reason_code": "POLICY_SATISFIED"}
|
|
14
|
+
DECISION_DENIED_FAST: Decision = {"decision": "DENIED", "reason_code": "DENY_FAST"}
|
|
15
|
+
DECISION_DENIED_INTENT_REQUIRED: Decision = {
|
|
16
|
+
"decision": "DENIED",
|
|
17
|
+
"reason_code": "E_INTENT_REQUIRED",
|
|
18
|
+
}
|
|
19
|
+
DECISION_DENIED_POLICY_VERSION: Decision = {
|
|
20
|
+
"decision": "DENIED",
|
|
21
|
+
"reason_code": "E_POLICY_VERSION_MISMATCH",
|
|
22
|
+
}
|
|
23
|
+
DECISION_DENIED_EVIDENCE: Decision = {
|
|
24
|
+
"decision": "DENIED",
|
|
25
|
+
"reason_code": "E_EVIDENCE_MISSING",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class CompiledScope:
|
|
31
|
+
allow_unknown_intents: bool
|
|
32
|
+
default_required_mask: int
|
|
33
|
+
intent_rules: dict[str, int]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class CompiledPolicy:
|
|
38
|
+
policy_version: str
|
|
39
|
+
default_scope: str
|
|
40
|
+
evidence_bits: dict[str, int]
|
|
41
|
+
scopes: dict[str, CompiledScope]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _normalize_id(value: object | None) -> str:
|
|
45
|
+
if value is None:
|
|
46
|
+
return ""
|
|
47
|
+
return str(value).strip().upper()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_unsigned_int_strict(text: str) -> int:
|
|
51
|
+
if len(text) == 0:
|
|
52
|
+
return -1
|
|
53
|
+
value = 0
|
|
54
|
+
for ch in text:
|
|
55
|
+
code = ord(ch)
|
|
56
|
+
if code < 48 or code > 57:
|
|
57
|
+
return -1
|
|
58
|
+
value = (value * 10) + (code - 48)
|
|
59
|
+
if not math.isfinite(value):
|
|
60
|
+
return -1
|
|
61
|
+
return value & 0xFFFFFFFF
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def to_evidence_mask(value: object, evidence_bits: Mapping[str, int]) -> int:
|
|
65
|
+
if isinstance(value, bool):
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
if isinstance(value, int):
|
|
69
|
+
return value & 0xFFFFFFFF
|
|
70
|
+
|
|
71
|
+
if isinstance(value, float) and math.isfinite(value):
|
|
72
|
+
return int(value) & 0xFFFFFFFF
|
|
73
|
+
|
|
74
|
+
if isinstance(value, str):
|
|
75
|
+
trimmed = value.strip()
|
|
76
|
+
numeric = _parse_unsigned_int_strict(trimmed)
|
|
77
|
+
if numeric >= 0:
|
|
78
|
+
return numeric & 0xFFFFFFFF
|
|
79
|
+
bit = evidence_bits.get(_normalize_id(trimmed))
|
|
80
|
+
return (bit & 0xFFFFFFFF) if bit is not None else 0
|
|
81
|
+
|
|
82
|
+
if isinstance(value, list):
|
|
83
|
+
mask = 0
|
|
84
|
+
for item in value:
|
|
85
|
+
bit = evidence_bits.get(_normalize_id(item))
|
|
86
|
+
if bit is not None:
|
|
87
|
+
mask |= bit
|
|
88
|
+
return mask & 0xFFFFFFFF
|
|
89
|
+
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _to_int(value: object, default: int = 0) -> int:
|
|
94
|
+
if isinstance(value, bool):
|
|
95
|
+
return default
|
|
96
|
+
if isinstance(value, int):
|
|
97
|
+
return value
|
|
98
|
+
if isinstance(value, float) and math.isfinite(value):
|
|
99
|
+
return int(value)
|
|
100
|
+
if isinstance(value, str):
|
|
101
|
+
parsed = _parse_unsigned_int_strict(value.strip())
|
|
102
|
+
if parsed >= 0:
|
|
103
|
+
return parsed
|
|
104
|
+
return default
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def compile_policy(policy_input: Mapping[str, object] | None = None) -> CompiledPolicy:
|
|
108
|
+
policy = policy_input if policy_input is not None else DEFAULT_POLICY
|
|
109
|
+
|
|
110
|
+
raw_evidence = policy.get("evidenceBits")
|
|
111
|
+
evidence_bits: dict[str, int] = {}
|
|
112
|
+
if isinstance(raw_evidence, Mapping):
|
|
113
|
+
for key, value in raw_evidence.items():
|
|
114
|
+
if not isinstance(key, str):
|
|
115
|
+
continue
|
|
116
|
+
evidence_bits[key] = _to_int(value) & 0xFFFFFFFF
|
|
117
|
+
|
|
118
|
+
raw_scopes = policy.get("scopes")
|
|
119
|
+
scopes: dict[str, CompiledScope] = {}
|
|
120
|
+
if isinstance(raw_scopes, Mapping):
|
|
121
|
+
for scope_name_obj, scope_cfg_obj in raw_scopes.items():
|
|
122
|
+
if not isinstance(scope_name_obj, str):
|
|
123
|
+
continue
|
|
124
|
+
scope_name = scope_name_obj
|
|
125
|
+
scope_cfg = scope_cfg_obj if isinstance(scope_cfg_obj, Mapping) else {}
|
|
126
|
+
|
|
127
|
+
raw_intents = scope_cfg.get("intents")
|
|
128
|
+
intent_rules: dict[str, int] = {}
|
|
129
|
+
if isinstance(raw_intents, Mapping):
|
|
130
|
+
for intent_name_obj, intent_mask_obj in raw_intents.items():
|
|
131
|
+
if not isinstance(intent_name_obj, str):
|
|
132
|
+
continue
|
|
133
|
+
intent_name = _normalize_id(intent_name_obj)
|
|
134
|
+
if not intent_name:
|
|
135
|
+
continue
|
|
136
|
+
intent_rules[intent_name] = _to_int(intent_mask_obj) & 0xFFFFFFFF
|
|
137
|
+
|
|
138
|
+
allow_unknown = bool(scope_cfg.get("allowUnknownIntents") is True)
|
|
139
|
+
default_required = _to_int(scope_cfg.get("defaultRequiredMask"), 0) & 0xFFFFFFFF
|
|
140
|
+
scopes[scope_name] = CompiledScope(
|
|
141
|
+
allow_unknown_intents=allow_unknown,
|
|
142
|
+
default_required_mask=default_required,
|
|
143
|
+
intent_rules=intent_rules,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
default_scope_obj = policy.get("defaultScope")
|
|
147
|
+
default_scope = str(default_scope_obj) if default_scope_obj is not None else "sdk_v1_default"
|
|
148
|
+
|
|
149
|
+
policy_version_obj = policy.get("policyVersion")
|
|
150
|
+
policy_version = str(policy_version_obj) if policy_version_obj is not None else "min-v1"
|
|
151
|
+
|
|
152
|
+
return CompiledPolicy(
|
|
153
|
+
policy_version=policy_version,
|
|
154
|
+
default_scope=default_scope,
|
|
155
|
+
evidence_bits=evidence_bits,
|
|
156
|
+
scopes=scopes,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Evaluator:
|
|
161
|
+
def __init__(self, compiled_policy: CompiledPolicy) -> None:
|
|
162
|
+
self._compiled = compiled_policy
|
|
163
|
+
self._default_scope = compiled_policy.scopes.get(compiled_policy.default_scope)
|
|
164
|
+
self.policy_version = compiled_policy.policy_version
|
|
165
|
+
|
|
166
|
+
def evaluate(self, input_dict: Mapping[str, object] | None) -> Decision:
|
|
167
|
+
if input_dict is None:
|
|
168
|
+
return DECISION_DENIED_INTENT_REQUIRED
|
|
169
|
+
|
|
170
|
+
raw_intent = (
|
|
171
|
+
input_dict.get("intent_id")
|
|
172
|
+
or input_dict.get("intent")
|
|
173
|
+
or input_dict.get("action")
|
|
174
|
+
)
|
|
175
|
+
intent_id = _normalize_id(raw_intent)
|
|
176
|
+
if not intent_id:
|
|
177
|
+
return DECISION_DENIED_INTENT_REQUIRED
|
|
178
|
+
|
|
179
|
+
authority_scope_id = input_dict.get("authority_scope_id")
|
|
180
|
+
if authority_scope_id is None or authority_scope_id == "":
|
|
181
|
+
scope = self._default_scope
|
|
182
|
+
if scope is None:
|
|
183
|
+
return DECISION_DENIED_FAST
|
|
184
|
+
else:
|
|
185
|
+
scope = self._compiled.scopes.get(str(authority_scope_id))
|
|
186
|
+
if scope is None:
|
|
187
|
+
return DECISION_DENIED_FAST
|
|
188
|
+
|
|
189
|
+
required_mask = scope.intent_rules.get(intent_id)
|
|
190
|
+
if required_mask is None:
|
|
191
|
+
if not scope.allow_unknown_intents:
|
|
192
|
+
return DECISION_DENIED_FAST
|
|
193
|
+
required_mask = scope.default_required_mask
|
|
194
|
+
|
|
195
|
+
requested_policy_version = input_dict.get("policy_version")
|
|
196
|
+
if (
|
|
197
|
+
requested_policy_version is not None
|
|
198
|
+
and requested_policy_version != ""
|
|
199
|
+
and str(requested_policy_version) != self._compiled.policy_version
|
|
200
|
+
):
|
|
201
|
+
return DECISION_DENIED_POLICY_VERSION
|
|
202
|
+
|
|
203
|
+
evidence_mask = to_evidence_mask(input_dict.get("evidence_mask"), self._compiled.evidence_bits)
|
|
204
|
+
if (evidence_mask & required_mask) != required_mask:
|
|
205
|
+
return DECISION_DENIED_EVIDENCE
|
|
206
|
+
|
|
207
|
+
return DECISION_ALLOWED
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def create_evaluator(policy_input: Mapping[str, object] | None = None) -> Evaluator:
|
|
211
|
+
return Evaluator(compile_policy(policy_input))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""VELLA SDK — MIT License — Copyright (c) 2026 Vella Cognitive, LLC"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
DEFAULT_POLICY: Final[dict[str, object]] = {
|
|
8
|
+
"policyVersion": "min-v1",
|
|
9
|
+
"defaultScope": "sdk_v1_default",
|
|
10
|
+
"evidenceBits": {
|
|
11
|
+
"AUTHN": 1,
|
|
12
|
+
"AUTHZ": 2,
|
|
13
|
+
"FRESHNESS": 4,
|
|
14
|
+
"ATTESTATION": 8,
|
|
15
|
+
},
|
|
16
|
+
"scopes": {
|
|
17
|
+
"sdk_v1_default": {
|
|
18
|
+
"allowUnknownIntents": False,
|
|
19
|
+
"defaultRequiredMask": 1,
|
|
20
|
+
"intents": {
|
|
21
|
+
"EXECUTE_CHANGE": 1,
|
|
22
|
+
"ESCALATE_PRIVILEGE": 1 | 2,
|
|
23
|
+
"DATA_EXPORT": 1 | 2,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""VELLA SDK — MIT License — Copyright (c) 2026 Vella Cognitive, LLC"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import struct
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
|
|
13
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
14
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
15
|
+
from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _uuidv7() -> str:
|
|
19
|
+
buffer = bytearray(os.urandom(16))
|
|
20
|
+
now_ms = int(time.time() * 1000)
|
|
21
|
+
timestamp_bytes = struct.pack(">Q", now_ms)
|
|
22
|
+
buffer[0:6] = timestamp_bytes[2:8]
|
|
23
|
+
buffer[6] = (buffer[6] & 0x0F) | 0x70
|
|
24
|
+
buffer[8] = (buffer[8] & 0x3F) | 0x80
|
|
25
|
+
hexed = buffer.hex()
|
|
26
|
+
return f"{hexed[:8]}-{hexed[8:12]}-{hexed[12:16]}-{hexed[16:20]}-{hexed[20:]}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_ec_private_key(signing_key_pem: str | bytes) -> ec.EllipticCurvePrivateKey:
|
|
30
|
+
key_bytes = signing_key_pem.encode("utf-8") if isinstance(signing_key_pem, str) else signing_key_pem
|
|
31
|
+
private_key = serialization.load_pem_private_key(key_bytes, password=None)
|
|
32
|
+
if not isinstance(private_key, ec.EllipticCurvePrivateKey):
|
|
33
|
+
raise ValueError("signing key must be an EC private key")
|
|
34
|
+
return private_key
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_envelope(
|
|
38
|
+
intent_input: dict[str, object] | None,
|
|
39
|
+
decision_result: dict[str, str] | None,
|
|
40
|
+
context: dict[str, object] | None,
|
|
41
|
+
) -> dict[str, object]:
|
|
42
|
+
request = intent_input if isinstance(intent_input, dict) else {}
|
|
43
|
+
decision = decision_result if isinstance(decision_result, dict) else {}
|
|
44
|
+
ctx = context if isinstance(context, dict) else {}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
"envelope_id": f"env_{_uuidv7()}",
|
|
48
|
+
"intent": request.get("intent_id") or request.get("intent") or request.get("action"),
|
|
49
|
+
"proposed": request.get("proposed"),
|
|
50
|
+
"authority_scope": ctx.get("authorityScope") or request.get("authority_scope_id") or "sdk_v1_default",
|
|
51
|
+
"evidence_mask": request.get("evidence_mask", 0),
|
|
52
|
+
"decision": decision.get("decision", "DENIED"),
|
|
53
|
+
"reason_code": decision.get("reason_code", "E_EVALUATOR_INTERNAL"),
|
|
54
|
+
"policy_version": ctx.get("policyVersion", "min-v1"),
|
|
55
|
+
"build_hash": ctx.get("buildHash", "unset"),
|
|
56
|
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
57
|
+
"ok": decision.get("decision") in ("ALLOWED", "DENIED"),
|
|
58
|
+
"external_effects": False,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def derive_key_id(signing_key_pem: str | bytes) -> str:
|
|
63
|
+
private_key = _load_ec_private_key(signing_key_pem)
|
|
64
|
+
public_der = private_key.public_key().public_bytes(
|
|
65
|
+
encoding=serialization.Encoding.DER,
|
|
66
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
67
|
+
)
|
|
68
|
+
return f"key_{hashlib.sha256(public_der).hexdigest()[:16]}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def sign_bundle(envelope: dict[str, object], signing_key_pem: str | bytes) -> dict[str, object]:
|
|
72
|
+
canonical_envelope = json.dumps(envelope, sort_keys=True, separators=(",", ":"))
|
|
73
|
+
envelope_hash = f"sha256:{hashlib.sha256(canonical_envelope.encode('utf-8')).hexdigest()}"
|
|
74
|
+
|
|
75
|
+
private_key = _load_ec_private_key(signing_key_pem)
|
|
76
|
+
der_signature = private_key.sign(envelope_hash.encode("utf-8"), ec.ECDSA(hashes.SHA256()))
|
|
77
|
+
r_value, s_value = asym_utils.decode_dss_signature(der_signature)
|
|
78
|
+
signature_bytes = r_value.to_bytes(32, "big") + s_value.to_bytes(32, "big")
|
|
79
|
+
signature = base64.b64encode(signature_bytes).decode("utf-8")
|
|
80
|
+
|
|
81
|
+
bundle_for_hash = {
|
|
82
|
+
**envelope,
|
|
83
|
+
"envelope_hash": envelope_hash,
|
|
84
|
+
"signature_alg": "ecdsa-p256-sha256",
|
|
85
|
+
"signature": signature,
|
|
86
|
+
}
|
|
87
|
+
canonical_bundle = json.dumps(bundle_for_hash, sort_keys=True, separators=(",", ":"))
|
|
88
|
+
sha256_bundle = f"sha256:{hashlib.sha256(canonical_bundle.encode('utf-8')).hexdigest()}"
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
**envelope,
|
|
92
|
+
"envelope_hash": envelope_hash,
|
|
93
|
+
"signature_alg": "ecdsa-p256-sha256",
|
|
94
|
+
"signature": signature,
|
|
95
|
+
"sha256_bundle": sha256_bundle,
|
|
96
|
+
"key_id": derive_key_id(signing_key_pem),
|
|
97
|
+
"kind": "vella_proof_bundle_v1",
|
|
98
|
+
"exported_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
99
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vella-sdk
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: VELLA — deterministic pre-execution governance substrate. SDK for producing signed decision records.
|
|
5
|
+
Author-email: "Vella Cognitive, LLC" <agent@vellacognitive.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://vellacognitive.com/research/an-inspectable-substrate-for-ai-governance
|
|
8
|
+
Project-URL: Repository, https://github.com/vellacognitive/vella-substrate
|
|
9
|
+
Project-URL: Issues, https://github.com/vellacognitive/vella-substrate/issues
|
|
10
|
+
Keywords: vella,governance,ai-safety,pre-execution,policy,audit,proof-bundle
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: cryptography>=41.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: ruff; extra == "dev"
|
|
23
|
+
Requires-Dist: mypy; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# vella-sdk
|
|
27
|
+
|
|
28
|
+
Python SDK for deterministic pre-execution adjudication and signed proof-bundle generation.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install vella-sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## From source (development)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python3 -m venv .venv
|
|
40
|
+
. .venv/bin/activate
|
|
41
|
+
python -m pip install --upgrade pip
|
|
42
|
+
pip install -e ".[dev]"
|
|
43
|
+
pytest
|
|
44
|
+
ruff check .
|
|
45
|
+
mypy --strict vella/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Basic usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from vella import govern
|
|
52
|
+
|
|
53
|
+
result = govern(
|
|
54
|
+
intent="EXECUTE_CHANGE",
|
|
55
|
+
evidence_mask=1,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(result["decision"]) # ALLOWED | DENIED
|
|
59
|
+
print(result["reason_code"]) # reason code string
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## With proof bundle
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from vella import govern
|
|
66
|
+
|
|
67
|
+
signing_key = open("./proof-signing.pem", "r", encoding="utf-8").read()
|
|
68
|
+
|
|
69
|
+
result = govern(
|
|
70
|
+
intent="EXECUTE_CHANGE",
|
|
71
|
+
evidence_mask=1,
|
|
72
|
+
proof_signing_key=signing_key,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
print(result["proof_bundle"]["kind"]) # vella_proof_bundle_v1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## When to use this SDK
|
|
79
|
+
|
|
80
|
+
This SDK runs VELLA in-process inside your Python application. It is the right choice for agent tool-call hooks, CI/CD gating, edge compute, research notebooks, batch processing, and any context where microsecond-latency adjudication is needed without a separate service.
|
|
81
|
+
|
|
82
|
+
For enterprise service mesh, polyglot environments (Go, Java, .NET), Kubernetes admission control, or multi-tenant deployments, a different VELLA component (the runtime service or sidecar adapter) is the better fit. See the full [deployment scope](../../DEPLOYMENT.md) in the repository root.
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
- `govern(intent, evidence_mask, authority_scope=None, policy_version=None, proof_signing_key=None)`
|
|
87
|
+
- Returns a dict with `decision`, `reason_code`, `latency_us`, and optional `proof_bundle`/`proof_error`
|
|
88
|
+
|
|
89
|
+
## Reference docs
|
|
90
|
+
|
|
91
|
+
See the root repository docs for full protocol details:
|
|
92
|
+
- `spec/icd.md`
|
|
93
|
+
- `spec/schemas/proof.json`
|
|
94
|
+
- `verify/`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
tests/test_evaluator.py
|
|
4
|
+
tests/test_proof.py
|
|
5
|
+
vella/__init__.py
|
|
6
|
+
vella/evaluator.py
|
|
7
|
+
vella/policy.py
|
|
8
|
+
vella/proof.py
|
|
9
|
+
vella_sdk.egg-info/PKG-INFO
|
|
10
|
+
vella_sdk.egg-info/SOURCES.txt
|
|
11
|
+
vella_sdk.egg-info/dependency_links.txt
|
|
12
|
+
vella_sdk.egg-info/requires.txt
|
|
13
|
+
vella_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vella
|