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,425 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Drift Detector — detects configuration and behavioral drift across agents.
4
+
5
+ Compares agent configurations, policies, and trust states across
6
+ repositories or environments to identify inconsistencies.
7
+
8
+ Usage::
9
+
10
+ from agent_os.integrations.drift_detector import (
11
+ DriftDetector, DriftType,
12
+ )
13
+
14
+ detector = DriftDetector()
15
+ findings = detector.compare_configs(
16
+ source_config={"max_tokens": 4096, "timeout": 300},
17
+ target_config={"max_tokens": 2048, "timeout": 300},
18
+ label="staging-vs-prod",
19
+ )
20
+ for f in findings:
21
+ print(f"{f.drift_type.value}: {f.field} — {f.message}")
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from dataclasses import dataclass, field
28
+ from datetime import datetime, timezone
29
+ from enum import Enum
30
+ from typing import Any
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ # ── Enums & value objects ──────────────────────────────────────
36
+
37
+
38
+ class DriftType(Enum):
39
+ """Categories of drift that can be detected."""
40
+
41
+ CONFIG_DRIFT = "config_drift"
42
+ POLICY_DRIFT = "policy_drift"
43
+ TRUST_DRIFT = "trust_drift"
44
+ VERSION_DRIFT = "version_drift"
45
+ CAPABILITY_DRIFT = "capability_drift"
46
+
47
+
48
+ @dataclass
49
+ class DriftFinding:
50
+ """A single drift finding.
51
+
52
+ Attributes:
53
+ drift_type: Category of drift.
54
+ severity: ``"info"``, ``"warning"``, or ``"critical"``.
55
+ source: Label for the source side.
56
+ target: Label for the target side.
57
+ field: Configuration or policy field that drifted.
58
+ expected: Value on the source side.
59
+ actual: Value on the target side.
60
+ message: Human-readable description.
61
+ """
62
+
63
+ drift_type: DriftType
64
+ severity: str
65
+ source: str
66
+ target: str
67
+ field: str
68
+ expected: Any
69
+ actual: Any
70
+ message: str
71
+
72
+
73
+ @dataclass
74
+ class DriftReport:
75
+ """Aggregate drift report.
76
+
77
+ Attributes:
78
+ findings: All drift findings.
79
+ scanned_at: UTC timestamp of the scan.
80
+ sources_scanned: Number of sources compared.
81
+ summary: Brief textual summary.
82
+ """
83
+
84
+ findings: list[DriftFinding] = field(default_factory=list)
85
+ scanned_at: str = ""
86
+ sources_scanned: int = 0
87
+ summary: str = ""
88
+
89
+
90
+ # ── Drift Detector ─────────────────────────────────────────────
91
+
92
+
93
+ class DriftDetector:
94
+ """Detects configuration and behavioral drift across agents or environments.
95
+
96
+ All comparison methods return lists of :class:`DriftFinding`; the
97
+ high-level :meth:`scan` method accepts a list of source dicts and
98
+ produces a :class:`DriftReport`.
99
+ """
100
+
101
+ # ── public comparison methods ───────────────────────────
102
+
103
+ def compare_configs(
104
+ self,
105
+ source_config: dict[str, Any],
106
+ target_config: dict[str, Any],
107
+ label: str = "",
108
+ ) -> list[DriftFinding]:
109
+ """Compare two configuration dicts and return findings for each diff.
110
+
111
+ Args:
112
+ source_config: Reference configuration.
113
+ target_config: Configuration to compare against.
114
+ label: Optional human-readable label for this comparison.
115
+
116
+ Returns:
117
+ List of :class:`DriftFinding` for every key that differs.
118
+ """
119
+ source_label = f"{label}/source" if label else "source"
120
+ target_label = f"{label}/target" if label else "target"
121
+ findings: list[DriftFinding] = []
122
+
123
+ all_keys = set(source_config) | set(target_config)
124
+ for key in sorted(all_keys):
125
+ src_val = source_config.get(key)
126
+ tgt_val = target_config.get(key)
127
+ if src_val != tgt_val:
128
+ severity = self._config_severity(key, src_val, tgt_val)
129
+ findings.append(
130
+ DriftFinding(
131
+ drift_type=DriftType.CONFIG_DRIFT,
132
+ severity=severity,
133
+ source=source_label,
134
+ target=target_label,
135
+ field=key,
136
+ expected=src_val,
137
+ actual=tgt_val,
138
+ message=self._config_message(key, src_val, tgt_val),
139
+ )
140
+ )
141
+ return findings
142
+
143
+ def compare_policies(
144
+ self,
145
+ source_policies: dict[str, Any],
146
+ target_policies: dict[str, Any],
147
+ ) -> list[DriftFinding]:
148
+ """Compare two policy dicts and return drift findings.
149
+
150
+ Missing keys on either side are flagged as *critical* drift.
151
+
152
+ Args:
153
+ source_policies: Reference policy set.
154
+ target_policies: Policy set to compare.
155
+
156
+ Returns:
157
+ List of :class:`DriftFinding`.
158
+ """
159
+ findings: list[DriftFinding] = []
160
+ all_keys = set(source_policies) | set(target_policies)
161
+
162
+ for key in sorted(all_keys):
163
+ src = source_policies.get(key)
164
+ tgt = target_policies.get(key)
165
+ if src == tgt:
166
+ continue
167
+
168
+ if src is None:
169
+ severity = "critical"
170
+ message = f"Policy '{key}' exists only in target"
171
+ elif tgt is None:
172
+ severity = "critical"
173
+ message = f"Policy '{key}' missing in target"
174
+ else:
175
+ severity = "warning"
176
+ message = f"Policy '{key}' differs: {src!r} vs {tgt!r}"
177
+
178
+ findings.append(
179
+ DriftFinding(
180
+ drift_type=DriftType.POLICY_DRIFT,
181
+ severity=severity,
182
+ source="source_policies",
183
+ target="target_policies",
184
+ field=key,
185
+ expected=src,
186
+ actual=tgt,
187
+ message=message,
188
+ )
189
+ )
190
+ return findings
191
+
192
+ def compare_trust_scores(
193
+ self,
194
+ source_scores: dict[str, float],
195
+ target_scores: dict[str, float],
196
+ tolerance: float = 0.1,
197
+ ) -> list[DriftFinding]:
198
+ """Compare trust scores, flagging differences beyond *tolerance*.
199
+
200
+ Args:
201
+ source_scores: Reference trust scores keyed by agent id.
202
+ target_scores: Trust scores to compare.
203
+ tolerance: Maximum allowed absolute difference before flagging.
204
+
205
+ Returns:
206
+ List of :class:`DriftFinding` for scores that drifted.
207
+ """
208
+ findings: list[DriftFinding] = []
209
+ all_agents = set(source_scores) | set(target_scores)
210
+
211
+ for agent in sorted(all_agents):
212
+ src = source_scores.get(agent)
213
+ tgt = target_scores.get(agent)
214
+
215
+ if src is None or tgt is None:
216
+ findings.append(
217
+ DriftFinding(
218
+ drift_type=DriftType.TRUST_DRIFT,
219
+ severity="warning",
220
+ source="source_scores",
221
+ target="target_scores",
222
+ field=agent,
223
+ expected=src,
224
+ actual=tgt,
225
+ message=(
226
+ f"Trust score for '{agent}' missing on "
227
+ f"{'source' if src is None else 'target'} side"
228
+ ),
229
+ )
230
+ )
231
+ continue
232
+
233
+ diff = abs(src - tgt)
234
+ if diff > tolerance:
235
+ severity = "critical" if diff > tolerance * 3 else "warning"
236
+ findings.append(
237
+ DriftFinding(
238
+ drift_type=DriftType.TRUST_DRIFT,
239
+ severity=severity,
240
+ source="source_scores",
241
+ target="target_scores",
242
+ field=agent,
243
+ expected=src,
244
+ actual=tgt,
245
+ message=(
246
+ f"Trust score for '{agent}' drifted by "
247
+ f"{diff:.3f} (tolerance={tolerance})"
248
+ ),
249
+ )
250
+ )
251
+ return findings
252
+
253
+ def detect_version_drift(
254
+ self, components: dict[str, str]
255
+ ) -> list[DriftFinding]:
256
+ """Find out-of-sync versions among components.
257
+
258
+ Expects a dict mapping component names to version strings. When
259
+ two or more distinct versions exist among *all* components that
260
+ share a common prefix (before the first ``-`` or ``/``), drift is
261
+ flagged.
262
+
263
+ For simpler use, the method also detects mixed version patterns
264
+ among *all* supplied components.
265
+
266
+ Args:
267
+ components: Mapping of ``component_name → version_string``.
268
+
269
+ Returns:
270
+ List of :class:`DriftFinding` for version mismatches.
271
+ """
272
+ findings: list[DriftFinding] = []
273
+ if not components:
274
+ return findings
275
+
276
+ # Group by prefix (e.g. "agent-os/kernel" → "agent-os")
277
+ groups: dict[str, dict[str, str]] = {}
278
+ for name, version in components.items():
279
+ prefix = name.split("-")[0].split("/")[0]
280
+ groups.setdefault(prefix, {})[name] = version
281
+
282
+ for prefix, members in sorted(groups.items()):
283
+ versions = set(members.values())
284
+ if len(versions) <= 1:
285
+ continue
286
+
287
+ for name, version in sorted(members.items()):
288
+ majority = max(versions, key=lambda v: list(members.values()).count(v))
289
+ if version != majority:
290
+ findings.append(
291
+ DriftFinding(
292
+ drift_type=DriftType.VERSION_DRIFT,
293
+ severity="warning",
294
+ source=prefix,
295
+ target=name,
296
+ field="version",
297
+ expected=majority,
298
+ actual=version,
299
+ message=(
300
+ f"Component '{name}' at version {version} "
301
+ f"differs from majority {majority}"
302
+ ),
303
+ )
304
+ )
305
+ return findings
306
+
307
+ def scan(self, sources: list[dict[str, Any]]) -> DriftReport:
308
+ """Run a comprehensive scan over multiple source dicts.
309
+
310
+ Each source dict must contain a ``"label"`` key and at least one of:
311
+ - ``"config"`` — configuration dict
312
+ - ``"policies"`` — policy dict
313
+ - ``"trust_scores"`` — agent trust score dict
314
+ - ``"components"`` — version component dict
315
+
316
+ Pairwise comparisons are performed between successive sources.
317
+
318
+ Args:
319
+ sources: List of source dicts to compare.
320
+
321
+ Returns:
322
+ A :class:`DriftReport`.
323
+ """
324
+ all_findings: list[DriftFinding] = []
325
+
326
+ for i in range(len(sources) - 1):
327
+ src = sources[i]
328
+ tgt = sources[i + 1]
329
+ label = f"{src.get('label', f'src-{i}')}-vs-{tgt.get('label', f'src-{i+1}')}"
330
+
331
+ if "config" in src and "config" in tgt:
332
+ all_findings.extend(
333
+ self.compare_configs(src["config"], tgt["config"], label)
334
+ )
335
+
336
+ if "policies" in src and "policies" in tgt:
337
+ all_findings.extend(
338
+ self.compare_policies(src["policies"], tgt["policies"])
339
+ )
340
+
341
+ if "trust_scores" in src and "trust_scores" in tgt:
342
+ tolerance = src.get("trust_tolerance", tgt.get("trust_tolerance", 0.1))
343
+ all_findings.extend(
344
+ self.compare_trust_scores(
345
+ src["trust_scores"], tgt["trust_scores"], tolerance
346
+ )
347
+ )
348
+
349
+ # Version drift across all sources that have components
350
+ all_components: dict[str, str] = {}
351
+ for src in sources:
352
+ all_components.update(src.get("components", {}))
353
+ if all_components:
354
+ all_findings.extend(self.detect_version_drift(all_components))
355
+
356
+ critical = sum(1 for f in all_findings if f.severity == "critical")
357
+ warnings = sum(1 for f in all_findings if f.severity == "warning")
358
+ info = sum(1 for f in all_findings if f.severity == "info")
359
+
360
+ report = DriftReport(
361
+ findings=all_findings,
362
+ scanned_at=datetime.now(timezone.utc).isoformat(),
363
+ sources_scanned=len(sources),
364
+ summary=(
365
+ f"{len(all_findings)} finding(s): "
366
+ f"{critical} critical, {warnings} warning, {info} info"
367
+ ),
368
+ )
369
+ return report
370
+
371
+ def to_markdown(self, report: DriftReport) -> str:
372
+ """Render a :class:`DriftReport` as a Markdown string.
373
+
374
+ Args:
375
+ report: The report to render.
376
+
377
+ Returns:
378
+ Markdown-formatted string.
379
+ """
380
+ lines = [
381
+ "# Drift Detection Report",
382
+ "",
383
+ f"**Scanned at:** {report.scanned_at} ",
384
+ f"**Sources scanned:** {report.sources_scanned} ",
385
+ f"**Summary:** {report.summary}",
386
+ "",
387
+ ]
388
+
389
+ if not report.findings:
390
+ lines.append("✅ No drift detected.")
391
+ return "\n".join(lines)
392
+
393
+ lines.append("| Severity | Type | Field | Expected | Actual | Message |")
394
+ lines.append("|----------|------|-------|----------|--------|---------|")
395
+
396
+ for f in report.findings:
397
+ lines.append(
398
+ f"| {f.severity} | {f.drift_type.value} | {f.field} "
399
+ f"| {f.expected} | {f.actual} | {f.message} |"
400
+ )
401
+
402
+ return "\n".join(lines)
403
+
404
+ # ── internal helpers ────────────────────────────────────
405
+
406
+ @staticmethod
407
+ def _config_severity(
408
+ key: str, src_val: Any, tgt_val: Any
409
+ ) -> str:
410
+ """Determine severity of a config difference."""
411
+ if src_val is None or tgt_val is None:
412
+ return "critical"
413
+ if isinstance(src_val, (int, float)) and isinstance(tgt_val, (int, float)):
414
+ if src_val != 0 and abs(src_val - tgt_val) / abs(src_val) > 0.5:
415
+ return "warning"
416
+ return "info"
417
+
418
+ @staticmethod
419
+ def _config_message(key: str, src_val: Any, tgt_val: Any) -> str:
420
+ """Build human-readable config diff message."""
421
+ if src_val is None:
422
+ return f"Key '{key}' only in target (value={tgt_val!r})"
423
+ if tgt_val is None:
424
+ return f"Key '{key}' missing in target (source={src_val!r})"
425
+ return f"Key '{key}' differs: {src_val!r} → {tgt_val!r}"
@@ -0,0 +1,124 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Dry-run policy wrapper for governance policies.
5
+
6
+ Wraps any GovernancePolicy and runs enforcement in "dry run" mode,
7
+ recording what WOULD have happened without actually blocking execution.
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from typing import Any, Optional
14
+
15
+ from .base import BaseIntegration, ExecutionContext
16
+
17
+
18
+ class DryRunDecision(Enum):
19
+ """Decision that would have been made by the wrapped policy."""
20
+ ALLOW = "ALLOW"
21
+ DENY = "DENY"
22
+ WARN = "WARN"
23
+
24
+
25
+ @dataclass
26
+ class DryRunResult:
27
+ """Result of a dry-run policy evaluation."""
28
+ action: str
29
+ decision: DryRunDecision
30
+ reason: Optional[str]
31
+ policy_name: str
32
+ timestamp: datetime = field(default_factory=datetime.now)
33
+
34
+
35
+ class DryRunCollector:
36
+ """Accumulates dry-run results and provides summary reports."""
37
+
38
+ def __init__(self) -> None:
39
+ self._results: list[DryRunResult] = []
40
+
41
+ def add(self, result: DryRunResult) -> None:
42
+ self._results.append(result)
43
+
44
+ def get_results(self) -> list[DryRunResult]:
45
+ return list(self._results)
46
+
47
+ def summary(self) -> dict[str, Any]:
48
+ total = len(self._results)
49
+ counts = {d.value: 0 for d in DryRunDecision}
50
+ for r in self._results:
51
+ counts[r.decision.value] += 1
52
+ return {
53
+ "total": total,
54
+ "allowed": counts["ALLOW"],
55
+ "denied": counts["DENY"],
56
+ "warnings": counts["WARN"],
57
+ }
58
+
59
+ def clear(self) -> None:
60
+ self._results.clear()
61
+
62
+
63
+ class DryRunPolicy:
64
+ """
65
+ Wraps a BaseIntegration to run policy checks in dry-run mode.
66
+
67
+ All policy evaluations are recorded but never block execution.
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ integration: BaseIntegration,
73
+ *,
74
+ policy_name: str = "default",
75
+ collector: Optional[DryRunCollector] = None,
76
+ ) -> None:
77
+ self._integration = integration
78
+ self._policy_name = policy_name
79
+ self.collector = collector or DryRunCollector()
80
+
81
+ def evaluate(self, action: str, context: ExecutionContext, input_data: Any = None) -> DryRunResult:
82
+ """
83
+ Evaluate a policy check in dry-run mode.
84
+
85
+ Runs the wrapped integration's pre_execute and records the decision
86
+ without blocking. Always returns a DryRunResult.
87
+ """
88
+ allowed, reason = self._integration.pre_execute(context, input_data)
89
+
90
+ if allowed:
91
+ decision = DryRunDecision.ALLOW
92
+ else:
93
+ # Distinguish warnings from denials: threshold-based checks
94
+ # (confidence, drift) are warnings; hard blocks are denials.
95
+ decision = DryRunDecision.DENY
96
+
97
+ result = DryRunResult(
98
+ action=action,
99
+ decision=decision,
100
+ reason=reason,
101
+ policy_name=self._policy_name,
102
+ )
103
+ self.collector.add(result)
104
+ return result
105
+
106
+ def evaluate_warn(self, action: str, reason: str) -> DryRunResult:
107
+ """Record a warning without running policy checks."""
108
+ result = DryRunResult(
109
+ action=action,
110
+ decision=DryRunDecision.WARN,
111
+ reason=reason,
112
+ policy_name=self._policy_name,
113
+ )
114
+ self.collector.add(result)
115
+ return result
116
+
117
+ def get_results(self) -> list[DryRunResult]:
118
+ return self.collector.get_results()
119
+
120
+ def summary(self) -> dict[str, Any]:
121
+ return self.collector.summary()
122
+
123
+ def clear(self) -> None:
124
+ self.collector.clear()