78 lines
3.2 KiB
Python
78 lines
3.2 KiB
Python
"""Tenant governance only narrows the platform tenant envelope (canon C14, G7)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from fixture_loading import ENVELOPES, FIXTURES, deep_merge, load_yaml
|
|
|
|
from emergent_security.tenant import PlatformTenantEnvelope, effective_ceilings, validate_tenant_governance
|
|
|
|
SPEC = load_yaml(FIXTURES / "tenants" / "governance-validation.yaml")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def platform() -> PlatformTenantEnvelope:
|
|
return PlatformTenantEnvelope.from_dict(load_yaml(ENVELOPES / SPEC["platform_envelope"]))
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def base_governance() -> dict:
|
|
return load_yaml(ENVELOPES / SPEC["base"])
|
|
|
|
|
|
@pytest.mark.parametrize("case", SPEC["cases"], ids=[c["name"] for c in SPEC["cases"]])
|
|
def test_governance_validation(case, platform, base_governance, scales):
|
|
errors = validate_tenant_governance(deep_merge(base_governance, case["patch"]), platform, scales)
|
|
if case["error"] is None:
|
|
assert errors == []
|
|
else:
|
|
assert any(case["error"] in error for error in errors), errors
|
|
|
|
|
|
def test_platform_example_is_valid(platform, scales):
|
|
assert platform.validate(scales) == []
|
|
|
|
|
|
def test_platform_delegations_must_be_capabilities(scales):
|
|
broken = PlatformTenantEnvelope.from_dict(
|
|
{"id": "envelope://x", "tenant": "tenant://x", "capabilities": ["code.edit"],
|
|
"delegated": {"iam.change": {"mode": "autonomous"}}, "ceilings": {}, "prohibitions": []}
|
|
)
|
|
assert any("delegated class outside capabilities: iam.change" in e for e in broken.validate(scales))
|
|
|
|
|
|
def test_platform_cannot_both_delegate_and_prohibit(scales):
|
|
broken = PlatformTenantEnvelope.from_dict(
|
|
{"id": "envelope://x", "tenant": "tenant://x", "capabilities": ["code.edit"],
|
|
"delegated": {"code.edit": {"mode": "autonomous"}}, "ceilings": {}, "prohibitions": ["code.edit"]}
|
|
)
|
|
assert any("both delegated and prohibited: code.edit" in e for e in broken.validate(scales))
|
|
|
|
|
|
def test_effective_ceiling_is_the_narrower_value(platform, base_governance, scales):
|
|
from emergent_security.tenant import TenantGovernance
|
|
|
|
effective = effective_ceilings(platform, TenantGovernance.from_dict(base_governance), scales)
|
|
assert effective["blast_radius"] == "repository" # tenant narrowed
|
|
assert effective["data_sensitivity"] == "personal" # platform value kept
|
|
|
|
|
|
def test_platform_widening_does_not_erase_tenant_restrictions(platform, base_governance, scales):
|
|
from dataclasses import replace
|
|
|
|
from emergent_security.tenant import TenantGovernance
|
|
|
|
wider = replace(platform, ceilings={**platform.ceilings, "blast_radius": "platform"})
|
|
effective = effective_ceilings(wider, TenantGovernance.from_dict(base_governance), scales)
|
|
assert effective["blast_radius"] == "repository"
|
|
|
|
|
|
def test_platform_narrowing_propagates_immediately(platform, base_governance, scales):
|
|
from dataclasses import replace
|
|
|
|
from emergent_security.tenant import TenantGovernance
|
|
|
|
narrower = replace(platform, ceilings={**platform.ceilings, "blast_radius": "task"})
|
|
effective = effective_ceilings(narrower, TenantGovernance.from_dict(base_governance), scales)
|
|
assert effective["blast_radius"] == "task"
|