polish(mail): consistency guards (PR #935 round-7)
Non-blocking consistency fixes: - scanner.py: skip re-queuing a mail_message whose placeholder status is "failed" (mirrors the file scanner's permanent-failure guard); the modified_at branch still retries once the message changes. - search/context.py: return None on an empty get_message payload during context expansion, mirroring the processor's index-time empty-payload guard. - tests: cover the non-numeric mailbox_id fail-open branch in the verifier. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
10e768e414
commit
15042c1b1b
@@ -834,6 +834,10 @@ async def _fetch_document_text(
|
|||||||
# Reconstruct full content via the shared helper so chunk offsets
|
# Reconstruct full content via the shared helper so chunk offsets
|
||||||
# match what the processor indexed (single source of truth).
|
# match what the processor indexed (single source of truth).
|
||||||
message = await nc_client.mail.get_message(int(doc_id))
|
message = await nc_client.mail.get_message(int(doc_id))
|
||||||
|
# Empty payload (OCS data=null with a <400 meta) -> skip context
|
||||||
|
# expansion, mirroring the processor's index-time guard.
|
||||||
|
if not message:
|
||||||
|
return None
|
||||||
return build_mail_content(message)
|
return build_mail_content(message)
|
||||||
else:
|
else:
|
||||||
logger.warning("Unsupported doc_type for context expansion: %s", doc_type)
|
logger.warning("Unsupported doc_type for context expansion: %s", doc_type)
|
||||||
|
|||||||
@@ -1549,6 +1549,16 @@ async def scan_mail_messages(
|
|||||||
needs_indexing = True
|
needs_indexing = True
|
||||||
elif existing_metadata.get("modified_at", 0) < modified_at:
|
elif existing_metadata.get("modified_at", 0) < modified_at:
|
||||||
needs_indexing = True
|
needs_indexing = True
|
||||||
|
elif existing_metadata.get("status") == "failed":
|
||||||
|
# A permanent processing failure — don't re-queue an
|
||||||
|
# unchanged message that will just fail again; the
|
||||||
|
# modified_at branch above retries once it changes
|
||||||
|
# (mirrors the file scanner's failed-placeholder guard).
|
||||||
|
logger.debug(
|
||||||
|
"Skipping mail message %s: previous processing "
|
||||||
|
"failed permanently",
|
||||||
|
doc_id,
|
||||||
|
)
|
||||||
elif existing_metadata.get("is_placeholder", False):
|
elif existing_metadata.get("is_placeholder", False):
|
||||||
queued_at = existing_metadata.get("queued_at", 0)
|
queued_at = existing_metadata.get("queued_at", 0)
|
||||||
placeholder_age = time.time() - queued_at
|
placeholder_age = time.time() - queued_at
|
||||||
|
|||||||
@@ -303,6 +303,22 @@ async def test_verify_mail_missing_mailbox_id_keeps(mocker):
|
|||||||
list_messages.assert_not_awaited()
|
list_messages.assert_not_awaited()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
async def test_verify_mail_non_numeric_mailbox_id_keeps(mocker):
|
||||||
|
"""A non-numeric mailbox_id in metadata is kept without a list call."""
|
||||||
|
list_messages = mocker.AsyncMock()
|
||||||
|
mail_client = SimpleNamespace(list_messages=list_messages)
|
||||||
|
client = SimpleNamespace(mail=mail_client, username="alice")
|
||||||
|
|
||||||
|
result = await _verify_mail_messages(
|
||||||
|
client,
|
||||||
|
[_make_result(42, doc_type="mail_message", metadata={"mailbox_id": "bad"})],
|
||||||
|
_sem(),
|
||||||
|
)
|
||||||
|
assert result == {"42"}
|
||||||
|
list_messages.assert_not_awaited()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
async def test_verify_mail_non_numeric_id_kept_when_mailbox_listed(mocker):
|
async def test_verify_mail_non_numeric_id_kept_when_mailbox_listed(mocker):
|
||||||
"""A malformed doc_id can't match the numeric listing, so it's kept."""
|
"""A malformed doc_id can't match the numeric listing, so it's kept."""
|
||||||
|
|||||||
Reference in New Issue
Block a user