feat: declare governed external provider state

This commit is contained in:
2026-08-01 17:48:37 +02:00
parent fe309dbff5
commit 5a3b13e26e
4 changed files with 622 additions and 0 deletions
+138
View File
@@ -28,12 +28,25 @@ 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.core.views import ViewSurface
from govoplan_core.db.base import Base
from govoplan_mail.backend.documentation import (
documentation_configuration_states,
documentation_topics,
)
from govoplan_mail.backend.provider_state import (
IMAP_PROVIDER_ID,
SMTP_PROVIDER_ID,
imap_provider_states,
smtp_provider_states,
)
from govoplan_mail.backend.db import models as mail_models # noqa: F401 - populate Mail ORM metadata
@@ -188,6 +201,99 @@ def _mail_router(context: ModuleContext):
return aggregate
SMTP_PROVIDER = ExternalProviderDeclaration(
id=SMTP_PROVIDER_ID,
module_id="mail",
label="SMTP message delivery",
maturity="publish",
operations=("discover", "read", "publish", "preview", "dry_run"),
objects=(
ProviderObjectDeclaration(
object_type="mail_server_endpoint",
field_groups=("identity", "transport", "policy", "revision"),
authority_modes=("external_authoritative", "governance_overlay"),
default_authority_mode="governance_overlay",
),
ProviderObjectDeclaration(
object_type="outbound_message",
field_groups=("envelope", "content_digest", "delivery_state", "evidence"),
authority_modes=("governance_overlay",),
default_authority_mode="governance_overlay",
),
),
behavior=ProviderBehaviorDeclaration(
revision_tokens="Every command pins the selected SMTP endpoint, credential, and random transport revision.",
concurrency="The expected transport revision is checked before credentials are decrypted or an effect starts.",
freshness="Delivery outcomes and completion times are retained; transport freshness is not periodic state.",
health="Successful deliveries and unresolved outcome-unknown commands are projected without exposing server details.",
max_read_items=5000,
idempotency="Tenant, command type, and idempotency key bind one canonical encrypted delivery request.",
retry="Only classified pre-acceptance temporary failures are retried with bounded scheduling.",
timeout_seconds=60,
conflicts="A reused idempotency key with different content is rejected before delivery.",
outcome_unknown="A connection failure after effect start becomes outcome_unknown and is never blindly retried.",
outcome_unknown_supported=True,
evidence="Encrypted command, attempt, acceptance/refusal summary, effect-start marker, and reconciliation are retained.",
audit_event_types=(
"mail.delivery_requested",
"mail.delivery_completed",
"mail.delivery_reconciled",
),
correction="A reconciled new command is a separate auditable effect and does not rewrite the original outcome.",
rollback="SMTP acceptance cannot be rolled back.",
compensation="A follow-up message or domain correction is the only safe compensation after acceptance.",
reconciliation="Operators record provider evidence and choose accepted or not-accepted before any resend.",
outage="Pending commands remain durable; accepted or outcome-unknown commands are not redelivered automatically.",
classifications=("confidential", "personal", "special_category"),
purposes=("governed message delivery", "notification delivery"),
retention="Payload, delivery evidence, and audit records follow separate configured retention policies.",
secret_handling="Credentials are decrypted only inside Mail after authorization and revision validation.",
),
interface_names=("mail.campaign_delivery", "mail.delivery_commands", "mail.delivery_outbox"),
documentation_topic_ids=("mail.reference.campaign-delivery-contract",),
)
IMAP_PROVIDER = ExternalProviderDeclaration(
id=IMAP_PROVIDER_ID,
module_id="mail",
label="IMAP mailbox projection",
maturity="read",
operations=("discover", "search", "read", "preview"),
objects=(
ProviderObjectDeclaration(
object_type="mailbox_folder",
field_groups=("identity", "flags", "counts", "revision"),
authority_modes=("external_authoritative", "external_mirror"),
default_authority_mode="external_mirror",
),
ProviderObjectDeclaration(
object_type="mailbox_message",
field_groups=("identity", "headers", "body_preview", "flags", "source_metadata"),
authority_modes=("external_authoritative", "external_mirror"),
default_authority_mode="external_mirror",
),
),
behavior=ProviderBehaviorDeclaration(
revision_tokens="UIDVALIDITY, UID, folder, flags, and transport revision identify mailbox observations.",
concurrency="Bounded reads pin the profile transport revision and never mutate message flags.",
freshness="Folder and message index timestamps state when the external mailbox was last observed.",
health="Index state and bounce-source errors are projected independently of secret profile fields.",
max_read_items=500,
evidence="Mailbox index rows and bounce observations retain bounded source identities and observation times.",
audit_event_types=("mail.mailbox.read", "mail.bounce.observed"),
correction="A later mailbox refresh replaces the derived projection while source-owned history remains external.",
reconciliation="UIDVALIDITY changes invalidate the affected derived index before a bounded refresh.",
outage="The last derived index remains readable with stale or unknown freshness where policy permits.",
classifications=("confidential", "personal", "special_category"),
purposes=("mailbox access", "delivery-status processing"),
retention="Derived mailbox indexes and bounce evidence follow Mail retention policy.",
secret_handling="IMAP credentials remain encrypted and are never returned through mailbox or provider-state APIs.",
),
documentation_topic_ids=("mail.workflow.read-mailbox",),
)
manifest = ModuleManifest(
id="mail",
name="Mail",
@@ -563,6 +669,38 @@ manifest = ModuleManifest(
resolve=documentation_configuration_states,
),
),
external_providers=(SMTP_PROVIDER, IMAP_PROVIDER),
external_provider_state_providers=(
ExternalProviderStateProviderRegistration(
module_id="mail",
provider_id=SMTP_PROVIDER_ID,
provider=smtp_provider_states,
),
ExternalProviderStateProviderRegistration(
module_id="mail",
provider_id=IMAP_PROVIDER_ID,
provider=imap_provider_states,
),
),
architecture=declared_module_architecture(
layer="communication_participation",
kind="integration",
maturity="vertical_slice",
documentation_ref="docs/MAIL_HANDBOOK.md",
test_ref="tests/test_delivery_outbox.py",
known_limits=("Provider recovery drills and a complete webmail profile are not reference-ready.",),
supported_authority_modes=(
"external_authoritative",
"external_mirror",
"governance_overlay",
),
owned_concepts=("mail profile", "mail delivery command", "delivery attempt", "mailbox projection"),
non_owned_concepts=("campaign", "notification", "recipient address directory", "external mailbox"),
target_tested_providers=(SMTP_PROVIDER_ID, IMAP_PROVIDER_ID),
recovery_docs=("docs/MAIL_HANDBOOK.md",),
security_docs=("docs/MAIL_HANDBOOK.md",),
operations_docs=("docs/MAIL_HANDBOOK.md",),
),
)