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,328 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Tests for the Reputation Engine."""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
_nexus_parent = os.path.join(os.path.dirname(__file__), "..", "..")
|
|
12
|
+
if _nexus_parent not in sys.path:
|
|
13
|
+
sys.path.insert(0, _nexus_parent)
|
|
14
|
+
|
|
15
|
+
from nexus.reputation import ReputationEngine, ReputationHistory, TrustScore, TrustTier, SlashEvent
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestTrustTier:
|
|
19
|
+
"""Tests for TrustScore.get_tier() classmethod."""
|
|
20
|
+
|
|
21
|
+
def test_verified_partner_at_900(self):
|
|
22
|
+
assert TrustScore.get_tier(900) == TrustTier.VERIFIED_PARTNER
|
|
23
|
+
|
|
24
|
+
def test_verified_partner_at_1000(self):
|
|
25
|
+
assert TrustScore.get_tier(1000) == TrustTier.VERIFIED_PARTNER
|
|
26
|
+
|
|
27
|
+
def test_trusted_at_700(self):
|
|
28
|
+
assert TrustScore.get_tier(700) == TrustTier.TRUSTED
|
|
29
|
+
|
|
30
|
+
def test_trusted_at_899(self):
|
|
31
|
+
assert TrustScore.get_tier(899) == TrustTier.TRUSTED
|
|
32
|
+
|
|
33
|
+
def test_standard_at_500(self):
|
|
34
|
+
assert TrustScore.get_tier(500) == TrustTier.STANDARD
|
|
35
|
+
|
|
36
|
+
def test_standard_at_699(self):
|
|
37
|
+
assert TrustScore.get_tier(699) == TrustTier.STANDARD
|
|
38
|
+
|
|
39
|
+
def test_probationary_at_300(self):
|
|
40
|
+
assert TrustScore.get_tier(300) == TrustTier.PROBATIONARY
|
|
41
|
+
|
|
42
|
+
def test_probationary_at_499(self):
|
|
43
|
+
assert TrustScore.get_tier(499) == TrustTier.PROBATIONARY
|
|
44
|
+
|
|
45
|
+
def test_untrusted_at_299(self):
|
|
46
|
+
assert TrustScore.get_tier(299) == TrustTier.UNTRUSTED
|
|
47
|
+
|
|
48
|
+
def test_untrusted_at_zero(self):
|
|
49
|
+
assert TrustScore.get_tier(0) == TrustTier.UNTRUSTED
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestTrustScoreMeetsThreshold:
|
|
53
|
+
"""Tests for TrustScore.meets_threshold()."""
|
|
54
|
+
|
|
55
|
+
def test_meets_when_equal(self):
|
|
56
|
+
ts = TrustScore(
|
|
57
|
+
agent_did="did:nexus:a", total_score=500, tier=TrustTier.STANDARD,
|
|
58
|
+
base_score=400, behavioral_modifier=100, capability_modifier=0,
|
|
59
|
+
)
|
|
60
|
+
assert ts.meets_threshold(500) is True
|
|
61
|
+
|
|
62
|
+
def test_meets_when_above(self):
|
|
63
|
+
ts = TrustScore(
|
|
64
|
+
agent_did="did:nexus:a", total_score=700, tier=TrustTier.TRUSTED,
|
|
65
|
+
base_score=600, behavioral_modifier=100, capability_modifier=0,
|
|
66
|
+
)
|
|
67
|
+
assert ts.meets_threshold(500) is True
|
|
68
|
+
|
|
69
|
+
def test_fails_when_below(self):
|
|
70
|
+
ts = TrustScore(
|
|
71
|
+
agent_did="did:nexus:a", total_score=300, tier=TrustTier.PROBATIONARY,
|
|
72
|
+
base_score=300, behavioral_modifier=0, capability_modifier=0,
|
|
73
|
+
)
|
|
74
|
+
assert ts.meets_threshold(500) is False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestReputationHistory:
|
|
78
|
+
"""Tests for ReputationHistory properties."""
|
|
79
|
+
|
|
80
|
+
def test_success_rate_with_tasks(self):
|
|
81
|
+
h = ReputationHistory(
|
|
82
|
+
agent_did="did:nexus:a", successful_tasks=8, failed_tasks=2, total_tasks=10,
|
|
83
|
+
)
|
|
84
|
+
assert h.success_rate == pytest.approx(0.8)
|
|
85
|
+
|
|
86
|
+
def test_success_rate_no_tasks(self):
|
|
87
|
+
h = ReputationHistory(agent_did="did:nexus:a")
|
|
88
|
+
assert h.success_rate == 0.0
|
|
89
|
+
|
|
90
|
+
def test_dispute_win_rate_with_disputes(self):
|
|
91
|
+
h = ReputationHistory(
|
|
92
|
+
agent_did="did:nexus:a", disputes_won=3, disputes_lost=1,
|
|
93
|
+
)
|
|
94
|
+
assert h.dispute_win_rate == pytest.approx(0.75)
|
|
95
|
+
|
|
96
|
+
def test_dispute_win_rate_no_disputes(self):
|
|
97
|
+
h = ReputationHistory(agent_did="did:nexus:a")
|
|
98
|
+
assert h.dispute_win_rate == 0.5 # neutral default
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TestCalculateTrustScore:
|
|
102
|
+
"""Tests for ReputationEngine.calculate_trust_score()."""
|
|
103
|
+
|
|
104
|
+
def test_unknown_verification_gives_low_base(self, reputation_engine):
|
|
105
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
106
|
+
score = reputation_engine.calculate_trust_score("unknown", history)
|
|
107
|
+
assert score.base_score == 100
|
|
108
|
+
|
|
109
|
+
def test_registered_verification(self, reputation_engine):
|
|
110
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
111
|
+
score = reputation_engine.calculate_trust_score("registered", history)
|
|
112
|
+
assert score.base_score == 400
|
|
113
|
+
|
|
114
|
+
def test_verified_verification(self, reputation_engine):
|
|
115
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
116
|
+
score = reputation_engine.calculate_trust_score("verified", history)
|
|
117
|
+
assert score.base_score == 650
|
|
118
|
+
|
|
119
|
+
def test_verified_partner_verification(self, reputation_engine):
|
|
120
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
121
|
+
score = reputation_engine.calculate_trust_score("verified_partner", history)
|
|
122
|
+
assert score.base_score == 800
|
|
123
|
+
|
|
124
|
+
def test_successful_tasks_increase_score(self, reputation_engine):
|
|
125
|
+
history = ReputationHistory(
|
|
126
|
+
agent_did="did:nexus:a", successful_tasks=50, total_tasks=50,
|
|
127
|
+
)
|
|
128
|
+
score = reputation_engine.calculate_trust_score("registered", history)
|
|
129
|
+
assert score.behavioral_modifier > 0
|
|
130
|
+
|
|
131
|
+
def test_failed_tasks_decrease_score(self, reputation_engine):
|
|
132
|
+
history = ReputationHistory(
|
|
133
|
+
agent_did="did:nexus:a", failed_tasks=10, total_tasks=10,
|
|
134
|
+
)
|
|
135
|
+
score = reputation_engine.calculate_trust_score("registered", history)
|
|
136
|
+
assert score.behavioral_modifier < 0
|
|
137
|
+
|
|
138
|
+
def test_disputes_lost_reduce_score(self, reputation_engine):
|
|
139
|
+
history = ReputationHistory(
|
|
140
|
+
agent_did="did:nexus:a", disputes_lost=3,
|
|
141
|
+
)
|
|
142
|
+
score = reputation_engine.calculate_trust_score("registered", history)
|
|
143
|
+
assert score.behavioral_modifier < 0
|
|
144
|
+
|
|
145
|
+
def test_capability_modifier_idempotency(self, reputation_engine):
|
|
146
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
147
|
+
score = reputation_engine.calculate_trust_score(
|
|
148
|
+
"registered", history, capabilities={"idempotency": True},
|
|
149
|
+
)
|
|
150
|
+
assert score.capability_modifier >= 20
|
|
151
|
+
|
|
152
|
+
def test_capability_modifier_reversibility_full(self, reputation_engine):
|
|
153
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
154
|
+
score = reputation_engine.calculate_trust_score(
|
|
155
|
+
"registered", history, capabilities={"reversibility": "full"},
|
|
156
|
+
)
|
|
157
|
+
assert score.capability_modifier >= 50
|
|
158
|
+
|
|
159
|
+
def test_privacy_ephemeral_boost(self, reputation_engine):
|
|
160
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
161
|
+
score = reputation_engine.calculate_trust_score(
|
|
162
|
+
"registered", history, privacy={"retention_policy": "ephemeral"},
|
|
163
|
+
)
|
|
164
|
+
assert score.capability_modifier >= 30
|
|
165
|
+
|
|
166
|
+
def test_score_clamped_to_0_1000(self, reputation_engine):
|
|
167
|
+
# Very bad history
|
|
168
|
+
history = ReputationHistory(
|
|
169
|
+
agent_did="did:nexus:a", failed_tasks=100, total_tasks=100,
|
|
170
|
+
disputes_lost=10, times_slashed=10,
|
|
171
|
+
)
|
|
172
|
+
score = reputation_engine.calculate_trust_score("unknown", history)
|
|
173
|
+
assert 0 <= score.total_score <= 1000
|
|
174
|
+
|
|
175
|
+
def test_score_has_correct_tier(self, reputation_engine):
|
|
176
|
+
history = ReputationHistory(agent_did="did:nexus:a")
|
|
177
|
+
score = reputation_engine.calculate_trust_score("verified_partner", history)
|
|
178
|
+
assert score.tier == TrustScore.get_tier(score.total_score)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestRecordTaskOutcome:
|
|
182
|
+
"""Tests for ReputationEngine.record_task_outcome()."""
|
|
183
|
+
|
|
184
|
+
def test_success_increments(self, reputation_engine):
|
|
185
|
+
h = reputation_engine.record_task_outcome("did:nexus:a", "success")
|
|
186
|
+
assert h.successful_tasks == 1
|
|
187
|
+
assert h.total_tasks == 1
|
|
188
|
+
|
|
189
|
+
def test_failure_increments(self, reputation_engine):
|
|
190
|
+
h = reputation_engine.record_task_outcome("did:nexus:a", "failure")
|
|
191
|
+
assert h.failed_tasks == 1
|
|
192
|
+
assert h.total_tasks == 1
|
|
193
|
+
|
|
194
|
+
def test_partial_increments_both(self, reputation_engine):
|
|
195
|
+
h = reputation_engine.record_task_outcome("did:nexus:a", "partial")
|
|
196
|
+
assert h.successful_tasks == 0.5
|
|
197
|
+
assert h.failed_tasks == 0.5
|
|
198
|
+
assert h.total_tasks == 1
|
|
199
|
+
|
|
200
|
+
def test_multiple_outcomes_accumulate(self, reputation_engine):
|
|
201
|
+
reputation_engine.record_task_outcome("did:nexus:a", "success")
|
|
202
|
+
reputation_engine.record_task_outcome("did:nexus:a", "success")
|
|
203
|
+
h = reputation_engine.record_task_outcome("did:nexus:a", "failure")
|
|
204
|
+
assert h.successful_tasks == 2
|
|
205
|
+
assert h.failed_tasks == 1
|
|
206
|
+
assert h.total_tasks == 3
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestRecordDisputeOutcome:
|
|
210
|
+
"""Tests for ReputationEngine.record_dispute_outcome()."""
|
|
211
|
+
|
|
212
|
+
def test_won_increments(self, reputation_engine):
|
|
213
|
+
h = reputation_engine.record_dispute_outcome("did:nexus:a", "won")
|
|
214
|
+
assert h.disputes_won == 1
|
|
215
|
+
|
|
216
|
+
def test_lost_increments(self, reputation_engine):
|
|
217
|
+
h = reputation_engine.record_dispute_outcome("did:nexus:a", "lost")
|
|
218
|
+
assert h.disputes_lost == 1
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TestSlashReputation:
|
|
222
|
+
"""Tests for ReputationEngine.slash_reputation()."""
|
|
223
|
+
|
|
224
|
+
def test_critical_slash_penalty(self, reputation_engine):
|
|
225
|
+
event = reputation_engine.slash_reputation(
|
|
226
|
+
"did:nexus:a", reason="fraud", severity="critical",
|
|
227
|
+
)
|
|
228
|
+
assert event.score_reduction == 200
|
|
229
|
+
|
|
230
|
+
def test_high_slash_penalty(self, reputation_engine):
|
|
231
|
+
event = reputation_engine.slash_reputation(
|
|
232
|
+
"did:nexus:a", reason="hallucination", severity="high",
|
|
233
|
+
)
|
|
234
|
+
assert event.score_reduction == 100
|
|
235
|
+
|
|
236
|
+
def test_medium_slash_penalty(self, reputation_engine):
|
|
237
|
+
event = reputation_engine.slash_reputation(
|
|
238
|
+
"did:nexus:a", reason="timeout", severity="medium",
|
|
239
|
+
)
|
|
240
|
+
assert event.score_reduction == 50
|
|
241
|
+
|
|
242
|
+
def test_low_slash_penalty(self, reputation_engine):
|
|
243
|
+
event = reputation_engine.slash_reputation(
|
|
244
|
+
"did:nexus:a", reason="policy_violation", severity="low",
|
|
245
|
+
)
|
|
246
|
+
assert event.score_reduction == 25
|
|
247
|
+
|
|
248
|
+
def test_slash_updates_history(self, reputation_engine):
|
|
249
|
+
reputation_engine.slash_reputation("did:nexus:a", reason="fraud", severity="critical")
|
|
250
|
+
h = reputation_engine._get_or_create_history("did:nexus:a")
|
|
251
|
+
assert h.times_slashed == 1
|
|
252
|
+
assert h.total_slash_amount == 200
|
|
253
|
+
|
|
254
|
+
def test_slash_with_evidence(self, reputation_engine):
|
|
255
|
+
event = reputation_engine.slash_reputation(
|
|
256
|
+
"did:nexus:a", reason="fraud", severity="high",
|
|
257
|
+
evidence_hash="ev_hash_123", trace_id="trace_456",
|
|
258
|
+
)
|
|
259
|
+
assert event.evidence_hash == "ev_hash_123"
|
|
260
|
+
assert event.trace_id == "trace_456"
|
|
261
|
+
|
|
262
|
+
def test_slash_broadcast_flag(self, reputation_engine):
|
|
263
|
+
event = reputation_engine.slash_reputation(
|
|
264
|
+
"did:nexus:a", reason="fraud", severity="high", broadcast=False,
|
|
265
|
+
)
|
|
266
|
+
assert event.broadcast_to_network is False
|
|
267
|
+
assert event.broadcast_at is None
|
|
268
|
+
|
|
269
|
+
def test_slash_score_after_not_negative(self, reputation_engine):
|
|
270
|
+
event = reputation_engine.slash_reputation(
|
|
271
|
+
"did:nexus:a", reason="fraud", severity="critical",
|
|
272
|
+
)
|
|
273
|
+
assert event.score_after >= 0
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class TestCheckTrustThreshold:
|
|
277
|
+
"""Tests for ReputationEngine.check_trust_threshold()."""
|
|
278
|
+
|
|
279
|
+
def test_new_agent_below_default_threshold(self, reputation_engine):
|
|
280
|
+
meets, score = reputation_engine.check_trust_threshold("did:nexus:new")
|
|
281
|
+
assert score.total_score == 400 # registered base
|
|
282
|
+
assert meets is False # 400 < 500
|
|
283
|
+
|
|
284
|
+
def test_custom_threshold(self, reputation_engine):
|
|
285
|
+
meets, score = reputation_engine.check_trust_threshold("did:nexus:a", required_score=300)
|
|
286
|
+
assert meets is True # 400 >= 300
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class TestLeaderboard:
|
|
290
|
+
"""Tests for ReputationEngine.get_leaderboard()."""
|
|
291
|
+
|
|
292
|
+
def test_empty_leaderboard(self, reputation_engine):
|
|
293
|
+
assert reputation_engine.get_leaderboard() == []
|
|
294
|
+
|
|
295
|
+
def test_leaderboard_sorted_descending(self, reputation_engine):
|
|
296
|
+
# Populate cache via check_trust_threshold
|
|
297
|
+
reputation_engine.check_trust_threshold("did:nexus:a")
|
|
298
|
+
reputation_engine.check_trust_threshold("did:nexus:b")
|
|
299
|
+
board = reputation_engine.get_leaderboard()
|
|
300
|
+
assert len(board) == 2
|
|
301
|
+
for i in range(len(board) - 1):
|
|
302
|
+
assert board[i].total_score >= board[i + 1].total_score
|
|
303
|
+
|
|
304
|
+
def test_leaderboard_limit(self, reputation_engine):
|
|
305
|
+
for i in range(5):
|
|
306
|
+
reputation_engine.check_trust_threshold(f"did:nexus:agent-{i}")
|
|
307
|
+
board = reputation_engine.get_leaderboard(limit=3)
|
|
308
|
+
assert len(board) == 3
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class TestSlashHistory:
|
|
312
|
+
"""Tests for ReputationEngine.get_slash_history()."""
|
|
313
|
+
|
|
314
|
+
def test_empty_history(self, reputation_engine):
|
|
315
|
+
assert reputation_engine.get_slash_history() == []
|
|
316
|
+
|
|
317
|
+
def test_filter_by_agent(self, reputation_engine):
|
|
318
|
+
reputation_engine.slash_reputation("did:nexus:a", reason="fraud", severity="high")
|
|
319
|
+
reputation_engine.slash_reputation("did:nexus:b", reason="timeout", severity="low")
|
|
320
|
+
events = reputation_engine.get_slash_history(agent_did="did:nexus:a")
|
|
321
|
+
assert len(events) == 1
|
|
322
|
+
assert events[0].agent_did == "did:nexus:a"
|
|
323
|
+
|
|
324
|
+
def test_filter_by_since(self, reputation_engine):
|
|
325
|
+
reputation_engine.slash_reputation("did:nexus:a", reason="fraud", severity="high")
|
|
326
|
+
future = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
327
|
+
events = reputation_engine.get_slash_history(since=future)
|
|
328
|
+
assert len(events) == 0
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Tests for Nexus schema models."""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
_nexus_parent = os.path.join(os.path.dirname(__file__), "..", "..")
|
|
12
|
+
if _nexus_parent not in sys.path:
|
|
13
|
+
sys.path.insert(0, _nexus_parent)
|
|
14
|
+
|
|
15
|
+
from nexus.schemas.manifest import (
|
|
16
|
+
AgentIdentity,
|
|
17
|
+
AgentCapabilities,
|
|
18
|
+
AgentPrivacy,
|
|
19
|
+
MuteRules,
|
|
20
|
+
AgentManifest,
|
|
21
|
+
)
|
|
22
|
+
from nexus.schemas.escrow import EscrowRequest, EscrowReceipt, EscrowStatus
|
|
23
|
+
from nexus.schemas.receipt import JobReceipt, JobCompletionReceipt, SignedReceipt
|
|
24
|
+
from nexus.schemas.compliance import ComplianceRecord, ComplianceAuditReport
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestAgentIdentity:
|
|
28
|
+
"""Tests for AgentIdentity DID format validation."""
|
|
29
|
+
|
|
30
|
+
def test_valid_did(self):
|
|
31
|
+
identity = AgentIdentity(
|
|
32
|
+
did="did:nexus:my-agent", verification_key="ed25519:key123", owner_id="org",
|
|
33
|
+
)
|
|
34
|
+
assert identity.did == "did:nexus:my-agent"
|
|
35
|
+
|
|
36
|
+
def test_invalid_did_prefix(self):
|
|
37
|
+
with pytest.raises(Exception):
|
|
38
|
+
AgentIdentity(
|
|
39
|
+
did="did:other:my-agent", verification_key="ed25519:key123", owner_id="org",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def test_invalid_verification_key(self):
|
|
43
|
+
with pytest.raises(Exception):
|
|
44
|
+
AgentIdentity(
|
|
45
|
+
did="did:nexus:my-agent", verification_key="rsa:key123", owner_id="org",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def test_valid_ed25519_key(self):
|
|
49
|
+
identity = AgentIdentity(
|
|
50
|
+
did="did:nexus:agent", verification_key="ed25519:abc", owner_id="org",
|
|
51
|
+
)
|
|
52
|
+
assert identity.verification_key == "ed25519:abc"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestAgentCapabilities:
|
|
56
|
+
"""Tests for AgentCapabilities constraints."""
|
|
57
|
+
|
|
58
|
+
def test_max_concurrency_min(self):
|
|
59
|
+
with pytest.raises(Exception):
|
|
60
|
+
AgentCapabilities(max_concurrency=0)
|
|
61
|
+
|
|
62
|
+
def test_max_concurrency_max(self):
|
|
63
|
+
with pytest.raises(Exception):
|
|
64
|
+
AgentCapabilities(max_concurrency=1001)
|
|
65
|
+
|
|
66
|
+
def test_max_concurrency_valid(self):
|
|
67
|
+
cap = AgentCapabilities(max_concurrency=500)
|
|
68
|
+
assert cap.max_concurrency == 500
|
|
69
|
+
|
|
70
|
+
def test_sla_latency_min(self):
|
|
71
|
+
with pytest.raises(Exception):
|
|
72
|
+
AgentCapabilities(sla_latency_ms=50) # min 100
|
|
73
|
+
|
|
74
|
+
def test_sla_latency_max(self):
|
|
75
|
+
with pytest.raises(Exception):
|
|
76
|
+
AgentCapabilities(sla_latency_ms=500000) # max 300000
|
|
77
|
+
|
|
78
|
+
def test_sla_latency_valid(self):
|
|
79
|
+
cap = AgentCapabilities(sla_latency_ms=10000)
|
|
80
|
+
assert cap.sla_latency_ms == 10000
|
|
81
|
+
|
|
82
|
+
def test_defaults(self):
|
|
83
|
+
cap = AgentCapabilities()
|
|
84
|
+
assert cap.max_concurrency == 10
|
|
85
|
+
assert cap.idempotency is False
|
|
86
|
+
assert cap.reversibility == "partial"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TestAgentManifest:
|
|
90
|
+
"""Tests for AgentManifest creation."""
|
|
91
|
+
|
|
92
|
+
def test_valid_manifest(self, sample_manifest):
|
|
93
|
+
assert sample_manifest.identity.did == "did:nexus:test-agent-v1"
|
|
94
|
+
assert sample_manifest.verification_level == "registered"
|
|
95
|
+
|
|
96
|
+
def test_is_attestation_valid_false_by_default(self, sample_manifest):
|
|
97
|
+
assert sample_manifest.is_attestation_valid() is False
|
|
98
|
+
|
|
99
|
+
def test_is_attestation_valid_when_set(self):
|
|
100
|
+
m = AgentManifest(
|
|
101
|
+
identity=AgentIdentity(
|
|
102
|
+
did="did:nexus:a", verification_key="ed25519:k", owner_id="org",
|
|
103
|
+
),
|
|
104
|
+
attestation_signature="sig_123",
|
|
105
|
+
attestation_expires=datetime.now(timezone.utc) + timedelta(hours=24),
|
|
106
|
+
)
|
|
107
|
+
assert m.is_attestation_valid() is True
|
|
108
|
+
|
|
109
|
+
def test_is_attestation_expired(self):
|
|
110
|
+
m = AgentManifest(
|
|
111
|
+
identity=AgentIdentity(
|
|
112
|
+
did="did:nexus:a", verification_key="ed25519:k", owner_id="org",
|
|
113
|
+
),
|
|
114
|
+
attestation_signature="sig_123",
|
|
115
|
+
attestation_expires=datetime.now(timezone.utc) - timedelta(hours=1),
|
|
116
|
+
)
|
|
117
|
+
assert m.is_attestation_valid() is False
|
|
118
|
+
|
|
119
|
+
def test_to_iatp_manifest(self, sample_manifest):
|
|
120
|
+
iatp = sample_manifest.to_iatp_manifest()
|
|
121
|
+
assert iatp["$schema"].startswith("https://")
|
|
122
|
+
assert iatp["identity"]["verification_key"] == "ed25519:testkey123abc"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestEscrowRequestConstraints:
|
|
126
|
+
"""Tests for EscrowRequest validation."""
|
|
127
|
+
|
|
128
|
+
def test_credits_must_be_positive(self):
|
|
129
|
+
with pytest.raises(Exception):
|
|
130
|
+
EscrowRequest(
|
|
131
|
+
requester_did="did:nexus:a", provider_did="did:nexus:b",
|
|
132
|
+
task_hash="h", credits=-1,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def test_credits_max_10000(self):
|
|
136
|
+
with pytest.raises(Exception):
|
|
137
|
+
EscrowRequest(
|
|
138
|
+
requester_did="did:nexus:a", provider_did="did:nexus:b",
|
|
139
|
+
task_hash="h", credits=10001,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def test_timeout_min_60(self):
|
|
143
|
+
with pytest.raises(Exception):
|
|
144
|
+
EscrowRequest(
|
|
145
|
+
requester_did="did:nexus:a", provider_did="did:nexus:b",
|
|
146
|
+
task_hash="h", credits=100, timeout_seconds=30,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def test_timeout_max_86400(self):
|
|
150
|
+
with pytest.raises(Exception):
|
|
151
|
+
EscrowRequest(
|
|
152
|
+
requester_did="did:nexus:a", provider_did="did:nexus:b",
|
|
153
|
+
task_hash="h", credits=100, timeout_seconds=100000,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestJobReceipt:
|
|
158
|
+
"""Tests for JobReceipt.compute_hash()."""
|
|
159
|
+
|
|
160
|
+
def test_compute_hash_deterministic(self):
|
|
161
|
+
now = datetime(2024, 1, 1, 12, 0, 0)
|
|
162
|
+
r1 = JobReceipt(
|
|
163
|
+
receipt_id="r1", task_id="t1", requester_did="did:nexus:a",
|
|
164
|
+
provider_did="did:nexus:b", task_hash="hash1", created_at=now,
|
|
165
|
+
)
|
|
166
|
+
r2 = JobReceipt(
|
|
167
|
+
receipt_id="r1", task_id="t1", requester_did="did:nexus:a",
|
|
168
|
+
provider_did="did:nexus:b", task_hash="hash1", created_at=now,
|
|
169
|
+
)
|
|
170
|
+
assert r1.compute_hash() == r2.compute_hash()
|
|
171
|
+
|
|
172
|
+
def test_compute_hash_differs_for_different_data(self):
|
|
173
|
+
now = datetime(2024, 1, 1, 12, 0, 0)
|
|
174
|
+
r1 = JobReceipt(
|
|
175
|
+
receipt_id="r1", task_id="t1", requester_did="did:nexus:a",
|
|
176
|
+
provider_did="did:nexus:b", task_hash="hash1", created_at=now,
|
|
177
|
+
)
|
|
178
|
+
r2 = JobReceipt(
|
|
179
|
+
receipt_id="r2", task_id="t1", requester_did="did:nexus:a",
|
|
180
|
+
provider_did="did:nexus:b", task_hash="hash1", created_at=now,
|
|
181
|
+
)
|
|
182
|
+
assert r1.compute_hash() != r2.compute_hash()
|
|
183
|
+
|
|
184
|
+
def test_hash_is_sha256(self):
|
|
185
|
+
r = JobReceipt(
|
|
186
|
+
receipt_id="r1", task_id="t1", requester_did="did:nexus:a",
|
|
187
|
+
provider_did="did:nexus:b", task_hash="hash1",
|
|
188
|
+
)
|
|
189
|
+
h = r.compute_hash()
|
|
190
|
+
assert len(h) == 64 # SHA-256 hex digest length
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class TestSignedReceipt:
|
|
194
|
+
"""Tests for SignedReceipt methods."""
|
|
195
|
+
|
|
196
|
+
def _make_signed_receipt(self, req_sig=None, prov_sig=None, nexus_witnessed=False, nexus_sig=None):
|
|
197
|
+
receipt = JobCompletionReceipt(
|
|
198
|
+
receipt_id="r1", task_id="t1", requester_did="did:nexus:a",
|
|
199
|
+
provider_did="did:nexus:b", task_hash="h", outcome="success",
|
|
200
|
+
duration_ms=100,
|
|
201
|
+
)
|
|
202
|
+
return SignedReceipt(
|
|
203
|
+
receipt=receipt,
|
|
204
|
+
receipt_hash=receipt.compute_hash(),
|
|
205
|
+
requester_signature=req_sig,
|
|
206
|
+
provider_signature=prov_sig,
|
|
207
|
+
nexus_witnessed=nexus_witnessed,
|
|
208
|
+
nexus_signature=nexus_sig,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def test_is_fully_signed_both(self):
|
|
212
|
+
sr = self._make_signed_receipt(req_sig="sig_r", prov_sig="sig_p")
|
|
213
|
+
assert sr.is_fully_signed() is True
|
|
214
|
+
|
|
215
|
+
def test_is_not_fully_signed_missing_provider(self):
|
|
216
|
+
sr = self._make_signed_receipt(req_sig="sig_r")
|
|
217
|
+
assert sr.is_fully_signed() is False
|
|
218
|
+
|
|
219
|
+
def test_is_not_fully_signed_missing_requester(self):
|
|
220
|
+
sr = self._make_signed_receipt(prov_sig="sig_p")
|
|
221
|
+
assert sr.is_fully_signed() is False
|
|
222
|
+
|
|
223
|
+
def test_is_nexus_witnessed(self):
|
|
224
|
+
sr = self._make_signed_receipt(nexus_witnessed=True, nexus_sig="sig_n")
|
|
225
|
+
assert sr.is_nexus_witnessed() is True
|
|
226
|
+
|
|
227
|
+
def test_is_not_nexus_witnessed_no_sig(self):
|
|
228
|
+
sr = self._make_signed_receipt(nexus_witnessed=True, nexus_sig=None)
|
|
229
|
+
assert sr.is_nexus_witnessed() is False
|
|
230
|
+
|
|
231
|
+
def test_is_not_nexus_witnessed_flag_false(self):
|
|
232
|
+
sr = self._make_signed_receipt(nexus_witnessed=False, nexus_sig="sig_n")
|
|
233
|
+
assert sr.is_nexus_witnessed() is False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class TestComplianceRecord:
|
|
237
|
+
"""Tests for ComplianceRecord.compute_hash()."""
|
|
238
|
+
|
|
239
|
+
def test_compute_hash_deterministic(self):
|
|
240
|
+
now = datetime(2024, 6, 1, 12, 0, 0)
|
|
241
|
+
r1 = ComplianceRecord(event_id="e1", event_type="agent_registered", timestamp=now)
|
|
242
|
+
r2 = ComplianceRecord(event_id="e1", event_type="agent_registered", timestamp=now)
|
|
243
|
+
assert r1.compute_hash() == r2.compute_hash()
|
|
244
|
+
|
|
245
|
+
def test_compute_hash_excludes_signature(self):
|
|
246
|
+
now = datetime(2024, 6, 1, 12, 0, 0)
|
|
247
|
+
r1 = ComplianceRecord(event_id="e1", event_type="agent_registered", timestamp=now)
|
|
248
|
+
r2 = ComplianceRecord(
|
|
249
|
+
event_id="e1", event_type="agent_registered", timestamp=now, signature="sig",
|
|
250
|
+
)
|
|
251
|
+
assert r1.compute_hash() == r2.compute_hash()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class TestEscrowReceiptMethods:
|
|
255
|
+
"""Tests for EscrowReceipt.is_expired() and is_active()."""
|
|
256
|
+
|
|
257
|
+
def _make_receipt(self, status=EscrowStatus.PENDING, expires_delta_hours=1):
|
|
258
|
+
return EscrowReceipt(
|
|
259
|
+
escrow_id="e1",
|
|
260
|
+
request=EscrowRequest(
|
|
261
|
+
requester_did="did:nexus:a", provider_did="did:nexus:b",
|
|
262
|
+
task_hash="h", credits=100,
|
|
263
|
+
),
|
|
264
|
+
status=status,
|
|
265
|
+
expires_at=datetime.now(timezone.utc) + timedelta(hours=expires_delta_hours),
|
|
266
|
+
requester_signature="sig",
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def test_not_expired(self):
|
|
270
|
+
r = self._make_receipt(expires_delta_hours=1)
|
|
271
|
+
assert r.is_expired() is False
|
|
272
|
+
|
|
273
|
+
def test_expired(self):
|
|
274
|
+
r = self._make_receipt(expires_delta_hours=-1)
|
|
275
|
+
assert r.is_expired() is True
|
|
276
|
+
|
|
277
|
+
def test_active_pending(self):
|
|
278
|
+
r = self._make_receipt(status=EscrowStatus.PENDING)
|
|
279
|
+
assert r.is_active() is True
|
|
280
|
+
|
|
281
|
+
def test_active_active(self):
|
|
282
|
+
r = self._make_receipt(status=EscrowStatus.ACTIVE)
|
|
283
|
+
assert r.is_active() is True
|
|
284
|
+
|
|
285
|
+
def test_active_awaiting(self):
|
|
286
|
+
r = self._make_receipt(status=EscrowStatus.AWAITING_VALIDATION)
|
|
287
|
+
assert r.is_active() is True
|
|
288
|
+
|
|
289
|
+
def test_not_active_released(self):
|
|
290
|
+
r = self._make_receipt(status=EscrowStatus.RELEASED)
|
|
291
|
+
assert r.is_active() is False
|
|
292
|
+
|
|
293
|
+
def test_not_active_refunded(self):
|
|
294
|
+
r = self._make_receipt(status=EscrowStatus.REFUNDED)
|
|
295
|
+
assert r.is_active() is False
|