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.
Files changed (124) hide show
  1. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/.gitignore +2 -0
  2. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/PKG-INFO +2 -2
  3. agent_hypervisor-3.7.0/examples/requirements.txt +1 -0
  4. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/notebooks/README.md +2 -1
  5. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/pyproject.toml +2 -2
  6. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/server.py +5 -8
  7. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/core.py +17 -0
  8. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/vouching.py +5 -0
  9. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/event_bus.py +97 -39
  10. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/providers.py +12 -5
  11. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/__init__.py +19 -3
  12. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/breach_detector.py +17 -1
  13. agent_hypervisor-3.7.0/src/hypervisor/rings/elevation.py +424 -0
  14. agent_hypervisor-3.7.0/src/hypervisor/rings/enforcer.py +215 -0
  15. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/orchestrator.py +21 -5
  16. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/state_machine.py +21 -1
  17. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/kill_switch.py +89 -15
  18. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/rate_limiter.py +71 -39
  19. agent_hypervisor-3.7.0/src/hypervisor/session/isolation.py +191 -0
  20. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/sso.py +15 -1
  21. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/vector_clock.py +27 -5
  22. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_agent_manager.py +38 -0
  23. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_breach_detector.py +44 -0
  24. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_kill_switch.py +71 -0
  25. agent_hypervisor-3.7.0/tests/test_providers.py +44 -0
  26. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_rate_limiter.py +127 -0
  27. agent_hypervisor-3.7.0/tests/test_session_isolation.py +95 -0
  28. agent_hypervisor-3.7.0/tests/test_spec_hypervisor_conformance.py +837 -0
  29. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability.py +15 -0
  30. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_observability.py +103 -1
  31. agent_hypervisor-3.7.0/tests/unit/test_ring_enforcement.py +533 -0
  32. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_ring_improvements.py +25 -14
  33. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga.py +34 -0
  34. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_session_security.py +6 -9
  35. agent_hypervisor-3.5.0/examples/requirements.txt +0 -1
  36. agent_hypervisor-3.5.0/src/hypervisor/rings/elevation.py +0 -219
  37. agent_hypervisor-3.5.0/src/hypervisor/rings/enforcer.py +0 -97
  38. agent_hypervisor-3.5.0/src/hypervisor/session/isolation.py +0 -37
  39. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/CHANGELOG.md +0 -0
  40. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/LICENSE +0 -0
  41. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/README.md +0 -0
  42. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/SECURITY.md +0 -0
  43. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/benchmarks/bench_hypervisor.py +0 -0
  44. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/benchmarks/results/BENCHMARKS.md +0 -0
  45. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/benchmarks/results/benchmarks.json +0 -0
  46. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/docs/api-reference.md +0 -0
  47. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/docs/joint-liability-guide.md +0 -0
  48. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/dashboard/README.md +0 -0
  49. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/dashboard/app.py +0 -0
  50. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/dashboard/requirements.txt +0 -0
  51. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/demo.py +0 -0
  52. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/Dockerfile +0 -0
  53. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/README.md +0 -0
  54. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/__init__.py +0 -0
  55. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/dashboard.py +0 -0
  56. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/sample_workflow.py +0 -0
  57. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/server.py +0 -0
  58. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/config/hypervisor.yaml +0 -0
  59. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/examples/docker-compose/docker-compose.yml +0 -0
  60. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/notebooks/hypervisor-exploration.ipynb +0 -0
  61. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/__init__.py +0 -0
  62. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/__init__.py +0 -0
  63. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/models.py +0 -0
  64. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/__init__.py +0 -0
  65. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/commitment.py +0 -0
  66. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/delta.py +0 -0
  67. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/gc.py +0 -0
  68. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/__init__.py +0 -0
  69. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/formatters.py +0 -0
  70. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/session_commands.py +0 -0
  71. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/constants.py +0 -0
  72. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/__init__.py +0 -0
  73. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/iatp_adapter.py +0 -0
  74. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/nexus_adapter.py +0 -0
  75. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/verification_adapter.py +0 -0
  76. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/__init__.py +0 -0
  77. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/attribution.py +0 -0
  78. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/ledger.py +0 -0
  79. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/quarantine.py +0 -0
  80. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/slashing.py +0 -0
  81. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/models.py +0 -0
  82. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/__init__.py +0 -0
  83. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/causal_trace.py +0 -0
  84. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/prometheus_collector.py +0 -0
  85. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/saga_span_exporter.py +0 -0
  86. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/py.typed +0 -0
  87. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/__init__.py +0 -0
  88. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/registry.py +0 -0
  89. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/classifier.py +0 -0
  90. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/__init__.py +0 -0
  91. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/checkpoint.py +0 -0
  92. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/dsl.py +0 -0
  93. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/fan_out.py +0 -0
  94. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/schema.py +0 -0
  95. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/__init__.py +0 -0
  96. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/__init__.py +0 -0
  97. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/intent_locks.py +0 -0
  98. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/__init__.py +0 -0
  99. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/history.py +0 -0
  100. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/__init__.py +0 -0
  101. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/integration/__init__.py +0 -0
  102. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/integration/test_hypervisor_e2e.py +0 -0
  103. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/integration/test_scenarios.py +0 -0
  104. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_classifier.py +0 -0
  105. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/test_shapley_attribution.py +0 -0
  106. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/__init__.py +0 -0
  107. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_audit.py +0 -0
  108. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_cli.py +0 -0
  109. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_config_validation.py +0 -0
  110. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability_improvements.py +0 -0
  111. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_models.py +0 -0
  112. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_prometheus_collector.py +0 -0
  113. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_reversibility_registry.py +0 -0
  114. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_rings.py +0 -0
  115. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_improvements.py +0 -0
  116. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_schema.py +0 -0
  117. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_span_exporter.py +0 -0
  118. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_session.py +0 -0
  119. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_slashing.py +0 -0
  120. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tests/unit/test_vfs_substrate.py +0 -0
  121. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/README.md +0 -0
  122. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/demo.py +0 -0
  123. {agent_hypervisor-3.5.0 → agent_hypervisor-3.7.0}/tutorials/saga-compensation/README.md +0 -0
  124. {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.5.0
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<2.0,>=1.8.0; extra == 'dev'
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 repository root
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.5.0"
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,<2.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
- total_participants = sum(
184
- m.sso.participant_count for m in hv._sessions.values()
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=len(hv._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=len(hv.vouching._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
- self._events: list[HypervisorEvent] = []
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, list[HypervisorEvent]] = {}
126
- self._by_session: dict[str, list[HypervisorEvent]] = {}
127
- self._by_agent: dict[str, list[HypervisorEvent]] = {}
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._events.append(event)
132
-
133
- # Index by type
134
- self._by_type.setdefault(event.event_type, []).append(event)
135
-
136
- # Index by session
137
- if event.session_id:
138
- self._by_session.setdefault(event.session_id, []).append(event)
139
-
140
- # Index by agent
141
- if event.agent_did:
142
- self._by_agent.setdefault(event.agent_did, []).append(event)
143
-
144
- # Notify type-specific subscribers
145
- for handler in self._subscribers.get(event.event_type, []):
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
- return list(self._by_type.get(event_type, []))
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
- return list(self._by_session.get(session_id, []))
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
- return list(self._by_agent.get(agent_did, []))
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
- return [e for e in self._events if start <= e.timestamp <= end]
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
- results = self._events
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
- return len(self._events)
249
+ with self._lock:
250
+ return len(self._events)
208
251
 
209
252
  @property
210
253
  def all_events(self) -> list[HypervisorEvent]:
211
- return list(self._events)
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
- return {t.value: len(evts) for t, evts in self._by_type.items()}
216
-
217
- def clear(self) -> None:
218
- """Clear all events (for testing)."""
219
- self._events.clear()
220
- self._by_type.clear()
221
- self._by_session.clear()
222
- self._by_agent.clear()
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: Basic vouching with linear slashing.
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
- from hypervisor.liability.engine import LiabilityEngine
79
- return LiabilityEngine(**kwargs)
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: Sequential saga with basic compensation.
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
- from hypervisor.saga.engine import SagaOrchestrator
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
- "RingElevationManager",
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
- actual_rate = call_count / self.window_seconds if self.window_seconds > 0 else 0.0
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.