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,478 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Constraint Graphs - Multi-Dimensional Context
|
|
6
|
+
|
|
7
|
+
Context in an enterprise isn't flat; it's a graph. The Constraint Graph system
|
|
8
|
+
provides multi-dimensional constraints that act as the "physics" of the agent's world.
|
|
9
|
+
|
|
10
|
+
Three types of graphs:
|
|
11
|
+
1. Data Graph: Tables, schemas, and data the agent can access
|
|
12
|
+
2. Policy Graph: Corporate rules (e.g., "No PII in output")
|
|
13
|
+
3. Temporal Graph: What is true RIGHT NOW (e.g., "Maintenance Window is Active")
|
|
14
|
+
|
|
15
|
+
If an agent tries to access something that exists in the Data Graph but is
|
|
16
|
+
blocked in the Policy Graph, the Control Plane intercepts it. The request
|
|
17
|
+
never even reaches the database.
|
|
18
|
+
|
|
19
|
+
Research Foundations:
|
|
20
|
+
- Context-aware access control from ABAC research (NIST SP 800-162)
|
|
21
|
+
- Multi-dimensional policy evaluation
|
|
22
|
+
- Graph-based constraint modeling for complex policy interactions
|
|
23
|
+
- Temporal logic for time-based constraints
|
|
24
|
+
- Privacy controls informed by "Privacy in Agentic Systems" (arXiv:2409.1087, 2024)
|
|
25
|
+
|
|
26
|
+
See docs/RESEARCH_FOUNDATION.md for complete references.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from typing import Any, Dict, List, Optional, Set, Callable, Tuple
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from datetime import datetime, time
|
|
32
|
+
from enum import Enum
|
|
33
|
+
from .agent_kernel import ExecutionRequest, ActionType
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GraphNodeType(Enum):
|
|
37
|
+
"""Types of nodes in constraint graphs"""
|
|
38
|
+
DATA_RESOURCE = "data_resource"
|
|
39
|
+
POLICY_RULE = "policy_rule"
|
|
40
|
+
TEMPORAL_CONSTRAINT = "temporal_constraint"
|
|
41
|
+
AGENT_ROLE = "agent_role"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class GraphNode:
|
|
46
|
+
"""A node in a constraint graph"""
|
|
47
|
+
node_id: str
|
|
48
|
+
node_type: GraphNodeType
|
|
49
|
+
name: str
|
|
50
|
+
properties: Dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class GraphEdge:
|
|
56
|
+
"""An edge connecting nodes in a constraint graph"""
|
|
57
|
+
from_node: str
|
|
58
|
+
to_node: str
|
|
59
|
+
edge_type: str # e.g., "blocks", "allows", "requires", "inherits"
|
|
60
|
+
properties: Dict[str, Any] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConstraintGraph:
|
|
64
|
+
"""Base class for constraint graphs"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, name: str):
|
|
67
|
+
self.name = name
|
|
68
|
+
self.nodes: Dict[str, GraphNode] = {}
|
|
69
|
+
self.edges: List[GraphEdge] = []
|
|
70
|
+
|
|
71
|
+
def add_node(self, node: GraphNode):
|
|
72
|
+
"""Add a node to the graph"""
|
|
73
|
+
self.nodes[node.node_id] = node
|
|
74
|
+
|
|
75
|
+
def add_edge(self, edge: GraphEdge):
|
|
76
|
+
"""Add an edge to the graph"""
|
|
77
|
+
self.edges.append(edge)
|
|
78
|
+
|
|
79
|
+
def get_node(self, node_id: str) -> Optional[GraphNode]:
|
|
80
|
+
"""Get a node by ID"""
|
|
81
|
+
return self.nodes.get(node_id)
|
|
82
|
+
|
|
83
|
+
def get_edges_from(self, node_id: str) -> List[GraphEdge]:
|
|
84
|
+
"""Get all edges originating from a node"""
|
|
85
|
+
return [e for e in self.edges if e.from_node == node_id]
|
|
86
|
+
|
|
87
|
+
def get_edges_to(self, node_id: str) -> List[GraphEdge]:
|
|
88
|
+
"""Get all edges pointing to a node"""
|
|
89
|
+
return [e for e in self.edges if e.to_node == node_id]
|
|
90
|
+
|
|
91
|
+
def is_allowed(self, from_node: str, to_node: str) -> bool:
|
|
92
|
+
"""Check if there's an 'allows' edge between nodes"""
|
|
93
|
+
return any(
|
|
94
|
+
e.from_node == from_node and e.to_node == to_node and e.edge_type == "allows"
|
|
95
|
+
for e in self.edges
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def is_blocked(self, from_node: str, to_node: str) -> bool:
|
|
99
|
+
"""Check if there's a 'blocks' edge between nodes"""
|
|
100
|
+
return any(
|
|
101
|
+
e.from_node == from_node and e.to_node == to_node and e.edge_type == "blocks"
|
|
102
|
+
for e in self.edges
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DataGraph(ConstraintGraph):
|
|
107
|
+
"""
|
|
108
|
+
Data Graph - Defines what data resources exist and can be accessed.
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
- Database tables and schemas
|
|
112
|
+
- File systems and directories
|
|
113
|
+
- API endpoints
|
|
114
|
+
- Data lakes and warehouses
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self):
|
|
118
|
+
super().__init__("DataGraph")
|
|
119
|
+
|
|
120
|
+
def add_database_table(self, table_name: str, schema: Dict[str, Any], metadata: Optional[Dict] = None):
|
|
121
|
+
"""Add a database table to the graph"""
|
|
122
|
+
node = GraphNode(
|
|
123
|
+
node_id=f"table:{table_name}",
|
|
124
|
+
node_type=GraphNodeType.DATA_RESOURCE,
|
|
125
|
+
name=table_name,
|
|
126
|
+
properties={"schema": schema, "resource_type": "database_table"},
|
|
127
|
+
metadata=metadata or {}
|
|
128
|
+
)
|
|
129
|
+
self.add_node(node)
|
|
130
|
+
|
|
131
|
+
def add_file_path(self, path: str, access_level: str = "read", metadata: Optional[Dict] = None):
|
|
132
|
+
"""Add a file path to the graph"""
|
|
133
|
+
node = GraphNode(
|
|
134
|
+
node_id=f"file:{path}",
|
|
135
|
+
node_type=GraphNodeType.DATA_RESOURCE,
|
|
136
|
+
name=path,
|
|
137
|
+
properties={"resource_type": "file", "access_level": access_level},
|
|
138
|
+
metadata=metadata or {}
|
|
139
|
+
)
|
|
140
|
+
self.add_node(node)
|
|
141
|
+
|
|
142
|
+
def add_api_endpoint(self, endpoint: str, methods: List[str], metadata: Optional[Dict] = None):
|
|
143
|
+
"""Add an API endpoint to the graph"""
|
|
144
|
+
node = GraphNode(
|
|
145
|
+
node_id=f"api:{endpoint}",
|
|
146
|
+
node_type=GraphNodeType.DATA_RESOURCE,
|
|
147
|
+
name=endpoint,
|
|
148
|
+
properties={"resource_type": "api", "methods": methods},
|
|
149
|
+
metadata=metadata or {}
|
|
150
|
+
)
|
|
151
|
+
self.add_node(node)
|
|
152
|
+
|
|
153
|
+
def get_accessible_tables(self) -> List[str]:
|
|
154
|
+
"""Get all accessible database tables"""
|
|
155
|
+
return [
|
|
156
|
+
node.name for node in self.nodes.values()
|
|
157
|
+
if node.properties.get("resource_type") == "database_table"
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
def get_accessible_paths(self) -> List[str]:
|
|
161
|
+
"""Get all accessible file paths"""
|
|
162
|
+
return [
|
|
163
|
+
node.name for node in self.nodes.values()
|
|
164
|
+
if node.properties.get("resource_type") == "file"
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class PolicyGraph(ConstraintGraph):
|
|
169
|
+
"""
|
|
170
|
+
Policy Graph - Defines corporate rules and compliance constraints.
|
|
171
|
+
|
|
172
|
+
Examples:
|
|
173
|
+
- "No PII in output"
|
|
174
|
+
- "Finance data requires approval"
|
|
175
|
+
- "Healthcare data is HIPAA protected"
|
|
176
|
+
- "Cannot access production during deployment"
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self):
|
|
180
|
+
super().__init__("PolicyGraph")
|
|
181
|
+
|
|
182
|
+
def add_policy_rule(
|
|
183
|
+
self,
|
|
184
|
+
rule_id: str,
|
|
185
|
+
name: str,
|
|
186
|
+
applies_to: List[str], # Node IDs this rule applies to
|
|
187
|
+
rule_type: str, # "allow", "deny", "require_approval"
|
|
188
|
+
validator: Optional[Callable] = None
|
|
189
|
+
):
|
|
190
|
+
"""Add a policy rule to the graph"""
|
|
191
|
+
node = GraphNode(
|
|
192
|
+
node_id=f"policy:{rule_id}",
|
|
193
|
+
node_type=GraphNodeType.POLICY_RULE,
|
|
194
|
+
name=name,
|
|
195
|
+
properties={
|
|
196
|
+
"rule_type": rule_type,
|
|
197
|
+
"applies_to": applies_to,
|
|
198
|
+
"validator": validator
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
self.add_node(node)
|
|
202
|
+
|
|
203
|
+
# Create edges to resources this policy applies to
|
|
204
|
+
for resource_id in applies_to:
|
|
205
|
+
edge = GraphEdge(
|
|
206
|
+
from_node=node.node_id,
|
|
207
|
+
to_node=resource_id,
|
|
208
|
+
edge_type=rule_type
|
|
209
|
+
)
|
|
210
|
+
self.add_edge(edge)
|
|
211
|
+
|
|
212
|
+
def add_pii_protection(self, resource_ids: List[str]):
|
|
213
|
+
"""Add PII protection policy to resources"""
|
|
214
|
+
self.add_policy_rule(
|
|
215
|
+
rule_id="pii_protection",
|
|
216
|
+
name="No PII in output",
|
|
217
|
+
applies_to=resource_ids,
|
|
218
|
+
rule_type="deny",
|
|
219
|
+
validator=lambda req: not self._contains_pii(req.parameters)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def add_approval_requirement(self, resource_ids: List[str], approver_role: str):
|
|
223
|
+
"""Add approval requirement for resources"""
|
|
224
|
+
self.add_policy_rule(
|
|
225
|
+
rule_id=f"require_approval_{approver_role}",
|
|
226
|
+
name=f"Requires approval from {approver_role}",
|
|
227
|
+
applies_to=resource_ids,
|
|
228
|
+
rule_type="require_approval",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def _contains_pii(parameters: Dict[str, Any]) -> bool:
|
|
233
|
+
"""Check if parameters might contain PII"""
|
|
234
|
+
pii_keywords = ['ssn', 'social_security', 'email', 'phone', 'address', 'credit_card']
|
|
235
|
+
params_str = str(parameters).lower()
|
|
236
|
+
return any(keyword in params_str for keyword in pii_keywords)
|
|
237
|
+
|
|
238
|
+
def check_policy_violations(self, agent_role: str, resource_id: str) -> List[str]:
|
|
239
|
+
"""Check if accessing a resource would violate policies"""
|
|
240
|
+
violations = []
|
|
241
|
+
|
|
242
|
+
# Find policies that apply to this resource
|
|
243
|
+
for edge in self.get_edges_to(resource_id):
|
|
244
|
+
if edge.edge_type == "deny":
|
|
245
|
+
policy_node = self.get_node(edge.from_node)
|
|
246
|
+
violations.append(f"Policy '{policy_node.name}' blocks access to resource '{resource_id}' for role '{agent_role}'")
|
|
247
|
+
|
|
248
|
+
return violations
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class TemporalGraph(ConstraintGraph):
|
|
252
|
+
"""
|
|
253
|
+
Temporal Graph - Defines what is true RIGHT NOW.
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
- "Maintenance Window is Active (no writes)"
|
|
257
|
+
- "Business hours (9-5 EST)"
|
|
258
|
+
- "End of quarter freeze period"
|
|
259
|
+
- "Peak traffic hours (throttle)"
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(self):
|
|
263
|
+
super().__init__("TemporalGraph")
|
|
264
|
+
self.time_constraints: Dict[str, Callable[[], bool]] = {}
|
|
265
|
+
|
|
266
|
+
def add_maintenance_window(
|
|
267
|
+
self,
|
|
268
|
+
window_id: str,
|
|
269
|
+
start_time: time,
|
|
270
|
+
end_time: time,
|
|
271
|
+
blocked_actions: List[ActionType]
|
|
272
|
+
):
|
|
273
|
+
"""Add a maintenance window constraint"""
|
|
274
|
+
node = GraphNode(
|
|
275
|
+
node_id=f"temporal:{window_id}",
|
|
276
|
+
node_type=GraphNodeType.TEMPORAL_CONSTRAINT,
|
|
277
|
+
name=f"Maintenance Window: {start_time}-{end_time}",
|
|
278
|
+
properties={
|
|
279
|
+
"start_time": start_time,
|
|
280
|
+
"end_time": end_time,
|
|
281
|
+
"blocked_actions": blocked_actions
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
self.add_node(node)
|
|
285
|
+
|
|
286
|
+
# Add constraint checker
|
|
287
|
+
self.time_constraints[window_id] = lambda: self._is_in_time_range(start_time, end_time)
|
|
288
|
+
|
|
289
|
+
def add_business_hours(
|
|
290
|
+
self,
|
|
291
|
+
hours_id: str,
|
|
292
|
+
start_time: time,
|
|
293
|
+
end_time: time,
|
|
294
|
+
required_for: List[ActionType]
|
|
295
|
+
):
|
|
296
|
+
"""Add business hours constraint"""
|
|
297
|
+
node = GraphNode(
|
|
298
|
+
node_id=f"temporal:{hours_id}",
|
|
299
|
+
node_type=GraphNodeType.TEMPORAL_CONSTRAINT,
|
|
300
|
+
name=f"Business Hours: {start_time}-{end_time}",
|
|
301
|
+
properties={
|
|
302
|
+
"start_time": start_time,
|
|
303
|
+
"end_time": end_time,
|
|
304
|
+
"required_for": required_for
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
self.add_node(node)
|
|
308
|
+
|
|
309
|
+
self.time_constraints[hours_id] = lambda: self._is_in_time_range(start_time, end_time)
|
|
310
|
+
|
|
311
|
+
def add_freeze_period(
|
|
312
|
+
self,
|
|
313
|
+
freeze_id: str,
|
|
314
|
+
start_date: datetime,
|
|
315
|
+
end_date: datetime,
|
|
316
|
+
reason: str
|
|
317
|
+
):
|
|
318
|
+
"""Add a freeze period (e.g., end of quarter, holidays)"""
|
|
319
|
+
node = GraphNode(
|
|
320
|
+
node_id=f"temporal:{freeze_id}",
|
|
321
|
+
node_type=GraphNodeType.TEMPORAL_CONSTRAINT,
|
|
322
|
+
name=f"Freeze Period: {reason}",
|
|
323
|
+
properties={
|
|
324
|
+
"start_date": start_date,
|
|
325
|
+
"end_date": end_date,
|
|
326
|
+
"reason": reason
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
self.add_node(node)
|
|
330
|
+
|
|
331
|
+
self.time_constraints[freeze_id] = lambda: self._is_in_date_range(start_date, end_date)
|
|
332
|
+
|
|
333
|
+
@staticmethod
|
|
334
|
+
def _is_in_time_range(start_time: time, end_time: time) -> bool:
|
|
335
|
+
"""Check if current time is within range"""
|
|
336
|
+
now = datetime.now().time()
|
|
337
|
+
if start_time <= end_time:
|
|
338
|
+
# Normal range (e.g., 9:00 to 17:00)
|
|
339
|
+
return start_time <= now <= end_time
|
|
340
|
+
else:
|
|
341
|
+
# Crosses midnight (e.g., 23:00 to 01:00)
|
|
342
|
+
return now >= start_time or now <= end_time
|
|
343
|
+
|
|
344
|
+
@staticmethod
|
|
345
|
+
def _is_in_date_range(start_date: datetime, end_date: datetime) -> bool:
|
|
346
|
+
"""Check if current date is within range"""
|
|
347
|
+
now = datetime.now()
|
|
348
|
+
return start_date <= now <= end_date
|
|
349
|
+
|
|
350
|
+
def is_action_allowed_now(self, action_type: ActionType) -> Tuple[bool, Optional[str]]:
|
|
351
|
+
"""Check if an action is allowed at the current time"""
|
|
352
|
+
for node in self.nodes.values():
|
|
353
|
+
if node.node_type == GraphNodeType.TEMPORAL_CONSTRAINT:
|
|
354
|
+
# Check maintenance windows
|
|
355
|
+
blocked_actions = node.properties.get("blocked_actions", [])
|
|
356
|
+
if action_type in blocked_actions:
|
|
357
|
+
constraint_id = node.node_id.split(":")[-1]
|
|
358
|
+
if self.time_constraints.get(constraint_id, lambda: False)():
|
|
359
|
+
return False, f"Action blocked by: {node.name}"
|
|
360
|
+
|
|
361
|
+
# Check business hours requirements
|
|
362
|
+
required_for = node.properties.get("required_for", [])
|
|
363
|
+
if action_type in required_for:
|
|
364
|
+
constraint_id = node.node_id.split(":")[-1]
|
|
365
|
+
if not self.time_constraints.get(constraint_id, lambda: True)():
|
|
366
|
+
return False, f"Action requires: {node.name}"
|
|
367
|
+
|
|
368
|
+
return True, None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class ConstraintGraphValidator:
|
|
372
|
+
"""
|
|
373
|
+
Validates requests against multi-dimensional constraint graphs.
|
|
374
|
+
|
|
375
|
+
This is deterministic enforcement. The LLM can "think" whatever it wants,
|
|
376
|
+
but it can only ACT on what the graphs permit.
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
def __init__(
|
|
380
|
+
self,
|
|
381
|
+
data_graph: DataGraph,
|
|
382
|
+
policy_graph: PolicyGraph,
|
|
383
|
+
temporal_graph: TemporalGraph
|
|
384
|
+
):
|
|
385
|
+
self.data_graph = data_graph
|
|
386
|
+
self.policy_graph = policy_graph
|
|
387
|
+
self.temporal_graph = temporal_graph
|
|
388
|
+
self.validation_log: List[Dict[str, Any]] = []
|
|
389
|
+
|
|
390
|
+
def validate_request(self, request: ExecutionRequest) -> Tuple[bool, List[str]]:
|
|
391
|
+
"""
|
|
392
|
+
Validate request against all constraint graphs.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
(is_valid, reasons_if_invalid)
|
|
396
|
+
"""
|
|
397
|
+
violations = []
|
|
398
|
+
|
|
399
|
+
# 1. Check Data Graph - does the resource exist and is it accessible?
|
|
400
|
+
data_valid, data_reason = self._validate_data_graph(request)
|
|
401
|
+
if not data_valid:
|
|
402
|
+
violations.append(f"Data Graph: {data_reason}")
|
|
403
|
+
|
|
404
|
+
# 2. Check Policy Graph - does this violate any policies?
|
|
405
|
+
policy_valid, policy_reasons = self._validate_policy_graph(request)
|
|
406
|
+
if not policy_valid:
|
|
407
|
+
violations.extend([f"Policy Graph: {r}" for r in policy_reasons])
|
|
408
|
+
|
|
409
|
+
# 3. Check Temporal Graph - is this allowed right now?
|
|
410
|
+
temporal_valid, temporal_reason = self._validate_temporal_graph(request)
|
|
411
|
+
if not temporal_valid:
|
|
412
|
+
violations.append(f"Temporal Graph: {temporal_reason}")
|
|
413
|
+
|
|
414
|
+
# Log validation
|
|
415
|
+
self.validation_log.append({
|
|
416
|
+
"request_id": request.request_id,
|
|
417
|
+
"timestamp": datetime.now().isoformat(),
|
|
418
|
+
"valid": len(violations) == 0,
|
|
419
|
+
"violations": violations
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
return len(violations) == 0, violations
|
|
423
|
+
|
|
424
|
+
def _validate_data_graph(self, request: ExecutionRequest) -> Tuple[bool, Optional[str]]:
|
|
425
|
+
"""Validate against data graph"""
|
|
426
|
+
# Check if accessing a database table
|
|
427
|
+
if request.action_type in [ActionType.DATABASE_QUERY, ActionType.DATABASE_WRITE]:
|
|
428
|
+
table = request.parameters.get('table', request.parameters.get('database'))
|
|
429
|
+
if table:
|
|
430
|
+
node = self.data_graph.get_node(f"table:{table}")
|
|
431
|
+
if not node:
|
|
432
|
+
return False, f"Table '{table}' not in accessible data graph"
|
|
433
|
+
|
|
434
|
+
# Check if accessing a file
|
|
435
|
+
elif request.action_type in [ActionType.FILE_READ, ActionType.FILE_WRITE]:
|
|
436
|
+
path = request.parameters.get('path')
|
|
437
|
+
if path:
|
|
438
|
+
# Check if path is in data graph
|
|
439
|
+
accessible_paths = self.data_graph.get_accessible_paths()
|
|
440
|
+
if not any(path.startswith(p) for p in accessible_paths):
|
|
441
|
+
return False, f"Path '{path}' not in accessible data graph"
|
|
442
|
+
|
|
443
|
+
return True, None
|
|
444
|
+
|
|
445
|
+
def _validate_policy_graph(self, request: ExecutionRequest) -> Tuple[bool, List[str]]:
|
|
446
|
+
"""Validate against policy graph"""
|
|
447
|
+
violations = []
|
|
448
|
+
|
|
449
|
+
# Build resource ID based on action type
|
|
450
|
+
resource_id = self._build_resource_id(request)
|
|
451
|
+
if resource_id:
|
|
452
|
+
policy_violations = self.policy_graph.check_policy_violations(
|
|
453
|
+
request.agent_context.agent_id,
|
|
454
|
+
resource_id
|
|
455
|
+
)
|
|
456
|
+
violations.extend(policy_violations)
|
|
457
|
+
|
|
458
|
+
return len(violations) == 0, violations
|
|
459
|
+
|
|
460
|
+
def _validate_temporal_graph(self, request: ExecutionRequest) -> Tuple[bool, Optional[str]]:
|
|
461
|
+
"""Validate against temporal graph"""
|
|
462
|
+
allowed, reason = self.temporal_graph.is_action_allowed_now(request.action_type)
|
|
463
|
+
return allowed, reason
|
|
464
|
+
|
|
465
|
+
@staticmethod
|
|
466
|
+
def _build_resource_id(request: ExecutionRequest) -> Optional[str]:
|
|
467
|
+
"""Build a resource ID from the request"""
|
|
468
|
+
if request.action_type in [ActionType.DATABASE_QUERY, ActionType.DATABASE_WRITE]:
|
|
469
|
+
table = request.parameters.get('table', request.parameters.get('database'))
|
|
470
|
+
return f"table:{table}" if table else None
|
|
471
|
+
elif request.action_type in [ActionType.FILE_READ, ActionType.FILE_WRITE]:
|
|
472
|
+
path = request.parameters.get('path')
|
|
473
|
+
return f"file:{path}" if path else None
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
def get_validation_log(self) -> List[Dict[str, Any]]:
|
|
477
|
+
"""Get validation log"""
|
|
478
|
+
return self.validation_log.copy()
|