Resolve campaign worker jobs to tenants

This commit is contained in:
2026-08-04 09:29:35 +02:00
parent dc63e35550
commit f98fe06143
3 changed files with 18 additions and 1 deletions
@@ -690,6 +690,10 @@ def policy_context_capability(context: object) -> CampaignPolicyContextService:
class CampaignDeliveryTaskService(CampaignDeliveryTaskProvider): class CampaignDeliveryTaskService(CampaignDeliveryTaskProvider):
def tenant_id_for_job(self, session: object, *, job_id: str) -> str | None:
job = session.get(CampaignJob, job_id) # type: ignore[attr-defined]
return job.tenant_id if job is not None else None
def send_campaign_job(self, session: object, *, job_id: str, enqueue_imap_task: bool = True) -> Mapping[str, object]: def send_campaign_job(self, session: object, *, job_id: str, enqueue_imap_task: bool = True) -> Mapping[str, object]:
from govoplan_campaign.backend.sending.jobs import send_campaign_job from govoplan_campaign.backend.sending.jobs import send_campaign_job
+1 -1
View File
@@ -796,7 +796,7 @@ manifest = ModuleManifest(
id="campaigns.mail-profile-operations", id="campaigns.mail-profile-operations",
title="Operate profile-backed campaign delivery", title="Operate profile-backed campaign delivery",
summary="Workers re-authorize and resolve Mail profiles at execution time while Campaign retains only opaque Mail-owned revisions and outcomes.", summary="Workers re-authorize and resolve Mail profiles at execution time while Campaign retains only opaque Mail-owned revisions and outcomes.",
body="A legacy snapshot, unauthorized or inactive profile, profile-reference mismatch, or changed SMTP/IMAP transport revision stops delivery. Preserve the record, migrate or correct the profile selection, revalidate, rebuild, and only then queue again. Password-only rotation remains possible without copying secrets into Campaign. Uncertain SMTP and IMAP effects remain blocked until an evidence-backed operator reconciliation.", body="A legacy snapshot, unauthorized or inactive profile, profile-reference mismatch, or changed SMTP/IMAP transport revision stops delivery. Preserve the record, migrate or correct the profile selection, revalidate, rebuild, and only then queue again. Password-only rotation remains possible without copying secrets into Campaign. Uncertain SMTP and IMAP effects remain blocked until an evidence-backed operator reconciliation. If Campaign becomes unavailable to the tenant after a job was accepted, the worker leaves the job untouched and reports an operator action instead of sending or dropping it.",
layer="configured", layer="configured",
documentation_types=("admin",), documentation_types=("admin",),
audience=("campaign_sender", "campaign_operator", "mail_admin"), audience=("campaign_sender", "campaign_operator", "mail_admin"),
+13
View File
@@ -60,6 +60,19 @@ class CampaignQueueSelectionTests(unittest.TestCase):
self.assertIsNotNone(capability) self.assertIsNotNone(capability)
configure.assert_called_once_with(registry=registry, settings=settings) configure.assert_called_once_with(registry=registry, settings=settings)
def test_delivery_task_capability_resolves_job_tenant_before_effect(self):
capability = delivery_tasks_capability(
SimpleNamespace(registry=object(), settings=object())
)
session = SimpleNamespace(
get=lambda _model, _job_id: SimpleNamespace(tenant_id="tenant-1")
)
self.assertEqual(
"tenant-1",
capability.tenant_id_for_job(session, job_id="job-1"),
)
def test_selects_queueable_jobs_without_reclassifying_retry_states(self): def test_selects_queueable_jobs_without_reclassifying_retry_states(self):
skipped_send = _job("1", send_status=JobSendStatus.FAILED_TEMPORARY.value) skipped_send = _job("1", send_status=JobSendStatus.FAILED_TEMPORARY.value)
skipped_queue = _job("2", queue_status=JobQueueStatus.PAUSED.value) skipped_queue = _job("2", queue_status=JobQueueStatus.PAUSED.value)