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