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,200 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Ring Breach Detector — behavioral anomaly detection for rogue agents.
5
+
6
+ Detects two classes of anomaly:
7
+
8
+ 1. **Tool-call frequency spikes** — an agent's call rate inside a sliding
9
+ window exceeds a configurable baseline by a severity-dependent multiplier.
10
+ 2. **Privilege-escalation attempts** — a low-privilege agent (Ring 3)
11
+ repeatedly calls into higher-privilege rings (Ring 0/1). The *ring
12
+ distance* amplifies the anomaly score so that sandbox→root jumps are
13
+ scored more aggressively than standard→privileged ones.
14
+
15
+ When a HIGH or CRITICAL breach is detected the internal circuit-breaker
16
+ trips and ``is_breaker_tripped()`` returns ``True`` until explicitly reset
17
+ via ``reset_breaker()``.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import time
23
+ from collections import deque
24
+ from dataclasses import dataclass, field
25
+ from datetime import UTC, datetime
26
+ from enum import Enum
27
+
28
+ from hypervisor.models import ExecutionRing
29
+
30
+
31
+ class BreachSeverity(str, Enum):
32
+ NONE = "none"
33
+ LOW = "low"
34
+ MEDIUM = "medium"
35
+ HIGH = "high"
36
+ CRITICAL = "critical"
37
+
38
+
39
+ # Multiplier thresholds: actual_rate / baseline_rate
40
+ _SEVERITY_THRESHOLDS: list[tuple[float, BreachSeverity]] = [
41
+ (20.0, BreachSeverity.CRITICAL),
42
+ (10.0, BreachSeverity.HIGH),
43
+ (5.0, BreachSeverity.MEDIUM),
44
+ (2.0, BreachSeverity.LOW),
45
+ ]
46
+
47
+
48
+ @dataclass
49
+ class BreachEvent:
50
+ """A detected ring breach anomaly."""
51
+
52
+ agent_did: str
53
+ session_id: str
54
+ severity: BreachSeverity
55
+ anomaly_score: float
56
+ call_count_window: int
57
+ expected_rate: float
58
+ actual_rate: float
59
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
60
+ details: str = ""
61
+
62
+
63
+ def _agent_key(agent_did: str, session_id: str) -> str:
64
+ """Internal composite key for per-agent tracking."""
65
+ return f"{agent_did}::{session_id}"
66
+
67
+
68
+ class RingBreachDetector:
69
+ """Behavioural anomaly detector for rogue-agent ring abuse.
70
+
71
+ Parameters
72
+ ----------
73
+ window_seconds:
74
+ Sliding window (in seconds) over which call rates are measured.
75
+ baseline_rate:
76
+ Expected calls-per-second within the window. Rates above multiples
77
+ of this value trigger breach events of increasing severity.
78
+ max_events_per_agent:
79
+ Maximum call timestamps retained per agent (bounded ``deque``).
80
+ """
81
+
82
+ def __init__(
83
+ self,
84
+ window_seconds: int = 60,
85
+ baseline_rate: float = 10.0,
86
+ max_events_per_agent: int = 1_000,
87
+ max_breach_history: int = 10_000,
88
+ ) -> None:
89
+ self.window_seconds = window_seconds
90
+ self.baseline_rate = baseline_rate
91
+ self.max_events_per_agent = max_events_per_agent
92
+
93
+ # Per-agent sliding-window timestamps
94
+ self._call_windows: dict[str, deque[float]] = {}
95
+ # Per-agent circuit-breaker flag
96
+ self._tripped: dict[str, bool] = {}
97
+ # Global breach history (bounded)
98
+ self._breach_history: deque[BreachEvent] = deque(maxlen=max_breach_history)
99
+
100
+ # ------------------------------------------------------------------
101
+ # Public API
102
+ # ------------------------------------------------------------------
103
+
104
+ def record_call(
105
+ self,
106
+ agent_did: str,
107
+ session_id: str,
108
+ agent_ring: ExecutionRing,
109
+ called_ring: ExecutionRing,
110
+ ) -> BreachEvent | None:
111
+ """Record a ring call and return a ``BreachEvent`` if anomalous.
112
+
113
+ Returns ``None`` when the call is within normal parameters.
114
+ """
115
+ key = _agent_key(agent_did, session_id)
116
+ now = time.monotonic()
117
+
118
+ # --- 1. Track timestamp in bounded deque ---
119
+ if key not in self._call_windows:
120
+ self._call_windows[key] = deque(maxlen=self.max_events_per_agent)
121
+ window = self._call_windows[key]
122
+ window.append(now)
123
+
124
+ # --- 2. Prune timestamps outside the sliding window ---
125
+ cutoff = now - self.window_seconds
126
+ while window and window[0] < cutoff:
127
+ window.popleft()
128
+
129
+ # --- 3. Compute actual rate (calls / second) ---
130
+ call_count = len(window)
131
+ actual_rate = call_count / self.window_seconds if self.window_seconds > 0 else 0.0
132
+
133
+ # --- 4. Ring-distance amplifier ---
134
+ # Upward calls (low value = higher privilege) are escalations.
135
+ # ExecutionRing values: 0=root, 1=priv, 2=std, 3=sandbox.
136
+ # ring_distance > 0 means privilege escalation.
137
+ ring_distance = int(agent_ring) - int(called_ring)
138
+ amplifier = max(ring_distance, 1) # at least 1× (no reduction)
139
+
140
+ # --- 5. Score = (actual / baseline) × amplifier ---
141
+ if self.baseline_rate <= 0:
142
+ ratio = 0.0
143
+ else:
144
+ ratio = actual_rate / self.baseline_rate
145
+ anomaly_score = ratio * amplifier
146
+
147
+ # --- 6. Map score → severity ---
148
+ severity = BreachSeverity.NONE
149
+ for threshold, sev in _SEVERITY_THRESHOLDS:
150
+ if anomaly_score >= threshold:
151
+ severity = sev
152
+ break
153
+
154
+ if severity == BreachSeverity.NONE:
155
+ return None
156
+
157
+ # --- 7. Build event ---
158
+ event = BreachEvent(
159
+ agent_did=agent_did,
160
+ session_id=session_id,
161
+ severity=severity,
162
+ anomaly_score=round(anomaly_score, 4),
163
+ call_count_window=call_count,
164
+ expected_rate=self.baseline_rate,
165
+ actual_rate=round(actual_rate, 4),
166
+ details=(
167
+ f"rate={actual_rate:.2f}/s (baseline={self.baseline_rate:.2f}/s), "
168
+ f"ring_distance={ring_distance}, amplifier={amplifier}×, "
169
+ f"score={anomaly_score:.2f}"
170
+ ),
171
+ )
172
+ self._breach_history.append(event)
173
+
174
+ # --- 8. Trip circuit-breaker on HIGH / CRITICAL ---
175
+ if severity in (BreachSeverity.HIGH, BreachSeverity.CRITICAL):
176
+ self._tripped[key] = True
177
+
178
+ return event
179
+
180
+ def is_breaker_tripped(self, agent_did: str, session_id: str) -> bool:
181
+ """Return ``True`` if the circuit-breaker is tripped for this agent."""
182
+ return self._tripped.get(_agent_key(agent_did, session_id), False)
183
+
184
+ def reset_breaker(self, agent_did: str, session_id: str) -> None:
185
+ """Reset the circuit-breaker and clear the call window for this agent."""
186
+ key = _agent_key(agent_did, session_id)
187
+ self._tripped.pop(key, None)
188
+ self._call_windows.pop(key, None)
189
+
190
+ # ------------------------------------------------------------------
191
+ # Read-only accessors
192
+ # ------------------------------------------------------------------
193
+
194
+ @property
195
+ def breach_history(self) -> list[BreachEvent]:
196
+ return list(self._breach_history)
197
+
198
+ @property
199
+ def breach_count(self) -> int:
200
+ return len(self._breach_history)
@@ -0,0 +1,78 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """
5
+ Action Risk Classifier
6
+
7
+ Classifies actions into ring levels and risk weights.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+ from hypervisor.models import ActionDescriptor, ExecutionRing, ReversibilityLevel
15
+
16
+
17
+ @dataclass
18
+ class ClassificationResult:
19
+ """Result of classifying an action."""
20
+
21
+ action_id: str
22
+ ring: ExecutionRing
23
+ risk_weight: float
24
+ reversibility: ReversibilityLevel
25
+ confidence: float = 1.0
26
+
27
+
28
+ class ActionClassifier:
29
+ """
30
+ Classifies actions into ring levels and risk weights.
31
+
32
+ Classification rules:
33
+ - Has Undo_API → reversible → Ring 2 minimum
34
+ - No Undo_API + destructive → non-reversible → Ring 1 minimum
35
+ - Config/admin operations → Ring 0
36
+ - Read-only operations → Ring 3
37
+ """
38
+
39
+ def __init__(self) -> None:
40
+ self._cache: dict[str, ClassificationResult] = {}
41
+ self._overrides: dict[str, ClassificationResult] = {}
42
+
43
+ def classify(self, action: ActionDescriptor) -> ClassificationResult:
44
+ """Classify an action and cache the result."""
45
+ if action.action_id in self._overrides:
46
+ return self._overrides[action.action_id]
47
+
48
+ if action.action_id in self._cache:
49
+ return self._cache[action.action_id]
50
+
51
+ result = ClassificationResult(
52
+ action_id=action.action_id,
53
+ ring=action.required_ring,
54
+ risk_weight=action.risk_weight,
55
+ reversibility=action.reversibility,
56
+ )
57
+ self._cache[action.action_id] = result
58
+ return result
59
+
60
+ def set_override(
61
+ self,
62
+ action_id: str,
63
+ ring: ExecutionRing | None = None,
64
+ risk_weight: float | None = None,
65
+ ) -> None:
66
+ """Set a session-level override for action classification."""
67
+ existing = self._cache.get(action_id)
68
+ self._overrides[action_id] = ClassificationResult(
69
+ action_id=action_id,
70
+ ring=ring or (existing.ring if existing else ExecutionRing.RING_3_SANDBOX),
71
+ risk_weight=risk_weight or (existing.risk_weight if existing else 0.5),
72
+ reversibility=existing.reversibility if existing else ReversibilityLevel.NONE,
73
+ confidence=0.9, # overrides have slightly lower confidence
74
+ )
75
+
76
+ def clear_cache(self) -> None:
77
+ """Clear classification cache (e.g., on manifest update)."""
78
+ self._cache.clear()
@@ -0,0 +1,219 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """
5
+ Ring Elevation — privilege escalation stubs.
6
+
7
+ Public Preview: elevation is not supported. All requests are denied.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+ from dataclasses import dataclass, field
14
+ from datetime import UTC, datetime
15
+
16
+ from hypervisor.models import ExecutionRing
17
+
18
+
19
+ class RingElevationError(Exception):
20
+ """Raised for invalid ring elevation requests."""
21
+
22
+ def __init__(
23
+ self,
24
+ message: str,
25
+ *,
26
+ current_ring: ExecutionRing | None = None,
27
+ target_ring: ExecutionRing | None = None,
28
+ reason: str | None = None,
29
+ agent_did: str = "",
30
+ ) -> None:
31
+ super().__init__(message)
32
+ self.current_ring = current_ring
33
+ self.target_ring = target_ring
34
+ self.denial_reason = reason
35
+ self.agent_did = agent_did
36
+
37
+
38
+ class ElevationDenialReason:
39
+ """Standard denial reasons for ring elevation failures."""
40
+
41
+ COMMUNITY_EDITION = "community_edition"
42
+ INVALID_TARGET = "invalid_target"
43
+ RING_0_FORBIDDEN = "ring_0_forbidden"
44
+ INSUFFICIENT_TRUST = "insufficient_trust"
45
+ NO_SPONSORSHIP = "no_sponsorship"
46
+ EXPIRED_TTL = "expired_ttl"
47
+
48
+
49
+ _RING_LABELS: dict[ExecutionRing, str] = {
50
+ ExecutionRing.RING_0_ROOT: "Ring 0 (Root)",
51
+ ExecutionRing.RING_1_PRIVILEGED: "Ring 1 (Privileged)",
52
+ ExecutionRing.RING_2_STANDARD: "Ring 2 (Standard)",
53
+ ExecutionRing.RING_3_SANDBOX: "Ring 3 (Sandbox)",
54
+ }
55
+
56
+ _DOCS_URL = "https://github.com/microsoft/agent-governance-toolkit/blob/main/docs/rings.md"
57
+
58
+
59
+ @dataclass
60
+ class RingElevation:
61
+ """A ring elevation grant (stub in Public Preview)."""
62
+
63
+ elevation_id: str = field(default_factory=lambda: f"elev:{uuid.uuid4().hex[:8]}")
64
+ agent_did: str = ""
65
+ session_id: str = ""
66
+ original_ring: ExecutionRing = ExecutionRing.RING_3_SANDBOX
67
+ elevated_ring: ExecutionRing = ExecutionRing.RING_2_STANDARD
68
+ granted_at: datetime = field(default_factory=lambda: datetime.now(UTC))
69
+ expires_at: datetime = field(default_factory=lambda: datetime.now(UTC))
70
+ attestation: str | None = None
71
+ reason: str = ""
72
+ is_active: bool = True
73
+
74
+ @property
75
+ def is_expired(self) -> bool:
76
+ return True
77
+
78
+ @property
79
+ def remaining_seconds(self) -> float:
80
+ return 0.0
81
+
82
+
83
+ class RingElevationManager:
84
+ """Manages ring elevations (Public Preview: always denies)."""
85
+
86
+ MAX_ELEVATION_TTL = 3600
87
+ DEFAULT_TTL = 300
88
+
89
+ def __init__(self) -> None:
90
+ self._elevations: dict[str, RingElevation] = {}
91
+
92
+ def request_elevation(
93
+ self,
94
+ agent_did: str,
95
+ session_id: str,
96
+ current_ring: ExecutionRing,
97
+ target_ring: ExecutionRing,
98
+ ttl_seconds: int = 0,
99
+ attestation: str | None = None,
100
+ reason: str = "",
101
+ ) -> RingElevation:
102
+ """Request temporary ring elevation (Public Preview: always denied)."""
103
+ # Validate: target must be a higher privilege (lower numeric value)
104
+ if target_ring.value >= current_ring.value:
105
+ denial = ElevationDenialReason.INVALID_TARGET
106
+ raise RingElevationError(
107
+ _build_elevation_error_message(
108
+ current_ring=current_ring,
109
+ target_ring=target_ring,
110
+ reason=denial,
111
+ agent_did=agent_did,
112
+ ),
113
+ current_ring=current_ring,
114
+ target_ring=target_ring,
115
+ reason=denial,
116
+ agent_did=agent_did,
117
+ )
118
+
119
+ # Validate: Ring 0 cannot be requested via standard API
120
+ if target_ring == ExecutionRing.RING_0_ROOT:
121
+ denial = ElevationDenialReason.RING_0_FORBIDDEN
122
+ raise RingElevationError(
123
+ _build_elevation_error_message(
124
+ current_ring=current_ring,
125
+ target_ring=target_ring,
126
+ reason=denial,
127
+ agent_did=agent_did,
128
+ ),
129
+ current_ring=current_ring,
130
+ target_ring=target_ring,
131
+ reason=denial,
132
+ agent_did=agent_did,
133
+ )
134
+
135
+ # Public Preview: all valid requests are denied
136
+ denial = ElevationDenialReason.COMMUNITY_EDITION
137
+ raise RingElevationError(
138
+ _build_elevation_error_message(
139
+ current_ring=current_ring,
140
+ target_ring=target_ring,
141
+ reason=denial,
142
+ agent_did=agent_did,
143
+ ),
144
+ current_ring=current_ring,
145
+ target_ring=target_ring,
146
+ reason=denial,
147
+ agent_did=agent_did,
148
+ )
149
+
150
+ def get_active_elevation(self, agent_did: str, session_id: str) -> RingElevation | None:
151
+ return None
152
+
153
+ def get_effective_ring(self, agent_did: str, session_id: str, base_ring: ExecutionRing) -> ExecutionRing:
154
+ return base_ring
155
+
156
+ def revoke_elevation(self, elevation_id: str) -> None:
157
+ raise RingElevationError(f"Elevation {elevation_id} not found")
158
+
159
+ def tick(self) -> list[RingElevation]:
160
+ return []
161
+
162
+ def register_child(self, parent_did: str, child_did: str, parent_ring: ExecutionRing) -> ExecutionRing:
163
+ child_ring_value = min(parent_ring.value + 1, ExecutionRing.RING_3_SANDBOX.value)
164
+ return ExecutionRing(child_ring_value)
165
+
166
+ @property
167
+ def active_elevations(self) -> list[RingElevation]:
168
+ return []
169
+
170
+
171
+ _REMEDIATION: dict[str, str] = {
172
+ ElevationDenialReason.COMMUNITY_EDITION: (
173
+ "Upgrade to the Enterprise edition to enable ring elevation, "
174
+ "or request access from your organization admin."
175
+ ),
176
+ ElevationDenialReason.INVALID_TARGET: (
177
+ "Request a target ring with a lower numeric value (higher privilege) "
178
+ "than the agent's current ring."
179
+ ),
180
+ ElevationDenialReason.RING_0_FORBIDDEN: (
181
+ "Ring 0 requires SRE Witness attestation and cannot be requested "
182
+ "via the standard elevation API. Contact your platform team."
183
+ ),
184
+ ElevationDenialReason.INSUFFICIENT_TRUST: (
185
+ "Increase the agent's effective trust score above the required "
186
+ "threshold by completing successful operations in the current ring."
187
+ ),
188
+ ElevationDenialReason.NO_SPONSORSHIP: (
189
+ "Obtain a sponsorship from a Ring 1 or Ring 0 agent to vouch "
190
+ "for this elevation request."
191
+ ),
192
+ ElevationDenialReason.EXPIRED_TTL: (
193
+ "Submit a new elevation request with a valid TTL "
194
+ f"(max {RingElevationManager.MAX_ELEVATION_TTL}s)."
195
+ ),
196
+ }
197
+
198
+
199
+ def _build_elevation_error_message(
200
+ *,
201
+ current_ring: ExecutionRing,
202
+ target_ring: ExecutionRing,
203
+ reason: str,
204
+ agent_did: str = "",
205
+ ) -> str:
206
+ """Build a structured, actionable error message for elevation failures."""
207
+ current_label = _RING_LABELS.get(current_ring, str(current_ring))
208
+ target_label = _RING_LABELS.get(target_ring, str(target_ring))
209
+ remediation = _REMEDIATION.get(reason, "Review the elevation requirements.")
210
+
211
+ parts = [
212
+ f"Ring elevation denied: {current_label} -> {target_label}",
213
+ ]
214
+ if agent_did:
215
+ parts.append(f" Agent: {agent_did}")
216
+ parts.append(f" Reason: {reason}")
217
+ parts.append(f" Remediation: {remediation}")
218
+ parts.append(f" Docs: {_DOCS_URL}")
219
+ return "\n".join(parts)
@@ -0,0 +1,97 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """
5
+ Ring Enforcer — simple 2-tier access control.
6
+
7
+ Public Preview: agents get RING_1 (trust > 0.7) or RING_2 (default).
8
+ Ring 0 is reserved for kernel-only operations and always denied.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+
15
+ from hypervisor.constants import RING_1_ENFORCER_THRESHOLD
16
+ from hypervisor.models import ActionDescriptor, ExecutionRing
17
+
18
+
19
+ @dataclass
20
+ class RingCheckResult:
21
+ """Result of a ring enforcement check."""
22
+
23
+ allowed: bool
24
+ required_ring: ExecutionRing
25
+ agent_ring: ExecutionRing
26
+ eff_score: float
27
+ reason: str
28
+ requires_consensus: bool = False
29
+ requires_sre_witness: bool = False
30
+
31
+
32
+ class RingEnforcer:
33
+ """
34
+ Simple 2-tier ring enforcer.
35
+
36
+ Ring 0 (Root): Always denied (kernel-only).
37
+ Ring 1 (Privileged): Requires trust > 0.7.
38
+ Ring 2 (Standard): Default for all agents.
39
+ Ring 3 (Sandbox): Read-only / research.
40
+ """
41
+
42
+ RING_1_THRESHOLD = RING_1_ENFORCER_THRESHOLD
43
+
44
+ def __init__(self) -> None:
45
+ pass
46
+
47
+ def check(
48
+ self,
49
+ agent_ring: ExecutionRing,
50
+ action: ActionDescriptor,
51
+ eff_score: float,
52
+ has_consensus: bool = False,
53
+ has_sre_witness: bool = False,
54
+ ) -> RingCheckResult:
55
+ """Check if an agent can perform an action given their ring level."""
56
+ required = action.required_ring
57
+
58
+ # Ring 0: always denied in Public Preview
59
+ if required == ExecutionRing.RING_0_ROOT:
60
+ return RingCheckResult(
61
+ allowed=False,
62
+ required_ring=required,
63
+ agent_ring=agent_ring,
64
+ eff_score=eff_score,
65
+ reason="Ring 0 actions are not available in Public Preview",
66
+ requires_sre_witness=True,
67
+ )
68
+
69
+ # Agent's ring must be <= required ring (lower number = more privileged)
70
+ if agent_ring.value > required.value:
71
+ return RingCheckResult(
72
+ allowed=False,
73
+ required_ring=required,
74
+ agent_ring=agent_ring,
75
+ eff_score=eff_score,
76
+ reason=(
77
+ f"Agent ring {agent_ring.value} insufficient for "
78
+ f"required ring {required.value}"
79
+ ),
80
+ )
81
+
82
+ return RingCheckResult(
83
+ allowed=True,
84
+ required_ring=required,
85
+ agent_ring=agent_ring,
86
+ eff_score=eff_score,
87
+ reason="Access granted",
88
+ )
89
+
90
+ def compute_ring(self, eff_score: float, has_consensus: bool = False) -> ExecutionRing:
91
+ """Compute ring assignment from trust score."""
92
+ return ExecutionRing.from_eff_score(eff_score, has_consensus)
93
+
94
+ def should_demote(self, current_ring: ExecutionRing, eff_score: float) -> bool:
95
+ """Check if an agent should be demoted based on trust drop."""
96
+ appropriate = self.compute_ring(eff_score)
97
+ return appropriate.value > current_ring.value
@@ -0,0 +1,22 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Saga subpackage — orchestration, fan-out, checkpoints, DSL."""
4
+
5
+ from hypervisor.saga.checkpoint import CheckpointManager, SemanticCheckpoint
6
+ from hypervisor.saga.dsl import SagaDefinition, SagaDSLError, SagaDSLParser
7
+ from hypervisor.saga.fan_out import FanOutGroup, FanOutOrchestrator, FanOutPolicy
8
+ from hypervisor.saga.schema import SAGA_DEFINITION_SCHEMA, SagaSchemaError, SagaSchemaValidator
9
+
10
+ __all__ = [
11
+ "FanOutOrchestrator",
12
+ "FanOutGroup",
13
+ "FanOutPolicy",
14
+ "CheckpointManager",
15
+ "SemanticCheckpoint",
16
+ "SagaDSLParser",
17
+ "SagaDefinition",
18
+ "SagaDSLError",
19
+ "SagaSchemaValidator",
20
+ "SagaSchemaError",
21
+ "SAGA_DEFINITION_SCHEMA",
22
+ ]