feat: declare governed external provider state
This commit is contained in:
@@ -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",),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user