beeq 1.0.0__tar.gz

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 (41) hide show
  1. beeq-1.0.0/PKG-INFO +130 -0
  2. beeq-1.0.0/README.md +89 -0
  3. beeq-1.0.0/beeq/__init__.py +22 -0
  4. beeq-1.0.0/beeq/_vendor/__init__.py +3 -0
  5. beeq-1.0.0/beeq/_vendor/aap/__init__.py +25 -0
  6. beeq-1.0.0/beeq/_vendor/aap/audit.py +160 -0
  7. beeq-1.0.0/beeq/_vendor/aap/authorization.py +149 -0
  8. beeq-1.0.0/beeq/_vendor/aap/exceptions.py +57 -0
  9. beeq-1.0.0/beeq/_vendor/aap/identity.py +197 -0
  10. beeq-1.0.0/beeq/_vendor/aap/provenance.py +107 -0
  11. beeq-1.0.0/beeq/cli/__init__.py +0 -0
  12. beeq-1.0.0/beeq/cli/main.py +297 -0
  13. beeq-1.0.0/beeq/core.py +165 -0
  14. beeq-1.0.0/beeq/dashboard/__init__.py +1 -0
  15. beeq-1.0.0/beeq/dashboard/server.py +595 -0
  16. beeq-1.0.0/beeq/governance/__init__.py +1 -0
  17. beeq-1.0.0/beeq/governance/hive.py +142 -0
  18. beeq-1.0.0/beeq/mcp/__init__.py +3 -0
  19. beeq-1.0.0/beeq/mcp/client.py +75 -0
  20. beeq-1.0.0/beeq/mcp/server.py +111 -0
  21. beeq-1.0.0/beeq/providers/__init__.py +12 -0
  22. beeq-1.0.0/beeq/providers/base.py +41 -0
  23. beeq-1.0.0/beeq/providers/claude.py +33 -0
  24. beeq-1.0.0/beeq/providers/manager.py +66 -0
  25. beeq-1.0.0/beeq/providers/ollama.py +48 -0
  26. beeq-1.0.0/beeq/providers/openai_p.py +42 -0
  27. beeq-1.0.0/beeq/queen/__init__.py +1 -0
  28. beeq-1.0.0/beeq/queen/queen.py +164 -0
  29. beeq-1.0.0/beeq/workers/__init__.py +11 -0
  30. beeq-1.0.0/beeq/workers/base.py +81 -0
  31. beeq-1.0.0/beeq/workers/code.py +128 -0
  32. beeq-1.0.0/beeq/workers/hardware.py +37 -0
  33. beeq-1.0.0/beeq/workers/memory.py +269 -0
  34. beeq-1.0.0/beeq/workers/ops.py +176 -0
  35. beeq-1.0.0/beeq/workers/research.py +134 -0
  36. beeq-1.0.0/beeq/workers/writer.py +104 -0
  37. beeq-1.0.0/install.sh +89 -0
  38. beeq-1.0.0/pyproject.toml +74 -0
  39. beeq-1.0.0/tests/__init__.py +0 -0
  40. beeq-1.0.0/tests/conftest.py +4 -0
  41. beeq-1.0.0/tests/test_beeq.py +187 -0
beeq-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: beeq
3
+ Version: 1.0.0
4
+ Summary: The first AI hive. One curl. One queen. An army that works.
5
+ Project-URL: Homepage, https://beeq.run
6
+ Project-URL: Documentation, https://beeq.run/docs/
7
+ Project-URL: Repository, https://github.com/ElmadaniS/beeq
8
+ Project-URL: Verify actions, https://beeq.run/verify/
9
+ Author-email: Elmadani SALKA <Elmadani.SALKA@proton.me>
10
+ License: BSL-1.1
11
+ Keywords: aap,agents,ai,beeq,hive,llm,orchestration,queen
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: aiohttp>=3.9.0
20
+ Requires-Dist: anthropic>=0.25.0
21
+ Requires-Dist: beautifulsoup4>=4.12.0
22
+ Requires-Dist: click>=8.1.0
23
+ Requires-Dist: cryptography>=42.0.0
24
+ Requires-Dist: fastapi>=0.111.0
25
+ Requires-Dist: httpx>=0.27.0
26
+ Requires-Dist: mcp>=1.0.0
27
+ Requires-Dist: ollama>=0.2.0
28
+ Requires-Dist: openai>=1.30.0
29
+ Requires-Dist: pydantic>=2.7.0
30
+ Requires-Dist: python-dotenv>=1.0.0
31
+ Requires-Dist: rich>=13.7.0
32
+ Requires-Dist: uvicorn>=0.30.0
33
+ Requires-Dist: websockets>=12.0
34
+ Provides-Extra: dev
35
+ Requires-Dist: build; extra == 'dev'
36
+ Requires-Dist: hatchling; extra == 'dev'
37
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
38
+ Requires-Dist: pytest>=7.0; extra == 'dev'
39
+ Requires-Dist: twine; extra == 'dev'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # BeeQ
43
+
44
+ **The first AI hive. One curl. One queen. An army that works.**
45
+
46
+ ```bash
47
+ curl -fsSL https://beeq.run/install | bash
48
+ ```
49
+
50
+ ```
51
+ BeeQ Hive Status
52
+ ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
53
+ ┃ Component ┃ Status ┃
54
+ ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
55
+ │ Queen │ ready │
56
+ │ Audit chain │ valid — 0 entries │
57
+ │ Worker-research │ active │
58
+ │ Worker-code │ active │
59
+ │ Worker-write │ active │
60
+ │ Worker-ops │ active │
61
+ └─────────────────┴───────────────────┘
62
+ ```
63
+
64
+ Every action signed. Every action traceable.
65
+ Every action reversible. You remain sovereign.
66
+
67
+ ---
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ # Option 1 — one curl (recommended)
73
+ curl -fsSL https://beeq.run/install | bash
74
+
75
+ # Option 2 — pip
76
+ pip install beeq
77
+ beeq connect ollama # or: beeq connect claude --key sk-ant-...
78
+ beeq start
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ ```bash
84
+ beeq start # start your hive interactively
85
+ beeq mission "prepare my weekly report" # single mission
86
+ beeq status # hive status
87
+ beeq audit # view full audit chain
88
+ beeq revoke research # revoke a worker (LEX §5)
89
+ ```
90
+
91
+ ## What makes BeeQ different
92
+
93
+ | | Others | BeeQ |
94
+ |---|---|---|
95
+ | Agent cryptographic identity | ✗ | ✅ AAP |
96
+ | Signed delegation chain | ✗ | ✅ scope(worker) ⊂ scope(queen) |
97
+ | Tamper-evident audit | ✗ | ✅ hash chain |
98
+ | Physical world (robots/IoT) | ✗ | ✅ NRP |
99
+ | Physical World Rule | ✗ | ✅ Level 4 forbidden |
100
+ | LLM-agnostic | partial | ✅ Claude/OpenAI/Ollama/GGUF |
101
+ | Local-first | ✗ | ✅ your data never leaves |
102
+ | Single curl install | partial | ✅ |
103
+
104
+ ## Architecture
105
+
106
+ ```
107
+ HUMAN (sovereign — gives mission, validates decisions)
108
+
109
+ QUEEN (orchestrates — decomposes, delegates, reports)
110
+
111
+ WORKERS (execute within signed AAP scope)
112
+ ├─ research web · APIs · documents
113
+ ├─ code files · git · execution
114
+ ├─ write emails · articles · reports
115
+ └─ ops infrastructure · monitoring
116
+ ```
117
+
118
+ ## Governed by
119
+
120
+ - **LEX** — 11 civilizational laws (MIT)
121
+ - **PHY** — 7 physical laws for embodied agents (MIT)
122
+ - **AAP** — Agent Accountability Protocol (MIT)
123
+ - **NRP** — Node Reach Protocol for physical world (MIT)
124
+
125
+ ## License
126
+
127
+ BSL-1.1 — Free for individuals, research, open source.
128
+ Commercial use requires a license.
129
+
130
+ See [beeq.run](https://beeq.run) for details.
beeq-1.0.0/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # BeeQ
2
+
3
+ **The first AI hive. One curl. One queen. An army that works.**
4
+
5
+ ```bash
6
+ curl -fsSL https://beeq.run/install | bash
7
+ ```
8
+
9
+ ```
10
+ BeeQ Hive Status
11
+ ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
12
+ ┃ Component ┃ Status ┃
13
+ ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
14
+ │ Queen │ ready │
15
+ │ Audit chain │ valid — 0 entries │
16
+ │ Worker-research │ active │
17
+ │ Worker-code │ active │
18
+ │ Worker-write │ active │
19
+ │ Worker-ops │ active │
20
+ └─────────────────┴───────────────────┘
21
+ ```
22
+
23
+ Every action signed. Every action traceable.
24
+ Every action reversible. You remain sovereign.
25
+
26
+ ---
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ # Option 1 — one curl (recommended)
32
+ curl -fsSL https://beeq.run/install | bash
33
+
34
+ # Option 2 — pip
35
+ pip install beeq
36
+ beeq connect ollama # or: beeq connect claude --key sk-ant-...
37
+ beeq start
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```bash
43
+ beeq start # start your hive interactively
44
+ beeq mission "prepare my weekly report" # single mission
45
+ beeq status # hive status
46
+ beeq audit # view full audit chain
47
+ beeq revoke research # revoke a worker (LEX §5)
48
+ ```
49
+
50
+ ## What makes BeeQ different
51
+
52
+ | | Others | BeeQ |
53
+ |---|---|---|
54
+ | Agent cryptographic identity | ✗ | ✅ AAP |
55
+ | Signed delegation chain | ✗ | ✅ scope(worker) ⊂ scope(queen) |
56
+ | Tamper-evident audit | ✗ | ✅ hash chain |
57
+ | Physical world (robots/IoT) | ✗ | ✅ NRP |
58
+ | Physical World Rule | ✗ | ✅ Level 4 forbidden |
59
+ | LLM-agnostic | partial | ✅ Claude/OpenAI/Ollama/GGUF |
60
+ | Local-first | ✗ | ✅ your data never leaves |
61
+ | Single curl install | partial | ✅ |
62
+
63
+ ## Architecture
64
+
65
+ ```
66
+ HUMAN (sovereign — gives mission, validates decisions)
67
+
68
+ QUEEN (orchestrates — decomposes, delegates, reports)
69
+
70
+ WORKERS (execute within signed AAP scope)
71
+ ├─ research web · APIs · documents
72
+ ├─ code files · git · execution
73
+ ├─ write emails · articles · reports
74
+ └─ ops infrastructure · monitoring
75
+ ```
76
+
77
+ ## Governed by
78
+
79
+ - **LEX** — 11 civilizational laws (MIT)
80
+ - **PHY** — 7 physical laws for embodied agents (MIT)
81
+ - **AAP** — Agent Accountability Protocol (MIT)
82
+ - **NRP** — Node Reach Protocol for physical world (MIT)
83
+
84
+ ## License
85
+
86
+ BSL-1.1 — Free for individuals, research, open source.
87
+ Commercial use requires a license.
88
+
89
+ See [beeq.run](https://beeq.run) for details.
@@ -0,0 +1,22 @@
1
+ """BeeQ — The First AI Hive."""
2
+ import sys
3
+ import os
4
+
5
+ # Ensure vendored AAP is available if not installed globally
6
+ _vendor = os.path.join(os.path.dirname(__file__), "_vendor")
7
+ if _vendor not in sys.path:
8
+ sys.path.insert(0, _vendor)
9
+
10
+ # Verify AAP is importable
11
+ try:
12
+ import aap # noqa: F401
13
+ except ImportError:
14
+ raise ImportError(
15
+ "BeeQ requires AAP (Agent Accountability Protocol). "
16
+ "Please run: pip install aap-protocol\n"
17
+ "Or it should be bundled — check your installation."
18
+ )
19
+
20
+ __version__ = "1.0.0"
21
+ __author__ = "Elmadani SALKA"
22
+ __url__ = "https://beeq.run"
@@ -0,0 +1,3 @@
1
+ # BeeQ vendored dependencies
2
+ # AAP (Agent Accountability Protocol) — MIT License
3
+ # Bundled to ensure zero external dependency at install time
@@ -0,0 +1,25 @@
1
+ """
2
+ AAP — Agent Accountability Protocol
3
+ Python SDK v0.1 — Reference Implementation
4
+
5
+ https://aap-protocol.dev
6
+ License: MIT
7
+ """
8
+ from .identity import AAPIdentity, AAPKeyPair
9
+ from .authorization import AAPAuthorization, AuthorizationLevel
10
+ from .provenance import AAPProvenance
11
+ from .audit import AAPAuditChain, AAPAuditEntry
12
+ from .exceptions import (
13
+ AAPError, AAPValidationError, AAPSignatureError,
14
+ AAPPhysicalWorldViolation, AAPScopeError, AAPRevocationError
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+ __all__ = [
19
+ "AAPIdentity", "AAPKeyPair",
20
+ "AAPAuthorization", "AuthorizationLevel",
21
+ "AAPProvenance",
22
+ "AAPAuditChain", "AAPAuditEntry",
23
+ "AAPError", "AAPValidationError", "AAPSignatureError",
24
+ "AAPPhysicalWorldViolation", "AAPScopeError", "AAPRevocationError",
25
+ ]
@@ -0,0 +1,160 @@
1
+ """AAP AuditChain — tamper-evident append-only log of all agent actions."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib, json, uuid
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import ClassVar
9
+
10
+ from .exceptions import AAPChainError, AAPValidationError
11
+ from .identity import _signable, _verify_signature, AAPKeyPair
12
+
13
+
14
+ def _hash_entry(entry_dict: dict) -> str:
15
+ canonical = json.dumps(entry_dict, sort_keys=True, separators=(",", ":")).encode()
16
+ return f"sha256:{hashlib.sha256(canonical).hexdigest()}"
17
+
18
+
19
+ @dataclass
20
+ class AAPAuditEntry:
21
+ """A single tamper-evident entry in the audit chain."""
22
+ AAP_VERSION: ClassVar[str] = "0.1"
23
+
24
+ entry_id: str
25
+ prev_hash: str # sha256:... or "genesis"
26
+ agent_id: str
27
+ action: str
28
+ result: str # success | failure | blocked | revoked
29
+ timestamp: datetime
30
+ provenance_id: str
31
+ signature: str
32
+ authorization_level: int = 0
33
+ physical: bool = False
34
+ result_detail: str | None = None
35
+
36
+ VALID_RESULTS: ClassVar[frozenset] = frozenset({"success", "failure", "blocked", "revoked"})
37
+
38
+ def __post_init__(self) -> None:
39
+ if self.result not in self.VALID_RESULTS:
40
+ raise AAPValidationError(
41
+ f"Invalid result: '{self.result}'. Must be one of: {self.VALID_RESULTS}",
42
+ field="result"
43
+ )
44
+
45
+ def to_dict(self) -> dict:
46
+ d = {
47
+ "aap_version": self.AAP_VERSION,
48
+ "entry_id": self.entry_id,
49
+ "prev_hash": self.prev_hash,
50
+ "agent_id": self.agent_id,
51
+ "action": self.action,
52
+ "result": self.result,
53
+ "timestamp": self.timestamp.isoformat(),
54
+ "provenance_id": self.provenance_id,
55
+ "authorization_level": self.authorization_level,
56
+ "physical": self.physical,
57
+ "signature": self.signature,
58
+ }
59
+ if self.result_detail:
60
+ d["result_detail"] = self.result_detail
61
+ return d
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: dict) -> "AAPAuditEntry":
65
+ return cls(
66
+ entry_id=data["entry_id"],
67
+ prev_hash=data["prev_hash"],
68
+ agent_id=data["agent_id"],
69
+ action=data["action"],
70
+ result=data["result"],
71
+ timestamp=datetime.fromisoformat(data["timestamp"]),
72
+ provenance_id=data["provenance_id"],
73
+ signature=data["signature"],
74
+ authorization_level=data.get("authorization_level", 0),
75
+ physical=data.get("physical", False),
76
+ result_detail=data.get("result_detail"),
77
+ )
78
+
79
+ def verify_signature(self, agent_public_key: str) -> None:
80
+ _verify_signature(agent_public_key, _signable(self.to_dict()), self.signature)
81
+
82
+
83
+ class AAPAuditChain:
84
+ """
85
+ Tamper-evident audit chain.
86
+ Append-only. Each entry hashes the previous.
87
+ Tampering any entry breaks all subsequent hashes.
88
+ """
89
+
90
+ def __init__(self, storage_path: Path | None = None) -> None:
91
+ self._entries: list[AAPAuditEntry] = []
92
+ self._storage_path = storage_path
93
+ if storage_path and storage_path.exists():
94
+ self._load(storage_path)
95
+
96
+ def append(
97
+ self,
98
+ agent_id: str,
99
+ action: str,
100
+ result: str,
101
+ provenance_id: str,
102
+ agent_keypair: AAPKeyPair,
103
+ authorization_level: int = 0,
104
+ physical: bool = False,
105
+ result_detail: str | None = None,
106
+ ) -> AAPAuditEntry:
107
+ """Append a new entry. Signs it with the agent's key."""
108
+ prev_hash = self._last_hash()
109
+ entry = AAPAuditEntry(
110
+ entry_id=str(uuid.uuid4()),
111
+ prev_hash=prev_hash,
112
+ agent_id=agent_id,
113
+ action=action,
114
+ result=result,
115
+ timestamp=datetime.now(timezone.utc),
116
+ provenance_id=provenance_id,
117
+ signature="",
118
+ authorization_level=authorization_level,
119
+ physical=physical,
120
+ result_detail=result_detail,
121
+ )
122
+ doc = entry.to_dict()
123
+ entry.signature = agent_keypair.sign(_signable(doc))
124
+ self._entries.append(entry)
125
+ if self._storage_path:
126
+ self._persist(entry)
127
+ return entry
128
+
129
+ def verify(self) -> tuple[bool, int, str | None]:
130
+ """
131
+ Verify chain integrity.
132
+ Returns: (valid, entries_checked, broken_at_entry_id)
133
+ """
134
+ prev_hash = "genesis"
135
+ for i, entry in enumerate(self._entries):
136
+ expected_prev = prev_hash
137
+ if entry.prev_hash != expected_prev:
138
+ return False, i, entry.entry_id
139
+ prev_hash = _hash_entry(entry.to_dict())
140
+ return True, len(self._entries), None
141
+
142
+ def _last_hash(self) -> str:
143
+ if not self._entries:
144
+ return "genesis"
145
+ return _hash_entry(self._entries[-1].to_dict())
146
+
147
+ def to_list(self) -> list[dict]:
148
+ return [e.to_dict() for e in self._entries]
149
+
150
+ def __len__(self) -> int:
151
+ return len(self._entries)
152
+
153
+ def _persist(self, entry: AAPAuditEntry) -> None:
154
+ with open(self._storage_path, "a") as f:
155
+ f.write(json.dumps(entry.to_dict()) + "\n")
156
+
157
+ def _load(self, path: Path) -> None:
158
+ for line in path.read_text().splitlines():
159
+ if line.strip():
160
+ self._entries.append(AAPAuditEntry.from_dict(json.loads(line)))
@@ -0,0 +1,149 @@
1
+ """AAP Authorization — human-granted permission for an agent to act."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import uuid
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timezone
8
+ from enum import IntEnum
9
+ from typing import ClassVar
10
+
11
+ from .exceptions import AAPPhysicalWorldViolation, AAPValidationError, AAPRevocationError
12
+ from .identity import _signable, _verify_signature, AAPKeyPair
13
+
14
+
15
+ class AuthorizationLevel(IntEnum):
16
+ OBSERVE = 0 # Read-only. No side effects.
17
+ SUGGEST = 1 # Propose actions. Human executes.
18
+ ASSISTED = 2 # Agent executes. Human confirms each action.
19
+ SUPERVISED = 3 # Agent executes. Human can intervene.
20
+ AUTONOMOUS = 4 # Agent executes within scope. Full audit required.
21
+
22
+ @property
23
+ def name_str(self) -> str:
24
+ return self.name.lower()
25
+
26
+
27
+ PHYSICAL_MAX_LEVEL = AuthorizationLevel.SUPERVISED
28
+
29
+
30
+ @dataclass
31
+ class AAPAuthorization:
32
+ """
33
+ Authorization token granting an agent permission to act.
34
+ Signed by a human supervisor.
35
+
36
+ PHYSICAL WORLD RULE: Level 4 (Autonomous) is FORBIDDEN for physical nodes.
37
+ This is enforced at construction time. It is not configurable.
38
+ """
39
+ AAP_VERSION: ClassVar[str] = "0.1"
40
+
41
+ agent_id: str
42
+ level: AuthorizationLevel
43
+ scope: list[str]
44
+ granted_by: str # did:key:z...
45
+ granted_at: datetime
46
+ signature: str
47
+ physical: bool = False
48
+ expires_at: datetime | None = None
49
+ session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
50
+ _revoked: bool = field(default=False, init=False, repr=False)
51
+
52
+ def __post_init__(self) -> None:
53
+ self._enforce_physical_world_rule()
54
+
55
+ def _enforce_physical_world_rule(self) -> None:
56
+ """
57
+ PHYSICAL WORLD RULE: Autonomous (Level 4) is forbidden for physical nodes.
58
+ This rule cannot be bypassed, overridden, or configured away.
59
+ """
60
+ if self.physical and self.level > PHYSICAL_MAX_LEVEL:
61
+ raise AAPPhysicalWorldViolation(self.agent_id)
62
+
63
+ def revoke(self) -> None:
64
+ """Revoke this authorization."""
65
+ self._revoked = True
66
+
67
+ @property
68
+ def is_revoked(self) -> bool:
69
+ return self._revoked
70
+
71
+ def is_expired(self) -> bool:
72
+ if not self.expires_at:
73
+ return False
74
+ return datetime.now(timezone.utc) > self.expires_at
75
+
76
+ def is_valid(self) -> bool:
77
+ return not self.is_revoked and not self.is_expired()
78
+
79
+ def check(self) -> None:
80
+ """Raise if this authorization is not valid."""
81
+ if self.is_revoked:
82
+ raise AAPRevocationError(f"Authorization '{self.session_id}' has been revoked")
83
+ if self.is_expired():
84
+ raise AAPRevocationError(f"Authorization '{self.session_id}' has expired")
85
+
86
+ def to_dict(self) -> dict:
87
+ d = {
88
+ "aap_version": self.AAP_VERSION,
89
+ "agent_id": self.agent_id,
90
+ "level": int(self.level),
91
+ "level_name": self.level.name_str,
92
+ "scope": self.scope,
93
+ "physical": self.physical,
94
+ "granted_by": self.granted_by,
95
+ "granted_at": self.granted_at.isoformat(),
96
+ "session_id": self.session_id,
97
+ "signature": self.signature,
98
+ }
99
+ if self.expires_at:
100
+ d["expires_at"] = self.expires_at.isoformat()
101
+ return d
102
+
103
+ @classmethod
104
+ def from_dict(cls, data: dict) -> "AAPAuthorization":
105
+ required = {"aap_version", "agent_id", "level", "scope", "granted_by", "granted_at", "signature"}
106
+ missing = required - set(data.keys())
107
+ if missing:
108
+ raise AAPValidationError(f"Missing required fields: {missing}")
109
+ return cls(
110
+ agent_id=data["agent_id"],
111
+ level=AuthorizationLevel(data["level"]),
112
+ scope=data["scope"],
113
+ physical=data.get("physical", False),
114
+ granted_by=data["granted_by"],
115
+ granted_at=datetime.fromisoformat(data["granted_at"]),
116
+ signature=data["signature"],
117
+ session_id=data.get("session_id", str(uuid.uuid4())),
118
+ expires_at=datetime.fromisoformat(data["expires_at"]) if data.get("expires_at") else None,
119
+ )
120
+
121
+ def verify_signature(self, supervisor_public_key: str) -> None:
122
+ _verify_signature(supervisor_public_key, _signable(self.to_dict()), self.signature)
123
+
124
+ @classmethod
125
+ def create(
126
+ cls,
127
+ agent_id: str,
128
+ level: AuthorizationLevel,
129
+ scope: list[str],
130
+ supervisor_keypair: AAPKeyPair,
131
+ supervisor_did: str,
132
+ physical: bool = False,
133
+ expires_at: datetime | None = None,
134
+ ) -> "AAPAuthorization":
135
+ """Create and sign a new authorization. Raises AAPPhysicalWorldViolation if applicable."""
136
+ now = datetime.now(timezone.utc)
137
+ auth = cls(
138
+ agent_id=agent_id,
139
+ level=level,
140
+ scope=scope,
141
+ physical=physical,
142
+ granted_by=supervisor_did,
143
+ granted_at=now,
144
+ signature="",
145
+ expires_at=expires_at,
146
+ )
147
+ doc = auth.to_dict()
148
+ auth.signature = supervisor_keypair.sign(_signable(doc))
149
+ return auth
@@ -0,0 +1,57 @@
1
+ """AAP exception hierarchy."""
2
+
3
+
4
+ class AAPError(Exception):
5
+ """Base class for all AAP errors."""
6
+ code: str = "AAP-000"
7
+
8
+ def __init__(self, message: str, field: str | None = None, detail: str | None = None):
9
+ super().__init__(message)
10
+ self.field = field
11
+ self.detail = detail
12
+
13
+ def to_dict(self) -> dict:
14
+ d = {"code": self.code, "message": str(self)}
15
+ if self.field:
16
+ d["field"] = self.field
17
+ if self.detail:
18
+ d["detail"] = self.detail
19
+ return d
20
+
21
+
22
+ class AAPValidationError(AAPError):
23
+ """Schema validation failed."""
24
+ code = "AAP-001"
25
+
26
+
27
+ class AAPSignatureError(AAPError):
28
+ """Cryptographic signature is invalid."""
29
+ code = "AAP-002"
30
+
31
+
32
+ class AAPPhysicalWorldViolation(AAPError):
33
+ """Physical World Rule violation: Level 4 forbidden for physical nodes."""
34
+ code = "AAP-003"
35
+
36
+ def __init__(self, agent_id: str):
37
+ super().__init__(
38
+ f"Physical World Rule: Autonomous (Level 4) is forbidden for physical agent '{agent_id}'. "
39
+ f"Maximum level for physical nodes is Supervised (Level 3). "
40
+ f"This rule is not configurable.",
41
+ field="level"
42
+ )
43
+
44
+
45
+ class AAPScopeError(AAPError):
46
+ """Action is outside the agent's authorized scope."""
47
+ code = "AAP-004"
48
+
49
+
50
+ class AAPRevocationError(AAPError):
51
+ """Identity or authorization has been revoked."""
52
+ code = "AAP-005"
53
+
54
+
55
+ class AAPChainError(AAPError):
56
+ """Audit chain integrity violation."""
57
+ code = "AAP-006"