Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-09-08 01:32:26 +02:00
parent 1b32427813
commit c51fc180fb
111 changed files with 6905 additions and 980 deletions
+104 -13
View File
@@ -30,9 +30,24 @@ been validated, built, reviewed, and locked.
for attempts, outcomes, and reconciliation.
- Confirm the effective Send now recipient-job limit. The safe default is 25;
use Queue for workers for ordinary batches or any run above that limit.
System administrators can explicitly configure 0500 under Administration →
SYSTEM → Campaign delivery; an explicit deployment ceiling remains binding,
and TENANT policy may only narrow the inherited limit. This setting affects
one synchronous request, not campaign size. It is audited and never sends
messages; large interactive requests may encounter proxy timeouts.
## Deliverability Preflight
The Mail server connection test checks that selected server and credential. It
does not authorize the campaign's sender, recipients, or resource selection.
SMTP runtime checks require the selected SMTP credential when policy forbids
inheritance, independently of IMAP. Sent-folder append checks IMAP credentials
independently; full campaign validation still checks both required selections.
Preflight errors distinguish Mail profile/credential policy, SMTP configuration,
authentication, and connectivity. Do not change TLS or credential policies merely
because a campaign preflight failed. A preflight rejection leaves staged jobs
uncommitted and starts no message delivery.
Before the first live send for a sender domain or mail-server profile:
- Confirm the selected SMTP identity matches the visible From/envelope sender
@@ -50,18 +65,25 @@ Before the first live send for a sender domain or mail-server profile:
## Queue And Send
1. Validate the version with file checks enabled.
1. Link all required managed files, then validate the version with file checks
enabled. Locking waits for a fresh attachment preview and asks for explicit
**Link and lock** confirmation when matches are not linked. A locked version
cannot change attachment links: use an editable version and repeat validation,
build and review rather than assuming unlinked files were included.
2. Build the version and inspect all blocking review items.
3. Queue only after the selected version is the intended immutable execution
version. Select **Queue for workers**, then verify the committed and
published counts.
4. Use **Send now** only if the exact eligible count is non-zero and at or below
the effective deployment/tenant limit shown on the page.
the effective deployment/system/tenant limit shown on the page.
5. In worker mode, verify queue counters move from queued/claimed/sending to a
terminal SMTP state.
6. If a synchronous request is used, keep Review and send open: it polls the
durable counters while the request runs. A rejection occurs before SMTP and
directs oversized runs to workers.
6. If a synchronous request is used, keep its blocking progress dialog open.
Only its small version-scoped persisted counters refresh; the workspace,
recipient list, attachment preview and full summary stay unchanged. Read-only
refresh failure retains the last counters and does not prove delivery failed.
A disconnected request may still be executing; never repeat it blindly.
Oversized initial runs are rejected before SMTP and directed to workers.
7. Review the SMTP batch line. `ready` means DNS/connectivity/TLS/auth preflight
succeeded. Connection and reconnect counts explain reuse. `paused` means a
systemic transport failure stopped the remaining jobs before their SMTP
@@ -97,9 +119,10 @@ unknown provider attempt merely to repair the other layer's state.
unavailable connectivity affect the batch rather than one recipient.
- `outcome_unknown`: Do not retry directly. Check SMTP logs, mailbox evidence, or
provider control panels, then reconcile as accepted or not sent.
- `claimed` or `sending` that does not progress: treat as a worker interruption.
Re-run worker handling or reconcile if SMTP may already have accepted the
message.
- `claimed` or `sending` that does not progress: investigate the owning runtime.
Duplicate worker handling leaves active state unchanged. Never infer from
elapsed time alone that SMTP did not accept the message. Use the fenced
**Recover interrupted claim** action described below when it is available.
- IMAP `appending`: A worker owns the durable append claim. Do not start a
second append; if the worker cannot finish, reconcile only after checking the
mailbox.
@@ -117,6 +140,65 @@ unknown provider attempt merely to repair the other layer's state.
- Add a note that identifies the evidence used, for example SMTP log line,
provider message ID, or operator ticket.
For pure-Mail SMTP and channel-specific IMAP operations, reconciliation updates
the original Campaign attempt, matching Campaign recovery operation and audit
record atomically under a fresh lease. A SMTP-only decision cannot resolve an
entire compound Mail/Postbox/Print operation; that ledger remains separately
unresolved until all of its effects are established. Campaign does
not rewrite Mail-owned nested provider-effect operations: those remain Mail's
separate evidence and operational responsibility. A failed audit or conflicting
claim must leave the prior unknown state intact.
### Recovery without workers
The Report offers explicit inline retry and continuation when workers are not
configured. Retry uses `campaigns:campaign:retry` plus
`campaigns:campaign:send`; continuation uses `campaigns:campaign:queue` plus
`campaigns:campaign:send`. Both use the canonical immutable jobs and ordinary
attempt ledger, not a separate one-message resend. Current Mail authorization,
review/approval, execution integrity, retry limits and rate limits still apply.
Each call is bounded by the effective synchronous recipient-job limit and
reports remaining eligible work. Continue explicitly until none remains; it
never selects accepted, excluded, active, uncertain or known failed jobs.
Known failures have their own explicit retry action. These actions do not
turn a long HTTP request into a background worker or guarantee exactly-once
SMTP when a provider acknowledgement is lost.
For an abandoned active SMTP or IMAP claim, the report exposes recovery only
after the original durable lease expires **and** the runtime registry proves
that its owner stopped or was replaced. A stale heartbeat is insufficient.
The opaque claim revision and original recovery evidence are rechecked under a
fresh lease. Recovery records the effect as **outcome unknown**, never not sent.
Then separately inspect external evidence and reconcile with a factual note
before any retry. This requires `campaigns:campaign:reconcile`. If the original
lease, evidence, or stopped-owner proof is missing, preserve the records and
investigate through Ops; do not edit delivery rows or force a lease expiry in
a real installation.
### Progress totals and Sent-folder batching
For each channel, **processed** includes successful, failed, uncertain and
cancelled outcomes. **In progress** is separate from pending, so the currently
sending/appending message remains visible. Paused SMTP work is also separate.
Excluded/non-requested channel work is outside the denominator. The endpoint
requires campaign read and object access and returns no recipient addresses,
message bodies, attachments or credentials. It is not the privacy-thresholded
aggregate Reports view and does not grant that view recipient access.
Append-to-Sent targets the selected campaign version, not all historical
versions. Each message remains a separately fenced, sequential IMAP APPEND.
Mail reuses the authenticated session and resolved folder for at most 100
messages or 300 seconds by default, then rotates the connection. Current
authorization, frozen transport revisions, credentials and recovery checks
still run for every message. A stale idle connection is checked before another
APPEND; an uncertain APPEND is never replayed. This removes repeated
connect/login/folder-list round trips, not the time needed to upload each EML.
It is not an atomic MULTIAPPEND transaction or parallel delivery.
SMTP and IMAP use the same progress dialog. An acknowledged operation remains
successful even if loading its follow-up diagnostics fails: use Reload to
refresh display, not to repeat the external effect.
## Fault Injection Checklist
Use mock infrastructure first, then repeat against the non-production real test
@@ -141,17 +223,21 @@ deliberately excluded.
The runner also terminates a dedicated OS process executing the registered
Campaign send task after complete DATA, then invokes the task in a fresh
process. The unfinished durable attempt must become `outcome_unknown` and the
endpoint must observe no second connection or DATA transaction. This covers
the worker task/process boundary but not a broker or daemon.
process. Redelivery must leave the active claim unchanged and the endpoint
must observe no second connection or DATA transaction. Only a subsequent
explicit, fenced recovery with test-fixture stopped-owner and expired-lease
proof may change the unfinished attempt to `outcome_unknown`. This covers the
worker task/process boundary but not a broker or daemon.
Run `dev/mail-testbed/run_celery_redelivery_acceptance.py` for the maintained
Redis/Celery delivery and broker redelivery boundary. It starts an isolated
Redis Compose service and real Celery workers, kills the first solo worker after complete DATA while the
late-ack task is unacknowledged, and requires the same task identity to reach a
replacement worker after Redis visibility recovery. Passing evidence also
requires durable `outcome_unknown`, an empty broker queue/unacked set, and
exactly one SMTP connection and DATA transaction. Raw worker logs and task,
requires unchanged active state on duplicate delivery, followed by explicit
fenced recovery to `outcome_unknown`, an empty broker queue/unacked set, and
exactly one SMTP connection and DATA transaction. Lease expiration is simulated
only in the isolated fixture after its owner was stopped. Raw worker logs and task,
database, endpoint, and credential identifiers are never retained.
That second runner proves local runner-supervised process replacement, not the
@@ -190,6 +276,11 @@ and retention.
## Reporting Checks
- The recipient-aware report shows every frozen To/Cc/Bcc address in authored
order, with a primary-address fallback only for old rows without that snapshot.
SMTP envelope evidence, not the old primary-only UI, establishes how many
recipients were actually offered to the provider. SMTP and IMAP diagnostics
use list filters and consistent translated labels.
- Partial delivery must show accepted, failed, and unknown counts separately.
- Excluded messages must show SMTP and IMAP as `skipped`, with skipped counts
and filters separate from unattempted or failed delivery.
+218 -5
View File
@@ -186,6 +186,11 @@ configured timeout. Opening the link never completes the Workflow.
password generator keeps its candidate separate from the form until **Use
password** is explicitly confirmed. Copying a candidate does not save or
submit it.
If legacy transport data is reported, choose **Migrate selected Mail
profile**, including when the existing profile selection is unchanged.
Follow a locked version's supported unlock or editable-successor action
before migration. Migration is explicit and audited, never sends mail, and
requires validation, build, and review again.
6. Save the editable version, validate the relevant sections, and resolve every
blocking issue. Warnings remain explicit review decisions.
7. Build the exact messages and inspect recipient, addressing, template,
@@ -195,11 +200,77 @@ If a selected optional module is absent, Campaign remains loadable and explains
which function is unavailable. It must not fail startup because Mail, Files, or
Addresses is not installed.
Opening or leaving Template without editing does not change the saved HTML or
mark the page dirty. Visual/source inspection and read-only changes likewise
do not require a save. Actual saves send only client-owned editor metadata. Review
and approval evidence remains server-owned and cannot be overwritten by an
ordinary editor save. Omitting that readable evidence from a save does not
delete it; normal version-lock and invalidation rules remain authoritative.
### Preserve recipient address order
In an individual or global address dialog, use the up/down actions to arrange
the addresses, then choose **Save** in the dialog. The campaign draft keeps that
order; saving no longer alphabetically sorts it. Duplicate email addresses keep
their first position, and pasted addresses append in their entered order.
The first individual To address is also the primary name/email shown in the
recipient row. Use the page's **Save** to persist the campaign draft. A rejected
page save keeps the reordered draft for an explicit retry. **Cancel** in the
dialog discards only its unconfirmed changes.
### Permit Legacy ZipCrypto as an explicit compatibility exception
AES remains the secure default. Campaign **Settings**, **Policies**, and
**Attachments** expose the effective archive policy, configuration links for
authorized administrators, and **Reload archive policy**.
1. A policy administrator opens **Administration → SYSTEM → Campaign archive
encryption**, enables **Legacy ZipCrypto**, and saves. The controls work
before the first system override exists; opening defaults alone does not
create an override or unsaved changes. Changing this global ceiling requires
both `system:settings:write` and `admin:policies:write`; tenant policy
authority alone cannot loosen it.
2. Check tenant and owner policy restrictions. Lower scopes may narrow, never
loosen, inherited methods and password-delivery channels.
3. The Campaign actor also needs `campaigns:archive:use_legacy_zipcrypto` and
edit access to the selected version. Policy administration does not replace
that dedicated permission.
4. Return to Campaign, reload archive policy, and select **Legacy ZipCrypto**
under **Attachments → ZIP attachments**. Acknowledge weak encryption, enter
an operational reason of at least 10 characters, and select an allowed
separate password-delivery channel.
5. Save, validate, build, and review. Policy/configuration saves never send
mail; delivery remains a separate action.
Legacy remains blocked while Policy is unavailable. Neither an encryption
error nor an incompatible client causes automatic fallback from AES to
ZipCrypto. The build retains policy and acknowledgement evidence but never the
password; see the manifest topic `campaigns.archive-encryption-governance`.
Mail migration and ZIP corrections can be saved in either order. A Mail-only
migration preserves unchanged ZIP settings without granting permission to use
them or adding acknowledgement evidence. An archive correction with unchanged
Mail references preserves legacy transport server-side until its separate,
explicit migration. Changes to ZIP settings still require the current policy
and any dedicated legacy permission; changes to Mail references still require
authorized migration. Validate, build and review again after both repairs.
A save and the following workspace refresh are separate operations. A failed
refresh does not undo a committed save or clear the last usable workspace.
Keep any newer unsaved edits, inspect the refresh error, and use Reload to fetch
the current state. Responses for an earlier campaign, version or signed-in
identity cannot overwrite the current workspace.
### Review and complete review
The reviewer should verify the immutable candidate that will be delivered, not
just the authoring form:
If a legacy Mail migration notice appears, follow **Open Mail settings** for
that exact version, complete migration, and validate and build again. Review
stays read-only until migration is resolved and does not repeatedly request
an attachment preview that the legacy transport boundary must reject.
1. Confirm purpose, owner, selected version, and recipient count.
2. Inspect blocking errors, warnings, exclusions, and recipients requiring
review.
@@ -223,6 +294,67 @@ the required action, the responsible role, and the workspace to open. The
review summary keeps reviewed and remaining counts visible; a completed review
acknowledges the group items and remains bound to the current build token.
Save each individual acceptance to persist its reason and reviewed state before
completing the entire review. Wait for acknowledgement; reloading then resumes
that build's saved progress. If saving fails or conflicts, the pending note
remains available for an explicit retry rather than becoming a false success.
This small save only loads the selected persisted jobs: it does not rebuild
messages, materialize attachments, or reload the whole workspace. Another
reviewer's existing decisions and attribution remain intact. Partial progress
does not enable delivery; final completion still checks the complete build.
Use **Accept similar review conditions** to record the same decision for a
counted selection of currently loaded matching messages. The server defines
eligible categories from the complete combination of overridable conditions;
the UI does not interpret a warning badge as permission to override. Select
one category, inspect the listed recipients, deselect any exceptions and enter
a common reason (required for attachment exceptions). A submission contains
at most 200 explicit message IDs. When more remain, save this selection and
reopen the dialog; the counts never imply acceptance of unloaded messages or
other categories. The reason is recorded separately against each selected
message's frozen evidence. A failed save retains the selection and reason for
an explicit retry, while a changed build prevents stale acceptance. This
action neither sends messages nor completes the final review gate. Hard
blockers cannot be accepted this way. Deliberate policy exclusions and
attachment rules that explicitly permit zero matches remain informational
and do not require review decisions.
An optional rule with explicit `missing_behavior: continue` may yield no files
without creating review work; that outcome remains informational evidence.
Required attachment and hard-block policies cannot be weakened by this setting.
The separate policy for sending a wholly attachment-free message still applies.
Rebuild existing messages after changing attachment policy; historical build
evidence is not rewritten.
The incremental review API uses `merge_progress: true`, the acknowledged
`base_revision`, and `build_token` set to the public `review_build_token`.
It merges exact reviewed keys/decision job IDs for the current build, with an
optional `decision_category_key` to bind a grouped acceptance. The safe review
reference is available without diagnostic access; raw build tokens remain
diagnostic data. Stale build/revision or simultaneous writes return HTTP 409
without overwriting progress. Normal review authorization and audit apply.
Accepted or expected attachment conditions remain satisfied in **Confirm and
send** for the same build. Raw missing/ambiguous source counts remain visible
for context; they are not a second approval gate. Reviewed-stage mock delivery
uses `use_reviewed_build: true`: it verifies the existing execution seal,
completed review, frozen job issues and EML integrity, and current Mail transport
before capturing anything in the mock mailbox. It uses those stored messages,
not freshly rendered replacements, and never mutates Campaign delivery state.
Stale review, changed inputs, changed bytes or changed transport stop the test
before captures or requested mailbox clearing. The authoring/mock-preview API
keeps its existing transient-build default; `include_needs_review` does not
bypass frozen review checks.
Validation details and repeated-file lists use the shared DataGrid pagination
controls so every item is reachable. Related missing-rule causes and their
attachment-free policy outcomes appear together, with the technical evidence
still expandable. Built messages have four operational states: **Ready**,
**Needs review**, **Blocked**, and **Excluded**, plus an explanatory column.
Accepted explicit decisions are Ready; warnings awaiting acknowledgment remain
Needs review. This presentation does not remove or rewrite frozen issues or
audit evidence.
This evidence is the Campaign input to separation-of-duties policy. Generic
approve/reject chains, delegation, substitutions, escalation, and signatures
belong to the optional Approvals capability. Campaign must not claim an
@@ -279,7 +411,7 @@ At a minimum:
ordinary batches; the durable progress remains visible after leaving and
returning to Review and send.
2. Use **Send now** only when the exact persisted eligible build is within the
effective synchronous limit shown by the UI. The default deployment limit
effective synchronous limit shown by the UI. The unchanged default limit
is 25 recipient jobs. The backend repeats the count and preflights every
message and the Mail profile revision before contacting SMTP.
3. Treat `smtp_accepted` as protected from ordinary retry.
@@ -302,10 +434,24 @@ Pause stops new eligible work but cannot undo a provider effect already in
progress. Cancel marks work that has not yet produced a protected SMTP outcome;
it cannot recall accepted mail.
The deployment ceiling is configured with
`GOVOPLAN_CAMPAIGN_SYNCHRONOUS_SEND_MAX_RECIPIENTS` (0 disables Send now; the
accepted range is 0500). A tenant may only narrow that ceiling with
`tenant.settings.campaign_delivery_policy.synchronous_send_max_recipients`.
Configure **Administration → SYSTEM → Campaign delivery** with
`system:settings:read/write`. The default stays 25, but an administrator may
explicitly choose 0500, for example 200 for a 183-recipient-job run. Zero disables
Send now. **TENANT → Campaign delivery** uses `admin:policies:read/write` and may
only narrow the inherited system policy. Clearing an override restores
inheritance. An explicitly configured deployment ceiling
`GOVOPLAN_CAMPAIGN_SYNCHRONOUS_SEND_MAX_RECIPIENTS` remains authoritative; the
implicit default does not prevent a system administrator choosing a larger
bounded value. Save changes only this setting, preserves unrelated settings,
checks a revision token including inherited policy, and records before/after
configuration history and audit. Failed saves retain the draft; conflicting
saves require explicit reload/reconciliation. Policy edits never send mail or
change existing reviews or approval requirements.
This limit applies to one interactive Send now request, not campaign size or
worker batching. Larger interactive requests run longer and can meet proxy
timeouts. Queue for workers is independent and requires enabled, healthy
Redis/Celery infrastructure; changing the numeric limit does not start workers.
The effective value and source are returned by the protected delivery-options
API, recorded for successful/rejected synchronous commands, and stated in the
configured handbook topic.
@@ -466,6 +612,73 @@ create an editable successor.
## Operations and recovery
### Interactive delivery and Sent-folder progress
Send now and workerless Report retry/continue use a compact blocking progress
dialog, as does inline append-to-Sent. It refreshes saved counters only, not the
whole campaign behind the overlay. Successful, pending, in-progress, failed,
uncertain and excluded messages are shown separately; a currently sending or
appending message therefore does not disappear between totals. Read errors keep
the last known counters. After a connection interruption, processing may still
be running; inspect saved evidence before repeating any action. A successful
write is not reclassified as failed when its later display refresh fails.
The recipient-aware Report shows all frozen To, Cc and Bcc addresses, not only
the primary row identity. Address order and recipient-read authorization remain
unchanged. SMTP/IMAP diagnostics use translated status-list filters.
Without workers, explicitly retry eligible failures or continue unattempted
jobs through Report. Each request uses the canonical job/attempt recovery
boundary and is limited by the effective synchronous policy. Accepted and
uncertain SMTP outcomes remain protected. Active abandoned claims require an
expired durable lease, proven stopped/replaced owner, current revision and
valid original evidence before recovery can mark them unknown. A separate
evidence-note reconciliation is required before retrying. A timeout alone is
never proof of non-delivery. See the delivery runbook for required permissions
and operational limitations.
Append-to-Sent is scoped to the selected version and reuses a bounded Mail-owned
connection/folder resolution (default 100 messages or 300 seconds), while
performing one sequential APPEND and all current checks per message. Uncertain
appends are never automatically repeated, and repairing Sent never resends SMTP.
Link required files before locking. Lock and validate waits for attachment
matches, rechecks them immediately before locking, and asks for Link and lock
confirmation if new unlinked files are found. A locked version cannot acquire
new attachment links; use an editable version and validate/build/review again.
### Fortschritt, Wiederherstellung und Dateiverknüpfungen
Jetzt senden, synchrone Wiederholung/Fortsetzung im Bericht und Kopieren nach
Gesendet verwenden einen kompakten sperrenden Fortschrittsdialog. Nur gespeicherte
Zähler werden aktualisiert, nicht der Arbeitsbereich im Hintergrund. Erfolgreich,
ausstehend, in Bearbeitung, fehlgeschlagen, ungewiss und ausgeschlossen bleiben
getrennt sichtbar. Bei Lesefehlern bleiben die letzten Werte erhalten. Nach einer
getrennten Verbindung kann die Verarbeitung weiterlaufen; prüfen Sie Nachweise,
bevor Sie erneut handeln. Ein bestätigter Versand wird durch einen nachfolgenden
Anzeigefehler nicht nachträglich als fehlgeschlagen dargestellt.
Der empfängerbezogene Bericht zeigt alle eingefrorenen An-, Cc- und Bcc-Adressen
in ihrer Reihenfolge. Leseberechtigungen bleiben unverändert. SMTP und IMAP
verwenden übersetzte Zustandslisten zum Filtern.
Ohne Worker können bekannte Fehler ausdrücklich wiederholt und unversuchte
Aufträge begrenzt fortgesetzt werden. Die wirksame synchrone Grenze, gespeicherte
Aufträge, Prüfungen, Freigaben und Wiederherstellungsnachweise bleiben verbindlich.
Angenommene und ungewisse SMTP-Ergebnisse werden nicht blind wiederholt. Die
Wiederherstellung aktiver, verlassener Aufträge benötigt eine abgelaufene Sperre,
nachweislich gestoppte/ersetzte Laufzeit und gültige ursprüngliche Nachweise. Sie
setzt ausschließlich auf ungewiss; vor Wiederholung sind externe Nachweise und
ein getrennter Abgleich mit Notiz erforderlich. Zeitablauf allein genügt nicht.
Kopieren nach Gesendet betrifft nur die ausgewählte Version. Mail verwendet die
Verbindung und Ordnerauflösung begrenzt wieder (Standard: 100 Nachrichten oder
300 Sekunden), prüft aber jede Nachricht erneut und führt APPEND nacheinander
aus. Ungewisse Ergebnisse werden nicht automatisch wiederholt. Verknüpfen Sie
benötigte Dateien vor dem Sperren; eine frische Prüfung fragt bei unverknüpften
Treffern nach Verknüpfen und sperren. Gesperrte Versionen benötigen zum Ändern
eine bearbeitbare Version mit erneuter Validierung, Build und Prüfung.
### Health to observe
- database and migration health;
+60 -1
View File
@@ -68,7 +68,9 @@ material and does not delete or rewrite the stored audit rows automatically:
`mail_profile_migration_required` marker.
- validation, build, queue, retry, and delivery fail closed with an actionable
profile-migration error;
- unrelated edits cannot silently scrub the legacy fields;
- unrelated edits preserve the exact stored legacy server object when the
submitted public Mail references are unchanged; they cannot silently scrub,
edit, or re-submit legacy fields or credentials;
- an editable version is migrated only through an explicit Mail-settings save
with an authorized profile; and
- a locked version remains unchanged. Creating its editable successor records
@@ -81,6 +83,63 @@ database as a whole; the product does not define a separate historical-JSON or
inline-secret recovery workflow. A restored legacy row remains inert and
fail-closed under the same rules.
### Migrate from the Campaign UI
Open **Mail settings** from the migration notice for the selected version.
Select an authorized Mail profile and choose **Migrate selected Mail profile**.
The migration action is available even when that profile was already selected
and the draft has no other unsaved changes. If the version is locked, first use
its supported unlock or editable-successor action; protected source evidence is
not rewritten. Reopen the settings and confirm that the migration notice has
gone, then validate, build, and review before separately authorizing delivery.
Migration itself never sends mail.
Mail migration and ZIP policy repairs can be saved in either order. An exact,
unchanged ZIP configuration does not require a new acknowledgement merely to
save a Mail migration, even if the existing ZIP policy or actor's permission is
no longer valid. No actor, timestamp, or consent is invented. Any ZIP change
still requires the complete current archive policy and, for ZipCrypto, the
dedicated permission and reasoned acknowledgement. Conversely, saving an
archive correction with unchanged public Mail references retains the exact
legacy transport server-side, without using or reauthorizing the old profile.
The migration notice remains until the explicit authorized migration succeeds.
Changing any Mail profile, server, or credential reference is not an unrelated
repair. Inline transport is rejected even if a caller echoes stored values.
The same edit-time separation applies after migration: retaining an unchanged
Mail selection does not invoke use-policy while saving an unrelated correction,
even if a later policy requires an explicit credential. Selecting a new resource
or explicitly migrating still checks Mail permission and current policy; actual
validation and delivery always recheck them, regardless of save history.
Both repair orders invalidate build/execution evidence; validation, build,
review and delivery remain blocked until all outstanding conditions are valid.
Successful saves and subsequent refreshes are separate outcomes. A committed
save is not undone by a failed workspace refresh. The workspace retains its
last usable same-campaign/version data and displays the refresh error; retry
Reload to fetch the current server state. Obsolete responses from an earlier
campaign, version, signed-in identity, or reload cannot replace newer data.
The normal profile selector requests only campaign-authorized profiles. The
administrative profile catalogue is requested separately on **Mail policy**;
failure or lack of authority there does not empty the usable profile selector.
Profile-list errors remain visible next to the affected settings.
While migration is required, **Review and send** remains read-only and links to
the exact version's Mail settings. It does not repeatedly attempt incompatible
attachment-preview requests. These UI affordances do not relax backend
validation, build, queue, retry, or delivery enforcement.
### Editor metadata and trusted evidence
Read responses may contain server-owned `review_send` and `approval_gate`
evidence. Ordinary version mutations send only client-owned `created_from`,
`field_overrides`, and `opt_ins` editor metadata. The client omits review and
approval evidence; the server rejects attempts to write it through an editor
mutation and preserves its existing trusted value during metadata updates.
Supported unlock, successor-version, and build invalidation rules still remove
stale evidence when required. Opening or leaving the Template editor must not
require manually deleting server review metadata.
## Operator checks
Before live delivery, confirm that: