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,270 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Policy Conflict Resolution.
5
+
6
+ When multiple policies apply to the same agent action, the conflict
7
+ resolution strategy determines which decision wins.
8
+
9
+ Strategies
10
+ ----------
11
+ - **DENY_OVERRIDES** (safest): If ANY matching rule denies, the action
12
+ is denied regardless of what other rules say. Standard in XACML and
13
+ most enterprise policy systems.
14
+ - **ALLOW_OVERRIDES**: If ANY matching rule allows, the action is
15
+ allowed. Useful for exception-based governance where you want
16
+ explicit allow-rules to punch through default-deny policies.
17
+ - **PRIORITY_FIRST_MATCH** (current default): Rules are sorted by
18
+ priority (highest first), and the first matching rule wins. This
19
+ preserves backward compatibility with the existing PolicyEngine.
20
+ - **MOST_SPECIFIC_WINS**: Agent-scoped rules override tenant-scoped,
21
+ which override global-scoped. Within the same scope, priority breaks
22
+ ties. Models the intuition that "closer policies override distant ones."
23
+
24
+ Scopes
25
+ ------
26
+ Each ``Policy`` can declare a ``scope`` that indicates its breadth:
27
+
28
+ - ``global``: Organization-wide default policies
29
+ - ``tenant``: Applied to a specific tenant or team
30
+ - ``agent``: Applied to a specific agent instance
31
+
32
+ When ``MOST_SPECIFIC_WINS`` is active, scope determines precedence.
33
+ When other strategies are active, scope is informational metadata.
34
+
35
+ Usage::
36
+
37
+ from agentmesh.governance.conflict_resolution import (
38
+ ConflictResolutionStrategy,
39
+ PolicyScope,
40
+ PolicyConflictResolver,
41
+ )
42
+
43
+ resolver = PolicyConflictResolver(ConflictResolutionStrategy.DENY_OVERRIDES)
44
+ final = resolver.resolve(candidate_decisions)
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import logging
50
+ from enum import Enum
51
+
52
+ from pydantic import BaseModel, Field
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+
57
+ class ConflictResolutionStrategy(str, Enum):
58
+ """Strategy for resolving conflicts between competing policy decisions."""
59
+
60
+ DENY_OVERRIDES = "deny_overrides"
61
+ ALLOW_OVERRIDES = "allow_overrides"
62
+ PRIORITY_FIRST_MATCH = "priority_first_match"
63
+ MOST_SPECIFIC_WINS = "most_specific_wins"
64
+
65
+
66
+ class PolicyScope(str, Enum):
67
+ """Breadth of a policy's applicability.
68
+
69
+ Specificity order (most → least): AGENT > ORGANIZATION > TENANT > GLOBAL.
70
+ """
71
+
72
+ GLOBAL = "global"
73
+ TENANT = "tenant"
74
+ ORGANIZATION = "organization"
75
+ AGENT = "agent"
76
+
77
+
78
+ # Specificity rank: higher = more specific
79
+ _SCOPE_SPECIFICITY: dict[PolicyScope, int] = {
80
+ PolicyScope.GLOBAL: 0,
81
+ PolicyScope.TENANT: 1,
82
+ PolicyScope.ORGANIZATION: 2,
83
+ PolicyScope.AGENT: 3,
84
+ }
85
+
86
+
87
+ class CandidateDecision(BaseModel):
88
+ """A single policy decision candidate awaiting conflict resolution.
89
+
90
+ Groups a decision with its originating policy metadata so the
91
+ resolver can apply scope- and priority-aware strategies.
92
+
93
+ Attributes:
94
+ action: The action the rule dictates (allow, deny, warn, etc.).
95
+ priority: Numeric priority from the matched rule.
96
+ scope: Scope of the policy that produced this decision.
97
+ policy_name: Name of the originating policy.
98
+ rule_name: Name of the matched rule.
99
+ reason: Human-readable explanation.
100
+ approvers: Required approvers for ``require_approval`` actions.
101
+ """
102
+
103
+ action: str
104
+ priority: int = 0
105
+ scope: PolicyScope = PolicyScope.GLOBAL
106
+ policy_name: str = ""
107
+ rule_name: str = ""
108
+ reason: str = ""
109
+ approvers: list[str] = Field(default_factory=list)
110
+
111
+ @property
112
+ def is_deny(self) -> bool:
113
+ return self.action == "deny"
114
+
115
+ @property
116
+ def is_allow(self) -> bool:
117
+ return self.action == "allow"
118
+
119
+ @property
120
+ def specificity(self) -> int:
121
+ return _SCOPE_SPECIFICITY.get(self.scope, 0)
122
+
123
+
124
+ class ResolutionResult(BaseModel):
125
+ """Outcome of conflict resolution.
126
+
127
+ Attributes:
128
+ winning_decision: The decision that prevailed.
129
+ strategy_used: Which strategy resolved the conflict.
130
+ candidates_evaluated: How many candidates were considered.
131
+ conflict_detected: Whether genuinely conflicting decisions existed.
132
+ resolution_trace: Human-readable trace of the resolution logic.
133
+ """
134
+
135
+ winning_decision: CandidateDecision
136
+ strategy_used: ConflictResolutionStrategy
137
+ candidates_evaluated: int = 0
138
+ conflict_detected: bool = False
139
+ resolution_trace: list[str] = Field(default_factory=list)
140
+
141
+
142
+ class PolicyConflictResolver:
143
+ """Resolves conflicts between competing policy decisions.
144
+
145
+ Args:
146
+ strategy: The conflict resolution strategy to apply.
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ strategy: ConflictResolutionStrategy = ConflictResolutionStrategy.PRIORITY_FIRST_MATCH,
152
+ ) -> None:
153
+ self.strategy = strategy
154
+
155
+ def resolve(self, candidates: list[CandidateDecision]) -> ResolutionResult:
156
+ """Resolve a list of candidate decisions into a single winner.
157
+
158
+ Args:
159
+ candidates: One or more candidate decisions from matching rules.
160
+
161
+ Returns:
162
+ A ``ResolutionResult`` containing the winning decision and
163
+ a trace of the resolution logic.
164
+
165
+ Raises:
166
+ ValueError: If ``candidates`` is empty.
167
+ """
168
+ if not candidates:
169
+ raise ValueError("Cannot resolve conflict with zero candidates")
170
+
171
+ if len(candidates) == 1:
172
+ return ResolutionResult(
173
+ winning_decision=candidates[0],
174
+ strategy_used=self.strategy,
175
+ candidates_evaluated=1,
176
+ conflict_detected=False,
177
+ resolution_trace=[f"Single candidate: {candidates[0].rule_name} → {candidates[0].action}"],
178
+ )
179
+
180
+ # Detect genuine conflict (mix of allow and deny)
181
+ actions = {c.action for c in candidates}
182
+ conflict_detected = "allow" in actions and "deny" in actions
183
+
184
+ dispatch = {
185
+ ConflictResolutionStrategy.DENY_OVERRIDES: self._deny_overrides,
186
+ ConflictResolutionStrategy.ALLOW_OVERRIDES: self._allow_overrides,
187
+ ConflictResolutionStrategy.PRIORITY_FIRST_MATCH: self._priority_first_match,
188
+ ConflictResolutionStrategy.MOST_SPECIFIC_WINS: self._most_specific_wins,
189
+ }
190
+
191
+ winner, trace = dispatch[self.strategy](candidates)
192
+
193
+ return ResolutionResult(
194
+ winning_decision=winner,
195
+ strategy_used=self.strategy,
196
+ candidates_evaluated=len(candidates),
197
+ conflict_detected=conflict_detected,
198
+ resolution_trace=trace,
199
+ )
200
+
201
+ # ── Strategy implementations ────────────────────────────
202
+
203
+ def _deny_overrides(
204
+ self, candidates: list[CandidateDecision]
205
+ ) -> tuple[CandidateDecision, list[str]]:
206
+ """DENY_OVERRIDES: any deny wins. Among denies, highest priority wins."""
207
+ trace = []
208
+ denies = [c for c in candidates if c.is_deny]
209
+ if denies:
210
+ denies.sort(key=lambda c: c.priority, reverse=True)
211
+ winner = denies[0]
212
+ trace.append(f"DENY_OVERRIDES: {len(denies)} deny rule(s) found")
213
+ trace.append(f"Winner: {winner.rule_name} (priority={winner.priority}, scope={winner.scope.value})")
214
+ return winner, trace
215
+
216
+ # No denies — pick highest-priority allow
217
+ candidates_sorted = sorted(candidates, key=lambda c: c.priority, reverse=True)
218
+ winner = candidates_sorted[0]
219
+ trace.append("DENY_OVERRIDES: no deny rules, selecting highest-priority allow")
220
+ trace.append(f"Winner: {winner.rule_name} (priority={winner.priority})")
221
+ return winner, trace
222
+
223
+ def _allow_overrides(
224
+ self, candidates: list[CandidateDecision]
225
+ ) -> tuple[CandidateDecision, list[str]]:
226
+ """ALLOW_OVERRIDES: any allow wins. Among allows, highest priority wins."""
227
+ trace = []
228
+ allows = [c for c in candidates if c.is_allow]
229
+ if allows:
230
+ allows.sort(key=lambda c: c.priority, reverse=True)
231
+ winner = allows[0]
232
+ trace.append(f"ALLOW_OVERRIDES: {len(allows)} allow rule(s) found")
233
+ trace.append(f"Winner: {winner.rule_name} (priority={winner.priority}, scope={winner.scope.value})")
234
+ return winner, trace
235
+
236
+ # No allows — pick highest-priority deny
237
+ candidates_sorted = sorted(candidates, key=lambda c: c.priority, reverse=True)
238
+ winner = candidates_sorted[0]
239
+ trace.append("ALLOW_OVERRIDES: no allow rules, selecting highest-priority deny")
240
+ trace.append(f"Winner: {winner.rule_name} (priority={winner.priority})")
241
+ return winner, trace
242
+
243
+ def _priority_first_match(
244
+ self, candidates: list[CandidateDecision]
245
+ ) -> tuple[CandidateDecision, list[str]]:
246
+ """PRIORITY_FIRST_MATCH: highest priority wins regardless of action."""
247
+ sorted_candidates = sorted(candidates, key=lambda c: c.priority, reverse=True)
248
+ winner = sorted_candidates[0]
249
+ trace = [
250
+ f"PRIORITY_FIRST_MATCH: {len(candidates)} candidates",
251
+ f"Winner: {winner.rule_name} (priority={winner.priority}, action={winner.action})",
252
+ ]
253
+ return winner, trace
254
+
255
+ def _most_specific_wins(
256
+ self, candidates: list[CandidateDecision]
257
+ ) -> tuple[CandidateDecision, list[str]]:
258
+ """MOST_SPECIFIC_WINS: agent > tenant > global. Priority breaks ties."""
259
+ sorted_candidates = sorted(
260
+ candidates,
261
+ key=lambda c: (c.specificity, c.priority),
262
+ reverse=True,
263
+ )
264
+ winner = sorted_candidates[0]
265
+ trace = [
266
+ f"MOST_SPECIFIC_WINS: {len(candidates)} candidates",
267
+ f"Specificity ranking: {[(c.rule_name, c.scope.value, c.specificity) for c in sorted_candidates]}",
268
+ f"Winner: {winner.rule_name} (scope={winner.scope.value}, priority={winner.priority}, action={winner.action})",
269
+ ]
270
+ return winner, trace
@@ -0,0 +1,252 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Data-layer ABAC policies for AI agent governance.
4
+
5
+ Extends the policy engine beyond tool-level to data-level enforcement.
6
+ Agents are checked not just for which tools they can use, but which
7
+ data classifications they can access.
8
+
9
+ Addresses the market gap where 63% of organizations cannot stop
10
+ their AI from accessing unauthorized data (Kiteworks 2026 research).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from datetime import datetime, timezone
17
+ from enum import IntEnum
18
+ from typing import Optional
19
+
20
+ from pydantic import BaseModel, Field
21
+
22
+
23
+ class DataClassification(IntEnum):
24
+ """Sensitivity levels for data classification, ordered by sensitivity."""
25
+
26
+ PUBLIC = 0
27
+ INTERNAL = 1
28
+ CONFIDENTIAL = 2
29
+ RESTRICTED = 3
30
+ TOP_SECRET = 4
31
+
32
+
33
+ class DataLabel(BaseModel):
34
+ """Label describing data sensitivity and handling requirements."""
35
+
36
+ classification: DataClassification
37
+ categories: list[str] = Field(
38
+ default_factory=list,
39
+ description="Data categories such as PII, PHI, PCI, GDPR, ITAR",
40
+ )
41
+ owner: str = ""
42
+ retention_days: int = 90
43
+ geography: str = ""
44
+
45
+
46
+ class ABACPolicy(BaseModel):
47
+ """Attribute-Based Access Control policy for an agent."""
48
+
49
+ agent_id: str
50
+ allowed_classifications: list[DataClassification] = Field(default_factory=list)
51
+ allowed_categories: list[str] = Field(default_factory=list)
52
+ denied_categories: list[str] = Field(default_factory=list)
53
+ required_geography: Optional[str] = None
54
+ max_classification: DataClassification = DataClassification.PUBLIC
55
+
56
+
57
+ class DataAccessDecision(BaseModel):
58
+ """Result of evaluating an agent's data access request."""
59
+
60
+ allowed: bool
61
+ reason: str
62
+ agent_id: str
63
+ data_label: DataLabel
64
+ matched_policy: Optional[str] = None
65
+ evaluated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
66
+
67
+
68
+ class DataAccessEvaluator:
69
+ """Evaluates agent data-access requests against ABAC policies.
70
+
71
+ When multiple policies exist for the same agent, the most restrictive
72
+ result wins (deny takes precedence over allow).
73
+ """
74
+
75
+ def __init__(self, policies: list[ABACPolicy]) -> None:
76
+ self._policies = policies
77
+
78
+ def evaluate(self, agent_id: str, data_label: DataLabel) -> DataAccessDecision:
79
+ """Check whether *agent_id* may access data described by *data_label*."""
80
+ agent_policies = [p for p in self._policies if p.agent_id == agent_id]
81
+ if not agent_policies:
82
+ return DataAccessDecision(
83
+ allowed=False,
84
+ reason="No ABAC policy registered for agent",
85
+ agent_id=agent_id,
86
+ data_label=data_label,
87
+ )
88
+
89
+ # Evaluate each policy; any denial means overall denial (most restrictive wins)
90
+ for policy in agent_policies:
91
+ decision = self._evaluate_single(agent_id, data_label, policy)
92
+ if not decision.allowed:
93
+ return decision
94
+
95
+ # All policies passed
96
+ return DataAccessDecision(
97
+ allowed=True,
98
+ reason="Access permitted by all applicable policies",
99
+ agent_id=agent_id,
100
+ data_label=data_label,
101
+ matched_policy=agent_policies[0].agent_id,
102
+ )
103
+
104
+ @staticmethod
105
+ def _evaluate_single(
106
+ agent_id: str, data_label: DataLabel, policy: ABACPolicy
107
+ ) -> DataAccessDecision:
108
+ policy_ref = policy.agent_id
109
+
110
+ if data_label.classification > policy.max_classification:
111
+ return DataAccessDecision(
112
+ allowed=False,
113
+ reason=(
114
+ f"Classification {data_label.classification.name} exceeds "
115
+ f"max {policy.max_classification.name}"
116
+ ),
117
+ agent_id=agent_id,
118
+ data_label=data_label,
119
+ matched_policy=policy_ref,
120
+ )
121
+
122
+ if (
123
+ policy.allowed_classifications
124
+ and data_label.classification not in policy.allowed_classifications
125
+ ):
126
+ return DataAccessDecision(
127
+ allowed=False,
128
+ reason=(
129
+ f"Classification {data_label.classification.name} not in "
130
+ f"allowed list"
131
+ ),
132
+ agent_id=agent_id,
133
+ data_label=data_label,
134
+ matched_policy=policy_ref,
135
+ )
136
+
137
+ for cat in data_label.categories:
138
+ if cat in policy.denied_categories:
139
+ return DataAccessDecision(
140
+ allowed=False,
141
+ reason=f"Category '{cat}' is explicitly denied",
142
+ agent_id=agent_id,
143
+ data_label=data_label,
144
+ matched_policy=policy_ref,
145
+ )
146
+
147
+ if policy.allowed_categories:
148
+ for cat in data_label.categories:
149
+ if cat not in policy.allowed_categories:
150
+ return DataAccessDecision(
151
+ allowed=False,
152
+ reason=f"Category '{cat}' not in allowed categories",
153
+ agent_id=agent_id,
154
+ data_label=data_label,
155
+ matched_policy=policy_ref,
156
+ )
157
+
158
+ if (
159
+ policy.required_geography
160
+ and data_label.geography
161
+ and data_label.geography != policy.required_geography
162
+ ):
163
+ return DataAccessDecision(
164
+ allowed=False,
165
+ reason=(
166
+ f"Geography '{data_label.geography}' does not match "
167
+ f"required '{policy.required_geography}'"
168
+ ),
169
+ agent_id=agent_id,
170
+ data_label=data_label,
171
+ matched_policy=policy_ref,
172
+ )
173
+
174
+ return DataAccessDecision(
175
+ allowed=True,
176
+ reason="Policy passed",
177
+ agent_id=agent_id,
178
+ data_label=data_label,
179
+ matched_policy=policy_ref,
180
+ )
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # PII / PHI / PCI detection helpers
185
+ # ---------------------------------------------------------------------------
186
+
187
+ _SSN_RE = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")
188
+ _EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
189
+ _PHONE_RE = re.compile(
190
+ r"\b(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b"
191
+ )
192
+
193
+ _MRN_RE = re.compile(r"\bMRN[-:\s]*\d{6,10}\b", re.IGNORECASE)
194
+ _ICD_RE = re.compile(r"\b[A-Z]\d{2}(?:\.\d{1,4})?\b")
195
+
196
+ _CC_RE = re.compile(r"\b(?:\d[ -]*?){13,19}\b")
197
+
198
+
199
+ def detect_pii(text: str) -> list[str]:
200
+ """Detect PII patterns (SSN, email, phone) in *text*."""
201
+ findings: list[str] = []
202
+ if _SSN_RE.search(text):
203
+ findings.append("SSN")
204
+ if _EMAIL_RE.search(text):
205
+ findings.append("email")
206
+ if _PHONE_RE.search(text):
207
+ findings.append("phone")
208
+ return findings
209
+
210
+
211
+ def detect_phi(text: str) -> list[str]:
212
+ """Detect PHI patterns (medical record numbers, diagnosis codes)."""
213
+ findings: list[str] = []
214
+ if _MRN_RE.search(text):
215
+ findings.append("MRN")
216
+ if _ICD_RE.search(text):
217
+ findings.append("ICD-code")
218
+ return findings
219
+
220
+
221
+ def detect_pci(text: str) -> list[str]:
222
+ """Detect PCI patterns (credit card numbers)."""
223
+ findings: list[str] = []
224
+ if _CC_RE.search(text):
225
+ findings.append("credit-card")
226
+ return findings
227
+
228
+
229
+ def classify_text(text: str) -> DataLabel:
230
+ """Auto-classify *text* based on detected sensitive-data patterns."""
231
+ categories: list[str] = []
232
+ classification = DataClassification.PUBLIC
233
+
234
+ pii = detect_pii(text)
235
+ if pii:
236
+ categories.append("PII")
237
+ classification = max(classification, DataClassification.CONFIDENTIAL)
238
+
239
+ phi = detect_phi(text)
240
+ if phi:
241
+ categories.append("PHI")
242
+ classification = max(classification, DataClassification.RESTRICTED)
243
+
244
+ pci = detect_pci(text)
245
+ if pci:
246
+ categories.append("PCI")
247
+ classification = max(classification, DataClassification.CONFIDENTIAL)
248
+
249
+ if not categories:
250
+ classification = DataClassification.PUBLIC
251
+
252
+ return DataLabel(classification=classification, categories=categories)