"""Determinism and idempotency of the decision function (canon A3, G9).""" from __future__ import annotations import copy import pytest from fixture_loading import decision_cases from emergent_security.decide import decide from emergent_security.delegation import Delegation from emergent_security.memory import MemoryEntry from emergent_security.model import Request from emergent_security.tenant import TenantContext CASES = decision_cases() def _decide(request_dict, context, envelope, scales, tenant, registry_order=1): registry = [Delegation.from_dict(d) for d in context.get("registry", [])][::registry_order] return decide( Request.from_dict(request_dict), envelope=envelope, scales=scales, registry=registry, tenant=tenant, memory=[MemoryEntry.from_dict(m) for m in context.get("memory", [])], ) @pytest.mark.parametrize(("request_dict", "context", "expect"), CASES) def test_same_inputs_same_decision(request_dict, context, expect, default_envelope, scales, tenant_context_from): tenant = tenant_context_from(context) first = _decide(request_dict, context, default_envelope, scales, tenant) second = _decide(request_dict, context, default_envelope, scales, tenant) assert first == second @pytest.mark.parametrize(("request_dict", "context", "expect"), CASES) def test_inputs_are_not_mutated(request_dict, context, expect, default_envelope, scales, tenant_context_from): snapshot = copy.deepcopy((request_dict, context)) _decide(request_dict, context, default_envelope, scales, tenant_context_from(context)) assert (request_dict, context) == snapshot @pytest.mark.parametrize(("request_dict", "context", "expect"), CASES) def test_registry_order_does_not_matter(request_dict, context, expect, default_envelope, scales, tenant_context_from): tenant = tenant_context_from(context) forward = _decide(request_dict, context, default_envelope, scales, tenant, registry_order=1) backward = _decide(request_dict, context, default_envelope, scales, tenant, registry_order=-1) assert forward == backward def test_most_specific_registry_delegation_wins(default_envelope, scales): request = { "request_id": "r", "principal": "agent://a", "decision_class": "git.merge_protected", "scope": {"level": "L0", "temperature": "hot", "phase": "build", "tenant": "none", "repository": "factory/billing", "environment": "dev"}, "risk": {}, "attributes": {}, "evidence": [], } broad = {"id": "delegation://broad", "decision_class": "git.merge_protected", "scope": {}, "mode": "autonomous", "risk_envelope": {}} narrow = {"id": "delegation://narrow", "decision_class": "git.merge_protected", "scope": {"repository": "factory/billing"}, "mode": "autonomous", "risk_envelope": {}} for order in ([broad, narrow], [narrow, broad]): decision = _decide(request, {"registry": order}, default_envelope, scales, None) assert decision.source == "delegation://narrow" @pytest.mark.parametrize( ("request_dict", "context", "expect"), [c for c in CASES if "tenant_governance" not in c.values[1] and "platform_envelope" in c.values[1]], ) def test_auto_mode_equals_no_tenant_configuration(request_dict, context, expect, default_envelope, scales, tenant_context_from): """G7: `auto` mode yields the same decision as having no tenant governance at all.""" from emergent_security.tenant import TenantGovernance without = tenant_context_from(context) auto = TenantContext( platform=without.platform, governance=TenantGovernance.from_dict( {"tenant": without.platform.tenant, "mode": "auto", "bounded_by": without.platform.id, "established_by": "human://acme/admin-1", "on_deadline": "escalate_to_next_admin"} ), ) assert _decide(request_dict, context, default_envelope, scales, without) == _decide( request_dict, context, default_envelope, scales, auto )