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.
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/.gitignore +1 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/PKG-INFO +1 -1
- agent_hypervisor-3.7.0/examples/requirements.txt +1 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/pyproject.toml +1 -1
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/__init__.py +19 -3
- agent_hypervisor-3.7.0/src/hypervisor/rings/elevation.py +424 -0
- agent_hypervisor-3.7.0/src/hypervisor/rings/enforcer.py +215 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/rate_limiter.py +6 -0
- agent_hypervisor-3.7.0/src/hypervisor/session/isolation.py +191 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/vector_clock.py +27 -5
- agent_hypervisor-3.7.0/tests/test_session_isolation.py +95 -0
- agent_hypervisor-3.7.0/tests/test_spec_hypervisor_conformance.py +837 -0
- agent_hypervisor-3.7.0/tests/unit/test_ring_enforcement.py +533 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_ring_improvements.py +25 -14
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_session_security.py +6 -9
- agent_hypervisor-3.6.0/examples/requirements.txt +0 -1
- agent_hypervisor-3.6.0/src/hypervisor/rings/elevation.py +0 -219
- agent_hypervisor-3.6.0/src/hypervisor/rings/enforcer.py +0 -97
- agent_hypervisor-3.6.0/src/hypervisor/session/isolation.py +0 -37
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/CHANGELOG.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/LICENSE +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/README.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/SECURITY.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/benchmarks/bench_hypervisor.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/benchmarks/results/BENCHMARKS.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/benchmarks/results/benchmarks.json +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/docs/api-reference.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/docs/joint-liability-guide.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/dashboard/README.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/dashboard/app.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/dashboard/requirements.txt +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/demo.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/Dockerfile +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/README.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/dashboard.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/sample_workflow.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/app/server.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/config/hypervisor.yaml +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/examples/docker-compose/docker-compose.yml +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/notebooks/README.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/notebooks/hypervisor-exploration.ipynb +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/models.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/api/server.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/commitment.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/delta.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/audit/gc.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/formatters.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/cli/session_commands.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/constants.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/core.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/iatp_adapter.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/nexus_adapter.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/integrations/verification_adapter.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/attribution.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/ledger.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/quarantine.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/slashing.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/liability/vouching.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/models.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/causal_trace.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/event_bus.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/prometheus_collector.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/observability/saga_span_exporter.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/providers.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/py.typed +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/reversibility/registry.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/breach_detector.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/rings/classifier.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/checkpoint.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/dsl.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/fan_out.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/orchestrator.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/schema.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/saga/state_machine.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/security/kill_switch.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/intent_locks.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/session/sso.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/src/hypervisor/verification/history.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/integration/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/integration/test_hypervisor_e2e.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/integration/test_scenarios.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_agent_manager.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_breach_detector.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_classifier.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_kill_switch.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_providers.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_rate_limiter.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/test_shapley_attribution.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/__init__.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_audit.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_cli.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_config_validation.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_liability_improvements.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_models.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_observability.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_prometheus_collector.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_reversibility_registry.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_rings.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_improvements.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_schema.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_saga_span_exporter.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_session.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_slashing.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tests/unit/test_vfs_substrate.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/README.md +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tutorials/execution-rings-workflow/demo.py +0 -0
- {agent_hypervisor-3.6.0 → agent_hypervisor-3.7.0}/tutorials/saga-compensation/README.md +0 -0
- {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.
|
|
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.
|
|
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
|
-
"
|
|
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)
|