vaultkit 0.1.0__py3-none-any.whl
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.
- vaultkit/__init__.py +85 -0
- vaultkit/client.py +441 -0
- vaultkit/core/__init__.py +1 -0
- vaultkit/core/http.py +190 -0
- vaultkit/core/polling.py +140 -0
- vaultkit/errors/__init__.py +36 -0
- vaultkit/errors/base.py +30 -0
- vaultkit/errors/exceptions.py +211 -0
- vaultkit/models/__init__.py +11 -0
- vaultkit/models/dataset_info.py +25 -0
- vaultkit/models/dataset_schema.py +75 -0
- vaultkit/models/fetch_result.py +53 -0
- vaultkit/models/query_result.py +73 -0
- vaultkit/tools/__init__.py +5 -0
- vaultkit/tools/adapters/__init__.py +12 -0
- vaultkit/tools/adapters/anthropic.py +17 -0
- vaultkit/tools/adapters/openai.py +23 -0
- vaultkit/tools/builder.py +128 -0
- vaultkit/tools/definitions.py +177 -0
- vaultkit/tools/executor.py +199 -0
- vaultkit/tools/schemas.py +39 -0
- vaultkit/utils/__init__.py +1 -0
- vaultkit/utils/retry.py +81 -0
- vaultkit/utils/validation.py +87 -0
- vaultkit-0.1.0.dist-info/METADATA +207 -0
- vaultkit-0.1.0.dist-info/RECORD +28 -0
- vaultkit-0.1.0.dist-info/WHEEL +5 -0
- vaultkit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# vaultkit/tools/executor.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from vaultkit.errors.exceptions import (
|
|
7
|
+
ApprovalRequiredError,
|
|
8
|
+
DeniedError,
|
|
9
|
+
GrantExpiredError,
|
|
10
|
+
GrantRevokedError,
|
|
11
|
+
PolicyBundleRevokedError,
|
|
12
|
+
QueuedError,
|
|
13
|
+
ValidationError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from vaultkit.client import VaultKitClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolExecutor:
|
|
21
|
+
"""
|
|
22
|
+
Provider-agnostic dispatcher for tool calls -> VaultKitClient methods.
|
|
23
|
+
|
|
24
|
+
Any agent runtime can use this as long as it can provide:
|
|
25
|
+
- tool_name: str
|
|
26
|
+
- tool_args: dict
|
|
27
|
+
|
|
28
|
+
Example (pseudo):
|
|
29
|
+
name = tool_call["name"]
|
|
30
|
+
args = tool_call["args"]
|
|
31
|
+
result = executor.execute(name, args)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
client: "VaultKitClient",
|
|
37
|
+
*,
|
|
38
|
+
default_purpose: Optional[str] = None,
|
|
39
|
+
default_requester_region: Optional[str] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
self._client = client
|
|
42
|
+
self._default_purpose = default_purpose
|
|
43
|
+
self._default_requester_region = default_requester_region
|
|
44
|
+
|
|
45
|
+
def execute(self, tool_name: str, tool_args: Dict[str, Any]) -> Dict[str, Any]:
|
|
46
|
+
"""Dispatch a tool call. Always returns a dict — never raises."""
|
|
47
|
+
try:
|
|
48
|
+
if tool_name == "vaultkit_discover":
|
|
49
|
+
return self._execute_discover(tool_args)
|
|
50
|
+
if tool_name == "vaultkit_query":
|
|
51
|
+
return self._execute_query(tool_args)
|
|
52
|
+
if tool_name == "vaultkit_check_approval":
|
|
53
|
+
return self._execute_check_approval(tool_args)
|
|
54
|
+
|
|
55
|
+
return self._error(
|
|
56
|
+
f"Unknown tool '{tool_name}'. "
|
|
57
|
+
"Available: vaultkit_discover, vaultkit_query, vaultkit_check_approval.",
|
|
58
|
+
code="unknown_tool",
|
|
59
|
+
)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return self._translate_error(e)
|
|
62
|
+
|
|
63
|
+
# dispatch
|
|
64
|
+
|
|
65
|
+
def _execute_discover(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
66
|
+
environment = args.get("environment", "production")
|
|
67
|
+
requester_region = args.get("requester_region")
|
|
68
|
+
dataset_region = args.get("dataset_region")
|
|
69
|
+
|
|
70
|
+
datasets = self._client.datasets(
|
|
71
|
+
environment=environment,
|
|
72
|
+
requester_region=requester_region,
|
|
73
|
+
dataset_region=dataset_region,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
visibility_map = {
|
|
77
|
+
"allow": "accessible",
|
|
78
|
+
"require_approval": "requires approval",
|
|
79
|
+
"deny": "not accessible",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
"status": "ok",
|
|
84
|
+
"datasets": [
|
|
85
|
+
{
|
|
86
|
+
"name": d.dataset,
|
|
87
|
+
"datasource": d.datasource,
|
|
88
|
+
"visibility": visibility_map.get(getattr(d, "visibility", None), getattr(d, "visibility", None)),
|
|
89
|
+
"field_count": len(getattr(d, "fields", []) or []),
|
|
90
|
+
}
|
|
91
|
+
for d in datasets
|
|
92
|
+
],
|
|
93
|
+
"count": len(datasets),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def _execute_query(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
97
|
+
dataset = args.get("dataset")
|
|
98
|
+
if not dataset:
|
|
99
|
+
return self._error("'dataset' is required for vaultkit_query.", code="validation_error")
|
|
100
|
+
|
|
101
|
+
purpose = args.get("purpose") or self._default_purpose
|
|
102
|
+
|
|
103
|
+
result = self._client.execute(
|
|
104
|
+
dataset=dataset,
|
|
105
|
+
fields=args.get("fields"),
|
|
106
|
+
filters=args.get("filters"),
|
|
107
|
+
limit=args.get("limit"),
|
|
108
|
+
purpose=purpose,
|
|
109
|
+
requester_region=args.get("requester_region") or self._default_requester_region,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
response: Dict[str, Any] = {
|
|
113
|
+
"status": "ok",
|
|
114
|
+
"dataset": dataset,
|
|
115
|
+
"row_count": result.row_count,
|
|
116
|
+
"data": result.data,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if result.masked_fields:
|
|
120
|
+
response["note"] = f"Fields {result.masked_fields} were masked per data policy."
|
|
121
|
+
|
|
122
|
+
return response
|
|
123
|
+
|
|
124
|
+
def _execute_check_approval(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
125
|
+
request_id = args.get("request_id")
|
|
126
|
+
if not request_id:
|
|
127
|
+
return self._error("'request_id' is required for vaultkit_check_approval.", code="validation_error")
|
|
128
|
+
|
|
129
|
+
poll_result = self._client.poll_request(request_id=request_id)
|
|
130
|
+
|
|
131
|
+
if poll_result.is_granted and poll_result.grant_ref:
|
|
132
|
+
fetch = self._client.fetch(grant_ref=poll_result.grant_ref)
|
|
133
|
+
return {
|
|
134
|
+
"status": "approved",
|
|
135
|
+
"row_count": fetch.row_count,
|
|
136
|
+
"data": fetch.data,
|
|
137
|
+
"masked_fields": fetch.masked_fields,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if poll_result.is_denied:
|
|
141
|
+
return {"status": "denied", "reason": poll_result.reason or "Approval was denied."}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
"status": "pending",
|
|
145
|
+
"message": "Approval is still pending. Try again shortly.",
|
|
146
|
+
"request_id": request_id,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# error translation
|
|
150
|
+
|
|
151
|
+
def _translate_error(self, exc: Exception) -> Dict[str, Any]:
|
|
152
|
+
msg = str(exc)
|
|
153
|
+
error_code = getattr(exc, "error_code", "error")
|
|
154
|
+
|
|
155
|
+
if isinstance(exc, DeniedError):
|
|
156
|
+
policy_id = getattr(exc, "policy_id", None)
|
|
157
|
+
extra = f" (policy_id={policy_id})" if policy_id else ""
|
|
158
|
+
return self._error(
|
|
159
|
+
f"Access denied by VaultKit policy{extra}. Reason: {msg}. "
|
|
160
|
+
"Do not retry — this denial is deterministic.",
|
|
161
|
+
code="denied",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if isinstance(exc, ApprovalRequiredError):
|
|
165
|
+
return {
|
|
166
|
+
"status": "pending_approval",
|
|
167
|
+
"message": (
|
|
168
|
+
"This request requires human approval. "
|
|
169
|
+
f"Use vaultkit_check_approval with request_id='{exc.request_id}' to check status."
|
|
170
|
+
),
|
|
171
|
+
"request_id": exc.request_id,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if isinstance(exc, (GrantExpiredError, GrantRevokedError)):
|
|
175
|
+
return self._error(
|
|
176
|
+
f"{msg} Re-submit your query to get a fresh grant.",
|
|
177
|
+
code=error_code,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if isinstance(exc, PolicyBundleRevokedError):
|
|
181
|
+
return self._error(
|
|
182
|
+
"The VaultKit policy bundle has been revoked. "
|
|
183
|
+
"No data access is possible. Escalate to your VaultKit administrator.",
|
|
184
|
+
code="bundle_revoked",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if isinstance(exc, ValidationError):
|
|
188
|
+
return self._error(f"Invalid query: {msg}", code="validation_error")
|
|
189
|
+
|
|
190
|
+
if isinstance(exc, QueuedError):
|
|
191
|
+
return {"status": "queued", "message": msg, "request_id": exc.request_id}
|
|
192
|
+
|
|
193
|
+
return self._error(f"Unexpected error: {type(exc).__name__}: {msg}", code=error_code)
|
|
194
|
+
|
|
195
|
+
# helpers
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def _error(message: str, *, code: str = "error") -> Dict[str, Any]:
|
|
199
|
+
return {"status": code, "error": message}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backward-compatible module.
|
|
3
|
+
|
|
4
|
+
If you previously imported:
|
|
5
|
+
from vaultkit.tools.schemas import query_tool_schema
|
|
6
|
+
|
|
7
|
+
You can keep doing so, but these now generate *OpenAI* tools by default.
|
|
8
|
+
|
|
9
|
+
Prefer the new canonical definitions via:
|
|
10
|
+
from vaultkit.tools.definitions import query_tool_def
|
|
11
|
+
ToolBuilder(...).build(provider=ToolProvider.ANTHROPIC)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from .adapters.openai import to_openai_tool
|
|
19
|
+
from .definitions import (
|
|
20
|
+
check_approval_tool_def,
|
|
21
|
+
discover_tool_def,
|
|
22
|
+
query_tool_def,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def discover_tool_schema(*, dataset_names: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
27
|
+
return to_openai_tool(discover_tool_def(dataset_names=dataset_names))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def query_tool_schema(
|
|
31
|
+
*,
|
|
32
|
+
dataset_names: Optional[List[str]] = None,
|
|
33
|
+
schema_hints: Optional[Dict[str, List[str]]] = None,
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
return to_openai_tool(query_tool_def(dataset_names=dataset_names, schema_hints=schema_hints))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_approval_tool_schema() -> Dict[str, Any]:
|
|
39
|
+
return to_openai_tool(check_approval_tool_def())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|
vaultkit/utils/retry.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import random
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Callable, TypeVar, Optional
|
|
8
|
+
|
|
9
|
+
from vaultkit.errors.exceptions import RateLimitError, TransportError, ServerError
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("vaultkit.retry")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class RetryConfig:
|
|
18
|
+
max_attempts: int = 3
|
|
19
|
+
base_sleep_s: float = 0.5
|
|
20
|
+
max_sleep_s: float = 4.0
|
|
21
|
+
|
|
22
|
+
# Add jitter (±20%)
|
|
23
|
+
jitter_ratio: float = 0.2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def retryable(exc: Exception) -> bool:
|
|
27
|
+
return isinstance(exc, (RateLimitError, TransportError, ServerError))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def with_retries(
|
|
31
|
+
fn: Callable[[], T],
|
|
32
|
+
*,
|
|
33
|
+
config: RetryConfig,
|
|
34
|
+
) -> T:
|
|
35
|
+
attempt = 0
|
|
36
|
+
sleep_s = config.base_sleep_s
|
|
37
|
+
|
|
38
|
+
while True:
|
|
39
|
+
attempt += 1
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
return fn()
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
is_retryable = retryable(e)
|
|
46
|
+
|
|
47
|
+
if attempt >= config.max_attempts or not is_retryable:
|
|
48
|
+
logger.debug(
|
|
49
|
+
"retry.exit",
|
|
50
|
+
extra={
|
|
51
|
+
"attempt": attempt,
|
|
52
|
+
"retryable": is_retryable,
|
|
53
|
+
"error_type": type(e).__name__,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
raise
|
|
57
|
+
|
|
58
|
+
# Apply jitter
|
|
59
|
+
jitter = random.uniform(
|
|
60
|
+
-sleep_s * config.jitter_ratio,
|
|
61
|
+
sleep_s * config.jitter_ratio,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
sleep_time = min(
|
|
65
|
+
max(0.0, sleep_s + jitter),
|
|
66
|
+
config.max_sleep_s,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
logger.debug(
|
|
70
|
+
"retry.sleep",
|
|
71
|
+
extra={
|
|
72
|
+
"attempt": attempt,
|
|
73
|
+
"sleep_s": sleep_time,
|
|
74
|
+
"error_type": type(e).__name__,
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
time.sleep(sleep_time)
|
|
79
|
+
|
|
80
|
+
# Exponential backoff
|
|
81
|
+
sleep_s = min(sleep_s * 2, config.max_sleep_s)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from vaultkit.errors.base import ErrorContext
|
|
6
|
+
from vaultkit.errors.exceptions import ValidationError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def require_str(name: str, value: Optional[str]) -> str:
|
|
10
|
+
if value is None:
|
|
11
|
+
raise ValidationError(
|
|
12
|
+
f"{name} is required",
|
|
13
|
+
context=ErrorContext(raw={name: value}),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
result = str(value).strip()
|
|
17
|
+
if not result:
|
|
18
|
+
raise ValidationError(
|
|
19
|
+
f"{name} cannot be empty",
|
|
20
|
+
context=ErrorContext(raw={name: value}),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return result
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def require_dict(name: str, value: Any) -> Dict[str, Any]:
|
|
27
|
+
if not isinstance(value, dict):
|
|
28
|
+
raise ValidationError(
|
|
29
|
+
f"{name} must be an object/dict",
|
|
30
|
+
context=ErrorContext(raw={name: value}),
|
|
31
|
+
)
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def require_list(name: str, value: Any) -> list:
|
|
36
|
+
if not isinstance(value, list):
|
|
37
|
+
raise ValidationError(
|
|
38
|
+
f"{name} must be a list",
|
|
39
|
+
context=ErrorContext(raw={name: value}),
|
|
40
|
+
)
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def validate_limit(value: Optional[int]) -> Optional[int]:
|
|
45
|
+
if value is None:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
if not isinstance(value, int):
|
|
49
|
+
raise ValidationError(
|
|
50
|
+
"limit must be an integer",
|
|
51
|
+
context=ErrorContext(raw=value),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if value <= 0:
|
|
55
|
+
raise ValidationError(
|
|
56
|
+
"limit must be greater than 0",
|
|
57
|
+
context=ErrorContext(raw=value),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if value > 10000:
|
|
61
|
+
raise ValidationError(
|
|
62
|
+
"limit cannot exceed 10000",
|
|
63
|
+
context=ErrorContext(raw=value),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_filters(value: Optional[Any]) -> Optional[list]:
|
|
70
|
+
if value is None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
if not isinstance(value, list):
|
|
74
|
+
raise ValidationError(
|
|
75
|
+
"filters must be a list",
|
|
76
|
+
context=ErrorContext(raw=value),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# light structural validation
|
|
80
|
+
for f in value:
|
|
81
|
+
if not isinstance(f, dict):
|
|
82
|
+
raise ValidationError(
|
|
83
|
+
"each filter must be an object",
|
|
84
|
+
context=ErrorContext(raw=f),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return value
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vaultkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: VaultKit Python SDK for policy-driven, runtime governed data access
|
|
5
|
+
Author-email: VaultKit <founders@vaultkit.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/vaultkit-inc/vaultkit-sdk-python
|
|
8
|
+
Project-URL: Documentation, https://docs.vaultkit.io
|
|
9
|
+
Project-URL: Source, https://github.com/vaultkit-inc/vaultkit-sdk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/vaultkit-inc/vaultkit-sdk-python/issues
|
|
11
|
+
Keywords: ai,data,sdk,governance,security,agents,runtime
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: httpx>=0.24.0
|
|
24
|
+
|
|
25
|
+
# VaultKit Python SDK
|
|
26
|
+
|
|
27
|
+
> Secure, policy-driven data access for AI agents and applications.
|
|
28
|
+
|
|
29
|
+
VaultKit is a control plane for governed data access. This SDK allows Python applications and AI agents to safely query data with built-in policy enforcement, approval workflows, and auditability.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Policy-enforced data access (masking, approval, deny)
|
|
36
|
+
- First-class support for AI agents (OpenAI, Anthropic)
|
|
37
|
+
- Built-in approval workflows
|
|
38
|
+
- Automatic polling and retries
|
|
39
|
+
- Schema-aware dataset discovery
|
|
40
|
+
- Simple, high-level API via `execute()`
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install vaultkit
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from vaultkit import VaultKitClient
|
|
56
|
+
|
|
57
|
+
client = VaultKitClient(
|
|
58
|
+
base_url="http://localhost:3000",
|
|
59
|
+
token="YOUR_TOKEN",
|
|
60
|
+
org="YOUR_ORG",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
result = client.execute(
|
|
64
|
+
dataset="users",
|
|
65
|
+
fields=["id", "email"],
|
|
66
|
+
limit=10,
|
|
67
|
+
purpose="Analyze user activity",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
print(result.rows)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## AI Agent Usage
|
|
76
|
+
|
|
77
|
+
VaultKit provides built-in tools for LLM agents.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from vaultkit.tools import ToolBuilder, ToolExecutor, ToolProvider
|
|
81
|
+
|
|
82
|
+
builder = ToolBuilder(client)
|
|
83
|
+
|
|
84
|
+
tools = builder.build(
|
|
85
|
+
provider=ToolProvider.OPENAI,
|
|
86
|
+
include_check_approval=True,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
executor = ToolExecutor(client)
|
|
90
|
+
|
|
91
|
+
result = executor.execute(
|
|
92
|
+
"vaultkit_query",
|
|
93
|
+
{
|
|
94
|
+
"dataset": "users",
|
|
95
|
+
"limit": 5,
|
|
96
|
+
"purpose": "Analyze user trends",
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
See full example: [`examples/agent_openai_demo.py`](examples/agent_openai_demo.py)
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Approval Flow
|
|
106
|
+
|
|
107
|
+
Some queries require human approval before data is returned.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from vaultkit.errors.exceptions import ApprovalRequiredError
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
client.execute(dataset="sensitive_data", purpose="Analysis")
|
|
114
|
+
except ApprovalRequiredError as e:
|
|
115
|
+
print(f"Approval required. Request ID: {e.request_id}")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Once approved, resume with:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
result = client.poll_request(request_id="req_123")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## API Overview
|
|
127
|
+
|
|
128
|
+
### High-Level
|
|
129
|
+
|
|
130
|
+
| Method | Description |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `client.execute(...)` | Full lifecycle: query → poll → fetch. Recommended for most use cases. |
|
|
133
|
+
|
|
134
|
+
### Low-Level
|
|
135
|
+
|
|
136
|
+
| Method | Description |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `client.query(...)` | Submit an intent request, get a `QueryResult` |
|
|
139
|
+
| `client.poll(result)` | Block until a queued result reaches a terminal state |
|
|
140
|
+
| `client.fetch(grant_ref=...)` | Redeem a grant for data |
|
|
141
|
+
| `client.poll_request(request_id=...)` | Poll by request ID (used in approval flows) |
|
|
142
|
+
|
|
143
|
+
### Discovery
|
|
144
|
+
|
|
145
|
+
| Method | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `client.datasets()` | List authorized datasets from the registry |
|
|
148
|
+
| `client.schema("users")` | Get field-level schema for a dataset |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## How It Works
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Client → VaultKit → Policy Engine → Data Source
|
|
156
|
+
↓
|
|
157
|
+
Enforced Policies
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
1. Queries are evaluated against policy bundles at runtime
|
|
161
|
+
2. Sensitive fields may be masked based on requester context
|
|
162
|
+
3. Some datasets require human approval before access is granted
|
|
163
|
+
4. All access is logged and auditable
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Why VaultKit?
|
|
168
|
+
|
|
169
|
+
Traditional access control is static — permissions are set upfront and rarely change. VaultKit enables:
|
|
170
|
+
|
|
171
|
+
- **Runtime, policy-driven access** — decisions made at query time based on context
|
|
172
|
+
- **AI-safe data access** — purpose and clearance are first-class query parameters
|
|
173
|
+
- **Auditability and compliance** — every request is tracked with correlation IDs
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Environment Variables
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
export VAULTKIT_URL=http://localhost:3000
|
|
181
|
+
export VAULTKIT_TOKEN=your_token
|
|
182
|
+
export VAULTKIT_ORG=your_org
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Or use a `.env` file (see [`.env.example`](.env.example)).
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Local Development
|
|
190
|
+
|
|
191
|
+
Start VaultKit locally with Docker:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
docker compose up
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Run the test suite:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pytest
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
vaultkit/__init__.py,sha256=xOscURPo7m01LpD36Px0bNKEz04QuMSM9oWSyTq9HTQ,1775
|
|
2
|
+
vaultkit/client.py,sha256=ojdvJD4B5C81SBEDLHVCIqUVmCveyGKi3u0jcbjPDug,14499
|
|
3
|
+
vaultkit/core/__init__.py,sha256=d4IG0OxUXj2HffepzQcYixHlZeuuuDMAFa09H_6LtmU,12
|
|
4
|
+
vaultkit/core/http.py,sha256=0eu91QTQr8I4YtRQSoemNVgKC6yY8L8q3E9ezEcsKsk,5451
|
|
5
|
+
vaultkit/core/polling.py,sha256=K_Y-5MvemWaeTppNPsBdevK5f_wkBB9KUPA_PZUBDe4,4163
|
|
6
|
+
vaultkit/errors/__init__.py,sha256=nh2Uu7MjIVcp3rD8uQTYidZXD-ysdV62dlj78AA6WhE,691
|
|
7
|
+
vaultkit/errors/base.py,sha256=U8ctwfA7d6IBXN5NBzIIn-sPTkd1kYZiLNQNNyaeBYU,835
|
|
8
|
+
vaultkit/errors/exceptions.py,sha256=sNZSqqONoT1DoSFLDsiZaO0jC5PBZIcHt_dt3gGAUKQ,4954
|
|
9
|
+
vaultkit/models/__init__.py,sha256=uUf4xeeM2Xz5hS-OZA9oAhy7LWLQd60Dv22Pw0Ff6uI,248
|
|
10
|
+
vaultkit/models/dataset_info.py,sha256=tJDklLxx7YYqUJ6xM2ICDMLqtozAyaaRtEN5ppHaVDE,699
|
|
11
|
+
vaultkit/models/dataset_schema.py,sha256=F7Dtz2doNka5lwUyMZNC-y_vFu1OHIMxXmxNgIJxwuo,1957
|
|
12
|
+
vaultkit/models/fetch_result.py,sha256=eTIsO3KXw-ga9XD3XFGygXLKrE0vLaRRKPPfd65box8,1547
|
|
13
|
+
vaultkit/models/query_result.py,sha256=u-CJlebz1xbYjDDaKYkZ7Q0-0-frLPx0KkvUww7aBW0,2233
|
|
14
|
+
vaultkit/tools/__init__.py,sha256=TnuStyD_9vVwiJ_aXO0TFx112w0ZHO_QfSajRTOizN0,162
|
|
15
|
+
vaultkit/tools/builder.py,sha256=Wui6reEqDSyKlGZg7-cPu3XLO7P9WEmHRSAtWzuoLIM,4137
|
|
16
|
+
vaultkit/tools/definitions.py,sha256=_p-LxBIGOSDnUruywMQfDc7HhXRYgDvMAOmQX2PIwGU,6163
|
|
17
|
+
vaultkit/tools/executor.py,sha256=1qNXEONY1-qVICAZ5mZQxUoGfMUEHjlHLWDvFbjbuZ0,6859
|
|
18
|
+
vaultkit/tools/schemas.py,sha256=EXP8PjOucffvuB1bGRYiSWBJ6nrZxz-AH2HUWsIxkRQ,1107
|
|
19
|
+
vaultkit/tools/adapters/__init__.py,sha256=r3-XRYvPkobjr0zA3bhKLSI8hH7j7oEkTaRpOTzdL3c,186
|
|
20
|
+
vaultkit/tools/adapters/anthropic.py,sha256=sV0rsgq-wEMBM5Yi6iwvvHpr_wBHG08GXugErgqqtbU,490
|
|
21
|
+
vaultkit/tools/adapters/openai.py,sha256=zmkI6y3m9HF7ppOqqLvqLoKIrYhTsp29pHeSYgxAFc4,658
|
|
22
|
+
vaultkit/utils/__init__.py,sha256=d4IG0OxUXj2HffepzQcYixHlZeuuuDMAFa09H_6LtmU,12
|
|
23
|
+
vaultkit/utils/retry.py,sha256=ONhY98EV3MviWN9nP-WRO5rNws_L1D6yK01kj5AneIY,1943
|
|
24
|
+
vaultkit/utils/validation.py,sha256=DBA_xS7frqn4IQTSFL0edxmG4oHwNmalAF9gHiXP7Vs,2183
|
|
25
|
+
vaultkit-0.1.0.dist-info/METADATA,sha256=b2BVQy5RSul26ouSwFEqpajGwYo4gx-kcwV9BYkRhLs,4821
|
|
26
|
+
vaultkit-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
27
|
+
vaultkit-0.1.0.dist-info/top_level.txt,sha256=c2ZPwFHOBzAB3cplCOpzxS7rp5RrYId8W3o2gnxOIUg,9
|
|
28
|
+
vaultkit-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vaultkit
|