agent_os_kernel 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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Declarative policy language for Agent-OS governance.
|
|
5
|
+
|
|
6
|
+
Separates policy rules (YAML/JSON data) from evaluation logic,
|
|
7
|
+
enabling policies to be authored, versioned, and shared as plain files.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .async_evaluator import AsyncPolicyEvaluator, ConcurrencyStats
|
|
11
|
+
from .bridge import document_to_governance, governance_to_document
|
|
12
|
+
from .conflict_resolution import (
|
|
13
|
+
CandidateDecision,
|
|
14
|
+
ConflictResolutionStrategy,
|
|
15
|
+
PolicyConflictResolver,
|
|
16
|
+
PolicyScope,
|
|
17
|
+
ResolutionResult,
|
|
18
|
+
)
|
|
19
|
+
from .backends import (
|
|
20
|
+
BackendDecision,
|
|
21
|
+
CedarBackend,
|
|
22
|
+
ExternalPolicyBackend,
|
|
23
|
+
OPABackend,
|
|
24
|
+
)
|
|
25
|
+
from .evaluator import PolicyDecision, PolicyEvaluator
|
|
26
|
+
from .rate_limiting import RateLimitConfig, RateLimitExceeded, TokenBucket
|
|
27
|
+
from .schema import (
|
|
28
|
+
PolicyAction,
|
|
29
|
+
PolicyCondition,
|
|
30
|
+
PolicyDefaults,
|
|
31
|
+
PolicyDocument,
|
|
32
|
+
PolicyOperator,
|
|
33
|
+
PolicyRule,
|
|
34
|
+
)
|
|
35
|
+
from .shared import (
|
|
36
|
+
Condition,
|
|
37
|
+
SharedPolicyDecision,
|
|
38
|
+
SharedPolicyEvaluator,
|
|
39
|
+
SharedPolicyRule,
|
|
40
|
+
SharedPolicySchema,
|
|
41
|
+
policy_document_to_shared,
|
|
42
|
+
shared_to_policy_document,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"AsyncPolicyEvaluator",
|
|
47
|
+
"BackendDecision",
|
|
48
|
+
"CandidateDecision",
|
|
49
|
+
"CedarBackend",
|
|
50
|
+
"ConcurrencyStats",
|
|
51
|
+
"Condition",
|
|
52
|
+
"ConflictResolutionStrategy",
|
|
53
|
+
"ExternalPolicyBackend",
|
|
54
|
+
"OPABackend",
|
|
55
|
+
"PolicyAction",
|
|
56
|
+
"PolicyCondition",
|
|
57
|
+
"PolicyConflictResolver",
|
|
58
|
+
"PolicyDecision",
|
|
59
|
+
"PolicyDefaults",
|
|
60
|
+
"PolicyDocument",
|
|
61
|
+
"PolicyEvaluator",
|
|
62
|
+
"PolicyOperator",
|
|
63
|
+
"PolicyRule",
|
|
64
|
+
"PolicyScope",
|
|
65
|
+
"RateLimitConfig",
|
|
66
|
+
"RateLimitExceeded",
|
|
67
|
+
"ResolutionResult",
|
|
68
|
+
"TokenBucket",
|
|
69
|
+
"SharedPolicyDecision",
|
|
70
|
+
"SharedPolicyEvaluator",
|
|
71
|
+
"SharedPolicyRule",
|
|
72
|
+
"SharedPolicySchema",
|
|
73
|
+
"document_to_governance",
|
|
74
|
+
"governance_to_document",
|
|
75
|
+
"policy_document_to_shared",
|
|
76
|
+
"shared_to_policy_document",
|
|
77
|
+
]
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Async-safe and thread-safe policy evaluator for Agent-OS governance.
|
|
5
|
+
|
|
6
|
+
Wraps the synchronous :class:`PolicyEvaluator` with proper concurrency
|
|
7
|
+
controls so that it can be used safely from ``asyncio`` coroutines **and**
|
|
8
|
+
from multiple OS threads simultaneously.
|
|
9
|
+
|
|
10
|
+
Concurrency guarantees
|
|
11
|
+
----------------------
|
|
12
|
+
* **asyncio.Lock** guards coroutine-level access so that only one
|
|
13
|
+
``await evaluate(...)`` executes the underlying evaluator at a time.
|
|
14
|
+
* **threading.RLock** guards thread-level access for the synchronous
|
|
15
|
+
:meth:`evaluate_sync` entry-point.
|
|
16
|
+
* A lightweight **read-write lock** pattern allows multiple concurrent
|
|
17
|
+
reads (evaluations) while giving exclusive access to writes
|
|
18
|
+
(policy reloads).
|
|
19
|
+
* All evaluation statistics are updated atomically via the thread lock.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import logging
|
|
26
|
+
import threading
|
|
27
|
+
import time
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from .evaluator import PolicyDecision, PolicyEvaluator
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _rounded_duration(value: float) -> float:
|
|
38
|
+
"""Round durations while preserving tiny positive measurements."""
|
|
39
|
+
if value <= 0.0:
|
|
40
|
+
return 0.0
|
|
41
|
+
return max(round(value, 6), 1e-6)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Concurrency statistics
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ConcurrencyStats:
|
|
50
|
+
"""Tracks concurrency-related metrics for the async evaluator.
|
|
51
|
+
|
|
52
|
+
All mutations happen under the owning evaluator's thread lock so
|
|
53
|
+
individual field updates are atomic with respect to other threads.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
evaluation_count: int = 0
|
|
57
|
+
total_evaluation_time: float = 0.0
|
|
58
|
+
error_count: int = 0
|
|
59
|
+
reload_count: int = 0
|
|
60
|
+
concurrent_peak: int = 0
|
|
61
|
+
_active_readers: int = field(default=0, repr=False)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def average_evaluation_time(self) -> float:
|
|
65
|
+
"""Return average evaluation latency in seconds, or 0.0."""
|
|
66
|
+
if self.evaluation_count == 0:
|
|
67
|
+
return 0.0
|
|
68
|
+
return self.total_evaluation_time / self.evaluation_count
|
|
69
|
+
|
|
70
|
+
def as_dict(self) -> dict[str, Any]:
|
|
71
|
+
"""Serialise stats to a plain dictionary."""
|
|
72
|
+
return {
|
|
73
|
+
"evaluation_count": self.evaluation_count,
|
|
74
|
+
"total_evaluation_time": _rounded_duration(self.total_evaluation_time),
|
|
75
|
+
"average_evaluation_time": _rounded_duration(self.average_evaluation_time),
|
|
76
|
+
"error_count": self.error_count,
|
|
77
|
+
"reload_count": self.reload_count,
|
|
78
|
+
"concurrent_peak": self.concurrent_peak,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Read-write lock helpers
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
class _ReadWriteLock:
|
|
87
|
+
"""Simple readers-writer lock built on :class:`threading.RLock`.
|
|
88
|
+
|
|
89
|
+
Multiple readers can hold the lock concurrently; a writer gets
|
|
90
|
+
exclusive access (no readers **and** no other writers).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self) -> None:
|
|
94
|
+
self._readers: int = 0
|
|
95
|
+
self._lock = threading.RLock() # protects ``_readers``
|
|
96
|
+
self._write_lock = threading.RLock() # exclusive writer access
|
|
97
|
+
|
|
98
|
+
def acquire_read(self) -> None:
|
|
99
|
+
with self._lock:
|
|
100
|
+
self._readers += 1
|
|
101
|
+
if self._readers == 1:
|
|
102
|
+
self._write_lock.acquire()
|
|
103
|
+
|
|
104
|
+
def release_read(self) -> None:
|
|
105
|
+
with self._lock:
|
|
106
|
+
self._readers -= 1
|
|
107
|
+
if self._readers == 0:
|
|
108
|
+
self._write_lock.release()
|
|
109
|
+
|
|
110
|
+
def acquire_write(self) -> None:
|
|
111
|
+
self._write_lock.acquire()
|
|
112
|
+
|
|
113
|
+
def release_write(self) -> None:
|
|
114
|
+
self._write_lock.release()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# Async-safe evaluator
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
class AsyncPolicyEvaluator:
|
|
122
|
+
"""Thread-safe and asyncio-safe policy evaluator.
|
|
123
|
+
|
|
124
|
+
Wraps :class:`PolicyEvaluator` with proper concurrency controls:
|
|
125
|
+
|
|
126
|
+
* :pyobj:`asyncio.Lock` for coroutine safety within a single event
|
|
127
|
+
loop.
|
|
128
|
+
* :class:`_ReadWriteLock` for thread safety — multiple concurrent
|
|
129
|
+
reads (evaluations) are allowed; writes (policy reloads) acquire
|
|
130
|
+
exclusive access.
|
|
131
|
+
* Evaluation statistics (:class:`ConcurrencyStats`) are maintained
|
|
132
|
+
atomically.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
evaluator:
|
|
137
|
+
The underlying synchronous evaluator to wrap.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, evaluator: PolicyEvaluator) -> None:
|
|
141
|
+
self._evaluator = evaluator
|
|
142
|
+
self._async_lock = asyncio.Lock()
|
|
143
|
+
self._rw_lock = _ReadWriteLock()
|
|
144
|
+
self._thread_lock = threading.RLock()
|
|
145
|
+
self._stats = ConcurrencyStats()
|
|
146
|
+
|
|
147
|
+
# -- properties --------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def evaluator(self) -> PolicyEvaluator:
|
|
151
|
+
"""Return the underlying synchronous evaluator."""
|
|
152
|
+
return self._evaluator
|
|
153
|
+
|
|
154
|
+
# -- async entry-points ------------------------------------------------
|
|
155
|
+
|
|
156
|
+
async def evaluate(self, context: dict[str, Any]) -> PolicyDecision:
|
|
157
|
+
"""Evaluate policies against *context* with async + thread safety.
|
|
158
|
+
|
|
159
|
+
Acquires the async lock (coroutine safety) and the read side of
|
|
160
|
+
the RW lock (thread safety) so that concurrent evaluations can
|
|
161
|
+
proceed but a policy reload will block until all in-flight
|
|
162
|
+
evaluations complete.
|
|
163
|
+
|
|
164
|
+
Thread-safety: YES — safe to call from multiple threads via
|
|
165
|
+
``asyncio.run_coroutine_threadsafe``.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
PolicyDecision
|
|
170
|
+
The result of evaluating the loaded policies.
|
|
171
|
+
"""
|
|
172
|
+
async with self._async_lock:
|
|
173
|
+
return await asyncio.get_running_loop().run_in_executor(
|
|
174
|
+
None, self._evaluate_with_read_lock, context
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def evaluate_sync(self, context: dict[str, Any]) -> PolicyDecision:
|
|
178
|
+
"""Thread-safe synchronous policy evaluation.
|
|
179
|
+
|
|
180
|
+
Uses the read side of the RW lock so multiple threads may
|
|
181
|
+
evaluate concurrently while policy reloads block.
|
|
182
|
+
|
|
183
|
+
Thread-safety: YES — safe to call from any OS thread.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
PolicyDecision
|
|
188
|
+
The result of evaluating the loaded policies.
|
|
189
|
+
"""
|
|
190
|
+
return self._evaluate_with_read_lock(context)
|
|
191
|
+
|
|
192
|
+
async def evaluate_batch(
|
|
193
|
+
self, contexts: list[dict[str, Any]]
|
|
194
|
+
) -> list[PolicyDecision]:
|
|
195
|
+
"""Evaluate multiple contexts concurrently.
|
|
196
|
+
|
|
197
|
+
Each context is evaluated in its own asyncio task. All tasks
|
|
198
|
+
share the same concurrency controls so a policy reload in
|
|
199
|
+
progress will block them.
|
|
200
|
+
|
|
201
|
+
Thread-safety: YES.
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
list[PolicyDecision]
|
|
206
|
+
One decision per input context, in the same order.
|
|
207
|
+
"""
|
|
208
|
+
tasks = [
|
|
209
|
+
asyncio.ensure_future(self.evaluate(ctx)) for ctx in contexts
|
|
210
|
+
]
|
|
211
|
+
return list(await asyncio.gather(*tasks))
|
|
212
|
+
|
|
213
|
+
async def reload_policies(self, directory: str | Path) -> None:
|
|
214
|
+
"""Reload policies from *directory* with an exclusive write lock.
|
|
215
|
+
|
|
216
|
+
Acquires the write side of the RW lock so that no evaluations
|
|
217
|
+
can proceed while the policy set is being replaced.
|
|
218
|
+
|
|
219
|
+
Thread-safety: YES.
|
|
220
|
+
"""
|
|
221
|
+
async with self._async_lock:
|
|
222
|
+
await asyncio.get_running_loop().run_in_executor(
|
|
223
|
+
None, self._reload_with_write_lock, directory
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def get_stats(self) -> dict[str, Any]:
|
|
227
|
+
"""Return a snapshot of concurrency statistics.
|
|
228
|
+
|
|
229
|
+
Thread-safety: YES — reads are guarded by the thread lock.
|
|
230
|
+
"""
|
|
231
|
+
with self._thread_lock:
|
|
232
|
+
return self._stats.as_dict()
|
|
233
|
+
|
|
234
|
+
# -- internal helpers --------------------------------------------------
|
|
235
|
+
|
|
236
|
+
def _evaluate_with_read_lock(
|
|
237
|
+
self, context: dict[str, Any]
|
|
238
|
+
) -> PolicyDecision:
|
|
239
|
+
"""Run the evaluator under the read side of the RW lock."""
|
|
240
|
+
self._rw_lock.acquire_read()
|
|
241
|
+
try:
|
|
242
|
+
with self._thread_lock:
|
|
243
|
+
self._stats._active_readers += 1
|
|
244
|
+
if self._stats._active_readers > self._stats.concurrent_peak:
|
|
245
|
+
self._stats.concurrent_peak = self._stats._active_readers
|
|
246
|
+
|
|
247
|
+
start = time.perf_counter()
|
|
248
|
+
try:
|
|
249
|
+
result = self._evaluator.evaluate(context)
|
|
250
|
+
except Exception:
|
|
251
|
+
with self._thread_lock:
|
|
252
|
+
self._stats.error_count += 1
|
|
253
|
+
raise
|
|
254
|
+
finally:
|
|
255
|
+
elapsed = max(time.perf_counter() - start, 1e-6)
|
|
256
|
+
with self._thread_lock:
|
|
257
|
+
self._stats.evaluation_count += 1
|
|
258
|
+
self._stats.total_evaluation_time += elapsed
|
|
259
|
+
self._stats._active_readers -= 1
|
|
260
|
+
|
|
261
|
+
return result
|
|
262
|
+
finally:
|
|
263
|
+
self._rw_lock.release_read()
|
|
264
|
+
|
|
265
|
+
def _reload_with_write_lock(self, directory: str | Path) -> None:
|
|
266
|
+
"""Perform the actual policy reload under the write lock."""
|
|
267
|
+
self._rw_lock.acquire_write()
|
|
268
|
+
try:
|
|
269
|
+
self._evaluator.policies.clear()
|
|
270
|
+
self._evaluator.load_policies(directory)
|
|
271
|
+
with self._thread_lock:
|
|
272
|
+
self._stats.reload_count += 1
|
|
273
|
+
logger.info("Policies reloaded from %s", directory)
|
|
274
|
+
finally:
|
|
275
|
+
self._rw_lock.release_write()
|