lockstock-integrations 1.0.1__py3-none-any.whl → 1.1.1__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.
- lockstock_core/__init__.py +10 -0
- lockstock_core/client.py +281 -0
- lockstock_core/types.py +264 -0
- {lockstock_integrations-1.0.1.dist-info → lockstock_integrations-1.1.1.dist-info}/METADATA +1 -1
- {lockstock_integrations-1.0.1.dist-info → lockstock_integrations-1.1.1.dist-info}/RECORD +7 -4
- lockstock_openai/guardrails.py +213 -100
- {lockstock_integrations-1.0.1.dist-info → lockstock_integrations-1.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LockStock Core Client
|
|
3
|
+
---------------------
|
|
4
|
+
Shared client for all SDK integrations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .client import LockStockClient
|
|
8
|
+
from .types import VerifyResult, AuditEntry, AgentCard
|
|
9
|
+
|
|
10
|
+
__all__ = ["LockStockClient", "VerifyResult", "AuditEntry", "AgentCard"]
|
lockstock_core/client.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LockStock Core Client
|
|
3
|
+
---------------------
|
|
4
|
+
HTTP client for LockStock API interactions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import hmac
|
|
9
|
+
import time
|
|
10
|
+
from typing import Optional, Dict, Any, List
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from .types import VerifyResult, VerifyStatus, AuditEntry, AgentCard, map_tool_to_capability, Matrix, get_generator, normalize_task_for_signature
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LockStockClient:
|
|
17
|
+
"""
|
|
18
|
+
Client for interacting with the LockStock API.
|
|
19
|
+
|
|
20
|
+
Provides capability verification, audit logging, and identity management.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
DEFAULT_ENDPOINT = "https://lockstock-api-i9kp.onrender.com"
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
agent_id: str,
|
|
28
|
+
secret: Optional[str] = None,
|
|
29
|
+
api_key: Optional[str] = None,
|
|
30
|
+
endpoint: str = DEFAULT_ENDPOINT,
|
|
31
|
+
timeout: float = 30.0
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Initialize LockStock client.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
agent_id: The agent's unique identifier
|
|
38
|
+
secret: The agent's HMAC secret (for signing requests)
|
|
39
|
+
api_key: Admin API key (for provisioning/management)
|
|
40
|
+
endpoint: LockStock API endpoint
|
|
41
|
+
timeout: Request timeout in seconds
|
|
42
|
+
"""
|
|
43
|
+
self.agent_id = agent_id
|
|
44
|
+
self.secret = secret
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.endpoint = endpoint.rstrip("/")
|
|
47
|
+
self.timeout = timeout
|
|
48
|
+
|
|
49
|
+
# State tracking
|
|
50
|
+
self._current_hash = "0" * 64 # Genesis hash
|
|
51
|
+
self._sequence = 0
|
|
52
|
+
self._state_matrix = Matrix.identity()
|
|
53
|
+
|
|
54
|
+
# HTTP client
|
|
55
|
+
self._client = httpx.AsyncClient(timeout=timeout)
|
|
56
|
+
|
|
57
|
+
async def verify(
|
|
58
|
+
self,
|
|
59
|
+
task: str,
|
|
60
|
+
tool_name: Optional[str] = None,
|
|
61
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
62
|
+
) -> VerifyResult:
|
|
63
|
+
"""
|
|
64
|
+
Verify that the agent is authorized to perform a task.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
task: The capability/task to verify (e.g., "DEPLOY", "RESTART")
|
|
68
|
+
tool_name: Optional tool name (will be mapped to capability)
|
|
69
|
+
metadata: Optional metadata to include in audit
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
VerifyResult with authorization status
|
|
73
|
+
"""
|
|
74
|
+
# Map tool name to capability if provided
|
|
75
|
+
if tool_name and task == "UNKNOWN":
|
|
76
|
+
task = map_tool_to_capability(tool_name)
|
|
77
|
+
|
|
78
|
+
# Prepare request
|
|
79
|
+
self._sequence += 1
|
|
80
|
+
timestamp = int(time.time())
|
|
81
|
+
|
|
82
|
+
# Apply generator matrix for this task (topological state transition)
|
|
83
|
+
generator = get_generator(task)
|
|
84
|
+
new_matrix = self._state_matrix.multiply(generator)
|
|
85
|
+
|
|
86
|
+
# Create signature with the NEW matrix (uses timestamp, not sequence)
|
|
87
|
+
signature = self._sign_request(task, self._current_hash, timestamp, new_matrix)
|
|
88
|
+
|
|
89
|
+
payload = {
|
|
90
|
+
"client_id": self.agent_id,
|
|
91
|
+
"task": task.lower(),
|
|
92
|
+
"parent_hash": self._current_hash,
|
|
93
|
+
"state_matrix": new_matrix.to_dict(),
|
|
94
|
+
"signature": signature,
|
|
95
|
+
"timestamp": timestamp,
|
|
96
|
+
"sequence": self._sequence
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
response = await self._client.post(
|
|
101
|
+
f"{self.endpoint}/verify",
|
|
102
|
+
json=payload,
|
|
103
|
+
headers=self._headers()
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
data = response.json()
|
|
107
|
+
|
|
108
|
+
status = VerifyStatus(data.get("status", "rejected"))
|
|
109
|
+
|
|
110
|
+
if status == VerifyStatus.ACCEPTED:
|
|
111
|
+
# Update state from server response
|
|
112
|
+
self._current_hash = data.get("state_hash", self._current_hash)
|
|
113
|
+
if "state_matrix" in data:
|
|
114
|
+
self._state_matrix = Matrix.from_dict(data["state_matrix"])
|
|
115
|
+
|
|
116
|
+
return VerifyResult(
|
|
117
|
+
status=status,
|
|
118
|
+
reason=data.get("reason"),
|
|
119
|
+
state_hash=data.get("state_hash"),
|
|
120
|
+
server_timestamp=data.get("server_timestamp")
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
except httpx.HTTPError as e:
|
|
124
|
+
return VerifyResult(
|
|
125
|
+
status=VerifyStatus.REJECTED,
|
|
126
|
+
reason=f"HTTP error: {str(e)}"
|
|
127
|
+
)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
return VerifyResult(
|
|
130
|
+
status=VerifyStatus.REJECTED,
|
|
131
|
+
reason=f"Client error: {str(e)}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def verify_tool(self, tool_name: str, tool_input: Optional[Dict] = None) -> VerifyResult:
|
|
135
|
+
"""
|
|
136
|
+
Verify authorization for a specific tool call.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
tool_name: Name of the tool being called
|
|
140
|
+
tool_input: The tool's input parameters
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
VerifyResult with authorization status
|
|
144
|
+
"""
|
|
145
|
+
capability = map_tool_to_capability(tool_name)
|
|
146
|
+
return await self.verify(task=capability, tool_name=tool_name)
|
|
147
|
+
|
|
148
|
+
async def bootstrap(self) -> Dict[str, Any]:
|
|
149
|
+
"""
|
|
150
|
+
Initialize the agent's identity with the server.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Bootstrap response with root hash and matrix
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
response = await self._client.post(
|
|
157
|
+
f"{self.endpoint}/bootstrap",
|
|
158
|
+
json={"client_id": self.agent_id},
|
|
159
|
+
headers=self._headers()
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
data = response.json()
|
|
163
|
+
|
|
164
|
+
if "root_hash" in data:
|
|
165
|
+
self._current_hash = data["root_hash"]
|
|
166
|
+
if "root_matrix" in data:
|
|
167
|
+
self._state_matrix = Matrix.from_dict(data["root_matrix"])
|
|
168
|
+
self._sequence = 0
|
|
169
|
+
|
|
170
|
+
return data
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
return {"error": str(e)}
|
|
174
|
+
|
|
175
|
+
async def get_agent_card(self) -> Optional[AgentCard]:
|
|
176
|
+
"""
|
|
177
|
+
Retrieve the agent's card from the server.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
AgentCard if found, None otherwise
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
response = await self._client.get(
|
|
184
|
+
f"{self.endpoint}/api/guard/agents/{self.agent_id}",
|
|
185
|
+
headers=self._headers()
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if response.status_code == 200:
|
|
189
|
+
data = response.json()
|
|
190
|
+
return AgentCard(
|
|
191
|
+
agent_id=data.get("agent_id", self.agent_id),
|
|
192
|
+
display_name=data.get("display_name", ""),
|
|
193
|
+
capabilities=data.get("capabilities", []),
|
|
194
|
+
status=data.get("status", "unknown"),
|
|
195
|
+
fingerprint=data.get("fingerprint"),
|
|
196
|
+
owner_id=data.get("owner_id"),
|
|
197
|
+
created_at=data.get("created_at")
|
|
198
|
+
)
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
except Exception:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
async def log_audit(
|
|
205
|
+
self,
|
|
206
|
+
action: str,
|
|
207
|
+
status: str,
|
|
208
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
209
|
+
) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Log an action to the audit trail.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
action: The action that was performed
|
|
215
|
+
status: Status of the action (e.g., "APPROVED", "DENIED")
|
|
216
|
+
metadata: Additional metadata to log
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
True if logged successfully
|
|
220
|
+
"""
|
|
221
|
+
# Audit is automatically logged via verify() calls
|
|
222
|
+
# This method is for explicit logging of non-verified actions
|
|
223
|
+
entry = AuditEntry(
|
|
224
|
+
sequence=self._sequence,
|
|
225
|
+
action=action,
|
|
226
|
+
timestamp=int(time.time()),
|
|
227
|
+
state_hash=self._current_hash,
|
|
228
|
+
signature=self._sign_request(action, self._current_hash, self._sequence),
|
|
229
|
+
metadata=metadata or {}
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# In production, this would POST to an audit endpoint
|
|
233
|
+
# For now, we just track locally
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
def _sign_request(self, task: str, parent_hash: str, timestamp: int, matrix: Matrix = None) -> str:
|
|
237
|
+
"""Create HMAC signature for a request.
|
|
238
|
+
|
|
239
|
+
The signature format matches the Rust server:
|
|
240
|
+
{client_id}|{Task}|{parent_hash}|{a11},{a12},{a21},{a22}|{timestamp}
|
|
241
|
+
|
|
242
|
+
IMPORTANT: The task name must match Rust's Debug format (e.g., "Heartbeat", "FileRead")
|
|
243
|
+
because the server uses {:?} formatting for the Task enum in signature generation.
|
|
244
|
+
"""
|
|
245
|
+
if not self.secret:
|
|
246
|
+
return "unsigned"
|
|
247
|
+
|
|
248
|
+
# Use provided matrix or current state
|
|
249
|
+
m = matrix if matrix else self._state_matrix
|
|
250
|
+
matrix_str = f"{m.a11},{m.a12},{m.a21},{m.a22}"
|
|
251
|
+
|
|
252
|
+
# Normalize task to Rust Debug format (e.g., "HEARTBEAT" -> "Heartbeat")
|
|
253
|
+
rust_task = normalize_task_for_signature(task)
|
|
254
|
+
|
|
255
|
+
# Build message in server's expected format
|
|
256
|
+
message = f"{self.agent_id}|{rust_task}|{parent_hash}|{matrix_str}|{timestamp}"
|
|
257
|
+
|
|
258
|
+
signature = hmac.new(
|
|
259
|
+
self.secret.encode(),
|
|
260
|
+
message.encode(),
|
|
261
|
+
hashlib.sha256
|
|
262
|
+
).hexdigest()
|
|
263
|
+
|
|
264
|
+
return signature
|
|
265
|
+
|
|
266
|
+
def _headers(self) -> Dict[str, str]:
|
|
267
|
+
"""Build request headers."""
|
|
268
|
+
headers = {"Content-Type": "application/json"}
|
|
269
|
+
if self.api_key:
|
|
270
|
+
headers["x-admin-key"] = self.api_key
|
|
271
|
+
return headers
|
|
272
|
+
|
|
273
|
+
async def close(self):
|
|
274
|
+
"""Close the HTTP client."""
|
|
275
|
+
await self._client.aclose()
|
|
276
|
+
|
|
277
|
+
async def __aenter__(self):
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
281
|
+
await self.close()
|
lockstock_core/types.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LockStock Core Types
|
|
3
|
+
--------------------
|
|
4
|
+
Shared types for all SDK integrations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import List, Optional, Dict, Any
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VerifyStatus(str, Enum):
|
|
13
|
+
ACCEPTED = "accepted"
|
|
14
|
+
REJECTED = "rejected"
|
|
15
|
+
BUFFERED = "buffered"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class VerifyResult:
|
|
20
|
+
"""Result of a capability verification request."""
|
|
21
|
+
status: VerifyStatus
|
|
22
|
+
reason: Optional[str] = None
|
|
23
|
+
state_hash: Optional[str] = None
|
|
24
|
+
server_timestamp: Optional[int] = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def authorized(self) -> bool:
|
|
28
|
+
return self.status == VerifyStatus.ACCEPTED
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class AuditEntry:
|
|
33
|
+
"""An entry in the agent's audit trail."""
|
|
34
|
+
sequence: int
|
|
35
|
+
action: str
|
|
36
|
+
timestamp: int
|
|
37
|
+
state_hash: str
|
|
38
|
+
signature: str
|
|
39
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class AgentCard:
|
|
44
|
+
"""Agent identity and capabilities."""
|
|
45
|
+
agent_id: str
|
|
46
|
+
display_name: str
|
|
47
|
+
capabilities: List[str]
|
|
48
|
+
status: str = "active"
|
|
49
|
+
fingerprint: Optional[str] = None
|
|
50
|
+
owner_id: Optional[str] = None
|
|
51
|
+
created_at: Optional[str] = None
|
|
52
|
+
|
|
53
|
+
def has_capability(self, capability: str) -> bool:
|
|
54
|
+
"""Check if agent has a specific capability."""
|
|
55
|
+
return capability.upper() in [c.upper() for c in self.capabilities]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Mapping from common tool names to LockStock capabilities
|
|
59
|
+
# These map to the Task enum variants in Rust (case-insensitive)
|
|
60
|
+
TOOL_CAPABILITY_MAP = {
|
|
61
|
+
# File operations -> FileRead, FileWrite
|
|
62
|
+
"read": "FILEREAD",
|
|
63
|
+
"read_file": "FILEREAD",
|
|
64
|
+
"Read": "FILEREAD",
|
|
65
|
+
"Glob": "FILEREAD",
|
|
66
|
+
"glob": "FILEREAD",
|
|
67
|
+
"Grep": "FILEREAD",
|
|
68
|
+
"grep": "FILEREAD",
|
|
69
|
+
"write": "FILEWRITE",
|
|
70
|
+
"write_file": "FILEWRITE",
|
|
71
|
+
"Write": "FILEWRITE",
|
|
72
|
+
"edit": "FILEWRITE",
|
|
73
|
+
"Edit": "FILEWRITE",
|
|
74
|
+
"NotebookEdit": "FILEWRITE",
|
|
75
|
+
|
|
76
|
+
# Shell/execution -> Shell (alias for Deploy)
|
|
77
|
+
"bash": "SHELL",
|
|
78
|
+
"Bash": "SHELL",
|
|
79
|
+
"shell": "SHELL",
|
|
80
|
+
"execute": "SHELL",
|
|
81
|
+
"run_command": "SHELL",
|
|
82
|
+
"KillShell": "SHELL",
|
|
83
|
+
|
|
84
|
+
# Network operations -> Network
|
|
85
|
+
"web_fetch": "NETWORK",
|
|
86
|
+
"WebFetch": "NETWORK",
|
|
87
|
+
"web_search": "NETWORK",
|
|
88
|
+
"WebSearch": "NETWORK",
|
|
89
|
+
"http_request": "NETWORK",
|
|
90
|
+
"fetch": "NETWORK",
|
|
91
|
+
|
|
92
|
+
# Database operations -> Database
|
|
93
|
+
"sql": "DATABASE",
|
|
94
|
+
"query": "DATABASE",
|
|
95
|
+
"db_read": "DATABASE",
|
|
96
|
+
"db_write": "DATABASE",
|
|
97
|
+
"LSP": "DATABASE", # Language Server Protocol reads code
|
|
98
|
+
|
|
99
|
+
# Agent operations -> Teleport, Handoff
|
|
100
|
+
"task": "TELEPORT",
|
|
101
|
+
"Task": "TELEPORT",
|
|
102
|
+
"spawn_agent": "TELEPORT",
|
|
103
|
+
"TaskOutput": "TELEPORT",
|
|
104
|
+
"handoff": "HANDOFF",
|
|
105
|
+
"delegate": "DELEGATE",
|
|
106
|
+
|
|
107
|
+
# State operations -> Checkpoint, Teleport
|
|
108
|
+
"checkpoint": "CHECKPOINT",
|
|
109
|
+
"save_state": "CHECKPOINT",
|
|
110
|
+
"export_passport": "TELEPORT",
|
|
111
|
+
"import_passport": "TELEPORT",
|
|
112
|
+
|
|
113
|
+
# Monitoring -> Heartbeat
|
|
114
|
+
"heartbeat": "HEARTBEAT",
|
|
115
|
+
"health_check": "HEARTBEAT",
|
|
116
|
+
|
|
117
|
+
# Operations -> Deploy, Restart, Backup, Rollback
|
|
118
|
+
"deploy": "DEPLOY",
|
|
119
|
+
"restart": "RESTART",
|
|
120
|
+
"backup": "BACKUP",
|
|
121
|
+
"rollback": "ROLLBACK",
|
|
122
|
+
|
|
123
|
+
# Task management tools
|
|
124
|
+
"TaskCreate": "FILEWRITE",
|
|
125
|
+
"TaskUpdate": "FILEWRITE",
|
|
126
|
+
"TaskList": "FILEREAD",
|
|
127
|
+
"TaskGet": "FILEREAD",
|
|
128
|
+
|
|
129
|
+
# Skills
|
|
130
|
+
"Skill": "SHELL",
|
|
131
|
+
"AskUserQuestion": "HEARTBEAT", # Interactive, low privilege
|
|
132
|
+
"EnterPlanMode": "HEARTBEAT",
|
|
133
|
+
"ExitPlanMode": "HEARTBEAT",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def map_tool_to_capability(tool_name: str) -> str:
|
|
138
|
+
"""Map a tool name to a LockStock capability."""
|
|
139
|
+
return TOOL_CAPABILITY_MAP.get(tool_name, "UNKNOWN")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Mapping from any case to Rust Debug format (used in HMAC signatures)
|
|
143
|
+
# The Rust server uses {:?} format which produces these exact strings
|
|
144
|
+
TASK_RUST_DEBUG_FORMAT = {
|
|
145
|
+
# Operations
|
|
146
|
+
"deploy": "Deploy",
|
|
147
|
+
"restart": "Restart",
|
|
148
|
+
"backup": "Backup",
|
|
149
|
+
"rollback": "Rollback",
|
|
150
|
+
# Agent lifecycle
|
|
151
|
+
"heartbeat": "Heartbeat",
|
|
152
|
+
"checkpoint": "Checkpoint",
|
|
153
|
+
"teleport": "Teleport",
|
|
154
|
+
# MCP tool categories
|
|
155
|
+
"fileread": "FileRead",
|
|
156
|
+
"file_read": "FileRead",
|
|
157
|
+
"read": "FileRead",
|
|
158
|
+
"query": "FileRead",
|
|
159
|
+
"filewrite": "FileWrite",
|
|
160
|
+
"file_write": "FileWrite",
|
|
161
|
+
"write": "FileWrite",
|
|
162
|
+
"edit": "FileWrite",
|
|
163
|
+
"network": "Network",
|
|
164
|
+
"http": "Network",
|
|
165
|
+
"fetch": "Network",
|
|
166
|
+
"shell": "Shell",
|
|
167
|
+
"bash": "Shell",
|
|
168
|
+
"exec": "Shell",
|
|
169
|
+
"execute": "Shell",
|
|
170
|
+
"database": "Database",
|
|
171
|
+
"db": "Database",
|
|
172
|
+
"sql": "Database",
|
|
173
|
+
# A2A protocol
|
|
174
|
+
"handoff": "Handoff",
|
|
175
|
+
"hand_off": "Handoff",
|
|
176
|
+
"delegate": "Delegate",
|
|
177
|
+
# Wildcard
|
|
178
|
+
"all": "All",
|
|
179
|
+
"admin": "All",
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def normalize_task_for_signature(task: str) -> str:
|
|
184
|
+
"""
|
|
185
|
+
Convert a task name to the Rust Debug format used in HMAC signatures.
|
|
186
|
+
|
|
187
|
+
The Rust server uses {:?} format for Task enum in signatures, which produces
|
|
188
|
+
PascalCase variants like 'Heartbeat', 'FileRead', etc.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
task: Task name in any case (e.g., "HEARTBEAT", "heartbeat", "Heartbeat")
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Task name in Rust Debug format (e.g., "Heartbeat")
|
|
195
|
+
"""
|
|
196
|
+
normalized = TASK_RUST_DEBUG_FORMAT.get(task.lower())
|
|
197
|
+
if normalized:
|
|
198
|
+
return normalized
|
|
199
|
+
|
|
200
|
+
# Fallback: try to capitalize properly for unknown tasks
|
|
201
|
+
# This handles cases like "deploy" -> "Deploy"
|
|
202
|
+
return task.capitalize()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# Prime field modulus (F_65537)
|
|
206
|
+
MODULUS = 65537
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Matrix:
|
|
210
|
+
"""2x2 matrix over F_65537 for topological state transitions."""
|
|
211
|
+
|
|
212
|
+
def __init__(self, a11: int, a12: int, a21: int, a22: int):
|
|
213
|
+
self.a11 = a11 % MODULUS
|
|
214
|
+
self.a12 = a12 % MODULUS
|
|
215
|
+
self.a21 = a21 % MODULUS
|
|
216
|
+
self.a22 = a22 % MODULUS
|
|
217
|
+
|
|
218
|
+
def multiply(self, other: "Matrix") -> "Matrix":
|
|
219
|
+
"""Matrix multiplication in F_65537."""
|
|
220
|
+
return Matrix(
|
|
221
|
+
(self.a11 * other.a11 + self.a12 * other.a21) % MODULUS,
|
|
222
|
+
(self.a11 * other.a12 + self.a12 * other.a22) % MODULUS,
|
|
223
|
+
(self.a21 * other.a11 + self.a22 * other.a21) % MODULUS,
|
|
224
|
+
(self.a21 * other.a12 + self.a22 * other.a22) % MODULUS,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def to_dict(self) -> Dict[str, int]:
|
|
228
|
+
"""Convert to dictionary for JSON serialization."""
|
|
229
|
+
return {"a11": self.a11, "a12": self.a12, "a21": self.a21, "a22": self.a22}
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
def from_dict(cls, d: Dict[str, int]) -> "Matrix":
|
|
233
|
+
"""Create from dictionary."""
|
|
234
|
+
return cls(d.get("a11", 1), d.get("a12", 0), d.get("a21", 0), d.get("a22", 1))
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def identity(cls) -> "Matrix":
|
|
238
|
+
"""Return identity matrix."""
|
|
239
|
+
return cls(1, 0, 0, 1)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Generator matrices for each task (matching Rust server)
|
|
243
|
+
GENERATOR_MATRICES = {
|
|
244
|
+
"DEPLOY": Matrix(1, 1, 0, 1),
|
|
245
|
+
"RESTART": Matrix(1, 0, 1, 1),
|
|
246
|
+
"BACKUP": Matrix(2, 1, 1, 1),
|
|
247
|
+
"ROLLBACK": Matrix(1, 1, 1, 2),
|
|
248
|
+
"HEARTBEAT": Matrix(0, 65536, 1, 0), # 90-degree rotation
|
|
249
|
+
"CHECKPOINT": Matrix(2, 1, 1, 2),
|
|
250
|
+
"TELEPORT": Matrix(3, 1, 1, 1),
|
|
251
|
+
"FILEREAD": Matrix(1, 2, 0, 1),
|
|
252
|
+
"FILEWRITE": Matrix(1, 3, 0, 1),
|
|
253
|
+
"NETWORK": Matrix(1, 0, 2, 1),
|
|
254
|
+
"SHELL": Matrix(1, 4, 0, 1), # Upper triangular (distinct from Deploy, v1.0.8+)
|
|
255
|
+
"DATABASE": Matrix(1, 0, 3, 1),
|
|
256
|
+
"HANDOFF": Matrix(3, 2, 1, 1),
|
|
257
|
+
"DELEGATE": Matrix(1, 1, 2, 3),
|
|
258
|
+
"ALL": Matrix(1, 0, 0, 1), # Identity
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def get_generator(task: str) -> Matrix:
|
|
263
|
+
"""Get the generator matrix for a task."""
|
|
264
|
+
return GENERATOR_MATRICES.get(task.upper(), Matrix.identity())
|
|
@@ -5,12 +5,15 @@ lockstock_a2a/task_handler.py,sha256=hKpk9smt12nqTlJua861LpdKiiXpcRGRYhCW0SwS438
|
|
|
5
5
|
lockstock_claude/__init__.py,sha256=yI1e13JC_KKgYrap2AIkVl5biCsHKfb9rPtgbN1QNtg,320
|
|
6
6
|
lockstock_claude/hooks.py,sha256=uJ-i3s9pmwcgSIpc84TlxNAyP2a1S8Auyo6o1H3KrcU,5116
|
|
7
7
|
lockstock_claude/skills.py,sha256=4f6HprTBfjmPvcDzJ40fcsMoF543EybbE0_KM_kBtpQ,4023
|
|
8
|
+
lockstock_core/__init__.py,sha256=Euan9X4O6XBgQ8IYoC5hj2ruTNU0vPLvBTCIPZZNQdU,258
|
|
9
|
+
lockstock_core/client.py,sha256=gNacFIoH9DA9Dw2moyfPHGj7mlexrJ7VxIo0e3iRX8M,9110
|
|
10
|
+
lockstock_core/types.py,sha256=4OtO7PWcZyXxbmuFcoXXxjO3tKHV1oNGpm5yExLTCQE,7426
|
|
8
11
|
lockstock_langgraph/__init__.py,sha256=7fBIYNCQygvUvG7IQOVY042sNNmu4UwkJoy0VLQn4Ik,425
|
|
9
12
|
lockstock_langgraph/checkpointer.py,sha256=XJ2c_92OSBq4aNpOT112nTy3yI3stFcSCb8OTx04nos,6317
|
|
10
13
|
lockstock_langgraph/middleware.py,sha256=ZSwoxdkn3cC6aIBnMgd1jYGHYKa1yQXRwnts3Ds4sfQ,9174
|
|
11
14
|
lockstock_openai/__init__.py,sha256=Pc4phRUJEqrnREggyGJnc1HDKa7puVAR6-G9WZ4dtXM,402
|
|
12
|
-
lockstock_openai/guardrails.py,sha256=
|
|
15
|
+
lockstock_openai/guardrails.py,sha256=fy84bDrhy6CVk2b4_yOl5XTxw9Lp1HT4Mw9WcaAHOsg,11920
|
|
13
16
|
lockstock_openai/tracing.py,sha256=VQWzG0y6t8q5rJZFbbP-bi9tsV0KaUqqH4Kr9-vr_e8,6025
|
|
14
|
-
lockstock_integrations-1.
|
|
15
|
-
lockstock_integrations-1.
|
|
16
|
-
lockstock_integrations-1.
|
|
17
|
+
lockstock_integrations-1.1.1.dist-info/METADATA,sha256=H022KMhtIDctPxNHkdc70WRfzjyO72xwK6Ed5u69Au8,5629
|
|
18
|
+
lockstock_integrations-1.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
+
lockstock_integrations-1.1.1.dist-info/RECORD,,
|
lockstock_openai/guardrails.py
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
2
|
LockStock OpenAI Agents SDK Guardrails
|
|
3
3
|
---------------------------------------
|
|
4
|
-
|
|
4
|
+
Chain-based guardrail implementations for OpenAI Agents SDK.
|
|
5
|
+
|
|
6
|
+
NO SECRETS. ONLY CHAIN STATE.
|
|
7
|
+
|
|
8
|
+
The Guard daemon tracks chain state (hash, matrix, sequence).
|
|
9
|
+
When signing, Guard uses current_hash as the HMAC key.
|
|
5
10
|
|
|
6
11
|
Usage:
|
|
7
12
|
from agents import Agent
|
|
8
13
|
from lockstock_openai import LockStockGuardrail
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
# Chain Authority Mode (recommended)
|
|
16
|
+
guardrail = LockStockGuardrail.from_liberty("agent_abc123")
|
|
11
17
|
|
|
12
18
|
agent = Agent(
|
|
13
19
|
name="my-agent",
|
|
@@ -16,11 +22,10 @@ Usage:
|
|
|
16
22
|
)
|
|
17
23
|
"""
|
|
18
24
|
|
|
19
|
-
from typing import Any, Dict, Optional
|
|
25
|
+
from typing import Any, Dict, Optional
|
|
20
26
|
from dataclasses import dataclass
|
|
21
27
|
|
|
22
|
-
from lockstock_core import
|
|
23
|
-
from lockstock_core.types import map_tool_to_capability, VerifyStatus
|
|
28
|
+
from lockstock_core.types import map_tool_to_capability
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
@dataclass
|
|
@@ -33,41 +38,56 @@ class GuardrailResult:
|
|
|
33
38
|
|
|
34
39
|
class LockStockGuardrail:
|
|
35
40
|
"""
|
|
36
|
-
|
|
41
|
+
Chain-based guardrail for OpenAI Agents SDK.
|
|
42
|
+
|
|
43
|
+
NO SECRETS STORED. Chain state is the authentication.
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
The Guard daemon:
|
|
46
|
+
1. Tracks current chain state (hash, matrix, sequence)
|
|
47
|
+
2. Uses current_hash as HMAC key for signatures
|
|
48
|
+
3. Returns signatures (not secrets!)
|
|
39
49
|
"""
|
|
40
50
|
|
|
41
51
|
name = "lockstock"
|
|
42
|
-
description = "LockStock
|
|
52
|
+
description = "LockStock chain-based authorization guardrail"
|
|
43
53
|
|
|
44
54
|
def __init__(
|
|
45
55
|
self,
|
|
46
56
|
agent_id: str,
|
|
47
|
-
|
|
48
|
-
api_key: Optional[str] = None,
|
|
57
|
+
guard_socket: str = "/var/run/lockstock-guard/guard.sock",
|
|
49
58
|
endpoint: str = "https://lockstock-api-i9kp.onrender.com",
|
|
50
59
|
block_on_failure: bool = True
|
|
51
60
|
):
|
|
52
61
|
"""
|
|
53
|
-
Initialize
|
|
62
|
+
Initialize chain-based guardrail.
|
|
54
63
|
|
|
55
64
|
Args:
|
|
56
65
|
agent_id: The agent's unique identifier
|
|
57
|
-
|
|
58
|
-
api_key: Admin API key
|
|
66
|
+
guard_socket: Path to Guard daemon Unix socket
|
|
59
67
|
endpoint: LockStock API endpoint
|
|
60
68
|
block_on_failure: If True, block execution on auth failure
|
|
69
|
+
|
|
70
|
+
NO SECRET PARAMETER! Secrets don't exist in this system.
|
|
61
71
|
"""
|
|
62
|
-
self.client = LockStockClient(
|
|
63
|
-
agent_id=agent_id,
|
|
64
|
-
secret=secret,
|
|
65
|
-
api_key=api_key,
|
|
66
|
-
endpoint=endpoint
|
|
67
|
-
)
|
|
68
72
|
self.agent_id = agent_id
|
|
73
|
+
self.guard_socket = guard_socket
|
|
74
|
+
self.endpoint = endpoint
|
|
69
75
|
self.block_on_failure = block_on_failure
|
|
70
76
|
|
|
77
|
+
# Chain state (cached locally, synced from Guard)
|
|
78
|
+
self.current_hash: Optional[str] = None
|
|
79
|
+
self.current_sequence: int = 0
|
|
80
|
+
self.current_matrix: Optional[Dict[str, int]] = None
|
|
81
|
+
self._chain_initialized = False
|
|
82
|
+
|
|
83
|
+
# Guard client (for chain operations)
|
|
84
|
+
from lockstock_guard.client import LibertyClient
|
|
85
|
+
self._guard = LibertyClient(socket_path=guard_socket)
|
|
86
|
+
|
|
87
|
+
# HTTP client (for server communication)
|
|
88
|
+
import httpx
|
|
89
|
+
self._http = httpx.AsyncClient(timeout=30.0)
|
|
90
|
+
|
|
71
91
|
@classmethod
|
|
72
92
|
def from_liberty(
|
|
73
93
|
cls,
|
|
@@ -77,52 +97,77 @@ class LockStockGuardrail:
|
|
|
77
97
|
block_on_failure: bool = True
|
|
78
98
|
):
|
|
79
99
|
"""
|
|
80
|
-
Create guardrail
|
|
100
|
+
Create guardrail using Chain Authority Mode.
|
|
101
|
+
|
|
102
|
+
NO SECRET RETRIEVAL. NO SECRET STORAGE.
|
|
81
103
|
|
|
82
|
-
|
|
83
|
-
are hardware-bound and managed by the lockstock-guard daemon.
|
|
104
|
+
The Guard daemon manages chain state. We just track it locally.
|
|
84
105
|
|
|
85
106
|
Args:
|
|
86
|
-
agent_id:
|
|
87
|
-
socket_path: Path to
|
|
107
|
+
agent_id: Agent identifier (from provisioning)
|
|
108
|
+
socket_path: Path to Guard daemon Unix socket
|
|
88
109
|
endpoint: LockStock API endpoint
|
|
89
|
-
block_on_failure:
|
|
110
|
+
block_on_failure: Whether to block on auth failure
|
|
90
111
|
|
|
91
112
|
Returns:
|
|
92
|
-
LockStockGuardrail instance
|
|
113
|
+
LockStockGuardrail instance using chain-based auth
|
|
93
114
|
|
|
94
115
|
Example:
|
|
95
116
|
>>> guardrail = LockStockGuardrail.from_liberty("agent_abc123")
|
|
96
117
|
>>> agent = Agent(guardrails=[guardrail])
|
|
97
118
|
"""
|
|
98
|
-
import json
|
|
99
|
-
from lockstock_guard.client import LibertyClient
|
|
100
|
-
|
|
101
|
-
# Retrieve secret from Guard daemon
|
|
102
|
-
guard_client = LibertyClient(socket_path=socket_path)
|
|
103
|
-
try:
|
|
104
|
-
secret_data = json.loads(guard_client.get(agent_id))
|
|
105
|
-
secret = secret_data["value"]
|
|
106
|
-
finally:
|
|
107
|
-
guard_client.close()
|
|
108
|
-
|
|
109
|
-
# Create guardrail with retrieved secret
|
|
110
119
|
return cls(
|
|
111
120
|
agent_id=agent_id,
|
|
112
|
-
|
|
121
|
+
guard_socket=socket_path,
|
|
113
122
|
endpoint=endpoint,
|
|
114
123
|
block_on_failure=block_on_failure
|
|
115
124
|
)
|
|
116
125
|
|
|
126
|
+
async def _ensure_chain_initialized(self):
|
|
127
|
+
"""
|
|
128
|
+
Sync chain state from Guard daemon on first use.
|
|
129
|
+
|
|
130
|
+
Guard fetches current state from server and caches it.
|
|
131
|
+
We sync that cached state to our local tracking.
|
|
132
|
+
"""
|
|
133
|
+
if self._chain_initialized:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
# Ask Guard to sync from server
|
|
138
|
+
sync_result = self._guard.sync_chain(self.agent_id)
|
|
139
|
+
|
|
140
|
+
if not sync_result.get("success"):
|
|
141
|
+
raise Exception(f"Chain sync failed: {sync_result}")
|
|
142
|
+
|
|
143
|
+
# Update local chain state cache
|
|
144
|
+
self.current_hash = sync_result.get("current_hash")
|
|
145
|
+
self.current_sequence = sync_result.get("sequence", 0)
|
|
146
|
+
self.current_matrix = sync_result.get("state_matrix")
|
|
147
|
+
self._chain_initialized = True
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
raise Exception(
|
|
151
|
+
f"Failed to initialize chain state for {self.agent_id}. "
|
|
152
|
+
f"Ensure Guard daemon is running and agent is provisioned. "
|
|
153
|
+
f"Error: {e}"
|
|
154
|
+
)
|
|
155
|
+
|
|
117
156
|
async def validate(
|
|
118
157
|
self,
|
|
119
158
|
agent: Any,
|
|
120
159
|
message: Dict[str, Any]
|
|
121
160
|
) -> GuardrailResult:
|
|
122
161
|
"""
|
|
123
|
-
Validate a message
|
|
162
|
+
Validate a message using chain-based authentication.
|
|
163
|
+
|
|
164
|
+
Flow:
|
|
165
|
+
1. Check if action requires authorization
|
|
166
|
+
2. Ask Guard to sign (Guard uses current_hash as HMAC key)
|
|
167
|
+
3. Send Guard's signature to server for verification
|
|
168
|
+
4. Update local chain state if verified
|
|
124
169
|
|
|
125
|
-
|
|
170
|
+
NO SECRETS are involved. Only chain state.
|
|
126
171
|
|
|
127
172
|
Args:
|
|
128
173
|
agent: The agent instance
|
|
@@ -134,42 +179,146 @@ class LockStockGuardrail:
|
|
|
134
179
|
# Check for tool calls in assistant messages
|
|
135
180
|
if message.get("role") == "assistant" and "tool_calls" in message:
|
|
136
181
|
for tool_call in message.get("tool_calls", []):
|
|
137
|
-
if "function" in tool_call:
|
|
138
|
-
|
|
182
|
+
if "function" not in tool_call:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
function_name = tool_call["function"].get("name", "")
|
|
186
|
+
|
|
187
|
+
# Map tool to capability
|
|
188
|
+
capability = map_tool_to_capability(function_name)
|
|
189
|
+
|
|
190
|
+
# Ensure chain state is synced
|
|
191
|
+
await self._ensure_chain_initialized()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
# ========================================================
|
|
195
|
+
# CHAIN AUTHORITY MODE
|
|
196
|
+
# ========================================================
|
|
197
|
+
# Guard daemon:
|
|
198
|
+
# 1. Looks up current chain state for this agent
|
|
199
|
+
# 2. Calculates: new_matrix = current_matrix × generator[task]
|
|
200
|
+
# 3. Signs with: HMAC(current_hash, message)
|
|
201
|
+
# ↑ current_hash IS the key! No separate secret!
|
|
202
|
+
# 4. Calculates: new_hash = SHA256(...)
|
|
203
|
+
# 5. Returns: {signature, new_hash, new_matrix, ...}
|
|
204
|
+
# ========================================================
|
|
205
|
+
|
|
206
|
+
sign_result = self._guard.sign_and_advance(
|
|
207
|
+
agent_id=self.agent_id,
|
|
208
|
+
task=capability,
|
|
209
|
+
parent_hash=self.current_hash
|
|
210
|
+
)
|
|
139
211
|
|
|
140
|
-
#
|
|
141
|
-
|
|
212
|
+
# Guard returned:
|
|
213
|
+
# - signature: HMAC computed using current_hash as key
|
|
214
|
+
# - new_hash: Next chain head
|
|
215
|
+
# - new_sequence: Incremented sequence
|
|
216
|
+
# - new_matrix: After applying generator
|
|
217
|
+
# - timestamp: Unix timestamp used in signature
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
return GuardrailResult(
|
|
221
|
+
passed=False,
|
|
222
|
+
reason=f"Guard signing failed: {e}"
|
|
223
|
+
)
|
|
142
224
|
|
|
143
|
-
|
|
144
|
-
|
|
225
|
+
# Send Guard's signature to LockStock server for verification
|
|
226
|
+
try:
|
|
227
|
+
verify_result = await self._verify_with_server(
|
|
145
228
|
task=capability,
|
|
146
|
-
|
|
229
|
+
signature=sign_result["signature"],
|
|
230
|
+
parent_hash=self.current_hash,
|
|
231
|
+
state_matrix=sign_result["new_matrix"],
|
|
232
|
+
timestamp=sign_result["timestamp"],
|
|
233
|
+
sequence=sign_result["new_sequence"]
|
|
147
234
|
)
|
|
148
235
|
|
|
149
|
-
if not
|
|
236
|
+
if not verify_result["authorized"]:
|
|
150
237
|
if self.block_on_failure:
|
|
151
238
|
return GuardrailResult(
|
|
152
239
|
passed=False,
|
|
153
240
|
reason=f"LockStock DENIED: {function_name} requires {capability}. "
|
|
154
|
-
f"
|
|
241
|
+
f"Reason: {verify_result.get('reason', 'Unknown')}"
|
|
155
242
|
)
|
|
156
243
|
|
|
244
|
+
# Update local chain state cache
|
|
245
|
+
self.current_hash = sign_result["new_hash"]
|
|
246
|
+
self.current_sequence = sign_result["new_sequence"]
|
|
247
|
+
self.current_matrix = sign_result["new_matrix"]
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
return GuardrailResult(
|
|
251
|
+
passed=False,
|
|
252
|
+
reason=f"Server verification failed: {e}"
|
|
253
|
+
)
|
|
254
|
+
|
|
157
255
|
return GuardrailResult(passed=True)
|
|
158
256
|
|
|
257
|
+
async def _verify_with_server(
|
|
258
|
+
self,
|
|
259
|
+
task: str,
|
|
260
|
+
signature: str,
|
|
261
|
+
parent_hash: str,
|
|
262
|
+
state_matrix: Dict[str, int],
|
|
263
|
+
timestamp: int,
|
|
264
|
+
sequence: int
|
|
265
|
+
) -> Dict[str, Any]:
|
|
266
|
+
"""
|
|
267
|
+
Send Guard-signed request to LockStock server.
|
|
268
|
+
|
|
269
|
+
The signature was created by Guard using current_hash as HMAC key.
|
|
270
|
+
Server will verify by:
|
|
271
|
+
1. Looking up its current_hash for this agent
|
|
272
|
+
2. Recalculating HMAC using that hash
|
|
273
|
+
3. Comparing to signature we send
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
task: Capability being verified
|
|
277
|
+
signature: HMAC signature from Guard (signed with current_hash)
|
|
278
|
+
parent_hash: Parent hash in chain
|
|
279
|
+
state_matrix: New state matrix
|
|
280
|
+
timestamp: Timestamp used in signature
|
|
281
|
+
sequence: Sequence number
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Dict with authorized (bool) and reason (str)
|
|
285
|
+
"""
|
|
286
|
+
payload = {
|
|
287
|
+
"client_id": self.agent_id,
|
|
288
|
+
"task": task.lower(),
|
|
289
|
+
"parent_hash": parent_hash,
|
|
290
|
+
"state_matrix": state_matrix,
|
|
291
|
+
"signature": signature, # ← From Guard, signed with current_hash
|
|
292
|
+
"timestamp": timestamp,
|
|
293
|
+
"sequence": sequence
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
response = await self._http.post(
|
|
297
|
+
f"{self.endpoint}/verify",
|
|
298
|
+
json=payload
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
data = response.json()
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
"authorized": data.get("status") == "accepted",
|
|
305
|
+
"reason": data.get("reason"),
|
|
306
|
+
"state_hash": data.get("state_hash")
|
|
307
|
+
}
|
|
308
|
+
|
|
159
309
|
async def close(self):
|
|
160
|
-
"""Close
|
|
161
|
-
await self.
|
|
310
|
+
"""Close connections."""
|
|
311
|
+
await self._http.aclose()
|
|
312
|
+
self._guard.close()
|
|
162
313
|
|
|
163
314
|
|
|
164
315
|
class LockStockInputGuardrail(LockStockGuardrail):
|
|
165
316
|
"""
|
|
166
|
-
Input guardrail
|
|
167
|
-
|
|
168
|
-
Validates incoming messages/requests before processing.
|
|
317
|
+
Input guardrail using chain-based authentication.
|
|
169
318
|
"""
|
|
170
319
|
|
|
171
320
|
name = "lockstock_input"
|
|
172
|
-
description = "LockStock input validation guardrail"
|
|
321
|
+
description = "LockStock chain-based input validation guardrail"
|
|
173
322
|
|
|
174
323
|
async def validate_input(
|
|
175
324
|
self,
|
|
@@ -177,35 +326,19 @@ class LockStockInputGuardrail(LockStockGuardrail):
|
|
|
177
326
|
user_input: str,
|
|
178
327
|
context: Optional[Dict[str, Any]] = None
|
|
179
328
|
) -> GuardrailResult:
|
|
180
|
-
"""
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
agent: The agent instance
|
|
185
|
-
user_input: The user's input string
|
|
186
|
-
context: Optional context dictionary
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
GuardrailResult indicating if validation passed
|
|
190
|
-
"""
|
|
191
|
-
# Input validation can check for:
|
|
192
|
-
# - Sensitive data patterns
|
|
193
|
-
# - Rate limiting
|
|
194
|
-
# - Input size limits
|
|
195
|
-
|
|
196
|
-
# For now, pass through - capability checks happen at tool use
|
|
329
|
+
"""Validate user input."""
|
|
330
|
+
# Input validation - pass through for now
|
|
331
|
+
# Capability checks happen at tool use
|
|
197
332
|
return GuardrailResult(passed=True)
|
|
198
333
|
|
|
199
334
|
|
|
200
335
|
class LockStockOutputGuardrail(LockStockGuardrail):
|
|
201
336
|
"""
|
|
202
|
-
Output guardrail
|
|
203
|
-
|
|
204
|
-
Validates agent outputs before returning to user.
|
|
337
|
+
Output guardrail using chain-based authentication.
|
|
205
338
|
"""
|
|
206
339
|
|
|
207
340
|
name = "lockstock_output"
|
|
208
|
-
description = "LockStock output validation guardrail"
|
|
341
|
+
description = "LockStock chain-based output validation guardrail"
|
|
209
342
|
|
|
210
343
|
async def validate_output(
|
|
211
344
|
self,
|
|
@@ -213,27 +346,7 @@ class LockStockOutputGuardrail(LockStockGuardrail):
|
|
|
213
346
|
output: str,
|
|
214
347
|
context: Optional[Dict[str, Any]] = None
|
|
215
348
|
) -> GuardrailResult:
|
|
216
|
-
"""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Args:
|
|
220
|
-
agent: The agent instance
|
|
221
|
-
output: The agent's output string
|
|
222
|
-
context: Optional context dictionary
|
|
223
|
-
|
|
224
|
-
Returns:
|
|
225
|
-
GuardrailResult indicating if validation passed
|
|
226
|
-
"""
|
|
227
|
-
# Output validation can check for:
|
|
228
|
-
# - Sensitive data leakage
|
|
229
|
-
# - Compliance requirements
|
|
230
|
-
# - Output format requirements
|
|
231
|
-
|
|
232
|
-
# Log the output to audit trail
|
|
233
|
-
await self.client.log_audit(
|
|
234
|
-
action="output",
|
|
235
|
-
status="COMPLETED",
|
|
236
|
-
metadata={"output_length": len(output)}
|
|
237
|
-
)
|
|
238
|
-
|
|
349
|
+
"""Validate agent output."""
|
|
350
|
+
# Output validation - pass through for now
|
|
351
|
+
# Could log to audit trail here
|
|
239
352
|
return GuardrailResult(passed=True)
|
|
File without changes
|