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,281 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Tests for the IATP Recovery Engine (scak integration).
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from iatp.models import (
|
|
9
|
+
AgentCapabilities,
|
|
10
|
+
CapabilityManifest,
|
|
11
|
+
PrivacyContract,
|
|
12
|
+
RetentionPolicy,
|
|
13
|
+
ReversibilityLevel,
|
|
14
|
+
TrustLevel,
|
|
15
|
+
)
|
|
16
|
+
from iatp.recovery import IATPRecoveryEngine
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
async def test_recovery_engine_initialization():
|
|
21
|
+
"""Test that recovery engine initializes correctly."""
|
|
22
|
+
engine = IATPRecoveryEngine()
|
|
23
|
+
assert engine is not None
|
|
24
|
+
assert engine.recovery_history == {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_handle_failure_with_reversibility():
|
|
29
|
+
"""Test handling failure with reversible agent."""
|
|
30
|
+
engine = IATPRecoveryEngine()
|
|
31
|
+
|
|
32
|
+
manifest = CapabilityManifest(
|
|
33
|
+
agent_id="reversible-agent",
|
|
34
|
+
trust_level=TrustLevel.TRUSTED,
|
|
35
|
+
capabilities=AgentCapabilities(
|
|
36
|
+
idempotency=True,
|
|
37
|
+
reversibility=ReversibilityLevel.FULL,
|
|
38
|
+
),
|
|
39
|
+
privacy_contract=PrivacyContract(
|
|
40
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
41
|
+
human_review=False,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
error = Exception("Test error")
|
|
46
|
+
payload = {"test": "data"}
|
|
47
|
+
|
|
48
|
+
# Test without compensation callback
|
|
49
|
+
result = await engine.handle_failure(
|
|
50
|
+
trace_id="test-trace-1",
|
|
51
|
+
error=error,
|
|
52
|
+
manifest=manifest,
|
|
53
|
+
payload=payload,
|
|
54
|
+
compensation_callback=None
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert result is not None
|
|
58
|
+
assert "strategy" in result
|
|
59
|
+
assert "trace_id" in result
|
|
60
|
+
assert result["trace_id"] == "test-trace-1"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_handle_failure_with_compensation():
|
|
65
|
+
"""Test handling failure with compensation callback."""
|
|
66
|
+
engine = IATPRecoveryEngine()
|
|
67
|
+
|
|
68
|
+
manifest = CapabilityManifest(
|
|
69
|
+
agent_id="compensating-agent",
|
|
70
|
+
trust_level=TrustLevel.TRUSTED,
|
|
71
|
+
capabilities=AgentCapabilities(
|
|
72
|
+
idempotency=True,
|
|
73
|
+
reversibility=ReversibilityLevel.FULL,
|
|
74
|
+
),
|
|
75
|
+
privacy_contract=PrivacyContract(
|
|
76
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
77
|
+
human_review=False,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
compensation_called = False
|
|
82
|
+
|
|
83
|
+
async def compensation_callback():
|
|
84
|
+
nonlocal compensation_called
|
|
85
|
+
compensation_called = True
|
|
86
|
+
|
|
87
|
+
error = Exception("Test error")
|
|
88
|
+
payload = {"test": "data"}
|
|
89
|
+
|
|
90
|
+
result = await engine.handle_failure(
|
|
91
|
+
trace_id="test-trace-2",
|
|
92
|
+
error=error,
|
|
93
|
+
manifest=manifest,
|
|
94
|
+
payload=payload,
|
|
95
|
+
compensation_callback=compensation_callback
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
assert result is not None
|
|
99
|
+
assert compensation_called is True
|
|
100
|
+
assert result["success"] is True
|
|
101
|
+
assert "compensation_executed" in result["actions_taken"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.asyncio
|
|
105
|
+
async def test_handle_failure_no_reversibility():
|
|
106
|
+
"""Test handling failure with non-reversible agent."""
|
|
107
|
+
engine = IATPRecoveryEngine()
|
|
108
|
+
|
|
109
|
+
manifest = CapabilityManifest(
|
|
110
|
+
agent_id="non-reversible-agent",
|
|
111
|
+
trust_level=TrustLevel.STANDARD,
|
|
112
|
+
capabilities=AgentCapabilities(
|
|
113
|
+
idempotency=False,
|
|
114
|
+
reversibility=ReversibilityLevel.NONE,
|
|
115
|
+
),
|
|
116
|
+
privacy_contract=PrivacyContract(
|
|
117
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
118
|
+
human_review=False,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
error = Exception("Test error")
|
|
123
|
+
payload = {"test": "data"}
|
|
124
|
+
|
|
125
|
+
result = await engine.handle_failure(
|
|
126
|
+
trace_id="test-trace-3",
|
|
127
|
+
error=error,
|
|
128
|
+
manifest=manifest,
|
|
129
|
+
payload=payload,
|
|
130
|
+
compensation_callback=None
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert result is not None
|
|
134
|
+
assert result["success"] is False
|
|
135
|
+
# Should give up or recommend retry
|
|
136
|
+
assert "strategy" in result
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_should_attempt_recovery_reversible():
|
|
140
|
+
"""Test recovery attempt decision for reversible agent."""
|
|
141
|
+
engine = IATPRecoveryEngine()
|
|
142
|
+
|
|
143
|
+
manifest = CapabilityManifest(
|
|
144
|
+
agent_id="reversible-agent",
|
|
145
|
+
trust_level=TrustLevel.TRUSTED,
|
|
146
|
+
capabilities=AgentCapabilities(
|
|
147
|
+
idempotency=True,
|
|
148
|
+
reversibility=ReversibilityLevel.FULL,
|
|
149
|
+
),
|
|
150
|
+
privacy_contract=PrivacyContract(
|
|
151
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
152
|
+
human_review=False,
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
error = Exception("Test error")
|
|
157
|
+
|
|
158
|
+
should_recover = engine.should_attempt_recovery(error, manifest)
|
|
159
|
+
|
|
160
|
+
assert should_recover is True
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_should_attempt_recovery_timeout():
|
|
164
|
+
"""Test recovery attempt decision for timeout error."""
|
|
165
|
+
engine = IATPRecoveryEngine()
|
|
166
|
+
|
|
167
|
+
manifest = CapabilityManifest(
|
|
168
|
+
agent_id="non-reversible-agent",
|
|
169
|
+
trust_level=TrustLevel.STANDARD,
|
|
170
|
+
capabilities=AgentCapabilities(
|
|
171
|
+
idempotency=False,
|
|
172
|
+
reversibility=ReversibilityLevel.NONE,
|
|
173
|
+
),
|
|
174
|
+
privacy_contract=PrivacyContract(
|
|
175
|
+
retention=RetentionPolicy.TEMPORARY,
|
|
176
|
+
human_review=False,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
error = TimeoutError("Request timeout")
|
|
181
|
+
|
|
182
|
+
should_recover = engine.should_attempt_recovery(error, manifest)
|
|
183
|
+
|
|
184
|
+
# Should attempt recovery for timeout even without reversibility
|
|
185
|
+
assert should_recover is True
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_get_recovery_history():
|
|
189
|
+
"""Test retrieval of recovery history."""
|
|
190
|
+
engine = IATPRecoveryEngine()
|
|
191
|
+
|
|
192
|
+
# Add some history manually
|
|
193
|
+
engine.recovery_history["test-trace"] = {
|
|
194
|
+
"test": "data"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
history = engine.get_recovery_history("test-trace")
|
|
198
|
+
|
|
199
|
+
assert history is not None
|
|
200
|
+
assert history["test"] == "data"
|
|
201
|
+
|
|
202
|
+
# Non-existent trace
|
|
203
|
+
missing_history = engine.get_recovery_history("non-existent")
|
|
204
|
+
assert missing_history is None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@pytest.mark.asyncio
|
|
208
|
+
async def test_handle_failure_compensation_exception():
|
|
209
|
+
"""Test handling failure when compensation callback raises exception."""
|
|
210
|
+
engine = IATPRecoveryEngine()
|
|
211
|
+
|
|
212
|
+
manifest = CapabilityManifest(
|
|
213
|
+
agent_id="failing-compensation-agent",
|
|
214
|
+
trust_level=TrustLevel.TRUSTED,
|
|
215
|
+
capabilities=AgentCapabilities(
|
|
216
|
+
idempotency=True,
|
|
217
|
+
reversibility=ReversibilityLevel.FULL,
|
|
218
|
+
),
|
|
219
|
+
privacy_contract=PrivacyContract(
|
|
220
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
221
|
+
human_review=False,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
async def failing_compensation():
|
|
226
|
+
raise Exception("Compensation failed")
|
|
227
|
+
|
|
228
|
+
error = Exception("Test error")
|
|
229
|
+
payload = {"test": "data"}
|
|
230
|
+
|
|
231
|
+
result = await engine.handle_failure(
|
|
232
|
+
trace_id="test-trace-4",
|
|
233
|
+
error=error,
|
|
234
|
+
manifest=manifest,
|
|
235
|
+
payload=payload,
|
|
236
|
+
compensation_callback=failing_compensation
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
assert result is not None
|
|
240
|
+
assert result["success"] is False
|
|
241
|
+
assert any("compensation_failed" in action for action in result["actions_taken"])
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@pytest.mark.asyncio
|
|
245
|
+
async def test_handle_failure_sync_callback():
|
|
246
|
+
"""Test handling failure with synchronous compensation callback."""
|
|
247
|
+
engine = IATPRecoveryEngine()
|
|
248
|
+
|
|
249
|
+
manifest = CapabilityManifest(
|
|
250
|
+
agent_id="sync-compensation-agent",
|
|
251
|
+
trust_level=TrustLevel.TRUSTED,
|
|
252
|
+
capabilities=AgentCapabilities(
|
|
253
|
+
idempotency=True,
|
|
254
|
+
reversibility=ReversibilityLevel.FULL,
|
|
255
|
+
),
|
|
256
|
+
privacy_contract=PrivacyContract(
|
|
257
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
258
|
+
human_review=False,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
compensation_called = False
|
|
263
|
+
|
|
264
|
+
def sync_compensation():
|
|
265
|
+
nonlocal compensation_called
|
|
266
|
+
compensation_called = True
|
|
267
|
+
|
|
268
|
+
error = Exception("Test error")
|
|
269
|
+
payload = {"test": "data"}
|
|
270
|
+
|
|
271
|
+
result = await engine.handle_failure(
|
|
272
|
+
trace_id="test-trace-5",
|
|
273
|
+
error=error,
|
|
274
|
+
manifest=manifest,
|
|
275
|
+
payload=payload,
|
|
276
|
+
compensation_callback=sync_compensation
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
assert result is not None
|
|
280
|
+
assert compensation_called is True
|
|
281
|
+
assert result["success"] is True
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for IATP security module.
|
|
5
|
+
"""
|
|
6
|
+
from iatp.models import (
|
|
7
|
+
AgentCapabilities,
|
|
8
|
+
CapabilityManifest,
|
|
9
|
+
PrivacyContract,
|
|
10
|
+
RetentionPolicy,
|
|
11
|
+
ReversibilityLevel,
|
|
12
|
+
TrustLevel,
|
|
13
|
+
)
|
|
14
|
+
from iatp.security import PrivacyScrubber, SecurityValidator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_detect_credit_card():
|
|
18
|
+
"""Test credit card detection with Luhn validation."""
|
|
19
|
+
validator = SecurityValidator()
|
|
20
|
+
|
|
21
|
+
# Test various credit card formats (using valid test card numbers)
|
|
22
|
+
# 4532015112830366 is a valid test Visa card that passes Luhn check
|
|
23
|
+
payload1 = {"data": "My card is 4532-0151-1283-0366"}
|
|
24
|
+
sensitive1 = validator.detect_sensitive_data(payload1)
|
|
25
|
+
assert "credit_card" in sensitive1
|
|
26
|
+
|
|
27
|
+
payload2 = {"data": "My card is 4532 0151 1283 0366"}
|
|
28
|
+
sensitive2 = validator.detect_sensitive_data(payload2)
|
|
29
|
+
assert "credit_card" in sensitive2
|
|
30
|
+
|
|
31
|
+
payload3 = {"data": "My card is 4532015112830366"}
|
|
32
|
+
sensitive3 = validator.detect_sensitive_data(payload3)
|
|
33
|
+
assert "credit_card" in sensitive3
|
|
34
|
+
|
|
35
|
+
# Test with invalid card number (should not be detected)
|
|
36
|
+
payload4 = {"data": "My card is 1234-5678-9012-3456"}
|
|
37
|
+
sensitive4 = validator.detect_sensitive_data(payload4)
|
|
38
|
+
assert "credit_card" not in sensitive4
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_detect_ssn():
|
|
42
|
+
"""Test SSN detection."""
|
|
43
|
+
validator = SecurityValidator()
|
|
44
|
+
|
|
45
|
+
payload = {"data": "My SSN is 123-45-6789"}
|
|
46
|
+
sensitive = validator.detect_sensitive_data(payload)
|
|
47
|
+
assert "ssn" in sensitive
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_detect_email():
|
|
51
|
+
"""Test email detection."""
|
|
52
|
+
validator = SecurityValidator()
|
|
53
|
+
|
|
54
|
+
payload = {"data": "Contact me at test@example.com"}
|
|
55
|
+
sensitive = validator.detect_sensitive_data(payload)
|
|
56
|
+
assert "email" in sensitive
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_validate_privacy_policy_blocks_credit_card_forever():
|
|
60
|
+
"""Test that credit cards are blocked for permanent retention."""
|
|
61
|
+
validator = SecurityValidator()
|
|
62
|
+
manifest = CapabilityManifest(
|
|
63
|
+
agent_id="bad-agent",
|
|
64
|
+
trust_level=TrustLevel.UNTRUSTED,
|
|
65
|
+
capabilities=AgentCapabilities(),
|
|
66
|
+
privacy_contract=PrivacyContract(
|
|
67
|
+
retention=RetentionPolicy.FOREVER
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
payload = {"credit_card": "4532-0151-1283-0366"} # Valid test card
|
|
72
|
+
is_valid, error = validator.validate_privacy_policy(manifest, payload)
|
|
73
|
+
|
|
74
|
+
assert not is_valid
|
|
75
|
+
assert "Privacy Violation" in error
|
|
76
|
+
assert "credit card" in error.lower()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_validate_privacy_policy_allows_credit_card_ephemeral():
|
|
80
|
+
"""Test that credit cards are allowed for ephemeral retention."""
|
|
81
|
+
validator = SecurityValidator()
|
|
82
|
+
manifest = CapabilityManifest(
|
|
83
|
+
agent_id="good-agent",
|
|
84
|
+
trust_level=TrustLevel.VERIFIED_PARTNER,
|
|
85
|
+
capabilities=AgentCapabilities(),
|
|
86
|
+
privacy_contract=PrivacyContract(
|
|
87
|
+
retention=RetentionPolicy.EPHEMERAL
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
payload = {"credit_card": "4532-0151-1283-0366"} # Valid test card
|
|
92
|
+
is_valid, error = validator.validate_privacy_policy(manifest, payload)
|
|
93
|
+
|
|
94
|
+
assert is_valid
|
|
95
|
+
assert error is None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_validate_privacy_policy_blocks_ssn_non_ephemeral():
|
|
99
|
+
"""Test that SSN is blocked for non-ephemeral retention."""
|
|
100
|
+
validator = SecurityValidator()
|
|
101
|
+
manifest = CapabilityManifest(
|
|
102
|
+
agent_id="medium-agent",
|
|
103
|
+
trust_level=TrustLevel.STANDARD,
|
|
104
|
+
capabilities=AgentCapabilities(),
|
|
105
|
+
privacy_contract=PrivacyContract(
|
|
106
|
+
retention=RetentionPolicy.TEMPORARY
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
payload = {"ssn": "123-45-6789"}
|
|
111
|
+
is_valid, error = validator.validate_privacy_policy(manifest, payload)
|
|
112
|
+
|
|
113
|
+
assert not is_valid
|
|
114
|
+
assert "SSN" in error
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_generate_warning_low_trust():
|
|
118
|
+
"""Test warning generation for low trust agents."""
|
|
119
|
+
validator = SecurityValidator()
|
|
120
|
+
manifest = CapabilityManifest(
|
|
121
|
+
agent_id="sketchy-agent",
|
|
122
|
+
trust_level=TrustLevel.UNTRUSTED,
|
|
123
|
+
capabilities=AgentCapabilities(
|
|
124
|
+
idempotency=False,
|
|
125
|
+
reversibility=ReversibilityLevel.NONE
|
|
126
|
+
),
|
|
127
|
+
privacy_contract=PrivacyContract(
|
|
128
|
+
retention=RetentionPolicy.FOREVER,
|
|
129
|
+
human_review=True
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
warning = validator.generate_warning_message(manifest, {})
|
|
134
|
+
|
|
135
|
+
assert warning is not None
|
|
136
|
+
assert "Low trust score" in warning
|
|
137
|
+
assert "does not support transaction reversal" in warning
|
|
138
|
+
assert "stores data indefinitely" in warning
|
|
139
|
+
assert "may have humans review your data" in warning
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_generate_warning_trusted_agent():
|
|
143
|
+
"""Test that no warning is generated for trusted agents."""
|
|
144
|
+
validator = SecurityValidator()
|
|
145
|
+
manifest = CapabilityManifest(
|
|
146
|
+
agent_id="trusted-agent",
|
|
147
|
+
trust_level=TrustLevel.VERIFIED_PARTNER,
|
|
148
|
+
capabilities=AgentCapabilities(
|
|
149
|
+
idempotency=True,
|
|
150
|
+
reversibility=ReversibilityLevel.FULL
|
|
151
|
+
),
|
|
152
|
+
privacy_contract=PrivacyContract(
|
|
153
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
154
|
+
human_review=False
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
warning = validator.generate_warning_message(manifest, {})
|
|
159
|
+
assert warning is None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_should_quarantine_untrusted():
|
|
163
|
+
"""Test quarantine decision for untrusted agents."""
|
|
164
|
+
validator = SecurityValidator()
|
|
165
|
+
manifest = CapabilityManifest(
|
|
166
|
+
agent_id="untrusted-agent",
|
|
167
|
+
trust_level=TrustLevel.UNTRUSTED,
|
|
168
|
+
capabilities=AgentCapabilities(),
|
|
169
|
+
privacy_contract=PrivacyContract(
|
|
170
|
+
retention=RetentionPolicy.EPHEMERAL
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert validator.should_quarantine(manifest)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_should_quarantine_no_reversibility_permanent():
|
|
178
|
+
"""Test quarantine for no reversibility and permanent storage."""
|
|
179
|
+
validator = SecurityValidator()
|
|
180
|
+
manifest = CapabilityManifest(
|
|
181
|
+
agent_id="risky-agent",
|
|
182
|
+
trust_level=TrustLevel.STANDARD,
|
|
183
|
+
capabilities=AgentCapabilities(
|
|
184
|
+
reversibility=ReversibilityLevel.NONE
|
|
185
|
+
),
|
|
186
|
+
privacy_contract=PrivacyContract(
|
|
187
|
+
retention=RetentionPolicy.FOREVER
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
assert validator.should_quarantine(manifest)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_scrub_credit_card():
|
|
195
|
+
"""Test scrubbing credit card from payload."""
|
|
196
|
+
payload = {
|
|
197
|
+
"user": "john",
|
|
198
|
+
"payment": {
|
|
199
|
+
"card": "4532-0151-1283-0366", # Valid test card
|
|
200
|
+
"cvv": "123"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
scrubbed = PrivacyScrubber.scrub_payload(payload)
|
|
205
|
+
|
|
206
|
+
scrubbed_str = str(scrubbed)
|
|
207
|
+
assert "4532-0151-1283-0366" not in scrubbed_str
|
|
208
|
+
assert "[CREDIT_CARD_REDACTED]" in scrubbed_str
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_scrub_ssn():
|
|
212
|
+
"""Test scrubbing SSN from payload."""
|
|
213
|
+
payload = {
|
|
214
|
+
"user": "john",
|
|
215
|
+
"ssn": "123-45-6789"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
scrubbed = PrivacyScrubber.scrub_payload(payload)
|
|
219
|
+
|
|
220
|
+
scrubbed_str = str(scrubbed)
|
|
221
|
+
assert "123-45-6789" not in scrubbed_str
|
|
222
|
+
assert "[SSN_REDACTED]" in scrubbed_str
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Integration tests for the IATP Sidecar.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
from fastapi.testclient import TestClient
|
|
8
|
+
|
|
9
|
+
from iatp.models import (
|
|
10
|
+
AgentCapabilities,
|
|
11
|
+
CapabilityManifest,
|
|
12
|
+
PrivacyContract,
|
|
13
|
+
RetentionPolicy,
|
|
14
|
+
ReversibilityLevel,
|
|
15
|
+
TrustLevel,
|
|
16
|
+
)
|
|
17
|
+
from iatp.sidecar import create_sidecar
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def trusted_manifest():
|
|
22
|
+
"""Create a trusted agent manifest for testing."""
|
|
23
|
+
return CapabilityManifest(
|
|
24
|
+
agent_id="test-trusted-agent",
|
|
25
|
+
agent_version="1.0.0",
|
|
26
|
+
trust_level=TrustLevel.VERIFIED_PARTNER,
|
|
27
|
+
capabilities=AgentCapabilities(
|
|
28
|
+
idempotency=True,
|
|
29
|
+
reversibility=ReversibilityLevel.FULL,
|
|
30
|
+
undo_window="24h",
|
|
31
|
+
sla_latency="1000ms"
|
|
32
|
+
),
|
|
33
|
+
privacy_contract=PrivacyContract(
|
|
34
|
+
retention=RetentionPolicy.EPHEMERAL,
|
|
35
|
+
storage_location="us-east",
|
|
36
|
+
human_review=False
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def untrusted_manifest():
|
|
43
|
+
"""Create an untrusted agent manifest for testing."""
|
|
44
|
+
return CapabilityManifest(
|
|
45
|
+
agent_id="test-untrusted-agent",
|
|
46
|
+
agent_version="0.1.0",
|
|
47
|
+
trust_level=TrustLevel.UNTRUSTED,
|
|
48
|
+
capabilities=AgentCapabilities(
|
|
49
|
+
idempotency=False,
|
|
50
|
+
reversibility=ReversibilityLevel.NONE
|
|
51
|
+
),
|
|
52
|
+
privacy_contract=PrivacyContract(
|
|
53
|
+
retention=RetentionPolicy.FOREVER,
|
|
54
|
+
storage_location="unknown",
|
|
55
|
+
human_review=True
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_health_check(trusted_manifest):
|
|
61
|
+
"""Test the health check endpoint."""
|
|
62
|
+
sidecar = create_sidecar(
|
|
63
|
+
agent_url="http://localhost:9999",
|
|
64
|
+
manifest=trusted_manifest
|
|
65
|
+
)
|
|
66
|
+
client = TestClient(sidecar.app)
|
|
67
|
+
|
|
68
|
+
response = client.get("/health")
|
|
69
|
+
assert response.status_code == 200
|
|
70
|
+
data = response.json()
|
|
71
|
+
assert data["status"] == "healthy"
|
|
72
|
+
assert data["agent_id"] == "test-trusted-agent"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_get_manifest(trusted_manifest):
|
|
76
|
+
"""Test getting the capability manifest."""
|
|
77
|
+
sidecar = create_sidecar(
|
|
78
|
+
agent_url="http://localhost:9999",
|
|
79
|
+
manifest=trusted_manifest
|
|
80
|
+
)
|
|
81
|
+
client = TestClient(sidecar.app)
|
|
82
|
+
|
|
83
|
+
response = client.get("/.well-known/agent-manifest")
|
|
84
|
+
assert response.status_code == 200
|
|
85
|
+
data = response.json()
|
|
86
|
+
assert data["agent_id"] == "test-trusted-agent"
|
|
87
|
+
assert data["trust_level"] == "verified_partner"
|
|
88
|
+
assert data["capabilities"]["idempotency"] is True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_invalid_json_payload(trusted_manifest):
|
|
92
|
+
"""Test that invalid JSON is rejected."""
|
|
93
|
+
sidecar = create_sidecar(
|
|
94
|
+
agent_url="http://localhost:9999",
|
|
95
|
+
manifest=trusted_manifest
|
|
96
|
+
)
|
|
97
|
+
client = TestClient(sidecar.app)
|
|
98
|
+
|
|
99
|
+
response = client.post(
|
|
100
|
+
"/proxy",
|
|
101
|
+
content="invalid json",
|
|
102
|
+
headers={"Content-Type": "application/json"}
|
|
103
|
+
)
|
|
104
|
+
assert response.status_code == 400
|
|
105
|
+
data = response.json()
|
|
106
|
+
assert "Invalid JSON" in data["error"]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_blocked_credit_card_permanent_storage(untrusted_manifest):
|
|
110
|
+
"""Test that credit cards are blocked for permanent storage."""
|
|
111
|
+
sidecar = create_sidecar(
|
|
112
|
+
agent_url="http://localhost:9999",
|
|
113
|
+
manifest=untrusted_manifest
|
|
114
|
+
)
|
|
115
|
+
client = TestClient(sidecar.app)
|
|
116
|
+
|
|
117
|
+
response = client.post(
|
|
118
|
+
"/proxy",
|
|
119
|
+
json={
|
|
120
|
+
"task": "purchase",
|
|
121
|
+
"card": "4532-0151-1283-0366" # Valid test card
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
assert response.status_code == 403
|
|
125
|
+
data = response.json()
|
|
126
|
+
assert "Privacy Violation" in data["error"]
|
|
127
|
+
assert data["blocked"] is True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_warning_without_override(untrusted_manifest):
|
|
131
|
+
"""Test that untrusted agents trigger warnings."""
|
|
132
|
+
sidecar = create_sidecar(
|
|
133
|
+
agent_url="http://localhost:9999",
|
|
134
|
+
manifest=untrusted_manifest
|
|
135
|
+
)
|
|
136
|
+
client = TestClient(sidecar.app)
|
|
137
|
+
|
|
138
|
+
response = client.post(
|
|
139
|
+
"/proxy",
|
|
140
|
+
json={"task": "test", "data": {}}
|
|
141
|
+
)
|
|
142
|
+
assert response.status_code == 449
|
|
143
|
+
data = response.json()
|
|
144
|
+
assert "warning" in data
|
|
145
|
+
assert "requires_override" in data
|
|
146
|
+
assert data["requires_override"] is True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_trace_id_injection(trusted_manifest):
|
|
150
|
+
"""Test that trace IDs are properly handled."""
|
|
151
|
+
sidecar = create_sidecar(
|
|
152
|
+
agent_url="http://localhost:9999",
|
|
153
|
+
manifest=trusted_manifest
|
|
154
|
+
)
|
|
155
|
+
client = TestClient(sidecar.app)
|
|
156
|
+
|
|
157
|
+
# Provide a custom trace ID
|
|
158
|
+
custom_trace_id = "custom-trace-123"
|
|
159
|
+
response = client.post(
|
|
160
|
+
"/proxy",
|
|
161
|
+
json={"task": "test"},
|
|
162
|
+
headers={"X-Agent-Trace-ID": custom_trace_id}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Even if backend fails, trace ID should be in error response
|
|
166
|
+
data = response.json()
|
|
167
|
+
assert "trace_id" in data
|