connectonion 0.6.4__py3-none-any.whl → 0.6.5__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.
- connectonion/__init__.py +1 -1
- connectonion/cli/co_ai/main.py +2 -2
- connectonion/cli/co_ai/prompts/connectonion/concepts/trust.md +166 -208
- connectonion/cli/commands/copy_commands.py +21 -0
- connectonion/cli/commands/trust_commands.py +152 -0
- connectonion/cli/main.py +82 -0
- connectonion/core/llm.py +2 -2
- connectonion/docs/concepts/fast_rules.md +237 -0
- connectonion/docs/concepts/onboarding.md +465 -0
- connectonion/docs/concepts/trust.md +933 -192
- connectonion/docs/design-decisions/023-trust-policy-system-design.md +323 -0
- connectonion/docs/network/README.md +23 -1
- connectonion/docs/network/connect.md +135 -0
- connectonion/docs/network/host.md +73 -4
- connectonion/network/__init__.py +7 -6
- connectonion/network/asgi/__init__.py +3 -0
- connectonion/network/asgi/http.py +125 -19
- connectonion/network/asgi/websocket.py +276 -15
- connectonion/network/connect.py +145 -29
- connectonion/network/host/auth.py +70 -67
- connectonion/network/host/routes.py +88 -3
- connectonion/network/host/server.py +100 -17
- connectonion/network/trust/__init__.py +27 -19
- connectonion/network/trust/factory.py +51 -24
- connectonion/network/trust/fast_rules.py +100 -0
- connectonion/network/trust/tools.py +316 -32
- connectonion/network/trust/trust_agent.py +403 -0
- connectonion/transcribe.py +1 -1
- {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/METADATA +1 -1
- {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/RECORD +32 -27
- connectonion/network/trust/prompts.py +0 -71
- {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/WHEEL +0 -0
- {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/entry_points.txt +0 -0
|
@@ -1,48 +1,86 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Purpose: Provide tool functions for trust agents to verify other agents
|
|
3
3
|
LLM-Note:
|
|
4
|
-
Dependencies: imports from [pathlib, typing] | imported by [.factory] | tested by [tests/unit/test_trust_functions.py]
|
|
5
|
-
Data flow:
|
|
6
|
-
State/Effects:
|
|
7
|
-
Integration: exposes
|
|
8
|
-
Performance: file
|
|
9
|
-
|
|
4
|
+
Dependencies: imports from [pathlib, typing] | imported by [.factory, .fast_rules] | tested by [tests/unit/test_trust_functions.py]
|
|
5
|
+
Data flow: Fast rules call is_whitelisted/is_blocked/is_contact directly → returns bool for instant decisions | Trust agents call check_whitelist/check_blocklist/get_level → returns strings for LLM interpretation
|
|
6
|
+
State/Effects: Reads/writes ~/.co/{whitelist,blocklist,contacts}.txt files | Supports wildcard patterns with * | promote_*/demote_*/block/unblock modify list files
|
|
7
|
+
Integration: exposes fast rule helpers (is_*), trust agent tools (check_*, get_level), state modifiers (promote_*, demote_*, block, unblock) | Used by factory.py and fast_rules.py
|
|
8
|
+
Performance: Simple file I/O | No network calls | O(n) list lookup
|
|
9
|
+
|
|
10
|
+
Trust Levels (stored in ~/.co/):
|
|
11
|
+
- stranger: Not in any list (default for unknown clients)
|
|
12
|
+
- contact: In contacts.txt (onboarded via invite/payment)
|
|
13
|
+
- whitelist: In whitelist.txt (fully trusted)
|
|
14
|
+
- blocked: In blocklist.txt (denied access)
|
|
10
15
|
"""
|
|
11
16
|
|
|
12
17
|
from pathlib import Path
|
|
13
18
|
from typing import List, Callable
|
|
14
19
|
|
|
15
20
|
|
|
21
|
+
CO_DIR = Path.home() / ".co"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _check_list(list_name: str, agent_id: str) -> bool:
|
|
25
|
+
"""Check if agent_id is in a list file. Supports wildcards."""
|
|
26
|
+
list_path = CO_DIR / f"{list_name}.txt"
|
|
27
|
+
if not list_path.exists():
|
|
28
|
+
return False
|
|
29
|
+
try:
|
|
30
|
+
content = list_path.read_text(encoding='utf-8')
|
|
31
|
+
for line in content.strip().split('\n'):
|
|
32
|
+
line = line.strip()
|
|
33
|
+
if not line or line.startswith('#'):
|
|
34
|
+
continue
|
|
35
|
+
if line == agent_id:
|
|
36
|
+
return True
|
|
37
|
+
if '*' in line:
|
|
38
|
+
pattern = line.replace('*', '')
|
|
39
|
+
if pattern in agent_id:
|
|
40
|
+
return True
|
|
41
|
+
return False
|
|
42
|
+
except Exception:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
16
46
|
def check_whitelist(agent_id: str) -> str:
|
|
17
47
|
"""
|
|
18
48
|
Check if an agent is on the whitelist.
|
|
19
|
-
|
|
49
|
+
|
|
20
50
|
Args:
|
|
21
51
|
agent_id: Identifier of the agent to check
|
|
22
|
-
|
|
52
|
+
|
|
23
53
|
Returns:
|
|
24
54
|
String indicating if agent is whitelisted or not
|
|
25
55
|
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
if _check_list("whitelist", agent_id):
|
|
57
|
+
return f"{agent_id} is on the whitelist"
|
|
58
|
+
return f"{agent_id} is NOT on the whitelist"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check_blocklist(agent_id: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Check if an agent is on the blocklist.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
agent_id: Identifier of the agent to check
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
String indicating if agent is blocked or not
|
|
70
|
+
"""
|
|
71
|
+
if _check_list("blocklist", agent_id):
|
|
72
|
+
return f"{agent_id} is BLOCKED"
|
|
73
|
+
return f"{agent_id} is not blocked"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_whitelisted(agent_id: str) -> bool:
|
|
77
|
+
"""Check if agent is whitelisted. Returns bool for fast rules."""
|
|
78
|
+
return _check_list("whitelist", agent_id)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_blocked(agent_id: str) -> bool:
|
|
82
|
+
"""Check if agent is blocked. Returns bool for fast rules."""
|
|
83
|
+
return _check_list("blocklist", agent_id)
|
|
46
84
|
|
|
47
85
|
|
|
48
86
|
def test_capability(agent_id: str, test: str, expected: str) -> str:
|
|
@@ -74,15 +112,261 @@ def verify_agent(agent_id: str, agent_info: str = "") -> str:
|
|
|
74
112
|
return f"Verifying agent: {agent_id}. Info: {agent_info}"
|
|
75
113
|
|
|
76
114
|
|
|
115
|
+
def _add_to_list(list_name: str, client_id: str) -> bool:
|
|
116
|
+
"""Add client_id to a list file."""
|
|
117
|
+
CO_DIR.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
list_path = CO_DIR / f"{list_name}.txt"
|
|
119
|
+
|
|
120
|
+
# Check if already in list
|
|
121
|
+
if _check_list(list_name, client_id):
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
# Append to file
|
|
125
|
+
with open(list_path, 'a', encoding='utf-8') as f:
|
|
126
|
+
f.write(f"{client_id}\n")
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _remove_from_list(list_name: str, client_id: str) -> bool:
|
|
131
|
+
"""Remove client_id from a list file."""
|
|
132
|
+
list_path = CO_DIR / f"{list_name}.txt"
|
|
133
|
+
if not list_path.exists():
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
content = list_path.read_text(encoding='utf-8')
|
|
137
|
+
lines = [line for line in content.strip().split('\n')
|
|
138
|
+
if line.strip() and line.strip() != client_id]
|
|
139
|
+
list_path.write_text('\n'.join(lines) + '\n' if lines else '', encoding='utf-8')
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# === Verification ===
|
|
144
|
+
|
|
145
|
+
def verify_invite(client_id: str, invite_code: str, valid_codes: list[str]) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Verify invite code. Promotes to contact if valid.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
client_id: Client to verify
|
|
151
|
+
invite_code: The invite code provided
|
|
152
|
+
valid_codes: List of valid invite codes
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Result message
|
|
156
|
+
"""
|
|
157
|
+
if invite_code in valid_codes:
|
|
158
|
+
promote_to_contact(client_id)
|
|
159
|
+
return f"Invite code valid. {client_id} promoted to contact."
|
|
160
|
+
return f"Invalid invite code for {client_id}."
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def verify_payment(client_id: str, amount: float, required_amount: float) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Verify payment. Promotes to contact if sufficient.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
client_id: Client to verify
|
|
169
|
+
amount: Payment amount received
|
|
170
|
+
required_amount: Required payment amount
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Result message
|
|
174
|
+
"""
|
|
175
|
+
if amount >= required_amount:
|
|
176
|
+
promote_to_contact(client_id)
|
|
177
|
+
return f"Payment verified. {client_id} promoted to contact."
|
|
178
|
+
return f"Insufficient payment for {client_id}. Required: {required_amount}, got: {amount}"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# === Promotion ===
|
|
182
|
+
|
|
183
|
+
def promote_to_contact(client_id: str) -> str:
|
|
184
|
+
"""Stranger → Contact"""
|
|
185
|
+
_add_to_list("contacts", client_id)
|
|
186
|
+
return f"{client_id} promoted to contact."
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def promote_to_whitelist(client_id: str) -> str:
|
|
190
|
+
"""Contact → Whitelist"""
|
|
191
|
+
_add_to_list("whitelist", client_id)
|
|
192
|
+
return f"{client_id} promoted to whitelist."
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# === Demotion ===
|
|
196
|
+
|
|
197
|
+
def demote_to_contact(client_id: str) -> str:
|
|
198
|
+
"""Whitelist → Contact"""
|
|
199
|
+
_remove_from_list("whitelist", client_id)
|
|
200
|
+
_add_to_list("contacts", client_id)
|
|
201
|
+
return f"{client_id} demoted to contact."
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def demote_to_stranger(client_id: str) -> str:
|
|
205
|
+
"""Contact → Stranger"""
|
|
206
|
+
_remove_from_list("contacts", client_id)
|
|
207
|
+
_remove_from_list("whitelist", client_id)
|
|
208
|
+
return f"{client_id} demoted to stranger."
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# === Blocking ===
|
|
212
|
+
|
|
213
|
+
def block(client_id: str, reason: str = "") -> str:
|
|
214
|
+
"""Add to blocklist."""
|
|
215
|
+
_add_to_list("blocklist", client_id)
|
|
216
|
+
return f"{client_id} blocked. Reason: {reason}"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def unblock(client_id: str) -> str:
|
|
220
|
+
"""Remove from blocklist."""
|
|
221
|
+
_remove_from_list("blocklist", client_id)
|
|
222
|
+
return f"{client_id} unblocked."
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# === Queries ===
|
|
226
|
+
|
|
227
|
+
def get_level(client_id: str) -> str:
|
|
228
|
+
"""Returns: stranger, contact, whitelist, or blocked."""
|
|
229
|
+
if is_blocked(client_id):
|
|
230
|
+
return "blocked"
|
|
231
|
+
if is_whitelisted(client_id):
|
|
232
|
+
return "whitelist"
|
|
233
|
+
if _check_list("contacts", client_id):
|
|
234
|
+
return "contact"
|
|
235
|
+
return "stranger"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def is_contact(client_id: str) -> bool:
|
|
239
|
+
"""Check if client is a contact."""
|
|
240
|
+
return _check_list("contacts", client_id)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def is_stranger(client_id: str) -> bool:
|
|
244
|
+
"""Check if client is a stranger (not contact, whitelist, or blocked)."""
|
|
245
|
+
return get_level(client_id) == "stranger"
|
|
246
|
+
|
|
247
|
+
|
|
77
248
|
def get_trust_verification_tools() -> List[Callable]:
|
|
78
249
|
"""
|
|
79
250
|
Get the list of trust verification tools.
|
|
80
|
-
|
|
251
|
+
|
|
81
252
|
Returns:
|
|
82
253
|
List of trust verification functions
|
|
83
254
|
"""
|
|
84
255
|
return [
|
|
85
256
|
check_whitelist,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
257
|
+
check_blocklist,
|
|
258
|
+
promote_to_contact,
|
|
259
|
+
promote_to_whitelist,
|
|
260
|
+
demote_to_contact,
|
|
261
|
+
demote_to_stranger,
|
|
262
|
+
block,
|
|
263
|
+
unblock,
|
|
264
|
+
get_level,
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# === Admin Management ===
|
|
269
|
+
|
|
270
|
+
def load_admins(co_dir: Path = None) -> set:
|
|
271
|
+
"""
|
|
272
|
+
Load admins list: self address (default) + ~/.co/admins.txt.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
co_dir: Project .co directory (for self address). Defaults to cwd/.co
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Set of admin addresses
|
|
279
|
+
"""
|
|
280
|
+
import json
|
|
281
|
+
|
|
282
|
+
admins = set()
|
|
283
|
+
|
|
284
|
+
# Self address is always admin (from project's .co/address.json)
|
|
285
|
+
if co_dir is None:
|
|
286
|
+
co_dir = Path.cwd() / ".co"
|
|
287
|
+
|
|
288
|
+
addr_file = co_dir / "address.json"
|
|
289
|
+
if addr_file.exists():
|
|
290
|
+
try:
|
|
291
|
+
addr_data = json.loads(addr_file.read_text(encoding='utf-8'))
|
|
292
|
+
if addr_data.get('address'):
|
|
293
|
+
admins.add(addr_data['address'])
|
|
294
|
+
except Exception:
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
# Additional admins from ~/.co/admins.txt
|
|
298
|
+
admins_file = CO_DIR / "admins.txt"
|
|
299
|
+
if admins_file.exists():
|
|
300
|
+
try:
|
|
301
|
+
for line in admins_file.read_text(encoding='utf-8').splitlines():
|
|
302
|
+
line = line.strip()
|
|
303
|
+
if line and not line.startswith('#'):
|
|
304
|
+
admins.add(line)
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
return admins
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def is_admin(client_id: str, co_dir: Path = None) -> bool:
|
|
312
|
+
"""Check if client is an admin."""
|
|
313
|
+
return client_id in load_admins(co_dir)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_self_address(co_dir: Path = None) -> str | None:
|
|
317
|
+
"""Get self address (super admin) from .co/address.json."""
|
|
318
|
+
import json
|
|
319
|
+
|
|
320
|
+
if co_dir is None:
|
|
321
|
+
co_dir = Path.cwd() / ".co"
|
|
322
|
+
|
|
323
|
+
addr_file = co_dir / "address.json"
|
|
324
|
+
if addr_file.exists():
|
|
325
|
+
try:
|
|
326
|
+
addr_data = json.loads(addr_file.read_text(encoding='utf-8'))
|
|
327
|
+
return addr_data.get('address')
|
|
328
|
+
except Exception:
|
|
329
|
+
pass
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def is_super_admin(client_id: str, co_dir: Path = None) -> bool:
|
|
334
|
+
"""Check if client is super admin (self address)."""
|
|
335
|
+
return client_id == get_self_address(co_dir)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def add_admin(admin_id: str) -> str:
|
|
339
|
+
"""Add an admin to ~/.co/admins.txt. Super admin only."""
|
|
340
|
+
CO_DIR.mkdir(parents=True, exist_ok=True)
|
|
341
|
+
admins_file = CO_DIR / "admins.txt"
|
|
342
|
+
|
|
343
|
+
# Check if already admin
|
|
344
|
+
existing = set()
|
|
345
|
+
if admins_file.exists():
|
|
346
|
+
existing = {line.strip() for line in admins_file.read_text(encoding='utf-8').splitlines()
|
|
347
|
+
if line.strip() and not line.startswith('#')}
|
|
348
|
+
|
|
349
|
+
if admin_id in existing:
|
|
350
|
+
return f"{admin_id} is already an admin."
|
|
351
|
+
|
|
352
|
+
with open(admins_file, 'a', encoding='utf-8') as f:
|
|
353
|
+
f.write(f"{admin_id}\n")
|
|
354
|
+
|
|
355
|
+
return f"{admin_id} added as admin."
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def remove_admin(admin_id: str) -> str:
|
|
359
|
+
"""Remove an admin from ~/.co/admins.txt. Super admin only."""
|
|
360
|
+
admins_file = CO_DIR / "admins.txt"
|
|
361
|
+
|
|
362
|
+
if not admins_file.exists():
|
|
363
|
+
return f"{admin_id} is not an admin."
|
|
364
|
+
|
|
365
|
+
lines = admins_file.read_text(encoding='utf-8').splitlines()
|
|
366
|
+
new_lines = [line for line in lines if line.strip() != admin_id]
|
|
367
|
+
|
|
368
|
+
if len(new_lines) == len(lines):
|
|
369
|
+
return f"{admin_id} is not an admin."
|
|
370
|
+
|
|
371
|
+
admins_file.write_text('\n'.join(new_lines) + '\n' if new_lines else '', encoding='utf-8')
|
|
372
|
+
return f"{admin_id} removed from admins."
|