agent_hypervisor 3.1.0__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 (60) hide show
  1. agent_hypervisor-3.1.0.dist-info/METADATA +824 -0
  2. agent_hypervisor-3.1.0.dist-info/RECORD +60 -0
  3. agent_hypervisor-3.1.0.dist-info/WHEEL +4 -0
  4. agent_hypervisor-3.1.0.dist-info/entry_points.txt +2 -0
  5. agent_hypervisor-3.1.0.dist-info/licenses/LICENSE +21 -0
  6. hypervisor/__init__.py +160 -0
  7. hypervisor/api/__init__.py +7 -0
  8. hypervisor/api/models.py +285 -0
  9. hypervisor/api/server.py +742 -0
  10. hypervisor/audit/__init__.py +4 -0
  11. hypervisor/audit/commitment.py +76 -0
  12. hypervisor/audit/delta.py +135 -0
  13. hypervisor/audit/gc.py +99 -0
  14. hypervisor/cli/__init__.py +3 -0
  15. hypervisor/cli/formatters.py +99 -0
  16. hypervisor/cli/session_commands.py +200 -0
  17. hypervisor/constants.py +106 -0
  18. hypervisor/core.py +352 -0
  19. hypervisor/integrations/__init__.py +10 -0
  20. hypervisor/integrations/iatp_adapter.py +142 -0
  21. hypervisor/integrations/nexus_adapter.py +108 -0
  22. hypervisor/integrations/verification_adapter.py +122 -0
  23. hypervisor/liability/__init__.py +142 -0
  24. hypervisor/liability/attribution.py +86 -0
  25. hypervisor/liability/ledger.py +121 -0
  26. hypervisor/liability/quarantine.py +119 -0
  27. hypervisor/liability/slashing.py +80 -0
  28. hypervisor/liability/vouching.py +134 -0
  29. hypervisor/models.py +277 -0
  30. hypervisor/observability/__init__.py +27 -0
  31. hypervisor/observability/causal_trace.py +70 -0
  32. hypervisor/observability/event_bus.py +222 -0
  33. hypervisor/observability/prometheus_collector.py +248 -0
  34. hypervisor/observability/saga_span_exporter.py +341 -0
  35. hypervisor/providers.py +121 -0
  36. hypervisor/py.typed +0 -0
  37. hypervisor/reversibility/__init__.py +3 -0
  38. hypervisor/reversibility/registry.py +108 -0
  39. hypervisor/rings/__init__.py +21 -0
  40. hypervisor/rings/breach_detector.py +200 -0
  41. hypervisor/rings/classifier.py +78 -0
  42. hypervisor/rings/elevation.py +219 -0
  43. hypervisor/rings/enforcer.py +97 -0
  44. hypervisor/saga/__init__.py +22 -0
  45. hypervisor/saga/checkpoint.py +110 -0
  46. hypervisor/saga/dsl.py +190 -0
  47. hypervisor/saga/fan_out.py +126 -0
  48. hypervisor/saga/orchestrator.py +229 -0
  49. hypervisor/saga/schema.py +244 -0
  50. hypervisor/saga/state_machine.py +157 -0
  51. hypervisor/security/__init__.py +13 -0
  52. hypervisor/security/kill_switch.py +200 -0
  53. hypervisor/security/rate_limiter.py +190 -0
  54. hypervisor/session/__init__.py +194 -0
  55. hypervisor/session/intent_locks.py +118 -0
  56. hypervisor/session/isolation.py +37 -0
  57. hypervisor/session/sso.py +169 -0
  58. hypervisor/session/vector_clock.py +118 -0
  59. hypervisor/verification/__init__.py +3 -0
  60. hypervisor/verification/history.py +173 -0
@@ -0,0 +1,169 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """Session-scoped VFS — simple dict-based storage."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import collections
9
+ import hashlib
10
+ import uuid
11
+ from dataclasses import dataclass, field
12
+ from datetime import UTC, datetime
13
+ from typing import Any
14
+
15
+
16
+ @dataclass
17
+ class VFSEdit:
18
+ """A tracked edit to the session VFS."""
19
+
20
+ path: str
21
+ operation: str # "create", "update", "delete", "permission", "restore"
22
+ agent_did: str
23
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
24
+ content_hash: str | None = None
25
+ previous_hash: str | None = None
26
+
27
+
28
+ class VFSPermissionError(Exception):
29
+ """Raised when an agent lacks permission to access a VFS path."""
30
+
31
+
32
+ class SessionVFS:
33
+ """
34
+ Simple dict-based session storage.
35
+
36
+ Public Preview: basic get/set/delete with a single global lock
37
+ (threading.Lock omitted since Python GIL provides serialization).
38
+ """
39
+
40
+ def __init__(self, session_id: str, namespace: str | None = None):
41
+ self.session_id = session_id
42
+ self.namespace = namespace or f"/sessions/{session_id}"
43
+ self._files: dict[str, str] = {}
44
+ self._permissions: dict[str, set[str]] = {}
45
+ self._edit_log: collections.deque[VFSEdit] = collections.deque(maxlen=10_000)
46
+ self._snapshots: dict[str, dict[str, Any]] = {}
47
+
48
+ def write(self, path: str, content: str, agent_did: str) -> VFSEdit:
49
+ """Write a file."""
50
+ full_path = self._resolve(path)
51
+ self._check_permission(full_path, agent_did)
52
+ operation = "update" if full_path in self._files else "create"
53
+ previous_hash = _hash(self._files.get(full_path, "")) if operation == "update" else None
54
+ self._files[full_path] = content
55
+ edit = VFSEdit(
56
+ path=full_path,
57
+ operation=operation,
58
+ agent_did=agent_did,
59
+ content_hash=_hash(content),
60
+ previous_hash=previous_hash,
61
+ )
62
+ self._edit_log.append(edit)
63
+ return edit
64
+
65
+ def read(self, path: str, agent_did: str | None = None) -> str | None:
66
+ """Read a file."""
67
+ full_path = self._resolve(path)
68
+ if agent_did is not None:
69
+ self._check_permission(full_path, agent_did)
70
+ return self._files.get(full_path)
71
+
72
+ def delete(self, path: str, agent_did: str) -> VFSEdit:
73
+ """Delete a file."""
74
+ full_path = self._resolve(path)
75
+ if full_path not in self._files:
76
+ raise FileNotFoundError(f"{full_path} not found in session VFS")
77
+ self._check_permission(full_path, agent_did)
78
+ previous_hash = _hash(self._files.pop(full_path))
79
+ self._permissions.pop(full_path, None)
80
+ edit = VFSEdit(
81
+ path=full_path,
82
+ operation="delete",
83
+ agent_did=agent_did,
84
+ previous_hash=previous_hash,
85
+ )
86
+ self._edit_log.append(edit)
87
+ return edit
88
+
89
+ def list_files(self) -> list[str]:
90
+ """List all files in the session VFS."""
91
+ prefix = self.namespace
92
+ return [p.removeprefix(prefix) for p in self._files if p.startswith(prefix)]
93
+
94
+ def set_permissions(
95
+ self, path: str, allowed_agents: set[str], agent_did: str
96
+ ) -> VFSEdit:
97
+ """Set path-level permissions."""
98
+ full_path = self._resolve(path)
99
+ self._permissions[full_path] = set(allowed_agents)
100
+ edit = VFSEdit(path=full_path, operation="permission", agent_did=agent_did)
101
+ self._edit_log.append(edit)
102
+ return edit
103
+
104
+ def clear_permissions(self, path: str) -> None:
105
+ full_path = self._resolve(path)
106
+ self._permissions.pop(full_path, None)
107
+
108
+ def get_permissions(self, path: str) -> set[str] | None:
109
+ return self._permissions.get(self._resolve(path))
110
+
111
+ def create_snapshot(self, snapshot_id: str | None = None) -> str:
112
+ """Snapshot current state (simple deep copy)."""
113
+ sid = snapshot_id or f"snap:{uuid.uuid4()}"
114
+ self._snapshots[sid] = {
115
+ "files": dict(self._files),
116
+ "permissions": {k: set(v) for k, v in self._permissions.items()},
117
+ }
118
+ return sid
119
+
120
+ def restore_snapshot(self, snapshot_id: str, agent_did: str) -> None:
121
+ """Restore VFS to a previous snapshot."""
122
+ if snapshot_id not in self._snapshots:
123
+ raise KeyError(f"Snapshot {snapshot_id} not found")
124
+ snapshot = self._snapshots[snapshot_id]
125
+ self._files = dict(snapshot["files"])
126
+ self._permissions = {k: set(v) for k, v in snapshot["permissions"].items()}
127
+ self._edit_log.append(VFSEdit(
128
+ path=self.namespace, operation="restore", agent_did=agent_did,
129
+ ))
130
+
131
+ def list_snapshots(self) -> list[str]:
132
+ return list(self._snapshots.keys())
133
+
134
+ def delete_snapshot(self, snapshot_id: str) -> None:
135
+ if snapshot_id not in self._snapshots:
136
+ raise KeyError(f"Snapshot {snapshot_id} not found")
137
+ del self._snapshots[snapshot_id]
138
+
139
+ @property
140
+ def edit_log(self) -> list[VFSEdit]:
141
+ return list(self._edit_log)
142
+
143
+ def edits_by_agent(self, agent_did: str) -> list[VFSEdit]:
144
+ return [e for e in self._edit_log if e.agent_did == agent_did]
145
+
146
+ @property
147
+ def file_count(self) -> int:
148
+ return len(self._files)
149
+
150
+ @property
151
+ def snapshot_count(self) -> int:
152
+ return len(self._snapshots)
153
+
154
+ def _resolve(self, path: str) -> str:
155
+ if path.startswith(self.namespace):
156
+ return path
157
+ clean = path.lstrip("/")
158
+ return f"{self.namespace}/{clean}"
159
+
160
+ def _check_permission(self, full_path: str, agent_did: str) -> None:
161
+ allowed = self._permissions.get(full_path)
162
+ if allowed is not None and agent_did not in allowed:
163
+ raise VFSPermissionError(
164
+ f"Agent {agent_did} not permitted to access {full_path}"
165
+ )
166
+
167
+
168
+ def _hash(content: str) -> str:
169
+ return hashlib.sha256(content.encode()).hexdigest()
@@ -0,0 +1,118 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """
5
+ Version Counters — stub implementation.
6
+
7
+ Public Preview: no causal consistency enforcement.
8
+ VectorClock and VectorClockManager are retained for API compatibility.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import threading
14
+ from dataclasses import dataclass, field
15
+
16
+
17
+ class CausalViolationError(Exception):
18
+ """Raised when a write would violate causal ordering."""
19
+
20
+
21
+ @dataclass
22
+ class VectorClock:
23
+ """A version counter (Public Preview: tracking only, no enforcement).
24
+
25
+ Thread-safe: all reads and mutations are guarded by an internal lock
26
+ to prevent data races when multiple agents tick/merge concurrently.
27
+ """
28
+
29
+ clocks: dict[str, int] = field(default_factory=dict)
30
+ _lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
31
+
32
+ def tick(self, agent_did: str) -> None:
33
+ """Increment the clock for an agent."""
34
+ with self._lock:
35
+ self.clocks[agent_did] = self.clocks.get(agent_did, 0) + 1
36
+
37
+ def get(self, agent_did: str) -> int:
38
+ with self._lock:
39
+ return self.clocks.get(agent_did, 0)
40
+
41
+ def merge(self, other: VectorClock) -> VectorClock:
42
+ """Merge two version counters (take component-wise max).
43
+
44
+ Acquires locks on both clocks to get consistent snapshots.
45
+ """
46
+ # Deterministic lock ordering by id() to prevent deadlocks
47
+ first, second = sorted([self, other], key=id)
48
+ with first._lock:
49
+ with second._lock:
50
+ merged_clocks = dict(self.clocks)
51
+ for agent, clock in other.clocks.items():
52
+ merged_clocks[agent] = max(merged_clocks.get(agent, 0), clock)
53
+ return VectorClock(clocks=merged_clocks)
54
+
55
+ def happens_before(self, other: VectorClock) -> bool:
56
+ return False
57
+
58
+ def is_concurrent(self, other: VectorClock) -> bool:
59
+ return False
60
+
61
+ def copy(self) -> VectorClock:
62
+ with self._lock:
63
+ return VectorClock(clocks=dict(self.clocks))
64
+
65
+ def __eq__(self, other: object) -> bool:
66
+ if not isinstance(other, VectorClock):
67
+ return False
68
+ first, second = sorted([self, other], key=id)
69
+ with first._lock:
70
+ with second._lock:
71
+ all_agents = set(self.clocks.keys()) | set(other.clocks.keys())
72
+ return all(
73
+ self.clocks.get(a, 0) == other.clocks.get(a, 0)
74
+ for a in all_agents
75
+ )
76
+
77
+
78
+ class VectorClockManager:
79
+ """
80
+ Version counter stub (Public Preview: no causal enforcement).
81
+ Reads and writes always succeed.
82
+ """
83
+
84
+ def __init__(self) -> None:
85
+ self._path_clocks: dict[str, VectorClock] = {}
86
+ self._agent_clocks: dict[str, VectorClock] = {}
87
+ self._conflict_count: int = 0
88
+
89
+ def read(self, path: str, agent_did: str) -> VectorClock:
90
+ """Record a read (no enforcement)."""
91
+ return self._path_clocks.get(path, VectorClock()).copy()
92
+
93
+ def write(
94
+ self,
95
+ path: str,
96
+ agent_did: str,
97
+ strict: bool = True,
98
+ ) -> VectorClock:
99
+ """Record a write (Public Preview: never rejects)."""
100
+ agent_clock = self._agent_clocks.get(agent_did, VectorClock())
101
+ agent_clock.tick(agent_did)
102
+ self._path_clocks[path] = agent_clock.copy()
103
+ self._agent_clocks[agent_did] = agent_clock
104
+ return self._path_clocks[path]
105
+
106
+ def get_path_clock(self, path: str) -> VectorClock:
107
+ return self._path_clocks.get(path, VectorClock()).copy()
108
+
109
+ def get_agent_clock(self, agent_did: str) -> VectorClock:
110
+ return self._agent_clocks.get(agent_did, VectorClock()).copy()
111
+
112
+ @property
113
+ def conflict_count(self) -> int:
114
+ return self._conflict_count
115
+
116
+ @property
117
+ def tracked_paths(self) -> int:
118
+ return len(self._path_clocks)
@@ -0,0 +1,3 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Verification subpackage."""
@@ -0,0 +1,173 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ DID Transaction History Verification
5
+
6
+ Verifies an agent's declared behavioral history by checking Summary Hash
7
+ consistency (duplicate hashes, temporal ordering, hash validity).
8
+
9
+ NOTE: This verifier checks the *integrity* of history declared by the
10
+ agent during the IATP handshake. It does NOT resolve DIDs from an
11
+ external DID registry or blockchain. A malicious agent that fabricates
12
+ a self-consistent history will pass verification. External DID
13
+ resolution is planned for a future release.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from datetime import UTC, datetime
20
+ from enum import Enum
21
+
22
+
23
+ class VerificationStatus(str, Enum):
24
+ """Result of transaction history verification."""
25
+
26
+ VERIFIED = "verified"
27
+ PROBATIONARY = "probationary" # new DID, limited history
28
+ SUSPICIOUS = "suspicious" # inconsistent hashes
29
+ UNREACHABLE = "unreachable" # couldn't fetch history
30
+ UNKNOWN = "unknown"
31
+
32
+
33
+ @dataclass
34
+ class TransactionRecord:
35
+ """A historical transaction record from a DID."""
36
+
37
+ session_id: str
38
+ summary_hash: str
39
+ timestamp: datetime
40
+ participant_count: int = 0
41
+
42
+
43
+ @dataclass
44
+ class VerificationResult:
45
+ """Result of verifying an agent's transaction history."""
46
+
47
+ agent_did: str
48
+ status: VerificationStatus
49
+ transactions_checked: int
50
+ transactions_found: int
51
+ inconsistencies: list[str] = field(default_factory=list)
52
+ verified_at: datetime = field(default_factory=lambda: datetime.now(UTC))
53
+ cached: bool = False
54
+
55
+ @property
56
+ def is_trustworthy(self) -> bool:
57
+ return self.status in (VerificationStatus.VERIFIED, VerificationStatus.PROBATIONARY)
58
+
59
+
60
+ class TransactionHistoryVerifier:
61
+ """
62
+ Verifies agent transaction history integrity.
63
+
64
+ During handshake, checks the last N declared transaction Summary Hashes
65
+ for internal consistency (duplicates, ordering, validity).
66
+
67
+ **Limitations:**
68
+ - Only validates *declared* history — does not fetch from DID registries
69
+ - A fabricated but self-consistent history will pass
70
+ - Results are cached in-memory (no persistent cache)
71
+ """
72
+
73
+ REQUIRED_HISTORY_DEPTH = 5
74
+
75
+ def __init__(self) -> None:
76
+ self._cache: dict[str, VerificationResult] = {}
77
+
78
+ def verify(
79
+ self,
80
+ agent_did: str,
81
+ declared_history: list[TransactionRecord] | None = None,
82
+ ) -> VerificationResult:
83
+ """
84
+ Verify an agent's transaction history.
85
+
86
+ Args:
87
+ agent_did: The agent's DID to verify
88
+ declared_history: History declared by the agent (to cross-check)
89
+
90
+ Returns:
91
+ VerificationResult with status and details
92
+ """
93
+ # Check cache first
94
+ if agent_did in self._cache:
95
+ cached = self._cache[agent_did]
96
+ cached.cached = True
97
+ return cached
98
+
99
+ if declared_history is None or len(declared_history) == 0:
100
+ # No history — treat as new/probationary
101
+ result = VerificationResult(
102
+ agent_did=agent_did,
103
+ status=VerificationStatus.PROBATIONARY,
104
+ transactions_checked=0,
105
+ transactions_found=0,
106
+ inconsistencies=["No transaction history available"],
107
+ )
108
+ elif len(declared_history) < self.REQUIRED_HISTORY_DEPTH:
109
+ # Insufficient history
110
+ result = VerificationResult(
111
+ agent_did=agent_did,
112
+ status=VerificationStatus.PROBATIONARY,
113
+ transactions_checked=len(declared_history),
114
+ transactions_found=len(declared_history),
115
+ inconsistencies=[
116
+ f"Only {len(declared_history)} transactions "
117
+ f"(need {self.REQUIRED_HISTORY_DEPTH})"
118
+ ],
119
+ )
120
+ else:
121
+ # Validate hash consistency
122
+ inconsistencies = self._check_consistency(declared_history)
123
+ status = (
124
+ VerificationStatus.SUSPICIOUS
125
+ if inconsistencies
126
+ else VerificationStatus.VERIFIED
127
+ )
128
+ result = VerificationResult(
129
+ agent_did=agent_did,
130
+ status=status,
131
+ transactions_checked=len(declared_history),
132
+ transactions_found=len(declared_history),
133
+ inconsistencies=inconsistencies,
134
+ )
135
+
136
+ self._cache[agent_did] = result
137
+ return result
138
+
139
+ def clear_cache(self, agent_did: str | None = None) -> None:
140
+ """Clear verification cache."""
141
+ if agent_did:
142
+ self._cache.pop(agent_did, None)
143
+ else:
144
+ self._cache.clear()
145
+
146
+ def _check_consistency(self, history: list[TransactionRecord]) -> list[str]:
147
+ """Check transaction history for inconsistencies."""
148
+ issues: list[str] = []
149
+
150
+ # Check for duplicate hashes (different sessions, same hash = suspicious)
151
+ seen_hashes: dict[str, str] = {}
152
+ for tx in history:
153
+ if tx.summary_hash in seen_hashes:
154
+ issues.append(
155
+ f"Duplicate hash in sessions {seen_hashes[tx.summary_hash]} "
156
+ f"and {tx.session_id}"
157
+ )
158
+ seen_hashes[tx.summary_hash] = tx.session_id
159
+
160
+ # Check for temporal ordering
161
+ for i in range(1, len(history)):
162
+ if history[i].timestamp < history[i - 1].timestamp:
163
+ issues.append(
164
+ f"Non-monotonic timestamps: {history[i].session_id} "
165
+ f"predates {history[i-1].session_id}"
166
+ )
167
+
168
+ # Check for empty hashes
169
+ for tx in history:
170
+ if not tx.summary_hash or len(tx.summary_hash) < 16:
171
+ issues.append(f"Invalid hash in session {tx.session_id}")
172
+
173
+ return issues