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.
Files changed (33) hide show
  1. connectonion/__init__.py +1 -1
  2. connectonion/cli/co_ai/main.py +2 -2
  3. connectonion/cli/co_ai/prompts/connectonion/concepts/trust.md +166 -208
  4. connectonion/cli/commands/copy_commands.py +21 -0
  5. connectonion/cli/commands/trust_commands.py +152 -0
  6. connectonion/cli/main.py +82 -0
  7. connectonion/core/llm.py +2 -2
  8. connectonion/docs/concepts/fast_rules.md +237 -0
  9. connectonion/docs/concepts/onboarding.md +465 -0
  10. connectonion/docs/concepts/trust.md +933 -192
  11. connectonion/docs/design-decisions/023-trust-policy-system-design.md +323 -0
  12. connectonion/docs/network/README.md +23 -1
  13. connectonion/docs/network/connect.md +135 -0
  14. connectonion/docs/network/host.md +73 -4
  15. connectonion/network/__init__.py +7 -6
  16. connectonion/network/asgi/__init__.py +3 -0
  17. connectonion/network/asgi/http.py +125 -19
  18. connectonion/network/asgi/websocket.py +276 -15
  19. connectonion/network/connect.py +145 -29
  20. connectonion/network/host/auth.py +70 -67
  21. connectonion/network/host/routes.py +88 -3
  22. connectonion/network/host/server.py +100 -17
  23. connectonion/network/trust/__init__.py +27 -19
  24. connectonion/network/trust/factory.py +51 -24
  25. connectonion/network/trust/fast_rules.py +100 -0
  26. connectonion/network/trust/tools.py +316 -32
  27. connectonion/network/trust/trust_agent.py +403 -0
  28. connectonion/transcribe.py +1 -1
  29. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/METADATA +1 -1
  30. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/RECORD +32 -27
  31. connectonion/network/trust/prompts.py +0 -71
  32. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/WHEEL +0 -0
  33. {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: create_trust_agent() calls get_trust_verification_tools() returns list of [check_whitelist, test_capability, verify_agent] functions these become tools for trust Agent trust agent calls tools with agent_id functions return descriptive strings AI interprets results per trust policy
6
- State/Effects: check_whitelist() reads ~/.connectonion/trusted.txt file if exists | supports wildcard patterns with * | test_capability() and verify_agent() are pure (no I/O, just return descriptive strings for AI)
7
- Integration: exposes check_whitelist(agent_id), test_capability(agent_id, test, expected), verify_agent(agent_id, agent_info), get_trust_verification_tools() | used by factory.py to equip trust agents with verification capabilities
8
- Performance: file read only for check_whitelist | simple string operations | no network calls
9
- Errors: check_whitelist() catches file read exceptions and returns error string | missing whitelist file returns helpful message | no exceptions raised (errors returned as strings for AI interpretation)
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 directlyreturns bool for instant decisions | Trust agents call check_whitelist/check_blocklist/get_levelreturns 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
- whitelist_path = Path.home() / ".connectonion" / "trusted.txt"
27
- if whitelist_path.exists():
28
- try:
29
- whitelist = whitelist_path.read_text(encoding='utf-8')
30
- lines = whitelist.strip().split('\n')
31
- for line in lines:
32
- line = line.strip()
33
- if not line or line.startswith('#'):
34
- continue
35
- if line == agent_id:
36
- return f"{agent_id} is on the whitelist"
37
- # Simple wildcard support
38
- if '*' in line:
39
- pattern = line.replace('*', '')
40
- if pattern in agent_id:
41
- return f"{agent_id} matches whitelist pattern: {line}"
42
- return f"{agent_id} is NOT on the whitelist"
43
- except Exception as e:
44
- return f"Error reading whitelist: {e}"
45
- return "No whitelist file found at ~/.connectonion/trusted.txt"
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
- test_capability,
87
- verify_agent
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."