|
|
|
@@ -26,6 +26,7 @@ _CAMPAIGN_USER_SCOPES = (
|
|
|
|
"campaigns:recipient:export",
|
|
|
|
"campaigns:recipient:export",
|
|
|
|
"campaigns:report:read",
|
|
|
|
"campaigns:report:read",
|
|
|
|
"campaigns:report:export",
|
|
|
|
"campaigns:report:export",
|
|
|
|
|
|
|
|
"campaigns:report:send",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
_CAMPAIGN_HELP_CONTEXTS = (
|
|
|
|
_CAMPAIGN_HELP_CONTEXTS = (
|
|
|
|
@@ -108,10 +109,10 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
body="A new campaign starts with one editable working version. Creating it does not grant access to Mail profiles, managed files, address sources, or delivery actions; those remain separately authorized.",
|
|
|
|
body="A new campaign starts with one editable working version. Creating it does not grant access to Mail profiles, managed files, address sources, or delivery actions; those remain separately authorized.",
|
|
|
|
order=30,
|
|
|
|
order=30,
|
|
|
|
audience=("campaign_manager", "campaign_author"),
|
|
|
|
audience=("campaign_manager", "campaign_author"),
|
|
|
|
required_scopes=("campaigns:campaign:create",),
|
|
|
|
required_scopes=("campaigns:campaign:read", "campaigns:campaign:create"),
|
|
|
|
route="/campaigns",
|
|
|
|
route="/campaigns",
|
|
|
|
screen="Campaigns",
|
|
|
|
screen="Campaigns",
|
|
|
|
help_contexts=("campaigns.list", "campaign.overview"),
|
|
|
|
help_contexts=("campaigns.list",),
|
|
|
|
prerequisites=("You may create campaigns in the active tenant.",),
|
|
|
|
prerequisites=("You may create campaigns in the active tenant.",),
|
|
|
|
steps=(
|
|
|
|
steps=(
|
|
|
|
"Open Campaigns and select New campaign.",
|
|
|
|
"Open Campaigns and select New campaign.",
|
|
|
|
@@ -244,7 +245,7 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
"Save, validate, and build, then inspect the resolved attachment evidence in Review and send.",
|
|
|
|
"Save, validate, and build, then inspect the resolved attachment evidence in Review and send.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
outcome="Governed file versions linked to the campaign and frozen into the exact build evidence.",
|
|
|
|
outcome="Governed file versions linked to the campaign and frozen into the exact build evidence.",
|
|
|
|
verification="Attachment preview and the built-message evidence show the expected file versions, matches, checksums, and archive behavior.",
|
|
|
|
verification="Confirm the expected filenames, paths, matches, and link state in the Campaign UI. Exact managed versions and checksums remain retained in build evidence for authorized supporting tools.",
|
|
|
|
related_topic_ids=("campaigns.workflow.prepare-validate-and-build",),
|
|
|
|
related_topic_ids=("campaigns.workflow.prepare-validate-and-build",),
|
|
|
|
links=(
|
|
|
|
links=(
|
|
|
|
DocumentationLink(label="Campaigns", href="/campaigns", kind="runtime"),
|
|
|
|
DocumentationLink(label="Campaigns", href="/campaigns", kind="runtime"),
|
|
|
|
@@ -269,7 +270,7 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
),
|
|
|
|
),
|
|
|
|
route="/campaigns/{campaign_id}/review",
|
|
|
|
route="/campaigns/{campaign_id}/review",
|
|
|
|
screen="Review and send",
|
|
|
|
screen="Review and send",
|
|
|
|
help_contexts=("campaign.review-send",),
|
|
|
|
help_contexts=(),
|
|
|
|
prerequisites=(
|
|
|
|
prerequisites=(
|
|
|
|
"The selected version is validated, locked, built, and reviewed.",
|
|
|
|
"The selected version is validated, locked, built, and reviewed.",
|
|
|
|
"Its Mail profile remains available and authorized for the current campaign context.",
|
|
|
|
"Its Mail profile remains available and authorized for the current campaign context.",
|
|
|
|
@@ -277,20 +278,21 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
),
|
|
|
|
),
|
|
|
|
steps=(
|
|
|
|
steps=(
|
|
|
|
"Open Review and send and confirm the selected version, build, recipients, warnings, and review completion.",
|
|
|
|
"Open Review and send and confirm the selected version, build, recipients, warnings, and review completion.",
|
|
|
|
"Run the queue estimate or dry run when offered and resolve every blocker.",
|
|
|
|
"Use an authorized supporting client to run the queue dry run and resolve every blocker.",
|
|
|
|
"Queue the build for background delivery; use the synchronous path only for an intentionally small controlled run.",
|
|
|
|
"Invoke the Queue action through that client so background workers can process the build.",
|
|
|
|
"Open the campaign Report and monitor queue, SMTP, IMAP, failure, and uncertain-outcome counts.",
|
|
|
|
"Open the campaign Report and monitor queue, SMTP, IMAP, failure, and uncertain-outcome counts.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
outcome="Eligible jobs queued with an auditable execution snapshot and protected delivery state.",
|
|
|
|
outcome="Eligible jobs queued with an auditable execution snapshot and protected delivery state.",
|
|
|
|
verification="The Report shows the selected version's jobs as queued or progressing, without changing any accepted job back to retryable.",
|
|
|
|
verification="The Report shows the selected version's jobs as queued or progressing, without changing any accepted job back to retryable.",
|
|
|
|
related_topic_ids=("campaigns.workflow.complete-review", "campaigns.workflow.retry-and-reconcile"),
|
|
|
|
related_topic_ids=("campaigns.workflow.complete-review", "campaigns.workflow.retry-and-reconcile"),
|
|
|
|
related_modules=("mail", "notifications"),
|
|
|
|
related_modules=("mail", "notifications"),
|
|
|
|
|
|
|
|
limitations=("The current Campaign Web UI does not expose the Queue action; use an authorized supporting client or API.",),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_workflow_topic(
|
|
|
|
_workflow_topic(
|
|
|
|
topic_id="campaigns.workflow.send-small-controlled-run",
|
|
|
|
topic_id="campaigns.workflow.send-small-controlled-run",
|
|
|
|
title="Send a small controlled campaign run",
|
|
|
|
title="Send a campaign immediately",
|
|
|
|
summary="Use the synchronous delivery action only for a deliberately small, fully reviewed campaign build.",
|
|
|
|
summary="Run the eligible jobs synchronously only after deliberately confirming that the reviewed campaign is small enough for an interactive request.",
|
|
|
|
body="The synchronous action is intentionally bounded and does not replace the worker queue for ordinary batches. It preserves the same protected SMTP, IMAP, retry, and uncertain-outcome rules.",
|
|
|
|
body="The current synchronous action does not enforce a server-side job-count limit. It must not replace the worker queue for ordinary batches, but it does preserve the same protected SMTP, IMAP, retry, and uncertain-outcome rules.",
|
|
|
|
order=36,
|
|
|
|
order=36,
|
|
|
|
audience=("campaign_sender", "campaign_operator"),
|
|
|
|
audience=("campaign_sender", "campaign_operator"),
|
|
|
|
required_modules=("campaigns", "mail"),
|
|
|
|
required_modules=("campaigns", "mail"),
|
|
|
|
@@ -306,7 +308,7 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
help_contexts=("campaign.review-send",),
|
|
|
|
help_contexts=("campaign.review-send",),
|
|
|
|
prerequisites=(
|
|
|
|
prerequisites=(
|
|
|
|
"The selected version is validated, locked, built, and reviewed.",
|
|
|
|
"The selected version is validated, locked, built, and reviewed.",
|
|
|
|
"The run is small enough for the configured synchronous limit.",
|
|
|
|
"An operator has deliberately confirmed that this is a small controlled run; the server does not currently enforce that bound.",
|
|
|
|
"Its Mail profile remains available and authorized for the current campaign context.",
|
|
|
|
"Its Mail profile remains available and authorized for the current campaign context.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
steps=(
|
|
|
|
steps=(
|
|
|
|
@@ -315,19 +317,20 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
"Select Send now and confirm the protected delivery action.",
|
|
|
|
"Select Send now and confirm the protected delivery action.",
|
|
|
|
"Open Report and inspect each resulting delivery state before attempting any recovery action.",
|
|
|
|
"Open Report and inspect each resulting delivery state before attempting any recovery action.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
outcome="A bounded real delivery run with recipient-level attempt and outcome evidence.",
|
|
|
|
outcome="A synchronous real delivery run with recipient-level attempt and outcome evidence.",
|
|
|
|
verification="The Report distinguishes accepted, failed, unattempted, cancelled, and outcome-unknown jobs and retains their attempt history.",
|
|
|
|
verification="The Report distinguishes accepted, failed, unattempted, cancelled, and outcome-unknown jobs and retains their attempt history.",
|
|
|
|
related_topic_ids=("campaigns.workflow.queue-delivery", "campaigns.workflow.retry-and-reconcile"),
|
|
|
|
related_topic_ids=("campaigns.workflow.queue-delivery", "campaigns.workflow.retry-and-reconcile"),
|
|
|
|
related_modules=("mail",),
|
|
|
|
related_modules=("mail",),
|
|
|
|
|
|
|
|
limitations=("No server-side synchronous job-count limit is currently enforced; use Queue and workers for ordinary batches.",),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_workflow_topic(
|
|
|
|
_workflow_topic(
|
|
|
|
topic_id="campaigns.workflow.view-delivery-report",
|
|
|
|
topic_id="campaigns.workflow.view-delivery-report",
|
|
|
|
title="Review a campaign delivery report",
|
|
|
|
title="Review campaign delivery details",
|
|
|
|
summary="Inspect aggregate delivery outcomes and, when separately authorized, recipient-level job evidence.",
|
|
|
|
summary="Inspect delivery totals and recipient-level job evidence in the current Campaign Report UI.",
|
|
|
|
body="Report access does not imply access to recipient details or diagnostics. Aggregate business state remains useful for readers, while recipient rows and infrastructure diagnostics require separate authority.",
|
|
|
|
body="The current Report UI requires recipient-read authority in addition to report access. Infrastructure diagnostics remain separately authorized, and every report action is still checked by the server even where the current UI has not yet hidden an unavailable control.",
|
|
|
|
order=37,
|
|
|
|
order=37,
|
|
|
|
audience=("campaign_reader", "campaign_manager", "campaign_reviewer", "campaign_sender"),
|
|
|
|
audience=("campaign_reader", "campaign_manager", "campaign_reviewer", "campaign_sender"),
|
|
|
|
required_scopes=("campaigns:campaign:read", "campaigns:report:read"),
|
|
|
|
required_scopes=("campaigns:campaign:read", "campaigns:report:read", "campaigns:recipient:read"),
|
|
|
|
route="/campaigns/{campaign_id}/report",
|
|
|
|
route="/campaigns/{campaign_id}/report",
|
|
|
|
screen="Campaign Report",
|
|
|
|
screen="Campaign Report",
|
|
|
|
help_contexts=("campaign.report",),
|
|
|
|
help_contexts=("campaign.report",),
|
|
|
|
@@ -335,12 +338,13 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
steps=(
|
|
|
|
steps=(
|
|
|
|
"Open the campaign and select Report.",
|
|
|
|
"Open the campaign and select Report.",
|
|
|
|
"Review totals for queued, accepted, failed, cancelled, unattempted, and outcome-unknown jobs.",
|
|
|
|
"Review totals for queued, accepted, failed, cancelled, unattempted, and outcome-unknown jobs.",
|
|
|
|
"Inspect recipient-level rows and attempt history only when those details are available to your role.",
|
|
|
|
"Inspect the authorized recipient-level rows and attempt history.",
|
|
|
|
"Treat outcome-unknown jobs as unresolved and hand them to an authorized operator for evidence-backed reconciliation.",
|
|
|
|
"Treat outcome-unknown jobs as unresolved and hand them to an authorized operator for evidence-backed reconciliation.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
outcome="A role-appropriate view of delivery progress and outcomes without exposing system internals.",
|
|
|
|
outcome="A recipient-aware view of delivery progress and outcomes with diagnostics still separately protected.",
|
|
|
|
verification="The report totals match the selected campaign version, and recipient or diagnostic details are absent unless separately authorized.",
|
|
|
|
verification="The report totals and job rows match the selected campaign version, and infrastructure diagnostics are absent unless separately authorized.",
|
|
|
|
related_topic_ids=("campaigns.workflow.retry-and-reconcile", "campaigns.workflow.export-delivery-report"),
|
|
|
|
related_topic_ids=("campaigns.workflow.retry-and-reconcile", "campaigns.workflow.export-delivery-report"),
|
|
|
|
|
|
|
|
limitations=("An aggregate-only Report Web UI for readers without recipient-read authority is not implemented yet.",),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_workflow_topic(
|
|
|
|
_workflow_topic(
|
|
|
|
topic_id="campaigns.workflow.export-delivery-report",
|
|
|
|
topic_id="campaigns.workflow.export-delivery-report",
|
|
|
|
@@ -351,7 +355,9 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
audience=("campaign_report_exporter", "campaign_auditor"),
|
|
|
|
audience=("campaign_report_exporter", "campaign_auditor"),
|
|
|
|
required_scopes=(
|
|
|
|
required_scopes=(
|
|
|
|
"campaigns:campaign:read",
|
|
|
|
"campaigns:campaign:read",
|
|
|
|
|
|
|
|
"campaigns:report:read",
|
|
|
|
"campaigns:report:export",
|
|
|
|
"campaigns:report:export",
|
|
|
|
|
|
|
|
"campaigns:recipient:read",
|
|
|
|
"campaigns:recipient:export",
|
|
|
|
"campaigns:recipient:export",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
route="/campaigns/{campaign_id}/report",
|
|
|
|
route="/campaigns/{campaign_id}/report",
|
|
|
|
@@ -418,9 +424,12 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
"Reopen or query the campaign and confirm its state is Archived.",
|
|
|
|
"Reopen or query the campaign and confirm its state is Archived.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
outcome="An archived campaign whose versions, reports, and audit evidence remain preserved.",
|
|
|
|
outcome="An archived campaign whose versions, reports, and audit evidence remain preserved.",
|
|
|
|
verification="The campaign state is Archived and its report and audit evidence remain available to authorized readers.",
|
|
|
|
verification="The campaign state is Archived and its report remains available. Ask an authorized audit reader to verify the platform audit event.",
|
|
|
|
related_topic_ids=("campaigns.workflow.view-delivery-report", "campaigns.workflow.delete-untouched-draft"),
|
|
|
|
related_topic_ids=("campaigns.workflow.view-delivery-report", "campaigns.workflow.delete-untouched-draft"),
|
|
|
|
limitations=("The current Campaign Web UI does not yet expose the archive action; use an authorized supporting client or API.",),
|
|
|
|
limitations=(
|
|
|
|
|
|
|
|
"The current Campaign Web UI does not yet expose the archive action; use an authorized supporting client or API.",
|
|
|
|
|
|
|
|
"The Campaign-local Audit page is not integrated yet; audit verification uses the platform audit surface or API.",
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_workflow_topic(
|
|
|
|
_workflow_topic(
|
|
|
|
topic_id="campaigns.workflow.delete-untouched-draft",
|
|
|
|
topic_id="campaigns.workflow.delete-untouched-draft",
|
|
|
|
@@ -444,9 +453,12 @@ CAMPAIGN_USER_DOCUMENTATION = (
|
|
|
|
"Verify that the deleted draft no longer appears in active Campaigns and that the audit event exists.",
|
|
|
|
"Verify that the deleted draft no longer appears in active Campaigns and that the audit event exists.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
outcome="An eligible untouched draft removed from active Campaigns with attributable deletion evidence.",
|
|
|
|
outcome="An eligible untouched draft removed from active Campaigns with attributable deletion evidence.",
|
|
|
|
verification="The draft is no longer returned as an active campaign, while an authorized audit reader can verify who deleted it and when.",
|
|
|
|
verification="The draft is no longer returned as an active campaign. Ask an authorized audit reader to verify who deleted it and when through the platform audit surface.",
|
|
|
|
related_topic_ids=("campaigns.workflow.archive-campaign",),
|
|
|
|
related_topic_ids=("campaigns.workflow.archive-campaign",),
|
|
|
|
limitations=("The current Campaign Web UI does not yet expose the delete action; use an authorized supporting client or API.",),
|
|
|
|
limitations=(
|
|
|
|
|
|
|
|
"The current Campaign Web UI does not yet expose the delete action; use an authorized supporting client or API.",
|
|
|
|
|
|
|
|
"The Campaign-local Audit page is not integrated yet; audit verification uses the platform audit surface or API.",
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -465,19 +477,21 @@ def documentation_topics(context: DocumentationContext) -> tuple[DocumentationTo
|
|
|
|
if principal is None or not _has_any_scope(principal, _CAMPAIGN_USER_SCOPES):
|
|
|
|
if principal is None or not _has_any_scope(principal, _CAMPAIGN_USER_SCOPES):
|
|
|
|
return ()
|
|
|
|
return ()
|
|
|
|
|
|
|
|
|
|
|
|
current_configuration = list(_actor_capabilities(principal))
|
|
|
|
mail_available = _integration_available(context.registry, _MAIL_INTEGRATION)
|
|
|
|
|
|
|
|
current_configuration = list(_actor_capabilities(principal, mail_available=mail_available))
|
|
|
|
integration_configuration, integration_limitations = _integration_summary(context.registry, principal)
|
|
|
|
integration_configuration, integration_limitations = _integration_summary(context.registry, principal)
|
|
|
|
current_configuration.extend(integration_configuration)
|
|
|
|
current_configuration.extend(integration_configuration)
|
|
|
|
limitations = [
|
|
|
|
limitations = [
|
|
|
|
"Access to a particular campaign still depends on its owner or sharing rules and on the campaign's current state.",
|
|
|
|
"Access to a particular campaign still depends on its owner or sharing rules and on the campaign's current state.",
|
|
|
|
|
|
|
|
"Profile, file, and address pickers re-evaluate the actual resources visible in the selected campaign; this overview does not claim that an eligible item currently exists.",
|
|
|
|
*integration_limitations,
|
|
|
|
*integration_limitations,
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
DocumentationTopic(
|
|
|
|
DocumentationTopic(
|
|
|
|
id="campaigns.current-composition",
|
|
|
|
id="campaigns.current-composition",
|
|
|
|
title="Campaign capabilities available to you",
|
|
|
|
title="Your Campaign role and installed composition",
|
|
|
|
summary="This guide reflects your current Campaign actions and the connected services available in this installation.",
|
|
|
|
summary="This guide separates actions authorized by your role from optional services connected to Campaign in this installation.",
|
|
|
|
body=_composition_body(current_configuration, limitations),
|
|
|
|
body=_composition_body(current_configuration, limitations),
|
|
|
|
layer="configured",
|
|
|
|
layer="configured",
|
|
|
|
documentation_types=("user",),
|
|
|
|
documentation_types=("user",),
|
|
|
|
@@ -504,7 +518,7 @@ def documentation_topics(context: DocumentationContext) -> tuple[DocumentationTo
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _actor_capabilities(principal: object) -> tuple[str, ...]:
|
|
|
|
def _actor_capabilities(principal: object, *, mail_available: bool) -> tuple[str, ...]:
|
|
|
|
capabilities: list[str] = []
|
|
|
|
capabilities: list[str] = []
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:read",), "Open campaigns shared with you and inspect their business state.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:read",), "Open campaigns shared with you and inspect their business state.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:create",), "Create new campaigns.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:create",), "Create new campaigns.")
|
|
|
|
@@ -516,12 +530,14 @@ def _actor_capabilities(principal: object) -> tuple[str, ...]:
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:validate",), "Validate campaign inputs and resolve blocking issues.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:validate",), "Validate campaign inputs and resolve blocking issues.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:build",), "Build exact recipient messages for review.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:build",), "Build exact recipient messages for review.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:review",), "Record review completion for an exact build.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:review",), "Record review completion for an exact build.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:send_test",), "Run authorized delivery verification tools.")
|
|
|
|
if mail_available:
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:queue",), "Queue an eligible reviewed campaign for controlled delivery.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:send_test",), "Run authorized delivery verification tools.")
|
|
|
|
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:queue",), "Queue an eligible reviewed campaign for controlled delivery.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:control",), "Pause, resume, or cancel eligible delivery jobs.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:control",), "Pause, resume, or cancel eligible delivery jobs.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:send",), "Start an eligible real delivery run.")
|
|
|
|
if mail_available:
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:retry",), "Retry delivery jobs whose recorded state permits a retry.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:send",), "Start an eligible real delivery run.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:reconcile",), "Reconcile delivery effects whose outcome is uncertain.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:retry",), "Retry delivery jobs whose recorded state permits a retry.")
|
|
|
|
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:reconcile",), "Reconcile delivery effects whose outcome is uncertain.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:report:read",), "View authorized campaign delivery reports.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:report:read",), "View authorized campaign delivery reports.")
|
|
|
|
_append_if(
|
|
|
|
_append_if(
|
|
|
|
capabilities,
|
|
|
|
capabilities,
|
|
|
|
@@ -533,6 +549,14 @@ def _actor_capabilities(principal: object) -> tuple[str, ...]:
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:share",), "Manage explicit access to eligible campaigns.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:share",), "Manage explicit access to eligible campaigns.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:archive",), "Archive eligible campaigns while retaining their evidence.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:archive",), "Archive eligible campaigns while retaining their evidence.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:delete",), "Delete eligible untouched drafts.")
|
|
|
|
_append_if(capabilities, principal, ("campaigns:campaign:delete",), "Delete eligible untouched drafts.")
|
|
|
|
|
|
|
|
if mail_available:
|
|
|
|
|
|
|
|
_append_if(
|
|
|
|
|
|
|
|
capabilities,
|
|
|
|
|
|
|
|
principal,
|
|
|
|
|
|
|
|
("campaigns:report:send", "campaigns:report:read", "mail:profile:use"),
|
|
|
|
|
|
|
|
"Send an authorized campaign report through a Mail profile.",
|
|
|
|
|
|
|
|
require_all=True,
|
|
|
|
|
|
|
|
)
|
|
|
|
return tuple(capabilities)
|
|
|
|
return tuple(capabilities)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -541,8 +565,18 @@ def _integration_summary(registry: object, principal: object) -> tuple[tuple[str
|
|
|
|
limitations: list[str] = []
|
|
|
|
limitations: list[str] = []
|
|
|
|
|
|
|
|
|
|
|
|
mail_available = _integration_available(registry, _MAIL_INTEGRATION)
|
|
|
|
mail_available = _integration_available(registry, _MAIL_INTEGRATION)
|
|
|
|
if mail_available and _has_scope(principal, "mail:profile:use"):
|
|
|
|
can_select_mail_profile = _has_all_scopes(
|
|
|
|
configured.append("Mail profile selection is available for Campaign delivery steps you are authorized to perform.")
|
|
|
|
principal,
|
|
|
|
|
|
|
|
("campaigns:campaign:read", "campaigns:campaign:update", "mail:profile:use"),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
can_deliver_with_mail = _has_scope(principal, "mail:profile:use") and _has_any_scope(
|
|
|
|
|
|
|
|
principal,
|
|
|
|
|
|
|
|
("campaigns:campaign:queue", "campaigns:campaign:send", "campaigns:campaign:retry"),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if mail_available and can_select_mail_profile:
|
|
|
|
|
|
|
|
configured.append("Installed composition: Campaign can open Mail's actor-filtered profile picker while you edit; eligible profiles are resolved in the selected campaign context.")
|
|
|
|
|
|
|
|
elif mail_available and can_deliver_with_mail:
|
|
|
|
|
|
|
|
configured.append("Installed composition: Mail-backed Campaign delivery is connected, and the selected profile is re-authorized for each delivery action.")
|
|
|
|
elif mail_available:
|
|
|
|
elif mail_available:
|
|
|
|
limitations.append("Mail delivery is configured, but selecting a Mail profile is not included in your current tasks.")
|
|
|
|
limitations.append("Mail delivery is configured, but selecting a Mail profile is not included in your current tasks.")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
@@ -555,12 +589,19 @@ def _integration_summary(registry: object, principal: object) -> tuple[tuple[str
|
|
|
|
)
|
|
|
|
)
|
|
|
|
can_link_files = _has_all_scopes(
|
|
|
|
can_link_files = _has_all_scopes(
|
|
|
|
principal,
|
|
|
|
principal,
|
|
|
|
("campaigns:campaign:validate", "campaigns:recipient:read", "files:file:read", "files:file:share"),
|
|
|
|
(
|
|
|
|
|
|
|
|
"campaigns:campaign:read",
|
|
|
|
|
|
|
|
"campaigns:campaign:update",
|
|
|
|
|
|
|
|
"campaigns:campaign:validate",
|
|
|
|
|
|
|
|
"campaigns:recipient:read",
|
|
|
|
|
|
|
|
"files:file:read",
|
|
|
|
|
|
|
|
"files:file:share",
|
|
|
|
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if files_available and can_link_files:
|
|
|
|
if files_available and can_link_files:
|
|
|
|
configured.append("Managed file versions can be selected, previewed, and linked as governed campaign attachments.")
|
|
|
|
configured.append("Installed composition: Managed file versions can be selected, previewed, and linked as governed campaign attachments when the selected campaign and files pass their resource checks.")
|
|
|
|
elif files_available and can_preview_files:
|
|
|
|
elif files_available and can_preview_files:
|
|
|
|
configured.append("Managed campaign attachments can be previewed; linking a version requires separate attachment-sharing authority.")
|
|
|
|
configured.append("Installed composition: Managed campaign attachments can be previewed; linking a version requires campaign-update, validation, and attachment-sharing authority.")
|
|
|
|
elif files_available:
|
|
|
|
elif files_available:
|
|
|
|
limitations.append("Managed attachments are configured, but they are not included in your current Campaign tasks.")
|
|
|
|
limitations.append("Managed attachments are configured, but they are not included in your current Campaign tasks.")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
@@ -569,20 +610,30 @@ def _integration_summary(registry: object, principal: object) -> tuple[tuple[str
|
|
|
|
lookup_available = _integration_available(registry, _ADDRESSES_LOOKUP_INTEGRATION)
|
|
|
|
lookup_available = _integration_available(registry, _ADDRESSES_LOOKUP_INTEGRATION)
|
|
|
|
source_available = _integration_available(registry, _ADDRESSES_SOURCE_INTEGRATION)
|
|
|
|
source_available = _integration_available(registry, _ADDRESSES_SOURCE_INTEGRATION)
|
|
|
|
if lookup_available and _has_scope(principal, "campaigns:recipient:read"):
|
|
|
|
if lookup_available and _has_scope(principal, "campaigns:recipient:read"):
|
|
|
|
configured.append("Address records can be looked up while preparing recipients.")
|
|
|
|
configured.append("Installed composition: Address records can be looked up while preparing recipients.")
|
|
|
|
elif lookup_available:
|
|
|
|
elif lookup_available:
|
|
|
|
limitations.append("Address lookup is configured, but recipient inspection is not included in your current Campaign tasks.")
|
|
|
|
limitations.append("Address lookup is configured, but recipient inspection is not included in your current Campaign tasks.")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
limitations.append("Connected address lookup is not available in the current composition.")
|
|
|
|
limitations.append("Connected address lookup is not available in the current composition.")
|
|
|
|
if source_available and _has_scope(principal, "campaigns:recipient:import"):
|
|
|
|
can_import_source = _has_all_scopes(
|
|
|
|
configured.append("An authorized address source can be copied into a campaign as a traceable recipient snapshot.")
|
|
|
|
principal,
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
"campaigns:campaign:read",
|
|
|
|
|
|
|
|
"campaigns:campaign:update",
|
|
|
|
|
|
|
|
"campaigns:recipient:read",
|
|
|
|
|
|
|
|
"campaigns:recipient:write",
|
|
|
|
|
|
|
|
"campaigns:recipient:import",
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if source_available and can_import_source:
|
|
|
|
|
|
|
|
configured.append("Installed composition: The actor-filtered Addresses source picker can copy a selected source into a campaign as a traceable recipient snapshot.")
|
|
|
|
elif source_available:
|
|
|
|
elif source_available:
|
|
|
|
limitations.append("Connected recipient sources are configured, but source import is not included in your current Campaign tasks.")
|
|
|
|
limitations.append("Connected recipient sources are configured, but source import is not included in your current Campaign tasks.")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
limitations.append("Connected recipient-source snapshots are not available; direct campaign imports remain available when authorized.")
|
|
|
|
limitations.append("Connected recipient-source snapshots are not available; direct campaign imports remain available when authorized.")
|
|
|
|
|
|
|
|
|
|
|
|
if _integration_available(registry, _NOTIFICATIONS_INTEGRATION):
|
|
|
|
if _integration_available(registry, _NOTIFICATIONS_INTEGRATION):
|
|
|
|
configured.append("In-app notifications can report important Campaign delivery status changes.")
|
|
|
|
configured.append("Installed composition: In-app notifications can report important Campaign delivery status changes.")
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
limitations.append("Automatic in-app Campaign status notifications are not configured.")
|
|
|
|
limitations.append("Automatic in-app Campaign status notifications are not configured.")
|
|
|
|
|
|
|
|
|
|
|
|
@@ -599,7 +650,7 @@ def _append_if(
|
|
|
|
) -> None:
|
|
|
|
) -> None:
|
|
|
|
allowed = _has_all_scopes(principal, scopes) if require_all else _has_any_scope(principal, scopes)
|
|
|
|
allowed = _has_all_scopes(principal, scopes) if require_all else _has_any_scope(principal, scopes)
|
|
|
|
if allowed:
|
|
|
|
if allowed:
|
|
|
|
target.append(text)
|
|
|
|
target.append(f"Role authorization: {text}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _has_scope(principal: object, scope: str) -> bool:
|
|
|
|
def _has_scope(principal: object, scope: str) -> bool:
|
|
|
|
@@ -650,6 +701,5 @@ def _registry_has_capability(registry: object, capability_name: str) -> bool:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _composition_body(current_configuration: list[str], limitations: list[str]) -> str:
|
|
|
|
def _composition_body(current_configuration: list[str], limitations: list[str]) -> str:
|
|
|
|
configured_lines = "\n".join(f"- {item}" for item in current_configuration)
|
|
|
|
del current_configuration, limitations
|
|
|
|
limitation_lines = "\n".join(f"- {item}" for item in limitations)
|
|
|
|
return "The facts below are calculated for the current actor and installed module contracts. They do not bypass campaign ownership, sharing, state, or operation-time resource and policy checks."
|
|
|
|
return f"Available in your current Campaign workspace:\n{configured_lines}\n\nLimits that still apply:\n{limitation_lines}"
|
|
|
|
|
|
|
|
|