agent_hypervisor 3.6.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.6.0 → agent_hypervisor-3.7.0}/.gitignore +1 -0
  2. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/PKG-INFO +1 -1
  3. agent_hypervisor-3.7.0/examples/requirements.txt +1 -0
  4. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/pyproject.toml +1 -1
  5. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/__init__.py +19 -3
  6. agent_hypervisor-3.7.0/src/hypervisor/rings/elevation.py +424 -0
  7. agent_hypervisor-3.7.0/src/hypervisor/rings/enforcer.py +215 -0
  8. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/rate_limiter.py +6 -0
  9. agent_hypervisor-3.7.0/src/hypervisor/session/isolation.py +191 -0
  10. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/vector_clock.py +27 -5
  11. agent_hypervisor-3.7.0/tests/test_session_isolation.py +95 -0
  12. agent_hypervisor-3.7.0/tests/test_spec_hypervisor_conformance.py +837 -0
  13. agent_hypervisor-3.7.0/tests/unit/test_ring_enforcement.py +533 -0
  14. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_ring_improvements.py +25 -14
  15. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_session_security.py +6 -9
  16. agent_hypervisor-3.6.0/examples/requirements.txt +0 -1
  17. agent_hypervisor-3.6.0/src/hypervisor/rings/elevation.py +0 -219
  18. agent_hypervisor-3.6.0/src/hypervisor/rings/enforcer.py +0 -97
  19. agent_hypervisor-3.6.0/src/hypervisor/session/isolation.py +0 -37
  20. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/CHANGELOG.md +0 -0
  21. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/LICENSE +0 -0
  22. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/README.md +0 -0
  23. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/SECURITY.md +0 -0
  24. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/benchmarks/bench_hypervisor.py +0 -0
  25. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/benchmarks/results/BENCHMARKS.md +0 -0
  26. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/benchmarks/results/benchmarks.json +0 -0
  27. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/docs/api-reference.md +0 -0
  28. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/docs/joint-liability-guide.md +0 -0
  29. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/dashboard/README.md +0 -0
  30. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/dashboard/app.py +0 -0
  31. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/dashboard/requirements.txt +0 -0
  32. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/demo.py +0 -0
  33. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/Dockerfile +0 -0
  34. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/README.md +0 -0
  35. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/__init__.py +0 -0
  36. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/dashboard.py +0 -0
  37. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/sample_workflow.py +0 -0
  38. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/server.py +0 -0
  39. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/config/hypervisor.yaml +0 -0
  40. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/docker-compose.yml +0 -0
  41. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/notebooks/README.md +0 -0
  42. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/notebooks/hypervisor-exploration.ipynb +0 -0
  43. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/__init__.py +0 -0
  44. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/__init__.py +0 -0
  45. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/models.py +0 -0
  46. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/server.py +0 -0
  47. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/__init__.py +0 -0
  48. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/commitment.py +0 -0
  49. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/delta.py +0 -0
  50. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/gc.py +0 -0
  51. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/__init__.py +0 -0
  52. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/formatters.py +0 -0
  53. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/session_commands.py +0 -0
  54. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/constants.py +0 -0
  55. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/core.py +0 -0
  56. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/__init__.py +0 -0
  57. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/iatp_adapter.py +0 -0
  58. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/nexus_adapter.py +0 -0
  59. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/verification_adapter.py +0 -0
  60. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/__init__.py +0 -0
  61. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/attribution.py +0 -0
  62. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/ledger.py +0 -0
  63. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/quarantine.py +0 -0
  64. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/slashing.py +0 -0
  65. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/vouching.py +0 -0
  66. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/models.py +0 -0
  67. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/__init__.py +0 -0
  68. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/causal_trace.py +0 -0
  69. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/event_bus.py +0 -0
  70. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/prometheus_collector.py +0 -0
  71. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/saga_span_exporter.py +0 -0
  72. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/providers.py +0 -0
  73. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/py.typed +0 -0
  74. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/__init__.py +0 -0
  75. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/registry.py +0 -0
  76. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/breach_detector.py +0 -0
  77. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/classifier.py +0 -0
  78. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/__init__.py +0 -0
  79. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/checkpoint.py +0 -0
  80. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/dsl.py +0 -0
  81. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/fan_out.py +0 -0
  82. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/orchestrator.py +0 -0
  83. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/schema.py +0 -0
  84. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/state_machine.py +0 -0
  85. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/__init__.py +0 -0
  86. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/kill_switch.py +0 -0
  87. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/__init__.py +0 -0
  88. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/intent_locks.py +0 -0
  89. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/sso.py +0 -0
  90. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/__init__.py +0 -0
  91. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/history.py +0 -0
  92. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/__init__.py +0 -0
  93. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/integration/__init__.py +0 -0
  94. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/integration/test_hypervisor_e2e.py +0 -0
  95. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/integration/test_scenarios.py +0 -0
  96. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_agent_manager.py +0 -0
  97. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_breach_detector.py +0 -0
  98. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_classifier.py +0 -0
  99. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_kill_switch.py +0 -0
  100. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_providers.py +0 -0
  101. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_rate_limiter.py +0 -0
  102. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_shapley_attribution.py +0 -0
  103. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/__init__.py +0 -0
  104. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_audit.py +0 -0
  105. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_cli.py +0 -0
  106. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_config_validation.py +0 -0
  107. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability.py +0 -0
  108. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability_improvements.py +0 -0
  109. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_models.py +0 -0
  110. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_observability.py +0 -0
  111. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_prometheus_collector.py +0 -0
  112. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_reversibility_registry.py +0 -0
  113. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_rings.py +0 -0
  114. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga.py +0 -0
  115. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_improvements.py +0 -0
  116. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_schema.py +0 -0
  117. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_span_exporter.py +0 -0
  118. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_session.py +0 -0
  119. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_slashing.py +0 -0
  120. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_vfs_substrate.py +0 -0
  121. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/README.md +0 -0
  122. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/demo.py +0 -0
  123. {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tutorials/saga-compensation/README.md +0 -0
  124. {agent_hypervisor-3.6.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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent_hypervisor
3
- Version: 3.6.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
@@ -0,0 +1 @@
1
+ agent-hypervisor>=3.6.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agent_hypervisor"
7
- version = "3.6.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"}
@@ -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
  ]
@@ -0,0 +1,424 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Ring Elevation — time-bounded privilege escalation with policy enforcement.
5
+
6
+ Agents can request temporary elevation to a higher-privilege ring.
7
+ Elevations are granted based on trust score thresholds, require attestation
8
+ for sensitive rings, and automatically expire via tick().
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import uuid
14
+ from dataclasses import dataclass, field
15
+ from datetime import UTC, datetime, timedelta
16
+ from typing import Callable, Optional
17
+
18
+ from hypervisor.models import ExecutionRing
19
+
20
+
21
+ class RingElevationError(Exception):
22
+ """Raised for invalid ring elevation requests."""
23
+
24
+ def __init__(
25
+ self,
26
+ message: str,
27
+ *,
28
+ current_ring: ExecutionRing | None = None,
29
+ target_ring: ExecutionRing | None = None,
30
+ reason: str | None = None,
31
+ agent_did: str = "",
32
+ ) -> None:
33
+ super().__init__(message)
34
+ self.current_ring = current_ring
35
+ self.target_ring = target_ring
36
+ self.denial_reason = reason
37
+ self.agent_did = agent_did
38
+
39
+
40
+ class ElevationDenialReason:
41
+ """Standard denial reasons for ring elevation failures."""
42
+
43
+ COMMUNITY_EDITION = "community_edition"
44
+ INVALID_TARGET = "invalid_target"
45
+ RING_0_FORBIDDEN = "ring_0_forbidden"
46
+ INSUFFICIENT_TRUST = "insufficient_trust"
47
+ NO_SPONSORSHIP = "no_sponsorship"
48
+ EXPIRED_TTL = "expired_ttl"
49
+ DUPLICATE = "duplicate_elevation"
50
+
51
+
52
+ _RING_LABELS: dict[ExecutionRing, str] = {
53
+ ExecutionRing.RING_0_ROOT: "Ring 0 (Root)",
54
+ ExecutionRing.RING_1_PRIVILEGED: "Ring 1 (Privileged)",
55
+ ExecutionRing.RING_2_STANDARD: "Ring 2 (Standard)",
56
+ ExecutionRing.RING_3_SANDBOX: "Ring 3 (Sandbox)",
57
+ }
58
+
59
+ _DOCS_URL = "https://github.com/microsoft/agent-governance-toolkit/blob/main/docs/rings.md"
60
+
61
+ # Trust score thresholds for elevation approval
62
+ ELEVATION_TRUST_THRESHOLDS: dict[ExecutionRing, float] = {
63
+ ExecutionRing.RING_1_PRIVILEGED: 0.85,
64
+ ExecutionRing.RING_2_STANDARD: 0.50,
65
+ }
66
+
67
+
68
+ @dataclass
69
+ class RingElevation:
70
+ """A time-bounded ring elevation grant."""
71
+
72
+ elevation_id: str = field(default_factory=lambda: f"elev:{uuid.uuid4().hex[:8]}")
73
+ agent_did: str = ""
74
+ session_id: str = ""
75
+ original_ring: ExecutionRing = ExecutionRing.RING_3_SANDBOX
76
+ elevated_ring: ExecutionRing = ExecutionRing.RING_2_STANDARD
77
+ granted_at: datetime = field(default_factory=lambda: datetime.now(UTC))
78
+ expires_at: datetime = field(default_factory=lambda: datetime.now(UTC))
79
+ attestation: str | None = None
80
+ reason: str = ""
81
+ is_active: bool = True
82
+
83
+ @property
84
+ def is_expired(self) -> bool:
85
+ return datetime.now(UTC) >= self.expires_at
86
+
87
+ @property
88
+ def remaining_seconds(self) -> float:
89
+ remaining = (self.expires_at - datetime.now(UTC)).total_seconds()
90
+ return max(0.0, remaining)
91
+
92
+
93
+ @dataclass
94
+ class ChildRegistration:
95
+ """Tracks a parent-child ring relationship."""
96
+
97
+ parent_did: str
98
+ child_did: str
99
+ parent_ring: ExecutionRing
100
+ child_ring: ExecutionRing
101
+ registered_at: datetime = field(default_factory=lambda: datetime.now(UTC))
102
+
103
+
104
+ class RingElevationManager:
105
+ """Manages time-bounded ring elevations with policy enforcement.
106
+
107
+ Elevation requests are evaluated against trust score thresholds.
108
+ Ring 1 requires attestation. Ring 0 is always forbidden via standard API.
109
+ Active elevations expire based on TTL and are cleaned up by tick().
110
+ """
111
+
112
+ MAX_ELEVATION_TTL = 3600
113
+ DEFAULT_TTL = 300
114
+
115
+ def __init__(
116
+ self,
117
+ trust_provider: Optional[Callable[[str], float]] = None,
118
+ ) -> None:
119
+ """Initialize the elevation manager.
120
+
121
+ Args:
122
+ trust_provider: Callable that returns trust score (0.0-1.0)
123
+ for a given agent DID. If None, elevation requires explicit
124
+ trust_score parameter.
125
+ """
126
+ self._elevations: dict[str, RingElevation] = {}
127
+ self._children: dict[str, ChildRegistration] = {}
128
+ self._trust_provider = trust_provider
129
+
130
+ def request_elevation(
131
+ self,
132
+ agent_did: str,
133
+ session_id: str,
134
+ current_ring: ExecutionRing,
135
+ target_ring: ExecutionRing,
136
+ ttl_seconds: int = 0,
137
+ attestation: str | None = None,
138
+ reason: str = "",
139
+ trust_score: Optional[float] = None,
140
+ ) -> RingElevation:
141
+ """Request temporary ring elevation.
142
+
143
+ Elevation is granted if the agent's trust score meets the threshold
144
+ for the target ring. Ring 1 additionally requires an attestation string.
145
+
146
+ Args:
147
+ agent_did: DID of the requesting agent.
148
+ session_id: Current session identifier.
149
+ current_ring: Agent's current execution ring.
150
+ target_ring: Requested elevated ring.
151
+ ttl_seconds: Duration in seconds (0 = DEFAULT_TTL, capped at MAX).
152
+ attestation: Required for Ring 1 elevation.
153
+ reason: Human-readable justification.
154
+ trust_score: Agent's trust score (0.0-1.0). If None, uses trust_provider.
155
+
156
+ Returns:
157
+ RingElevation grant if approved.
158
+
159
+ Raises:
160
+ RingElevationError: If elevation is denied.
161
+ """
162
+ # Validate: target must be a higher privilege (lower numeric value)
163
+ if target_ring.value >= current_ring.value:
164
+ denial = ElevationDenialReason.INVALID_TARGET
165
+ raise RingElevationError(
166
+ _build_elevation_error_message(
167
+ current_ring=current_ring,
168
+ target_ring=target_ring,
169
+ reason=denial,
170
+ agent_did=agent_did,
171
+ ),
172
+ current_ring=current_ring,
173
+ target_ring=target_ring,
174
+ reason=denial,
175
+ agent_did=agent_did,
176
+ )
177
+
178
+ # Validate: Ring 0 cannot be requested via standard API
179
+ if target_ring == ExecutionRing.RING_0_ROOT:
180
+ denial = ElevationDenialReason.RING_0_FORBIDDEN
181
+ raise RingElevationError(
182
+ _build_elevation_error_message(
183
+ current_ring=current_ring,
184
+ target_ring=target_ring,
185
+ reason=denial,
186
+ agent_did=agent_did,
187
+ ),
188
+ current_ring=current_ring,
189
+ target_ring=target_ring,
190
+ reason=denial,
191
+ agent_did=agent_did,
192
+ )
193
+
194
+ # Check for duplicate active elevation
195
+ existing = self._find_active(agent_did, session_id)
196
+ if existing is not None:
197
+ denial = ElevationDenialReason.DUPLICATE
198
+ raise RingElevationError(
199
+ _build_elevation_error_message(
200
+ current_ring=current_ring,
201
+ target_ring=target_ring,
202
+ reason=denial,
203
+ agent_did=agent_did,
204
+ ),
205
+ current_ring=current_ring,
206
+ target_ring=target_ring,
207
+ reason=denial,
208
+ agent_did=agent_did,
209
+ )
210
+
211
+ # Resolve trust score
212
+ score = trust_score
213
+ if score is None and self._trust_provider:
214
+ score = self._trust_provider(agent_did)
215
+
216
+ if score is None:
217
+ denial = ElevationDenialReason.INSUFFICIENT_TRUST
218
+ raise RingElevationError(
219
+ _build_elevation_error_message(
220
+ current_ring=current_ring,
221
+ target_ring=target_ring,
222
+ reason=denial,
223
+ agent_did=agent_did,
224
+ ),
225
+ current_ring=current_ring,
226
+ target_ring=target_ring,
227
+ reason=denial,
228
+ agent_did=agent_did,
229
+ )
230
+
231
+ # Check trust threshold
232
+ threshold = ELEVATION_TRUST_THRESHOLDS.get(target_ring, 1.0)
233
+ if score < threshold:
234
+ denial = ElevationDenialReason.INSUFFICIENT_TRUST
235
+ raise RingElevationError(
236
+ _build_elevation_error_message(
237
+ current_ring=current_ring,
238
+ target_ring=target_ring,
239
+ reason=denial,
240
+ agent_did=agent_did,
241
+ ),
242
+ current_ring=current_ring,
243
+ target_ring=target_ring,
244
+ reason=denial,
245
+ agent_did=agent_did,
246
+ )
247
+
248
+ # Ring 1 requires attestation
249
+ if target_ring == ExecutionRing.RING_1_PRIVILEGED and not attestation:
250
+ denial = ElevationDenialReason.NO_SPONSORSHIP
251
+ raise RingElevationError(
252
+ _build_elevation_error_message(
253
+ current_ring=current_ring,
254
+ target_ring=target_ring,
255
+ reason=denial,
256
+ agent_did=agent_did,
257
+ ),
258
+ current_ring=current_ring,
259
+ target_ring=target_ring,
260
+ reason=denial,
261
+ agent_did=agent_did,
262
+ )
263
+
264
+ # Compute TTL
265
+ ttl = ttl_seconds if ttl_seconds > 0 else self.DEFAULT_TTL
266
+ ttl = min(ttl, self.MAX_ELEVATION_TTL)
267
+
268
+ # Grant elevation
269
+ now = datetime.now(UTC)
270
+ elevation = RingElevation(
271
+ agent_did=agent_did,
272
+ session_id=session_id,
273
+ original_ring=current_ring,
274
+ elevated_ring=target_ring,
275
+ granted_at=now,
276
+ expires_at=now + timedelta(seconds=ttl),
277
+ attestation=attestation,
278
+ reason=reason,
279
+ is_active=True,
280
+ )
281
+ self._elevations[elevation.elevation_id] = elevation
282
+ return elevation
283
+
284
+ def get_active_elevation(self, agent_did: str, session_id: str) -> RingElevation | None:
285
+ """Get the active (non-expired) elevation for an agent in a session."""
286
+ elev = self._find_active(agent_did, session_id)
287
+ if elev and not elev.is_expired:
288
+ return elev
289
+ return None
290
+
291
+ def get_effective_ring(
292
+ self, agent_did: str, session_id: str, base_ring: ExecutionRing
293
+ ) -> ExecutionRing:
294
+ """Get the effective ring considering active elevations."""
295
+ elev = self.get_active_elevation(agent_did, session_id)
296
+ if elev:
297
+ return elev.elevated_ring
298
+ return base_ring
299
+
300
+ def revoke_elevation(self, elevation_id: str) -> None:
301
+ """Revoke an active elevation by ID.
302
+
303
+ Raises:
304
+ RingElevationError: If elevation not found or already inactive.
305
+ """
306
+ elev = self._elevations.get(elevation_id)
307
+ if elev is None:
308
+ raise RingElevationError(
309
+ f"Elevation {elevation_id} not found",
310
+ reason="not_found",
311
+ )
312
+ elev.is_active = False
313
+
314
+ def tick(self) -> list[RingElevation]:
315
+ """Expire all elevations past their TTL.
316
+
317
+ Returns:
318
+ List of elevations that were expired by this tick.
319
+ """
320
+ expired: list[RingElevation] = []
321
+ for elev in self._elevations.values():
322
+ if elev.is_active and elev.is_expired:
323
+ elev.is_active = False
324
+ expired.append(elev)
325
+ return expired
326
+
327
+ def register_child(
328
+ self, parent_did: str, child_did: str, parent_ring: ExecutionRing
329
+ ) -> ExecutionRing:
330
+ """Register a child agent with ring <= parent ring.
331
+
332
+ Child agents are assigned one ring level lower privilege than parent
333
+ (higher numeric value), capped at Ring 3.
334
+
335
+ Args:
336
+ parent_did: DID of the parent agent.
337
+ child_did: DID of the child agent.
338
+ parent_ring: Parent's current execution ring.
339
+
340
+ Returns:
341
+ The assigned ring for the child.
342
+ """
343
+ child_ring_value = min(parent_ring.value + 1, ExecutionRing.RING_3_SANDBOX.value)
344
+ child_ring = ExecutionRing(child_ring_value)
345
+
346
+ self._children[child_did] = ChildRegistration(
347
+ parent_did=parent_did,
348
+ child_did=child_did,
349
+ parent_ring=parent_ring,
350
+ child_ring=child_ring,
351
+ )
352
+ return child_ring
353
+
354
+ def get_child_registration(self, child_did: str) -> ChildRegistration | None:
355
+ """Get the registration for a child agent."""
356
+ return self._children.get(child_did)
357
+
358
+ @property
359
+ def active_elevations(self) -> list[RingElevation]:
360
+ """All currently active (non-expired) elevations."""
361
+ return [e for e in self._elevations.values() if e.is_active and not e.is_expired]
362
+
363
+ def _find_active(self, agent_did: str, session_id: str) -> RingElevation | None:
364
+ """Find an active elevation for an agent/session pair."""
365
+ for elev in self._elevations.values():
366
+ if (
367
+ elev.agent_did == agent_did
368
+ and elev.session_id == session_id
369
+ and elev.is_active
370
+ and not elev.is_expired
371
+ ):
372
+ return elev
373
+ return None
374
+
375
+
376
+ _REMEDIATION: dict[str, str] = {
377
+ ElevationDenialReason.COMMUNITY_EDITION: (
378
+ "Upgrade to the Enterprise edition to enable ring elevation, "
379
+ "or request access from your organization admin."
380
+ ),
381
+ ElevationDenialReason.INVALID_TARGET: (
382
+ "Request a target ring with a lower numeric value (higher privilege) "
383
+ "than the agent's current ring."
384
+ ),
385
+ ElevationDenialReason.RING_0_FORBIDDEN: (
386
+ "Ring 0 requires SRE Witness attestation and cannot be requested "
387
+ "via the standard elevation API. Contact your platform team."
388
+ ),
389
+ ElevationDenialReason.INSUFFICIENT_TRUST: (
390
+ "Increase the agent's effective trust score above the required "
391
+ "threshold by completing successful operations in the current ring."
392
+ ),
393
+ ElevationDenialReason.NO_SPONSORSHIP: (
394
+ "Obtain a sponsorship from a Ring 1 or Ring 0 agent to vouch "
395
+ "for this elevation request."
396
+ ),
397
+ ElevationDenialReason.EXPIRED_TTL: (
398
+ "Submit a new elevation request with a valid TTL "
399
+ f"(max {RingElevationManager.MAX_ELEVATION_TTL}s)."
400
+ ),
401
+ }
402
+
403
+
404
+ def _build_elevation_error_message(
405
+ *,
406
+ current_ring: ExecutionRing,
407
+ target_ring: ExecutionRing,
408
+ reason: str,
409
+ agent_did: str = "",
410
+ ) -> str:
411
+ """Build a structured, actionable error message for elevation failures."""
412
+ current_label = _RING_LABELS.get(current_ring, str(current_ring))
413
+ target_label = _RING_LABELS.get(target_ring, str(target_ring))
414
+ remediation = _REMEDIATION.get(reason, "Review the elevation requirements.")
415
+
416
+ parts = [
417
+ f"Ring elevation denied: {current_label} -> {target_label}",
418
+ ]
419
+ if agent_did:
420
+ parts.append(f" Agent: {agent_did}")
421
+ parts.append(f" Reason: {reason}")
422
+ parts.append(f" Remediation: {remediation}")
423
+ parts.append(f" Docs: {_DOCS_URL}")
424
+ return "\n".join(parts)