from __future__ import annotations from datetime import UTC, datetime import unittest from govoplan_core.core.modules import ModuleManifest from govoplan_core.core.provider_governance import ( ArchitectureDeclarationError, ExternalProviderDeclaration, ExternalProviderRuntimeState, ExternalProviderStateContext, ExternalProviderStateProviderRegistration, ModuleArchitectureDeclaration, ModuleArchitectureDocumentation, ModuleMaturityEvidence, ProviderBehaviorDeclaration, ProviderObjectDeclaration, collect_external_provider_states, declared_module_architecture, external_provider_from_mapping, module_architecture_from_mapping, module_architecture_issues, ) from govoplan_core.core.registry import PlatformRegistry, RegistryError def _architecture( *, providers: tuple[str, ...] = (), modes: tuple[str, ...] = ("external_mirror",), ) -> ModuleArchitectureDeclaration: return ModuleArchitectureDeclaration( layer="data_reporting_integration", kind="integration", maturity="vertical_slice", evidence=( ModuleMaturityEvidence( kind="test", reference="tests/test_provider.py", summary="Exercises the bounded provider contract.", ), ModuleMaturityEvidence( kind="documentation", reference="docs/PROVIDER.md", summary="Documents authority and outage behavior.", ), ), known_limits=("Only bounded test objects are supported.",), supported_authority_modes=modes, owned_concepts=("external transport",), non_owned_concepts=("domain records",), target_tested_providers=providers, ) def _mirror_provider() -> ExternalProviderDeclaration: return ExternalProviderDeclaration( id="connectors.read_mirror", module_id="connectors", label="Read-only mirror", maturity="read", operations=("discover", "search", "read", "preview"), objects=( ProviderObjectDeclaration( object_type="register_entry", field_groups=("identity", "content", "source_metadata"), authority_modes=("external_authoritative", "external_mirror"), default_authority_mode="external_mirror", ), ), behavior=ProviderBehaviorDeclaration( revision_tokens="ETag and source revision are retained.", concurrency="Reads can require an expected source revision.", freshness="The latest successful acquisition time is reported.", health="Transport and parsing health are reported separately.", max_read_items=500, evidence="Immutable snapshots retain acquisition provenance.", correction="Refresh creates a new snapshot; old evidence is retained.", reconciliation="Compare the latest source revision and content digest.", outage="The last governed snapshot remains readable as stale.", classifications=("internal",), purposes=("governed import",), retention="Owning datasource retention policy applies.", secret_handling="Credentials stay in credential envelopes.", ), capability_names=("connectors.readMirror",), ) def _writable_provider() -> ExternalProviderDeclaration: return ExternalProviderDeclaration( id="calendar.caldav_sync", module_id="calendar", label="CalDAV synchronization", maturity="synchronize", operations=("discover", "read", "write", "delete", "synchronize", "preview"), objects=( ProviderObjectDeclaration( object_type="calendar_event", field_groups=("identity", "schedule", "participants", "recurrence"), authority_modes=("external_authoritative", "governed_sync"), default_authority_mode="governed_sync", ), ), behavior=ProviderBehaviorDeclaration( revision_tokens="ETag and sync token.", concurrency="Conditional requests reject stale ETags.", freshness="Last successful sync and pending changes are reported.", health="Discovery, authentication, and collection health are separate.", max_read_items=1000, idempotency="Stable UID and operation keys suppress duplicate effects.", retry="Bounded retry is limited to classified transient failures.", timeout_seconds=30, conflicts="Conflicts remain explicit until policy or a user resolves them.", outcome_unknown="Timed-out writes are outcome-unknown, never blindly retried.", outcome_unknown_supported=True, evidence="Request, response token, and reconciliation evidence are retained.", audit_event_types=("calendar.sync.requested", "calendar.sync.reconciled"), correction="A later governed write or tombstone corrects remote state.", rollback="Remote writes are not assumed rollback-safe.", compensation="A compensating update can be requested after reconciliation.", reconciliation="Read by UID and compare ETag before retrying.", outage="Local state remains marked stale and pending effects stay queued.", classifications=("confidential",), purposes=("calendar synchronization",), retention="Calendar and audit retention policies apply independently.", secret_handling="Credentials are referenced through credential envelopes.", ), capability_names=("calendar.sync",), ) def _state_registration( *, module_id: str, provider_id: str, ) -> ExternalProviderStateProviderRegistration: return ExternalProviderStateProviderRegistration( module_id=module_id, provider_id=provider_id, provider=lambda _context: (), ) class ProviderGovernanceContractTests(unittest.TestCase): def test_concise_declaration_keeps_repository_evidence_explicit(self) -> None: architecture = declared_module_architecture( layer="human_work_procedure", kind="domain", maturity="vertical_slice", documentation_ref="docs/DOMAIN.md", test_ref="tests/test_service.py", known_limits=("Only the bounded service slice is implemented.",), owned_concepts=("domain record",), non_owned_concepts=("identity",), ) self.assertEqual("vertical_slice", architecture.maturity) self.assertEqual( {"documentation", "test"}, {item.kind for item in architecture.evidence}, ) self.assertEqual(("domain record",), architecture.owned_concepts) def test_declarations_round_trip_through_catalog_mappings(self) -> None: provider = _writable_provider() architecture = _architecture( providers=(provider.id,), modes=("external_authoritative", "governed_sync"), ) self.assertEqual( architecture, module_architecture_from_mapping(architecture.to_dict()), ) self.assertEqual( provider, external_provider_from_mapping(provider.to_dict()), ) def test_read_only_mirror_declaration_is_registry_validated(self) -> None: provider = _mirror_provider() registry = PlatformRegistry() registry.register( ModuleManifest( id="connectors", name="Connectors", version="1.0.0", architecture=_architecture( providers=(provider.id,), modes=("external_authoritative", "external_mirror"), ), external_providers=(provider,), external_provider_state_providers=( _state_registration( module_id="connectors", provider_id=provider.id, ), ), capability_factories={"connectors.readMirror": lambda _context: object()}, ) ) snapshot = registry.validate() self.assertEqual("connectors", snapshot.manifests[0].id) self.assertEqual((provider,), registry.external_provider_declarations()) def test_writable_outcome_unknown_provider_is_explicit(self) -> None: provider = _writable_provider() registry = PlatformRegistry() registry.register( ModuleManifest( id="calendar", name="Calendar", version="1.0.0", architecture=_architecture( providers=(provider.id,), modes=("external_authoritative", "governed_sync"), ), external_providers=(provider,), external_provider_state_providers=( _state_registration( module_id="calendar", provider_id=provider.id, ), ), capability_factories={"calendar.sync": lambda _context: object()}, ) ) registry.validate() payload = provider.to_dict() self.assertTrue(payload["behavior"]["outcome_unknown_supported"]) self.assertIn("reconciliation", payload["behavior"]) def test_provider_requires_a_sanitized_runtime_state_registration(self) -> None: provider = _mirror_provider() registry = PlatformRegistry() registry.register( ModuleManifest( id="connectors", name="Connectors", version="1.0.0", architecture=_architecture( providers=(provider.id,), modes=("external_authoritative", "external_mirror"), ), external_providers=(provider,), capability_factories={ "connectors.readMirror": lambda _context: object() }, ) ) with self.assertRaisesRegex( RegistryError, "require sanitized runtime state providers", ): registry.validate() def test_runtime_binding_states_are_validated_and_aggregated(self) -> None: observed_at = datetime(2026, 8, 1, 12, 0, tzinfo=UTC) def states(_context: ExternalProviderStateContext): return ( ExternalProviderRuntimeState( provider_id="calendar.caldav_sync", binding_ref="calendar:sync-source:one", authority_mode="governed_sync", observed_at=observed_at, configured=True, active=True, health="healthy", freshness="current", conflict="clear", recovery="ready", ), ExternalProviderRuntimeState( provider_id="calendar.caldav_sync", binding_ref="calendar:sync-source:two", authority_mode="external_mirror", observed_at=observed_at, configured=True, active=True, health="warning", freshness="stale", conflict="pending", recovery="attention", ), ) payload = collect_external_provider_states( ( ExternalProviderStateProviderRegistration( module_id="calendar", provider_id="calendar.caldav_sync", provider=states, ), ), ExternalProviderStateContext(session=None, tenant_id="tenant-1"), )["calendar.caldav_sync"] self.assertEqual("warning", payload["health"]) self.assertEqual("stale", payload["freshness"]) self.assertEqual("pending", payload["conflict"]) self.assertEqual("attention", payload["recovery"]) self.assertEqual(2, len(payload["bindings"])) def test_runtime_state_registration_requires_declared_provider(self) -> None: registry = PlatformRegistry() registry.register( ModuleManifest( id="calendar", name="Calendar", version="1.0.0", architecture=_architecture(), external_provider_state_providers=( ExternalProviderStateProviderRegistration( module_id="calendar", provider_id="calendar.caldav_sync", provider=lambda _context: (), ), ), ) ) with self.assertRaisesRegex(RegistryError, "undeclared provider"): registry.validate() def test_runtime_state_failure_is_sanitized(self) -> None: def fail(_context: ExternalProviderStateContext): raise RuntimeError("secret transport detail") payload = collect_external_provider_states( ( ExternalProviderStateProviderRegistration( module_id="calendar", provider_id="calendar.caldav_sync", provider=fail, ), ), ExternalProviderStateContext(session=None), )["calendar.caldav_sync"] self.assertEqual("error", payload["health"]) self.assertNotIn("secret transport detail", str(payload)) def test_supported_claim_requires_evidence_not_only_a_string(self) -> None: registry = PlatformRegistry() registry.register( ModuleManifest( id="example", name="Example", version="1.0.0", architecture=ModuleArchitectureDeclaration( layer="domain_capability", kind="domain", maturity="supported", evidence=(), owned_concepts=("example",), ), ) ) with self.assertRaisesRegex(RegistryError, "missing evidence"): registry.validate() def test_supported_and_lts_claims_require_reference_packages(self) -> None: for maturity in ("supported", "lts"): with self.subTest(maturity=maturity): declaration = ModuleArchitectureDeclaration( layer="domain_capability", kind="domain", maturity=maturity, evidence=tuple( ModuleMaturityEvidence( kind=kind, reference=f"evidence/{kind}.json", summary=f"{kind} evidence.", ) for kind in ( "test", "documentation", "reference_process", "target_test", "recovery", "security", "operations", "accessibility", "privacy", "upgrade", *(("compatibility",) if maturity == "lts" else ()), ) ), owned_concepts=("example",), documentation=ModuleArchitectureDocumentation( upgrade=("docs/UPGRADE.md",), recovery=("docs/RECOVERY.md",), security=("docs/SECURITY.md",), operations=("docs/OPERATIONS.md",), ), ) self.assertEqual( (f"{maturity} maturity requires a reference package",), module_architecture_issues(declaration, has_migrations=False), ) def test_reference_ready_claim_requires_target_human_and_operator_evidence(self) -> None: declaration = ModuleArchitectureDeclaration( layer="domain_capability", kind="domain", maturity="reference_ready", evidence=( ModuleMaturityEvidence( kind="test", reference="tests/test_reference.py", summary="Automated journey proof.", ), ModuleMaturityEvidence( kind="documentation", reference="docs/REFERENCE.md", summary="Reference composition documentation.", ), ModuleMaturityEvidence( kind="reference_process", reference="docs/JOURNEY.md", summary="Reference process definition.", ), ModuleMaturityEvidence( kind="recovery", reference="evidence/recovery.json", summary="Recovery drill evidence.", ), ), known_limits=("One provider profile remains bounded.",), owned_concepts=("example",), reference_packages=("reference.example",), ) issues = module_architecture_issues(declaration, has_migrations=False) self.assertIn("accessibility", issues[0]) self.assertIn("operations", issues[0]) self.assertIn("privacy", issues[0]) self.assertIn("security", issues[0]) self.assertIn("target_test", issues[0]) self.assertIn("missing documentation", issues[1]) def test_target_tested_provider_requires_provider_evidence(self) -> None: evidence = tuple( ModuleMaturityEvidence( kind=kind, reference=f"evidence/{kind}.json", summary=f"{kind} evidence.", ) for kind in ( "test", "documentation", "reference_process", "target_test", "recovery", "security", "operations", "accessibility", "privacy", ) ) declaration = ModuleArchitectureDeclaration( layer="data_reporting_integration", kind="integration", maturity="reference_ready", evidence=evidence, known_limits=("One bounded target profile is assessed.",), reference_packages=("reference.integration",), target_tested_providers=("connectors.example",), documentation=ModuleArchitectureDocumentation( recovery=("docs/RECOVERY.md",), security=("docs/SECURITY.md",), operations=("docs/OPERATIONS.md",), ), ) self.assertEqual( ( "maturity 'reference_ready' is missing evidence: provider", ), module_architecture_issues(declaration, has_migrations=False), ) def test_provider_references_must_exist_in_manifest(self) -> None: provider = _mirror_provider() registry = PlatformRegistry() registry.register( ModuleManifest( id="connectors", name="Connectors", version="1.0.0", architecture=_architecture( providers=(provider.id,), modes=("external_authoritative", "external_mirror"), ), external_providers=(provider,), ) ) with self.assertRaisesRegex(RegistryError, "undeclared capabilities"): registry.validate() def test_governed_sync_requires_read_and_write_semantics(self) -> None: with self.assertRaisesRegex( ArchitectureDeclarationError, "read and write/synchronize", ): ExternalProviderDeclaration( id="broken.sync", module_id="broken", label="Broken sync", maturity="synchronize", operations=("synchronize",), objects=( ProviderObjectDeclaration( object_type="object", field_groups=("content",), authority_modes=("governed_sync",), default_authority_mode="governed_sync", ), ), behavior=_writable_provider().behavior, ) if __name__ == "__main__": unittest.main()