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()