feat: add institutional governance and recovery contracts
This commit is contained in:
@@ -0,0 +1,816 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
from datetime import UTC, datetime, timedelta
|
||||
import unittest
|
||||
|
||||
from govoplan_core.core.automation import ActionExecutionRequest, AutomationInvocation
|
||||
from govoplan_core.core.events import PlatformEvent
|
||||
from govoplan_core.core.external_references import ExternalObjectReference
|
||||
from govoplan_core.core.institutional import (
|
||||
ActorRepresentationReference,
|
||||
DecisionEffectReference,
|
||||
EvidenceReference,
|
||||
ExternalSourceReference,
|
||||
FormDefinition,
|
||||
FormFieldDefinition,
|
||||
FormalDecision,
|
||||
GovernedContextEnvelope,
|
||||
InformationGovernanceReference,
|
||||
InstitutionalContextError,
|
||||
InstitutionalReference,
|
||||
LegalBasisReference,
|
||||
MandateDefinition,
|
||||
MandateResolution,
|
||||
MandateResolutionRequest,
|
||||
PartyRepresentation,
|
||||
PartySubjectReference,
|
||||
ProcedureParty,
|
||||
ServiceBinding,
|
||||
ServiceAvailabilityRequirement,
|
||||
ServiceDefinition,
|
||||
ServiceLaunchRequest,
|
||||
ServiceLaunchResult,
|
||||
TemporalRevision,
|
||||
derive_service_restriction,
|
||||
resolve_mandate_candidates,
|
||||
revise_procedure_party,
|
||||
revise_formal_decision,
|
||||
revise_mandate_definition,
|
||||
revoke_party_representation,
|
||||
)
|
||||
|
||||
|
||||
NOW = datetime(2026, 8, 1, 10, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
def _reference(
|
||||
kind: str,
|
||||
object_id: str,
|
||||
*,
|
||||
tenant_id: str = "tenant-1",
|
||||
) -> InstitutionalReference:
|
||||
owners = {
|
||||
"institution": "tenancy",
|
||||
"organization_unit": "organizations",
|
||||
"function": "organizations",
|
||||
"function_assignment": "idm",
|
||||
"mandate": "cases",
|
||||
"jurisdiction": "cases",
|
||||
"service": "portal",
|
||||
"form": "forms",
|
||||
"form_submission": "forms_runtime",
|
||||
"case": "cases",
|
||||
"party": "cases",
|
||||
"decision": "committee",
|
||||
"work_item": "workflow_engine",
|
||||
"workflow": "workflow_engine",
|
||||
"approval": "workflow_engine",
|
||||
"record": "audit",
|
||||
}
|
||||
return InstitutionalReference(
|
||||
kind=kind,
|
||||
owner_module=owners[kind],
|
||||
object_id=object_id,
|
||||
tenant_id=tenant_id,
|
||||
version="3",
|
||||
valid_at=NOW,
|
||||
label=f"Protected label for {object_id}",
|
||||
)
|
||||
|
||||
|
||||
def _context() -> GovernedContextEnvelope:
|
||||
external = ExternalObjectReference(
|
||||
system="gis.example",
|
||||
object_type="administrative_area",
|
||||
object_id="area-7",
|
||||
maturity="read",
|
||||
authority_mode="external_mirror",
|
||||
canonical_url="https://gis.example/areas/7",
|
||||
observed_at=NOW,
|
||||
)
|
||||
return GovernedContextEnvelope(
|
||||
tenant_id="tenant-1",
|
||||
temporal=TemporalRevision(
|
||||
revision="7",
|
||||
valid_from=NOW - timedelta(days=1),
|
||||
valid_to=NOW + timedelta(days=1),
|
||||
recorded_at=NOW,
|
||||
change_reason="Case state changed.",
|
||||
),
|
||||
actor=ActorRepresentationReference(
|
||||
tenant_id="tenant-1",
|
||||
account_id="account-1",
|
||||
identity_id="identity-1",
|
||||
represented_function_ref=_reference("function", "function-1"),
|
||||
function_assignment_ref=_reference(
|
||||
"function_assignment", "assignment-1"
|
||||
),
|
||||
mandate_ref=_reference("mandate", "mandate-1"),
|
||||
),
|
||||
institution_ref=_reference("institution", "institution-1"),
|
||||
organization_unit_ref=_reference("organization_unit", "unit-1"),
|
||||
function_ref=_reference("function", "function-1"),
|
||||
mandate_ref=_reference("mandate", "mandate-1"),
|
||||
jurisdiction_refs=(_reference("jurisdiction", "jurisdiction-1"),),
|
||||
service_ref=_reference("service", "service-1"),
|
||||
case_ref=_reference("case", "case-1"),
|
||||
party_refs=(_reference("party", "party-1"),),
|
||||
work_item_ref=_reference("work_item", "work-item-1"),
|
||||
workflow_ref=_reference("workflow", "workflow-1"),
|
||||
approval_refs=(_reference("approval", "approval-1"),),
|
||||
decision_ref=_reference("decision", "decision-1"),
|
||||
record_refs=(_reference("record", "record-1"),),
|
||||
legal_bases=(
|
||||
LegalBasisReference(
|
||||
kind="law",
|
||||
authority="Example legislature",
|
||||
reference="law-1/section-3",
|
||||
version="2026-01",
|
||||
effective_from=NOW - timedelta(days=30),
|
||||
title="Protected legal title",
|
||||
inspection_url="https://law.example/section-3",
|
||||
),
|
||||
),
|
||||
evidence=(
|
||||
EvidenceReference(
|
||||
kind="document",
|
||||
owner_module="files",
|
||||
evidence_id="file-1",
|
||||
tenant_id="tenant-1",
|
||||
version="4",
|
||||
checksum="sha256:abcd",
|
||||
captured_at=NOW,
|
||||
inspection_url="https://govoplan.example/files/file-1",
|
||||
),
|
||||
),
|
||||
information_governance=InformationGovernanceReference(
|
||||
classification="confidential",
|
||||
purposes=("case decision",),
|
||||
legal_basis_refs=("law-1/section-3",),
|
||||
retention_policy_ref="records:case-default",
|
||||
disclosure_state="restricted",
|
||||
),
|
||||
external_sources=(
|
||||
ExternalSourceReference(
|
||||
reference=external,
|
||||
authority_mode="external_mirror",
|
||||
maturity="read",
|
||||
freshness_state="current",
|
||||
health_state="ok",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class InstitutionalContractTests(unittest.TestCase):
|
||||
def test_context_round_trip_preserves_versioned_references(self) -> None:
|
||||
context = _context()
|
||||
|
||||
restored = GovernedContextEnvelope.from_mapping(
|
||||
context.to_dict(include_inspection=True)
|
||||
)
|
||||
|
||||
self.assertEqual(context.tenant_id, restored.tenant_id)
|
||||
self.assertEqual("mandate-1", restored.mandate_ref.object_id)
|
||||
self.assertEqual(
|
||||
"external_mirror", restored.external_sources[0].authority_mode
|
||||
)
|
||||
self.assertEqual("workflow-1", restored.workflow_ref.object_id)
|
||||
self.assertEqual("approval-1", restored.approval_refs[0].object_id)
|
||||
self.assertEqual("record-1", restored.record_refs[0].object_id)
|
||||
self.assertTrue(restored.temporal.effective_at(NOW))
|
||||
|
||||
def test_cross_tenant_reference_is_rejected(self) -> None:
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "different tenant"):
|
||||
GovernedContextEnvelope(
|
||||
tenant_id="tenant-1",
|
||||
temporal=TemporalRevision(revision="1", recorded_at=NOW),
|
||||
case_ref=_reference("case", "case-2", tenant_id="tenant-2"),
|
||||
)
|
||||
|
||||
def test_default_serialization_redacts_labels_and_inspection_links(self) -> None:
|
||||
payload = _context().to_dict()
|
||||
|
||||
self.assertIsNone(payload["case_ref"]["label"])
|
||||
self.assertIsNone(payload["legal_bases"][0]["title"])
|
||||
self.assertIsNone(payload["legal_bases"][0]["inspection_url"])
|
||||
self.assertIsNone(payload["evidence"][0]["inspection_url"])
|
||||
|
||||
def test_event_and_action_envelopes_carry_reference_context(self) -> None:
|
||||
context = _context()
|
||||
event = PlatformEvent(
|
||||
type="committee.decision.recorded",
|
||||
module_id="committee",
|
||||
institutional_context=context,
|
||||
)
|
||||
action = ActionExecutionRequest(
|
||||
tenant_id="tenant-1",
|
||||
action_key="committee.decision.publish",
|
||||
input={"decision_ref": "decision-1"},
|
||||
idempotency_key="decision-1:publish:1",
|
||||
invocation=AutomationInvocation(kind="workflow"),
|
||||
institutional_context=context,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
"decision-1",
|
||||
event.to_dict()["institutional_context"]["decision_ref"]["object_id"],
|
||||
)
|
||||
self.assertIs(context, action.institutional_context)
|
||||
|
||||
def test_formal_decision_redacts_reasoning_but_keeps_evidence_refs(self) -> None:
|
||||
context = _context()
|
||||
decision = FormalDecision(
|
||||
reference=_reference("decision", "decision-1"),
|
||||
temporal=context.temporal,
|
||||
decision_type="permit",
|
||||
subject_refs=(_reference("case", "case-1"),),
|
||||
state="decided",
|
||||
authority_context=context,
|
||||
fact_evidence=context.evidence,
|
||||
legal_bases=context.legal_bases,
|
||||
operative_result="Permit granted.",
|
||||
reasoning="Protected reasoning.",
|
||||
)
|
||||
|
||||
redacted = decision.to_dict()
|
||||
protected = decision.to_dict(include_protected=True)
|
||||
|
||||
self.assertIsNone(redacted["reasoning"])
|
||||
self.assertEqual("Protected reasoning.", protected["reasoning"])
|
||||
self.assertEqual(
|
||||
"file-1", redacted["fact_evidence"][0]["evidence_id"]
|
||||
)
|
||||
|
||||
restored = FormalDecision.from_mapping(protected)
|
||||
self.assertEqual("Protected reasoning.", restored.reasoning)
|
||||
self.assertEqual("decision-1", restored.reference.object_id)
|
||||
|
||||
def test_semantic_provider_dtos_round_trip_without_domain_tables(self) -> None:
|
||||
context = _context()
|
||||
mandate = MandateDefinition(
|
||||
reference=_reference("mandate", "mandate-1"),
|
||||
temporal=context.temporal,
|
||||
task_types=("committee.formal_decision",),
|
||||
authority_types=("permit",),
|
||||
organization_unit_refs=(
|
||||
_reference("organization_unit", "unit-1"),
|
||||
),
|
||||
function_refs=(_reference("function", "function-1"),),
|
||||
jurisdiction_refs=context.jurisdiction_refs,
|
||||
legal_bases=context.legal_bases,
|
||||
evidence=context.evidence,
|
||||
)
|
||||
resolution = MandateResolution(
|
||||
competent=True,
|
||||
mandates=(mandate,),
|
||||
explanation="Authority matched.",
|
||||
evidence=context.evidence,
|
||||
)
|
||||
service = ServiceDefinition(
|
||||
reference=_reference("service", "service-1"),
|
||||
key="permit.service",
|
||||
temporal=context.temporal,
|
||||
title="Permit service",
|
||||
audience=("residents",),
|
||||
legal_bases=context.legal_bases,
|
||||
channels=("portal", "mail"),
|
||||
responsible_organization_ref=_reference(
|
||||
"organization_unit", "unit-1"
|
||||
),
|
||||
responsible_function_ref=_reference("function", "function-1"),
|
||||
mandate_ref=_reference("mandate", "mandate-1"),
|
||||
jurisdiction_refs=context.jurisdiction_refs,
|
||||
bindings=(ServiceBinding("workflow", "workflow:permit"),),
|
||||
publication_state="published",
|
||||
)
|
||||
represented = _reference("party", "party-applicant")
|
||||
representative = _reference("party", "party-agent")
|
||||
party = ProcedureParty(
|
||||
reference=represented,
|
||||
procedure_ref=_reference("case", "case-1"),
|
||||
role="applicant",
|
||||
subject=PartySubjectReference(
|
||||
kind="identity",
|
||||
provider="identity",
|
||||
subject_id="identity-1",
|
||||
tenant_id="tenant-1",
|
||||
version="2",
|
||||
),
|
||||
temporal=context.temporal,
|
||||
preferred_channels=("portal",),
|
||||
permitted_channels=("portal", "mail"),
|
||||
delivery_recipient=True,
|
||||
representations=(
|
||||
PartyRepresentation(
|
||||
representative_party_ref=representative,
|
||||
represented_party_ref=represented,
|
||||
power_ref="power-1",
|
||||
permitted_actions=("submit", "receive"),
|
||||
temporal=context.temporal,
|
||||
evidence=context.evidence,
|
||||
),
|
||||
),
|
||||
contact_snapshot_refs=("addresses:snapshot-1",),
|
||||
evidence=context.evidence,
|
||||
)
|
||||
|
||||
restored_resolution = MandateResolution.from_mapping(
|
||||
resolution.to_dict(include_inspection=True)
|
||||
)
|
||||
restored_service = ServiceDefinition.from_mapping(service.to_dict())
|
||||
restored_party = ProcedureParty.from_mapping(
|
||||
party.to_dict(include_inspection=True)
|
||||
)
|
||||
|
||||
self.assertTrue(restored_resolution.competent)
|
||||
self.assertEqual(
|
||||
"committee.formal_decision",
|
||||
restored_resolution.mandates[0].task_types[0],
|
||||
)
|
||||
self.assertEqual("workflow:permit", restored_service.bindings[0].reference)
|
||||
self.assertEqual("identity-1", restored_party.subject.subject_id)
|
||||
self.assertEqual(
|
||||
"party-agent",
|
||||
restored_party.representations[0].representative_party_ref.object_id,
|
||||
)
|
||||
|
||||
def test_invalid_semantic_states_are_rejected(self) -> None:
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "disclosure state"):
|
||||
InformationGovernanceReference(
|
||||
classification="internal",
|
||||
purposes=("test",),
|
||||
disclosure_state="invalid", # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def test_service_launch_contract_requires_exact_same_tenant_targets(self) -> None:
|
||||
service_ref = _reference("service", "service-1")
|
||||
binding = ServiceBinding("case", "permit")
|
||||
request = ServiceLaunchRequest(
|
||||
service_ref=service_ref,
|
||||
binding=binding,
|
||||
idempotency_key="launch-1",
|
||||
requested_at=NOW,
|
||||
parameters={"title": "Permit application"},
|
||||
)
|
||||
result = ServiceLaunchResult(
|
||||
service_ref=service_ref,
|
||||
binding=binding,
|
||||
state="started",
|
||||
target_ref=_reference("case", "case-1"),
|
||||
href="/cases/case-1",
|
||||
metadata={"case_number": "PERMIT-1"},
|
||||
)
|
||||
|
||||
self.assertEqual("launch-1", request.idempotency_key)
|
||||
self.assertEqual("case-1", result.to_dict()["target_ref"]["object_id"])
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "another tenant"):
|
||||
ServiceLaunchResult(
|
||||
service_ref=service_ref,
|
||||
binding=binding,
|
||||
state="started",
|
||||
target_ref=_reference("case", "case-2", tenant_id="tenant-2"),
|
||||
metadata={},
|
||||
)
|
||||
|
||||
def test_form_definition_contract_round_trips_exact_schema(self) -> None:
|
||||
definition = FormDefinition(
|
||||
reference=_reference("form", "permit-application"),
|
||||
key="permit-application",
|
||||
temporal=TemporalRevision(
|
||||
revision="3",
|
||||
recorded_at=NOW,
|
||||
change_reason="Add delivery preference.",
|
||||
),
|
||||
title="Permit application",
|
||||
description="Apply for a permit.",
|
||||
fields=(
|
||||
FormFieldDefinition(
|
||||
key="email",
|
||||
label="Email address",
|
||||
value_type="email",
|
||||
required=True,
|
||||
constraints={"max_length": 255},
|
||||
),
|
||||
FormFieldDefinition(
|
||||
key="delivery",
|
||||
label="Delivery",
|
||||
value_type="choice",
|
||||
options=("portal", "mail"),
|
||||
),
|
||||
),
|
||||
max_attachments=3,
|
||||
signature_requirement="optional",
|
||||
policy_refs=("policy:permit-intake",),
|
||||
handoff_kinds=("case",),
|
||||
)
|
||||
|
||||
restored = FormDefinition.from_mapping(definition.to_dict())
|
||||
|
||||
self.assertEqual("3", restored.reference.version)
|
||||
self.assertEqual("email", restored.fields[0].key)
|
||||
self.assertEqual(("portal", "mail"), restored.fields[1].options)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "unique"):
|
||||
replace(restored, fields=(restored.fields[0], restored.fields[0]))
|
||||
|
||||
def test_service_launch_redirect_rejects_credentialed_urls(self) -> None:
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "credential-free"):
|
||||
ServiceLaunchResult(
|
||||
service_ref=_reference("service", "service-1"),
|
||||
binding=ServiceBinding("external", "external-entry"),
|
||||
state="redirect",
|
||||
href="https://user:secret@example.test/start",
|
||||
metadata={},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "must be a boolean"):
|
||||
ServiceBinding.from_mapping(
|
||||
{
|
||||
"kind": "workflow",
|
||||
"reference": "workflow:permit",
|
||||
"required": "false",
|
||||
}
|
||||
)
|
||||
|
||||
def test_service_derivation_can_only_tighten_parent_constraints(self) -> None:
|
||||
parent = ServiceDefinition(
|
||||
reference=_reference("service", "permit-template"),
|
||||
key="permit.apply",
|
||||
temporal=TemporalRevision(
|
||||
revision="7",
|
||||
valid_from=NOW - timedelta(days=1),
|
||||
valid_to=NOW + timedelta(days=10),
|
||||
recorded_at=NOW - timedelta(days=2),
|
||||
),
|
||||
title="Permit service",
|
||||
audience=("resident", "business"),
|
||||
prerequisites=("registered-address",),
|
||||
required_evidence_types=("application",),
|
||||
channels=("portal", "mail"),
|
||||
bindings=(ServiceBinding("case", "permit-case"),),
|
||||
availability_requirements=(
|
||||
ServiceAvailabilityRequirement("policy", "permit-access"),
|
||||
),
|
||||
publication_state="published",
|
||||
)
|
||||
temporal = TemporalRevision(
|
||||
revision="8",
|
||||
valid_from=NOW,
|
||||
valid_to=NOW + timedelta(days=5),
|
||||
recorded_at=NOW,
|
||||
change_reason="Tenant specialization.",
|
||||
)
|
||||
reference = replace(
|
||||
_reference("service", "permit-tenant"),
|
||||
version="8",
|
||||
)
|
||||
|
||||
derived = derive_service_restriction(
|
||||
parent,
|
||||
reference=reference,
|
||||
temporal=temporal,
|
||||
audience=("resident",),
|
||||
channels=("portal",),
|
||||
add_prerequisites=("tenant-residency",),
|
||||
add_required_evidence_types=("identity",),
|
||||
add_bindings=(ServiceBinding("workflow", "permit-review"),),
|
||||
)
|
||||
|
||||
self.assertEqual(parent.reference, derived.derived_from_ref)
|
||||
self.assertEqual(("resident",), derived.audience)
|
||||
self.assertEqual(("portal",), derived.channels)
|
||||
self.assertEqual(
|
||||
("registered-address", "tenant-residency"),
|
||||
derived.prerequisites,
|
||||
)
|
||||
self.assertIn(ServiceBinding("case", "permit-case"), derived.bindings)
|
||||
self.assertEqual(
|
||||
parent.availability_requirements,
|
||||
derived.availability_requirements,
|
||||
)
|
||||
self.assertEqual(
|
||||
parent.availability_requirements,
|
||||
ServiceDefinition.from_mapping(
|
||||
derived.to_dict()
|
||||
).availability_requirements,
|
||||
)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "widen.*audience"):
|
||||
derive_service_restriction(
|
||||
parent,
|
||||
reference=reference,
|
||||
temporal=temporal,
|
||||
audience=("resident", "visitor"),
|
||||
)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "beyond"):
|
||||
derive_service_restriction(
|
||||
parent,
|
||||
reference=reference,
|
||||
temporal=replace(temporal, valid_to=None),
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
InstitutionalContextError,
|
||||
"publication state",
|
||||
):
|
||||
derive_service_restriction(
|
||||
replace(parent, publication_state="suspended"),
|
||||
reference=reference,
|
||||
temporal=temporal,
|
||||
publication_state="published",
|
||||
)
|
||||
|
||||
def test_mandate_resolution_is_effective_scoped_and_deterministic(self) -> None:
|
||||
context = _context()
|
||||
mandate = MandateDefinition(
|
||||
reference=_reference("mandate", "mandate-1"),
|
||||
temporal=TemporalRevision(
|
||||
revision="1",
|
||||
valid_from=NOW - timedelta(days=1),
|
||||
valid_to=NOW + timedelta(days=1),
|
||||
recorded_at=NOW - timedelta(days=2),
|
||||
),
|
||||
task_types=("committee.formal_decision",),
|
||||
authority_types=("permit",),
|
||||
organization_unit_refs=(
|
||||
_reference("organization_unit", "unit-1"),
|
||||
),
|
||||
function_refs=(_reference("function", "function-1"),),
|
||||
jurisdiction_refs=context.jurisdiction_refs,
|
||||
subject_types=("permit",),
|
||||
evidence=context.evidence,
|
||||
)
|
||||
request = MandateResolutionRequest(
|
||||
tenant_id="tenant-1",
|
||||
effective_at=NOW,
|
||||
task_type="committee.formal_decision",
|
||||
authority_type="permit",
|
||||
organization_unit_ref=replace(
|
||||
_reference("organization_unit", "unit-1"), version="9"
|
||||
),
|
||||
function_ref=replace(
|
||||
_reference("function", "function-1"), version="9"
|
||||
),
|
||||
jurisdiction_refs=tuple(
|
||||
replace(item, version="9") for item in context.jurisdiction_refs
|
||||
),
|
||||
subject_type="permit",
|
||||
)
|
||||
|
||||
resolution = resolve_mandate_candidates(
|
||||
request,
|
||||
(
|
||||
replace(
|
||||
mandate,
|
||||
reference=_reference("mandate", "retired"),
|
||||
status="retired",
|
||||
),
|
||||
mandate,
|
||||
),
|
||||
)
|
||||
|
||||
self.assertTrue(resolution.competent)
|
||||
self.assertEqual("mandate-1", resolution.mandates[0].reference.object_id)
|
||||
self.assertEqual("file-1", resolution.evidence[0].evidence_id)
|
||||
|
||||
ambiguous = resolve_mandate_candidates(
|
||||
request,
|
||||
(
|
||||
mandate,
|
||||
replace(
|
||||
mandate,
|
||||
reference=_reference("mandate", "mandate-2"),
|
||||
),
|
||||
),
|
||||
)
|
||||
self.assertFalse(ambiguous.competent)
|
||||
self.assertEqual(2, len(ambiguous.conflict_refs))
|
||||
|
||||
missing_jurisdiction = resolve_mandate_candidates(
|
||||
replace(request, jurisdiction_refs=()),
|
||||
(mandate,),
|
||||
)
|
||||
self.assertFalse(missing_jurisdiction.competent)
|
||||
|
||||
def test_mandate_resolution_rejects_cross_tenant_candidates(self) -> None:
|
||||
request = MandateResolutionRequest(
|
||||
tenant_id="tenant-1",
|
||||
effective_at=NOW,
|
||||
task_type="committee.formal_decision",
|
||||
authority_type="permit",
|
||||
)
|
||||
candidate = MandateDefinition(
|
||||
reference=_reference("mandate", "mandate-2", tenant_id="tenant-2"),
|
||||
temporal=TemporalRevision(revision="1", recorded_at=NOW),
|
||||
task_types=("committee.formal_decision",),
|
||||
authority_types=("permit",),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "cross tenants"):
|
||||
resolve_mandate_candidates(request, (candidate,))
|
||||
|
||||
def test_mandate_lifecycle_revisions_are_linked_by_stable_identity(self) -> None:
|
||||
current = MandateDefinition(
|
||||
reference=_reference("mandate", "mandate-1"),
|
||||
temporal=TemporalRevision(revision="1", recorded_at=NOW),
|
||||
task_types=("committee.formal_decision",),
|
||||
authority_types=("permit",),
|
||||
status="active",
|
||||
)
|
||||
temporal = TemporalRevision(
|
||||
revision="2",
|
||||
recorded_at=NOW + timedelta(minutes=1),
|
||||
change_reason="Authority temporarily suspended.",
|
||||
)
|
||||
|
||||
suspended = revise_mandate_definition(
|
||||
current,
|
||||
expected_revision="1",
|
||||
temporal=temporal,
|
||||
status="suspended",
|
||||
suspension_reason="Delegation is under review.",
|
||||
)
|
||||
|
||||
self.assertEqual("active", current.status)
|
||||
self.assertEqual("suspended", suspended.status)
|
||||
self.assertEqual("2", suspended.reference.version)
|
||||
self.assertEqual(current.reference.object_id, suspended.reference.object_id)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "stale"):
|
||||
revise_mandate_definition(
|
||||
current,
|
||||
expected_revision="0",
|
||||
temporal=temporal,
|
||||
status="suspended",
|
||||
suspension_reason="Review.",
|
||||
)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "not allowed"):
|
||||
revise_mandate_definition(
|
||||
suspended,
|
||||
expected_revision="2",
|
||||
temporal=replace(
|
||||
temporal,
|
||||
revision="3",
|
||||
change_reason="Invalid reset.",
|
||||
),
|
||||
status="draft",
|
||||
)
|
||||
|
||||
def test_formal_decision_revisions_are_linked_and_occ_guarded(self) -> None:
|
||||
context = _context()
|
||||
reference = replace(
|
||||
_reference("decision", "decision-1"),
|
||||
version=context.temporal.revision,
|
||||
)
|
||||
context = replace(context, decision_ref=reference)
|
||||
current = FormalDecision(
|
||||
reference=reference,
|
||||
temporal=context.temporal,
|
||||
decision_type="permit",
|
||||
subject_refs=(_reference("case", "case-1"),),
|
||||
state="decided",
|
||||
authority_context=context,
|
||||
fact_evidence=context.evidence,
|
||||
legal_bases=context.legal_bases,
|
||||
operative_result="Permit granted.",
|
||||
reasoning="Protected reasoning.",
|
||||
requested_effects=(
|
||||
DecisionEffectReference("notify", "requested"),
|
||||
),
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
InstitutionalContextError,
|
||||
"service account",
|
||||
):
|
||||
replace(current, assurance_level="automated_under_mandate")
|
||||
revision = TemporalRevision(
|
||||
revision="8",
|
||||
valid_from=NOW + timedelta(minutes=1),
|
||||
recorded_at=NOW + timedelta(minutes=1),
|
||||
change_reason="Decision entered into force.",
|
||||
)
|
||||
|
||||
effective = revise_formal_decision(
|
||||
current,
|
||||
expected_revision="7",
|
||||
temporal=revision,
|
||||
state="effective",
|
||||
)
|
||||
|
||||
self.assertEqual("decided", current.state)
|
||||
self.assertEqual("8", effective.reference.version)
|
||||
self.assertEqual("7", effective.supersedes_ref.version)
|
||||
self.assertEqual(
|
||||
effective.reference,
|
||||
effective.authority_context.decision_ref,
|
||||
)
|
||||
self.assertEqual(current.requested_effects, effective.requested_effects)
|
||||
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "stale"):
|
||||
revise_formal_decision(
|
||||
current,
|
||||
expected_revision="6",
|
||||
temporal=revision,
|
||||
state="effective",
|
||||
)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "not allowed"):
|
||||
revise_formal_decision(
|
||||
effective,
|
||||
expected_revision="8",
|
||||
temporal=replace(
|
||||
revision,
|
||||
revision="9",
|
||||
change_reason="Invalid regression.",
|
||||
),
|
||||
state="proposed",
|
||||
)
|
||||
|
||||
corrected = revise_formal_decision(
|
||||
effective,
|
||||
expected_revision="8",
|
||||
temporal=replace(
|
||||
revision,
|
||||
revision="9",
|
||||
recorded_at=NOW + timedelta(minutes=2),
|
||||
change_reason="Corrected operative wording.",
|
||||
),
|
||||
state="corrected",
|
||||
operative_result="Permit granted subject to condition A.",
|
||||
)
|
||||
self.assertEqual(effective.reference, corrected.correction_of_ref)
|
||||
self.assertEqual(effective.reference, corrected.supersedes_ref)
|
||||
|
||||
def test_party_corrections_and_representation_revocation_are_versioned(self) -> None:
|
||||
context = _context()
|
||||
represented = _reference("party", "party-applicant")
|
||||
representative = _reference("party", "party-agent")
|
||||
representation = PartyRepresentation(
|
||||
representative_party_ref=representative,
|
||||
represented_party_ref=represented,
|
||||
power_ref="power-1",
|
||||
permitted_actions=("submit", "receive"),
|
||||
temporal=TemporalRevision(
|
||||
revision="1",
|
||||
valid_from=NOW - timedelta(days=1),
|
||||
recorded_at=NOW - timedelta(days=1),
|
||||
),
|
||||
evidence=context.evidence,
|
||||
)
|
||||
current = ProcedureParty(
|
||||
reference=represented,
|
||||
procedure_ref=_reference("case", "case-1"),
|
||||
role="applicant",
|
||||
subject=PartySubjectReference(
|
||||
kind="identity",
|
||||
provider="identity",
|
||||
subject_id="identity-1",
|
||||
tenant_id="tenant-1",
|
||||
version="2",
|
||||
),
|
||||
temporal=TemporalRevision(revision="1", recorded_at=NOW),
|
||||
permitted_channels=("mail", "postbox"),
|
||||
preferred_channels=("mail",),
|
||||
delivery_recipient=True,
|
||||
representations=(representation,),
|
||||
contact_snapshot_refs=("addresses:snapshot-1",),
|
||||
evidence=context.evidence,
|
||||
)
|
||||
revision = TemporalRevision(
|
||||
revision="2",
|
||||
valid_from=NOW,
|
||||
recorded_at=NOW + timedelta(minutes=1),
|
||||
change_reason="Correct preferred delivery channel.",
|
||||
)
|
||||
|
||||
corrected = revise_procedure_party(
|
||||
current,
|
||||
expected_revision="1",
|
||||
temporal=revision,
|
||||
preferred_channels=("postbox",),
|
||||
)
|
||||
revoked_power = revoke_party_representation(
|
||||
representation,
|
||||
expected_revision="1",
|
||||
temporal=TemporalRevision(
|
||||
revision="2",
|
||||
valid_from=NOW - timedelta(days=1),
|
||||
valid_to=NOW + timedelta(minutes=2),
|
||||
recorded_at=NOW + timedelta(minutes=2),
|
||||
change_reason="Power was revoked.",
|
||||
),
|
||||
revoked_at=NOW + timedelta(minutes=2),
|
||||
)
|
||||
|
||||
self.assertEqual(("mail",), current.preferred_channels)
|
||||
self.assertEqual(("postbox",), corrected.preferred_channels)
|
||||
self.assertEqual("2", corrected.reference.version)
|
||||
self.assertIsNone(representation.revoked_at)
|
||||
self.assertEqual(NOW + timedelta(minutes=2), revoked_power.revoked_at)
|
||||
with self.assertRaisesRegex(InstitutionalContextError, "stale"):
|
||||
revise_procedure_party(
|
||||
current,
|
||||
expected_revision="0",
|
||||
temporal=revision,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user