Implement governed hybrid campaign delivery

This commit is contained in:
2026-08-02 13:58:37 +02:00
parent b38597f2be
commit 7733265cc8
40 changed files with 2531 additions and 122 deletions
@@ -295,6 +295,7 @@ class _JobReportAggregate:
queue: Counter[str] = field(default_factory=Counter)
send: Counter[str] = field(default_factory=Counter)
postbox: Counter[str] = field(default_factory=Counter)
print: Counter[str] = field(default_factory=Counter)
imap: Counter[str] = field(default_factory=Counter)
issue_total: int = 0
issue_severity: Counter[str] = field(default_factory=Counter)
@@ -333,6 +334,7 @@ class _JobReportAggregate:
self.queue[job.queue_status or "unknown"] += 1
self.send[job.send_status or "unknown"] += 1
self.postbox[job.postbox_status or "unknown"] += 1
self.print[getattr(job, "print_status", None) or "unknown"] += 1
self.imap[job.imap_status or "unknown"] += 1
def _add_delivery_counts(self, job: CampaignJob, *, retry_max_attempts: int | None) -> None:
@@ -398,6 +400,7 @@ NON_CANCELLABLE_SEND_STATUSES = {
"skipped",
"smtp_accepted",
"postbox_accepted",
"print_accepted",
"delivered",
"partially_accepted",
"sent",
@@ -416,6 +419,7 @@ def _job_is_queueable_unattempted(job: CampaignJob) -> bool:
return (
job.attempt_count == 0
and job.postbox_attempt_count == 0
and getattr(job, "print_attempt_count", 0) == 0
and job.send_status in {"not_queued", "cancelled"}
and _job_is_queueable(job)
)
@@ -449,6 +453,7 @@ def _job_needs_attention(job: CampaignJob) -> bool:
"rejected_permanent",
"outcome_unknown",
}
or getattr(job, "print_status", "not_requested") == "failed"
or job.imap_status == "failed"
)
@@ -465,6 +470,7 @@ def _job_is_recent_failure(job: CampaignJob) -> bool:
"rejected_permanent",
"outcome_unknown",
}
or getattr(job, "print_status", "not_requested") == "failed"
or job.imap_status == "failed"
)
@@ -601,12 +607,20 @@ def _job_row(
"postbox_status",
"not_requested",
),
"print_status": getattr(job, "print_status", "not_requested"),
"imap_status": job.imap_status,
"attempt_count": job.attempt_count,
"postbox_attempt_count": getattr(job, "postbox_attempt_count", 0),
"print_attempt_count": getattr(job, "print_attempt_count", 0),
"postbox_target_count": len(
getattr(job, "resolved_postbox_targets", None) or []
),
"print_output": public_campaign_payload(
getattr(job, "resolved_print_output", None)
),
"delivery_provenance": public_campaign_payload(
getattr(job, "delivery_provenance", None) or {}
),
"queued_at": job.queued_at.isoformat() if job.queued_at else None,
"outcome_unknown_at": job.outcome_unknown_at.isoformat() if job.outcome_unknown_at else None,
"sent_at": job.sent_at.isoformat() if job.sent_at else None,
@@ -713,6 +727,7 @@ def _job_evidence_row(
"message_id_header": job.message_id_header,
**_job_evidence_addresses(recipients),
"postbox_targets": _job_postbox_target_summary(job),
**_job_print_and_route_evidence(job),
"attachment_names": _attachment_names(job.resolved_attachments),
**_job_attempt_evidence(latest_smtp=latest_smtp, latest_imap=latest_imap),
"latest_message_action_kind": (
@@ -766,6 +781,45 @@ def _job_postbox_target_summary(job: CampaignJob) -> str:
)
def _job_print_and_route_evidence(job: CampaignJob) -> dict[str, Any]:
output = (
job.resolved_print_output
if isinstance(getattr(job, "resolved_print_output", None), dict)
else {}
)
artifact = output.get("artifact") if isinstance(output.get("artifact"), dict) else {}
provenance = (
job.delivery_provenance
if isinstance(getattr(job, "delivery_provenance", None), dict)
else {}
)
selected_route = (
provenance.get("selected_route")
if isinstance(provenance.get("selected_route"), dict)
else {}
)
return {
"distribution_list_id": provenance.get("list_id"),
"distribution_list_revision": provenance.get("list_revision"),
"distribution_snapshot_id": provenance.get("snapshot_id"),
"distribution_expansion_hash": provenance.get("expansion_hash"),
"distribution_recipient_key": provenance.get("recipient_key"),
"selected_route_channel": selected_route.get("channel"),
"selected_route_target_key": selected_route.get("target_key"),
"route_reason": provenance.get("route_reason"),
"print_render_id": output.get("render_id"),
"print_template_id": output.get("template_id"),
"print_template_revision_id": output.get("template_revision_id"),
"print_template_hash": output.get("template_hash"),
"print_input_hash": output.get("input_hash"),
"print_output_sha256": output.get("output_sha256"),
"print_artifact_kind": artifact.get("kind"),
"print_artifact_file_id": artifact.get("file_asset_id"),
"print_artifact_download_path": artifact.get("download_path"),
"print_actor_account_id": output.get("actor_account_id"),
}
def _iso_timestamp(value: datetime | None) -> str | None:
return value.isoformat() if value else None
@@ -1421,6 +1475,7 @@ def _campaign_report_status_counts_from_aggregate(
"queue": dict(aggregate.queue),
"send": dict(aggregate.send),
"postbox": dict(aggregate.postbox),
"print": dict(aggregate.print),
"imap": dict(aggregate.imap),
}
@@ -1435,6 +1490,7 @@ def _campaign_report_cards_from_aggregate(
send_counts.get("sent", 0)
+ send_counts.get("smtp_accepted", 0)
+ send_counts.get("postbox_accepted", 0)
+ send_counts.get("print_accepted", 0)
+ send_counts.get("delivered", 0)
+ send_counts.get("partially_accepted", 0)
)
@@ -1460,6 +1516,7 @@ def _campaign_report_cards_from_aggregate(
+ send_counts.get("smtp_accepted", 0)
),
"postbox_accepted": send_counts.get("postbox_accepted", 0),
"print_accepted": send_counts.get("print_accepted", 0),
"delivered": send_counts.get("delivered", 0),
"partially_accepted": send_counts.get("partially_accepted", 0),
"failed": failed,
@@ -1594,11 +1651,31 @@ def generate_jobs_csv(
"send_status",
"delivery_channel_policy",
"postbox_status",
"print_status",
"imap_status",
"attempt_count",
"postbox_attempt_count",
"print_attempt_count",
"postbox_target_count",
"postbox_targets",
"distribution_list_id",
"distribution_list_revision",
"distribution_snapshot_id",
"distribution_expansion_hash",
"distribution_recipient_key",
"selected_route_channel",
"selected_route_target_key",
"route_reason",
"print_render_id",
"print_template_id",
"print_template_revision_id",
"print_template_hash",
"print_input_hash",
"print_output_sha256",
"print_artifact_kind",
"print_artifact_file_id",
"print_artifact_download_path",
"print_actor_account_id",
"queued_at",
"outcome_unknown_at",
"sent_at",