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,341 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ OpenTelemetry-compatible span exporter for Saga orchestration steps.
5
+
6
+ Subscribes to the HypervisorEventBus for saga lifecycle events and
7
+ produces span records that can be forwarded to any tracing backend
8
+ via the ``SpanSink`` protocol.
9
+
10
+ No external dependencies — the hypervisor stays standalone. A concrete
11
+ ``OTelSpanSink`` adapter lives in ``agent-sre`` and bridges into the
12
+ existing ``TraceExporter``.
13
+
14
+ Usage::
15
+
16
+ from hypervisor.observability import SagaSpanExporter, HypervisorEventBus
17
+
18
+ bus = HypervisorEventBus()
19
+ exporter = SagaSpanExporter(bus)
20
+
21
+ # Optionally attach a sink (e.g. OTelSpanSink from agent-sre)
22
+ exporter.attach_sink(my_sink)
23
+
24
+ # ... saga events flow through the bus ...
25
+
26
+ # Or inspect buffered spans directly
27
+ spans = exporter.completed_spans
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from dataclasses import dataclass, field
33
+ from typing import Any, Protocol, runtime_checkable
34
+
35
+ from hypervisor.observability.event_bus import EventType, HypervisorEvent, HypervisorEventBus
36
+
37
+ # Saga event types we subscribe to
38
+ _SAGA_LIFECYCLE_EVENTS = frozenset({
39
+ EventType.SAGA_CREATED,
40
+ EventType.SAGA_STEP_STARTED,
41
+ EventType.SAGA_STEP_COMMITTED,
42
+ EventType.SAGA_STEP_FAILED,
43
+ EventType.SAGA_COMPENSATING,
44
+ EventType.SAGA_COMPLETED,
45
+ EventType.SAGA_ESCALATED,
46
+ })
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # SpanSink protocol — no hard OTel dependency in hypervisor
51
+ # ---------------------------------------------------------------------------
52
+
53
+
54
+ @runtime_checkable
55
+ class SpanSink(Protocol):
56
+ """Protocol for receiving completed span records.
57
+
58
+ Any object implementing this interface can receive spans from the
59
+ ``SagaSpanExporter``. The ``agent-sre`` package ships an
60
+ ``OTelSpanSink`` adapter that bridges into the native OTel SDK.
61
+ """
62
+
63
+ def record_span(
64
+ self,
65
+ name: str,
66
+ start_time: float,
67
+ end_time: float,
68
+ attributes: dict[str, Any],
69
+ status: str,
70
+ ) -> None: ...
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Span record dataclass
75
+ # ---------------------------------------------------------------------------
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class SagaSpanRecord:
80
+ """An immutable record of a completed saga span."""
81
+
82
+ name: str
83
+ saga_id: str
84
+ step_id: str
85
+ step_action: str
86
+ start_time: float
87
+ end_time: float
88
+ status: str # "ok", "error", "compensating"
89
+ attributes: dict[str, Any] = field(default_factory=dict)
90
+
91
+ @property
92
+ def duration_seconds(self) -> float:
93
+ return self.end_time - self.start_time
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # SagaSpanExporter
98
+ # ---------------------------------------------------------------------------
99
+
100
+
101
+ class SagaSpanExporter:
102
+ """Exports OpenTelemetry-compatible spans for saga orchestration steps.
103
+
104
+ Subscribes to the ``HypervisorEventBus`` for saga lifecycle events.
105
+ On each step completion (committed, failed, escalated), records a span
106
+ with timing, saga context, and status information.
107
+
108
+ Completed spans are:
109
+ 1. Buffered internally in ``completed_spans``
110
+ 2. Forwarded to an attached ``SpanSink`` (if any)
111
+
112
+ Attributes:
113
+ _bus: The event bus this exporter is subscribed to.
114
+ _sink: Optional SpanSink for forwarding completed spans.
115
+ _step_starts: Tracks step start times: ``(saga_id, step_id) -> timestamp``.
116
+ _saga_starts: Tracks saga creation times: ``saga_id -> timestamp``.
117
+ _completed_spans: Buffer of completed span records.
118
+ """
119
+
120
+ def __init__(self, bus: HypervisorEventBus, sink: SpanSink | None = None) -> None:
121
+ self._bus = bus
122
+ self._sink = sink
123
+
124
+ # In-flight tracking: (saga_id, step_id) -> start timestamp
125
+ self._step_starts: dict[tuple[str, str], float] = {}
126
+
127
+ # Saga-level tracking: saga_id -> creation timestamp
128
+ self._saga_starts: dict[str, float] = {}
129
+
130
+ # Completed span buffer
131
+ self._completed_spans: list[SagaSpanRecord] = []
132
+
133
+ # Total events processed
134
+ self._events_processed: int = 0
135
+
136
+ # Subscribe to all saga lifecycle events
137
+ for event_type in _SAGA_LIFECYCLE_EVENTS:
138
+ bus.subscribe(event_type=event_type, handler=self._handle_event)
139
+
140
+ # ------------------------------------------------------------------
141
+ # Public API
142
+ # ------------------------------------------------------------------
143
+
144
+ def attach_sink(self, sink: SpanSink) -> None:
145
+ """Attach a SpanSink to receive completed spans in real time."""
146
+ self._sink = sink
147
+
148
+ def detach_sink(self) -> None:
149
+ """Detach the current SpanSink."""
150
+ self._sink = None
151
+
152
+ @property
153
+ def completed_spans(self) -> list[SagaSpanRecord]:
154
+ """Return a copy of all completed span records."""
155
+ return list(self._completed_spans)
156
+
157
+ @property
158
+ def events_processed(self) -> int:
159
+ """Total saga events processed."""
160
+ return self._events_processed
161
+
162
+ def reset(self) -> None:
163
+ """Reset all internal state (for testing)."""
164
+ self._step_starts.clear()
165
+ self._saga_starts.clear()
166
+ self._completed_spans.clear()
167
+ self._events_processed = 0
168
+
169
+ # ------------------------------------------------------------------
170
+ # Event handling
171
+ # ------------------------------------------------------------------
172
+
173
+ def _handle_event(self, event: HypervisorEvent) -> None:
174
+ """Process a saga lifecycle event from the bus."""
175
+ self._events_processed += 1
176
+ payload = event.payload
177
+ saga_id = payload.get("saga_id", event.causal_trace_id or "unknown")
178
+ step_id = payload.get("step_id", "")
179
+ step_action = payload.get("action", payload.get("step_action", ""))
180
+
181
+ if event.event_type == EventType.SAGA_CREATED:
182
+ self._saga_starts[saga_id] = event.timestamp.timestamp()
183
+
184
+ elif event.event_type == EventType.SAGA_STEP_STARTED:
185
+ key = (saga_id, step_id or step_action)
186
+ self._step_starts[key] = event.timestamp.timestamp()
187
+
188
+ elif event.event_type == EventType.SAGA_STEP_COMMITTED:
189
+ self._complete_step(
190
+ saga_id=saga_id,
191
+ step_id=step_id or step_action,
192
+ step_action=step_action,
193
+ end_time=event.timestamp.timestamp(),
194
+ status="ok",
195
+ extra_attrs=payload,
196
+ agent_did=event.agent_did,
197
+ session_id=event.session_id,
198
+ )
199
+
200
+ elif event.event_type == EventType.SAGA_STEP_FAILED:
201
+ self._complete_step(
202
+ saga_id=saga_id,
203
+ step_id=step_id or step_action,
204
+ step_action=step_action,
205
+ end_time=event.timestamp.timestamp(),
206
+ status="error",
207
+ extra_attrs=payload,
208
+ agent_did=event.agent_did,
209
+ session_id=event.session_id,
210
+ )
211
+
212
+ elif event.event_type == EventType.SAGA_COMPENSATING:
213
+ # Compensation is a span in its own right
214
+ self._record_span(
215
+ name=f"saga.compensate.{step_action or step_id}",
216
+ saga_id=saga_id,
217
+ step_id=step_id or "compensation",
218
+ step_action=step_action or "compensate",
219
+ start_time=event.timestamp.timestamp(),
220
+ end_time=event.timestamp.timestamp(), # instantaneous marker
221
+ status="compensating",
222
+ extra_attrs=payload,
223
+ agent_did=event.agent_did,
224
+ session_id=event.session_id,
225
+ )
226
+
227
+ elif event.event_type == EventType.SAGA_COMPLETED:
228
+ # Record a saga-level span from creation to completion
229
+ start = self._saga_starts.pop(saga_id, None)
230
+ if start is not None:
231
+ self._record_span(
232
+ name=f"saga.completed.{saga_id}",
233
+ saga_id=saga_id,
234
+ step_id="",
235
+ step_action="complete",
236
+ start_time=start,
237
+ end_time=event.timestamp.timestamp(),
238
+ status="ok",
239
+ extra_attrs=payload,
240
+ agent_did=event.agent_did,
241
+ session_id=event.session_id,
242
+ )
243
+
244
+ elif event.event_type == EventType.SAGA_ESCALATED:
245
+ start = self._saga_starts.pop(saga_id, None)
246
+ if start is not None:
247
+ self._record_span(
248
+ name=f"saga.escalated.{saga_id}",
249
+ saga_id=saga_id,
250
+ step_id="",
251
+ step_action="escalate",
252
+ start_time=start,
253
+ end_time=event.timestamp.timestamp(),
254
+ status="error",
255
+ extra_attrs=payload,
256
+ agent_did=event.agent_did,
257
+ session_id=event.session_id,
258
+ )
259
+
260
+ def _complete_step(
261
+ self,
262
+ saga_id: str,
263
+ step_id: str,
264
+ step_action: str,
265
+ end_time: float,
266
+ status: str,
267
+ extra_attrs: dict[str, Any],
268
+ agent_did: str | None = None,
269
+ session_id: str | None = None,
270
+ ) -> None:
271
+ """Complete an in-flight step span."""
272
+ key = (saga_id, step_id)
273
+ start_time = self._step_starts.pop(key, None)
274
+ if start_time is None:
275
+ # Step started before we subscribed, use end_time as fallback
276
+ start_time = end_time
277
+
278
+ self._record_span(
279
+ name=f"saga.step.{step_action or step_id}",
280
+ saga_id=saga_id,
281
+ step_id=step_id,
282
+ step_action=step_action,
283
+ start_time=start_time,
284
+ end_time=end_time,
285
+ status=status,
286
+ extra_attrs=extra_attrs,
287
+ agent_did=agent_did,
288
+ session_id=session_id,
289
+ )
290
+
291
+ def _record_span(
292
+ self,
293
+ name: str,
294
+ saga_id: str,
295
+ step_id: str,
296
+ step_action: str,
297
+ start_time: float,
298
+ end_time: float,
299
+ status: str,
300
+ extra_attrs: dict[str, Any] | None = None,
301
+ agent_did: str | None = None,
302
+ session_id: str | None = None,
303
+ ) -> None:
304
+ """Create a span record, buffer it, and forward to sink if attached."""
305
+ attrs: dict[str, Any] = {
306
+ "agent.saga.id": saga_id,
307
+ "agent.saga.step_id": step_id,
308
+ "agent.saga.step_action": step_action,
309
+ "agent.saga.state": status,
310
+ }
311
+ if agent_did:
312
+ attrs["agent.did"] = agent_did
313
+ if session_id:
314
+ attrs["session.id"] = session_id
315
+ if extra_attrs:
316
+ # Include select payload fields as span attributes
317
+ for key in ("error", "reason", "result"):
318
+ if key in extra_attrs:
319
+ attrs[f"agent.saga.{key}"] = str(extra_attrs[key])
320
+
321
+ record = SagaSpanRecord(
322
+ name=name,
323
+ saga_id=saga_id,
324
+ step_id=step_id,
325
+ step_action=step_action,
326
+ start_time=start_time,
327
+ end_time=end_time,
328
+ status=status,
329
+ attributes=attrs,
330
+ )
331
+ self._completed_spans.append(record)
332
+
333
+ # Forward to sink if attached
334
+ if self._sink is not None:
335
+ self._sink.record_span(
336
+ name=record.name,
337
+ start_time=record.start_time,
338
+ end_time=record.end_time,
339
+ attributes=record.attributes,
340
+ status=record.status,
341
+ )
@@ -0,0 +1,121 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Provider Discovery System for Agent Hypervisor
5
+
6
+ Enables plug-and-play upgrades from Public Preview to Advanced implementations.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from importlib.metadata import entry_points
13
+ from typing import Any
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ PROVIDER_GROUPS = {
18
+ "ring_engine": "hypervisor.providers.ring_engine",
19
+ "liability": "hypervisor.providers.liability",
20
+ "saga_engine": "hypervisor.providers.saga_engine",
21
+ "breach_detector": "hypervisor.providers.breach_detector",
22
+ "session_manager": "hypervisor.providers.session_manager",
23
+ "audit_engine": "hypervisor.providers.audit_engine",
24
+ }
25
+
26
+ _provider_cache: dict[str, Any] = {}
27
+
28
+
29
+ def _discover_provider(group: str) -> type | None:
30
+ """Discover an advanced provider via entry_points."""
31
+ if group in _provider_cache:
32
+ return _provider_cache[group]
33
+
34
+ try:
35
+ eps = entry_points(group=group)
36
+ if eps:
37
+ ep = next(iter(eps))
38
+ provider_cls = ep.load()
39
+ if not isinstance(provider_cls, type):
40
+ logger.warning(
41
+ "Provider %s is not a class, skipping", ep.name
42
+ )
43
+ else:
44
+ _provider_cache[group] = provider_cls
45
+ logger.info("Advanced provider loaded: %s from %s", ep.name, ep.value)
46
+ return provider_cls
47
+ except Exception:
48
+ logger.debug("Provider discovery failed for %s", group, exc_info=True)
49
+
50
+ _provider_cache[group] = None
51
+ return None
52
+
53
+
54
+ def get_ring_engine(**kwargs: Any):
55
+ """Get the best available execution ring engine.
56
+
57
+ Advanced: 4-ring privilege escalation with breach detection.
58
+ Community: Basic ring assignment with classifier.
59
+ """
60
+ provider = _discover_provider(PROVIDER_GROUPS["ring_engine"])
61
+ if provider is not None:
62
+ return provider(**kwargs)
63
+
64
+ from hypervisor.rings.enforcer import RingEnforcer
65
+ return RingEnforcer(**kwargs)
66
+
67
+
68
+ def get_liability_engine(**kwargs: Any):
69
+ """Get the best available liability engine.
70
+
71
+ Advanced: Shapley-value fault attribution with vouch cascades.
72
+ Community: Basic vouching with linear slashing.
73
+ """
74
+ provider = _discover_provider(PROVIDER_GROUPS["liability"])
75
+ if provider is not None:
76
+ return provider(**kwargs)
77
+
78
+ from hypervisor.liability.engine import LiabilityEngine
79
+ return LiabilityEngine(**kwargs)
80
+
81
+
82
+ def get_saga_engine(**kwargs: Any):
83
+ """Get the best available saga orchestration engine.
84
+
85
+ Advanced: Multi-pattern saga with parallel fan-out and escalation.
86
+ Community: Sequential saga with basic compensation.
87
+ """
88
+ provider = _discover_provider(PROVIDER_GROUPS["saga_engine"])
89
+ if provider is not None:
90
+ return provider(**kwargs)
91
+
92
+ from hypervisor.saga.engine import SagaOrchestrator
93
+ return SagaOrchestrator(**kwargs)
94
+
95
+
96
+ def get_breach_detector(**kwargs: Any):
97
+ """Get the best available breach detector.
98
+
99
+ Advanced: Multi-signal breach detection with severity scoring.
100
+ Community: Basic threshold-based detection with safe defaults.
101
+ """
102
+ provider = _discover_provider(PROVIDER_GROUPS["breach_detector"])
103
+ if provider is not None:
104
+ return provider(**kwargs)
105
+
106
+ from hypervisor.rings.breach_detector import RingBreachDetector
107
+ return RingBreachDetector(**kwargs)
108
+
109
+
110
+ def list_providers() -> dict[str, str]:
111
+ """List all provider slots and their current implementations."""
112
+ result = {}
113
+ for name, group in PROVIDER_GROUPS.items():
114
+ provider = _discover_provider(group)
115
+ result[name] = "advanced" if provider is not None else "community"
116
+ return result
117
+
118
+
119
+ def clear_cache() -> None:
120
+ """Clear the provider cache."""
121
+ _provider_cache.clear()
hypervisor/py.typed ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Reversibility subpackage."""
@@ -0,0 +1,108 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Reversibility Registry
5
+
6
+ Maps every declared action to its Execute_API and Undo_API,
7
+ populated during the IATP handshake.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+ from hypervisor.models import ActionDescriptor, ReversibilityLevel
15
+
16
+
17
+ @dataclass
18
+ class ReversibilityEntry:
19
+ """An entry in the reversibility registry."""
20
+
21
+ action_id: str
22
+ execute_api: str
23
+ undo_api: str | None
24
+ reversibility: ReversibilityLevel
25
+ undo_window_seconds: int
26
+ compensation_method: str | None
27
+ risk_weight: float
28
+ undo_api_healthy: bool = True
29
+ last_health_check: str | None = None
30
+
31
+
32
+ class ReversibilityRegistry:
33
+ """
34
+ Session-scoped registry of action reversibility mappings.
35
+
36
+ Auto-populated from IATP Capability Manifests during handshake.
37
+ Provides lookup for the Saga orchestrator during rollback.
38
+ """
39
+
40
+ def __init__(self, session_id: str) -> None:
41
+ self.session_id = session_id
42
+ self._entries: dict[str, ReversibilityEntry] = {}
43
+
44
+ def register(self, action: ActionDescriptor) -> ReversibilityEntry:
45
+ """Register an action from a capability manifest."""
46
+ entry = ReversibilityEntry(
47
+ action_id=action.action_id,
48
+ execute_api=action.execute_api,
49
+ undo_api=action.undo_api,
50
+ reversibility=action.reversibility,
51
+ undo_window_seconds=action.undo_window_seconds,
52
+ compensation_method=action.compensation_method,
53
+ risk_weight=action.risk_weight,
54
+ )
55
+ self._entries[action.action_id] = entry
56
+ return entry
57
+
58
+ def register_from_manifest(self, actions: list[ActionDescriptor]) -> int:
59
+ """Register all actions from a manifest. Returns count registered."""
60
+ for action in actions:
61
+ self.register(action)
62
+ return len(actions)
63
+
64
+ def get(self, action_id: str) -> ReversibilityEntry | None:
65
+ """Look up an action's reversibility entry."""
66
+ return self._entries.get(action_id)
67
+
68
+ def get_undo_api(self, action_id: str) -> str | None:
69
+ """Get the Undo_API for an action, if any."""
70
+ entry = self._entries.get(action_id)
71
+ return entry.undo_api if entry else None
72
+
73
+ def is_reversible(self, action_id: str) -> bool:
74
+ """Check if an action has any reversibility."""
75
+ entry = self._entries.get(action_id)
76
+ if not entry:
77
+ return False
78
+ return entry.reversibility != ReversibilityLevel.NONE
79
+
80
+ def get_risk_weight(self, action_id: str) -> float:
81
+ """Get the risk weight ω for an action."""
82
+ entry = self._entries.get(action_id)
83
+ return entry.risk_weight if entry else ReversibilityLevel.NONE.default_risk_weight
84
+
85
+ def has_non_reversible_actions(self) -> bool:
86
+ """Check if any registered action is non-reversible."""
87
+ return any(
88
+ e.reversibility == ReversibilityLevel.NONE
89
+ for e in self._entries.values()
90
+ )
91
+
92
+ def mark_undo_unhealthy(self, action_id: str) -> None:
93
+ """Mark an Undo_API as unhealthy (failed health check)."""
94
+ entry = self._entries.get(action_id)
95
+ if entry:
96
+ entry.undo_api_healthy = False
97
+
98
+ @property
99
+ def entries(self) -> list[ReversibilityEntry]:
100
+ return list(self._entries.values())
101
+
102
+ @property
103
+ def non_reversible_actions(self) -> list[str]:
104
+ return [
105
+ e.action_id
106
+ for e in self._entries.values()
107
+ if e.reversibility == ReversibilityLevel.NONE
108
+ ]
@@ -0,0 +1,21 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Execution rings subpackage — enforcement, classification, elevation, breach detection."""
4
+
5
+ from hypervisor.rings.breach_detector import BreachEvent, BreachSeverity, RingBreachDetector
6
+ from hypervisor.rings.elevation import (
7
+ ElevationDenialReason,
8
+ RingElevation,
9
+ RingElevationError,
10
+ RingElevationManager,
11
+ )
12
+
13
+ __all__ = [
14
+ "RingElevationManager",
15
+ "RingElevation",
16
+ "RingElevationError",
17
+ "ElevationDenialReason",
18
+ "RingBreachDetector",
19
+ "BreachEvent",
20
+ "BreachSeverity",
21
+ ]