mailsec: --reason on message bulk-action (+ SDK parameter) - #354
Merged
Conversation
…ind it Single-message remediation has always taken a reason; the bulk route did not, because the API gateway's execute forwarder dropped the field. With the backend change that reads and records it, the client can offer it. - Mailsec.bulk_action_execute takes an optional `reason`, sent only when given. - `mailsec message bulk-action --reason` passes it on the EXECUTE. - The preview deliberately takes no reason: it mints the confirmation, which the backend derives from (oid, action, attempt, members), and a reason that reached it could enter that derivation — rewording a justification would then invalidate a selection somebody had already approved and start a second job over the same messages. Passing --reason without --confirm therefore prints a note rather than dropping it silently. The SDK docstring's "there is deliberately no reason" note is replaced with an accurate one: it requires a backend that reads the field, and a client cannot detect an older one from the response, which answers 200 either way. Requires the backend changes to be deployed first; without them the reason is accepted here and discarded in transit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lcbill
approved these changes
Sep 2, 2026
maximelb
marked this pull request as ready for review
September 2, 2026 16:25
maximelb
enabled auto-merge (squash)
September 2, 2026 16:25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why the SDK said there was no reason
Mailsec.bulk_action_execute's docstring carried this note:That was the right call against the backend as it stood. The backend changes it: the gateway
now forwards
reason, and the collector records it on the job's audit row and on everymessage's — the same record
act_on_message(..., reason=...)produces for one message.The change
Mailsec.bulk_action_execute(..., reason=None), sent only when supplied (the sameomit-when-absent discipline every other optional on these routes uses).
limacharlie mailsec message bulk-action --reason, passed on the execute.reasonneeds a backend that readsit, and a client cannot detect an older one from the response — the execute answers 200
either way, which is exactly why the old note existed.
The preview still takes no reason, on purpose
The preview mints the confirmation, and the backend derives that token from
(oid, action, attempt, members). A reason that reached the preview could end up in thederivation, and then rewording a justification would invalidate a selection somebody had
already approved — and mint a second bulk id, i.e. a second job over the same 500
messages instead of the idempotent adoption the design guarantees.
So
--reasonwithout--confirmprints a note saying it applies to the execute, ratherthan accepting it quietly. A justification silently discarded is worse than a refused one,
because the caller believes it was recorded — which is the entire failure this PR exists to
end, and it would be embarrassing to reintroduce it one layer up.
Tests
tests/unit/test_sdk_mailsec.pytest_execute_carries_the_operator_reasontest_the_preview_takes_no_reason— the structural half, asserted as aTypeError.tests/unit/test_cli_mailsec_bulk.pytest_the_reason_reaches_the_executetest_a_reason_on_a_preview_is_said_out_loud_not_dropped, which also asserts the reasondid not travel on the preview call.
pytest tests/unit/→ 3996 passed, 6 skipped (the same 6 skips asorigin/master; myfour are the delta from 3992).
Not included
No
CHANGELOG.mdentry and no version bump: the changelog here is organized per releasedversion with no Unreleased section, and choosing the version this lands in is a release
decision rather than part of the change.