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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. 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()