harden(mail): address PR #935 round-5 review

No blockers raised; residual cleanup:
- processor.py: guard int(doc_task.doc_id) with is_valid_nextcloud_doc_id in
  the mail_message branch (consistent with search/context.py + the verifier).
- mail metadata symmetry: store `bcc` in file_metadata and the Qdrant payload
  alongside cc (build_mail_content already emits a Bcc: line).
- server/mail.py: extract _cap_attachment_content helper (byte-accurate cap)
  and unit-test it (small/None/oversized/multibyte).
- client/mail.py: give the synthetic OCS-error Response an explicit empty body;
  add a test that a traversal-style attachment_id is percent-encoded.
- models/mail.py: clarify ListMessagesResponse.total_count is the page count,
  not the mailbox total.

Deferred (Deck #376): _potentially_deleted doc_type-in-key. It's pre-existing
and spans ~30 sites across the notes/news/deck/file/mail scanners (whose
deletion paths have no unit coverage), so it belongs in its own focused PR
rather than expanding this mail PR's blast radius into other doc types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-20 13:24:24 +02:00
co-authored by Claude Opus 4.8
parent 0856d59956
commit d006145444
6 changed files with 91 additions and 11 deletions
+5 -1
View File
@@ -162,7 +162,11 @@ class ListMessagesResponse(BaseResponse):
"""Response model for listing message envelopes."""
results: list[MailMessageSummary] = Field(description="List of message summaries")
total_count: int = Field(description="Number of messages returned")
total_count: int = Field(
description="Number of messages returned in this page (NOT the mailbox "
"total, which isn't known without a full scan); page with cursor and "
"stop on an empty result"
)
has_more: bool = Field(False, description="Whether more messages may exist")