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,211 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Tests for Ed25519 cryptographic attestation in IATP."""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from iatp.attestation import (
|
|
7
|
+
AttestationValidator,
|
|
8
|
+
generate_ed25519_keypair,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def keypair():
|
|
14
|
+
"""Generate a fresh Ed25519 key pair."""
|
|
15
|
+
return generate_ed25519_keypair()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def validator_with_key(keypair):
|
|
20
|
+
"""Create an AttestationValidator with a registered public key."""
|
|
21
|
+
_, public_b64 = keypair
|
|
22
|
+
v = AttestationValidator()
|
|
23
|
+
v.add_trusted_key("test-key", public_b64)
|
|
24
|
+
return v
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestGenerateKeypair:
|
|
28
|
+
def test_returns_two_strings(self):
|
|
29
|
+
priv, pub = generate_ed25519_keypair()
|
|
30
|
+
assert isinstance(priv, str)
|
|
31
|
+
assert isinstance(pub, str)
|
|
32
|
+
|
|
33
|
+
def test_keys_are_base64(self):
|
|
34
|
+
import base64
|
|
35
|
+
priv, pub = generate_ed25519_keypair()
|
|
36
|
+
priv_bytes = base64.b64decode(priv)
|
|
37
|
+
pub_bytes = base64.b64decode(pub)
|
|
38
|
+
assert len(priv_bytes) == 32 # Ed25519 private key is 32 bytes
|
|
39
|
+
assert len(pub_bytes) == 32 # Ed25519 public key is 32 bytes
|
|
40
|
+
|
|
41
|
+
def test_unique_each_call(self):
|
|
42
|
+
_, pub1 = generate_ed25519_keypair()
|
|
43
|
+
_, pub2 = generate_ed25519_keypair()
|
|
44
|
+
assert pub1 != pub2
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestRealCryptoSignAndVerify:
|
|
48
|
+
def test_sign_and_verify_roundtrip(self, keypair, validator_with_key):
|
|
49
|
+
"""Create an attestation with real key, verify with real crypto."""
|
|
50
|
+
priv_b64, _ = keypair
|
|
51
|
+
v = validator_with_key
|
|
52
|
+
|
|
53
|
+
attestation = v.create_attestation(
|
|
54
|
+
agent_id="agent-001",
|
|
55
|
+
codebase_hash="aabbccdd",
|
|
56
|
+
config_hash="11223344",
|
|
57
|
+
signing_key_id="test-key",
|
|
58
|
+
private_key=priv_b64,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
62
|
+
assert is_valid, f"Verification failed: {error}"
|
|
63
|
+
assert error is None
|
|
64
|
+
|
|
65
|
+
def test_tampered_signature_rejected(self, keypair, validator_with_key):
|
|
66
|
+
"""Tampered signature must be rejected."""
|
|
67
|
+
import base64
|
|
68
|
+
priv_b64, _ = keypair
|
|
69
|
+
v = validator_with_key
|
|
70
|
+
|
|
71
|
+
attestation = v.create_attestation(
|
|
72
|
+
agent_id="agent-001",
|
|
73
|
+
codebase_hash="aabbccdd",
|
|
74
|
+
config_hash="11223344",
|
|
75
|
+
signing_key_id="test-key",
|
|
76
|
+
private_key=priv_b64,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Tamper with the signature (flip a byte)
|
|
80
|
+
sig_bytes = bytearray(base64.b64decode(attestation.signature))
|
|
81
|
+
sig_bytes[0] ^= 0xFF
|
|
82
|
+
attestation.signature = base64.b64encode(bytes(sig_bytes)).decode()
|
|
83
|
+
|
|
84
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
85
|
+
assert not is_valid
|
|
86
|
+
assert "invalid signature" in error.lower()
|
|
87
|
+
|
|
88
|
+
def test_tampered_agent_id_rejected(self, keypair, validator_with_key):
|
|
89
|
+
"""Changing the agent_id after signing must be rejected."""
|
|
90
|
+
priv_b64, _ = keypair
|
|
91
|
+
v = validator_with_key
|
|
92
|
+
|
|
93
|
+
attestation = v.create_attestation(
|
|
94
|
+
agent_id="agent-001",
|
|
95
|
+
codebase_hash="aabbccdd",
|
|
96
|
+
config_hash="11223344",
|
|
97
|
+
signing_key_id="test-key",
|
|
98
|
+
private_key=priv_b64,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Tamper with agent_id (message changes, signature invalid)
|
|
102
|
+
attestation.agent_id = "evil-agent"
|
|
103
|
+
|
|
104
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
105
|
+
assert not is_valid
|
|
106
|
+
assert "invalid signature" in error.lower()
|
|
107
|
+
|
|
108
|
+
def test_tampered_codebase_hash_rejected(self, keypair, validator_with_key):
|
|
109
|
+
"""Changing the codebase_hash after signing must be rejected."""
|
|
110
|
+
priv_b64, _ = keypair
|
|
111
|
+
v = validator_with_key
|
|
112
|
+
|
|
113
|
+
attestation = v.create_attestation(
|
|
114
|
+
agent_id="agent-001",
|
|
115
|
+
codebase_hash="aabbccdd",
|
|
116
|
+
config_hash="11223344",
|
|
117
|
+
signing_key_id="test-key",
|
|
118
|
+
private_key=priv_b64,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
attestation.codebase_hash = "deadbeef"
|
|
122
|
+
|
|
123
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
124
|
+
assert not is_valid
|
|
125
|
+
|
|
126
|
+
def test_wrong_key_rejected(self, keypair):
|
|
127
|
+
"""Signature made with one key must fail with a different public key."""
|
|
128
|
+
priv_b64, _ = keypair
|
|
129
|
+
_, other_pub = generate_ed25519_keypair()
|
|
130
|
+
|
|
131
|
+
v = AttestationValidator()
|
|
132
|
+
v.add_trusted_key("other-key", other_pub)
|
|
133
|
+
|
|
134
|
+
# Sign with original key, register different public key
|
|
135
|
+
attestation = v.create_attestation(
|
|
136
|
+
agent_id="agent-001",
|
|
137
|
+
codebase_hash="aabb",
|
|
138
|
+
config_hash="ccdd",
|
|
139
|
+
signing_key_id="other-key",
|
|
140
|
+
private_key=priv_b64,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
144
|
+
assert not is_valid
|
|
145
|
+
assert "invalid signature" in error.lower()
|
|
146
|
+
|
|
147
|
+
def test_unsigned_attestation_accepted_without_verify_flag(self, validator_with_key):
|
|
148
|
+
"""When verify_signature=False, unsigned attestations pass."""
|
|
149
|
+
v = validator_with_key
|
|
150
|
+
attestation = v.create_attestation(
|
|
151
|
+
agent_id="agent-001",
|
|
152
|
+
codebase_hash="aabb",
|
|
153
|
+
config_hash="ccdd",
|
|
154
|
+
signing_key_id="test-key",
|
|
155
|
+
private_key=None, # No signing
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=False)
|
|
159
|
+
assert is_valid
|
|
160
|
+
|
|
161
|
+
def test_unsigned_attestation_rejected_with_verify_flag(self, validator_with_key):
|
|
162
|
+
"""Unsigned attestation fails when verify_signature=True."""
|
|
163
|
+
v = validator_with_key
|
|
164
|
+
attestation = v.create_attestation(
|
|
165
|
+
agent_id="agent-001",
|
|
166
|
+
codebase_hash="aabb",
|
|
167
|
+
config_hash="ccdd",
|
|
168
|
+
signing_key_id="test-key",
|
|
169
|
+
private_key=None, # No real signing — base64 of message, not Ed25519
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
173
|
+
assert not is_valid
|
|
174
|
+
|
|
175
|
+
def test_multiple_agents_different_keys(self):
|
|
176
|
+
"""Each agent can have its own key pair."""
|
|
177
|
+
v = AttestationValidator()
|
|
178
|
+
|
|
179
|
+
priv1, pub1 = generate_ed25519_keypair()
|
|
180
|
+
priv2, pub2 = generate_ed25519_keypair()
|
|
181
|
+
v.add_trusted_key("key-agent1", pub1)
|
|
182
|
+
v.add_trusted_key("key-agent2", pub2)
|
|
183
|
+
|
|
184
|
+
att1 = v.create_attestation("agent-1", "h1", "c1", "key-agent1", priv1)
|
|
185
|
+
att2 = v.create_attestation("agent-2", "h2", "c2", "key-agent2", priv2)
|
|
186
|
+
|
|
187
|
+
assert v.validate_attestation(att1, verify_signature=True) == (True, None)
|
|
188
|
+
assert v.validate_attestation(att2, verify_signature=True) == (True, None)
|
|
189
|
+
|
|
190
|
+
# Cross-verify should fail (agent-1's attestation with agent-2's key)
|
|
191
|
+
att1.signing_key_id = "key-agent2"
|
|
192
|
+
is_valid, _ = v.validate_attestation(att1, verify_signature=True)
|
|
193
|
+
assert not is_valid
|
|
194
|
+
|
|
195
|
+
def test_expired_attestation_rejected_even_with_valid_sig(self, keypair, validator_with_key):
|
|
196
|
+
"""Valid signature doesn't help if attestation is expired."""
|
|
197
|
+
priv_b64, _ = keypair
|
|
198
|
+
v = validator_with_key
|
|
199
|
+
|
|
200
|
+
attestation = v.create_attestation(
|
|
201
|
+
agent_id="agent-001",
|
|
202
|
+
codebase_hash="aabb",
|
|
203
|
+
config_hash="ccdd",
|
|
204
|
+
signing_key_id="test-key",
|
|
205
|
+
private_key=priv_b64,
|
|
206
|
+
expires_in_hours=-1, # Already expired
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
is_valid, error = v.validate_attestation(attestation, verify_signature=True)
|
|
210
|
+
assert not is_valid
|
|
211
|
+
assert "expired" in error.lower()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for IATP models.
|
|
5
|
+
"""
|
|
6
|
+
from iatp.models import (
|
|
7
|
+
AgentCapabilities,
|
|
8
|
+
CapabilityManifest,
|
|
9
|
+
PrivacyContract,
|
|
10
|
+
RetentionPolicy,
|
|
11
|
+
ReversibilityLevel,
|
|
12
|
+
TrustLevel,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_capability_manifest_creation():
|
|
17
|
+
"""Test creating a capability manifest."""
|
|
18
|
+
manifest = CapabilityManifest(
|
|
19
|
+
agent_id="test-agent",
|
|
20
|
+
agent_version="1.0.0",
|
|
21
|
+
trust_level=TrustLevel.TRUSTED,
|
|
22
|
+
capabilities=AgentCapabilities(
|
|
23
|
+
idempotency=True,
|
|
24
|
+
reversibility=ReversibilityLevel.FULL,
|
|
25
|
+
undo_window="24h",
|
|
26
|
+
sla_latency="1000ms"
|
|
27
|
+
),
|
|
28
|
+
privacy_contract=PrivacyContract(
|
|
29
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
30
|
+
storage_location="us-east",
|
|
31
|
+
human_review=False
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert manifest.agent_id == "test-agent"
|
|
36
|
+
assert manifest.trust_level == TrustLevel.TRUSTED
|
|
37
|
+
assert manifest.capabilities.idempotency is True
|
|
38
|
+
assert manifest.privacy_contract.retention == RetentionPolicy.EPHEMERAL
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_capability_manifest_with_scopes():
|
|
42
|
+
"""Test creating a capability manifest with RBAC scopes."""
|
|
43
|
+
manifest = CapabilityManifest(
|
|
44
|
+
agent_id="coder-agent",
|
|
45
|
+
agent_version="1.0.0",
|
|
46
|
+
trust_level=TrustLevel.TRUSTED,
|
|
47
|
+
capabilities=AgentCapabilities(
|
|
48
|
+
idempotency=True,
|
|
49
|
+
reversibility=ReversibilityLevel.FULL,
|
|
50
|
+
),
|
|
51
|
+
privacy_contract=PrivacyContract(
|
|
52
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
53
|
+
),
|
|
54
|
+
scopes=["repo:read", "repo:write"]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert manifest.agent_id == "coder-agent"
|
|
58
|
+
assert manifest.scopes == ["repo:read", "repo:write"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_capability_manifest_default_scopes():
|
|
62
|
+
"""Test that scopes defaults to empty list."""
|
|
63
|
+
manifest = CapabilityManifest(
|
|
64
|
+
agent_id="agent-without-scopes",
|
|
65
|
+
trust_level=TrustLevel.STANDARD,
|
|
66
|
+
capabilities=AgentCapabilities(),
|
|
67
|
+
privacy_contract=PrivacyContract(
|
|
68
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
assert manifest.scopes == []
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_trust_score_verified_partner():
|
|
76
|
+
"""Test trust score for verified partner."""
|
|
77
|
+
manifest = CapabilityManifest(
|
|
78
|
+
agent_id="verified-agent",
|
|
79
|
+
trust_level=TrustLevel.VERIFIED_PARTNER,
|
|
80
|
+
capabilities=AgentCapabilities(
|
|
81
|
+
idempotency=True,
|
|
82
|
+
reversibility=ReversibilityLevel.FULL
|
|
83
|
+
),
|
|
84
|
+
privacy_contract=PrivacyContract(
|
|
85
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
86
|
+
human_review=False
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
score = manifest.calculate_trust_score()
|
|
91
|
+
assert score >= 8 # Verified partner with good privacy should score high
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_trust_score_untrusted():
|
|
95
|
+
"""Test trust score for untrusted agent."""
|
|
96
|
+
manifest = CapabilityManifest(
|
|
97
|
+
agent_id="sketchy-agent",
|
|
98
|
+
trust_level=TrustLevel.UNTRUSTED,
|
|
99
|
+
capabilities=AgentCapabilities(
|
|
100
|
+
idempotency=False,
|
|
101
|
+
reversibility=ReversibilityLevel.NONE
|
|
102
|
+
),
|
|
103
|
+
privacy_contract=PrivacyContract(
|
|
104
|
+
retention=RetentionPolicy.FOREVER,
|
|
105
|
+
human_review=True
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
score = manifest.calculate_trust_score()
|
|
110
|
+
assert score <= 3 # Untrusted agent with bad privacy should score low
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_privacy_contract_defaults():
|
|
114
|
+
"""Test privacy contract default values."""
|
|
115
|
+
contract = PrivacyContract(
|
|
116
|
+
retention=RetentionPolicy.TEMPORARY
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
assert contract.human_review is False
|
|
120
|
+
assert contract.encryption_at_rest is True
|
|
121
|
+
assert contract.encryption_in_transit is True
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_agent_capabilities_defaults():
|
|
125
|
+
"""Test agent capabilities default values."""
|
|
126
|
+
capabilities = AgentCapabilities()
|
|
127
|
+
|
|
128
|
+
assert capabilities.idempotency is False
|
|
129
|
+
assert capabilities.reversibility == ReversibilityLevel.NONE
|
|
130
|
+
assert capabilities.undo_window is None
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Tests for the IATP Policy Engine.
|
|
5
|
+
"""
|
|
6
|
+
from iatp.models import (
|
|
7
|
+
AgentCapabilities,
|
|
8
|
+
CapabilityManifest,
|
|
9
|
+
PrivacyContract,
|
|
10
|
+
RetentionPolicy,
|
|
11
|
+
ReversibilityLevel,
|
|
12
|
+
TrustLevel,
|
|
13
|
+
)
|
|
14
|
+
from iatp.policy_engine import IATPPolicyEngine
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_policy_engine_initialization():
|
|
18
|
+
"""Test that policy engine initializes with default policies."""
|
|
19
|
+
engine = IATPPolicyEngine()
|
|
20
|
+
assert engine is not None
|
|
21
|
+
assert len(engine.rules) > 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_validate_manifest_ephemeral_allowed():
|
|
25
|
+
"""Test that ephemeral retention is allowed."""
|
|
26
|
+
engine = IATPPolicyEngine()
|
|
27
|
+
|
|
28
|
+
manifest = CapabilityManifest(
|
|
29
|
+
agent_id="test-agent",
|
|
30
|
+
trust_level=TrustLevel.TRUSTED,
|
|
31
|
+
capabilities=AgentCapabilities(
|
|
32
|
+
idempotency=True,
|
|
33
|
+
reversibility=ReversibilityLevel.FULL,
|
|
34
|
+
),
|
|
35
|
+
privacy_contract=PrivacyContract(
|
|
36
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
37
|
+
human_review=False,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
is_allowed, error_msg, warning_msg = engine.validate_manifest(manifest)
|
|
42
|
+
|
|
43
|
+
assert is_allowed is True
|
|
44
|
+
assert error_msg is None
|
|
45
|
+
# May have warning for other reasons, but should not block
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_validate_manifest_permanent_warning():
|
|
49
|
+
"""Test that permanent retention generates a warning but doesn't block."""
|
|
50
|
+
engine = IATPPolicyEngine()
|
|
51
|
+
|
|
52
|
+
manifest = CapabilityManifest(
|
|
53
|
+
agent_id="permanent-storage-agent",
|
|
54
|
+
trust_level=TrustLevel.STANDARD,
|
|
55
|
+
capabilities=AgentCapabilities(
|
|
56
|
+
idempotency=False,
|
|
57
|
+
reversibility=ReversibilityLevel.NONE,
|
|
58
|
+
),
|
|
59
|
+
privacy_contract=PrivacyContract(
|
|
60
|
+
retention=RetentionPolicy.PERMANENT,
|
|
61
|
+
human_review=True,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
is_allowed, error_msg, warning_msg = engine.validate_manifest(manifest)
|
|
66
|
+
|
|
67
|
+
# Should be allowed but may have warnings
|
|
68
|
+
# (Actual blocking based on sensitive data happens in SecurityValidator)
|
|
69
|
+
assert is_allowed is True
|
|
70
|
+
# May have warning about no reversibility
|
|
71
|
+
if warning_msg:
|
|
72
|
+
assert "reversal" in warning_msg.lower() or "warning" in warning_msg.lower()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_validate_manifest_no_reversibility_warning():
|
|
76
|
+
"""Test that no reversibility generates warning."""
|
|
77
|
+
engine = IATPPolicyEngine()
|
|
78
|
+
|
|
79
|
+
manifest = CapabilityManifest(
|
|
80
|
+
agent_id="limited-agent",
|
|
81
|
+
trust_level=TrustLevel.STANDARD,
|
|
82
|
+
capabilities=AgentCapabilities(
|
|
83
|
+
idempotency=True,
|
|
84
|
+
reversibility=ReversibilityLevel.NONE,
|
|
85
|
+
),
|
|
86
|
+
privacy_contract=PrivacyContract(
|
|
87
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
88
|
+
human_review=False,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
is_allowed, error_msg, warning_msg = engine.validate_manifest(manifest)
|
|
93
|
+
|
|
94
|
+
assert is_allowed is True
|
|
95
|
+
assert error_msg is None
|
|
96
|
+
# Warning about no reversibility
|
|
97
|
+
if warning_msg:
|
|
98
|
+
assert "reversal" in warning_msg.lower() or "warning" in warning_msg.lower()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_add_custom_rule():
|
|
102
|
+
"""Test adding custom policy rules."""
|
|
103
|
+
engine = IATPPolicyEngine()
|
|
104
|
+
|
|
105
|
+
custom_rule = {
|
|
106
|
+
"name": "CustomTestRule",
|
|
107
|
+
"description": "Test custom rule",
|
|
108
|
+
"action": "deny",
|
|
109
|
+
"conditions": {"trust_level": ["untrusted"]}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
engine.add_custom_rule(custom_rule)
|
|
113
|
+
|
|
114
|
+
# Test with untrusted agent
|
|
115
|
+
manifest = CapabilityManifest(
|
|
116
|
+
agent_id="untrusted-agent",
|
|
117
|
+
trust_level=TrustLevel.UNTRUSTED,
|
|
118
|
+
capabilities=AgentCapabilities(
|
|
119
|
+
idempotency=True,
|
|
120
|
+
reversibility=ReversibilityLevel.FULL,
|
|
121
|
+
),
|
|
122
|
+
privacy_contract=PrivacyContract(
|
|
123
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
124
|
+
human_review=False,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
is_allowed, error_msg, warning_msg = engine.validate_manifest(manifest)
|
|
129
|
+
|
|
130
|
+
# Should be blocked by custom rule or existing rules
|
|
131
|
+
assert is_allowed is False or warning_msg is not None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_validate_handshake_compatible():
|
|
135
|
+
"""Test handshake validation with compatible agent."""
|
|
136
|
+
engine = IATPPolicyEngine()
|
|
137
|
+
|
|
138
|
+
manifest = CapabilityManifest(
|
|
139
|
+
agent_id="compatible-agent",
|
|
140
|
+
trust_level=TrustLevel.TRUSTED,
|
|
141
|
+
capabilities=AgentCapabilities(
|
|
142
|
+
idempotency=True,
|
|
143
|
+
reversibility=ReversibilityLevel.FULL,
|
|
144
|
+
),
|
|
145
|
+
privacy_contract=PrivacyContract(
|
|
146
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
147
|
+
human_review=False,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
is_compatible, error_msg = engine.validate_handshake(
|
|
152
|
+
manifest,
|
|
153
|
+
required_capabilities=["reversibility", "idempotency"]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
assert is_compatible is True
|
|
157
|
+
assert error_msg is None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_validate_handshake_missing_capabilities():
|
|
161
|
+
"""Test handshake validation with missing capabilities."""
|
|
162
|
+
engine = IATPPolicyEngine()
|
|
163
|
+
|
|
164
|
+
manifest = CapabilityManifest(
|
|
165
|
+
agent_id="limited-agent",
|
|
166
|
+
trust_level=TrustLevel.STANDARD,
|
|
167
|
+
capabilities=AgentCapabilities(
|
|
168
|
+
idempotency=False,
|
|
169
|
+
reversibility=ReversibilityLevel.NONE,
|
|
170
|
+
),
|
|
171
|
+
privacy_contract=PrivacyContract(
|
|
172
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
173
|
+
human_review=False,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
is_compatible, error_msg = engine.validate_handshake(
|
|
178
|
+
manifest,
|
|
179
|
+
required_capabilities=["reversibility"]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
assert is_compatible is False
|
|
183
|
+
assert error_msg is not None
|
|
184
|
+
assert "reversibility" in error_msg.lower()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_manifest_to_context():
|
|
188
|
+
"""Test conversion of manifest to policy context."""
|
|
189
|
+
engine = IATPPolicyEngine()
|
|
190
|
+
|
|
191
|
+
manifest = CapabilityManifest(
|
|
192
|
+
agent_id="test-agent",
|
|
193
|
+
trust_level=TrustLevel.VERIFIED_PARTNER,
|
|
194
|
+
capabilities=AgentCapabilities(
|
|
195
|
+
idempotency=True,
|
|
196
|
+
reversibility=ReversibilityLevel.PARTIAL,
|
|
197
|
+
),
|
|
198
|
+
privacy_contract=PrivacyContract(
|
|
199
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
200
|
+
human_review=True,
|
|
201
|
+
encryption_at_rest=True,
|
|
202
|
+
encryption_in_transit=True,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
context = engine._manifest_to_context(manifest)
|
|
207
|
+
|
|
208
|
+
assert context["agent_id"] == "test-agent"
|
|
209
|
+
assert context["trust_level"] == "verified_partner"
|
|
210
|
+
assert context["retention_policy"] == "ephemeral"
|
|
211
|
+
assert context["reversibility"] == "partial"
|
|
212
|
+
assert context["idempotency"] is True
|
|
213
|
+
assert context["human_review"] is True
|
|
214
|
+
assert context["encryption_at_rest"] is True
|
|
215
|
+
assert context["encryption_in_transit"] is True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_validate_handshake_with_required_scopes():
|
|
219
|
+
"""Test handshake validation with required scopes."""
|
|
220
|
+
engine = IATPPolicyEngine()
|
|
221
|
+
|
|
222
|
+
manifest = CapabilityManifest(
|
|
223
|
+
agent_id="coder-agent",
|
|
224
|
+
trust_level=TrustLevel.TRUSTED,
|
|
225
|
+
capabilities=AgentCapabilities(
|
|
226
|
+
idempotency=True,
|
|
227
|
+
reversibility=ReversibilityLevel.FULL,
|
|
228
|
+
),
|
|
229
|
+
privacy_contract=PrivacyContract(
|
|
230
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
231
|
+
human_review=False,
|
|
232
|
+
),
|
|
233
|
+
scopes=["repo:read", "repo:write"]
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
is_compatible, error_msg = engine.validate_handshake(
|
|
237
|
+
manifest,
|
|
238
|
+
required_scopes=["repo:write"]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
assert is_compatible is True
|
|
242
|
+
assert error_msg is None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_validate_handshake_missing_scopes():
|
|
246
|
+
"""Test handshake validation with missing required scopes."""
|
|
247
|
+
engine = IATPPolicyEngine()
|
|
248
|
+
|
|
249
|
+
manifest = CapabilityManifest(
|
|
250
|
+
agent_id="reviewer-agent",
|
|
251
|
+
trust_level=TrustLevel.STANDARD,
|
|
252
|
+
capabilities=AgentCapabilities(
|
|
253
|
+
idempotency=True,
|
|
254
|
+
reversibility=ReversibilityLevel.FULL,
|
|
255
|
+
),
|
|
256
|
+
privacy_contract=PrivacyContract(
|
|
257
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
258
|
+
human_review=False,
|
|
259
|
+
),
|
|
260
|
+
scopes=["repo:read"] # Only has read access
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
is_compatible, error_msg = engine.validate_handshake(
|
|
264
|
+
manifest,
|
|
265
|
+
required_scopes=["repo:write"] # But needs write access
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
assert is_compatible is False
|
|
269
|
+
assert error_msg is not None
|
|
270
|
+
assert "repo:write" in error_msg
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_validate_handshake_multiple_scopes():
|
|
274
|
+
"""Test handshake validation with multiple required scopes."""
|
|
275
|
+
engine = IATPPolicyEngine()
|
|
276
|
+
|
|
277
|
+
manifest = CapabilityManifest(
|
|
278
|
+
agent_id="admin-agent",
|
|
279
|
+
trust_level=TrustLevel.VERIFIED_PARTNER,
|
|
280
|
+
capabilities=AgentCapabilities(
|
|
281
|
+
idempotency=True,
|
|
282
|
+
reversibility=ReversibilityLevel.FULL,
|
|
283
|
+
),
|
|
284
|
+
privacy_contract=PrivacyContract(
|
|
285
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
286
|
+
human_review=False,
|
|
287
|
+
),
|
|
288
|
+
scopes=["repo:read", "repo:write", "admin:manage"]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
is_compatible, error_msg = engine.validate_handshake(
|
|
292
|
+
manifest,
|
|
293
|
+
required_scopes=["repo:read", "repo:write"]
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
assert is_compatible is True
|
|
297
|
+
assert error_msg is None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_validate_handshake_no_scopes_required():
|
|
301
|
+
"""Test handshake validation when no scopes are required."""
|
|
302
|
+
engine = IATPPolicyEngine()
|
|
303
|
+
|
|
304
|
+
manifest = CapabilityManifest(
|
|
305
|
+
agent_id="basic-agent",
|
|
306
|
+
trust_level=TrustLevel.STANDARD,
|
|
307
|
+
capabilities=AgentCapabilities(
|
|
308
|
+
idempotency=True,
|
|
309
|
+
reversibility=ReversibilityLevel.FULL,
|
|
310
|
+
),
|
|
311
|
+
privacy_contract=PrivacyContract(
|
|
312
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
313
|
+
human_review=False,
|
|
314
|
+
),
|
|
315
|
+
scopes=[] # No scopes
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
is_compatible, error_msg = engine.validate_handshake(
|
|
319
|
+
manifest,
|
|
320
|
+
required_scopes=None # No scopes required
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
assert is_compatible is True
|
|
324
|
+
assert error_msg is None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def test_manifest_to_context_with_scopes():
|
|
328
|
+
"""Test conversion of manifest with scopes to policy context."""
|
|
329
|
+
engine = IATPPolicyEngine()
|
|
330
|
+
|
|
331
|
+
manifest = CapabilityManifest(
|
|
332
|
+
agent_id="scoped-agent",
|
|
333
|
+
trust_level=TrustLevel.TRUSTED,
|
|
334
|
+
capabilities=AgentCapabilities(
|
|
335
|
+
idempotency=True,
|
|
336
|
+
reversibility=ReversibilityLevel.FULL,
|
|
337
|
+
),
|
|
338
|
+
privacy_contract=PrivacyContract(
|
|
339
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
340
|
+
human_review=False,
|
|
341
|
+
),
|
|
342
|
+
scopes=["repo:read", "repo:write"]
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
context = engine._manifest_to_context(manifest)
|
|
346
|
+
|
|
347
|
+
assert context["scopes"] == ["repo:read", "repo:write"]
|