537 lines
20 KiB
Python
537 lines
20 KiB
Python
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()
|