feat: add institutional governance and recovery contracts
This commit is contained in:
@@ -0,0 +1,536 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user