feat: declare governed external provider state

This commit is contained in:
2026-08-01 17:47:50 +02:00
parent 41ccd4c807
commit 67392f620f
4 changed files with 380 additions and 0 deletions
@@ -27,7 +27,18 @@ from govoplan_core.core.modules import (
PermissionDefinition,
RoleTemplate,
)
from govoplan_core.core.provider_governance import (
ExternalProviderDeclaration,
ExternalProviderStateProviderRegistration,
ProviderBehaviorDeclaration,
ProviderObjectDeclaration,
declared_module_architecture,
)
from govoplan_core.db.base import Base
from govoplan_addresses.backend.provider_state import (
CARDDAV_PROVIDER_ID,
carddav_provider_states,
)
_addresses_table_retirement_provider = drop_table_retirement_provider(
@@ -152,6 +163,59 @@ def _addresses_router(_context: ModuleContext):
return router
CARDDAV_PROVIDER = ExternalProviderDeclaration(
id=CARDDAV_PROVIDER_ID,
module_id="addresses",
label="CardDAV address-book synchronization",
maturity="synchronize",
operations=("discover", "read", "write", "delete", "synchronize", "preview"),
objects=(
ProviderObjectDeclaration(
object_type="address_book",
field_groups=("identity", "display", "sync_state"),
authority_modes=("external_authoritative", "external_mirror", "governed_sync"),
default_authority_mode="external_mirror",
),
ProviderObjectDeclaration(
object_type="contact",
field_groups=("identity", "name", "postal", "email", "phone", "source_metadata"),
authority_modes=("external_authoritative", "external_mirror", "governed_sync"),
default_authority_mode="governed_sync",
),
),
behavior=ProviderBehaviorDeclaration(
revision_tokens="CardDAV sync tokens, resource hrefs, and ETags are retained.",
concurrency="Conditional writes reject stale ETags and preserve explicit conflicts.",
freshness="Last attempt, last success, source status, and sync token are recorded.",
health="Transport failures, diagnostics, and unresolved conflicts are projected separately.",
max_read_items=5000,
idempotency="Stable source, href, UID, and ETag facts prevent duplicate contact effects.",
retry="Only a new governed sync attempt retries failed transport operations.",
timeout_seconds=30,
conflicts="Local and remote values remain in an explicit conflict record until resolved.",
outcome_unknown="Timed-out writes require a subsequent CardDAV read before correction or retry.",
outcome_unknown_supported=True,
evidence="Sync diagnostics, tombstones, conflicts, source revisions, and contact provenance are retained.",
audit_event_types=(
"addresses.sync.started",
"addresses.sync.finished",
"addresses.sync.conflict_recorded",
),
correction="A resolved conflict or later synchronized revision corrects state without rewriting prior evidence.",
rollback="Remote writes are not assumed to be transactionally reversible.",
compensation="A reconciled update or tombstone can compensate after the remote outcome is known.",
reconciliation="Read by resource href and compare ETag, UID, and local revision before applying changes.",
outage="Existing local contacts remain available with stale or unknown freshness.",
classifications=("personal", "confidential"),
purposes=("address-book synchronization", "governed recipient resolution"),
retention="Address-book and audit retention policies apply independently.",
secret_handling="Only credential references and sanitized authentication metadata are persisted in sync state.",
),
capability_names=(CAPABILITY_ADDRESSES_LOOKUP, CAPABILITY_ADDRESSES_CONTACT_WRITER),
documentation_topic_ids=("addresses.boundary",),
)
manifest = ModuleManifest(
id="addresses",
name="Addresses",
@@ -234,6 +298,33 @@ manifest = ModuleManifest(
order=30,
),
),
external_providers=(CARDDAV_PROVIDER,),
external_provider_state_providers=(
ExternalProviderStateProviderRegistration(
module_id="addresses",
provider_id=CARDDAV_PROVIDER_ID,
provider=carddav_provider_states,
),
),
architecture=declared_module_architecture(
layer="communication_participation",
kind="domain",
maturity="vertical_slice",
documentation_ref="docs/ADDRESS_MODULE_ARCHITECTURE.md",
test_ref="tests/test_addresses_service.py",
known_limits=("External address-book synchronization remains a bounded connector slice rather than a supported provider profile.",),
supported_authority_modes=(
"native_authoritative",
"external_authoritative",
"external_mirror",
"governed_sync",
),
owned_concepts=("contact point", "address book", "contact consent", "recipient source"),
non_owned_concepts=("identity", "organization", "campaign recipient snapshot", "procedure party"),
target_tested_providers=(CARDDAV_PROVIDER_ID,),
security_docs=("docs/ADDRESS_MODULE_ARCHITECTURE.md",),
operations_docs=("README.md",),
),
)