agent_hypervisor 3.5.0__tar.gz → 3.7.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.
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/.gitignore +2 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/PKG-INFO +2 -2
- agent_hypervisor-3.7.0/examples/requirements.txt +1 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/notebooks/README.md +2 -1
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/pyproject.toml +2 -2
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/server.py +5 -8
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/core.py +17 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/vouching.py +5 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/event_bus.py +97 -39
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/providers.py +12 -5
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/__init__.py +19 -3
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/breach_detector.py +17 -1
- agent_hypervisor-3.7.0/src/hypervisor/rings/elevation.py +424 -0
- agent_hypervisor-3.7.0/src/hypervisor/rings/enforcer.py +215 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/orchestrator.py +21 -5
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/state_machine.py +21 -1
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/kill_switch.py +89 -15
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/rate_limiter.py +71 -39
- agent_hypervisor-3.7.0/src/hypervisor/session/isolation.py +191 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/sso.py +15 -1
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/vector_clock.py +27 -5
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_agent_manager.py +38 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_breach_detector.py +44 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_kill_switch.py +71 -0
- agent_hypervisor-3.7.0/tests/test_providers.py +44 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_rate_limiter.py +127 -0
- agent_hypervisor-3.7.0/tests/test_session_isolation.py +95 -0
- agent_hypervisor-3.7.0/tests/test_spec_hypervisor_conformance.py +837 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability.py +15 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_observability.py +103 -1
- agent_hypervisor-3.7.0/tests/unit/test_ring_enforcement.py +533 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_ring_improvements.py +25 -14
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga.py +34 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_session_security.py +6 -9
- agent_hypervisor-3.5.0/examples/requirements.txt +0 -1
- agent_hypervisor-3.5.0/src/hypervisor/rings/elevation.py +0 -219
- agent_hypervisor-3.5.0/src/hypervisor/rings/enforcer.py +0 -97
- agent_hypervisor-3.5.0/src/hypervisor/session/isolation.py +0 -37
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/CHANGELOG.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/LICENSE +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/README.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/SECURITY.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/benchmarks/bench_hypervisor.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/benchmarks/results/BENCHMARKS.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/benchmarks/results/benchmarks.json +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/docs/api-reference.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/docs/joint-liability-guide.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/dashboard/README.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/dashboard/app.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/dashboard/requirements.txt +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/demo.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/Dockerfile +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/README.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/dashboard.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/sample_workflow.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/server.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/config/hypervisor.yaml +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/docker-compose.yml +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/notebooks/hypervisor-exploration.ipynb +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/models.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/commitment.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/delta.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/gc.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/formatters.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/session_commands.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/constants.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/iatp_adapter.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/nexus_adapter.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/verification_adapter.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/attribution.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/ledger.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/quarantine.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/slashing.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/models.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/causal_trace.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/prometheus_collector.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/saga_span_exporter.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/py.typed +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/registry.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/classifier.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/checkpoint.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/dsl.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/fan_out.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/schema.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/intent_locks.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/history.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/integration/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/integration/test_hypervisor_e2e.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/integration/test_scenarios.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_classifier.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_shapley_attribution.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/__init__.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_audit.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_cli.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_config_validation.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability_improvements.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_models.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_prometheus_collector.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_reversibility_registry.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_rings.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_improvements.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_schema.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_span_exporter.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_session.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_slashing.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_vfs_substrate.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/README.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/demo.py +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/saga-compensation/README.md +0 -0
- {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/saga-compensation/demo.py +0 -0
|
@@ -69,6 +69,7 @@ bld/
|
|
|
69
69
|
|
|
70
70
|
# Build results on 'Bin' directories
|
|
71
71
|
**/[Bb]in/*
|
|
72
|
+
!agent-governance-copilot-cli/bin/agt-copilot.mjs
|
|
72
73
|
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
|
73
74
|
# (https://github.com/github/gitignore/pull/3736)
|
|
74
75
|
#!**/[Bb]in/*.refresh
|
|
@@ -465,3 +466,4 @@ _site/
|
|
|
465
466
|
|
|
466
467
|
# Code Security Assessment artifacts
|
|
467
468
|
.security-assessment/
|
|
469
|
+
*.tgz
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent_hypervisor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.0
|
|
4
4
|
Summary: Public Preview — Agent Hypervisor: Runtime supervisor for multi-agent Shared Sessions with Execution Rings, Joint Liability, Saga Orchestration, and hash-chained audit trails
|
|
5
5
|
Project-URL: Homepage, https://github.com/microsoft/agent-governance-toolkit
|
|
6
6
|
Project-URL: Repository, https://github.com/microsoft/agent-governance-toolkit
|
|
@@ -35,7 +35,7 @@ Requires-Dist: web3<8.0,>=6.0.0; extra == 'blockchain'
|
|
|
35
35
|
Provides-Extra: dev
|
|
36
36
|
Requires-Dist: hypothesis<7.0,>=6.0.0; extra == 'dev'
|
|
37
37
|
Requires-Dist: jsonschema<5.0,>=4.0.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: mypy<
|
|
38
|
+
Requires-Dist: mypy<3.0,>=1.8.0; extra == 'dev'
|
|
39
39
|
Requires-Dist: pytest-asyncio<2.0,>=0.23.0; extra == 'dev'
|
|
40
40
|
Requires-Dist: pytest-cov<8.0,>=4.0.0; extra == 'dev'
|
|
41
41
|
Requires-Dist: pytest<10.0,>=8.0.0; extra == 'dev'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent-hypervisor>=3.6.0
|
|
@@ -11,7 +11,8 @@ Interactive Jupyter notebooks for exploring the **agent-hypervisor** runtime.
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
# From the
|
|
14
|
+
# From the agent-hypervisor package root
|
|
15
|
+
cd agent-governance-python/agent-hypervisor
|
|
15
16
|
pip install -e ".[dev]" plotly nest-asyncio
|
|
16
17
|
jupyter notebook notebooks/
|
|
17
18
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent_hypervisor"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.7.0"
|
|
8
8
|
description = "Public Preview — Agent Hypervisor: Runtime supervisor for multi-agent Shared Sessions with Execution Rings, Joint Liability, Saga Orchestration, and hash-chained audit trails"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -58,7 +58,7 @@ dev = [
|
|
|
58
58
|
"pytest-cov>=4.0.0,<8.0",
|
|
59
59
|
"hypothesis>=6.0.0,<7.0",
|
|
60
60
|
"ruff>=0.4.0,<1.0",
|
|
61
|
-
"mypy>=1.8.0,<
|
|
61
|
+
"mypy>=1.8.0,<3.0",
|
|
62
62
|
"jsonschema>=4.0.0,<5.0",
|
|
63
63
|
]
|
|
64
64
|
blockchain = [
|
|
@@ -180,19 +180,16 @@ async def get_stats() -> StatsResponse:
|
|
|
180
180
|
"""Get overall hypervisor statistics."""
|
|
181
181
|
hv = _hv()
|
|
182
182
|
bus = _bus()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
186
|
-
active_sagas = sum(
|
|
187
|
-
len(m.saga.active_sagas) for m in hv._sessions.values()
|
|
188
|
-
)
|
|
183
|
+
sessions = hv.sessions
|
|
184
|
+
total_participants = sum(m.sso.participant_count for m in sessions)
|
|
185
|
+
active_sagas = sum(len(m.saga.active_sagas) for m in sessions)
|
|
189
186
|
return StatsResponse(
|
|
190
187
|
version=__version__,
|
|
191
|
-
total_sessions=
|
|
188
|
+
total_sessions=hv.session_count,
|
|
192
189
|
active_sessions=len(hv.active_sessions),
|
|
193
190
|
total_participants=total_participants,
|
|
194
191
|
active_sagas=active_sagas,
|
|
195
|
-
total_vouches=
|
|
192
|
+
total_vouches=hv.vouching.vouch_count,
|
|
196
193
|
event_count=bus.event_count,
|
|
197
194
|
)
|
|
198
195
|
|
|
@@ -306,6 +306,23 @@ class Hypervisor:
|
|
|
306
306
|
return [self._sessions[sid] for sid in self._active_ids
|
|
307
307
|
if sid in self._sessions]
|
|
308
308
|
|
|
309
|
+
@property
|
|
310
|
+
def sessions(self) -> list[ManagedSession]:
|
|
311
|
+
"""All managed sessions, including archived/terminating ones.
|
|
312
|
+
|
|
313
|
+
``active_sessions`` filters via ``_active_ids``; this property
|
|
314
|
+
exposes the full registry for callers (admin APIs, monitoring,
|
|
315
|
+
stats) that need a count or iterator over every session the
|
|
316
|
+
Hypervisor is still tracking. Returns a snapshot list so callers
|
|
317
|
+
can iterate without holding any internal reference.
|
|
318
|
+
"""
|
|
319
|
+
return list(self._sessions.values())
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def session_count(self) -> int:
|
|
323
|
+
"""Total number of managed sessions, including archived/terminating."""
|
|
324
|
+
return len(self._sessions)
|
|
325
|
+
|
|
309
326
|
def _get_session(self, session_id: str) -> ManagedSession:
|
|
310
327
|
managed = self._sessions.get(session_id)
|
|
311
328
|
if not managed:
|
|
@@ -57,6 +57,11 @@ class VouchingEngine:
|
|
|
57
57
|
self._vouches: dict[str, VouchRecord] = {}
|
|
58
58
|
self.max_exposure = max_exposure or self.DEFAULT_MAX_EXPOSURE
|
|
59
59
|
|
|
60
|
+
@property
|
|
61
|
+
def vouch_count(self) -> int:
|
|
62
|
+
"""Total number of sponsorship records (active + released)."""
|
|
63
|
+
return len(self._vouches)
|
|
64
|
+
|
|
60
65
|
def vouch(
|
|
61
66
|
self,
|
|
62
67
|
voucher_did: str,
|
|
@@ -10,13 +10,21 @@ full replay debugging, post-mortem analysis, and real-time monitoring.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import threading
|
|
13
14
|
import uuid
|
|
15
|
+
from collections import deque
|
|
14
16
|
from collections.abc import Callable
|
|
15
17
|
from dataclasses import dataclass, field
|
|
16
18
|
from datetime import UTC, datetime
|
|
17
19
|
from enum import Enum
|
|
18
20
|
from typing import Any
|
|
19
21
|
|
|
22
|
+
# Default cap for the in-memory event store. Hypervisor deployments run for
|
|
23
|
+
# weeks; an unbounded list eventually OOMs. The cap is configurable via the
|
|
24
|
+
# ``HypervisorEventBus(max_events=...)`` constructor; ``None`` opts back into
|
|
25
|
+
# unbounded growth for tests or analysis tooling that needs full history.
|
|
26
|
+
DEFAULT_MAX_EVENTS = 100_000
|
|
27
|
+
|
|
20
28
|
|
|
21
29
|
class EventType(str, Enum):
|
|
22
30
|
"""Categorised hypervisor event types."""
|
|
@@ -119,34 +127,61 @@ class HypervisorEventBus:
|
|
|
119
127
|
- Event count and statistics
|
|
120
128
|
"""
|
|
121
129
|
|
|
122
|
-
def __init__(self) -> None:
|
|
123
|
-
|
|
130
|
+
def __init__(self, max_events: int | None = DEFAULT_MAX_EVENTS) -> None:
|
|
131
|
+
"""Create an event bus.
|
|
132
|
+
|
|
133
|
+
``max_events`` caps the in-memory store. Each per-key index list
|
|
134
|
+
(by-type, by-session, by-agent) is independently capped to the
|
|
135
|
+
same value, so a single chatty session cannot starve the
|
|
136
|
+
history of other sessions. Pass ``None`` to disable the cap
|
|
137
|
+
(testing or full-replay tooling).
|
|
138
|
+
"""
|
|
139
|
+
self._max_events = max_events
|
|
140
|
+
# `deque` with `maxlen` evicts the oldest entry on overflow in
|
|
141
|
+
# O(1), avoiding the OOM cliff of an unbounded `list`.
|
|
142
|
+
self._events: deque[HypervisorEvent] = deque(maxlen=max_events)
|
|
124
143
|
self._subscribers: dict[EventType | None, list[EventHandler]] = {}
|
|
125
|
-
self._by_type: dict[EventType,
|
|
126
|
-
self._by_session: dict[str,
|
|
127
|
-
self._by_agent: dict[str,
|
|
144
|
+
self._by_type: dict[EventType, deque[HypervisorEvent]] = {}
|
|
145
|
+
self._by_session: dict[str, deque[HypervisorEvent]] = {}
|
|
146
|
+
self._by_agent: dict[str, deque[HypervisorEvent]] = {}
|
|
147
|
+
# Use an RLock so a subscriber that re-enters the bus (e.g.
|
|
148
|
+
# emits an event in response to another event) doesn't deadlock.
|
|
149
|
+
self._lock = threading.RLock()
|
|
150
|
+
|
|
151
|
+
def _new_index_deque(self) -> deque[HypervisorEvent]:
|
|
152
|
+
return deque(maxlen=self._max_events)
|
|
128
153
|
|
|
129
154
|
def emit(self, event: HypervisorEvent) -> None:
|
|
130
155
|
"""Append an event and notify subscribers."""
|
|
131
|
-
self.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
with self._lock:
|
|
157
|
+
self._events.append(event)
|
|
158
|
+
|
|
159
|
+
self._by_type.setdefault(
|
|
160
|
+
event.event_type, self._new_index_deque()
|
|
161
|
+
).append(event)
|
|
162
|
+
|
|
163
|
+
if event.session_id:
|
|
164
|
+
self._by_session.setdefault(
|
|
165
|
+
event.session_id, self._new_index_deque()
|
|
166
|
+
).append(event)
|
|
167
|
+
|
|
168
|
+
if event.agent_did:
|
|
169
|
+
self._by_agent.setdefault(
|
|
170
|
+
event.agent_did, self._new_index_deque()
|
|
171
|
+
).append(event)
|
|
172
|
+
|
|
173
|
+
# Snapshot subscriber lists while holding the lock so a
|
|
174
|
+
# subscriber that mutates the registry mid-notify doesn't
|
|
175
|
+
# invalidate iteration.
|
|
176
|
+
type_subs = list(self._subscribers.get(event.event_type, ()))
|
|
177
|
+
wildcard_subs = list(self._subscribers.get(None, ()))
|
|
178
|
+
|
|
179
|
+
# Invoke handlers outside the lock so a slow subscriber can't
|
|
180
|
+
# serialize the entire bus or, worse, deadlock with a caller
|
|
181
|
+
# that also holds an external lock.
|
|
182
|
+
for handler in type_subs:
|
|
146
183
|
handler(event)
|
|
147
|
-
|
|
148
|
-
# Notify wildcard subscribers
|
|
149
|
-
for handler in self._subscribers.get(None, []):
|
|
184
|
+
for handler in wildcard_subs:
|
|
150
185
|
handler(event)
|
|
151
186
|
|
|
152
187
|
def subscribe(
|
|
@@ -155,20 +190,25 @@ class HypervisorEventBus:
|
|
|
155
190
|
handler: EventHandler | None = None,
|
|
156
191
|
) -> None:
|
|
157
192
|
"""Subscribe to events. Use event_type=None for all events."""
|
|
158
|
-
if handler:
|
|
193
|
+
if not handler:
|
|
194
|
+
return
|
|
195
|
+
with self._lock:
|
|
159
196
|
self._subscribers.setdefault(event_type, []).append(handler)
|
|
160
197
|
|
|
161
198
|
def query_by_type(self, event_type: EventType) -> list[HypervisorEvent]:
|
|
162
199
|
"""Get all events of a specific type."""
|
|
163
|
-
|
|
200
|
+
with self._lock:
|
|
201
|
+
return list(self._by_type.get(event_type, ()))
|
|
164
202
|
|
|
165
203
|
def query_by_session(self, session_id: str) -> list[HypervisorEvent]:
|
|
166
204
|
"""Get all events for a specific session."""
|
|
167
|
-
|
|
205
|
+
with self._lock:
|
|
206
|
+
return list(self._by_session.get(session_id, ()))
|
|
168
207
|
|
|
169
208
|
def query_by_agent(self, agent_did: str) -> list[HypervisorEvent]:
|
|
170
209
|
"""Get all events involving a specific agent."""
|
|
171
|
-
|
|
210
|
+
with self._lock:
|
|
211
|
+
return list(self._by_agent.get(agent_did, ()))
|
|
172
212
|
|
|
173
213
|
def query_by_time_range(
|
|
174
214
|
self,
|
|
@@ -178,7 +218,8 @@ class HypervisorEventBus:
|
|
|
178
218
|
"""Get events within a time range."""
|
|
179
219
|
if end is None:
|
|
180
220
|
end = datetime.now(UTC)
|
|
181
|
-
|
|
221
|
+
with self._lock:
|
|
222
|
+
return [e for e in self._events if start <= e.timestamp <= end]
|
|
182
223
|
|
|
183
224
|
def query(
|
|
184
225
|
self,
|
|
@@ -188,7 +229,8 @@ class HypervisorEventBus:
|
|
|
188
229
|
limit: int | None = None,
|
|
189
230
|
) -> list[HypervisorEvent]:
|
|
190
231
|
"""Flexible query with multiple filters."""
|
|
191
|
-
|
|
232
|
+
with self._lock:
|
|
233
|
+
results: list[HypervisorEvent] = list(self._events)
|
|
192
234
|
|
|
193
235
|
if event_type is not None:
|
|
194
236
|
results = [e for e in results if e.event_type == event_type]
|
|
@@ -204,19 +246,35 @@ class HypervisorEventBus:
|
|
|
204
246
|
|
|
205
247
|
@property
|
|
206
248
|
def event_count(self) -> int:
|
|
207
|
-
|
|
249
|
+
with self._lock:
|
|
250
|
+
return len(self._events)
|
|
208
251
|
|
|
209
252
|
@property
|
|
210
253
|
def all_events(self) -> list[HypervisorEvent]:
|
|
211
|
-
|
|
254
|
+
with self._lock:
|
|
255
|
+
return list(self._events)
|
|
212
256
|
|
|
213
257
|
def type_counts(self) -> dict[str, int]:
|
|
214
258
|
"""Return count of events per type."""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
259
|
+
with self._lock:
|
|
260
|
+
return {t.value: len(evts) for t, evts in self._by_type.items()}
|
|
261
|
+
|
|
262
|
+
def _clear(self) -> None:
|
|
263
|
+
"""Clear all events. **Test-only — do not call in production.**
|
|
264
|
+
|
|
265
|
+
The event bus is wired into the hypervisor as a long-lived,
|
|
266
|
+
process-singleton-shaped collaborator (see
|
|
267
|
+
``hypervisor.api.server._event_bus``): production calls would
|
|
268
|
+
wipe the audit trail of every running session at once.
|
|
269
|
+
|
|
270
|
+
The leading underscore makes the test-only contract visible at
|
|
271
|
+
every call site. The method is kept on the class (rather than
|
|
272
|
+
moved to a test helper) because some tests construct a fresh
|
|
273
|
+
bus and then exercise the clear path itself; it just shouldn't
|
|
274
|
+
be reached from non-test code.
|
|
275
|
+
"""
|
|
276
|
+
with self._lock:
|
|
277
|
+
self._events.clear()
|
|
278
|
+
self._by_type.clear()
|
|
279
|
+
self._by_session.clear()
|
|
280
|
+
self._by_agent.clear()
|
|
@@ -69,27 +69,34 @@ def get_liability_engine(**kwargs: Any):
|
|
|
69
69
|
"""Get the best available liability engine.
|
|
70
70
|
|
|
71
71
|
Advanced: Shapley-value fault attribution with vouch cascades.
|
|
72
|
-
Community:
|
|
72
|
+
Community: ``LiabilityMatrix`` from ``hypervisor.liability``.
|
|
73
73
|
"""
|
|
74
74
|
provider = _discover_provider(PROVIDER_GROUPS["liability"])
|
|
75
75
|
if provider is not None:
|
|
76
76
|
return provider(**kwargs)
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
# Community fallback. The previous import targeted
|
|
79
|
+
# ``hypervisor.liability.engine.LiabilityEngine`` which does not
|
|
80
|
+
# exist in this tree; ``LiabilityMatrix`` is the real public-
|
|
81
|
+
# edition entry point.
|
|
82
|
+
from hypervisor.liability import LiabilityMatrix
|
|
83
|
+
return LiabilityMatrix(**kwargs)
|
|
80
84
|
|
|
81
85
|
|
|
82
86
|
def get_saga_engine(**kwargs: Any):
|
|
83
87
|
"""Get the best available saga orchestration engine.
|
|
84
88
|
|
|
85
89
|
Advanced: Multi-pattern saga with parallel fan-out and escalation.
|
|
86
|
-
Community:
|
|
90
|
+
Community: ``SagaOrchestrator`` from ``hypervisor.saga.orchestrator``.
|
|
87
91
|
"""
|
|
88
92
|
provider = _discover_provider(PROVIDER_GROUPS["saga_engine"])
|
|
89
93
|
if provider is not None:
|
|
90
94
|
return provider(**kwargs)
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
# Community fallback. The previous import targeted
|
|
97
|
+
# ``hypervisor.saga.engine.SagaOrchestrator`` which does not exist
|
|
98
|
+
# in this tree; the real module is ``hypervisor.saga.orchestrator``.
|
|
99
|
+
from hypervisor.saga.orchestrator import SagaOrchestrator
|
|
93
100
|
return SagaOrchestrator(**kwargs)
|
|
94
101
|
|
|
95
102
|
|
|
@@ -4,18 +4,34 @@
|
|
|
4
4
|
|
|
5
5
|
from hypervisor.rings.breach_detector import BreachEvent, BreachSeverity, RingBreachDetector
|
|
6
6
|
from hypervisor.rings.elevation import (
|
|
7
|
+
ChildRegistration,
|
|
7
8
|
ElevationDenialReason,
|
|
9
|
+
ELEVATION_TRUST_THRESHOLDS,
|
|
8
10
|
RingElevation,
|
|
9
11
|
RingElevationError,
|
|
10
12
|
RingElevationManager,
|
|
11
13
|
)
|
|
14
|
+
from hypervisor.rings.enforcer import (
|
|
15
|
+
ResourceConstraints,
|
|
16
|
+
ResourceType,
|
|
17
|
+
RING_CONSTRAINTS,
|
|
18
|
+
RingCheckResult,
|
|
19
|
+
RingEnforcer,
|
|
20
|
+
)
|
|
12
21
|
|
|
13
22
|
__all__ = [
|
|
14
|
-
"
|
|
15
|
-
"RingElevation",
|
|
16
|
-
"RingElevationError",
|
|
23
|
+
"ChildRegistration",
|
|
17
24
|
"ElevationDenialReason",
|
|
25
|
+
"ELEVATION_TRUST_THRESHOLDS",
|
|
26
|
+
"ResourceConstraints",
|
|
27
|
+
"ResourceType",
|
|
28
|
+
"RING_CONSTRAINTS",
|
|
18
29
|
"RingBreachDetector",
|
|
30
|
+
"RingCheckResult",
|
|
31
|
+
"RingElevation",
|
|
32
|
+
"RingElevationError",
|
|
33
|
+
"RingElevationManager",
|
|
34
|
+
"RingEnforcer",
|
|
19
35
|
"BreachEvent",
|
|
20
36
|
"BreachSeverity",
|
|
21
37
|
]
|
|
@@ -127,8 +127,24 @@ class RingBreachDetector:
|
|
|
127
127
|
window.popleft()
|
|
128
128
|
|
|
129
129
|
# --- 3. Compute actual rate (calls / second) ---
|
|
130
|
+
# Dividing by the full ``window_seconds`` underestimates the rate
|
|
131
|
+
# when the window has just begun — 10 calls in the first 2s of a
|
|
132
|
+
# 60s window would read as 0.16/s instead of 5/s, missing real
|
|
133
|
+
# bursts. Use the shorter of (window, time_since_first_event)
|
|
134
|
+
# as the denominator so early bursts surface accurately. A single
|
|
135
|
+
# call has no rate to measure (need ≥2 samples for an interval),
|
|
136
|
+
# so fall back to the conservative full-window divisor.
|
|
130
137
|
call_count = len(window)
|
|
131
|
-
|
|
138
|
+
if self.window_seconds <= 0 or call_count == 0:
|
|
139
|
+
actual_rate = 0.0
|
|
140
|
+
elif call_count < 2:
|
|
141
|
+
actual_rate = call_count / self.window_seconds
|
|
142
|
+
else:
|
|
143
|
+
time_since_first = max(now - window[0], 0.0)
|
|
144
|
+
# Floor at 1ms: prevents divide-by-zero for ultra-tight
|
|
145
|
+
# bursts while still surfacing them with a high rate.
|
|
146
|
+
denominator = max(min(self.window_seconds, time_since_first), 1e-3)
|
|
147
|
+
actual_rate = call_count / denominator
|
|
132
148
|
|
|
133
149
|
# --- 4. Ring-distance amplifier ---
|
|
134
150
|
# Upward calls (low value = higher privilege) are escalations.
|