lockstock-integrations 1.1.0__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.
@@ -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"]
@@ -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()
@@ -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())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockstock-integrations
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: LockStock compliance runtime integrations for AI Agent SDKs
5
5
  Project-URL: Homepage, https://d3cipher.ai
6
6
  Project-URL: Documentation, https://d3cipher.ai/docs
@@ -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
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.1.0.dist-info/METADATA,sha256=2oQWjepWJDR6-Q_L5tOmiaDCg4gGm6K5-JcEIRYtRSE,5629
15
- lockstock_integrations-1.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- lockstock_integrations-1.1.0.dist-info/RECORD,,
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,,