89 lines
3.9 KiB
Python
89 lines
3.9 KiB
Python
"""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
|
|
)
|