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
@@ -482,6 +482,7 @@ def _delivery_issues(
config: CampaignConfig,
*,
postbox_available: bool,
templates_available: bool,
) -> list[SemanticIssue]:
issues: list[SemanticIssue] = []
policies = _delivery_policies(config)
@@ -533,6 +534,46 @@ def _delivery_issues(
postbox_available=postbox_available,
)
)
if any(policy.uses_print for policy in policies):
if not templates_available:
issues.append(
_issue(
Severity.ERROR,
"templates_unavailable",
"Printable Campaign delivery requires the optional Templates renderer.",
"/delivery/print/template_id",
)
)
if not config.delivery.print.template_id:
issues.append(
_issue(
Severity.ERROR,
"print_template_missing",
"Select a published compatible template for printable Campaign output.",
"/delivery/print/template_id",
)
)
elif config.delivery.print.template_revision is None:
issues.append(
_issue(
Severity.ERROR,
"print_template_revision_missing",
"Printable delivery must pin one published template revision.",
"/delivery/print/template_revision",
)
)
for entry_index, entry in enumerate(_active_delivery_entries(config)):
if not effective_delivery_channel_policy(config, entry).uses_print:
continue
if entry.print_target is None:
issues.append(
_issue(
Severity.ERROR,
"print_target_missing",
"Printable delivery requires an explicit postal or internal-mail target.",
f"/entries/inline/{entry_index}/print_target",
)
)
return issues
@@ -784,6 +825,7 @@ def validate_campaign_config(
campaign_file: str | Path | None = None,
check_files: bool = False,
postbox_available: bool = False,
templates_available: bool = False,
) -> SemanticReport:
campaign_path = Path(campaign_file).resolve() if campaign_file else Path.cwd() / "campaign.json"
issues: list[SemanticIssue] = []
@@ -799,6 +841,7 @@ def validate_campaign_config(
_delivery_issues(
config,
postbox_available=postbox_available,
templates_available=templates_available,
)
)
issues.extend(_sender_issues(config))