nobulex 0.1.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.
- nobulex-0.1.0/PKG-INFO +118 -0
- nobulex-0.1.0/README.md +93 -0
- nobulex-0.1.0/nobulex/__init__.py +30 -0
- nobulex-0.1.0/nobulex/__main__.py +81 -0
- nobulex-0.1.0/nobulex/agent.py +90 -0
- nobulex-0.1.0/nobulex/chain.py +156 -0
- nobulex-0.1.0/nobulex/crewai.py +93 -0
- nobulex-0.1.0/nobulex/crypto.py +86 -0
- nobulex-0.1.0/nobulex/decorator.py +105 -0
- nobulex-0.1.0/nobulex/langchain.py +113 -0
- nobulex-0.1.0/nobulex/receipt.py +162 -0
- nobulex-0.1.0/nobulex/trust.py +68 -0
- nobulex-0.1.0/nobulex.egg-info/PKG-INFO +118 -0
- nobulex-0.1.0/nobulex.egg-info/SOURCES.txt +17 -0
- nobulex-0.1.0/nobulex.egg-info/dependency_links.txt +1 -0
- nobulex-0.1.0/nobulex.egg-info/requires.txt +2 -0
- nobulex-0.1.0/nobulex.egg-info/top_level.txt +1 -0
- nobulex-0.1.0/pyproject.toml +38 -0
- nobulex-0.1.0/setup.cfg +4 -0
nobulex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nobulex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Trust Capital for AI agents. Tamper-proof receipts for everything your AI agent does.
|
|
5
|
+
Author-email: Arian Gogani <nobulex.dev@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://nobulex.com
|
|
8
|
+
Project-URL: Repository, https://github.com/arian-gogani/nobulex
|
|
9
|
+
Project-URL: Documentation, https://nobulex.com/docs
|
|
10
|
+
Keywords: ai,agents,trust,accountability,receipts,verification
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Security :: Cryptography
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: cryptography>=41.0
|
|
24
|
+
Requires-Dist: rfc8785>=0.1.2
|
|
25
|
+
|
|
26
|
+
# nobulex
|
|
27
|
+
|
|
28
|
+
**Trust Capital for AI agents. Tamper-proof receipts for everything your AI agent does.**
|
|
29
|
+
|
|
30
|
+
Credit scores exist for people. They don't exist for machines. Until now.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install nobulex
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start (5 minutes)
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from nobulex import Agent
|
|
42
|
+
|
|
43
|
+
# Create an agent with a cryptographic identity
|
|
44
|
+
agent = Agent("my-agent")
|
|
45
|
+
|
|
46
|
+
# Every action generates a signed receipt
|
|
47
|
+
receipt = agent.act("send_email", scope="user@example.com")
|
|
48
|
+
|
|
49
|
+
# Receipts are cryptographically verifiable
|
|
50
|
+
assert receipt.verify() # Ed25519 signature check
|
|
51
|
+
print(receipt.action_ref) # SHA-256 hash of the action
|
|
52
|
+
|
|
53
|
+
# Trust Capital builds with every verified action
|
|
54
|
+
print(agent.trust_score) # 13.86
|
|
55
|
+
|
|
56
|
+
# Denied actions prove the system caught violations
|
|
57
|
+
agent.deny("delete_database", scope="production")
|
|
58
|
+
print(agent.trust_score) # 15.22
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## What is this?
|
|
62
|
+
|
|
63
|
+
Every time an AI agent does something, Nobulex generates a **cryptographic receipt**:
|
|
64
|
+
|
|
65
|
+
- **WHO** acted (agent_id)
|
|
66
|
+
- **WHAT** they did (action_type)
|
|
67
|
+
- **ON WHAT** (scope)
|
|
68
|
+
- **WHEN** (timestamp_ms)
|
|
69
|
+
- **WHETHER** it was allowed (verdict)
|
|
70
|
+
- **PROOF** it happened (Ed25519 signature + SHA-256 hash)
|
|
71
|
+
|
|
72
|
+
Receipts are tamper-proof. You can't edit them after the fact. You can't fake them. An independent verifier can check any receipt without trusting the agent.
|
|
73
|
+
|
|
74
|
+
Over time, receipts build into **Trust Capital** — a portable trust score that follows the agent across deployments. You can copy an agent's code, but you can't copy its credit score. The copy starts at zero.
|
|
75
|
+
|
|
76
|
+
## Use Cases
|
|
77
|
+
|
|
78
|
+
- **Audit trails**: Prove what your agent did to regulators (EU AI Act Article 12)
|
|
79
|
+
- **Agent-to-agent trust**: Agents verify each other's track records before collaborating
|
|
80
|
+
- **Compliance**: Tamper-evident records for financial, healthcare, and legal agents
|
|
81
|
+
- **Accountability**: When something goes wrong, receipts prove what happened
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### Agent
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from nobulex import Agent
|
|
89
|
+
|
|
90
|
+
agent = Agent("my-agent") # Create agent identity
|
|
91
|
+
receipt = agent.act("tool_call", # Record an action
|
|
92
|
+
scope="api.stripe.com")
|
|
93
|
+
agent.deny("unauthorized_action", # Record a caught violation
|
|
94
|
+
scope="admin_panel")
|
|
95
|
+
print(agent.trust_score) # Get Trust Capital score
|
|
96
|
+
print(agent.receipts) # Get all receipts
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Receipt
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from nobulex import Receipt, KeyPair
|
|
103
|
+
|
|
104
|
+
keys = KeyPair()
|
|
105
|
+
receipt = Receipt.create(
|
|
106
|
+
agent_id="agent-1",
|
|
107
|
+
action_type="send_email",
|
|
108
|
+
scope="user@example.com",
|
|
109
|
+
keys=keys,
|
|
110
|
+
)
|
|
111
|
+
assert receipt.verify() # Verify signature
|
|
112
|
+
print(receipt.action_ref) # Content-addressable hash
|
|
113
|
+
print(receipt.to_json()) # JSON serialization
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT — Arian Gogani (@nobulexlabs)
|
nobulex-0.1.0/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# nobulex
|
|
2
|
+
|
|
3
|
+
**Trust Capital for AI agents. Tamper-proof receipts for everything your AI agent does.**
|
|
4
|
+
|
|
5
|
+
Credit scores exist for people. They don't exist for machines. Until now.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install nobulex
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start (5 minutes)
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from nobulex import Agent
|
|
17
|
+
|
|
18
|
+
# Create an agent with a cryptographic identity
|
|
19
|
+
agent = Agent("my-agent")
|
|
20
|
+
|
|
21
|
+
# Every action generates a signed receipt
|
|
22
|
+
receipt = agent.act("send_email", scope="user@example.com")
|
|
23
|
+
|
|
24
|
+
# Receipts are cryptographically verifiable
|
|
25
|
+
assert receipt.verify() # Ed25519 signature check
|
|
26
|
+
print(receipt.action_ref) # SHA-256 hash of the action
|
|
27
|
+
|
|
28
|
+
# Trust Capital builds with every verified action
|
|
29
|
+
print(agent.trust_score) # 13.86
|
|
30
|
+
|
|
31
|
+
# Denied actions prove the system caught violations
|
|
32
|
+
agent.deny("delete_database", scope="production")
|
|
33
|
+
print(agent.trust_score) # 15.22
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What is this?
|
|
37
|
+
|
|
38
|
+
Every time an AI agent does something, Nobulex generates a **cryptographic receipt**:
|
|
39
|
+
|
|
40
|
+
- **WHO** acted (agent_id)
|
|
41
|
+
- **WHAT** they did (action_type)
|
|
42
|
+
- **ON WHAT** (scope)
|
|
43
|
+
- **WHEN** (timestamp_ms)
|
|
44
|
+
- **WHETHER** it was allowed (verdict)
|
|
45
|
+
- **PROOF** it happened (Ed25519 signature + SHA-256 hash)
|
|
46
|
+
|
|
47
|
+
Receipts are tamper-proof. You can't edit them after the fact. You can't fake them. An independent verifier can check any receipt without trusting the agent.
|
|
48
|
+
|
|
49
|
+
Over time, receipts build into **Trust Capital** — a portable trust score that follows the agent across deployments. You can copy an agent's code, but you can't copy its credit score. The copy starts at zero.
|
|
50
|
+
|
|
51
|
+
## Use Cases
|
|
52
|
+
|
|
53
|
+
- **Audit trails**: Prove what your agent did to regulators (EU AI Act Article 12)
|
|
54
|
+
- **Agent-to-agent trust**: Agents verify each other's track records before collaborating
|
|
55
|
+
- **Compliance**: Tamper-evident records for financial, healthcare, and legal agents
|
|
56
|
+
- **Accountability**: When something goes wrong, receipts prove what happened
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### Agent
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from nobulex import Agent
|
|
64
|
+
|
|
65
|
+
agent = Agent("my-agent") # Create agent identity
|
|
66
|
+
receipt = agent.act("tool_call", # Record an action
|
|
67
|
+
scope="api.stripe.com")
|
|
68
|
+
agent.deny("unauthorized_action", # Record a caught violation
|
|
69
|
+
scope="admin_panel")
|
|
70
|
+
print(agent.trust_score) # Get Trust Capital score
|
|
71
|
+
print(agent.receipts) # Get all receipts
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Receipt
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from nobulex import Receipt, KeyPair
|
|
78
|
+
|
|
79
|
+
keys = KeyPair()
|
|
80
|
+
receipt = Receipt.create(
|
|
81
|
+
agent_id="agent-1",
|
|
82
|
+
action_type="send_email",
|
|
83
|
+
scope="user@example.com",
|
|
84
|
+
keys=keys,
|
|
85
|
+
)
|
|
86
|
+
assert receipt.verify() # Verify signature
|
|
87
|
+
print(receipt.action_ref) # Content-addressable hash
|
|
88
|
+
print(receipt.to_json()) # JSON serialization
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT — Arian Gogani (@nobulexlabs)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nobulex - Trust Capital for AI Agents
|
|
3
|
+
|
|
4
|
+
Tamper-proof receipts for everything your AI agent does.
|
|
5
|
+
Credit scores for machines.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from nobulex import Agent, Receipt
|
|
9
|
+
|
|
10
|
+
# Create an agent identity
|
|
11
|
+
agent = Agent("my-agent")
|
|
12
|
+
|
|
13
|
+
# Generate a receipt for any action
|
|
14
|
+
receipt = agent.act("send_email", scope="user@example.com")
|
|
15
|
+
|
|
16
|
+
# Verify any receipt
|
|
17
|
+
assert receipt.verify()
|
|
18
|
+
|
|
19
|
+
# Get trust score
|
|
20
|
+
print(agent.trust_score)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from nobulex.agent import Agent
|
|
24
|
+
from nobulex.receipt import Receipt
|
|
25
|
+
from nobulex.trust import TrustLedger
|
|
26
|
+
from nobulex.crypto import KeyPair
|
|
27
|
+
from nobulex.decorator import track
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
__all__ = ["Agent", "Receipt", "TrustLedger", "KeyPair", "track"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Nobulex CLI - Verify receipts from the command line.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python -m nobulex verify receipt.json
|
|
7
|
+
python -m nobulex demo
|
|
8
|
+
python -m nobulex keygen
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import json
|
|
13
|
+
from nobulex import Agent, Receipt, KeyPair
|
|
14
|
+
from nobulex.chain import ReceiptChain
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cmd_demo():
|
|
18
|
+
"""Run a quick demo showing receipt generation and verification."""
|
|
19
|
+
print("nobulex demo")
|
|
20
|
+
print("=" * 40)
|
|
21
|
+
agent = Agent("demo-agent")
|
|
22
|
+
r1 = agent.act("send_email", scope="user@example.com")
|
|
23
|
+
r2 = agent.act("api_call", scope="stripe.com")
|
|
24
|
+
r3 = agent.deny("delete_db", scope="production")
|
|
25
|
+
print(f"generated 3 receipts")
|
|
26
|
+
print(f" allow: {r1.action_ref[:24]}... verified={r1.verify()}")
|
|
27
|
+
print(f" allow: {r2.action_ref[:24]}... verified={r2.verify()}")
|
|
28
|
+
print(f" deny: {r3.action_ref[:24]}... verified={r3.verify()}")
|
|
29
|
+
print(f"trust score: {agent.trust_score}")
|
|
30
|
+
print(f"\ntamper test:")
|
|
31
|
+
r1.scope = "TAMPERED"
|
|
32
|
+
print(f" modified receipt verified={r1.verify()} (tamper detected)")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cmd_verify(filepath):
|
|
36
|
+
"""Verify a receipt JSON file."""
|
|
37
|
+
try:
|
|
38
|
+
with open(filepath) as f:
|
|
39
|
+
data = json.load(f)
|
|
40
|
+
receipt = Receipt.from_dict(data)
|
|
41
|
+
ok = receipt.verify()
|
|
42
|
+
print(f"receipt: {filepath}")
|
|
43
|
+
print(f" agent: {receipt.agent_id}")
|
|
44
|
+
print(f" action: {receipt.action_type}")
|
|
45
|
+
print(f" scope: {receipt.scope}")
|
|
46
|
+
print(f" verdict: {receipt.verdict}")
|
|
47
|
+
print(f" action_ref: {receipt.action_ref[:32]}...")
|
|
48
|
+
print(f" verified: {ok}")
|
|
49
|
+
sys.exit(0 if ok else 1)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"error: {e}")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cmd_keygen():
|
|
56
|
+
"""Generate a new Ed25519 key pair."""
|
|
57
|
+
keys = KeyPair()
|
|
58
|
+
print(f"public_key: {keys.public_hex}")
|
|
59
|
+
print("(private key held in memory only)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main():
|
|
63
|
+
if len(sys.argv) < 2:
|
|
64
|
+
print("usage: python -m nobulex [demo|verify|keygen]")
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
cmd = sys.argv[1]
|
|
68
|
+
if cmd == "demo":
|
|
69
|
+
cmd_demo()
|
|
70
|
+
elif cmd == "verify" and len(sys.argv) >= 3:
|
|
71
|
+
cmd_verify(sys.argv[2])
|
|
72
|
+
elif cmd == "keygen":
|
|
73
|
+
cmd_keygen()
|
|
74
|
+
else:
|
|
75
|
+
print(f"unknown command: {cmd}")
|
|
76
|
+
print("usage: python -m nobulex [demo|verify|keygen]")
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Agent identity and receipt management for Nobulex."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from nobulex.crypto import KeyPair
|
|
7
|
+
from nobulex.receipt import Receipt
|
|
8
|
+
from nobulex.trust import TrustLedger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Agent:
|
|
12
|
+
"""
|
|
13
|
+
An AI agent with a cryptographic identity and trust history.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
agent = Agent("my-agent")
|
|
17
|
+
receipt = agent.act("send_email", scope="user@example.com")
|
|
18
|
+
assert receipt.verify()
|
|
19
|
+
print(agent.trust_score)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, agent_id: str, keys: Optional[KeyPair] = None):
|
|
23
|
+
self.agent_id = agent_id
|
|
24
|
+
self.keys = keys or KeyPair()
|
|
25
|
+
self._ledger = TrustLedger()
|
|
26
|
+
self._receipts: List[Receipt] = []
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def public_key(self) -> str:
|
|
30
|
+
return self.keys.public_hex
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def trust_score(self) -> float:
|
|
34
|
+
return self._ledger.score(self.agent_id)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def receipts(self) -> List[Receipt]:
|
|
38
|
+
return list(self._receipts)
|
|
39
|
+
|
|
40
|
+
def act(
|
|
41
|
+
self,
|
|
42
|
+
action_type: str,
|
|
43
|
+
scope: str,
|
|
44
|
+
verdict: str = "ALLOW",
|
|
45
|
+
metadata: Optional[dict] = None,
|
|
46
|
+
) -> Receipt:
|
|
47
|
+
"""
|
|
48
|
+
Record an agent action and generate a signed receipt.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
action_type: What the agent did (e.g., "send_email")
|
|
52
|
+
scope: What resource was acted on
|
|
53
|
+
verdict: "ALLOW" or "DENY"
|
|
54
|
+
metadata: Optional extra data
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A signed, verifiable Receipt
|
|
58
|
+
"""
|
|
59
|
+
receipt = Receipt.create(
|
|
60
|
+
agent_id=self.agent_id,
|
|
61
|
+
action_type=action_type,
|
|
62
|
+
scope=scope,
|
|
63
|
+
keys=self.keys,
|
|
64
|
+
verdict=verdict,
|
|
65
|
+
metadata=metadata,
|
|
66
|
+
)
|
|
67
|
+
self._receipts.append(receipt)
|
|
68
|
+
self._ledger.record(receipt)
|
|
69
|
+
return receipt
|
|
70
|
+
|
|
71
|
+
def deny(
|
|
72
|
+
self, action_type: str, scope: str, metadata: Optional[dict] = None
|
|
73
|
+
) -> Receipt:
|
|
74
|
+
"""Record a denied action. Proves the system caught a violation."""
|
|
75
|
+
return self.act(action_type, scope, verdict="DENY", metadata=metadata)
|
|
76
|
+
|
|
77
|
+
def verify_receipt(self, receipt: Receipt) -> bool:
|
|
78
|
+
"""Verify a receipt was signed by this agent."""
|
|
79
|
+
return (
|
|
80
|
+
receipt.verify()
|
|
81
|
+
and receipt.signer_public_key == self.public_key
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def __repr__(self) -> str:
|
|
85
|
+
n = len(self._receipts)
|
|
86
|
+
return (
|
|
87
|
+
f"Agent(id={self.agent_id!r}, "
|
|
88
|
+
f"receipts={n}, "
|
|
89
|
+
f"trust_score={self.trust_score:.2f})"
|
|
90
|
+
)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Receipt chain for tamper-evident audit trails.
|
|
2
|
+
|
|
3
|
+
Links receipts together with hash pointers so tampering
|
|
4
|
+
with any single receipt breaks the entire chain.
|
|
5
|
+
|
|
6
|
+
from nobulex.chain import ReceiptChain
|
|
7
|
+
|
|
8
|
+
chain = ReceiptChain(agent_id="my-agent")
|
|
9
|
+
chain.append("send_email", scope="user@example.com")
|
|
10
|
+
chain.append("api_call", scope="stripe.com")
|
|
11
|
+
|
|
12
|
+
assert chain.verify() # Verify entire chain
|
|
13
|
+
|
|
14
|
+
# Export the audit trail
|
|
15
|
+
chain.export("audit-trail.json")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from typing import List, Optional
|
|
20
|
+
from nobulex.agent import Agent
|
|
21
|
+
from nobulex.receipt import Receipt
|
|
22
|
+
from nobulex.crypto import sha256_hex, jcs_canonicalize
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ReceiptChain:
|
|
26
|
+
"""
|
|
27
|
+
A hash-linked chain of receipts.
|
|
28
|
+
Tampering with any receipt breaks the chain.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, agent_id: str = "chain-agent"):
|
|
32
|
+
self.agent = Agent(agent_id)
|
|
33
|
+
self._chain: List[dict] = []
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def length(self) -> int:
|
|
37
|
+
return len(self._chain)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def head_hash(self) -> Optional[str]:
|
|
41
|
+
if not self._chain:
|
|
42
|
+
return None
|
|
43
|
+
return self._chain[-1]["chain_hash"]
|
|
44
|
+
|
|
45
|
+
def append(
|
|
46
|
+
self,
|
|
47
|
+
action_type: str,
|
|
48
|
+
scope: str,
|
|
49
|
+
verdict: str = "ALLOW",
|
|
50
|
+
metadata: Optional[dict] = None,
|
|
51
|
+
) -> Receipt:
|
|
52
|
+
"""Add an action to the chain."""
|
|
53
|
+
if verdict == "ALLOW":
|
|
54
|
+
receipt = self.agent.act(action_type, scope=scope, metadata=metadata)
|
|
55
|
+
else:
|
|
56
|
+
receipt = self.agent.deny(action_type, scope=scope, metadata=metadata)
|
|
57
|
+
|
|
58
|
+
# Compute chain hash: SHA-256(prev_hash + receipt.action_ref)
|
|
59
|
+
prev_hash = self.head_hash or "0" * 64
|
|
60
|
+
chain_input = prev_hash + receipt.action_ref
|
|
61
|
+
chain_hash = sha256_hex(chain_input)
|
|
62
|
+
|
|
63
|
+
self._chain.append({
|
|
64
|
+
"index": len(self._chain),
|
|
65
|
+
"receipt": receipt,
|
|
66
|
+
"prev_hash": prev_hash,
|
|
67
|
+
"chain_hash": chain_hash,
|
|
68
|
+
})
|
|
69
|
+
return receipt
|
|
70
|
+
|
|
71
|
+
def verify(self) -> bool:
|
|
72
|
+
"""Verify the entire chain is intact."""
|
|
73
|
+
if not self._chain:
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
for i, entry in enumerate(self._chain):
|
|
77
|
+
# Verify receipt signature
|
|
78
|
+
if not entry["receipt"].verify():
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
# Verify chain hash
|
|
82
|
+
prev = self._chain[i - 1]["chain_hash"] if i > 0 else "0" * 64
|
|
83
|
+
expected = sha256_hex(prev + entry["receipt"].action_ref)
|
|
84
|
+
if entry["chain_hash"] != expected:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
def export(self, filepath: str) -> None:
|
|
90
|
+
"""Export the chain as a JSON audit trail."""
|
|
91
|
+
data = {
|
|
92
|
+
"agent_id": self.agent.agent_id,
|
|
93
|
+
"chain_length": len(self._chain),
|
|
94
|
+
"head_hash": self.head_hash,
|
|
95
|
+
"verified": self.verify(),
|
|
96
|
+
"entries": [
|
|
97
|
+
{
|
|
98
|
+
"index": e["index"],
|
|
99
|
+
"action_type": e["receipt"].action_type,
|
|
100
|
+
"scope": e["receipt"].scope,
|
|
101
|
+
"verdict": e["receipt"].verdict,
|
|
102
|
+
"action_ref": e["receipt"].action_ref,
|
|
103
|
+
"timestamp_ms": e["receipt"].timestamp_ms,
|
|
104
|
+
"prev_hash": e["prev_hash"],
|
|
105
|
+
"chain_hash": e["chain_hash"],
|
|
106
|
+
}
|
|
107
|
+
for e in self._chain
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
with open(filepath, "w") as f:
|
|
111
|
+
json.dump(data, f, indent=2)
|
|
112
|
+
|
|
113
|
+
def __repr__(self) -> str:
|
|
114
|
+
v = "verified" if self.verify() else "BROKEN"
|
|
115
|
+
return f"ReceiptChain(agent={self.agent.agent_id!r}, length={self.length}, status={v})"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def verify_audit_trail(filepath: str) -> dict:
|
|
119
|
+
"""Verify an exported audit trail JSON file.
|
|
120
|
+
|
|
121
|
+
Checks hash chain integrity (prev_hash linkage).
|
|
122
|
+
Returns a verification report.
|
|
123
|
+
"""
|
|
124
|
+
import json
|
|
125
|
+
import hashlib
|
|
126
|
+
|
|
127
|
+
with open(filepath) as f:
|
|
128
|
+
data = json.load(f)
|
|
129
|
+
|
|
130
|
+
entries = data.get("entries", [])
|
|
131
|
+
results = []
|
|
132
|
+
all_valid = True
|
|
133
|
+
prev = "0" * 64
|
|
134
|
+
|
|
135
|
+
for entry in entries:
|
|
136
|
+
# Verify chain linkage
|
|
137
|
+
chain_ok = entry.get("prev_hash") == prev
|
|
138
|
+
if not chain_ok:
|
|
139
|
+
all_valid = False
|
|
140
|
+
|
|
141
|
+
results.append({
|
|
142
|
+
"index": entry.get("index", len(results)),
|
|
143
|
+
"action_type": entry.get("action_type", ""),
|
|
144
|
+
"verdict": entry.get("verdict", ""),
|
|
145
|
+
"action_ref": entry.get("action_ref", "")[:32] + "...",
|
|
146
|
+
"chain_valid": chain_ok,
|
|
147
|
+
})
|
|
148
|
+
prev = entry.get("chain_hash", "")
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"file": filepath,
|
|
152
|
+
"total_receipts": len(results),
|
|
153
|
+
"chain_intact": all_valid,
|
|
154
|
+
"head_hash": data.get("head_hash", ""),
|
|
155
|
+
"receipts": results,
|
|
156
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""CrewAI integration for Nobulex receipts.
|
|
2
|
+
|
|
3
|
+
Add tamper-proof receipts to any CrewAI crew:
|
|
4
|
+
|
|
5
|
+
from nobulex.crewai import NobuCrewTracker
|
|
6
|
+
tracker = NobuCrewTracker("my-crew")
|
|
7
|
+
|
|
8
|
+
# Track agent actions
|
|
9
|
+
tracker.on_task_start(agent_name="researcher", task="find data")
|
|
10
|
+
tracker.on_task_complete(agent_name="researcher", task="find data", result="found 10 results")
|
|
11
|
+
tracker.on_task_fail(agent_name="writer", task="write report", error="timeout")
|
|
12
|
+
|
|
13
|
+
print(tracker.trust_scores) # Per-agent trust scores
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Dict, List, Optional
|
|
17
|
+
from nobulex.agent import Agent
|
|
18
|
+
from nobulex.receipt import Receipt
|
|
19
|
+
from nobulex.trust import TrustLedger
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NobuCrewTracker:
|
|
23
|
+
"""Track trust across a crew of AI agents."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, crew_id: str = "crewai-crew"):
|
|
26
|
+
self.crew_id = crew_id
|
|
27
|
+
self._agents: Dict[str, Agent] = {}
|
|
28
|
+
self._all_receipts: List[Receipt] = []
|
|
29
|
+
|
|
30
|
+
def _get_agent(self, agent_name: str) -> Agent:
|
|
31
|
+
if agent_name not in self._agents:
|
|
32
|
+
agent_id = f"{self.crew_id}:{agent_name}"
|
|
33
|
+
self._agents[agent_name] = Agent(agent_id)
|
|
34
|
+
return self._agents[agent_name]
|
|
35
|
+
|
|
36
|
+
def on_task_start(
|
|
37
|
+
self, agent_name: str, task: str, metadata: Optional[dict] = None
|
|
38
|
+
) -> Receipt:
|
|
39
|
+
agent = self._get_agent(agent_name)
|
|
40
|
+
receipt = agent.act(
|
|
41
|
+
action_type=f"task:{task}",
|
|
42
|
+
scope=f"crew:{self.crew_id}",
|
|
43
|
+
metadata=metadata or {},
|
|
44
|
+
)
|
|
45
|
+
self._all_receipts.append(receipt)
|
|
46
|
+
return receipt
|
|
47
|
+
|
|
48
|
+
def on_task_complete(
|
|
49
|
+
self, agent_name: str, task: str, result: str = "", metadata: Optional[dict] = None
|
|
50
|
+
) -> Receipt:
|
|
51
|
+
agent = self._get_agent(agent_name)
|
|
52
|
+
meta = metadata or {}
|
|
53
|
+
meta["result_preview"] = result[:200]
|
|
54
|
+
receipt = agent.act(
|
|
55
|
+
action_type=f"complete:{task}",
|
|
56
|
+
scope=f"crew:{self.crew_id}",
|
|
57
|
+
metadata=meta,
|
|
58
|
+
)
|
|
59
|
+
self._all_receipts.append(receipt)
|
|
60
|
+
return receipt
|
|
61
|
+
|
|
62
|
+
def on_task_fail(
|
|
63
|
+
self, agent_name: str, task: str, error: str = "", metadata: Optional[dict] = None
|
|
64
|
+
) -> Receipt:
|
|
65
|
+
agent = self._get_agent(agent_name)
|
|
66
|
+
meta = metadata or {}
|
|
67
|
+
meta["error"] = error[:200]
|
|
68
|
+
receipt = agent.deny(
|
|
69
|
+
action_type=f"fail:{task}",
|
|
70
|
+
scope=f"crew:{self.crew_id}",
|
|
71
|
+
metadata=meta,
|
|
72
|
+
)
|
|
73
|
+
self._all_receipts.append(receipt)
|
|
74
|
+
return receipt
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def trust_scores(self) -> Dict[str, float]:
|
|
78
|
+
return {name: a.trust_score for name, a in self._agents.items()}
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def receipts(self) -> List[Receipt]:
|
|
82
|
+
return list(self._all_receipts)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def agent_names(self) -> List[str]:
|
|
86
|
+
return list(self._agents.keys())
|
|
87
|
+
|
|
88
|
+
def summary(self) -> str:
|
|
89
|
+
lines = [f"Crew: {self.crew_id}", f"Agents: {len(self._agents)}", f"Total receipts: {len(self._all_receipts)}", ""]
|
|
90
|
+
for name, agent in self._agents.items():
|
|
91
|
+
n = len(agent.receipts)
|
|
92
|
+
lines.append(f" {name}: {n} receipts, trust={agent.trust_score:.1f}")
|
|
93
|
+
return "\n".join(lines)
|